Next: , Up: GNU lightning examples  


4.1 A function which increments a number by one

Let’s see how to create and use the sample incr function created in GNU lightning’s instruction set:

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

static jit_state_t *_jit;

typedef int (*pifi)(int);    /* Pointer to Int Function of Int */

int main(int argc, char *argv[])
{
  jit_node_t  *in;
  pifi         incr;

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

  jit_prolog();                    /*      prolog              */
  in = jit_arg();                  /*      in = arg            */
  jit_getarg(JIT_R0, in);          /*      getarg R0           */
  jit_addi(JIT_R0, JIT_R0, 1);     /*      addi   R0, R0, 1    */
  jit_retr(JIT_R0);                /*      retr   R0           */

  incr = jit_emit();
  jit_clear_state();

  /* call the generated code, passing 5 as an argument */
  printf("%d + 1 = %d\n", 5, incr(5));

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

Let’s examine the code line by line (well, almost…):

#include <lightning.h>

You already know about this. It defines all of GNU lightning’s macros.

static jit_state_t *_jit;

You might wonder about what is jit_state_t. It is a structure that stores jit code generation information. The name _jit is special, because since multiple jit generators can run at the same time, you must either #define _jit my_jit_state or name it _jit.

typedef int (*pifi)(int);

Just a handy typedef for a pointer to a function that takes an int and returns another.

jit_node_t *in;

Declares a variable to hold an identifier for a function argument. It is an opaque pointer, that will hold the return of a call to arg and be used as argument to getarg.

pifi incr;

Declares a function pointer variable to a function that receives an int and returns an int.

init_jit(argv[0]);

You must call this function before creating a jit_state_t object. This function does global state initialization, and may need to detect CPU or Operating System features. It receives a string argument that is later used to read symbols from a shared object using GNU binutils if disassembly was enabled at configure time. If no disassembly will be performed a NULL pointer can be used as argument.

_jit = jit_new_state();

This call initializes a GNU lightning jit state.

jit_prolog();

Ok, so we start generating code for our beloved function…

in = jit_arg();
jit_getarg(JIT_R0, in);

We retrieve the first (and only) argument, an integer, and store it into the general-purpose register R0.

jit_addi(JIT_R0, JIT_R0, 1);

We add one to the content of the register.

jit_retr(JIT_R0);

This instruction generates a standard function epilog that returns the contents of the R0 register.

incr = jit_emit();

This instruction is very important. It actually translates the GNU lightning macros used before to machine code, flushes the generated code area out of the processor’s instruction cache and return a pointer to the start of the code.

jit_clear_state();

This call cleanups any data not required for jit execution. Note that it must be called after any call to jit_print or jit_address, as this call destroy the GNU lightning intermediate representation.

printf("%d + 1 = %d", 5, incr(5));

Calling our function is this simple—it is not distinguishable from a normal C function call, the only difference being that incr is a variable.

jit_destroy_state();

Releases all memory associated with the jit context. It should be called after known the jit will no longer be called.

finish_jit();

This call cleanups any global state hold by GNU lightning, and is advisable to call it once jit code will no longer be generated.

GNU lightning abstracts two phases of dynamic code generation: selecting instructions that map the standard representation, and emitting binary code for these instructions. The client program has the responsibility of describing the code to be generated using the standard GNU lightning instruction set.

Let’s examine the code generated for incr on the SPARC and x86_64 architecture (on the right is the code that an assembly-language programmer would write):

SPARC
      save  %sp, -112, %sp
      mov  %i0, %g2                 retl
      inc  %g2                      inc %o0
      mov  %g2, %i0
      restore 
      retl 
      nop 

In this case, GNU lightning introduces overhead to create a register window (not knowing that the procedure is a leaf procedure) and to move the argument to the general purpose register R0 (which maps to %g2 on the SPARC).

x86_64
    sub   $0x30,%rsp
    mov   %rbp,(%rsp)
    mov   %rsp,%rbp
    sub   $0x18,%rsp
    mov   %rdi,%rax            mov %rdi, %rax
    add   $0x1,%rax            inc %rax
    mov   %rbp,%rsp
    mov   (%rsp),%rbp
    add   $0x30,%rsp
    retq                       retq

In this case, the main overhead is due to the function’s prolog and epilog, and stack alignment after reserving stack space for word to/from float conversions or moving data from/to x87 to/from SSE. Note that besides allocating space to save callee saved registers, no registers are saved/restored because GNU lightning notices those registers are not modified. There is currently no logic to detect if it needs to allocate stack space for type conversions neither proper leaf function detection, but these are subject to change (FIXME).


Next: , Up: GNU lightning examples