code wiki / (root) / nx_emu_mips64.nx

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}