[ << ] [ < ] [ Up ] [ > ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7 Working with temporary values in the JIT

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.

Function: jit_value_t jit_value_create (jit_function_t func, jit_type_t type)

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.

Function: jit_value_t jit_value_create_nint_constant (jit_function_t func, jit_type_t type, jit_nint const_value)

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(func, 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.

Function: jit_value_t jit_value_create_long_constant (jit_function_t func, jit_type_t type, jit_long const_value)

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.

Function: jit_value_t jit_value_create_float32_constant (jit_function_t func, jit_type_t type, jit_float32 const_value)

Create a new 32-bit floating-point constant in the specified function. Returns NULL if out of memory.

Function: jit_value_t jit_value_create_float64_constant (jit_function_t func, jit_type_t type, jit_float64 const_value)

Create a new 64-bit floating-point constant in the specified function. Returns NULL if out of memory.

Function: jit_value_t jit_value_create_nfloat_constant (jit_function_t func, jit_type_t type, jit_nfloat const_value)

Create a new native floating-point constant in the specified function. Returns NULL if out of memory.

Function: jit_value_t jit_value_create_constant (jit_function_t func, const jit_constant_t *const_value)

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.

Function: jit_value_t jit_value_get_param (jit_function_t func, unsigned int param)

Get the value that corresponds to a specified function parameter. Returns NULL if out of memory or param is invalid.

Function: jit_value_t jit_value_get_struct_pointer (jit_function_t func)

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.

Function: int jit_value_is_temporary (jit_value_t value)

Determine if a value is temporary. i.e. its scope extends over a single block within its function.

Function: int jit_value_is_local (jit_value_t value)

Determine if a value is local. i.e. its scope extends over multiple blocks within its function.

Function: int jit_value_is_constant (jit_value_t value)

Determine if a value is a constant.

Function: int jit_value_is_parameter (jit_value_t value)

Determine if a value is a function parameter.

Function: void jit_value_ref (jit_function_t func, jit_value_t value)

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.

Function: void jit_value_set_volatile (jit_value_t value)

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.

Function: int jit_value_is_volatile (jit_value_t value)

Determine if a value is volatile.

Function: void jit_value_set_addressable (jit_value_t value)

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.

Function: int jit_value_is_addressable (jit_value_t value)

Determine if a value is addressable.

Function: jit_type_t jit_value_get_type (jit_value_t value)

Get the type that is associated with a value.

Function: jit_function_t jit_value_get_function (jit_value_t value)

Get the function which owns a particular value.

Function: jit_block_t jit_value_get_block (jit_value_t value)

Get the block which owns a particular value.

Function: jit_context_t jit_value_get_context (jit_value_t value)

Get the context which owns a particular value.

Function: jit_constant_t jit_value_get_constant (jit_value_t 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.

Function: jit_nint jit_value_get_nint_constant (jit_value_t value)

Get the constant value within a particular value, assuming that its type is compatible with jit_type_nint.

Function: jit_nint jit_value_get_long_constant (jit_value_t value)

Get the constant value within a particular value, assuming that its type is compatible with jit_type_long.

Function: jit_float32 jit_value_get_float32_constant (jit_value_t value)

Get the constant value within a particular value, assuming that its type is compatible with jit_type_float32.

Function: jit_float64 jit_value_get_float64_constant (jit_value_t value)

Get the constant value within a particular value, assuming that its type is compatible with jit_type_float64.

Function: jit_nfloat jit_value_get_nfloat_constant (jit_value_t value)

Get the constant value within a particular value, assuming that its type is compatible with jit_type_nfloat.

Function: int jit_value_is_true (jit_value_t value)

Determine if value is constant and non-zero.

Function: int jit_constant_convert (jit_constant_t *result, const jit_constant_t *value, jit_type_t type, int overflow_check)

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 on September 17, 2016 using texi2html 5.0.