nx_emu_mips64.nx source
↩ module page · 156 lines · 9354 B
1// nx_emu_mips64.nx -- sovereign MIPS64 (n64, big-endian) interpreter (NX-EMU).
2// Flat 32 GPRs (r0==zero), BIG-ENDIAN, DELAY SLOTS (PC/nPC model like SPARC).
3// Decode/execute pure NishiLang per the MIPS64 ISA -- NO qemu (qemu-mips64 =
4// differential BENCHMARK that must agree). Resolves the earlier "inconclusive"
5// run-proof (that was a qemu-harness ABI artifact; here Nishi carries it).
6//
7// Forms: R-type opcode0 (funct): OR 0x25 / DADDU 0x2d / DSUBU 0x2f / AND 0x24 /
8// XOR 0x26 / DMUL(R6) funct 0x1c sa 2 / JR 0x08 / JALR 0x09 / SLL 0x00 (nop) /
9// SYSCALL 0x0c / DSLL 0x38 / DSRL 0x3a / DSRA 0x3b. I-type: ADDIU 0x09 /
10// DADDIU 0x19 / ORI 0x0d / LUI 0x0f / LD 0x37 / SD 0x3f. J-type: J 0x02 /
11// JAL 0x03 (link r31=pc+8). n64 syscalls v0=num,a0..=args: exit=5058.
12//
13// license_tier: ORIGINAL
14
15import "nx_syscalls_x86_64.nx"
16const MI_MAGIC_200000000: i64 = 200000000
17
18const MI_GUEST_SIZE: i64 = 16777216
19const MI_SYS_READ: i64 = 5000
20const MI_SYS_WRITE: i64 = 5001
21const MI_SYS_EXIT: i64 = 5058
22const MI_SYS_EXITG: i64 = 5205
23const MIE_UNSUPPORTED: i64 = -1
24const MIE_FAULT: i64 = -3
25// The step budget needs its OWN code. Before this, exhausting it left result at its
26// initial MIE_FAULT, so a runaway program and a bad PC were the same answer -- two
27// conditions collapsed into one negative word, which is the defect the conformance
28// ruler exists to refuse.
29const MIE_STEPCAP: i64 = -4
30
31func mi_g_ld(mem: *u8, va: i64, width: i64) -> i64 { // big-endian
32 var v: i64 = 0
33 var i: i64 = 0
34 while i < width { v = (v << 8) | (mem[va + i] & 0xff); i = i + 1 }
35 return v
36}
37func mi_g_st(mem: *u8, va: i64, width: i64, val: i64) -> i64 {
38 var i: i64 = 0
39 while i < width { mem[va + (width - 1 - i)] = (val >> (i * 8)) & 0xff; i = i + 1 }
40 return 0
41}
42func mi_sx16(x: i64) -> i64 { if (x & 0x8000) != 0 { return x - 0x10000 } return x }
43// LOGICAL right shift. The dialect's >> is ARITHMETIC, so DSRL and DSRA were the
44// SAME expression here and one of the two was necessarily wrong. A mask is the only
45// way to get a true logical shift; the n == 0 guard exists because 1 << 64 is not a
46// shift this machine performs.
47func mi_srl(v: i64, n: i64) -> i64 { if n == 0 { return v } let mask: i64 = (1 << (64 - n)) - 1; return (v >> n) & mask }
48// Sign-extend the low 32 bits. MIPS64 ADDIU is a 32-BIT op whose result is
49// sign-extended to 64; without this it was byte-identical to DADDIU and therefore
50// wrong for every result that leaves 32 bits.
51func mi_sx32(v: i64) -> i64 { let t: i64 = v & 0xFFFFFFFF; if (t & 0x80000000) != 0 { return t - 4294967296 } return t }
52func mi_rd(r: *i64, n: i64) -> i64 { if n == 0 { return 0 } return r[n] }
53func mi_wr(r: *i64, n: i64, v: i64) -> i64 { if n != 0 { r[n] = v } return 0 }
54
55func emu_mips64_run_mem(mem: *u8, mem_size: i64, entry: i64, sp0: i64) -> i64 {
56 let r: *i64 = sys_mmap(32 * 8) as *i64
57 var i: i64 = 0
58 while i < 32 { r[i] = 0; i = i + 1 }
59 r[29] = sp0 // $sp = $29
60 var pc: i64 = entry
61 var npc: i64 = entry + 4
62 var steps: i64 = 0
63 var halted: i64 = 0
64 var result: i64 = MIE_FAULT
65 while halted == 0 && steps < MI_MAGIC_200000000 {
66 if pc < 0 { halted = 1; result = MIE_FAULT }
67 if pc + 4 > mem_size { halted = 1; result = MIE_FAULT }
68 if halted == 0 {
69 let w: i64 = mi_g_ld(mem, pc, 4)
70 let opc: i64 = (w >> 26) & 0x3F
71 let rs: i64 = (w >> 21) & 0x1F
72 let rt: i64 = (w >> 16) & 0x1F
73 let rd: i64 = (w >> 11) & 0x1F
74 let sa: i64 = (w >> 6) & 0x1F
75 let fn: i64 = w & 0x3F
76 var next_pc: i64 = npc
77 var next_npc: i64 = npc + 4
78 var handled: i64 = 0
79
80 if opc == 0 { // SPECIAL (R-type)
81 if fn == 0x25 { handled = 1; mi_wr(r, rd, mi_rd(r, rs) | mi_rd(r, rt)) } // OR / move
82 if fn == 0x2d { handled = 1; mi_wr(r, rd, mi_rd(r, rs) + mi_rd(r, rt)) } // DADDU
83 if fn == 0x2f { handled = 1; mi_wr(r, rd, mi_rd(r, rs) - mi_rd(r, rt)) } // DSUBU
84 if fn == 0x24 { handled = 1; mi_wr(r, rd, mi_rd(r, rs) & mi_rd(r, rt)) } // AND
85 if fn == 0x26 { handled = 1; mi_wr(r, rd, mi_rd(r, rs) ^ mi_rd(r, rt)) } // XOR
86 if fn == 0x1c { if sa == 2 { handled = 1; mi_wr(r, rd, mi_rd(r, rs) * mi_rd(r, rt)) } } // DMUL (R6)
87 if fn == 0x38 { handled = 1; mi_wr(r, rd, mi_rd(r, rt) << sa) } // DSLL
88 if fn == 0x3a { handled = 1; mi_wr(r, rd, mi_srl(mi_rd(r, rt), sa)) } // DSRL -- LOGICAL, was byte-identical to DSRA so one of the two was necessarily wrong
89 if fn == 0x3b { handled = 1; mi_wr(r, rd, mi_rd(r, rt) >> sa) } // DSRA
90 if fn == 0x00 { handled = 1; mi_wr(r, rd, mi_rd(r, rt) << sa) } // SLL (nop=0)
91 if fn == 0x08 { handled = 1; next_npc = mi_rd(r, rs) } // JR
92 if fn == 0x09 { handled = 1; mi_wr(r, rd, pc + 8); next_npc = mi_rd(r, rs) } // JALR
93 if fn == 0x0c { // SYSCALL
94 handled = 1
95 let nr: i64 = mi_rd(r, 2) // v0
96 if nr == MI_SYS_EXIT { result = mi_rd(r, 4) & 0xff; halted = 1 }
97 if nr == MI_SYS_EXITG { result = mi_rd(r, 4) & 0xff; halted = 1 }
98 if nr == MI_SYS_WRITE { mi_wr(r, 2, sys_write(mi_rd(r, 4), ((mem as i64) + mi_rd(r, 5)) as *u8, mi_rd(r, 6))) }
99 if nr == MI_SYS_READ { mi_wr(r, 2, sys_read(mi_rd(r, 4), ((mem as i64) + mi_rd(r, 5)) as *u8, mi_rd(r, 6))) }
100 }
101 }
102 if opc == 0x09 { handled = 1; mi_wr(r, rt, mi_sx32(mi_rd(r, rs) + mi_sx16(w & 0xFFFF))) } // ADDIU -- 32-bit op sign-extended to 64, was identical to DADDIU
103 if opc == 0x19 { handled = 1; mi_wr(r, rt, mi_rd(r, rs) + mi_sx16(w & 0xFFFF)) } // DADDIU
104 if opc == 0x0d { handled = 1; mi_wr(r, rt, mi_rd(r, rs) | (w & 0xFFFF)) } // ORI
105 if opc == 0x0f { handled = 1; mi_wr(r, rt, mi_sx16(w & 0xFFFF) << 16) } // LUI
106 if opc == 0x37 { handled = 1; mi_wr(r, rt, mi_g_ld(mem, mi_rd(r, rs) + mi_sx16(w & 0xFFFF), 8)) } // LD
107 if opc == 0x3f { handled = 1; mi_g_st(mem, mi_rd(r, rs) + mi_sx16(w & 0xFFFF), 8, mi_rd(r, rt)) } // SD
108 if opc == 0x02 { handled = 1; next_npc = (pc & 0xF0000000) | ((w & 0x3FFFFFF) << 2) } // J
109 if opc == 0x03 { handled = 1; mi_wr(r, 31, pc + 8); next_npc = (pc & 0xF0000000) | ((w & 0x3FFFFFF) << 2) } // JAL
110
111 // CONDITIONAL BRANCHES, added 2026-09-03. Without these this interpreter
112 // could not run a LOOP at all, so "we emulate MIPS64" was not a true claim.
113 // nx_isa_conform_gate named the gap by running a BEQ and reporting
114 // UNSUPPORTED-instruction-class rather than a wrong answer.
115 // TARGET = (pc + 4) + (offset << 2). In this PC/nPC model pc + 4 IS npc,
116 // so the delay-slot instruction at npc ALWAYS executes before the target --
117 // that is the semantic a naive branch implementation silently gets wrong.
118 if opc == 0x04 { handled = 1; if mi_rd(r, rs) == mi_rd(r, rt) { next_npc = npc + (mi_sx16(w & 0xFFFF) << 2) } } // BEQ
119 if opc == 0x05 { handled = 1; if mi_rd(r, rs) != mi_rd(r, rt) { next_npc = npc + (mi_sx16(w & 0xFFFF) << 2) } } // BNE
120 if opc == 0x06 { handled = 1; if mi_rd(r, rs) <= 0 { next_npc = npc + (mi_sx16(w & 0xFFFF) << 2) } } // BLEZ
121 if opc == 0x07 { handled = 1; if mi_rd(r, rs) > 0 { next_npc = npc + (mi_sx16(w & 0xFFFF) << 2) } } // BGTZ
122 if opc == 0x01 { // REGIMM: rt selects the test
123 if rt == 0 { handled = 1; if mi_rd(r, rs) < 0 { next_npc = npc + (mi_sx16(w & 0xFFFF) << 2) } } // BLTZ
124 if rt == 1 { handled = 1; if mi_rd(r, rs) >= 0 { next_npc = npc + (mi_sx16(w & 0xFFFF) << 2) } } // BGEZ
125 }
126
127 if handled == 0 { result = MIE_UNSUPPORTED; halted = 1 }
128 pc = next_pc
129 npc = next_npc
130 steps = steps + 1
131 }
132 }
133 return result
134}
135
136func emu_mips64_load_elf(buf: *u8, len: i64) -> i64 {
137 if len < 64 { return MIE_FAULT }
138 let e_entry: i64 = mi_g_ld(buf, 24, 8)
139 let e_phoff: i64 = mi_g_ld(buf, 32, 8)
140 let e_phnum: i64 = mi_g_ld(buf, 56, 2)
141 let e_phent: i64 = mi_g_ld(buf, 54, 2)
142 let mem: *u8 = sys_mmap(MI_GUEST_SIZE)
143 var idx: i64 = 0
144 while idx < e_phnum {
145 let ph: i64 = e_phoff + idx * e_phent
146 if mi_g_ld(buf, ph, 4) == 1 {
147 let p_off: i64 = mi_g_ld(buf, ph + 8, 8)
148 let p_va: i64 = mi_g_ld(buf, ph + 16, 8)
149 let p_fs: i64 = mi_g_ld(buf, ph + 32, 8)
150 var k: i64 = 0
151 while k < p_fs { if (p_va + k) < MI_GUEST_SIZE { mem[p_va + k] = buf[p_off + k] }; k = k + 1 }
152 }
153 idx = idx + 1
154 }
155 return emu_mips64_run_mem(mem, MI_GUEST_SIZE, e_entry, 0x00F00000)
156}