nx_dwarf_line.nx source
↩ module page · 289 lines · 10226 B
1// nx_dwarf_line.nx -- DWARF v5 .debug_line section builder.
2//
3// Maps machine-instruction addresses back to source-file line + col.
4// Enables `gdb` (and our future sovereign debugger) to show the .nx
5// source line when stopped at any RV64 PC.
6//
7// DWARF v5 line program (DWARF 5 spec §6.2):
8//
9// .debug_line consists of one "line number program" per compilation
10// unit. Header (~30 bytes), then a sequence of standard + extended
11// opcodes that build a "line table" via a small VM:
12//
13// state: address (u64), op_index, file, line, column,
14// is_stmt, basic_block, end_sequence, prologue_end,
15// epilogue_begin, isa, discriminator
16//
17// Standard opcodes drive the VM:
18// DW_LNS_copy commit current row
19// DW_LNS_advance_pc += operand uleb128
20// DW_LNS_advance_line += signed leb
21// DW_LNS_set_file := operand
22// DW_LNS_set_column := operand
23// ... etc
24//
25// Why now (R2 capability lever):
26//
27// * Managed platforms (V8/WASM) can't give us proper PC-to-source
28// debugging at the silicon level. We can.
29// * Pairs with nx_dbg.nx (future) for full sovereign debugging:
30// read .debug_line, decode VM, print source location at faults.
31// * F6-deterministic: same source -> same .debug_line bytes.
32//
33// What we ship here (v0.0.1):
34//
35// * Builder for the .debug_line bytes (header + line program).
36// * uleb128 / sleb128 encoders (used widely in DWARF).
37// * Helper to register (address, file, line) tuples and finalize.
38//
39// What we DEFER (separate commits):
40//
41// * .debug_info builder (DIE forest -- compilation units, types,
42// functions, variables). ~500 LoC; lands when nxc2 grows the
43// -g flag.
44// * .debug_str + .debug_str_offsets builders. Tied to nx_strtab.
45// * .debug_abbrev builder (abbreviation table for DIEs).
46// * Reader/decoder for sovereign debugger.
47
48// nx_safety_envelope:
49// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
50// sil_target: SIL1
51// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
52// verdict: NOT_YET_EVALUATED
53
54import "syscalls.nx"
55
56// ---- VM standard opcodes (DWARF 5 §6.2.5.2) ----------------------
57
58const DW_LNS_copy: i64 = 0x01
59const DW_LNS_advance_pc: i64 = 0x02
60const DW_LNS_advance_line: i64 = 0x03
61const DW_LNS_set_file: i64 = 0x04
62const DW_LNS_set_column: i64 = 0x05
63const DW_LNS_negate_stmt: i64 = 0x06
64const DW_LNS_set_basic_block: i64 = 0x07
65const DW_LNS_const_add_pc: i64 = 0x08
66const DW_LNS_fixed_advance_pc: i64 = 0x09
67const DW_LNS_set_prologue_end: i64 = 0x0A
68const DW_LNS_set_epilogue_begin: i64 = 0x0B
69const DW_LNS_set_isa: i64 = 0x0C
70
71// Extended opcodes (preceded by 0 + uleb-length).
72const DW_LNE_end_sequence: i64 = 0x01
73const DW_LNE_set_address: i64 = 0x02
74const DW_LNE_set_discriminator: i64 = 0x04
75
76// Special-opcode tuning constants. Keep simple defaults; line_base
77// = -5 / line_range = 14 / opcode_base = 13 are common across LLVM
78// + GCC.
79const NX_DW_LINE_BASE: i64 = -5
80const NX_DW_LINE_RANGE: i64 = 14
81const NX_DW_OPCODE_BASE: i64 = 13
82const NX_DW_MIN_INST_LEN: i64 = 1
83const NX_DW_MAX_OPS_PER_INST: i64 = 1
84const NX_DW_DEFAULT_IS_STMT: i64 = 1
85
86// ---- builder context ---------------------------------------------
87
88struct NxDwLine {
89 buf: *u8,
90 cap: i64,
91 used: i64,
92 cur_addr: i64, // VM state: current address
93 cur_line: i64, // VM state: current line
94 cur_file: i64, // VM state: current file index (1-based)
95}
96
97const NX_DWLINE_BYTES: i64 = 48
98
99func nx_dwline_new(cap: i64) -> *NxDwLine {
100 let raw: *u8 = sys_mmap(NX_DWLINE_BYTES)
101 let d: *NxDwLine = raw as *NxDwLine
102 d.buf = sys_mmap(cap)
103 d.cap = cap
104 d.used = 0
105 d.cur_addr = 0
106 d.cur_line = 1 // DWARF lines are 1-based
107 d.cur_file = 1 // first file
108 return d
109}
110
111// ---- byte append ---------------------------------------------------
112
113func nx_dwline_byte(d: *NxDwLine, v: i64) -> i64 {
114 if d.used >= d.cap { return -1 }
115 d.buf[d.used] = v & 0xFF
116 d.used = d.used + 1
117 return 0
118}
119
120// uleb128 encoder.
121func nx_dwline_uleb(d: *NxDwLine, v: i64) -> i64 {
122 var x: i64 = v
123 var go: i64 = 1
124 while go == 1 {
125 var b: i64 = x & 0x7F
126 x = x >> 7
127 if x != 0 { b = b | 0x80 }
128 if nx_dwline_byte(d, b) < 0 { return -1 }
129 if x == 0 { go = 0 }
130 }
131 return 0
132}
133
134// sleb128 encoder. Two's-complement with sign-extension stop rule.
135func nx_dwline_sleb(d: *NxDwLine, v: i64) -> i64 {
136 var x: i64 = v
137 var go: i64 = 1
138 while go == 1 {
139 var b: i64 = x & 0x7F
140 // Arithmetic shift: preserves sign for negatives.
141 x = x >> 7
142 let sign_bit: i64 = b & 0x40
143 let done: i64 = ((x == 0) & (sign_bit == 0)) | ((x == -1) & (sign_bit != 0))
144 if done == 0 { b = b | 0x80 }
145 if nx_dwline_byte(d, b) < 0 { return -1 }
146 if done != 0 { go = 0 }
147 }
148 return 0
149}
150
151// ---- VM ops ------------------------------------------------------
152
153// DW_LNS_advance_pc: opcode + uleb operand
154func nx_dwline_advance_pc(d: *NxDwLine, delta: i64) -> i64 {
155 if delta < 0 { return -1 }
156 nx_dwline_byte(d, DW_LNS_advance_pc)
157 nx_dwline_uleb(d, delta / NX_DW_MIN_INST_LEN)
158 d.cur_addr = d.cur_addr + delta
159 return 0
160}
161
162// DW_LNS_advance_line: opcode + sleb operand
163func nx_dwline_advance_line(d: *NxDwLine, delta: i64) -> i64 {
164 nx_dwline_byte(d, DW_LNS_advance_line)
165 nx_dwline_sleb(d, delta)
166 d.cur_line = d.cur_line + delta
167 return 0
168}
169
170// DW_LNS_set_file: opcode + uleb file index
171func nx_dwline_set_file(d: *NxDwLine, file_idx: i64) -> i64 {
172 nx_dwline_byte(d, DW_LNS_set_file)
173 nx_dwline_uleb(d, file_idx)
174 d.cur_file = file_idx
175 return 0
176}
177
178// DW_LNS_copy: commit current row
179func nx_dwline_copy(d: *NxDwLine) -> i64 {
180 return nx_dwline_byte(d, DW_LNS_copy)
181}
182
183// Extended op: end of sequence (closes the program).
184func nx_dwline_end_sequence(d: *NxDwLine) -> i64 {
185 // 0x00 (extended marker), uleb128(1) (length), DW_LNE_end_sequence
186 nx_dwline_byte(d, 0)
187 nx_dwline_byte(d, 1)
188 nx_dwline_byte(d, DW_LNE_end_sequence)
189 return 0
190}
191
192// Extended op: set address (8 bytes for 64-bit).
193func nx_dwline_set_address(d: *NxDwLine, addr: i64) -> i64 {
194 nx_dwline_byte(d, 0)
195 nx_dwline_byte(d, 9) // length: opcode + 8 bytes
196 nx_dwline_byte(d, DW_LNE_set_address)
197 nx_dwline_byte(d, (addr ) & 0xFF)
198 nx_dwline_byte(d, (addr >> 8) & 0xFF)
199 nx_dwline_byte(d, (addr >> 16) & 0xFF)
200 nx_dwline_byte(d, (addr >> 24) & 0xFF)
201 nx_dwline_byte(d, (addr >> 32) & 0xFF)
202 nx_dwline_byte(d, (addr >> 40) & 0xFF)
203 nx_dwline_byte(d, (addr >> 48) & 0xFF)
204 nx_dwline_byte(d, (addr >> 56) & 0xFF)
205 d.cur_addr = addr
206 return 0
207}
208
209// ---- high-level: register one (addr, file, line) row -------------
210//
211// Idiomatic usage: walk emitted instructions and call this for each
212// statement boundary. The builder advances PC + line via the VM,
213// then DW_LNS_copy commits.
214
215func nx_dwline_record(d: *NxDwLine, addr: i64, file_idx: i64, line: i64) -> i64 {
216 if file_idx != d.cur_file {
217 nx_dwline_set_file(d, file_idx)
218 }
219 let pc_delta: i64 = addr - d.cur_addr
220 if pc_delta < 0 {
221 // Backward jump (e.g. inlining): emit DW_LNE_set_address.
222 nx_dwline_set_address(d, addr)
223 } else {
224 if pc_delta > 0 { nx_dwline_advance_pc(d, pc_delta) }
225 }
226 let line_delta: i64 = line - d.cur_line
227 if line_delta != 0 { nx_dwline_advance_line(d, line_delta) }
228 return nx_dwline_copy(d)
229}
230
231// ---- query --------------------------------------------------------
232
233func nx_dwline_size(d: *NxDwLine) -> i64 { return d.used }
234func nx_dwline_bytes(d: *NxDwLine) -> *u8 { return d.buf }
235
236// ---- self-test ----------------------------------------------------
237
238func main() -> i64 {
239 let d: *NxDwLine = nx_dwline_new(256)
240
241 // Empty: 0 bytes.
242 if nx_dwline_size(d) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
243
244 // Encode a few typical operations.
245
246 // Set initial address to 0x10000.
247 nx_dwline_set_address(d, 0x10000)
248 // Bytes: 0x00, 0x09, 0x02, then 8 LE bytes for 0x10000.
249 if d.buf[0] != 0x00 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
250 if d.buf[1] != 0x09 { return __syscall(93, 21, 0, 0, 0, 0, 0) }
251 if d.buf[2] != 0x02 { return __syscall(93, 22, 0, 0, 0, 0, 0) }
252 if d.buf[3] != 0x00 { return __syscall(93, 23, 0, 0, 0, 0, 0) } // addr LE byte 0
253 if d.buf[4] != 0x00 { return __syscall(93, 24, 0, 0, 0, 0, 0) }
254 if d.buf[5] != 0x01 { return __syscall(93, 25, 0, 0, 0, 0, 0) } // 0x10000 byte 2
255 if d.cur_addr != 0x10000 { return __syscall(93, 26, 0, 0, 0, 0, 0) }
256
257 // Advance PC by 4 (one RV64 instr).
258 let s_before: i64 = d.used
259 nx_dwline_advance_pc(d, 4)
260 // Expect: 0x02 + uleb128(4) = 2 bytes total.
261 if (d.used - s_before) != 2 { return __syscall(93, 30, 0, 0, 0, 0, 0) }
262 if d.buf[s_before + 0] != DW_LNS_advance_pc { return __syscall(93, 31, 0, 0, 0, 0, 0) }
263 if d.buf[s_before + 1] != 4 { return __syscall(93, 32, 0, 0, 0, 0, 0) }
264 if d.cur_addr != 0x10004 { return __syscall(93, 33, 0, 0, 0, 0, 0) }
265
266 // Advance line by +3.
267 let s_line: i64 = d.used
268 nx_dwline_advance_line(d, 3)
269 if (d.used - s_line) != 2 { return __syscall(93, 40, 0, 0, 0, 0, 0) }
270 if d.buf[s_line + 0] != DW_LNS_advance_line { return __syscall(93, 41, 0, 0, 0, 0, 0) }
271 if d.buf[s_line + 1] != 3 { return __syscall(93, 42, 0, 0, 0, 0, 0) }
272 if d.cur_line != 4 { return __syscall(93, 43, 0, 0, 0, 0, 0) }
273
274 // Test high-level record helper.
275 let d2: *NxDwLine = nx_dwline_new(256)
276 nx_dwline_set_address(d2, 0x10000)
277 nx_dwline_record(d2, 0x10010, 1, 5)
278 if d2.cur_addr != 0x10010 { return __syscall(93, 50, 0, 0, 0, 0, 0) }
279 if d2.cur_line != 5 { return __syscall(93, 51, 0, 0, 0, 0, 0) }
280
281 nx_dwline_record(d2, 0x10014, 1, 6)
282 if d2.cur_addr != 0x10014 { return __syscall(93, 52, 0, 0, 0, 0, 0) }
283 if d2.cur_line != 6 { return __syscall(93, 53, 0, 0, 0, 0, 0) }
284
285 // End sequence cleanly.
286 nx_dwline_end_sequence(d2)
287
288 return 0
289}