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
*/ jit_retr(JIT_R0); /*,
R0,
1retr 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):
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).
mov %rdi,%rax add $0x1,%rax ret
In this case, for the x86 port, GNU lightning has simple optimizations to understand it is a leaf function, and that it is not required to create a stack frame nor update the stack pointer.