7 Customizations

Frequently it is desirable to have more control over how code is generated or how memory is used during jit generation or execution.

7.1 Memory functions

To aid in complete control of memory allocation and deallocation GNU lightning provides wrappers that default to standard malloc, realloc and free. These are loosely based on the GNU GMP counterparts, with the difference that they use the same prototype of the system allocation functions, that is, no size for free or old_size for realloc.

Function: void jit_set_memory_functions (
void *(*alloc_func_ptr) (size_t),
void *(*realloc_func_ptr) (void *, size_t),
void (*free_func_ptr) (void *))

GNU lightning guarantees that memory is only allocated or released using these wrapped functions, but you must note that if lightning was linked to GNU binutils, malloc is probably will be called multiple times from there when initializing the disassembler.

Because init_jit may call memory functions, if you need to call jit_set_memory_functions, it must be called before init_jit, otherwise, when calling finish_jit, a pointer allocated with the previous or default wrappers will be passed.

Function: void jit_get_memory_functions (
void *(**alloc_func_ptr) (size_t),
void *(**realloc_func_ptr) (void *, size_t),
void (**free_func_ptr) (void *))

Get the current memory allocation function. Also, unlike the GNU GMP counterpart, it is an error to pass NULL pointers as arguments.

7.2 Protection

Unless an alternate code buffer is used (see below), jit_emit set the access protections that the code buffer’s memory can be read and executed, but not modified. One can use the following functions after jit_emit but before jit_clear to temporarily lift the protection:

Function: void jit_unprotect ()

Changes the access protection that the code buffer’s memory can be read and modified. Before the emitted code can be invoked, jit_protect has to be called to reset the change.

This procedure has no effect when an alternate code buffer (see below) is used.

Function: void jit_protect ()

Changes the access protection that the code buffer’s memory can be read and executed.

This procedure has no effect when an alternate code buffer (see below) is used.

7.3 Alternate code buffer

To instruct GNU lightning to use an alternate code buffer it is required to call jit_realize before jit_emit, and then query states and customize as appropriate.

Function: void jit_realize ()

Must be called once, before jit_emit, to instruct GNU lightning that no other jit_xyz call will be made.

Function: jit_pointer_t jit_get_code (jit_word_t *code_size)

Returns NULL or the previous value set with jit_set_code, and sets the code_size argument to an appropriate value. If jit_get_code is called before jit_emit, the code_size argument is set to the expected amount of bytes required to generate code. If jit_get_code is called after jit_emit, the code_size argument is set to the exact amount of bytes used by the code.

Function: void jit_set_code (jit_ponter_t code, jit_word_t size)

Instructs GNU lightning to output to the code argument and use size as a guard to not write to invalid memory. If during jit_emit GNU lightning finds out that the code would not fit in size bytes, it halts code emit and returns NULL.

A simple example of a loop using an alternate buffer is:

  jit_uint8_t   *code;
  int           *(func)(int);      /* function pointer */
  jit_word_t     code_size;
  jit_word_t     real_code_size;
  ...
  jit_realize();                   /* ready to generate code */
  jit_get_code(&code_size);        /* get expected code size */
  code_size = (code_size + 4095) & -4096;
  do (;;) {
    code = mmap(NULL, code_size, PROT_EXEC | PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANON, -1, 0);
    jit_set_code(code, code_size);
    if ((func = jit_emit()) == NULL) {
      munmap(code, code_size);
      code_size += 4096;
    }
  } while (func == NULL);
  jit_get_code(&real_code_size);   /* query exact size of the code */

The first call to jit_get_code should return NULL and set the code_size argument to the expected amount of bytes required to emit code. The second call to jit_get_code is after a successful call to jit_emit, and will return the value previously set with jit_set_code and set the real_code_size argument to the exact amount of bytes used to emit the code.

7.4 Alternate data buffer

Sometimes it may be desirable to customize how, or to prevent GNU lightning from using an extra buffer for constants or debug annotation. Usually when also using an alternate code buffer.

Function: jit_pointer_t jit_get_data (jit_word_t *data_size, jit_word_t *note_size)

Returns NULL or the previous value set with jit_set_data, and sets the data_size argument to how many bytes are required for the constants data buffer, and note_size to how many bytes are required to store the debug note information. Note that it always preallocate one debug note entry even if jit_name or jit_note are never called, but will return zero in the data_size argument if no constant is required; constants are only used for the float and double operations that have an immediate argument, and not in all GNU lightning ports.

Function: void jit_set_data (jit_pointer_t data, jit_word_t size, jit_word_t flags)

data can be NULL if disabling constants and annotations, otherwise, a valid pointer must be passed. An assertion is done that the data will fit in size bytes (but that is a noop if GNU lightning was built with -DNDEBUG).

size tells the space in bytes available in data.

flags can be zero to tell to just use the alternate data buffer, or a composition of JIT_DISABLE_DATA and JIT_DISABLE_NOTE

JIT_DISABLE_DATA

Instructs GNU lightning to not use a constant table, but to use an alternate method to synthesize those, usually with a larger code sequence using stack space to transfer the value from a GPR to a FPR register.

JIT_DISABLE_NOTE

Instructs GNU lightning to not store file or function name, and line numbers in the constant buffer.

A simple example of a preventing usage of a data buffer is:

  ...
  jit_realize();                        /* ready to generate code */
  jit_get_data(NULL, NULL);
  jit_set_data(NULL, 0, JIT_DISABLE_DATA | JIT_DISABLE_NOTE);
  ...

Or to only use a data buffer, if required:

  jit_uint8_t   *data;
  jit_word_t     data_size;
  ...
  jit_realize();                        /* ready to generate code */
  jit_get_data(&data_size, NULL);
  if (data_size)
    data = malloc(data_size);
  else
    data = NULL;
  jit_set_data(data, data_size, JIT_DISABLE_NOTE);
  ...
  if (data)
    free(data);
  ...