code wiki / _hdl_build / nx_rv64_tier.nx

nx_rv64_tier.nx source

↩ module page · 187 lines · 11581 B

1// nx_rv64_tier.nx -- the TIERED EXECUTION ENGINE (the SOTA emulator architecture: V8/HotSpot/QEMU-TCG-style). It 2// INTERPRETS cold code via the shared fk_step (so it cannot diverge from the golden interpreter), PROFILES loop 3// back-edges, and when a loop header crosses a hotness threshold it JIT-compiles that loop's region and CACHES the 4// native code; on subsequent entries the loop runs NATIVELY. Loops the JIT can't handle (unsupported op / control 5// flow leaving the region) simply stay interpreted -- fail-safe, never miscompiled. This is what makes the JIT 6// accelerate REAL mixed programs, not isolated loops. license_tier: ORIGINAL 7import "nx_syscalls.nx" 8import "nx_rv64_fast.nx" // fk_step + FK_* + FK_MEMBASE 9import "nx_rv64_jit.nx" // jit_compile + jit_set_membase + jit_mmap_rwx 10 11// tiered run. stats[0]=interpreted steps, stats[1]=loops JIT-compiled, stats[2]=native block executions. `threshold` 12// = back-edge count at which a loop header is compiled (use a huge value to disable JIT = pure interpretation). 13func tier_run(opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64, ncount: i64, reg: *i64, mem: *u8, membytes: i64, maxsteps: i64, dev: *NxVirtioMmio, threshold: i64, stats: *i64) -> i64 { 14 let valbox: *i64 = sys_mmap(16) as *i64; let haltbox: *i64 = sys_mmap(16) as *i64 15 let hotcount: *i64 = sys_mmap(ncount*8+64) as *i64 // per loop-header back-edge counter 16 let jit_ptr: *i64 = sys_mmap(ncount*8+64) as *i64 // cached native code addr per header (0 = none) 17 let jit_exit: *i64 = sys_mmap(ncount*8+64) as *i64 // resume idx after the loop 18 let jit_tried: *i64 = sys_mmap(ncount*8+64) as *i64 // 1 = compile attempted (don't retry) 19 var z: i64=0; while z<ncount { hotcount[z]=0; jit_ptr[z]=0; jit_exit[z]=0; jit_tried[z]=0; z=z+1 } 20 jit_set_membase((mem as i64) - FK_MEMBASE, membytes) // bake the guest-mem base + bound into every JIT'd block 21 stats[0]=0; stats[1]=0; stats[2]=0 22 var pc: i64=FK_MEMBASE; var steps: i64=0; var halt: i64=0 // REAL (0x80000000-based) pc -- consistent with fk_run (auipc/jalr/globals work) 23 reg[0]=0 24 while halt==0 { 25 if steps >= maxsteps { halt=1 } else { 26 let idx: i64 = (pc - FK_MEMBASE) >> 2 27 if idx < 0 { halt=1 } else { if idx >= ncount { halt=1 } else { 28 if jit_ptr[idx] != 0 { 29 // TIER-1: this loop header is compiled -> run the whole loop natively, resume past it. 30 let fp: func(i64) -> i64 = jit_ptr[idx] as func(i64) -> i64 31 fp(reg as i64) 32 stats[2]=stats[2]+1 33 pc = FK_MEMBASE + (jit_exit[idx] << 2) // jit_exit is an op INDEX -> real resume address 34 } else { 35 // TIER-0: interpret one instruction via the shared golden step. 36 haltbox[0]=0 37 let next: i64 = fk_step(opk, rd, rs1, rs2, imm, idx, pc, 4, reg, mem, membytes, dev, valbox, haltbox) 38 if haltbox[0]==1 { halt=1 } 39 steps=steps+1; stats[0]=stats[0]+1 40 if halt==0 { 41 if next < pc { 42 // backward edge -> loop header at tgt (op index; next is a real address). profile it; compile when hot. 43 let tgt: i64 = (next - FK_MEMBASE) >> 2 44 hotcount[tgt]=hotcount[tgt]+1 45 if hotcount[tgt] >= threshold { if jit_ptr[tgt]==0 { if jit_tried[tgt]==0 { 46 jit_tried[tgt]=1 47 let rl: i64 = idx - tgt + 1 // region [header .. back-edge] inclusive 48 let sopk: *i64=((opk as i64)+tgt*8) as *i64; let srd: *i64=((rd as i64)+tgt*8) as *i64 49 let srs1: *i64=((rs1 as i64)+tgt*8) as *i64; let srs2: *i64=((rs2 as i64)+tgt*8) as *i64; let simm: *i64=((imm as i64)+tgt*8) as *i64 50 let x86: *u8=sys_mmap(65536); let x86off: *i64=sys_mmap(rl*8+64) as *i64 51 let xlen: i64 = jit_compile(sopk, srd, srs1, srs2, simm, rl, x86, x86off) 52 if xlen > 0 { 53 let ex: *u8 = jit_mmap_rwx(xlen+64); var c: i64=0; while c<xlen { ex[c]=x86[c]; c=c+1 } 54 jit_ptr[tgt]=ex as i64; jit_exit[tgt]=idx+1; stats[1]=stats[1]+1 55 } 56 } } } 57 } 58 pc=next 59 } 60 } 61 } } 62 } 63 } 64 return steps 65} 66// ===== COMPRESSED-REGION COMPACTOR + RELINKER (JIT-accelerates compressed/mixed hot loops) ===== 67// A hot-loop region in the halfword layout is variable-length (compressed=1 slot, 32-bit=2 slots incl a hole) with 68// byte-relative branch immediates -- jit_compile assumes CONTIGUOUS 4-byte ops with im/4 target math. So we COMPACT: 69// walk the region [lo_byte,hi_byte) by instruction (length from mem), pack the ops into a dense array, and RELINK every 70// pc-relative branch/JAL-x0 immediate to compacted-index-relative (*4). Then the UNMODIFIED jit_compile compiles it. 71// A branch leaving the region (target not an in-region instruction boundary) -> -1 -> tier interprets (fail-safe). Any 72// op jit_compile can't do (auipc/jalr/amo/div/*W-var-shift) -> jit_compile returns -1 -> bail. exit_hslot = resume after loop. 73struct CReg { 74 opk: *i64 75 rd: *i64 76 rs1: *i64 77 rs2: *i64 78 imm: *i64 79 lo_byte: i64 80 hi_byte: i64 81 mem: *u8 82 out: *u8 83 x86off: *i64 84 exit_hslot: i64 85} 86func jit_compile_creg(c: *CReg) -> i64 { 87 let lo: i64 = c.lo_byte; let hi: i64 = c.hi_byte 88 let rl: i64 = (hi - lo) >> 1 // region span in halfword slots 89 if rl < 1 { return 0-1 } if rl > 4000 { return 0-1 } 90 let map: *i64 = sys_mmap(rl*8+64) as *i64 // region-rel hslot -> compacted index (-1 = hole / not a start) 91 let shs: *i64 = sys_mmap(rl*8+64) as *i64 // compacted index -> region-rel hslot 92 var s: i64=0; while s<rl { map[s]=0-1; s=s+1 } 93 let dopk: *i64=sys_mmap(rl*8+64) as *i64; let drd: *i64=sys_mmap(rl*8+64) as *i64 94 let drs1: *i64=sys_mmap(rl*8+64) as *i64; let drs2: *i64=sys_mmap(rl*8+64) as *i64; let dimm: *i64=sys_mmap(rl*8+64) as *i64 95 // pass 1: walk instructions (length from mem), compact into dense arrays + build hslot<->ci maps 96 var bo: i64 = lo; var ci: i64 = 0 97 while bo < hi { 98 let rs2h: i64 = (bo - lo) >> 1 // region-rel hslot 99 let half: i64 = (c.mem[bo] as i64) | ((c.mem[bo+1] as i64)<<8) 100 var ilen: i64 = 4; if (half&3)!=3 { ilen=2 } 101 let ahs: i64 = bo >> 1 // absolute hslot (index into the full predecoded arrays) 102 dopk[ci]=c.opk[ahs]; drd[ci]=c.rd[ahs]; drs1[ci]=c.rs1[ahs]; drs2[ci]=c.rs2[ahs]; dimm[ci]=c.imm[ahs] 103 map[rs2h]=ci; shs[ci]=rs2h; ci=ci+1 104 bo = bo + ilen 105 } 106 let n: i64 = ci 107 c.exit_hslot = hi >> 1 // resume (absolute hslot) = one past the region 108 // pass 2: RELINK pc-relative branch/JAL-x0 immediates -> compacted-index-relative (jit_compile does tgt=i+im/4). 109 var i: i64=0 110 while i<n { 111 let k: i64=dopk[i] 112 var rel: i64=0 113 if k==FK_BEQ {rel=1} if k==FK_BNE {rel=1} if k==FK_BLT {rel=1} if k==FK_BGE {rel=1} if k==FK_BLTU {rel=1} if k==FK_BGEU {rel=1} 114 if k==FK_JAL { if drd[i]==0 { rel=1 } } 115 if rel==1 { 116 let thslot: i64 = shs[i] + (dimm[i] >> 1) // region-rel target hslot (im is even; >>1 = /2) 117 if thslot < 0 { return 0-1 } 118 if thslot >= rl { return 0-1 } 119 if map[thslot] < 0 { return 0-1 } // target not an in-region instruction boundary -> bail 120 dimm[i] = (map[thslot] - i) * 4 121 } 122 i=i+1 123 } 124 return jit_compile(dopk, drd, drs1, drs2, dimm, n, c.out, c.x86off) 125} 126 127// C-CAPABLE tiered run: halfword-indexed (idx=(pc-base)>>1), derives ilen from the code halfword in mem, runs REAL 128// rv64imac binaries. TIER-1 JIT via the COMPACTOR: a hot-loop region is compacted+relinked (jit_compile_creg) then 129// native-compiled; branches leaving the region / unsupported ops -> -1 -> the region cleanly TIERS to interpretation. 130func tier_run_c(opk: *i64, rd: *i64, rs1: *i64, rs2: *i64, imm: *i64, nhalf: i64, reg: *i64, mem: *u8, membytes: i64, maxsteps: i64, dev: *NxVirtioMmio, threshold: i64, stats: *i64) -> i64 { 131 let valbox: *i64 = sys_mmap(16) as *i64; let haltbox: *i64 = sys_mmap(16) as *i64 132 let hotcount: *i64 = sys_mmap(nhalf*8+64) as *i64 133 let jit_ptr: *i64 = sys_mmap(nhalf*8+64) as *i64 134 let jit_exit: *i64 = sys_mmap(nhalf*8+64) as *i64 135 let jit_tried: *i64 = sys_mmap(nhalf*8+64) as *i64 136 var z: i64=0; while z<nhalf { hotcount[z]=0; jit_ptr[z]=0; jit_exit[z]=0; jit_tried[z]=0; z=z+1 } 137 jit_set_membase((mem as i64) - FK_MEMBASE, membytes) 138 stats[0]=0; stats[1]=0; stats[2]=0 139 var pc: i64=FK_MEMBASE; var steps: i64=0; var halt: i64=0 140 reg[0]=0 141 while halt==0 { 142 if steps >= maxsteps { halt=1 } else { 143 let o: i64 = pc - FK_MEMBASE 144 let idx: i64 = o >> 1 145 if idx < 0 { halt=1 } else { if idx >= nhalf { halt=1 } else { 146 if jit_ptr[idx] != 0 { 147 let fp: func(i64) -> i64 = jit_ptr[idx] as func(i64) -> i64 148 fp(reg as i64); stats[2]=stats[2]+1 149 pc = FK_MEMBASE + (jit_exit[idx] << 1) 150 } else { 151 var ilen: i64 = 4 152 if o+1 < membytes { let half: i64 = (mem[o] as i64) | ((mem[o+1] as i64)<<8); if (half&3)!=3 { ilen=2 } } 153 haltbox[0]=0 154 let next: i64 = fk_step(opk, rd, rs1, rs2, imm, idx, pc, ilen, reg, mem, membytes, dev, valbox, haltbox) 155 if haltbox[0]==1 { halt=1 } 156 steps=steps+1; stats[0]=stats[0]+1 157 if halt==0 { 158 if next < pc { 159 // backward edge -> loop header. compact+relink [header, back-edge-end) and JIT it. 160 let tgt: i64 = (next - FK_MEMBASE) >> 1 // header hslot 161 hotcount[tgt]=hotcount[tgt]+1 162 if hotcount[tgt] >= threshold { if jit_ptr[tgt]==0 { if jit_tried[tgt]==0 { 163 jit_tried[tgt]=1 164 let creg: *CReg = sys_mmap(128) as *CReg 165 creg.opk=opk; creg.rd=rd; creg.rs1=rs1; creg.rs2=rs2; creg.imm=imm 166 creg.lo_byte = next - FK_MEMBASE // header byte offset (branch target) 167 creg.hi_byte = o + ilen // one past the back-edge instruction 168 creg.mem = mem; creg.exit_hslot = 0 169 let span: i64 = (creg.hi_byte - creg.lo_byte) >> 1 170 let x86: *u8=sys_mmap(65536); let x86off: *i64=sys_mmap(span*8+64) as *i64 171 creg.out=x86; creg.x86off=x86off 172 let xlen: i64 = jit_compile_creg(creg) 173 if xlen > 0 { 174 let ex: *u8 = jit_mmap_rwx(xlen+64); var c2: i64=0; while c2<xlen { ex[c2]=x86[c2]; c2=c2+1 } 175 jit_ptr[tgt]=ex as i64; jit_exit[tgt]=creg.exit_hslot; stats[1]=stats[1]+1 176 } 177 } } } 178 } 179 pc=next 180 } 181 } 182 } } 183 } 184 } 185 return steps 186} 187func tier_main_ignore() -> i64 { return 0 }