NAME
src/jit.c - JIT
DESCRIPTION
JIT (Just In Time) compilation converts bytecode to native machine code instructions and executes the generated instruction sequence directly.
Actually it's not really just in time, it's just before this piece of code is used and not per subroutine or even opcode, it works per bytecode segment.
Functions
static void insert_fixup_targets(PARROT_INTERP, char *branch, size_t limit)
Look at fixups,
mark all fixup entries as branch target.TODO: actually this is wrong: fixups belong only to one code segment.
The code below doesn't check,
for which segments the fixups are inserted.
static void make_branch_list(PARROT_INTERP, Parrot_jit_optimizer_t *optimizer, opcode_t *code_start, opcode_t *code_end)
- branch instructions have
JIT_BRANCH_SOURCE
- opcodes jumped to have
JIT_BRANCH_TARGET
- mapped arguments have register type + 1 and finally
- after register allocation these have the processor register that got mapped
static void set_register_usage(PARROT_INTERP, Parrot_jit_info_t *jit_info, Parrot_jit_optimizer_section_ptr cur_section, op_info_t *op_info, opcode_t *cur_op, opcode_t *code_start)
Sets the register usage counts.
static void init_regusage(PARROT_INTERP, Parrot_jit_optimizer_section_ptr cur_section)
Init all register usage to Parrot register usage.
Used when JITting subroutines to registers only
static void make_sections(PARROT_INTERP, Parrot_jit_info_t *jit_info, opcode_t *code_start, opcode_t *code_end)
I386 has JITed vtables,
which have the vtable# in extcall.This static void make_branch_targets( Parrot_jit_optimizer_t *optimizer, opcode_t *code_start)
Makes the branch targets.
static void sort_registers(Parrot_jit_info_t *jit_info)
Sorts the Parrot registers prior to mapping them to actual hardware registers.
static void assign_registers(PARROT_INTERP, Parrot_jit_info_t *jit_info, Parrot_jit_optimizer_section_ptr cur_section, opcode_t *code_start, int from_imcc)
Called by static void map_registers(PARROT_INTERP, Parrot_jit_info_t *jit_info, opcode_t *code_start)
Maps the most used Parrot registers to hardware registers.
static void debug_sections(PARROT_INTERP, Parrot_jit_optimizer_t *optimizer, opcode_t *code_start)
Prints out debugging info.
static Parrot_jit_optimizer_t *optimize_jit(PARROT_INTERP, Parrot_jit_info_t *jit_info, opcode_t *code_start, opcode_t *code_end)
Called by static Parrot_jit_optimizer_t *optimize_imcc_jit(PARROT_INTERP, Parrot_jit_info_t *jit_info, opcode_t *code_start, opcode_t *code_end, PackFile_Segment *jit_seg)
Generate optimizer stuff from the size_t reg_offs(int typ, int i)
Returns the offset of register static void Parrot_jit_load_registers(Parrot_jit_info_t *jit_info, PARROT_INTERP, int volatiles)
Load registers for the current section from parrot to processor registers. If static void Parrot_jit_save_registers(Parrot_jit_info_t *jit_info, PARROT_INTERP, int volatiles)
Save registers for the current section. If void Parrot_destroy_jit(void *ptr)
Frees the memory used by the JIT subsystem.
Parrot_jit_info_t *parrot_build_asm(PARROT_INTERP, opcode_t *code_start, opcode_t *code_end, void *objfile, enum_jit_code_type)
This is the main function of the JIT code generator.It loops over the bytecode, calling the code generating routines for each opcode.The information obtained is used to perform certain types of fixups on native code, as well as by the native code itself to convert bytecode program counters values to hardware program counter values.Finally this code here is used to generate native executables (or better object files that are linked to executables), if EXEC_CAPABLE is defined. This functionality is triggered by
void Parrot_jit_newfixup(Parrot_jit_info_t *jit_info)
Remember the current position in the native code for later update.
optimizer->map_branch
parallels the opcodes with a list of branch information and register mapping information
Parrot_jit_vtable_n_op()
does use register mappings.
map_registers()
to actually assign the Parrot registers to hardware registers.TODOBefore actually assigning registers,
we should optimize a bit:1) calculate max use count of register types for all sections2) calculate costs for register preserving and restoring for two different allocation strategies:
a) allocate non-volatiles first overhead for jit_begin, jit_end: - 2 * max_used_non_volatile registers overhead for register preserving around non-jitted sections: - only used IN arguments are saved - only OUT non-volatile arguments are restored b) allocate volatiles first no overhead for jit_begin, jit_end overhead per JITed op that calls a C function: - 2 * n_used_volatiles_to_preserve for each call overhead for register preserving around non-jitted sections: - all volatiles are saved and restored around non-jitted sectionsNB for all cost estimations size does matter: a 64bit double counts as two 32bit ints. Opcode count is assumed to be just one.3) depending on costs from 2) use one of the strategies That does still not account for any usage patterns. Imcc has loop nesting depth, but that's not available here. OTOH smaller code tends to perform better because of better cache usage.Usage analysis could show that a mixture of both strategies is best, e.g: allocate 2-4 non-volatiles and the rest from volatiles. But that would complicate the allocation code a bit.
parrot_build_asm()
to run the optimizer.
_JIT
section in the packfile.
typ[i]
.src/jit/arch/jit_emit.h has to define Parrot_jit_emit_get_base_reg_no(pc)
volatiles
is true, this code is used to restore these registers in JITted code that calls out to Parrot.
volatiles
is true, this code is used to preserve these registers in JITted code that calls out to Parrot.
parrot -o foo.o foo.pirwhich uses the JIT engine to translate to native code inside the object file.
SEE ALSO
src/jit.h, docs/jit.pod, src/jit_debug.c, src/jit/$jitcpuarch/jit_emit.h, jit/$jitcpuarch/core.jit.