1 /* A simple stack-based virtual machine to demonstrate
3 Copyright (C) 2014-2022 Free Software Foundation, Inc.
5 This file is part of GCC.
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)
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.
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/>. */
27 #include "jit-dejagnu.h"
29 #include <libgccjit++.h>
31 /* Wrapper around a gcc_jit_result *. */
33 class compilation_result
36 compilation_result (gcc_jit_result
*result
) :
40 ~compilation_result ()
42 gcc_jit_result_release (m_result
);
45 void *get_code (const char *funcname
)
47 return gcc_jit_result_get_code (m_result
, funcname
);
51 gcc_jit_result
*m_result
;
54 /* Functions are compiled to this function ptr type. */
55 typedef int (*toyvm_compiled_func
) (int);
58 /* Ops taking no operand. */
68 /* Ops taking an operand. */
73 #define FIRST_UNARY_OPCODE (PUSH_CONST)
75 const char * const opcode_names
[] = {
91 /* Which operation. */
92 enum opcode op_opcode
;
94 /* Some opcodes take an argument. */
97 /* The line number of the operation within the source file. */
107 add_op (enum opcode opcode
,
108 int operand
, int linenum
);
111 add_unary_op (enum opcode opcode
,
112 const char *rest_of_line
, int linenum
);
114 static toyvm_function
*
115 parse (const char *filename
, const char *name
);
118 disassemble_op (toyvm_op
*op
, int index
, FILE *out
);
121 disassemble (FILE *out
);
124 interpret (int arg
, FILE *trace
);
130 get_function_name () const { return m_funcname
; }
134 make_function_name (const char *filename
);
137 const char *fn_filename
;
140 toyvm_op fn_ops
[MAX_OPS
];
141 friend struct compilation_state
;
144 #define MAX_STACK_DEPTH (8)
151 void dump_stack (FILE *out
);
154 toyvm_function
*frm_function
;
156 int frm_stack
[MAX_STACK_DEPTH
];
159 friend int toyvm_function::interpret (int arg
, FILE *trace
);
164 toyvm_function::add_op (enum opcode opcode
,
165 int operand
, int linenum
)
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
;
176 toyvm_function::add_unary_op (enum opcode opcode
,
177 const char *rest_of_line
, int linenum
)
179 int operand
= atoi (rest_of_line
);
180 add_op (opcode
, operand
, linenum
);
184 toyvm_function::make_function_name (const char *filename
)
186 /* Skip any path separators. */
187 const char *pathsep
= strrchr (filename
, '/');
189 filename
= pathsep
+ 1;
191 /* Copy filename to funcname. */
192 m_funcname
= (char *)malloc (strlen (filename
) + 1);
194 strcpy (m_funcname
, filename
);
196 /* Convert "." to NIL terminator. */
197 *(strchr (m_funcname
, '.')) = '\0';
201 toyvm_function::parse (const char *filename
, const char *name
)
204 toyvm_function
*fn
= NULL
;
213 f
= fopen (filename
, "r");
217 "cannot open file %s: %s\n",
218 filename
, strerror (errno
));
222 fn
= (toyvm_function
*)calloc (1, sizeof (toyvm_function
));
225 fprintf (stderr
, "out of memory allocating toyvm_function\n");
228 fn
->fn_filename
= filename
;
229 fn
->make_function_name (filename
);
231 /* Read the lines of the file. */
232 while ((linelen
= getline (&line
, &bufsize
, f
)) != -1)
234 /* Note that this is a terrible parser, but it avoids the need to
235 bring in lex/yacc as a dependency. */
239 fprintf (stdout
, "%3d: %s", linenum
, line
);
241 /* Lines beginning with # are comments. */
245 /* Skip blank lines. */
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
);
274 fprintf (stderr
, "%s:%d: parse error\n", filename
, linenum
);
295 toyvm_function::disassemble_op (toyvm_op
*op
, int index
, FILE *out
)
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
);
306 toyvm_function::disassemble (FILE *out
)
309 for (i
= 0; i
< fn_num_ops
; i
++)
311 toyvm_op
*op
= &fn_ops
[i
];
312 disassemble_op (op
, i
, out
);
317 toyvm_frame::push (int arg
)
319 assert (frm_cur_depth
< MAX_STACK_DEPTH
);
320 frm_stack
[frm_cur_depth
++] = arg
;
326 assert (frm_cur_depth
> 0);
327 return frm_stack
[--frm_cur_depth
];
331 toyvm_frame::dump_stack (FILE *out
)
334 fprintf (out
, "stack:");
335 for (i
= 0; i
< frm_cur_depth
; i
++)
337 fprintf (out
, " %d", frm_stack
[i
]);
342 /* Execute the given function. */
345 toyvm_function::interpret (int arg
, FILE *trace
)
348 #define PUSH(ARG) (frame.push (ARG))
349 #define POP(ARG) (frame.pop ())
351 frame
.frm_function
= this;
353 frame
.frm_cur_depth
= 0;
361 assert (frame
.frm_pc
< fn_num_ops
);
362 op
= &fn_ops
[frame
.frm_pc
++];
366 frame
.dump_stack (trace
);
367 disassemble_op (op
, frame
.frm_pc
, trace
);
370 switch (op
->op_opcode
)
372 /* Ops taking no operand. */
392 case BINARY_SUBTRACT
:
404 case BINARY_COMPARE_LT
:
412 x
= interpret (x
, trace
);
419 /* Ops taking an operand. */
421 PUSH (op
->op_operand
);
424 case JUMP_ABS_IF_TRUE
:
427 frame
.frm_pc
= op
->op_operand
;
431 assert (0); /* unknown opcode */
433 } /* end of switch on opcode */
434 } /* end of while loop */
440 /* JIT compilation. */
442 class compilation_state
445 compilation_state (toyvm_function
&toyvmfn
) :
449 void create_context ();
450 void create_types ();
451 void create_locations ();
452 void create_function (const char *funcname
);
453 compilation_result
compile ();
457 add_push (gccjit::block block
,
458 gccjit::rvalue rvalue
,
459 gccjit::location loc
);
462 add_pop (gccjit::block block
,
463 gccjit::lvalue lvalue
,
464 gccjit::location loc
);
470 toyvm_function
&toyvmfn
;
472 gccjit::context ctxt
;
474 gccjit::type int_type
;
475 gccjit::type bool_type
;
476 gccjit::type stack_type
; /* int[MAX_STACK_DEPTH] */
478 gccjit::rvalue const_one
;
481 gccjit::param param_arg
;
482 gccjit::lvalue stack
;
483 gccjit::lvalue stack_depth
;
487 gccjit::location op_locs
[MAX_OPS
];
488 gccjit::block initial_block
;
489 gccjit::block op_blocks
[MAX_OPS
];
493 /* The main compilation hook. */
496 toyvm_function::compile ()
498 compilation_state
state (*this);
500 state
.create_context ();
501 state
.create_types ();
502 state
.create_locations ();
503 state
.create_function (get_function_name ());
505 /* We've now finished populating the context. Compile it. */
506 return state
.compile ();
509 /* Stack manipulation. */
512 compilation_state::add_push (gccjit::block block
,
513 gccjit::rvalue rvalue
,
514 gccjit::location loc
)
516 /* stack[stack_depth] = RVALUE */
517 block
.add_assignment (
518 /* stack[stack_depth] */
519 ctxt
.new_array_access (
526 /* "stack_depth++;". */
527 block
.add_assignment_op (
529 GCC_JIT_BINARY_OP_PLUS
,
535 compilation_state::add_pop (gccjit::block block
,
536 gccjit::lvalue lvalue
,
537 gccjit::location loc
)
539 /* "--stack_depth;". */
540 block
.add_assignment_op (
542 GCC_JIT_BINARY_OP_MINUS
,
546 /* "LVALUE = stack[stack_depth];". */
547 block
.add_assignment (
549 /* stack[stack_depth] */
550 ctxt
.new_array_access (stack
,
556 /* Create the context. */
559 compilation_state::create_context ()
561 ctxt
= gccjit::context::acquire ();
563 ctxt
.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE
,
565 ctxt
.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE
,
567 ctxt
.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL
,
569 ctxt
.set_bool_option (GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES
,
571 ctxt
.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING
,
573 ctxt
.set_bool_option (GCC_JIT_BOOL_OPTION_DEBUGINFO
,
580 compilation_state::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
);
587 /* The constant value 1. */
588 const_one
= ctxt
.one (int_type
);
592 /* Create locations. */
595 compilation_state::create_locations ()
597 for (int pc
= 0; pc
< toyvmfn
.fn_num_ops
; pc
++)
599 toyvm_op
*op
= &toyvmfn
.fn_ops
[pc
];
601 op_locs
[pc
] = ctxt
.new_location (toyvmfn
.fn_filename
,
607 /* Creating the function. */
610 compilation_state::create_function (const char *funcname
)
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
,
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");
627 /* 1st pass: create blocks, one per opcode. */
629 /* We need an entry block to do one-time initialization, so create that
631 initial_block
= fn
.new_block ("initial");
633 /* Create a block per operation. */
634 for (int pc
= 0; pc
< toyvmfn
.fn_num_ops
; pc
++)
637 sprintf (buf
, "instr%i", pc
);
638 op_blocks
[pc
] = fn
.new_block (buf
);
641 /* Populate the initial block. */
643 /* "stack_depth = 0;". */
644 initial_block
.add_assignment (stack_depth
,
645 ctxt
.zero (int_type
),
649 add_push (initial_block
,
653 /* ...and jump to insn 0. */
654 initial_block
.end_with_jump (op_blocks
[0],
657 /* 2nd pass: fill in instructions. */
658 for (int pc
= 0; pc
< toyvmfn
.fn_num_ops
; pc
++)
660 gccjit::location loc
= op_locs
[pc
];
662 gccjit::block block
= op_blocks
[pc
];
663 gccjit::block next_block
= (pc
< toyvmfn
.fn_num_ops
668 op
= &toyvmfn
.fn_ops
[pc
];
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)
683 block
.add_comment (opcode_names
[op
->op_opcode
], loc
);
685 /* Handle the individual opcodes. */
687 switch (op
->op_opcode
)
707 GCC_JIT_BINARY_OP_PLUS
,
713 case BINARY_SUBTRACT
:
718 GCC_JIT_BINARY_OP_MINUS
,
729 GCC_JIT_BINARY_OP_MULT
,
735 case BINARY_COMPARE_LT
:
739 /* cast of bool to int */
741 /* (x < y) as a bool */
742 ctxt
.new_comparison (
743 GCC_JIT_COMPARISON_LT
,
763 block
.end_with_return (x
, loc
);
766 /* Ops taking an operand. */
769 ctxt
.new_rvalue (int_type
, op
->op_operand
));
772 case JUMP_ABS_IF_TRUE
:
774 block
.end_with_conditional (
776 ctxt
.new_cast (x
, bool_type
, loc
),
777 op_blocks
[op
->op_operand
], /* on_true */
778 next_block
, /* on_false */
784 } /* end of switch on opcode */
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
);
791 } /* end of loop on PC locations. */
795 compilation_state::compile ()
797 return ctxt
.compile ();
802 #define CHECK_NON_NULL(PTR) \
806 pass ("%s: %s is non-null", test, #PTR); \
810 fail ("%s: %s is NULL", test, #PTR); \
815 #define CHECK_VALUE(ACTUAL, EXPECTED) \
817 if ((ACTUAL) == (EXPECTED)) \
819 pass ("%s: actual: %s == expected: %s", test, #ACTUAL, #EXPECTED); \
823 fail ("%s: actual: %s != expected: %s", test, #ACTUAL, #EXPECTED); \
824 fprintf (stderr, "incorrect value\n"); \
830 test_script (const char *scripts_dir
, const char *script_name
, int input
,
835 int interpreted_result
;
836 toyvm_compiled_func code
;
839 snprintf (test
, sizeof (test
), "toyvm.cc: %s", script_name
);
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
);
846 fn
= toyvm_function::parse (script_path
, script_name
);
849 interpreted_result
= fn
->interpret (input
, NULL
);
850 CHECK_VALUE (interpreted_result
, expected_result
);
852 compilation_result compiler_result
= fn
->compile ();
854 const char *funcname
= fn
->get_function_name ();
855 code
= (toyvm_compiled_func
)compiler_result
.get_code (funcname
);
856 CHECK_NON_NULL (code
);
858 compiled_result
= code (input
);
859 CHECK_VALUE (compiled_result
, expected_result
);
864 #define PATH_TO_SCRIPTS ("/jit/docs/examples/tut04-toyvm/")
872 snprintf (test
, sizeof (test
), "toyvm.cc");
874 /* We need to locate the test scripts.
875 Rely on "srcdir" being set in the environment. */
877 srcdir
= getenv ("srcdir");
878 CHECK_NON_NULL (srcdir
);
880 scripts_dir
= (char *)malloc (strlen (srcdir
) + strlen(PATH_TO_SCRIPTS
)
882 CHECK_NON_NULL (scripts_dir
);
883 sprintf (scripts_dir
, "%s%s", srcdir
, PATH_TO_SCRIPTS
);
885 test_script (scripts_dir
, "factorial.toy", 10, 3628800);
886 test_script (scripts_dir
, "fibonacci.toy", 10, 55);
892 main (int argc
, char **argv
)
894 const char *filename
= NULL
;
895 toyvm_function
*fn
= NULL
;
897 /* If called with no args, assume we're being run by the test suite. */
907 "%s FILENAME INPUT: Parse and run a .toy file\n",
913 fn
= toyvm_function::parse (filename
, filename
);
918 fn
->disassemble (stdout
);
920 printf ("interpreter result: %d\n",
921 fn
->interpret (atoi (argv
[2]), NULL
));
923 /* JIT-compilation. */
924 compilation_result compiler_result
= fn
->compile ();
926 const char *funcname
= fn
->get_function_name ();
927 toyvm_compiled_func code
928 = (toyvm_compiled_func
)compiler_result
.get_code (funcname
);
930 printf ("compiler result: %d\n",
931 code (atoi (argv
[2])));