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 }