1 /* A simple stack-based virtual machine to demonstrate
2    JIT-compilation.
3    Copyright (C) 2014-2021 Free Software Foundation, Inc.
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 "jit-dejagnu.h"
28 
29 #include <libgccjit++.h>
30 
31 /* Wrapper around a gcc_jit_result *.  */
32 
33 class compilation_result
34 {
35 public:
compilation_result(gcc_jit_result * result)36   compilation_result (gcc_jit_result *result) :
37     m_result (result)
38   {
39   }
~compilation_result()40   ~compilation_result ()
41   {
42     gcc_jit_result_release (m_result);
43   }
44 
get_code(const char * funcname)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 *
get_function_name() const130   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
add_op(enum opcode opcode,int operand,int linenum)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
add_unary_op(enum opcode opcode,const char * rest_of_line,int linenum)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
make_function_name(const char * filename)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 *
parse(const char * filename,const char * name)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
disassemble_op(toyvm_op * op,int index,FILE * out)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
disassemble(FILE * out)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
push(int arg)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
pop()324 toyvm_frame::pop ()
325 {
326   assert (frm_cur_depth > 0);
327   return frm_stack[--frm_cur_depth];
328 }
329 
330 void
dump_stack(FILE * out)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
interpret(int arg,FILE * trace)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:
compilation_state(toyvm_function & toyvmfn)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
compile()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
add_push(gccjit::block block,gccjit::rvalue rvalue,gccjit::location loc)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
add_pop(gccjit::block block,gccjit::lvalue lvalue,gccjit::location loc)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
create_context()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
create_types()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
create_locations()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
create_function(const char * funcname)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[100];
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
compile()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
test_script(const char * scripts_dir,const char * script_name,int input,int expected_result)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
test_suite(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
main(int argc,char ** argv)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 }
935