1.4.10 Behind the curtain: How does our code get optimized?

Our example is done, but you may be wondering about exactly how the compiler turned what we gave it into the machine code seen above.

We can examine what the compiler is doing in detail by setting:

gcc_jit_context_set_bool_option (state.ctxt,
                                 GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING,
                                 1);
gcc_jit_context_set_bool_option (state.ctxt,
                                 GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES,
                                 1);

This will dump detailed information about the compiler’s state to a directory under /tmp, and keep it from being cleaned up.

The precise names and their formats of these files is subject to change. Higher optimization levels lead to more files. Here’s what I saw (edited for brevity; there were almost 200 files):

intermediate files written to /tmp/libgccjit-KPQbGw
$ ls /tmp/libgccjit-KPQbGw/
fake.c.000i.cgraph
fake.c.000i.type-inheritance
fake.c.004t.gimple
fake.c.007t.omplower
fake.c.008t.lower
fake.c.011t.eh
fake.c.012t.cfg
fake.c.014i.visibility
fake.c.015i.early_local_cleanups
fake.c.016t.ssa
# etc

The gimple code is converted into Static Single Assignment form, with annotations for use when generating the debuginfo:

$ less /tmp/libgccjit-KPQbGw/fake.c.016t.ssa
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)

factorial (signed int arg)
{
  signed int stack[8];
  signed int stack_depth;
  signed int x;
  signed int y;
  <unnamed type> _20;
  signed int _21;
  signed int _38;
  signed int _44;
  signed int _51;
  signed int _56;

initial:
  stack_depth_3 = 0;
  # DEBUG stack_depth => stack_depth_3
  stack[stack_depth_3] = arg_5(D);
  stack_depth_7 = stack_depth_3 + 1;
  # DEBUG stack_depth => stack_depth_7
  # DEBUG instr0 => NULL
  # DEBUG /* DUP */ => NULL
  stack_depth_8 = stack_depth_7 + -1;
  # DEBUG stack_depth => stack_depth_8
  x_9 = stack[stack_depth_8];
  # DEBUG x => x_9
  stack[stack_depth_8] = x_9;
  stack_depth_11 = stack_depth_8 + 1;
  # DEBUG stack_depth => stack_depth_11
  stack[stack_depth_11] = x_9;
  stack_depth_13 = stack_depth_11 + 1;
  # DEBUG stack_depth => stack_depth_13
  # DEBUG instr1 => NULL
  # DEBUG /* PUSH_CONST */ => NULL
  stack[stack_depth_13] = 2;

  /* etc; edited for brevity */

We can perhaps better see the code by turning off GCC_JIT_BOOL_OPTION_DEBUGINFO to suppress all those DEBUG statements, giving:

$ less /tmp/libgccjit-1Hywc0/fake.c.016t.ssa
;; Function factorial (factorial, funcdef_no=0, decl_uid=53, symbol_order=0)

factorial (signed int arg)
{
  signed int stack[8];
  signed int stack_depth;
  signed int x;
  signed int y;
  <unnamed type> _20;
  signed int _21;
  signed int _38;
  signed int _44;
  signed int _51;
  signed int _56;

initial:
  stack_depth_3 = 0;
  stack[stack_depth_3] = arg_5(D);
  stack_depth_7 = stack_depth_3 + 1;
  stack_depth_8 = stack_depth_7 + -1;
  x_9 = stack[stack_depth_8];
  stack[stack_depth_8] = x_9;
  stack_depth_11 = stack_depth_8 + 1;
  stack[stack_depth_11] = x_9;
  stack_depth_13 = stack_depth_11 + 1;
  stack[stack_depth_13] = 2;
  stack_depth_15 = stack_depth_13 + 1;
  stack_depth_16 = stack_depth_15 + -1;
  y_17 = stack[stack_depth_16];
  stack_depth_18 = stack_depth_16 + -1;
  x_19 = stack[stack_depth_18];
  _20 = x_19 < y_17;
  _21 = (signed int) _20;
  stack[stack_depth_18] = _21;
  stack_depth_23 = stack_depth_18 + 1;
  stack_depth_24 = stack_depth_23 + -1;
  x_25 = stack[stack_depth_24];
  if (x_25 != 0)
    goto <bb 4> (instr9);
  else
    goto <bb 3> (instr4);

instr4:
/* DUP */:
  stack_depth_26 = stack_depth_24 + -1;
  x_27 = stack[stack_depth_26];
  stack[stack_depth_26] = x_27;
  stack_depth_29 = stack_depth_26 + 1;
  stack[stack_depth_29] = x_27;
  stack_depth_31 = stack_depth_29 + 1;
  stack[stack_depth_31] = 1;
  stack_depth_33 = stack_depth_31 + 1;
  stack_depth_34 = stack_depth_33 + -1;
  y_35 = stack[stack_depth_34];
  stack_depth_36 = stack_depth_34 + -1;
  x_37 = stack[stack_depth_36];
  _38 = x_37 - y_35;
  stack[stack_depth_36] = _38;
  stack_depth_40 = stack_depth_36 + 1;
  stack_depth_41 = stack_depth_40 + -1;
  x_42 = stack[stack_depth_41];
  _44 = factorial (x_42);
  stack[stack_depth_41] = _44;
  stack_depth_46 = stack_depth_41 + 1;
  stack_depth_47 = stack_depth_46 + -1;
  y_48 = stack[stack_depth_47];
  stack_depth_49 = stack_depth_47 + -1;
  x_50 = stack[stack_depth_49];
  _51 = x_50 * y_48;
  stack[stack_depth_49] = _51;
  stack_depth_53 = stack_depth_49 + 1;

  # stack_depth_1 = PHI <stack_depth_24(2), stack_depth_53(3)>
instr9:
/* RETURN */:
  stack_depth_54 = stack_depth_1 + -1;
  x_55 = stack[stack_depth_54];
  _56 = x_55;
  stack ={v} {CLOBBER};
  return _56;

}

Note in the above how all the gcc_jit_block instances we created have been consolidated into just 3 blocks in GCC’s internal representation: initial, instr4 and instr9.