nx_dwarf_loc.nx source
↩ module page · 245 lines · 8324 B
1// nx_dwarf_loc.nx -- DWARF location expressions (DW_OP_*).
2//
3// Closes the gap in the DWARF emit pipeline: nx_dwarf_info today
4// can describe what a variable IS (DIE for the type) but cannot
5// say WHERE it lives (frame offset, register, etc.). Without
6// location expressions, gdb shows "<optimized out>" for everything.
7//
8// A DWARF location expression is a stack-machine bytecode evaluated
9// by the debugger's expression interpreter. The bytecode includes:
10// - Push/Pop constants and registers
11// - Arithmetic (plus, minus, mul, div, etc.)
12// - Dereferences (deref, deref_size)
13// - Composite-piece markers (for split-storage values)
14//
15// Common cases this module emits:
16// - Frame-relative local: DW_OP_fbreg <sleb128 offset>
17// - In a specific register: DW_OP_regN (N = 0..31) or
18// DW_OP_regx <uleb128 reg>
19// - Constant address: DW_OP_addr <8-byte vaddr>
20// - Composite (e.g. struct split across regs): DW_OP_piece
21//
22// Reference: DWARF v5 spec, section 2.5 (location expressions),
23// table 7.9 (operation encodings).
24//
25// Pairs with nx_dwarf_info -- the loc-expr bytes go inside a
26// DW_AT_location attribute (DW_FORM_exprloc).
27
28// nx_safety_envelope:
29// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
30// sil_target: SIL1
31// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
32// verdict: NOT_YET_EVALUATED
33
34import "syscalls.nx"
35
36// DW_OP_* opcodes we expose.
37const NX_DW_OP_addr: i64 = 0x03
38const NX_DW_OP_deref: i64 = 0x06
39const NX_DW_OP_const1u: i64 = 0x08
40const NX_DW_OP_const1s: i64 = 0x09
41const NX_DW_OP_const2u: i64 = 0x0A
42const NX_DW_OP_const4u: i64 = 0x0C
43const NX_DW_OP_const8u: i64 = 0x0E
44const NX_DW_OP_constu: i64 = 0x10
45const NX_DW_OP_consts: i64 = 0x11
46const NX_DW_OP_dup: i64 = 0x12
47const NX_DW_OP_drop: i64 = 0x13
48const NX_DW_OP_over: i64 = 0x14
49const NX_DW_OP_pick: i64 = 0x15
50const NX_DW_OP_swap: i64 = 0x16
51const NX_DW_OP_rot: i64 = 0x17
52const NX_DW_OP_xderef: i64 = 0x18
53const NX_DW_OP_abs: i64 = 0x19
54const NX_DW_OP_and: i64 = 0x1A
55const NX_DW_OP_div: i64 = 0x1B
56const NX_DW_OP_minus: i64 = 0x1C
57const NX_DW_OP_mod: i64 = 0x1D
58const NX_DW_OP_mul: i64 = 0x1E
59const NX_DW_OP_neg: i64 = 0x1F
60const NX_DW_OP_not: i64 = 0x20
61const NX_DW_OP_or: i64 = 0x21
62const NX_DW_OP_plus: i64 = 0x22
63const NX_DW_OP_plus_uconst: i64 = 0x23
64const NX_DW_OP_shl: i64 = 0x24
65const NX_DW_OP_shr: i64 = 0x25
66const NX_DW_OP_shra: i64 = 0x26
67const NX_DW_OP_xor: i64 = 0x27
68const NX_DW_OP_eq: i64 = 0x29
69const NX_DW_OP_ge: i64 = 0x2A
70const NX_DW_OP_gt: i64 = 0x2B
71const NX_DW_OP_le: i64 = 0x2C
72const NX_DW_OP_lt: i64 = 0x2D
73const NX_DW_OP_ne: i64 = 0x2E
74const NX_DW_OP_lit0: i64 = 0x30 // lit0..lit31 = 0x30..0x4F
75const NX_DW_OP_reg0: i64 = 0x50 // reg0..reg31 = 0x50..0x6F
76const NX_DW_OP_breg0: i64 = 0x70 // breg0..breg31 = 0x70..0x8F
77const NX_DW_OP_regx: i64 = 0x90
78const NX_DW_OP_fbreg: i64 = 0x91
79const NX_DW_OP_bregx: i64 = 0x92
80const NX_DW_OP_piece: i64 = 0x93
81const NX_DW_OP_deref_size: i64 = 0x94
82const NX_DW_OP_call_frame_cfa: i64 = 0x9C
83
84// Builder = simple growable byte buffer.
85struct NxDwLoc {
86 buf: *u8,
87 len: i64,
88 cap: i64,
89}
90
91const NX_DWLOC_BYTES: i64 = 24
92
93func nx_dwloc_new(cap: i64) -> *NxDwLoc {
94 let raw: *u8 = sys_mmap(NX_DWLOC_BYTES)
95 let l: *NxDwLoc = raw as *NxDwLoc
96 l.cap = cap
97 l.buf = sys_mmap(cap)
98 l.len = 0
99 return l
100}
101
102func nx_dwloc_byte(l: *NxDwLoc, b: i64) -> i64 {
103 if l.len >= l.cap { return -1 }
104 l.buf[l.len] = b & 0xFF
105 l.len = l.len + 1
106 return 0
107}
108
109// LEB128 encoders.
110
111func nx_dwloc_uleb128(l: *NxDwLoc, v: i64) -> i64 {
112 var n: i64 = v
113 var done: i64 = 0
114 while done == 0 {
115 let lo: i64 = n & 0x7F
116 n = (n >> 7) & 0x01FFFFFFFFFFFFFF // logical shift
117 if n == 0 {
118 nx_dwloc_byte(l, lo)
119 done = 1
120 } else {
121 nx_dwloc_byte(l, lo | 0x80)
122 }
123 }
124 return 0
125}
126
127func nx_dwloc_sleb128(l: *NxDwLoc, v: i64) -> i64 {
128 var n: i64 = v
129 var done: i64 = 0
130 while done == 0 {
131 let lo: i64 = n & 0x7F
132 let sign: i64 = lo & 0x40
133 let n2: i64 = n >> 7
134 let last_pos: i64 = (n == 0)
135 let last_neg: i64 = (n == (0 - 1))
136 var stop: i64 = 0
137 if n2 == 0 {
138 if sign == 0 { stop = 1 }
139 }
140 if n2 == (0 - 1) {
141 if sign != 0 { stop = 1 }
142 }
143 if stop == 1 {
144 nx_dwloc_byte(l, lo)
145 done = 1
146 } else {
147 nx_dwloc_byte(l, lo | 0x80)
148 n = n2
149 }
150 }
151 return 0
152}
153
154// ---- high-level emitters -----------------------------------------
155
156// Push the absolute address `vaddr` (8-byte little-endian).
157func nx_dwloc_addr(l: *NxDwLoc, vaddr: i64) -> i64 {
158 nx_dwloc_byte(l, NX_DW_OP_addr)
159 var i: i64 = 0
160 while i < 8 {
161 nx_dwloc_byte(l, (vaddr >> (i * 8)) & 0xFF)
162 i = i + 1
163 }
164 return 0
165}
166
167// Variable lives in register `r` (0..31): emit DW_OP_regN if N<=31,
168// else DW_OP_regx <uleb r>.
169func nx_dwloc_reg(l: *NxDwLoc, r: i64) -> i64 {
170 if r <= 31 {
171 return nx_dwloc_byte(l, NX_DW_OP_reg0 + r)
172 }
173 nx_dwloc_byte(l, NX_DW_OP_regx)
174 return nx_dwloc_uleb128(l, r)
175}
176
177// Variable lives at [reg + offset]: emit DW_OP_bregN <sleb off>
178// or DW_OP_bregx <uleb r> <sleb off>.
179func nx_dwloc_breg(l: *NxDwLoc, r: i64, off: i64) -> i64 {
180 if r <= 31 {
181 nx_dwloc_byte(l, NX_DW_OP_breg0 + r)
182 return nx_dwloc_sleb128(l, off)
183 }
184 nx_dwloc_byte(l, NX_DW_OP_bregx)
185 nx_dwloc_uleb128(l, r)
186 return nx_dwloc_sleb128(l, off)
187}
188
189// Frame-base + offset (most common for locals).
190func nx_dwloc_fbreg(l: *NxDwLoc, off: i64) -> i64 {
191 nx_dwloc_byte(l, NX_DW_OP_fbreg)
192 return nx_dwloc_sleb128(l, off)
193}
194
195// Composite piece marker: previous expression provides `n` bytes.
196func nx_dwloc_piece(l: *NxDwLoc, n_bytes: i64) -> i64 {
197 nx_dwloc_byte(l, NX_DW_OP_piece)
198 return nx_dwloc_uleb128(l, n_bytes)
199}
200
201// ---- self-test ---------------------------------------------------
202
203func main() -> i64 {
204 let l: *NxDwLoc = nx_dwloc_new(64)
205
206 // Encode "local at fbreg -16"
207 nx_dwloc_fbreg(l, 0 - 16)
208 if l.len != 2 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
209 if l.buf[0] != NX_DW_OP_fbreg { return __syscall(93, 2, 0, 0, 0, 0, 0) }
210 // sleb128(-16): -16 is 0x70 (0111 0000) -- top bit set in low 7 bits
211 // means sign extension would produce all-ones, so this is a
212 // single-byte sleb128 where bit6 is the sign bit. -16 = ...11110000
213 // which lo7 = 0x70. Top bit 0 means stop.
214 if l.buf[1] != 0x70 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
215
216 // Reset + encode "var in reg 10 (a0)"
217 let l2: *NxDwLoc = nx_dwloc_new(64)
218 nx_dwloc_reg(l2, 10)
219 if l2.len != 1 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
220 if l2.buf[0] != NX_DW_OP_reg0 + 10 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
221
222 // breg(reg 8 = s0, +24)
223 let l3: *NxDwLoc = nx_dwloc_new(64)
224 nx_dwloc_breg(l3, 8, 24)
225 if l3.buf[0] != NX_DW_OP_breg0 + 8 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
226 if l3.buf[1] != 24 { return __syscall(93, 7, 0, 0, 0, 0, 0) } // sleb128(24) = single byte 24
227
228 // addr(0x12345678)
229 let l4: *NxDwLoc = nx_dwloc_new(64)
230 nx_dwloc_addr(l4, 0x12345678)
231 if l4.len != 9 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
232 if l4.buf[0] != NX_DW_OP_addr { return __syscall(93, 9, 0, 0, 0, 0, 0) }
233 if l4.buf[1] != 0x78 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
234 if l4.buf[2] != 0x56 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
235 if l4.buf[3] != 0x34 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
236 if l4.buf[4] != 0x12 { return __syscall(93, 13, 0, 0, 0, 0, 0) }
237
238 // reg32 should use bregx encoding (regx for r>31)
239 let l5: *NxDwLoc = nx_dwloc_new(64)
240 nx_dwloc_reg(l5, 50)
241 if l5.buf[0] != NX_DW_OP_regx { return __syscall(93, 14, 0, 0, 0, 0, 0) }
242 if l5.buf[1] != 50 { return __syscall(93, 15, 0, 0, 0, 0, 0) } // uleb128(50) single byte
243
244 return 0
245}