4.2 A simple function call to printf

Again, here is the code for the example:

#include <stdio.h>
#include <lightning.h>

static jit_state_t *_jit;

typedef void (*pvfi)(int);      /* Pointer to Void Function of Int */

int main(int argc, char *argv[])
{
  pvfi          myFunction;             /* ptr to generated code */
  jit_node_t    *start, *end;           /* a couple of labels */
  jit_node_t    *in;                    /* to get the argument */

  init_jit(argv[0]);
  _jit = jit_new_state();

  start = jit_note(__FILE__, __LINE__);
  jit_prolog();
  in = jit_arg();
  jit_getarg(JIT_R1, in);
  jit_prepare();
  jit_pushargi((jit_word_t)"generated %d bytes\n");
  jit_ellipsis();
  jit_pushargr(JIT_R1);
  jit_finishi(printf);
  jit_ret();
  jit_epilog();
  end = jit_note(__FILE__, __LINE__);

  myFunction = jit_emit();

  /* call the generated code, passing its size as argument */
  myFunction((char*)jit_address(end) - (char*)jit_address(start));
  jit_clear_state();

  jit_disassemble();

  jit_destroy_state();
  finish_jit();
  return 0;
}

The function shows how many bytes were generated. Most of the code is not very interesting, as it resembles very closely the program presented in A function which increments a number by one.

For this reason, we’re going to concentrate on just a few statements.

start = jit_note(__FILE__, __LINE__);
end = jit_note(__FILE__, __LINE__);

These two instruction call the jit_note macro, which creates a note in the jit code; arguments to jit_note usually are a filename string and line number integer, but using NULL for the string argument is perfectly valid if only need to create a simple marker in the code.

jit_ellipsis();

ellipsis usually is only required if calling varargs functions with double arguments, but it is a good practice to properly describe the in the call sequence.

jit_pushargi((jit_word_t)"generated %d bytes\n");

Note the use of the (jit_word_t) cast, that is used only to avoid a compiler warning, due to using a pointer where a wordsize integer type was expected.

jit_prepare();
jit_finishi(printf);

Once the arguments to printf have been pushed, what means moving them to stack or register arguments, the printf function is called and the stack cleaned. Note how GNU lightning abstracts the differences between different architectures and ABI’s – the client program does not know how parameter passing works on the host architecture.

jit_epilog();

Usually it is not required to call epilog, but because it is implicitly called when noticing the end of a function, if the end variable was set with a note call after the ret, it would not consider the function epilog.

myFunction((char*)jit_address(end) - (char*)jit_address(start));

This calls the generate jit function passing as argument the offset difference from the start and end notes. The address call must be done after the emit call or either a fatal error will happen (if GNU lightning is built with assertions enable) or an undefined value will be returned.

jit_clear_state();

Note that jit_clear_state was called after executing jit in this example. It was done because it must be called after any call to jit_address or jit_print.

jit_disassemble();

disassemble will dump the generated code to standard output, unless GNU lightning was built with the disassembler disabled, in which case no output will be shown.