| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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_insn codeBuffer[1024];
typedef int (*pifi)(int); /* Pointer to Int Function of Int */
int main()
{
pifi incr = (pifi) (jit_set_ip(codeBuffer).iptr);
int in;
jit_leaf(1); /* leaf 1 */
in = jit_arg_i(); /* in = arg_i */
jit_getarg_i(JIT_R0, in); /* getarg_i R0 */
jit_addi_i(JIT_RET, JIT_R0, 1); /* addi_i RET, R0, 1 */
jit_ret(); /* ret */
jit_flush_code(codeBuffer, jit_get_ip().ptr);
/* call the generated code, passing 5 as an argument */
printf("%d + 1 = %d\n", 5, incr(5));
return 0;
}
|
Let's examine the code line by line (well, almost...):
jit_insn. It is just a type that
is defined by GNU lightning. Its exact definition depends on the
architecture; in general, defining an array of 1024 jit_insns
allows one to write 100 to 400 GNU lightning instructions (depending on
the architecture and exact instructions).
int and returns another.
jit_set_ip, which takes a pointer to an
area of memory where compiled code will be put and returns the same
value, cast to a union type whose members are pointers to
functions returning different C types. This union is called
jit_code and is defined as follows:
typedef union jit_code {
char *ptr;
void (*vptr)();
char (*cptr)();
unsigned char (*ucptr)();
short (*sptr)();
unsigned short (*usptr)();
int (*iptr)();
unsigned int (*uiptr)();
long (*lptr)();
unsigned long (*ulptr)();
void * (*pptr)();
float (*fptr)();
double (*dptr)();
} jit_code;
|
Any of the members could have been used, since the result is soon casted
to type pifi but, for the sake of clarity, the program uses
iptr, a pointer to a function with no prototype and returning an
int.
Analogous to jit_set_ip is jit_get_ip, which does not
modify the instruction pointer--it is nothing more than a cast of the
current IP to jit_code.
arg, says that macros implementing
arg return a value--we'll be using this variable to store the
result of arg.
R0.
RET register.
jit_flush_code function accepts the first and the last address
to flush; we use jit_get_ip to find out the latter.
incr
is a variable.
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
architectures (on the right is the code that an assembly-language
programmer would write):
save %sp, -96, %sp
mov %i0, %l0 retl
add %l0, 1, %i0 add %o0, 1, %o0
ret
restore
|
R0 (which
maps to %l0 on the SPARC). The former overhead could be
avoided by teaching GNU lightning about leaf procedures (see section 4. The future of GNU lightning);
the latter could instead be avoided by rewriting the getarg instruction
as jit_getarg_i(JIT_RET, in), which was not done in this
example.
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %esi
pushl %edi
movl 8(%ebp), %eax movl 4(%esp), %eax
addl $1, %eax incl %eax
popl %edi
popl %esi
popl %ebx
popl %ebp
ret ret
|
V0 through V2 registers (%ebx, %esi,
%edi); in this case, a hand-written routine would have included
the prolog too. Also, a ten byte prolog would probably be a small
overhead in a more complex function.
In such a simple case, the macros that make up the back-end compile reasonably efficient code, with the notable exception of prolog/epilog code.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |