nx_dis.nx source
↩ module page · 480 lines · 16786 B
1// nx_dis.nx -- RV64I disassembler (machine bytes -> mnemonic).
2//
3// Inverse of nxasm: takes 4-byte RV64 instructions and prints the
4// canonical mnemonic + operand list. Foundation for sovereign
5// `objdump -d` (the disassembly view), the future nx_dbg.nx
6// stepping debugger, and Wheeler-DDC byte-level audit.
7//
8// Coverage (v0.0.1 -- the instruction set our codegen actually
9// emits):
10//
11// R-type: add, sub, and, or, xor, sll, srl, sra, slt, sltu
12// mul, mulh, div, divu, rem, remu (M extension)
13// I-type: addi, andi, ori, xori, slli, srli, srai, slti, sltiu
14// jalr, ld, lw, lh, lb, lwu, lhu, lbu, ebreak, ecall
15// S-type: sd, sw, sh, sb
16// B-type: beq, bne, blt, bge, bltu, bgeu
17// U-type: lui, auipc
18// J-type: jal
19//
20// Floating-point (F/D extension), atomic (A), compressed (C),
21// vector (V) deferred -- separate commits when nxc2 emits them.
22//
23// Pseudoinstruction recognition (addi rd,zero,N -> li, etc.) is
24// also deferred to nx_dis_pretty.nx.
25
26// nx_safety_envelope:
27// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
28// sil_target: SIL1
29// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
30// verdict: NOT_YET_EVALUATED
31
32import "syscalls.nx"
33
34// ---- decode helpers ------------------------------------------------
35
36// Read a 4-byte little-endian instruction word.
37func nx_dis_word_at(buf: *u8, off: i64) -> i64 {
38 let b0: i64 = buf[off + 0]
39 let b1: i64 = buf[off + 1]
40 let b2: i64 = buf[off + 2]
41 let b3: i64 = buf[off + 3]
42 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
43}
44
45func nx_dis_opcode(w: i64) -> i64 { return w & 0x7F }
46func nx_dis_rd(w: i64) -> i64 { return (w >> 7) & 0x1F }
47func nx_dis_funct3(w: i64) -> i64 { return (w >> 12) & 0x7 }
48func nx_dis_rs1(w: i64) -> i64 { return (w >> 15) & 0x1F }
49func nx_dis_rs2(w: i64) -> i64 { return (w >> 20) & 0x1F }
50func nx_dis_funct7(w: i64) -> i64 { return (w >> 25) & 0x7F }
51
52// I-type immediate (signed 12-bit).
53func nx_dis_imm_i(w: i64) -> i64 {
54 let imm: i64 = (w >> 20) & 0xFFF
55 if (imm & 0x800) != 0 { return imm | (~0xFFF) }
56 return imm
57}
58
59// S-type immediate (signed 12-bit, split across two fields).
60func nx_dis_imm_s(w: i64) -> i64 {
61 let imm: i64 = ((w >> 25) & 0x7F) << 5 | ((w >> 7) & 0x1F)
62 if (imm & 0x800) != 0 { return imm | (~0xFFF) }
63 return imm
64}
65
66// B-type immediate (signed 13-bit, split across funky fields).
67func nx_dis_imm_b(w: i64) -> i64 {
68 let bit12: i64 = (w >> 31) & 0x1
69 let bit11: i64 = (w >> 7) & 0x1
70 let bits_10_5: i64 = (w >> 25) & 0x3F
71 let bits_4_1: i64 = (w >> 8) & 0xF
72 let imm: i64 = (bit12 << 12) | (bit11 << 11) | (bits_10_5 << 5) | (bits_4_1 << 1)
73 if (imm & 0x1000) != 0 { return imm | (~0x1FFF) }
74 return imm
75}
76
77// U-type immediate (upper 20 bits, sign-extended after shift left).
78func nx_dis_imm_u(w: i64) -> i64 {
79 let imm: i64 = w & 0xFFFFF000
80 if (imm & 0x80000000) != 0 { return imm | (~0xFFFFFFFF) }
81 return imm
82}
83
84// J-type immediate (signed 21-bit, scrambled).
85func nx_dis_imm_j(w: i64) -> i64 {
86 let bit20: i64 = (w >> 31) & 0x1
87 let bits_10_1: i64 = (w >> 21) & 0x3FF
88 let bit11: i64 = (w >> 20) & 0x1
89 let bits_19_12: i64 = (w >> 12) & 0xFF
90 let imm: i64 = (bit20 << 20) | (bits_19_12 << 12) | (bit11 << 11) | (bits_10_1 << 1)
91 if (imm & 0x100000) != 0 { return imm | (~0x1FFFFF) }
92 return imm
93}
94
95// ---- register names ----------------------------------------------
96
97// Returns a *u8 to a static name string for register `n` (0..31).
98// Standard ABI mnemonics: zero, ra, sp, gp, tp, t0..t6, s0..s11,
99// a0..a7.
100func nx_dis_reg_name(n: i64) -> *u8 {
101 if n == 0 { return "zero" as *u8 }
102 if n == 1 { return "ra" as *u8 }
103 if n == 2 { return "sp" as *u8 }
104 if n == 3 { return "gp" as *u8 }
105 if n == 4 { return "tp" as *u8 }
106 if n == 5 { return "t0" as *u8 }
107 if n == 6 { return "t1" as *u8 }
108 if n == 7 { return "t2" as *u8 }
109 if n == 8 { return "s0" as *u8 }
110 if n == 9 { return "s1" as *u8 }
111 if n == 10 { return "a0" as *u8 }
112 if n == 11 { return "a1" as *u8 }
113 if n == 12 { return "a2" as *u8 }
114 if n == 13 { return "a3" as *u8 }
115 if n == 14 { return "a4" as *u8 }
116 if n == 15 { return "a5" as *u8 }
117 if n == 16 { return "a6" as *u8 }
118 if n == 17 { return "a7" as *u8 }
119 if n == 18 { return "s2" as *u8 }
120 if n == 19 { return "s3" as *u8 }
121 if n == 20 { return "s4" as *u8 }
122 if n == 21 { return "s5" as *u8 }
123 if n == 22 { return "s6" as *u8 }
124 if n == 23 { return "s7" as *u8 }
125 if n == 24 { return "s8" as *u8 }
126 if n == 25 { return "s9" as *u8 }
127 if n == 26 { return "s10" as *u8 }
128 if n == 27 { return "s11" as *u8 }
129 if n == 28 { return "t3" as *u8 }
130 if n == 29 { return "t4" as *u8 }
131 if n == 30 { return "t5" as *u8 }
132 if n == 31 { return "t6" as *u8 }
133 return "?" as *u8
134}
135
136// ---- mnemonic lookup ----------------------------------------------
137
138// R-type opcode (0x33) and 0x3B (RV64 word ops). Returns mnemonic.
139func nx_dis_r_mnem(funct3: i64, funct7: i64, is_w: i64) -> *u8 {
140 if is_w == 1 {
141 if funct3 == 0 { if funct7 == 0 { return "addw" as *u8 } else { return "subw" as *u8 } }
142 if funct3 == 1 { return "sllw" as *u8 }
143 if funct3 == 5 { if funct7 == 0 { return "srlw" as *u8 } else { return "sraw" as *u8 } }
144 }
145 if funct3 == 0 {
146 if funct7 == 0 { return "add" as *u8 }
147 if funct7 == 0x20 { return "sub" as *u8 }
148 if funct7 == 1 { return "mul" as *u8 }
149 }
150 if funct3 == 1 {
151 if funct7 == 0 { return "sll" as *u8 }
152 if funct7 == 1 { return "mulh" as *u8 }
153 }
154 if funct3 == 2 { if funct7 == 0 { return "slt" as *u8 } }
155 if funct3 == 3 { if funct7 == 0 { return "sltu" as *u8 } }
156 if funct3 == 4 {
157 if funct7 == 0 { return "xor" as *u8 }
158 if funct7 == 1 { return "div" as *u8 }
159 }
160 if funct3 == 5 {
161 if funct7 == 0 { return "srl" as *u8 }
162 if funct7 == 0x20 { return "sra" as *u8 }
163 if funct7 == 1 { return "divu" as *u8 }
164 }
165 if funct3 == 6 {
166 if funct7 == 0 { return "or" as *u8 }
167 if funct7 == 1 { return "rem" as *u8 }
168 }
169 if funct3 == 7 {
170 if funct7 == 0 { return "and" as *u8 }
171 if funct7 == 1 { return "remu" as *u8 }
172 }
173 return "?r" as *u8
174}
175
176// I-type opcode 0x13 / 0x1B (immediate ALU).
177func nx_dis_i_alu_mnem(funct3: i64, funct7: i64, is_w: i64) -> *u8 {
178 if is_w == 1 {
179 if funct3 == 0 { return "addiw" as *u8 }
180 if funct3 == 1 { return "slliw" as *u8 }
181 if funct3 == 5 {
182 if funct7 == 0 { return "srliw" as *u8 }
183 return "sraiw" as *u8
184 }
185 }
186 if funct3 == 0 { return "addi" as *u8 }
187 if funct3 == 2 { return "slti" as *u8 }
188 if funct3 == 3 { return "sltiu" as *u8 }
189 if funct3 == 4 { return "xori" as *u8 }
190 if funct3 == 6 { return "ori" as *u8 }
191 if funct3 == 7 { return "andi" as *u8 }
192 if funct3 == 1 { return "slli" as *u8 }
193 if funct3 == 5 {
194 if funct7 == 0 { return "srli" as *u8 }
195 return "srai" as *u8
196 }
197 return "?i" as *u8
198}
199
200// I-type opcode 0x03 (loads).
201func nx_dis_load_mnem(funct3: i64) -> *u8 {
202 if funct3 == 0 { return "lb" as *u8 }
203 if funct3 == 1 { return "lh" as *u8 }
204 if funct3 == 2 { return "lw" as *u8 }
205 if funct3 == 3 { return "ld" as *u8 }
206 if funct3 == 4 { return "lbu" as *u8 }
207 if funct3 == 5 { return "lhu" as *u8 }
208 if funct3 == 6 { return "lwu" as *u8 }
209 return "?l" as *u8
210}
211
212// S-type opcode 0x23 (stores).
213func nx_dis_store_mnem(funct3: i64) -> *u8 {
214 if funct3 == 0 { return "sb" as *u8 }
215 if funct3 == 1 { return "sh" as *u8 }
216 if funct3 == 2 { return "sw" as *u8 }
217 if funct3 == 3 { return "sd" as *u8 }
218 return "?s" as *u8
219}
220
221// B-type opcode 0x63 (branches).
222func nx_dis_branch_mnem(funct3: i64) -> *u8 {
223 if funct3 == 0 { return "beq" as *u8 }
224 if funct3 == 1 { return "bne" as *u8 }
225 if funct3 == 4 { return "blt" as *u8 }
226 if funct3 == 5 { return "bge" as *u8 }
227 if funct3 == 6 { return "bltu" as *u8 }
228 if funct3 == 7 { return "bgeu" as *u8 }
229 return "?b" as *u8
230}
231
232// ---- print helpers ------------------------------------------------
233
234func nx_dis_puts(fd: i64, s: *u8) -> i64 {
235 var n: i64 = 0
236 while s[n] != 0 { n = n + 1 }
237 sys_write(fd, s, n)
238 return 0
239}
240
241func nx_dis_putdec(fd: i64, v: i64) -> i64 {
242 let buf: *u8 = sys_mmap(16)
243 var n: i64 = v
244 if n < 0 {
245 sys_write(fd, "-" as *u8, 1)
246 n = 0 - n
247 }
248 if n == 0 { sys_write(fd, "0" as *u8, 1); return 0 }
249 var k: i64 = 0
250 while n > 0 {
251 buf[k] = 0x30 + (n - (n / 10) * 10)
252 n = n / 10
253 k = k + 1
254 }
255 var j: i64 = 0
256 var l: i64 = k - 1
257 while j < l {
258 let t: i64 = buf[j]; buf[j] = buf[l]; buf[l] = t
259 j = j + 1
260 l = l - 1
261 }
262 sys_write(fd, buf, k)
263 return 0
264}
265
266// ---- the main disassembler ---------------------------------------
267
268// Disassemble one 4-byte instruction at byte offset `off` in `buf`.
269// Prints to `fd`. Returns the number of bytes consumed (4 for full
270// RV64 instr; future C-extension will return 2).
271func nx_dis_one(buf: *u8, off: i64, fd: i64) -> i64 {
272 let w: i64 = nx_dis_word_at(buf, off)
273 let op: i64 = nx_dis_opcode(w)
274
275 // R-type ALU (0x33 = full, 0x3B = word).
276 if op == 0x33 {
277 nx_dis_puts(fd, nx_dis_r_mnem(nx_dis_funct3(w), nx_dis_funct7(w), 0))
278 sys_write(fd, " " as *u8, 1)
279 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
280 sys_write(fd, ", " as *u8, 2)
281 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w)))
282 sys_write(fd, ", " as *u8, 2)
283 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs2(w)))
284 return 4
285 }
286 if op == 0x3B {
287 nx_dis_puts(fd, nx_dis_r_mnem(nx_dis_funct3(w), nx_dis_funct7(w), 1))
288 sys_write(fd, " " as *u8, 1)
289 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
290 sys_write(fd, ", " as *u8, 2)
291 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w)))
292 sys_write(fd, ", " as *u8, 2)
293 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs2(w)))
294 return 4
295 }
296
297 // I-type ALU (0x13 = full, 0x1B = word).
298 if op == 0x13 {
299 nx_dis_puts(fd, nx_dis_i_alu_mnem(nx_dis_funct3(w), nx_dis_funct7(w), 0))
300 sys_write(fd, " " as *u8, 1)
301 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
302 sys_write(fd, ", " as *u8, 2)
303 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w)))
304 sys_write(fd, ", " as *u8, 2)
305 nx_dis_putdec(fd, nx_dis_imm_i(w))
306 return 4
307 }
308 if op == 0x1B {
309 nx_dis_puts(fd, nx_dis_i_alu_mnem(nx_dis_funct3(w), nx_dis_funct7(w), 1))
310 sys_write(fd, " " as *u8, 1)
311 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
312 sys_write(fd, ", " as *u8, 2)
313 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w)))
314 sys_write(fd, ", " as *u8, 2)
315 nx_dis_putdec(fd, nx_dis_imm_i(w))
316 return 4
317 }
318
319 // Loads (0x03).
320 if op == 0x03 {
321 nx_dis_puts(fd, nx_dis_load_mnem(nx_dis_funct3(w)))
322 sys_write(fd, " " as *u8, 1)
323 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
324 sys_write(fd, ", " as *u8, 2)
325 nx_dis_putdec(fd, nx_dis_imm_i(w))
326 sys_write(fd, "(" as *u8, 1)
327 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w)))
328 sys_write(fd, ")" as *u8, 1)
329 return 4
330 }
331
332 // Stores (0x23).
333 if op == 0x23 {
334 nx_dis_puts(fd, nx_dis_store_mnem(nx_dis_funct3(w)))
335 sys_write(fd, " " as *u8, 1)
336 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs2(w)))
337 sys_write(fd, ", " as *u8, 2)
338 nx_dis_putdec(fd, nx_dis_imm_s(w))
339 sys_write(fd, "(" as *u8, 1)
340 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w)))
341 sys_write(fd, ")" as *u8, 1)
342 return 4
343 }
344
345 // Branches (0x63).
346 if op == 0x63 {
347 nx_dis_puts(fd, nx_dis_branch_mnem(nx_dis_funct3(w)))
348 sys_write(fd, " " as *u8, 1)
349 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w)))
350 sys_write(fd, ", " as *u8, 2)
351 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs2(w)))
352 sys_write(fd, ", " as *u8, 2)
353 nx_dis_putdec(fd, nx_dis_imm_b(w))
354 return 4
355 }
356
357 // U-type: lui (0x37), auipc (0x17).
358 if op == 0x37 {
359 nx_dis_puts(fd, "lui " as *u8)
360 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
361 sys_write(fd, ", " as *u8, 2)
362 nx_dis_putdec(fd, nx_dis_imm_u(w) >> 12)
363 return 4
364 }
365 if op == 0x17 {
366 nx_dis_puts(fd, "auipc " as *u8)
367 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
368 sys_write(fd, ", " as *u8, 2)
369 nx_dis_putdec(fd, nx_dis_imm_u(w) >> 12)
370 return 4
371 }
372
373 // J-type: jal (0x6F).
374 if op == 0x6F {
375 nx_dis_puts(fd, "jal " as *u8)
376 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
377 sys_write(fd, ", " as *u8, 2)
378 nx_dis_putdec(fd, nx_dis_imm_j(w))
379 return 4
380 }
381
382 // I-type jump: jalr (0x67).
383 if op == 0x67 {
384 nx_dis_puts(fd, "jalr " as *u8)
385 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rd(w)))
386 sys_write(fd, ", " as *u8, 2)
387 nx_dis_putdec(fd, nx_dis_imm_i(w))
388 sys_write(fd, "(" as *u8, 1)
389 nx_dis_puts(fd, nx_dis_reg_name(nx_dis_rs1(w)))
390 sys_write(fd, ")" as *u8, 1)
391 return 4
392 }
393
394 // System: ecall / ebreak (0x73 with rd=0, rs1=0).
395 if op == 0x73 {
396 if nx_dis_imm_i(w) == 0 {
397 nx_dis_puts(fd, "ecall" as *u8)
398 } else {
399 nx_dis_puts(fd, "ebreak" as *u8)
400 }
401 return 4
402 }
403
404 // Unknown -- print as raw word.
405 nx_dis_puts(fd, "?? 0x" as *u8)
406 let hi: i64 = (w >> 16) & 0xFFFF
407 let lo: i64 = w & 0xFFFF
408 nx_dis_putdec(fd, hi)
409 sys_write(fd, "_" as *u8, 1)
410 nx_dis_putdec(fd, lo)
411 return 4
412}
413
414// Disassemble a contiguous range; one instr per line.
415func nx_dis_range(buf: *u8, off: i64, len: i64, fd: i64) -> i64 {
416 var p: i64 = off
417 while p < off + len {
418 let n: i64 = nx_dis_one(buf, p, fd)
419 sys_write(fd, "\n" as *u8, 1)
420 if n <= 0 { return -1 }
421 p = p + n
422 }
423 return 0
424}
425
426// ---- self-test ----------------------------------------------------
427
428func main() -> i64 {
429 // Hand-encoded RV64 instructions:
430 // addi a0, zero, 42 -> 0x02A00513
431 // add s0, s1, s2 -> 0x01248433
432 // ld t0, 8(sp) -> 0x00813283
433 // sd t0, 16(sp) -> 0x00513823
434 // ecall -> 0x00000073
435 let buf: *u8 = sys_mmap(64)
436 // addi a0, zero, 42 = 0x02A00513
437 buf[0] = 0x13; buf[1] = 0x05; buf[2] = 0xA0; buf[3] = 0x02
438 // add s0, s1, s2 = 0x01248433
439 buf[4] = 0x33; buf[5] = 0x84; buf[6] = 0x24; buf[7] = 0x01
440 // ld t0, 8(sp) = 0x00813283
441 buf[8] = 0x83; buf[9] = 0x32; buf[10] = 0x81; buf[11] = 0x00
442 // sd t0, 16(sp) = 0x00513823
443 buf[12] = 0x23; buf[13] = 0x38; buf[14] = 0x51; buf[15] = 0x00
444 // ecall = 0x00000073
445 buf[16] = 0x73; buf[17] = 0x00; buf[18] = 0x00; buf[19] = 0x00
446
447 // Verify decode helpers.
448 let w0: i64 = nx_dis_word_at(buf, 0)
449 if nx_dis_opcode(w0) != 0x13 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
450 if nx_dis_rd(w0) != 10 { return __syscall(93, 11, 0, 0, 0, 0, 0) } // a0
451 if nx_dis_rs1(w0) != 0 { return __syscall(93, 12, 0, 0, 0, 0, 0) } // zero
452 if nx_dis_imm_i(w0) != 42 { return __syscall(93, 13, 0, 0, 0, 0, 0) }
453
454 let w1: i64 = nx_dis_word_at(buf, 4)
455 if nx_dis_opcode(w1) != 0x33 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
456 if nx_dis_rd(w1) != 8 { return __syscall(93, 21, 0, 0, 0, 0, 0) } // s0
457 if nx_dis_rs1(w1) != 9 { return __syscall(93, 22, 0, 0, 0, 0, 0) } // s1
458 if nx_dis_rs2(w1) != 18 { return __syscall(93, 23, 0, 0, 0, 0, 0) } // s2
459
460 let w2: i64 = nx_dis_word_at(buf, 8)
461 if nx_dis_opcode(w2) != 0x03 { return __syscall(93, 30, 0, 0, 0, 0, 0) }
462 if nx_dis_imm_i(w2) != 8 { return __syscall(93, 31, 0, 0, 0, 0, 0) }
463
464 let w3: i64 = nx_dis_word_at(buf, 12)
465 if nx_dis_opcode(w3) != 0x23 { return __syscall(93, 40, 0, 0, 0, 0, 0) }
466 if nx_dis_imm_s(w3) != 16 { return __syscall(93, 41, 0, 0, 0, 0, 0) }
467
468 let w4: i64 = nx_dis_word_at(buf, 16)
469 if nx_dis_opcode(w4) != 0x73 { return __syscall(93, 50, 0, 0, 0, 0, 0) }
470
471 // Register-name lookup.
472 let s_zero: *u8 = nx_dis_reg_name(0)
473 if s_zero[0] != 0x7A { return __syscall(93, 60, 0, 0, 0, 0, 0) } // 'z'
474 let s_ra: *u8 = nx_dis_reg_name(1)
475 if s_ra[0] != 0x72 { return __syscall(93, 61, 0, 0, 0, 0, 0) } // 'r'
476 let s_a0: *u8 = nx_dis_reg_name(10)
477 if s_a0[0] != 0x61 { return __syscall(93, 62, 0, 0, 0, 0, 0) } // 'a'
478
479 return 0
480}