From 930e0f29ff5637f38a878d2e61352b151698a0ea Mon Sep 17 00:00:00 2001 From: Kimplul Date: Fri, 26 Apr 2024 20:09:13 +0300 Subject: add some comments here and there --- src/compile.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) (limited to 'src/compile.c') diff --git a/src/compile.c b/src/compile.c index dd4c73d..e5f232b 100644 --- a/src/compile.c +++ b/src/compile.c @@ -9,6 +9,43 @@ #include +/* we have a couple assumptions running through this code: + * 1) For each branch/jump, there's always a corresponding label + * 2) The global value array is in the virtual register JIT_V0 + * 3) At the start of a function, we immediately allocate f->max_sp slots, and + * slot indexes are relative to JIT_SP + * + * Lightening is a very cool library that provides a kind of virtual assembler, + * as in there's a number of virtual registers that map to physical registers + * and each virtual instruction maps to some number of physical instructions. + * This means that the user is required to handle register allocations, (virtual) + * code generation and possible optimizations, but with some clever tricks we can + * pretty much ignore these limitations completely. + * + * Each bytecode instruction gets compiled down to some number of virtual + * Lightening instructions that perform the same task 'as if' the instruction was + * being interpreted. This means each operation first loads its arguments from + * memory to virtual registers, does whatever with them, and stores the result into + * some memory location. This is pretty inefficient but very easy to implement, and + * even a poor JIT is almost always faster than the best bytecode. + * + * As for register allocations, as long as we ensure that each bytecode instruction + * effectively 'frees' the registers it used once it's been executed, we don't + * need to worry about it. The values are safely stored in memory where the + * input/output locs can fetch them later, and there are no register-register + * dependencies between bytecode instructions. + * + * Each operation uses at most two caller-save registers to perform its operation. + * The only callee-save register we use is JIT_V0 for the global array, so + * we can ask Lightening to not save the other callee-save registers, saving a bit of + * overhead when entering/exiting functions. + * + * There's no particularly good way to view the generated machine code, as it would + * require adding something like binutils' BFD library as a dependency. You can + * always just dump each code arena to a file and look at it later, but I + * haven't implemented it. + */ + static void compile_fn(struct fn *f); static void *alloc_arena(size_t size) @@ -29,6 +66,7 @@ static jit_operand_t formal_at(size_t i) i * sizeof(int64_t)); } +/* load value from global array/stack location l and place it into virtual register r */ static void get(jit_state_t *j, jit_gpr_t r, struct loc l) { if (l.l) { @@ -39,6 +77,7 @@ static void get(jit_state_t *j, jit_gpr_t r, struct loc l) jit_ldxi_l(j, r, JIT_V0, l.o * sizeof(int64_t)); } +/* store value from virtual register r to global array/stack location l*/ static void put(jit_state_t *j, jit_gpr_t r, struct loc l) { if (l.l) { @@ -95,6 +134,8 @@ static void compile_const(jit_state_t *j, struct insn i) static void compile_eq(jit_state_t *j, struct insn i) { + /* Lightening doesn't really have any register-register comparison + * instructions, so implement them as local branches */ get(j, JIT_R0, i.i0); get(j, JIT_R1, i.i1); jit_reloc_t branch = jit_beqr(j, JIT_R0, JIT_R1); @@ -125,6 +166,8 @@ static void compile_lt(jit_state_t *j, struct insn i) put(j, JIT_R0, i.o); } +/* helper function for compiling PRINT_DATE, Lightening doesn't handle variadics + * so do the heavy lifting with GCC (or Clang or whatever) */ static void print_date(int64_t date) { char str[11]; @@ -189,6 +232,7 @@ static void compile_print_space(jit_state_t *j, struct insn i) static void compile_label(jit_state_t *j, size_t ii, struct vec *labels) { + /* add a mapping between instruction index (ii) and current address */ vect_at(jit_addr_t, *labels, ii) = jit_address(j); } @@ -224,6 +268,9 @@ static void compile_arg(struct insn i, struct vec *params) { jit_operand_t operand; struct loc l = i.i0; + /* Lightening allows us to specify memory locations as arguments to + * function calls, and will automatically move the value from memory to + * the correct register according to the ABI */ if (l.l) { operand = jit_operand_mem(JIT_OPERAND_ABI_INT64, JIT_SP, l.o * sizeof(int64_t)); @@ -244,10 +291,14 @@ static void compile_call(jit_state_t *j, struct insn i, struct vec *params) struct fn *f = find_fn(i.v); assert(f); + /* if we haven't already compiled this function, do it now */ if (!f->arena) compile_fn(f); jit_calli(j, f->arena, vec_len(params), params->buf); + /* argument instructions are only ever directly before a call + * instruction, so there's no risk of messing up some other call's + * args and we can avoid allocating a new vector */ vec_reset(params); } @@ -271,6 +322,8 @@ static void compile_today(jit_state_t *j, struct insn i) put(j, JIT_R0, i.o); } +/* lots of these date handling instructions I've implemented as helper functions, mostly + * just for my own sanity */ static ph_date_t date_add(int64_t i0, int64_t i1) { struct tm time = tm_from_date((ph_date_t)i0); @@ -585,9 +638,13 @@ static size_t compile_fn_body(struct fn *f, jit_state_t *j) size_t size = 0; void *p = jit_end(j, &size); + /* if we had enough room to finish compilation, return 0 to signify that + * we don't have to try to compile anything again */ if (p) return 0; + /* otherwise, return how many bytes lightening estimates that we would + * need to succesfully compile this function */ return size; } @@ -599,6 +656,9 @@ static void compile_fn(struct fn *f) void *arena_base = NULL; size_t arena_size = 4096; + /* technically there should probably be a limit to how many times we + * attempt compilation, but in practice we're unlikely to ever need it. + */ while (1) { arena_base = alloc_arena(arena_size); assert(arena_base && -- cgit v1.2.3