111
|
1 /* A simple stack-based virtual machine to demonstrate
|
|
2 JIT-compilation.
|
131
|
3 Copyright (C) 2014-2018 Free Software Foundation, Inc.
|
111
|
4
|
|
5 This file is part of GCC.
|
|
6
|
|
7 GCC is free software; you can redistribute it and/or modify it
|
|
8 under the terms of the GNU General Public License as published by
|
|
9 the Free Software Foundation; either version 3, or (at your option)
|
|
10 any later version.
|
|
11
|
|
12 GCC is distributed in the hope that it will be useful, but
|
|
13 WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
15 General Public License for more details.
|
|
16
|
|
17 You should have received a copy of the GNU General Public License
|
|
18 along with GCC; see the file COPYING3. If not see
|
|
19 <http://www.gnu.org/licenses/>. */
|
|
20
|
|
21 #include <assert.h>
|
|
22 #include <errno.h>
|
|
23 #include <stdio.h>
|
|
24 #include <stdlib.h>
|
|
25 #include <string.h>
|
|
26
|
|
27 #include <dejagnu.h>
|
|
28
|
|
29 #include <libgccjit++.h>
|
|
30
|
|
31 /* Wrapper around a gcc_jit_result *. */
|
|
32
|
|
33 class compilation_result
|
|
34 {
|
|
35 public:
|
|
36 compilation_result (gcc_jit_result *result) :
|
|
37 m_result (result)
|
|
38 {
|
|
39 }
|
|
40 ~compilation_result ()
|
|
41 {
|
|
42 gcc_jit_result_release (m_result);
|
|
43 }
|
|
44
|
|
45 void *get_code (const char *funcname)
|
|
46 {
|
|
47 return gcc_jit_result_get_code (m_result, funcname);
|
|
48 }
|
|
49
|
|
50 private:
|
|
51 gcc_jit_result *m_result;
|
|
52 };
|
|
53
|
|
54 /* Functions are compiled to this function ptr type. */
|
|
55 typedef int (*toyvm_compiled_func) (int);
|
|
56
|
|
57 enum opcode {
|
|
58 /* Ops taking no operand. */
|
|
59 DUP,
|
|
60 ROT,
|
|
61 BINARY_ADD,
|
|
62 BINARY_SUBTRACT,
|
|
63 BINARY_MULT,
|
|
64 BINARY_COMPARE_LT,
|
|
65 RECURSE,
|
|
66 RETURN,
|
|
67
|
|
68 /* Ops taking an operand. */
|
|
69 PUSH_CONST,
|
|
70 JUMP_ABS_IF_TRUE
|
|
71 };
|
|
72
|
|
73 #define FIRST_UNARY_OPCODE (PUSH_CONST)
|
|
74
|
|
75 const char * const opcode_names[] = {
|
|
76 "DUP",
|
|
77 "ROT",
|
|
78 "BINARY_ADD",
|
|
79 "BINARY_SUBTRACT",
|
|
80 "BINARY_MULT",
|
|
81 "BINARY_COMPARE_LT",
|
|
82 "RECURSE",
|
|
83 "RETURN",
|
|
84
|
|
85 "PUSH_CONST",
|
|
86 "JUMP_ABS_IF_TRUE",
|
|
87 };
|
|
88
|
|
89 struct toyvm_op
|
|
90 {
|
|
91 /* Which operation. */
|
|
92 enum opcode op_opcode;
|
|
93
|
|
94 /* Some opcodes take an argument. */
|
|
95 int op_operand;
|
|
96
|
|
97 /* The line number of the operation within the source file. */
|
|
98 int op_linenum;
|
|
99 };
|
|
100
|
|
101 #define MAX_OPS (64)
|
|
102
|
|
103 class toyvm_function
|
|
104 {
|
|
105 public:
|
|
106 void
|
|
107 add_op (enum opcode opcode,
|
|
108 int operand, int linenum);
|
|
109
|
|
110 void
|
|
111 add_unary_op (enum opcode opcode,
|
|
112 const char *rest_of_line, int linenum);
|
|
113
|
|
114 static toyvm_function *
|
|
115 parse (const char *filename, const char *name);
|
|
116
|
|
117 void
|
|
118 disassemble_op (toyvm_op *op, int index, FILE *out);
|
|
119
|
|
120 void
|
|
121 disassemble (FILE *out);
|
|
122
|
|
123 int
|
|
124 interpret (int arg, FILE *trace);
|
|
125
|
|
126 compilation_result
|
|
127 compile ();
|
|
128
|
|
129 const char *
|
|
130 get_function_name () const { return m_funcname; }
|
|
131
|
|
132 private:
|
|
133 void
|
|
134 make_function_name (const char *filename);
|
|
135
|
|
136 private:
|
|
137 const char *fn_filename;
|
|
138 char *m_funcname;
|
|
139 int fn_num_ops;
|
|
140 toyvm_op fn_ops[MAX_OPS];
|
|
141 friend struct compilation_state;
|
|
142 };
|
|
143
|
|
144 #define MAX_STACK_DEPTH (8)
|
|
145
|
|
146 class toyvm_frame
|
|
147 {
|
|
148 public:
|
|
149 void push (int arg);
|
|
150 int pop ();
|
|
151 void dump_stack (FILE *out);
|
|
152
|
|
153 private:
|
|
154 toyvm_function *frm_function;
|
|
155 int frm_pc;
|
|
156 int frm_stack[MAX_STACK_DEPTH];
|
|
157 int frm_cur_depth;
|
|
158
|
|
159 friend int toyvm_function::interpret (int arg, FILE *trace);
|
|
160
|
|
161 };
|
|
162
|
|
163 void
|
|
164 toyvm_function::add_op (enum opcode opcode,
|
|
165 int operand, int linenum)
|
|
166 {
|
|
167 toyvm_op *op;
|
|
168 assert (fn_num_ops < MAX_OPS);
|
|
169 op = &fn_ops[fn_num_ops++];
|
|
170 op->op_opcode = opcode;
|
|
171 op->op_operand = operand;
|
|
172 op->op_linenum = linenum;
|
|
173 }
|
|
174
|
|
175 void
|
|
176 toyvm_function::add_unary_op (enum opcode opcode,
|
|
177 const char *rest_of_line, int linenum)
|
|
178 {
|
|
179 int operand = atoi (rest_of_line);
|
|
180 add_op (opcode, operand, linenum);
|
|
181 }
|
|
182
|
|
183 void
|
|
184 toyvm_function::make_function_name (const char *filename)
|
|
185 {
|
|
186 /* Skip any path separators. */
|
|
187 const char *pathsep = strrchr (filename, '/');
|
|
188 if (pathsep)
|
|
189 filename = pathsep + 1;
|
|
190
|
|
191 /* Copy filename to funcname. */
|
|
192 m_funcname = (char *)malloc (strlen (filename) + 1);
|
|
193
|
|
194 strcpy (m_funcname, filename);
|
|
195
|
|
196 /* Convert "." to NIL terminator. */
|
|
197 *(strchr (m_funcname, '.')) = '\0';
|
|
198 }
|
|
199
|
|
200 toyvm_function *
|
|
201 toyvm_function::parse (const char *filename, const char *name)
|
|
202 {
|
|
203 FILE *f = NULL;
|
|
204 toyvm_function *fn = NULL;
|
|
205 char *line = NULL;
|
|
206 ssize_t linelen;
|
|
207 size_t bufsize;
|
|
208 int linenum = 0;
|
|
209
|
|
210 assert (filename);
|
|
211 assert (name);
|
|
212
|
|
213 f = fopen (filename, "r");
|
|
214 if (!f)
|
|
215 {
|
|
216 fprintf (stderr,
|
|
217 "cannot open file %s: %s\n",
|
|
218 filename, strerror (errno));
|
|
219 goto error;
|
|
220 }
|
|
221
|
|
222 fn = (toyvm_function *)calloc (1, sizeof (toyvm_function));
|
|
223 if (!fn)
|
|
224 {
|
|
225 fprintf (stderr, "out of memory allocating toyvm_function\n");
|
|
226 goto error;
|
|
227 }
|
|
228 fn->fn_filename = filename;
|
|
229 fn->make_function_name (filename);
|
|
230
|
|
231 /* Read the lines of the file. */
|
|
232 while ((linelen = getline (&line, &bufsize, f)) != -1)
|
|
233 {
|
|
234 /* Note that this is a terrible parser, but it avoids the need to
|
|
235 bring in lex/yacc as a dependency. */
|
|
236 linenum++;
|
|
237
|
|
238 if (0)
|
|
239 fprintf (stdout, "%3d: %s", linenum, line);
|
|
240
|
|
241 /* Lines beginning with # are comments. */
|
|
242 if (line[0] == '#')
|
|
243 continue;
|
|
244
|
|
245 /* Skip blank lines. */
|
|
246 if (line[0] == '\n')
|
|
247 continue;
|
|
248
|
|
249 #define LINE_MATCHES(OPCODE) (0 == strncmp ((OPCODE), line, strlen (OPCODE)))
|
|
250 if (LINE_MATCHES ("DUP\n"))
|
|
251 fn->add_op (DUP, 0, linenum);
|
|
252 else if (LINE_MATCHES ("ROT\n"))
|
|
253 fn->add_op (ROT, 0, linenum);
|
|
254 else if (LINE_MATCHES ("BINARY_ADD\n"))
|
|
255 fn->add_op (BINARY_ADD, 0, linenum);
|
|
256 else if (LINE_MATCHES ("BINARY_SUBTRACT\n"))
|
|
257 fn->add_op (BINARY_SUBTRACT, 0, linenum);
|
|
258 else if (LINE_MATCHES ("BINARY_MULT\n"))
|
|
259 fn->add_op (BINARY_MULT, 0, linenum);
|
|
260 else if (LINE_MATCHES ("BINARY_COMPARE_LT\n"))
|
|
261 fn->add_op (BINARY_COMPARE_LT, 0, linenum);
|
|
262 else if (LINE_MATCHES ("RECURSE\n"))
|
|
263 fn->add_op (RECURSE, 0, linenum);
|
|
264 else if (LINE_MATCHES ("RETURN\n"))
|
|
265 fn->add_op (RETURN, 0, linenum);
|
|
266 else if (LINE_MATCHES ("PUSH_CONST "))
|
|
267 fn->add_unary_op (PUSH_CONST,
|
|
268 line + strlen ("PUSH_CONST "), linenum);
|
|
269 else if (LINE_MATCHES ("JUMP_ABS_IF_TRUE "))
|
|
270 fn->add_unary_op (JUMP_ABS_IF_TRUE,
|
|
271 line + strlen("JUMP_ABS_IF_TRUE "), linenum);
|
|
272 else
|
|
273 {
|
|
274 fprintf (stderr, "%s:%d: parse error\n", filename, linenum);
|
|
275 free (fn);
|
|
276 fn = NULL;
|
|
277 goto error;
|
|
278 }
|
|
279 #undef LINE_MATCHES
|
|
280 }
|
|
281 free (line);
|
|
282 fclose (f);
|
|
283
|
|
284 return fn;
|
|
285
|
|
286 error:
|
|
287 free (line);
|
|
288 if (f)
|
|
289 fclose (f);
|
|
290 free (fn);
|
|
291 return NULL;
|
|
292 }
|
|
293
|
|
294 void
|
|
295 toyvm_function::disassemble_op (toyvm_op *op, int index, FILE *out)
|
|
296 {
|
|
297 fprintf (out, "%s:%d: index %d: %s",
|
|
298 fn_filename, op->op_linenum, index,
|
|
299 opcode_names[op->op_opcode]);
|
|
300 if (op->op_opcode >= FIRST_UNARY_OPCODE)
|
|
301 fprintf (out, " %d", op->op_operand);
|
|
302 fprintf (out, "\n");
|
|
303 }
|
|
304
|
|
305 void
|
|
306 toyvm_function::disassemble (FILE *out)
|
|
307 {
|
|
308 int i;
|
|
309 for (i = 0; i < fn_num_ops; i++)
|
|
310 {
|
|
311 toyvm_op *op = &fn_ops[i];
|
|
312 disassemble_op (op, i, out);
|
|
313 }
|
|
314 }
|
|
315
|
|
316 void
|
|
317 toyvm_frame::push (int arg)
|
|
318 {
|
|
319 assert (frm_cur_depth < MAX_STACK_DEPTH);
|
|
320 frm_stack[frm_cur_depth++] = arg;
|
|
321 }
|
|
322
|
|
323 int
|
|
324 toyvm_frame::pop ()
|
|
325 {
|
|
326 assert (frm_cur_depth > 0);
|
|
327 return frm_stack[--frm_cur_depth];
|
|
328 }
|
|
329
|
|
330 void
|
|
331 toyvm_frame::dump_stack (FILE *out)
|
|
332 {
|
|
333 int i;
|
|
334 fprintf (out, "stack:");
|
|
335 for (i = 0; i < frm_cur_depth; i++)
|
|
336 {
|
|
337 fprintf (out, " %d", frm_stack[i]);
|
|
338 }
|
|
339 fprintf (out, "\n");
|
|
340 }
|
|
341
|
|
342 /* Execute the given function. */
|
|
343
|
|
344 int
|
|
345 toyvm_function::interpret (int arg, FILE *trace)
|
|
346 {
|
|
347 toyvm_frame frame;
|
|
348 #define PUSH(ARG) (frame.push (ARG))
|
|
349 #define POP(ARG) (frame.pop ())
|
|
350
|
|
351 frame.frm_function = this;
|
|
352 frame.frm_pc = 0;
|
|
353 frame.frm_cur_depth = 0;
|
|
354
|
|
355 PUSH (arg);
|
|
356
|
|
357 while (1)
|
|
358 {
|
|
359 toyvm_op *op;
|
|
360 int x, y;
|
|
361 assert (frame.frm_pc < fn_num_ops);
|
|
362 op = &fn_ops[frame.frm_pc++];
|
|
363
|
|
364 if (trace)
|
|
365 {
|
|
366 frame.dump_stack (trace);
|
|
367 disassemble_op (op, frame.frm_pc, trace);
|
|
368 }
|
|
369
|
|
370 switch (op->op_opcode)
|
|
371 {
|
|
372 /* Ops taking no operand. */
|
|
373 case DUP:
|
|
374 x = POP ();
|
|
375 PUSH (x);
|
|
376 PUSH (x);
|
|
377 break;
|
|
378
|
|
379 case ROT:
|
|
380 y = POP ();
|
|
381 x = POP ();
|
|
382 PUSH (y);
|
|
383 PUSH (x);
|
|
384 break;
|
|
385
|
|
386 case BINARY_ADD:
|
|
387 y = POP ();
|
|
388 x = POP ();
|
|
389 PUSH (x + y);
|
|
390 break;
|
|
391
|
|
392 case BINARY_SUBTRACT:
|
|
393 y = POP ();
|
|
394 x = POP ();
|
|
395 PUSH (x - y);
|
|
396 break;
|
|
397
|
|
398 case BINARY_MULT:
|
|
399 y = POP ();
|
|
400 x = POP ();
|
|
401 PUSH (x * y);
|
|
402 break;
|
|
403
|
|
404 case BINARY_COMPARE_LT:
|
|
405 y = POP ();
|
|
406 x = POP ();
|
|
407 PUSH (x < y);
|
|
408 break;
|
|
409
|
|
410 case RECURSE:
|
|
411 x = POP ();
|
|
412 x = interpret (x, trace);
|
|
413 PUSH (x);
|
|
414 break;
|
|
415
|
|
416 case RETURN:
|
|
417 return POP ();
|
|
418
|
|
419 /* Ops taking an operand. */
|
|
420 case PUSH_CONST:
|
|
421 PUSH (op->op_operand);
|
|
422 break;
|
|
423
|
|
424 case JUMP_ABS_IF_TRUE:
|
|
425 x = POP ();
|
|
426 if (x)
|
|
427 frame.frm_pc = op->op_operand;
|
|
428 break;
|
|
429
|
|
430 default:
|
|
431 assert (0); /* unknown opcode */
|
|
432
|
|
433 } /* end of switch on opcode */
|
|
434 } /* end of while loop */
|
|
435
|
|
436 #undef PUSH
|
|
437 #undef POP
|
|
438 }
|
|
439
|
|
440 /* JIT compilation. */
|
|
441
|
|
442 class compilation_state
|
|
443 {
|
|
444 public:
|
|
445 compilation_state (toyvm_function &toyvmfn) :
|
|
446 toyvmfn (toyvmfn)
|
|
447 {}
|
|
448
|
|
449 void create_context ();
|
|
450 void create_types ();
|
|
451 void create_locations ();
|
|
452 void create_function (const char *funcname);
|
|
453 compilation_result compile ();
|
|
454
|
|
455 private:
|
|
456 void
|
|
457 add_push (gccjit::block block,
|
|
458 gccjit::rvalue rvalue,
|
|
459 gccjit::location loc);
|
|
460
|
|
461 void
|
|
462 add_pop (gccjit::block block,
|
|
463 gccjit::lvalue lvalue,
|
|
464 gccjit::location loc);
|
|
465
|
|
466 private:
|
|
467
|
|
468 /* State. */
|
|
469
|
|
470 toyvm_function &toyvmfn;
|
|
471
|
|
472 gccjit::context ctxt;
|
|
473
|
|
474 gccjit::type int_type;
|
|
475 gccjit::type bool_type;
|
|
476 gccjit::type stack_type; /* int[MAX_STACK_DEPTH] */
|
|
477
|
|
478 gccjit::rvalue const_one;
|
|
479
|
|
480 gccjit::function fn;
|
|
481 gccjit::param param_arg;
|
|
482 gccjit::lvalue stack;
|
|
483 gccjit::lvalue stack_depth;
|
|
484 gccjit::lvalue x;
|
|
485 gccjit::lvalue y;
|
|
486
|
|
487 gccjit::location op_locs[MAX_OPS];
|
|
488 gccjit::block initial_block;
|
|
489 gccjit::block op_blocks[MAX_OPS];
|
|
490
|
|
491 };
|
|
492
|
|
493 /* The main compilation hook. */
|
|
494
|
|
495 compilation_result
|
|
496 toyvm_function::compile ()
|
|
497 {
|
|
498 compilation_state state (*this);
|
|
499
|
|
500 state.create_context ();
|
|
501 state.create_types ();
|
|
502 state.create_locations ();
|
|
503 state.create_function (get_function_name ());
|
|
504
|
|
505 /* We've now finished populating the context. Compile it. */
|
|
506 return state.compile ();
|
|
507 }
|
|
508
|
|
509 /* Stack manipulation. */
|
|
510
|
|
511 void
|
|
512 compilation_state::add_push (gccjit::block block,
|
|
513 gccjit::rvalue rvalue,
|
|
514 gccjit::location loc)
|
|
515 {
|
|
516 /* stack[stack_depth] = RVALUE */
|
|
517 block.add_assignment (
|
|
518 /* stack[stack_depth] */
|
|
519 ctxt.new_array_access (
|
|
520 stack,
|
|
521 stack_depth,
|
|
522 loc),
|
|
523 rvalue,
|
|
524 loc);
|
|
525
|
|
526 /* "stack_depth++;". */
|
|
527 block.add_assignment_op (
|
|
528 stack_depth,
|
|
529 GCC_JIT_BINARY_OP_PLUS,
|
|
530 const_one,
|
|
531 loc);
|
|
532 }
|
|
533
|
|
534 void
|
|
535 compilation_state::add_pop (gccjit::block block,
|
|
536 gccjit::lvalue lvalue,
|
|
537 gccjit::location loc)
|
|
538 {
|
|
539 /* "--stack_depth;". */
|
|
540 block.add_assignment_op (
|
|
541 stack_depth,
|
|
542 GCC_JIT_BINARY_OP_MINUS,
|
|
543 const_one,
|
|
544 loc);
|
|
545
|
|
546 /* "LVALUE = stack[stack_depth];". */
|
|
547 block.add_assignment (
|
|
548 lvalue,
|
|
549 /* stack[stack_depth] */
|
|
550 ctxt.new_array_access (stack,
|
|
551 stack_depth,
|
|
552 loc),
|
|
553 loc);
|
|
554 }
|
|
555
|
|
556 /* Create the context. */
|
|
557
|
|
558 void
|
|
559 compilation_state::create_context ()
|
|
560 {
|
|
561 ctxt = gccjit::context::acquire ();
|
|
562
|
|
563 ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
|
|
564 0);
|
|
565 ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
|
|
566 0);
|
|
567 ctxt.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
|
|
568 3);
|
|
569 ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES,
|
|
570 0);
|
|
571 ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING,
|
|
572 0);
|
|
573 ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DEBUGINFO,
|
|
574 1);
|
|
575 }
|
|
576
|
|
577 /* Create types. */
|
|
578
|
|
579 void
|
|
580 compilation_state::create_types ()
|
|
581 {
|
|
582 /* Create types. */
|
|
583 int_type = ctxt.get_type (GCC_JIT_TYPE_INT);
|
|
584 bool_type = ctxt.get_type (GCC_JIT_TYPE_BOOL);
|
|
585 stack_type = ctxt.new_array_type (int_type, MAX_STACK_DEPTH);
|
|
586
|
|
587 /* The constant value 1. */
|
|
588 const_one = ctxt.one (int_type);
|
|
589
|
|
590 }
|
|
591
|
|
592 /* Create locations. */
|
|
593
|
|
594 void
|
|
595 compilation_state::create_locations ()
|
|
596 {
|
|
597 for (int pc = 0; pc < toyvmfn.fn_num_ops; pc++)
|
|
598 {
|
|
599 toyvm_op *op = &toyvmfn.fn_ops[pc];
|
|
600
|
|
601 op_locs[pc] = ctxt.new_location (toyvmfn.fn_filename,
|
|
602 op->op_linenum,
|
|
603 0); /* column */
|
|
604 }
|
|
605 }
|
|
606
|
|
607 /* Creating the function. */
|
|
608
|
|
609 void
|
|
610 compilation_state::create_function (const char *funcname)
|
|
611 {
|
|
612 std::vector <gccjit::param> params;
|
|
613 param_arg = ctxt.new_param (int_type, "arg", op_locs[0]);
|
|
614 params.push_back (param_arg);
|
|
615 fn = ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
|
|
616 int_type,
|
|
617 funcname,
|
|
618 params, 0,
|
|
619 op_locs[0]);
|
|
620
|
|
621 /* Create stack lvalues. */
|
|
622 stack = fn.new_local (stack_type, "stack");
|
|
623 stack_depth = fn.new_local (int_type, "stack_depth");
|
|
624 x = fn.new_local (int_type, "x");
|
|
625 y = fn.new_local (int_type, "y");
|
|
626
|
|
627 /* 1st pass: create blocks, one per opcode. */
|
|
628
|
|
629 /* We need an entry block to do one-time initialization, so create that
|
|
630 first. */
|
|
631 initial_block = fn.new_block ("initial");
|
|
632
|
|
633 /* Create a block per operation. */
|
|
634 for (int pc = 0; pc < toyvmfn.fn_num_ops; pc++)
|
|
635 {
|
|
636 char buf[16];
|
|
637 sprintf (buf, "instr%i", pc);
|
|
638 op_blocks[pc] = fn.new_block (buf);
|
|
639 }
|
|
640
|
|
641 /* Populate the initial block. */
|
|
642
|
|
643 /* "stack_depth = 0;". */
|
|
644 initial_block.add_assignment (stack_depth,
|
|
645 ctxt.zero (int_type),
|
|
646 op_locs[0]);
|
|
647
|
|
648 /* "PUSH (arg);". */
|
|
649 add_push (initial_block,
|
|
650 param_arg,
|
|
651 op_locs[0]);
|
|
652
|
|
653 /* ...and jump to insn 0. */
|
|
654 initial_block.end_with_jump (op_blocks[0],
|
|
655 op_locs[0]);
|
|
656
|
|
657 /* 2nd pass: fill in instructions. */
|
|
658 for (int pc = 0; pc < toyvmfn.fn_num_ops; pc++)
|
|
659 {
|
|
660 gccjit::location loc = op_locs[pc];
|
|
661
|
|
662 gccjit::block block = op_blocks[pc];
|
|
663 gccjit::block next_block = (pc < toyvmfn.fn_num_ops
|
|
664 ? op_blocks[pc + 1]
|
|
665 : NULL);
|
|
666
|
|
667 toyvm_op *op;
|
|
668 op = &toyvmfn.fn_ops[pc];
|
|
669
|
|
670 /* Helper macros. */
|
|
671
|
|
672 #define X_EQUALS_POP()\
|
|
673 add_pop (block, x, loc)
|
|
674 #define Y_EQUALS_POP()\
|
|
675 add_pop (block, y, loc)
|
|
676 #define PUSH_RVALUE(RVALUE)\
|
|
677 add_push (block, (RVALUE), loc)
|
|
678 #define PUSH_X()\
|
|
679 PUSH_RVALUE (x)
|
|
680 #define PUSH_Y() \
|
|
681 PUSH_RVALUE (y)
|
|
682
|
|
683 block.add_comment (opcode_names[op->op_opcode], loc);
|
|
684
|
|
685 /* Handle the individual opcodes. */
|
|
686
|
|
687 switch (op->op_opcode)
|
|
688 {
|
|
689 case DUP:
|
|
690 X_EQUALS_POP ();
|
|
691 PUSH_X ();
|
|
692 PUSH_X ();
|
|
693 break;
|
|
694
|
|
695 case ROT:
|
|
696 Y_EQUALS_POP ();
|
|
697 X_EQUALS_POP ();
|
|
698 PUSH_Y ();
|
|
699 PUSH_X ();
|
|
700 break;
|
|
701
|
|
702 case BINARY_ADD:
|
|
703 Y_EQUALS_POP ();
|
|
704 X_EQUALS_POP ();
|
|
705 PUSH_RVALUE (
|
|
706 ctxt.new_binary_op (
|
|
707 GCC_JIT_BINARY_OP_PLUS,
|
|
708 int_type,
|
|
709 x, y,
|
|
710 loc));
|
|
711 break;
|
|
712
|
|
713 case BINARY_SUBTRACT:
|
|
714 Y_EQUALS_POP ();
|
|
715 X_EQUALS_POP ();
|
|
716 PUSH_RVALUE (
|
|
717 ctxt.new_binary_op (
|
|
718 GCC_JIT_BINARY_OP_MINUS,
|
|
719 int_type,
|
|
720 x, y,
|
|
721 loc));
|
|
722 break;
|
|
723
|
|
724 case BINARY_MULT:
|
|
725 Y_EQUALS_POP ();
|
|
726 X_EQUALS_POP ();
|
|
727 PUSH_RVALUE (
|
|
728 ctxt.new_binary_op (
|
|
729 GCC_JIT_BINARY_OP_MULT,
|
|
730 int_type,
|
|
731 x, y,
|
|
732 loc));
|
|
733 break;
|
|
734
|
|
735 case BINARY_COMPARE_LT:
|
|
736 Y_EQUALS_POP ();
|
|
737 X_EQUALS_POP ();
|
|
738 PUSH_RVALUE (
|
|
739 /* cast of bool to int */
|
|
740 ctxt.new_cast (
|
|
741 /* (x < y) as a bool */
|
|
742 ctxt.new_comparison (
|
|
743 GCC_JIT_COMPARISON_LT,
|
|
744 x, y,
|
|
745 loc),
|
|
746 int_type,
|
|
747 loc));
|
|
748 break;
|
|
749
|
|
750 case RECURSE:
|
|
751 {
|
|
752 X_EQUALS_POP ();
|
|
753 PUSH_RVALUE (
|
|
754 ctxt.new_call (
|
|
755 fn,
|
|
756 x,
|
|
757 loc));
|
|
758 break;
|
|
759 }
|
|
760
|
|
761 case RETURN:
|
|
762 X_EQUALS_POP ();
|
|
763 block.end_with_return (x, loc);
|
|
764 break;
|
|
765
|
|
766 /* Ops taking an operand. */
|
|
767 case PUSH_CONST:
|
|
768 PUSH_RVALUE (
|
|
769 ctxt.new_rvalue (int_type, op->op_operand));
|
|
770 break;
|
|
771
|
|
772 case JUMP_ABS_IF_TRUE:
|
|
773 X_EQUALS_POP ();
|
|
774 block.end_with_conditional (
|
|
775 /* "(bool)x". */
|
|
776 ctxt.new_cast (x, bool_type, loc),
|
|
777 op_blocks[op->op_operand], /* on_true */
|
|
778 next_block, /* on_false */
|
|
779 loc);
|
|
780 break;
|
|
781
|
|
782 default:
|
|
783 assert(0);
|
|
784 } /* end of switch on opcode */
|
|
785
|
|
786 /* Go to the next block. */
|
|
787 if (op->op_opcode != JUMP_ABS_IF_TRUE
|
|
788 && op->op_opcode != RETURN)
|
|
789 block.end_with_jump (next_block, loc);
|
|
790
|
|
791 } /* end of loop on PC locations. */
|
|
792 }
|
|
793
|
|
794 compilation_result
|
|
795 compilation_state::compile ()
|
|
796 {
|
|
797 return ctxt.compile ();
|
|
798 }
|
|
799
|
|
800 char test[1024];
|
|
801
|
|
802 #define CHECK_NON_NULL(PTR) \
|
|
803 do { \
|
|
804 if ((PTR) != NULL) \
|
|
805 { \
|
|
806 pass ("%s: %s is non-null", test, #PTR); \
|
|
807 } \
|
|
808 else \
|
|
809 { \
|
|
810 fail ("%s: %s is NULL", test, #PTR); \
|
|
811 abort (); \
|
|
812 } \
|
|
813 } while (0)
|
|
814
|
|
815 #define CHECK_VALUE(ACTUAL, EXPECTED) \
|
|
816 do { \
|
|
817 if ((ACTUAL) == (EXPECTED)) \
|
|
818 { \
|
|
819 pass ("%s: actual: %s == expected: %s", test, #ACTUAL, #EXPECTED); \
|
|
820 } \
|
|
821 else \
|
|
822 { \
|
|
823 fail ("%s: actual: %s != expected: %s", test, #ACTUAL, #EXPECTED); \
|
|
824 fprintf (stderr, "incorrect value\n"); \
|
|
825 abort (); \
|
|
826 } \
|
|
827 } while (0)
|
|
828
|
|
829 static void
|
|
830 test_script (const char *scripts_dir, const char *script_name, int input,
|
|
831 int expected_result)
|
|
832 {
|
|
833 char *script_path;
|
|
834 toyvm_function *fn;
|
|
835 int interpreted_result;
|
|
836 toyvm_compiled_func code;
|
|
837 int compiled_result;
|
|
838
|
|
839 snprintf (test, sizeof (test), "toyvm.cc: %s", script_name);
|
|
840
|
|
841 script_path = (char *)malloc (strlen (scripts_dir)
|
|
842 + strlen (script_name) + 1);
|
|
843 CHECK_NON_NULL (script_path);
|
|
844 sprintf (script_path, "%s%s", scripts_dir, script_name);
|
|
845
|
|
846 fn = toyvm_function::parse (script_path, script_name);
|
|
847 CHECK_NON_NULL (fn);
|
|
848
|
|
849 interpreted_result = fn->interpret (input, NULL);
|
|
850 CHECK_VALUE (interpreted_result, expected_result);
|
|
851
|
|
852 compilation_result compiler_result = fn->compile ();
|
|
853
|
|
854 const char *funcname = fn->get_function_name ();
|
|
855 code = (toyvm_compiled_func)compiler_result.get_code (funcname);
|
|
856 CHECK_NON_NULL (code);
|
|
857
|
|
858 compiled_result = code (input);
|
|
859 CHECK_VALUE (compiled_result, expected_result);
|
|
860
|
|
861 free (script_path);
|
|
862 }
|
|
863
|
|
864 #define PATH_TO_SCRIPTS ("/jit/docs/examples/tut04-toyvm/")
|
|
865
|
|
866 static void
|
|
867 test_suite (void)
|
|
868 {
|
|
869 const char *srcdir;
|
|
870 char *scripts_dir;
|
|
871
|
|
872 snprintf (test, sizeof (test), "toyvm.cc");
|
|
873
|
|
874 /* We need to locate the test scripts.
|
|
875 Rely on "srcdir" being set in the environment. */
|
|
876
|
|
877 srcdir = getenv ("srcdir");
|
|
878 CHECK_NON_NULL (srcdir);
|
|
879
|
|
880 scripts_dir = (char *)malloc (strlen (srcdir) + strlen(PATH_TO_SCRIPTS)
|
|
881 + 1);
|
|
882 CHECK_NON_NULL (scripts_dir);
|
|
883 sprintf (scripts_dir, "%s%s", srcdir, PATH_TO_SCRIPTS);
|
|
884
|
|
885 test_script (scripts_dir, "factorial.toy", 10, 3628800);
|
|
886 test_script (scripts_dir, "fibonacci.toy", 10, 55);
|
|
887
|
|
888 free (scripts_dir);
|
|
889 }
|
|
890
|
|
891 int
|
|
892 main (int argc, char **argv)
|
|
893 {
|
|
894 const char *filename = NULL;
|
|
895 toyvm_function *fn = NULL;
|
|
896
|
|
897 /* If called with no args, assume we're being run by the test suite. */
|
|
898 if (argc < 3)
|
|
899 {
|
|
900 test_suite ();
|
|
901 return 0;
|
|
902 }
|
|
903
|
|
904 if (argc != 3)
|
|
905 {
|
|
906 fprintf (stdout,
|
|
907 "%s FILENAME INPUT: Parse and run a .toy file\n",
|
|
908 argv[0]);
|
|
909 exit (1);
|
|
910 }
|
|
911
|
|
912 filename = argv[1];
|
|
913 fn = toyvm_function::parse (filename, filename);
|
|
914 if (!fn)
|
|
915 exit (1);
|
|
916
|
|
917 if (0)
|
|
918 fn->disassemble (stdout);
|
|
919
|
|
920 printf ("interpreter result: %d\n",
|
|
921 fn->interpret (atoi (argv[2]), NULL));
|
|
922
|
|
923 /* JIT-compilation. */
|
|
924 compilation_result compiler_result = fn->compile ();
|
|
925
|
|
926 const char *funcname = fn->get_function_name ();
|
|
927 toyvm_compiled_func code
|
|
928 = (toyvm_compiled_func)compiler_result.get_code (funcname);
|
|
929
|
|
930 printf ("compiler result: %d\n",
|
|
931 code (atoi (argv[2])));
|
|
932
|
|
933 return 0;
|
|
934 }
|