moonshot

technical

jit

the jit is a minimal x86-64 code generator that walks skalman's REPL ASTthe same ast nodes repl_eval_node interprets in c0. and emits raw machine code into an identity-mapped page. the generated code is invoked through c0's indirect-call mechanismint fn = jit_page; return fn();, c0's function-reference feature. and the return value in rax becomes the c0 return value. no coff, no page-table changes. the kernel identity-maps the first 16 mb, jenna's pmm_alloc_page returns addresses in that range, and every page is executable by default.

architecture

instruction encoder

eighteen functions that emit raw x86-64 bytes. jit_emit(pos, byte) does a single store8 into the jit page. everything else is built on top of it, mov eax, imm32, add/sub/imul rax, rbx, cqo + idiv, cmp + setcc + movzx for comparisons, push/pop for register save and restore, call rax for function calls into kernel helpers, test + je/jmp with rel32 offsets for control flow. all immediates are byte-extracted via arithmetic (c0 has no bitwise operators).

a helper jit_byte(v, n) extracts byte n from value v using repeated division by 256. byte 7 overflows (256^7 × 256 = 2^64 wraps to 0 → divide-by-zero), so the function short-circuits to 0 for all bytes 4 and above, since all kernel addresses are identity-mapped and fit in 32 bits.

ast compiler

jit_compile_node(nd, pos) recursively walks the repl ast and emits code for every supported node kind.

right-first evaluation (compile right → push rax → compile left → pop rbx) gives rax=left, rbx=right for all binary operators with just two bytes of push/pop overhead per operation.

lifecycle

jit_init allocates one 4096-byte page. jit_compile_and_run sets the page writable, compiles the ast, emits a final ret, removes the writable bit, and calls the code. after execution, the writable bit is restored for the next compilation. the page is never simultaneously writable and executable (w^x via the writable bit).

shell integration

eval -c <expr> in skalman routes through repl_eval_jit, which does tokenize → parse → compile → execute. output is prefixed [jit] (vs [repl] for the interpreter), both for testability and to make the distinction visible. a full program with multiple statements compiles natively. loops, variables, conditionals, and function calls all work in a single pass.

compiled code can call back into c0 functions at runtime. kernel helpers live in the elf at known addresses (auto-patched by sync_addrs.sh after every rebuild). this is the current helper table.

slotfunctionpurpose
JIT_PRINT_ADDRjit_print_helper(v)print an integer + newline
JIT_PRINT_STR_ADDRjit_print_str_helper(str)print a string + newline (serial + screen)
JIT_ASSIGN_ADDRjit_assign_helper(name, len, value)create or update a repl variable

w^x protection

efer.nxe is enabled at boot (bit 11 in the efer msr), so bit 63 in a page-table entry controls executability. the jit page toggles between two modes.

modeptewnxeffect
compilephys + 3 + NXyesyescan write, cannot execute
executephys + 1nonocan execute, cannot write

jit_set_writable(w) walks the four-level identity-mapped page table, clears the nx bit arithmetically (c0's truncate-toward-zero division gives wrong results on negative pte values), and stores the new pte. no tlb flush, a write to a pte invalidates any cached tlb entries for that virtual address on x86.