nx_emu_rv64.nx source
↩ module page · 363 lines · 16666 B
1// nx_emu_rv64.nx -- sovereign RV64 interpreter (NX-EMU). Decodes +
2// executes the RV64I + RV64M base needed to run nxc2-compiled RV64
3// programs over a flat guest address space, with a Linux RV64 syscall
4// surface (exit/write/read). NO qemu -- the foreign emulator is demoted
5// to a build-time differential ORACLE only. Replaces qemu for the
6// non-native lanes (RV64/aarch64/arm32 on an x86_64 host).
7//
8// Guest memory model (per the grounded design): ONE flat mmap region;
9// vaddr == offset (GUEST_BASE 0); pc is a guest vaddr fetched via g_ld;
10// MAP_ANON zero-fill gives free .bss; loads/stores hit the same region;
11// sp (x2) initialised by the loader. All guest pointers are translated
12// (mem + va) before any host syscall.
13//
14// Decoded: lui(sign-ext) auipc | addi slti sltiu xori ori andi slli
15// srli srai | add sub sll slt sltu xor srl sra or and | mul div divu
16// rem remu (RV64M; div/rem edge cases per spec 13.2) | lb lh lw ld lbu
17// lhu lwu | sb sh sw sd | beq bne blt bge bltu bgeu | jal jalr | ecall
18// (exit 93/94, write 64, read 63).
19//
20// genealogy_id: riscv_unprivileged_isa_v20240411 + linux_riscv_abi
21// lineage_id: nishi_nx_emu_rv64_m7
22// license_tier: ORIGINAL
23
24import "nx_syscalls_x86_64.nx"
25const RV_MAGIC_4096: i64 = 4096
26const RV_MAGIC_8192: i64 = 8192
27const RV_MAGIC_2097152: i64 = 2097152
28const RV_MAGIC_4294967296: i64 = 4294967296
29const RV_MAGIC_200000000: i64 = 200000000
30
31const RV_OP_LOAD: i64 = 0x03
32const RV_OP_IMM: i64 = 0x13
33const RV_OP_AUIPC: i64 = 0x17
34const RV_OP_STORE: i64 = 0x23
35const RV_OP_REG: i64 = 0x33
36const RV_OP_LUI: i64 = 0x37
37const RV_OP_IMM_W: i64 = 0x1b // addiw/slliw/srliw/sraiw (32-bit)
38const RV_OP_W: i64 = 0x3b // addw/subw/sllw/srlw/sraw + mulw/divw...
39const RV_OP_BRANCH: i64 = 0x63
40const RV_OP_JALR: i64 = 0x67
41const RV_OP_JAL: i64 = 0x6f
42const RV_SYSTEM: i64 = 0x73
43
44const RV_SYS_READ: i64 = 63
45const RV_SYS_WRITE: i64 = 64
46const RV_SYS_EXIT: i64 = 93
47const RV_SYS_EXIT_GROUP: i64 = 94
48
49const EMU_RV_UNSUPPORTED: i64 = -1
50const EMU_RV_RANOFF: i64 = -2
51const EMU_RV_FAULT: i64 = -3
52
53const EMU_GUEST_SIZE: i64 = 16777216 // 16 MiB flat guest RAM
54
55func emu_rv_sext12(v: i64) -> i64 {
56 if (v & 0x800) != 0 { return v - RV_MAGIC_4096 }
57 return v
58}
59func emu_rv_bimm(w: i64) -> i64 {
60 let b12: i64 = (w >> 31) & 1
61 let b10_5: i64 = (w >> 25) & 0x3f
62 let b4_1: i64 = (w >> 8) & 0xf
63 let b11: i64 = (w >> 7) & 1
64 var v: i64 = (b12 << 12) | (b11 << 11) | (b10_5 << 5) | (b4_1 << 1)
65 if (v & 0x1000) != 0 { v = v - RV_MAGIC_8192 }
66 return v
67}
68func emu_rv_jimm(w: i64) -> i64 {
69 let j20: i64 = (w >> 31) & 1
70 let j10_1: i64 = (w >> 21) & 0x3ff
71 let j11: i64 = (w >> 20) & 1
72 let j19_12: i64 = (w >> 12) & 0xff
73 var v: i64 = (j20 << 20) | (j19_12 << 12) | (j11 << 11) | (j10_1 << 1)
74 if (v & 0x100000) != 0 { v = v - RV_MAGIC_2097152 }
75 return v
76}
77// unsigned a < b (flip the sign bit, then signed compare)
78func emu_ltu(a: i64, b: i64) -> i64 {
79 let m: i64 = 1 << 63
80 if (a ^ m) < (b ^ m) { return 1 }
81 return 0
82}
83// logical right shift (zero-fill); NishiLang >> is arithmetic, so mask.
84func emu_srl(v: i64, n: i64) -> i64 {
85 if n == 0 { return v }
86 let mask: i64 = (1 << (64 - n)) - 1
87 return (v >> n) & mask
88}
89// sign-extend the low 32 bits to 64 (the *W result rule)
90func emu_sext32(v: i64) -> i64 {
91 let lo: i64 = v & 0xffffffff
92 if (lo & 0x80000000) != 0 { return lo - RV_MAGIC_4294967296 }
93 return lo
94}
95// little-endian guest load of `width` bytes at vaddr; sign-extend if signd.
96func emu_g_ld(mem: *u8, va: i64, width: i64, signd: i64) -> i64 {
97 var v: i64 = 0
98 var i: i64 = 0
99 while i < width { v = v | ((mem[va + i] & 0xff) << (i * 8)); i = i + 1 }
100 if signd == 1 {
101 if width < 8 {
102 let sign: i64 = 1 << (width * 8 - 1)
103 if (v & sign) != 0 { v = v - (1 << (width * 8)) }
104 }
105 }
106 return v
107}
108func emu_g_st(mem: *u8, va: i64, width: i64, val: i64) -> i64 {
109 var i: i64 = 0
110 while i < width { mem[va + i] = (val >> (i * 8)) & 0xff; i = i + 1 }
111 return 0
112}
113
114// Core engine: run the program already loaded into `mem`, entry pc, sp.
115// Returns exit status (0..255) or a negative EMU_RV_* sentinel.
116func emu_rv64_run_mem(mem: *u8, mem_size: i64, entry: i64, sp: i64) -> i64 {
117 let x: *i64 = sys_mmap(32 * 8) as *i64
118 var i: i64 = 0
119 while i < 32 { x[i] = 0; i = i + 1 }
120 x[2] = sp // sp
121 var pc: i64 = entry
122 var steps: i64 = 0
123 var result: i64 = EMU_RV_RANOFF
124 var halted: i64 = 0
125 let MIN: i64 = 1 << 63
126 while halted == 0 && steps < RV_MAGIC_200000000 {
127 if pc < 0 { result = EMU_RV_FAULT; halted = 1 }
128 if pc + 4 > mem_size { result = EMU_RV_FAULT; halted = 1 }
129 if halted == 0 {
130 let w: i64 = emu_g_ld(mem, pc, 4, 0)
131 let opcode: i64 = w & 0x7f
132 let rd: i64 = (w >> 7) & 0x1f
133 let f3: i64 = (w >> 12) & 0x7
134 let rs1: i64 = (w >> 15) & 0x1f
135 let rs2: i64 = (w >> 20) & 0x1f
136 let f7: i64 = (w >> 25) & 0x7f
137 var next: i64 = pc + 4
138 var handled: i64 = 0
139
140 if opcode == RV_OP_LUI {
141 handled = 1
142 var v: i64 = w & 0xfffff000
143 if (v & 0x80000000) != 0 { v = v - RV_MAGIC_4294967296 } // sign-ext bit31
144 if rd != 0 { x[rd] = v }
145 }
146 if opcode == RV_OP_AUIPC {
147 handled = 1
148 var v2: i64 = w & 0xfffff000
149 if (v2 & 0x80000000) != 0 { v2 = v2 - RV_MAGIC_4294967296 }
150 if rd != 0 { x[rd] = pc + v2 }
151 }
152 if opcode == RV_OP_IMM {
153 handled = 1
154 let imm: i64 = emu_rv_sext12((w >> 20) & 0xfff)
155 let shamt: i64 = (w >> 20) & 0x3f
156 var r: i64 = x[rd]
157 if f3 == 0 { r = x[rs1] + imm } // addi
158 if f3 == 1 { r = x[rs1] << shamt } // slli
159 if f3 == 2 { if x[rs1] < imm { r = 1 } else { r = 0 } } // slti
160 if f3 == 3 { r = emu_ltu(x[rs1], imm) } // sltiu
161 if f3 == 4 { r = x[rs1] ^ imm } // xori
162 if f3 == 5 {
163 if (w >> 30) & 1 == 0 { r = emu_srl(x[rs1], shamt) } // srli
164 else { r = x[rs1] >> shamt } // srai
165 }
166 if f3 == 6 { r = x[rs1] | imm } // ori
167 if f3 == 7 { r = x[rs1] & imm } // andi
168 if rd != 0 { x[rd] = r }
169 }
170 if opcode == RV_OP_REG {
171 handled = 1
172 var r2: i64 = x[rd]
173 if f7 == 1 {
174 // RV64M
175 if f3 == 0 { r2 = x[rs1] * x[rs2] } // mul
176 if f3 == 4 { // div (signed)
177 if x[rs2] == 0 { r2 = 0 - 1 } else {
178 if x[rs1] == MIN && x[rs2] == (0 - 1) { r2 = MIN } else { r2 = x[rs1] / x[rs2] }
179 }
180 }
181 if f3 == 5 { // divu
182 if x[rs2] == 0 { r2 = 0 - 1 } else { r2 = x[rs1] / x[rs2] }
183 }
184 if f3 == 6 { // rem (signed)
185 if x[rs2] == 0 { r2 = x[rs1] } else {
186 if x[rs1] == MIN && x[rs2] == (0 - 1) { r2 = 0 } else { r2 = x[rs1] - (x[rs1] / x[rs2]) * x[rs2] }
187 }
188 }
189 if f3 == 7 { // remu
190 if x[rs2] == 0 { r2 = x[rs1] } else { r2 = x[rs1] - (x[rs1] / x[rs2]) * x[rs2] }
191 }
192 } else {
193 let sh: i64 = x[rs2] & 0x3f
194 if f3 == 0 {
195 if f7 == 0x20 { r2 = x[rs1] - x[rs2] } else { r2 = x[rs1] + x[rs2] } // add/sub
196 }
197 if f3 == 1 { r2 = x[rs1] << sh } // sll
198 if f3 == 2 { if x[rs1] < x[rs2] { r2 = 1 } else { r2 = 0 } } // slt
199 if f3 == 3 { r2 = emu_ltu(x[rs1], x[rs2]) } // sltu
200 if f3 == 4 { r2 = x[rs1] ^ x[rs2] } // xor
201 if f3 == 5 {
202 if f7 == 0x20 { r2 = x[rs1] >> sh } else { r2 = emu_srl(x[rs1], sh) } // sra/srl
203 }
204 if f3 == 6 { r2 = x[rs1] | x[rs2] } // or
205 if f3 == 7 { r2 = x[rs1] & x[rs2] } // and
206 }
207 if rd != 0 { x[rd] = r2 }
208 }
209 // OP-IMM-W: addiw/slliw/srliw/sraiw (operate on low 32, sign-extend)
210 if opcode == RV_OP_IMM_W {
211 handled = 1
212 let immw: i64 = emu_rv_sext12((w >> 20) & 0xfff)
213 let sh5: i64 = (w >> 20) & 0x1f
214 var rw: i64 = x[rd]
215 if f3 == 0 { rw = emu_sext32(x[rs1] + immw) } // addiw
216 if f3 == 1 { rw = emu_sext32((x[rs1] & 0xffffffff) << sh5) } // slliw
217 if f3 == 5 {
218 if ((w >> 30) & 1) == 0 { rw = emu_sext32(emu_srl(x[rs1] & 0xffffffff, sh5)) } // srliw
219 else { rw = emu_sext32(emu_sext32(x[rs1]) >> sh5) } // sraiw
220 }
221 if rd != 0 { x[rd] = rw }
222 }
223 // OP-W: addw/subw/sllw/srlw/sraw + mulw/divw/divuw/remw/remuw
224 if opcode == RV_OP_W {
225 handled = 1
226 let f7w: i64 = (w >> 25) & 0x7f
227 let shw: i64 = x[rs2] & 0x1f
228 let a32: i64 = emu_sext32(x[rs1])
229 let b32: i64 = emu_sext32(x[rs2])
230 var rw2: i64 = x[rd]
231 if f7w == 1 {
232 if f3 == 0 { rw2 = emu_sext32(x[rs1] * x[rs2]) } // mulw
233 if f3 == 4 { if b32 == 0 { rw2 = 0 - 1 } else { rw2 = emu_sext32(a32 / b32) } } // divw
234 if f3 == 5 {
235 let ua: i64 = x[rs1] & 0xffffffff
236 let ub: i64 = x[rs2] & 0xffffffff
237 if ub == 0 { rw2 = 0 - 1 } else { rw2 = emu_sext32(ua / ub) } // divuw
238 }
239 if f3 == 6 { if b32 == 0 { rw2 = a32 } else { rw2 = emu_sext32(a32 - (a32 / b32) * b32) } } // remw
240 if f3 == 7 {
241 let ua2: i64 = x[rs1] & 0xffffffff
242 let ub2: i64 = x[rs2] & 0xffffffff
243 if ub2 == 0 { rw2 = emu_sext32(ua2) } else { rw2 = emu_sext32(ua2 - (ua2 / ub2) * ub2) } // remuw
244 }
245 } else {
246 if f3 == 0 { if f7w == 0x20 { rw2 = emu_sext32(x[rs1] - x[rs2]) } else { rw2 = emu_sext32(x[rs1] + x[rs2]) } } // addw/subw
247 if f3 == 1 { rw2 = emu_sext32((x[rs1] & 0xffffffff) << shw) } // sllw
248 if f3 == 5 {
249 if f7w == 0x20 { rw2 = emu_sext32(a32 >> shw) } else { rw2 = emu_sext32(emu_srl(x[rs1] & 0xffffffff, shw)) } // sraw/srlw
250 }
251 }
252 if rd != 0 { x[rd] = rw2 }
253 }
254 if opcode == RV_OP_LOAD {
255 handled = 1
256 let ea: i64 = x[rs1] + emu_rv_sext12((w >> 20) & 0xfff)
257 var lv: i64 = 0
258 if f3 == 0 { lv = emu_g_ld(mem, ea, 1, 1) } // lb
259 if f3 == 1 { lv = emu_g_ld(mem, ea, 2, 1) } // lh
260 if f3 == 2 { lv = emu_g_ld(mem, ea, 4, 1) } // lw
261 if f3 == 3 { lv = emu_g_ld(mem, ea, 8, 0) } // ld
262 if f3 == 4 { lv = emu_g_ld(mem, ea, 1, 0) } // lbu
263 if f3 == 5 { lv = emu_g_ld(mem, ea, 2, 0) } // lhu
264 if f3 == 6 { lv = emu_g_ld(mem, ea, 4, 0) } // lwu
265 if rd != 0 { x[rd] = lv }
266 }
267 if opcode == RV_OP_STORE {
268 handled = 1
269 let simm: i64 = emu_rv_sext12(((w >> 25) << 5) | ((w >> 7) & 0x1f))
270 let ea2: i64 = x[rs1] + simm
271 if f3 == 0 { emu_g_st(mem, ea2, 1, x[rs2]) } // sb
272 if f3 == 1 { emu_g_st(mem, ea2, 2, x[rs2]) } // sh
273 if f3 == 2 { emu_g_st(mem, ea2, 4, x[rs2]) } // sw
274 if f3 == 3 { emu_g_st(mem, ea2, 8, x[rs2]) } // sd
275 }
276 if opcode == RV_OP_BRANCH {
277 handled = 1
278 let bimm: i64 = emu_rv_bimm(w)
279 var take: i64 = 0
280 if f3 == 0 { if x[rs1] == x[rs2] { take = 1 } } // beq
281 if f3 == 1 { if x[rs1] != x[rs2] { take = 1 } } // bne
282 if f3 == 4 { if x[rs1] < x[rs2] { take = 1 } } // blt
283 if f3 == 5 { if x[rs1] >= x[rs2] { take = 1 } } // bge
284 if f3 == 6 { take = emu_ltu(x[rs1], x[rs2]) } // bltu
285 if f3 == 7 { if emu_ltu(x[rs1], x[rs2]) == 0 { take = 1 } } // bgeu
286 if take == 1 { next = pc + bimm }
287 }
288 if opcode == RV_OP_JAL {
289 handled = 1
290 if rd != 0 { x[rd] = pc + 4 }
291 next = pc + emu_rv_jimm(w)
292 }
293 if opcode == RV_OP_JALR {
294 handled = 1
295 let t: i64 = x[rs1] + emu_rv_sext12((w >> 20) & 0xfff)
296 if rd != 0 { x[rd] = pc + 4 }
297 next = t - (t & 1)
298 }
299 if opcode == RV_SYSTEM {
300 handled = 1
301 let nr: i64 = x[17]
302 if nr == RV_SYS_EXIT { result = x[10] & 0xff; halted = 1 }
303 if nr == RV_SYS_EXIT_GROUP { result = x[10] & 0xff; halted = 1 }
304 if nr == RV_SYS_WRITE {
305 let hp: i64 = (mem as i64) + x[11]
306 x[10] = sys_write(x[10], hp as *u8, x[12])
307 }
308 if nr == RV_SYS_READ {
309 let hp2: i64 = (mem as i64) + x[11]
310 x[10] = sys_read(x[10], hp2 as *u8, x[12])
311 }
312 }
313
314 if handled == 0 { result = EMU_RV_UNSUPPORTED; halted = 1 }
315 pc = next
316 steps = steps + 1
317 }
318 }
319 return result
320}
321
322// Convenience: run a flat RV64 code blob loaded at guest vaddr 0.
323func emu_rv64_run(code: *u8, code_len: i64) -> i64 {
324 let mem: *u8 = sys_mmap(EMU_GUEST_SIZE)
325 var i: i64 = 0
326 while i < code_len { mem[i] = code[i]; i = i + 1 }
327 let sp: i64 = 0x00800000 // 8 MiB: above code, below the 16 MiB top
328 return emu_rv64_run_mem(mem, EMU_GUEST_SIZE, 0, sp)
329}
330
331// Load a static RV64 ELF (from buf/len) into a flat guest image and run it.
332// Parses ELF64 header + program headers inline (identity vaddr map), maps
333// every PT_LOAD, seeds a minimal stack (argc=0), pc=e_entry.
334func emu_rv64_load_elf(buf: *u8, len: i64) -> i64 {
335 // ELF64 header field offsets
336 if len < 64 { return EMU_RV_FAULT }
337 if (buf[0] & 0xff) != 0x7f { return EMU_RV_FAULT }
338 let e_entry: i64 = emu_g_ld(buf, 24, 8, 0)
339 let e_phoff: i64 = emu_g_ld(buf, 32, 8, 0)
340 let e_phnum: i64 = emu_g_ld(buf, 56, 2, 0)
341 let e_phent: i64 = emu_g_ld(buf, 54, 2, 0)
342 let mem: *u8 = sys_mmap(EMU_GUEST_SIZE)
343 var idx: i64 = 0
344 while idx < e_phnum {
345 let ph: i64 = e_phoff + idx * e_phent
346 let p_type: i64 = emu_g_ld(buf, ph, 4, 0)
347 if p_type == 1 { // PT_LOAD
348 let p_offset: i64 = emu_g_ld(buf, ph + 8, 8, 0)
349 let p_vaddr: i64 = emu_g_ld(buf, ph + 16, 8, 0)
350 let p_filesz: i64 = emu_g_ld(buf, ph + 32, 8, 0)
351 var k: i64 = 0
352 while k < p_filesz {
353 if (p_vaddr + k) < EMU_GUEST_SIZE { mem[p_vaddr + k] = buf[p_offset + k] }
354 k = k + 1
355 }
356 }
357 idx = idx + 1
358 }
359 // minimal SysV stack: sp 16-aligned, argc=0 at sp
360 let sp: i64 = 0x00F00000 // 15 MiB
361 emu_g_st(mem, sp, 8, 0) // argc = 0
362 return emu_rv64_run_mem(mem, EMU_GUEST_SIZE, e_entry, sp)
363}