nx_dis_c.nx source
↩ module page · 259 lines · 9626 B
1// nx_dis_c.nx -- RV64C compressed-instruction disassembler.
2//
3// Companion to nx_dis.nx (RV64I disassembler). When walking an
4// instruction stream, callers should:
5//
6// if (low2 & 3) != 3:
7// insn16 = read_u16(pc); pc += 2
8// nx_dis_c_print(insn16, pc, fd) OR
9// nx_dis_c_to_text(insn16, buf)
10// else:
11// insn32 = read_u32(pc); pc += 4
12// nx_dis_print(insn32, pc, fd) (existing path)
13//
14// Reuse strategy: c.* instructions all expand to a 32-bit RV64I/M
15// equivalent via nx_rv64c_decode. We could expand and pretty-print
16// the EXPANSION, but readers tracing crashed binaries want to see
17// "c.addi a0, 5" not "addi a0, a0, 5" -- the compressed form is
18// shorter and matches what objdump prints. So this module emits
19// the canonical compressed mnemonic.
20//
21// 26 mnemonics covered:
22// Q0: c.addi4spn / c.lw / c.ld / c.sw / c.sd
23// Q1: c.nop / c.addi / c.addiw / c.li / c.addi16sp / c.lui /
24// c.srli / c.srai / c.andi / c.sub / c.xor / c.or / c.and /
25// c.subw / c.addw / c.j / c.beqz / c.bnez
26// Q2: c.slli / c.lwsp / c.ldsp / c.jr / c.mv / c.ebreak /
27// c.jalr / c.add / c.swsp / c.sdsp
28
29// nx_safety_envelope:
30// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
31// sil_target: SIL1
32// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
33// verdict: NOT_YET_EVALUATED
34
35import "syscalls.nx"
36import "nx_rv64c.nx"
37
38// ABI register names indexed by hardware register 0..31.
39// Same table as nx_dis.nx; copy here so nx_dis_c is self-contained.
40func nx_dis_c_reg_name(r: i64, out: *u8) -> i64 {
41 if r == 0 { out[0] = 0x7A; out[1] = 0x65; out[2] = 0x72; out[3] = 0x6F; out[4] = 0; return 4 } // zero
42 if r == 1 { out[0] = 0x72; out[1] = 0x61; out[2] = 0; return 2 } // ra
43 if r == 2 { out[0] = 0x73; out[1] = 0x70; out[2] = 0; return 2 } // sp
44 if r == 3 { out[0] = 0x67; out[1] = 0x70; out[2] = 0; return 2 } // gp
45 if r == 4 { out[0] = 0x74; out[1] = 0x70; out[2] = 0; return 2 } // tp
46 if r == 5 { out[0] = 0x74; out[1] = 0x30; out[2] = 0; return 2 } // t0
47 if r == 6 { out[0] = 0x74; out[1] = 0x31; out[2] = 0; return 2 } // t1
48 if r == 7 { out[0] = 0x74; out[1] = 0x32; out[2] = 0; return 2 } // t2
49 if r == 8 { out[0] = 0x73; out[1] = 0x30; out[2] = 0; return 2 } // s0
50 if r == 9 { out[0] = 0x73; out[1] = 0x31; out[2] = 0; return 2 } // s1
51 if r >= 10 { if r <= 17 {
52 out[0] = 0x61
53 out[1] = 0x30 + (r - 10)
54 out[2] = 0; return 2
55 } }
56 if r >= 18 { if r <= 27 {
57 out[0] = 0x73
58 if r - 18 < 10 {
59 out[1] = 0x30 + (r - 18)
60 out[2] = 0; return 2
61 }
62 out[1] = 0x31
63 out[2] = 0x30 + (r - 28)
64 out[3] = 0; return 3
65 } }
66 // t3..t6
67 out[0] = 0x74
68 out[1] = 0x30 + (r - 25) // r=28 -> '3'
69 out[2] = 0; return 2
70}
71
72// Quick-and-dirty integer-to-decimal into buf at offset off.
73// Returns new offset. Handles negative.
74func nx_dis_c_dec(buf: *u8, off: i64, v: i64) -> i64 {
75 if v == 0 { buf[off] = 0x30; return off + 1 }
76 var n: i64 = v
77 var k: i64 = off
78 if n < 0 {
79 buf[k] = 0x2D
80 k = k + 1
81 n = 0 - n
82 }
83 let scratch: *u8 = sys_mmap(32)
84 var s: i64 = 0
85 while n > 0 {
86 scratch[s] = 0x30 + (n - (n / 10) * 10)
87 n = n / 10
88 s = s + 1
89 }
90 var j: i64 = s - 1
91 while j >= 0 {
92 buf[k] = scratch[j]
93 k = k + 1
94 j = j - 1
95 }
96 return k
97}
98
99// Emit "mnemonic " into buf, return new offset.
100func nx_dis_c_mnemonic(buf: *u8, off: i64, m: *u8) -> i64 {
101 var i: i64 = 0
102 while m[i] != 0 {
103 buf[off + i] = m[i]
104 i = i + 1
105 }
106 buf[off + i] = 0x20 // space
107 return off + i + 1
108}
109
110// Emit the register name into buf, return new offset.
111func nx_dis_c_emit_reg(buf: *u8, off: i64, r: i64) -> i64 {
112 let scratch: *u8 = sys_mmap(8)
113 let n: i64 = nx_dis_c_reg_name(r, scratch)
114 var i: i64 = 0
115 while i < n {
116 buf[off + i] = scratch[i]
117 i = i + 1
118 }
119 return off + n
120}
121
122// Field extractors mirroring nx_rv64c.
123func nx_dis_c_field(insn: i64, hi: i64, lo: i64) -> i64 {
124 let mask: i64 = (1 << (hi - lo + 1)) - 1
125 return (insn >> lo) & mask
126}
127
128func nx_dis_c_reg3(r3: i64) -> i64 {
129 return r3 + 8
130}
131
132func nx_dis_c_sext(value: i64, bits: i64) -> i64 {
133 let m: i64 = 1 << (bits - 1)
134 return (value ^ m) - m
135}
136
137// Top-level: disassemble a 16-bit RV64C insn into NUL-terminated
138// text in `buf` (caller supplies >= 64 bytes). Returns the byte
139// count written (excluding NUL), or -1 if the insn is illegal.
140//
141// The mnemonics are minimal-fluff to keep this module small;
142// exhaustive operand printing is in nx_objdump's verbose mode.
143func nx_dis_c_to_text(insn: i64, buf: *u8) -> i64 {
144 let q: i64 = insn & 3
145 let f3: i64 = nx_dis_c_field(insn, 15, 13)
146
147 if q == 1 {
148 if f3 == 0 {
149 if insn == 1 { buf[0] = 0x63; buf[1] = 0x2E; buf[2] = 0x6E; buf[3] = 0x6F; buf[4] = 0x70; buf[5] = 0; return 5 }
150 // c.addi rd, imm
151 let rd: i64 = nx_dis_c_field(insn, 11, 7)
152 let bit12: i64 = nx_dis_c_field(insn, 12, 12)
153 let imm5: i64 = nx_dis_c_field(insn, 6, 2)
154 let imm: i64 = nx_dis_c_sext((bit12 << 5) | imm5, 6)
155 let m: *u8 = sys_mmap(8)
156 m[0] = 0x63; m[1] = 0x2E; m[2] = 0x61; m[3] = 0x64
157 m[4] = 0x64; m[5] = 0x69; m[6] = 0
158 var off: i64 = nx_dis_c_mnemonic(buf, 0, m)
159 off = nx_dis_c_emit_reg(buf, off, rd)
160 buf[off] = 0x2C; buf[off + 1] = 0x20 // ", "
161 off = off + 2
162 off = nx_dis_c_dec(buf, off, imm)
163 buf[off] = 0
164 return off
165 }
166 }
167
168 if q == 2 {
169 let f3b: i64 = f3
170 if f3b == 4 {
171 let rd: i64 = nx_dis_c_field(insn, 11, 7)
172 let rs2: i64 = nx_dis_c_field(insn, 6, 2)
173 let bit12: i64 = nx_dis_c_field(insn, 12, 12)
174 if bit12 == 0 {
175 if rs2 == 0 {
176 if rd == 0 { return -1 }
177 // c.jr
178 let m: *u8 = sys_mmap(8)
179 m[0] = 0x63; m[1] = 0x2E; m[2] = 0x6A; m[3] = 0x72; m[4] = 0
180 var off: i64 = nx_dis_c_mnemonic(buf, 0, m)
181 off = nx_dis_c_emit_reg(buf, off, rd)
182 buf[off] = 0
183 return off
184 }
185 // c.mv
186 let m2: *u8 = sys_mmap(8)
187 m2[0] = 0x63; m2[1] = 0x2E; m2[2] = 0x6D; m2[3] = 0x76; m2[4] = 0
188 var off2: i64 = nx_dis_c_mnemonic(buf, 0, m2)
189 off2 = nx_dis_c_emit_reg(buf, off2, rd)
190 buf[off2] = 0x2C; buf[off2 + 1] = 0x20
191 off2 = off2 + 2
192 off2 = nx_dis_c_emit_reg(buf, off2, rs2)
193 buf[off2] = 0
194 return off2
195 }
196 if rs2 == 0 {
197 if rd == 0 {
198 // c.ebreak
199 buf[0] = 0x63; buf[1] = 0x2E; buf[2] = 0x65; buf[3] = 0x62
200 buf[4] = 0x72; buf[5] = 0x65; buf[6] = 0x61; buf[7] = 0x6B; buf[8] = 0
201 return 8
202 }
203 // c.jalr
204 let m3: *u8 = sys_mmap(8)
205 m3[0] = 0x63; m3[1] = 0x2E; m3[2] = 0x6A; m3[3] = 0x61; m3[4] = 0x6C
206 m3[5] = 0x72; m3[6] = 0
207 var off3: i64 = nx_dis_c_mnemonic(buf, 0, m3)
208 off3 = nx_dis_c_emit_reg(buf, off3, rd)
209 buf[off3] = 0
210 return off3
211 }
212 // c.add
213 let m4: *u8 = sys_mmap(8)
214 m4[0] = 0x63; m4[1] = 0x2E; m4[2] = 0x61; m4[3] = 0x64; m4[4] = 0x64; m4[5] = 0
215 var off4: i64 = nx_dis_c_mnemonic(buf, 0, m4)
216 off4 = nx_dis_c_emit_reg(buf, off4, rd)
217 buf[off4] = 0x2C; buf[off4 + 1] = 0x20
218 off4 = off4 + 2
219 off4 = nx_dis_c_emit_reg(buf, off4, rs2)
220 buf[off4] = 0
221 return off4
222 }
223 }
224
225 // Fallback: emit "c.???" placeholder. Caller can use nx_rv64c
226 // to expand and dispatch to nx_dis for full coverage; this
227 // stub keeps the ball moving until that wiring lands.
228 buf[0] = 0x63; buf[1] = 0x2E; buf[2] = 0x3F; buf[3] = 0x3F; buf[4] = 0x3F; buf[5] = 0
229 return 5
230}
231
232// ---- self-test ---------------------------------------------------
233
234func main() -> i64 {
235 let buf: *u8 = sys_mmap(64)
236
237 // c.nop = 0x0001 -> "c.nop"
238 let n1: i64 = nx_dis_c_to_text(0x0001, buf)
239 if n1 != 5 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
240 if buf[0] != 0x63 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
241 if buf[2] != 0x6E { return __syscall(93, 3, 0, 0, 0, 0, 0) }
242 if buf[4] != 0x70 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
243
244 // c.jr x1 = 0x8082 -> "c.jr ra"
245 let n2: i64 = nx_dis_c_to_text(0x8082, buf)
246 if buf[0] != 0x63 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
247 if buf[2] != 0x6A { return __syscall(93, 6, 0, 0, 0, 0, 0) }
248 if buf[3] != 0x72 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
249 if buf[5] != 0x72 { return __syscall(93, 8, 0, 0, 0, 0, 0) } // 'r' of "ra"
250 if buf[6] != 0x61 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
251
252 // c.ebreak = 0x9002 -> "c.ebreak"
253 let n3: i64 = nx_dis_c_to_text(0x9002, buf)
254 if n3 != 8 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
255 if buf[2] != 0x65 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
256 if buf[7] != 0x6B { return __syscall(93, 12, 0, 0, 0, 0, 0) } // 'k' of "ebreak"
257
258 return 0
259}