[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Values form the backbone of the storage system in libjit
.
Every value in the system, be it a constant, a local variable,
or a temporary result, is represented by an object of type
jit_value_t
. The JIT then allocates registers or memory
locations to the values as appropriate.
We will demonstrate how to use values with a simple example of adding two local variables together and putting the result into a third local variable. First, we allocate storage for the three local variables:
value1 = jit_value_create(func, jit_type_int); value2 = jit_value_create(func, jit_type_int); value3 = jit_value_create(func, jit_type_int); |
Here, func
is the function that we are building. To add
value1
and value2
and put the result into value3
,
we use the following code:
temp = jit_insn_add(func, value1, value2); jit_insn_store(func, value3, temp); |
The jit_insn_add
function allocates a temporary value
(temp
) and places the result of the addition into it.
The jit_insn_store
function then stores the temporary
result into value3
.
You might be tempted to think that the above code is inefficient.
Why do we copy the result into a temporary variable first?
Why not put the result directly to value3
?
Behind the scenes, the JIT will typically optimize temp
away,
resulting in the final code that you expect (i.e. value3 = value1 +
value2
). It is simply easier to use libjit
if all results
end up in temporary variables first, so that's what we do.
Using temporary values, it is very easy to convert stack machine
bytecodes into JIT instructions. Consider the following Java
Virtual Machine bytecode (representing value4 = value1 * value2 +
value3
):
iload 1 iload 2 imul iload 3 iadd istore 4 |
Let us demonstrate how this code would be translated, instruction
by instruction. We assume that we have a stack
available,
which keeps track of the temporary values in the system. We also
assume that jit_value_t
objects representing the local variables
are already stored in an array called locals
.
First, we load local variable 1 onto the stack:
stack[size++] = jit_insn_load(func, locals[1]); |
We repeat this for local variable 2:
stack[size++] = jit_insn_load(func, locals[2]); |
Now we pop these two values and push their multiplication:
stack[size - 2] = jit_insn_mul(func, stack[size - 2], stack[size - 1]); --size; |
Next, we need to push the value of local variable 3 and add it to the product that we just computed:
stack[size++] = jit_insn_load(func, locals[3]); stack[size - 2] = jit_insn_add(func, stack[size - 2], stack[size - 1]); --size; |
Finally, we store the result into local variable 4:
jit_insn_store(func, locals[4], stack[--size]); |
Collecting up all of the above code, we get the following:
stack[size++] = jit_insn_load(func, locals[1]); stack[size++] = jit_insn_load(func, locals[2]); stack[size - 2] = jit_insn_mul(func, stack[size - 2], stack[size - 1]); --size; stack[size++] = jit_insn_load(func, locals[3]); stack[size - 2] = jit_insn_add(func, stack[size - 2], stack[size - 1]); --size; jit_insn_store(func, locals[4], stack[--size]); |
The JIT will optimize away most of these temporary results, leaving the final machine code that you expect.
If the virtual machine was register-based, then a slightly different
translation strategy would be used. Consider the following code,
which computes reg4 = reg1 * reg2 + reg3
, with the intermediate
result stored temporarily in reg5
:
mul reg5, reg1, reg2 add reg4, reg5, reg3 |
You would start by allocating value objects for all of the registers
in your system (with jit_value_create
):
reg[1] = jit_value_create(func, jit_type_int); reg[2] = jit_value_create(func, jit_type_int); reg[3] = jit_value_create(func, jit_type_int); reg[4] = jit_value_create(func, jit_type_int); reg[5] = jit_value_create(func, jit_type_int); |
Then, the virtual register machine code is translated as follows:
temp1 = jit_insn_mul(func, reg[1], reg[2]); jit_insn_store(reg[5], temp1); temp2 = jit_insn_add(func, reg[5], reg[3]); jit_insn_store(reg[4], temp2); |
Each virtual register machine instruction turns into two libjit
function calls. The JIT will normally optimize away the temporary
results. If the value in reg5
is not used further down the code,
then the JIT may also be able to optimize reg5
away.
The rest of this section describes the functions that are available to create and manipulate values.
Create a new value in the context of a function's current block. The value initially starts off as a block-specific temporary. It will be converted into a function-wide local variable if it is ever referenced from a different block. Returns NULL if out of memory.
Note: It isn't possible to refer to global variables directly using
values. If you need to access a global variable, then load its
address into a temporary and use jit_insn_load_relative
or jit_insn_store_relative
to manipulate it. It simplifies
the JIT if it can assume that all values are local.
Create a new native integer constant in the specified function. Returns NULL if out of memory.
The type parameter indicates the actual type of the constant,
if it happens to be something other than jit_type_nint
.
For example, the following will create an unsigned byte constant:
value = jit_value_create_nint_constant(context, jit_type_ubyte, 128); |
This function can be used to create constants of type jit_type_sbyte
,
jit_type_ubyte
, jit_type_short
, jit_type_ushort
,
jit_type_int
, jit_type_uint
, jit_type_nint
,
jit_type_nuint
, and all pointer types.
Create a new 64-bit integer constant in the specified
function. This can also be used to create constants of
type jit_type_ulong
. Returns NULL if out of memory.
Create a new 32-bit floating-point constant in the specified function. Returns NULL if out of memory.
Create a new 64-bit floating-point constant in the specified function. Returns NULL if out of memory.
Create a new native floating-point constant in the specified function. Returns NULL if out of memory.
Create a new constant from a generic constant structure in the specified function. Returns NULL if out of memory or if the type in const_value is not suitable for a constant.
Get the value that corresponds to a specified function parameter. Returns NULL if out of memory.
Get the value that contains the structure return pointer for a function. If the function does not have a structure return pointer (i.e. structures are returned in registers), then this returns NULL.
Determine if a value is temporary. i.e. its scope extends over a single block within its function.
Determine if a value is local. i.e. its scope extends over multiple blocks within its function.
Determine if a value is a constant.
Determine if a value is a function parameter.
Create a reference to the specified value from the current block in func. This will convert a temporary value into a local value if value is being referenced from a different block than its original.
It is not necessary that func be the same function as the one where the value was originally created. It may be a nested function, referring to a local variable in its parent function.
Set a flag on a value to indicate that it is volatile. The contents of the value must always be reloaded from memory, never from a cached register copy.
Determine if a value is volatile.
Set a flag on a value to indicate that it is addressable.
This should be used when you want to take the address of a
value (e.g. &variable
in C). The value is guaranteed
to not be stored in a register across a function call.
If you refer to a value from a nested function (jit_value_ref
),
then the value will be automatically marked as addressable.
Determine if a value is addressable.
Get the type that is associated with a value.
Get the function which owns a particular value.
Get the block which owns a particular value.
Get the context which owns a particular value.
Get the constant value within a particular value. The returned
structure's type
field will be jit_type_void
if
value
is not a constant.
Get the constant value within a particular value, assuming
that its type is compatible with jit_type_nint
.
Get the constant value within a particular value, assuming
that its type is compatible with jit_type_long
.
Get the constant value within a particular value, assuming
that its type is compatible with jit_type_float32
.
Get the constant value within a particular value, assuming
that its type is compatible with jit_type_float64
.
Get the constant value within a particular value, assuming
that its type is compatible with jit_type_nfloat
.
Determine if value is constant and non-zero.
Convert a the constant value into a new type, and return its value in result. Returns zero if the conversion is not possible, usually due to overflow.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Klaus Treichel on May, 11 2008 using texi2html 1.78.