3.1.4.2 Compiling to machine code

We want to generate machine code that can be cast to this type and then directly executed in-process:

typedef int (*toyvm_compiled_func) (int);

Our compiler isn’t very sophisticated; it takes the implementation of each opcode above, and maps it directly to the operations supported by the libgccjit API.

How should we handle the stack? In theory we could calculate what the stack depth will be at each opcode, and optimize away the stack manipulation “by hand”. We’ll see below that libgccjit is able to do this for us, so we’ll implement stack manipulation in a direct way, by creating a stack array and stack_depth variables, local within the generated function, equivalent to this C code:

int stack_depth;
int stack[MAX_STACK_DEPTH];

We’ll also have local variables x and y for use when implementing the opcodes, equivalent to this:

int x;
int y;

This means our compiler has the following state:


  toyvm_function &toyvmfn;

  gccjit::context ctxt;

  gccjit::type int_type;
  gccjit::type bool_type;
  gccjit::type stack_type; /* int[MAX_STACK_DEPTH] */

  gccjit::rvalue const_one;

  gccjit::function fn;
  gccjit::param param_arg;
  gccjit::lvalue stack;
  gccjit::lvalue stack_depth;
  gccjit::lvalue x;
  gccjit::lvalue y;

  gccjit::location op_locs[MAX_OPS];
  gccjit::block initial_block;
  gccjit::block op_blocks[MAX_OPS];