code wiki / _hdl_build / nx_rv64_jit.nx
nx_rv64_jit.nx source
↩ module page · 217 lines · 26175 B
1// nx_rv64_jit.nx -- a SOVEREIGN RV64 -> x86-64 JIT (dynamic binary translation): the SOTA emulator technique, above
2// the decode-cache interpreter. It TRANSLATES a basic block of predecoded RV64 ops into NATIVE x86-64 machine code
3// (per-instruction, not a shape template): the 32 RV64 registers live in a caller-provided i64 array (base in rdi per
4// SysV); each RV64 instruction emits x86 that loads operands from reg[], computes in rax, stores back; a backward
5// branch emits a NATIVE conditional jump so a loop runs entirely in host code. The block is mmap'd RWX and CALLED
6// (the RWX + cast-fn-ptr primitive is proven by nx_nxe_loader). Equivalence with the golden behavioral sim is proven
7// by nx_rv64_jit_gate; the speedup is measured there. Subset: LUI/ADDI/ADD/SUB/ANDI/ORI/SLLI + BEQ/BNE/BLT/BGE
8// (backward branches). license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_rv64_fast.nx" // FK_* op kinds (shared decode table)
11
12func j_b(out: *u8, o: i64, v: i64) -> i64 { out[o]=(v&0xff) as u8; return o+1 }
13func j_w32(out: *u8, o: i64, v: i64) -> i64 { out[o]=(v&0xff) as u8; out[o+1]=((v>>8)&0xff) as u8; out[o+2]=((v>>16)&0xff) as u8; out[o+3]=((v>>24)&0xff) as u8; return o+4 }
14// x86-64 emitters (base rdi = ®[0]; scratch rax). disp32 = reg_index*8.
15func e_load(out: *u8, o: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x8B); p=j_b(out,p,0x87); p=j_w32(out,p,ri*8); return p } // mov rax,[rdi+ri*8]
16func e_store(out: *u8, o: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x89); p=j_b(out,p,0x87); p=j_w32(out,p,ri*8); return p } // mov [rdi+ri*8],rax
17func e_movimm(out: *u8, o: i64, imm: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xC7); p=j_b(out,p,0xC0); p=j_w32(out,p,imm); return p } // mov rax,imm32(sx)
18func e_addimm(out: *u8, o: i64, imm: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x05); p=j_w32(out,p,imm); return p } // add rax,imm32
19func e_andimm(out: *u8, o: i64, imm: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x25); p=j_w32(out,p,imm); return p } // and rax,imm32
20func e_orimm(out: *u8, o: i64, imm: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x0D); p=j_w32(out,p,imm); return p } // or rax,imm32
21func e_opreg(out: *u8, o: i64, opc: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,opc); p=j_b(out,p,0x87); p=j_w32(out,p,ri*8); return p } // <op> rax,[rdi+ri*8]
22func e_imul_mem(out: *u8, o: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x0F); p=j_b(out,p,0xAF); p=j_b(out,p,0x87); p=j_w32(out,p,ri*8); return p } // imul rax,[rdi+ri*8]
23func e_mulhi_u(out: *u8, o: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xF7); p=j_b(out,p,0xA7); p=j_w32(out,p,ri*8); return p } // mul qword[rdi+ri*8] -> rdx:rax (unsigned)
24func e_mulhi_s(out: *u8, o: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xF7); p=j_b(out,p,0xAF); p=j_w32(out,p,ri*8); return p } // imul qword[rdi+ri*8] -> rdx:rax (signed)
25func e_mov_rax_rdx(out: *u8, o: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x89); p=j_b(out,p,0xD0); return p } // mov rax,rdx (grab the high half)
26func e_shl(out: *u8, o: i64, sh: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xC1); p=j_b(out,p,0xE0); p=j_b(out,p,sh); return p } // shl rax,imm8
27func e_jcc(out: *u8, o: i64, cc: i64, rel: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x0F); p=j_b(out,p,cc); p=j_w32(out,p,rel); return p } // jcc rel32
28// *W (32-bit word) emitters: operate on EAX (32-bit, no REX.W), then MOVSXD rax,eax to sign-extend the low-32 result
29// to 64 (RV64 *W semantics). load32=mov eax,[rdi+ri*8]; the arithmetic mirrors the 64-bit forms without the 0x48 REX.
30func e_load32(out: *u8, o: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x8B); p=j_b(out,p,0x87); p=j_w32(out,p,ri*8); return p } // mov eax,[rdi+ri*8]
31func e_op32_mem(out: *u8, o: i64, opc: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,opc); p=j_b(out,p,0x87); p=j_w32(out,p,ri*8); return p } // <op> eax,[rdi+ri*8] (03 add / 2B sub)
32func e_imul32_mem(out: *u8, o: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x0F); p=j_b(out,p,0xAF); p=j_b(out,p,0x87); p=j_w32(out,p,ri*8); return p } // imul eax,[rdi+ri*8]
33func e_addimm32(out: *u8, o: i64, imm: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x05); p=j_w32(out,p,imm); return p } // add eax,imm32
34func e_shl32(out: *u8, o: i64, sh: i64) -> i64 { var p: i64=o; p=j_b(out,p,0xC1); p=j_b(out,p,0xE0); p=j_b(out,p,sh); return p } // shl eax,imm8
35func e_shr32(out: *u8, o: i64, sh: i64) -> i64 { var p: i64=o; p=j_b(out,p,0xC1); p=j_b(out,p,0xE8); p=j_b(out,p,sh); return p } // shr eax,imm8
36func e_sar32(out: *u8, o: i64, sh: i64) -> i64 { var p: i64=o; p=j_b(out,p,0xC1); p=j_b(out,p,0xF8); p=j_b(out,p,sh); return p } // sar eax,imm8
37func e_movsxd(out: *u8, o: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x63); p=j_b(out,p,0xC0); return p } // movsxd rax,eax (sext32->64)
38func e_ret(out: *u8, o: i64) -> i64 { return j_b(out,o,0xC3) }
39func jit_mmap_rwx(size: i64) -> *u8 { let r: i64 = __syscall(SYS_MMAP, 0, size, 7, 0x22, -1, 0); return r as *u8 }
40func j_w64(out: *u8, o: i64, v: i64) -> i64 { var i: i64=0; while i<8 { out[o+i]=((v>>(i*8))&0xff) as u8; i=i+1 } return o+8 }
41// memory-op emitters. rsi = biased guest-mem base (host_ptr - 0x80000000), baked into the block; rax = computed guest
42// address AND load destination (the CPU reads the [rsi+rax] operand before writing rax, so aliasing is fine); rdx =
43// store value. SIB byte 0x06 = [rsi + rax*1].
44func e_movimm64_rsi(out: *u8, o: i64, imm: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xBE); p=j_w64(out,p,imm); return p } // mov rsi, imm64
45func e_ld_rdx(out: *u8, o: i64, ri: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x8B); p=j_b(out,p,0x97); p=j_w32(out,p,ri*8); return p } // mov rdx,[rdi+ri*8]
46func e_lw_mem(out: *u8, o: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x63); p=j_b(out,p,0x04); p=j_b(out,p,0x06); return p } // movsxd rax,dword[rsi+rax]
47func e_ld_mem(out: *u8, o: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x8B); p=j_b(out,p,0x04); p=j_b(out,p,0x06); return p } // mov rax,[rsi+rax]
48func e_sw_mem(out: *u8, o: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x89); p=j_b(out,p,0x14); p=j_b(out,p,0x06); return p } // mov [rsi+rax],edx (store 32)
49func e_sd_mem(out: *u8, o: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x89); p=j_b(out,p,0x14); p=j_b(out,p,0x06); return p } // mov [rsi+rax],rdx (store 64)
50func e_shr(out: *u8, o: i64, sh: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xC1); p=j_b(out,p,0xE8); p=j_b(out,p,sh); return p } // shr rax,imm8 (SRLI)
51func e_sar(out: *u8, o: i64, sh: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xC1); p=j_b(out,p,0xF8); p=j_b(out,p,sh); return p } // sar rax,imm8 (SRAI)
52// guest-memory base for JIT'd loads/stores (biased: host_ptr - 0x80000000) + the guest memory SIZE (bound). set by
53// the caller before jit_compile so JIT'd loads/stores can bounds-check EXACTLY like the interpreter (fk_memok).
54static jit_membase: i64 = 0
55static jit_membytes: i64 = 0
56func jit_set_membase(v: i64, membytes: i64) -> i64 { jit_membase = v; jit_membytes = membytes; return 0 }
57// emit a guest-memory BOUNDS CHECK before a load/store: offset = rax - 0x80000000 (in rdx); if (unsigned)offset >
58// membytes-width -> jump over the access (a negative offset wraps to huge-unsigned, so one unsigned compare covers
59// both under- and over-run). returns o with a `ja rel8` PLACEHOLDER whose rel8 byte is at o-1 (caller patches it to
60// skip the access) -- so an OOB access is a NO-OP, matching the interpreter's fk_memok (never escapes to host memory).
61func e_bcheck(out: *u8, o: i64, width: i64) -> i64 {
62 var p: i64=o
63 p=j_b(out,p,0x48); p=j_b(out,p,0x8D); p=j_b(out,p,0x90); p=j_w32(out,p,0x80000000) // lea rdx,[rax-2^31] (=guest offset)
64 p=j_b(out,p,0x48); p=j_b(out,p,0x81); p=j_b(out,p,0xFA); p=j_w32(out,p,jit_membytes-width) // cmp rdx, membytes-width
65 p=j_b(out,p,0x77); p=j_b(out,p,0x00) // ja rel8 (placeholder)
66 return p
67}
68
69// TRANSLATE predecoded ops [0, nidx) into native x86-64 in `out`. records each op's x86 offset so backward branches
70// resolve. returns the x86 byte length, or -1 on an unsupported op (fail-loud, no silent miscompile).
71func jit_compile(opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64, nidx: i64, out: *u8, x86off: *i64) -> i64 {
72 var o: i64 = 0; var i: i64 = 0
73 // fixup table for FORWARD branches/jumps (target x86 offset not yet emitted): patched after the emit pass.
74 let fx_loc: *i64 = sys_mmap(nidx*8+64) as *i64; let fx_tgt: *i64 = sys_mmap(nidx*8+64) as *i64; let fx_end: *i64 = sys_mmap(nidx*8+64) as *i64; var nfx: i64 = 0
75 // prologue: save + bake the guest-mem base into rsi (rdx = store scratch). rsi/rdx restored at the epilogue so the
76 // block preserves the caller's ABI regardless of which regs NishiLang treats as callee-saved.
77 o=j_b(out,o,0x56); o=j_b(out,o,0x52); o=e_movimm64_rsi(out,o,jit_membase) // push rsi; push rdx; mov rsi,membase
78 while i < nidx {
79 x86off[i] = o
80 let k: i64=opk[i]; let d: i64=rd[i]; let a: i64=rs1[i]; let b: i64=rs2[i]; let im: i64=imm[i]
81 var ok: i64 = 0
82 if k==FK_ADDI { o=e_load(out,o,a); o=e_addimm(out,o,im); if d!=0 { o=e_store(out,o,d) } ok=1 }
83 if k==FK_LUI { o=e_movimm(out,o,im); if d!=0 { o=e_store(out,o,d) } ok=1 }
84 if k==FK_ADD { o=e_load(out,o,a); o=e_opreg(out,o,0x03,b); if d!=0 { o=e_store(out,o,d) } ok=1 }
85 if k==FK_SUB { o=e_load(out,o,a); o=e_opreg(out,o,0x2B,b); if d!=0 { o=e_store(out,o,d) } ok=1 }
86 if k==FK_AND { o=e_load(out,o,a); o=e_opreg(out,o,0x23,b); if d!=0 { o=e_store(out,o,d) } ok=1 }
87 if k==FK_OR { o=e_load(out,o,a); o=e_opreg(out,o,0x0B,b); if d!=0 { o=e_store(out,o,d) } ok=1 }
88 if k==FK_ANDI { o=e_load(out,o,a); o=e_andimm(out,o,im); if d!=0 { o=e_store(out,o,d) } ok=1 }
89 if k==FK_ORI { o=e_load(out,o,a); o=e_orimm(out,o,im); if d!=0 { o=e_store(out,o,d) } ok=1 }
90 if k==FK_SLLI { o=e_load(out,o,a); o=e_shl(out,o,im); if d!=0 { o=e_store(out,o,d) } ok=1 }
91 if k==FK_MUL { o=e_load(out,o,a); o=e_imul_mem(out,o,b); if d!=0 { o=e_store(out,o,d) } ok=1 } // M ext: MUL (imul, low 64)
92 // *W (32-bit word) ops: compute in EAX, MOVSXD to sign-extend low-32 -> 64. Real `int` loops (i++ = addiw etc.)
93 // are full of these -- without them the JIT never fires on real code. (register-shift + div *W tier to interp.)
94 if k==FK_ADDIW { o=e_load32(out,o,a); o=e_addimm32(out,o,im); o=e_movsxd(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
95 if k==FK_ADDW { o=e_load32(out,o,a); o=e_op32_mem(out,o,0x03,b); o=e_movsxd(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
96 if k==FK_SUBW { o=e_load32(out,o,a); o=e_op32_mem(out,o,0x2B,b); o=e_movsxd(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
97 if k==FK_MULW { o=e_load32(out,o,a); o=e_imul32_mem(out,o,b); o=e_movsxd(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
98 if k==FK_SLLIW { o=e_load32(out,o,a); o=e_shl32(out,o,im); o=e_movsxd(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
99 if k==FK_SRLIW { o=e_load32(out,o,a); o=e_shr32(out,o,im); o=e_movsxd(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
100 if k==FK_SRAIW { o=e_load32(out,o,a); o=e_sar32(out,o,im); o=e_movsxd(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
101 // MULH/MULHU: one-operand imul/mul -> rdx:rax; high half in rdx -> mov to rax -> store. rdx is saved/restored by
102 // the prologue push/epilogue pop, so clobbering it here is safe. MULHSU has no x86 form -> tiers to the interpreter.
103 if k==FK_MULH { o=e_load(out,o,a); o=e_mulhi_s(out,o,b); o=e_mov_rax_rdx(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
104 if k==FK_MULHU { o=e_load(out,o,a); o=e_mulhi_u(out,o,b); o=e_mov_rax_rdx(out,o); if d!=0 { o=e_store(out,o,d) } ok=1 }
105 if k==FK_SRLI { o=e_load(out,o,a); o=e_shr(out,o,im); if d!=0 { o=e_store(out,o,d) } ok=1 }
106 if k==FK_SRAI { o=e_load(out,o,a); o=e_sar(out,o,im); if d!=0 { o=e_store(out,o,d) } ok=1 }
107 // guest memory: addr = reg[rs1]+imm (in rax); [rsi+rax] is the host location. loads use rax as dest; stores
108 // put reg[rs2] in rdx first. NOTE: a store to the finisher MMIO is NOT special-cased -- JIT'd blocks are pure
109 // compute + RAM; the finisher/halt stays the interpreter's job (blocks with it must not be JIT'd).
110 // each memory op is BOUNDS-CHECKED (e_bcheck): an out-of-range guest address skips the access, exactly like the
111 // interpreter's fk_memok -- the JIT never escapes the guest-memory sandbox. loads: addr in rax, then check, then
112 // load; stores: addr in rax, then check, then load-value-into-rdx + store (so an OOB store skips both).
113 if k==FK_LW { o=e_load(out,o,a); o=e_addimm(out,o,im); o=e_bcheck(out,o,4); let jp: i64=o-1; o=e_lw_mem(out,o); if d!=0 { o=e_store(out,o,d) } out[jp]=((o-(jp+1))&0xff) as u8; ok=1 }
114 if k==FK_LD { o=e_load(out,o,a); o=e_addimm(out,o,im); o=e_bcheck(out,o,8); let jp: i64=o-1; o=e_ld_mem(out,o); if d!=0 { o=e_store(out,o,d) } out[jp]=((o-(jp+1))&0xff) as u8; ok=1 }
115 if k==FK_SW { o=e_load(out,o,a); o=e_addimm(out,o,im); o=e_bcheck(out,o,4); let jp: i64=o-1; o=e_ld_rdx(out,o,b); o=e_sw_mem(out,o); out[jp]=((o-(jp+1))&0xff) as u8; ok=1 }
116 if k==FK_SD { o=e_load(out,o,a); o=e_addimm(out,o,im); o=e_bcheck(out,o,8); let jp: i64=o-1; o=e_ld_rdx(out,o,b); o=e_sd_mem(out,o); out[jp]=((o-(jp+1))&0xff) as u8; ok=1 }
117 // conditional branches, FORWARD or backward. cmp rs1,rs2 (via rax) then jcc; a forward target (not yet
118 // emitted) gets a fixup patched after the pass; a backward target resolves immediately. (tgt out of range -> -1.)
119 if k==FK_BEQ { o=e_load(out,o,a); o=e_opreg(out,o,0x3B,b); let tgt: i64=i+(im/4); if tgt<0 { return 0-1 } if tgt>=nidx { return 0-1 } let js: i64=o; if tgt<=i { o=e_jcc(out,o,0x84,x86off[tgt]-(js+6)) } else { o=e_jcc(out,o,0x84,0); fx_loc[nfx]=js+2; fx_tgt[nfx]=tgt; fx_end[nfx]=js+6; nfx=nfx+1 } ok=1 }
120 if k==FK_BNE { o=e_load(out,o,a); o=e_opreg(out,o,0x3B,b); let tgt: i64=i+(im/4); if tgt<0 { return 0-1 } if tgt>=nidx { return 0-1 } let js: i64=o; if tgt<=i { o=e_jcc(out,o,0x85,x86off[tgt]-(js+6)) } else { o=e_jcc(out,o,0x85,0); fx_loc[nfx]=js+2; fx_tgt[nfx]=tgt; fx_end[nfx]=js+6; nfx=nfx+1 } ok=1 }
121 if k==FK_BLT { o=e_load(out,o,a); o=e_opreg(out,o,0x3B,b); let tgt: i64=i+(im/4); if tgt<0 { return 0-1 } if tgt>=nidx { return 0-1 } let js: i64=o; if tgt<=i { o=e_jcc(out,o,0x8C,x86off[tgt]-(js+6)) } else { o=e_jcc(out,o,0x8C,0); fx_loc[nfx]=js+2; fx_tgt[nfx]=tgt; fx_end[nfx]=js+6; nfx=nfx+1 } ok=1 }
122 if k==FK_BGE { o=e_load(out,o,a); o=e_opreg(out,o,0x3B,b); let tgt: i64=i+(im/4); if tgt<0 { return 0-1 } if tgt>=nidx { return 0-1 } let js: i64=o; if tgt<=i { o=e_jcc(out,o,0x8D,x86off[tgt]-(js+6)) } else { o=e_jcc(out,o,0x8D,0); fx_loc[nfx]=js+2; fx_tgt[nfx]=tgt; fx_end[nfx]=js+6; nfx=nfx+1 } ok=1 }
123 // unconditional jump: JAL x0 = j (rd==0). jal-with-link (rd!=0) unsupported here (no guest PC in the JIT).
124 if k==FK_JAL { if d==0 { let tgt: i64=i+(im/4); if tgt<0 { return 0-1 } if tgt>=nidx { return 0-1 } let js: i64=o; o=j_b(out,o,0xE9); o=j_w32(out,o,0); if tgt<=i { j_w32(out,js+1,x86off[tgt]-(js+5)) } else { fx_loc[nfx]=js+1; fx_tgt[nfx]=tgt; fx_end[nfx]=js+5; nfx=nfx+1 } ok=1 } }
125 if ok==0 { return 0-1 } // unsupported op -> fail-loud (do not silently miscompile)
126 i=i+1
127 }
128 o=j_b(out,o,0x5A); o=j_b(out,o,0x5E); o=e_ret(out,o) // epilogue: pop rdx; pop rsi; ret
129 // patch forward fixups now that every op's x86 offset is known.
130 var f: i64=0
131 while f<nfx { j_w32(out, fx_loc[f], x86off[fx_tgt[f]] - fx_end[f]); f=f+1 }
132 return o
133}
134// mmap the translated block RWX + CALL it with rdi = ®[0]; the RV64 registers are updated in place.
135func jit_run(x86: *u8, len: i64, reg: *i64) -> i64 {
136 let ex: *u8 = jit_mmap_rwx(len + 64)
137 var i: i64 = 0; while i < len { ex[i] = x86[i]; i = i + 1 }
138 let fp: func(i64) -> i64 = (ex as i64) as func(i64) -> i64
139 fp(reg as i64)
140 return 0
141}
142// ============================================================================================================
143// REGISTER-ALLOCATED JIT (the SOTA quality step over the memory-per-op JIT): keep the block's hot RV64 registers in
144// x86-64 registers for the whole block -- fill from reg[] at entry, spill at exit -- so a loop body does ZERO memory
145// traffic (the memory-per-op JIT loads/stores every op). Pool = 3 caller-saved x86 regs {rcx,rdx,rsi} (uniform low
146// encoding, no push/pop, no REX.B); rax=scratch, rdi=reg[] base. TIERED + FAIL-SAFE: any op/pattern this fast path
147// does not handle -> returns -2 so the caller falls back to the proven memory JIT (never a miscompile). Handles the
148// common idioms (li via ADDI-from-x0, mv via ADD/OR-from-x0, in-place ADDI, reg-reg ALU, backward branches).
149// ============================================================================================================
150func ra_pool(s: i64) -> i64 { if s==0 { return 1 } if s==1 { return 2 } return 6 } // rcx, rdx, rsi
151func era_fill(out: *u8, o: i64, preg: i64, disp: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x8B); p=j_b(out,p,0x87|(preg<<3)); p=j_w32(out,p,disp); return p } // mov preg,[rdi+disp]
152func era_spill(out: *u8, o: i64, preg: i64, disp: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x89); p=j_b(out,p,0x87|(preg<<3)); p=j_w32(out,p,disp); return p } // mov [rdi+disp],preg
153func era_mov_rr(out: *u8, o: i64, dst: i64, src: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x8B); p=j_b(out,p,0xC0|(dst<<3)|src); return p } // mov dst,src
154func era_alu_rr(out: *u8, o: i64, opc: i64, dst: i64, src: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,opc); p=j_b(out,p,0xC0|(dst<<3)|src); return p } // <op> dst,src (03/2B/23/0B/3B)
155func era_alu_ri(out: *u8, o: i64, slash: i64, dst: i64, imm: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0x81); p=j_b(out,p,0xC0|(slash<<3)|dst); p=j_w32(out,p,imm); return p } // add(/0)/or(/1)/and(/4) dst,imm32
156func era_shl_ri(out: *u8, o: i64, dst: i64, sh: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xC1); p=j_b(out,p,0xE0|dst); p=j_b(out,p,sh); return p } // shl dst,imm8
157func era_mov_ri(out: *u8, o: i64, dst: i64, imm: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x48); p=j_b(out,p,0xC7); p=j_b(out,p,0xC0|dst); p=j_w32(out,p,imm); return p } // mov dst,imm32(sx)
158func era_jcc(out: *u8, o: i64, cc: i64, rel: i64) -> i64 { var p: i64=o; p=j_b(out,p,0x0F); p=j_b(out,p,cc); p=j_w32(out,p,rel); return p }
159// allocate a pool reg to nonzero RV64 reg r; 0 ok / -2 pool-full. x0 is never allocated.
160func jit_ra_alloc(rvmap: *i64, nbox: *i64, r: i64) -> i64 { if r==0 { return 0 } if rvmap[r]>=0 { return 0 } if nbox[0]>=3 { return 0-2 } rvmap[r]=ra_pool(nbox[0]); nbox[0]=nbox[0]+1; return 0 }
161
162// register-allocated compile. returns x86 length, or -2 (ineligible -> caller uses the memory JIT). rvmap = caller
163// 32-array (filled with the RV64->x86 mapping). x86off = caller nidx-array (per-op x86 offsets for branch resolution).
164func jit_compile_ra(opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64, nidx: i64, out: *u8, x86off: *i64, rvmap: *i64) -> i64 {
165 var i: i64=0; while i<32 { rvmap[i]=-1; i=i+1 }
166 let nbox: *i64 = sys_mmap(16) as *i64; nbox[0]=0
167 // ---- pass 1: eligibility + allocation (FLAT dispatch -- one kind matches; matched==0 -> bail -2). ----
168 i=0
169 while i<nidx {
170 let k: i64=opk[i]; let d: i64=rd[i]; let a: i64=rs1[i]; let b: i64=rs2[i]; let im: i64=imm[i]
171 var matched: i64=0
172 if k==FK_LUI { if d==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } matched=1 }
173 if k==FK_ADDI { if d==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } if a!=0 { if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } } matched=1 }
174 if k==FK_ANDI { if d==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } if a!=0 { if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } } matched=1 }
175 if k==FK_ORI { if d==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } if a!=0 { if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } } matched=1 }
176 if k==FK_SLLI { if d==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } if a!=0 { if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } } matched=1 }
177 if k==FK_ADD { if d==0 { return 0-2 } if a==0 { if b==0 { return 0-2 } } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } if a!=0 { if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } } if b!=0 { if jit_ra_alloc(rvmap,nbox,b)<0 { return 0-2 } } matched=1 }
178 if k==FK_SUB { if d==0 { return 0-2 } if a==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } if b!=0 { if jit_ra_alloc(rvmap,nbox,b)<0 { return 0-2 } } matched=1 }
179 if k==FK_AND { if d==0 { return 0-2 } if a==0 { return 0-2 } if b==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,b)<0 { return 0-2 } matched=1 }
180 if k==FK_OR { if d==0 { return 0-2 } if a==0 { if b==0 { return 0-2 } } if jit_ra_alloc(rvmap,nbox,d)<0 { return 0-2 } if a!=0 { if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } } if b!=0 { if jit_ra_alloc(rvmap,nbox,b)<0 { return 0-2 } } matched=1 }
181 if k==FK_BEQ { let tgt: i64=i+(im/4); if tgt>i { return 0-2 } if a==0 { return 0-2 } if b==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,b)<0 { return 0-2 } matched=1 }
182 if k==FK_BNE { let tgt: i64=i+(im/4); if tgt>i { return 0-2 } if a==0 { return 0-2 } if b==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,b)<0 { return 0-2 } matched=1 }
183 if k==FK_BLT { let tgt: i64=i+(im/4); if tgt>i { return 0-2 } if a==0 { return 0-2 } if b==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,b)<0 { return 0-2 } matched=1 }
184 if k==FK_BGE { let tgt: i64=i+(im/4); if tgt>i { return 0-2 } if a==0 { return 0-2 } if b==0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,a)<0 { return 0-2 } if jit_ra_alloc(rvmap,nbox,b)<0 { return 0-2 } matched=1 }
185 if matched==0 { return 0-2 }
186 i=i+1
187 }
188 // ---- pass 2: prologue = SAVE the pool regs we clobber (push; they may be callee-saved in the caller's ABI) +
189 // FILL them from reg[]; body; epilogue = SPILL back + RESTORE (pop, reverse order) + ret. ----
190 var o: i64=0
191 var s: i64=0; while s<nbox[0] { o=j_b(out,o, 0x50|ra_pool(s)); s=s+1 } // push rcx/rdx/rsi (the slots in use)
192 var r: i64=0; while r<32 { if rvmap[r]>=0 { o=era_fill(out,o,rvmap[r],r*8) } r=r+1 }
193 i=0
194 while i<nidx {
195 x86off[i]=o
196 let k: i64=opk[i]; let d: i64=rd[i]; let a: i64=rs1[i]; let b: i64=rs2[i]; let im: i64=imm[i]
197 if k==FK_LUI { o=era_mov_ri(out,o,rvmap[d],im) }
198 if k==FK_ADDI { if a==0 { o=era_mov_ri(out,o,rvmap[d],im) } else { if d==a { o=era_alu_ri(out,o,0,rvmap[d],im) } else { o=era_mov_rr(out,o,0,rvmap[a]); o=era_alu_ri(out,o,0,0,im); o=era_mov_rr(out,o,rvmap[d],0) } } }
199 if k==FK_ANDI { if a==0 { o=era_mov_ri(out,o,rvmap[d],0) } else { if d==a { o=era_alu_ri(out,o,4,rvmap[d],im) } else { o=era_mov_rr(out,o,0,rvmap[a]); o=era_alu_ri(out,o,4,0,im); o=era_mov_rr(out,o,rvmap[d],0) } } }
200 if k==FK_ORI { if a==0 { o=era_mov_ri(out,o,rvmap[d],im) } else { if d==a { o=era_alu_ri(out,o,1,rvmap[d],im) } else { o=era_mov_rr(out,o,0,rvmap[a]); o=era_alu_ri(out,o,1,0,im); o=era_mov_rr(out,o,rvmap[d],0) } } }
201 if k==FK_SLLI { if a==0 { o=era_mov_ri(out,o,rvmap[d],0) } else { if d==a { o=era_shl_ri(out,o,rvmap[d],im) } else { o=era_mov_rr(out,o,0,rvmap[a]); o=era_shl_ri(out,o,0,im); o=era_mov_rr(out,o,rvmap[d],0) } } }
202 if k==FK_ADD { if a==0 { o=era_mov_rr(out,o,rvmap[d],rvmap[b]) } else { if b==0 { o=era_mov_rr(out,o,rvmap[d],rvmap[a]) } else { o=era_mov_rr(out,o,0,rvmap[a]); o=era_alu_rr(out,o,0x03,0,rvmap[b]); o=era_mov_rr(out,o,rvmap[d],0) } } }
203 if k==FK_SUB { if b==0 { o=era_mov_rr(out,o,rvmap[d],rvmap[a]) } else { o=era_mov_rr(out,o,0,rvmap[a]); o=era_alu_rr(out,o,0x2B,0,rvmap[b]); o=era_mov_rr(out,o,rvmap[d],0) } }
204 if k==FK_AND { o=era_mov_rr(out,o,0,rvmap[a]); o=era_alu_rr(out,o,0x23,0,rvmap[b]); o=era_mov_rr(out,o,rvmap[d],0) }
205 if k==FK_OR { if a==0 { o=era_mov_rr(out,o,rvmap[d],rvmap[b]) } else { if b==0 { o=era_mov_rr(out,o,rvmap[d],rvmap[a]) } else { o=era_mov_rr(out,o,0,rvmap[a]); o=era_alu_rr(out,o,0x0B,0,rvmap[b]); o=era_mov_rr(out,o,rvmap[d],0) } } }
206 if k==FK_BEQ { o=era_alu_rr(out,o,0x3B,rvmap[a],rvmap[b]); let tgt: i64=i+(im/4); let rel: i64=x86off[tgt]-(o+6); o=era_jcc(out,o,0x84,rel) }
207 if k==FK_BNE { o=era_alu_rr(out,o,0x3B,rvmap[a],rvmap[b]); let tgt: i64=i+(im/4); let rel: i64=x86off[tgt]-(o+6); o=era_jcc(out,o,0x85,rel) }
208 if k==FK_BLT { o=era_alu_rr(out,o,0x3B,rvmap[a],rvmap[b]); let tgt: i64=i+(im/4); let rel: i64=x86off[tgt]-(o+6); o=era_jcc(out,o,0x8C,rel) }
209 if k==FK_BGE { o=era_alu_rr(out,o,0x3B,rvmap[a],rvmap[b]); let tgt: i64=i+(im/4); let rel: i64=x86off[tgt]-(o+6); o=era_jcc(out,o,0x8D,rel) }
210 i=i+1
211 }
212 r=0; while r<32 { if rvmap[r]>=0 { o=era_spill(out,o,rvmap[r],r*8) } r=r+1 }
213 s=nbox[0]-1; while s>=0 { o=j_b(out,o, 0x58|ra_pool(s)); s=s-1 } // pop rsi/rdx/rcx (reverse of push) -> restore
214 o=e_ret(out,o)
215 return o
216}
217func jit_main_ignore() -> i64 { return 0 }