Unlike the previous tutorial, this time we’ll compile the context directly to an executable, using gcc_jit_context_compile_to_file():
gcc_jit_context_compile_to_file (ctxt, GCC_JIT_OUTPUT_KIND_EXECUTABLE, output_file);
Here’s the top-level of the compiler, which is what actually calls into gcc_jit_context_compile_to_file():
int main (int argc, char **argv) { const char *input_file; const char *output_file; gcc_jit_context *ctxt; const char *err; if (argc != 3) { fprintf (stderr, "%s: INPUT_FILE OUTPUT_FILE\n", argv[0]); return 1; } input_file = argv[1]; output_file = argv[2]; ctxt = bf_compile (input_file); gcc_jit_context_compile_to_file (ctxt, GCC_JIT_OUTPUT_KIND_EXECUTABLE, output_file); err = gcc_jit_context_get_first_error (ctxt); if (err) { gcc_jit_context_release (ctxt); return 1; } gcc_jit_context_release (ctxt); return 0; }
Note how once the context is populated you could trivially instead compile it to memory using gcc_jit_context_compile() and run it in-process as in the previous tutorial.
To create an executable, we need to export a main
function. Here’s
how to create one from the JIT API:
/* Make "main" function: int main (int argc, char **argv) { ... } */ static gcc_jit_function * make_main (gcc_jit_context *ctxt) { gcc_jit_type *int_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT); gcc_jit_param *param_argc = gcc_jit_context_new_param (ctxt, NULL, int_type, "argc"); gcc_jit_type *char_ptr_ptr_type = gcc_jit_type_get_pointer ( gcc_jit_type_get_pointer ( gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_CHAR))); gcc_jit_param *param_argv = gcc_jit_context_new_param (ctxt, NULL, char_ptr_ptr_type, "argv"); gcc_jit_param *params[2] = {param_argc, param_argv}; gcc_jit_function *func_main = gcc_jit_context_new_function (ctxt, NULL, GCC_JIT_FUNCTION_EXPORTED, int_type, "main", 2, params, 0); return func_main; }
|
Upon compiling this C code, we obtain a bf-to-machine-code compiler;
let’s call it bfc
:
$ gcc \ tut05-bf.c \ -o bfc \ -lgccjit
We can now use bfc
to compile .bf files into machine code executables:
$ ./bfc \ emit-alphabet.bf \ a.out
which we can run directly:
$ ./a.out ABCDEFGHIJKLMNOPQRSTUVWXYZ
Success!
We can also inspect the generated executable using standard tools:
$ objdump -d a.out |less
which shows that libgccjit has managed to optimize the function somewhat (for example, the runs of 26 and 65 increment operations have become integer constants 0x1a and 0x41):
0000000000400620 <main>: 400620: 80 3d 39 0a 20 00 00 cmpb $0x0,0x200a39(%rip) # 601060 <data 400627: 74 07 je 400630 <main 400629: eb fe jmp 400629 <main+0x9> 40062b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 400630: 48 83 ec 08 sub $0x8,%rsp 400634: 0f b6 05 26 0a 20 00 movzbl 0x200a26(%rip),%eax # 601061 <data_cells+0x1> 40063b: c6 05 1e 0a 20 00 1a movb $0x1a,0x200a1e(%rip) # 601060 <data_cells> 400642: 8d 78 41 lea 0x41(%rax),%edi 400645: 40 88 3d 15 0a 20 00 mov %dil,0x200a15(%rip) # 601061 <data_cells+0x1> 40064c: 0f 1f 40 00 nopl 0x0(%rax) 400650: 40 0f b6 ff movzbl %dil,%edi 400654: e8 87 fe ff ff callq 4004e0 <putchar@plt> 400659: 0f b6 05 01 0a 20 00 movzbl 0x200a01(%rip),%eax # 601061 <data_cells+0x1> 400660: 80 2d f9 09 20 00 01 subb $0x1,0x2009f9(%rip) # 601060 <data_cells> 400667: 8d 78 01 lea 0x1(%rax),%edi 40066a: 40 88 3d f0 09 20 00 mov %dil,0x2009f0(%rip) # 601061 <data_cells+0x1> 400671: 75 dd jne 400650 <main+0x30> 400673: 31 c0 xor %eax,%eax 400675: 48 83 c4 08 add $0x8,%rsp 400679: c3 retq 40067a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
We also set up debugging information (via
gcc_jit_context_new_location() and
GCC_JIT_BOOL_OPTION_DEBUGINFO), so it’s possible to use gdb
to singlestep through the generated binary and inspect the internal
state idx
and data_cells
:
(gdb) break main Breakpoint 1 at 0x400790 (gdb) run Starting program: a.out Breakpoint 1, 0x0000000000400790 in main (argc=1, argv=0x7fffffffe448) (gdb) stepi 0x0000000000400797 in main (argc=1, argv=0x7fffffffe448) (gdb) stepi 0x00000000004007a0 in main (argc=1, argv=0x7fffffffe448) (gdb) stepi 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< (gdb) list 4 5 cell 0 = 26 6 ++++++++++++++++++++++++++ 7 8 cell 1 = 65 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< 10 11 while cell#0 != 0 12 [ 13 > (gdb) n 6 ++++++++++++++++++++++++++ (gdb) n 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< (gdb) p idx $1 = 1 (gdb) p data_cells $2 = "\032", '\000' <repeats 29998 times> (gdb) p data_cells[0] $3 = 26 '\032' (gdb) p data_cells[1] $4 = 0 '\000' (gdb) list 4 5 cell 0 = 26 6 ++++++++++++++++++++++++++ 7 8 cell 1 = 65 9 >+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++< 10 11 while cell#0 != 0 12 [ 13 >