nx_dwarf_str.nx source
↩ module page · 148 lines · 5330 B
1// nx_dwarf_str.nx -- DWARF .debug_str + .debug_line_str builder.
2//
3// DWARF stores its strings in dedicated sections (.debug_str /
4// .debug_line_str) so DIE entries (.debug_info) and line-program
5// file names (.debug_line) can reference them by 4- or 8-byte
6// offset instead of inlining bytes. Crucial for keeping debug
7// info compact when many DIEs share names ("int", "char *",
8// function names, etc.).
9//
10// API matches nx_strtab but with two ergonomic differences:
11//
12// * Dedup via nx_intern on the input bytes. Same input string
13// always returns the same offset, so DIE-side references
14// hash-cons cleanly.
15//
16// * `intern_at_in_pool` returns the OFFSET (the value DWARF
17// wants), not a *u8.
18//
19// Pairs with nx_dwarf_line.nx (file-name strings) and the
20// forthcoming nx_dwarf_info.nx (DIE attribute strings).
21
22// nx_safety_envelope:
23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
24// sil_target: SIL1
25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
26// verdict: NOT_YET_EVALUATED
27
28import "syscalls.nx"
29import "nx_intern.nx"
30const NX_MAGIC_1024: i64 = 1024
31
32struct NxDwStr {
33 pool: *NxIntern,
34 flat: *u8, // backing flat .debug_str-shape buffer
35 flat_used: i64, // bytes used in flat
36 flat_cap: i64,
37}
38
39const NX_DWSTR_BYTES: i64 = 32
40
41func nx_dwstr_new(bytes_cap: i64, ids_cap: i64) -> *NxDwStr {
42 let raw: *u8 = sys_mmap(NX_DWSTR_BYTES)
43 let s: *NxDwStr = raw as *NxDwStr
44 s.pool = nx_intern_new(bytes_cap, ids_cap)
45 s.flat = sys_mmap(bytes_cap)
46 s.flat[0] = 0 // DWARF .debug_str begins with a NUL too
47 s.flat_used = 1
48 s.flat_cap = bytes_cap
49 return s
50}
51
52// Insert (or look up) `bytes`; return the byte OFFSET in the
53// .debug_str section. -1 on overflow.
54func nx_dwstr_get(s: *NxDwStr, bytes: *u8, len: i64) -> i64 {
55 // Dedup via the intern pool.
56 let id: i64 = nx_intern_get(s.pool, bytes, len)
57 if id < 0 { return -1 }
58
59 // For NEW ids we must also append to the flat buffer. Since
60 // intern_get returns dense ids (0..n), we know id == n_ids - 1
61 // means we just inserted; in that case append to flat too.
62 let n_ids_now: i64 = nx_intern_count(s.pool)
63 if id == n_ids_now - 1 {
64 if s.flat_used + len + 1 > s.flat_cap { return -1 }
65 let off: i64 = s.flat_used
66 var i: i64 = 0
67 while i < len {
68 s.flat[off + i] = bytes[i]
69 i = i + 1
70 }
71 s.flat[off + len] = 0
72 s.flat_used = off + len + 1
73 return off
74 }
75
76 // Already-interned: walk via lookup to find the offset.
77 // Simple linear scan -- fine for v0.0.1. A future commit
78 // will add a parallel id->offset side-table.
79 var off: i64 = 1
80 var k: i64 = 0
81 while k < id {
82 var l: i64 = 0
83 while s.flat[off + l] != 0 { l = l + 1 }
84 off = off + l + 1
85 k = k + 1
86 }
87 return off
88}
89
90// Convenience: NUL-terminated C-string.
91func nx_dwstr_get_cstr(s: *NxDwStr, str: *u8) -> i64 {
92 var n: i64 = 0
93 while str[n] != 0 { n = n + 1 }
94 return nx_dwstr_get(s, str, n)
95}
96
97// Total bytes used by the .debug_str section (caller copies these
98// into the section buffer at link time).
99func nx_dwstr_size(s: *NxDwStr) -> i64 { return s.flat_used }
100func nx_dwstr_bytes(s: *NxDwStr) -> *u8 { return s.flat }
101
102// Read back: get the *u8 of the string starting at `offset`.
103func nx_dwstr_at(s: *NxDwStr, offset: i64) -> *u8 {
104 let base: i64 = (s.flat as i64) + offset
105 return base as *u8
106}
107
108// ---- self-test ---------------------------------------------------
109
110func main() -> i64 {
111 let s: *NxDwStr = nx_dwstr_new(NX_MAGIC_1024, 64)
112
113 // Initial: just the NUL at offset 0, size 1.
114 if nx_dwstr_size(s) != 1 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
115
116 // Append "main" -> offset 1, size 1+4+1=6.
117 let main_str: *u8 = sys_mmap(8)
118 main_str[0] = 0x6D; main_str[1] = 0x61; main_str[2] = 0x69; main_str[3] = 0x6E
119 let off_main: i64 = nx_dwstr_get(s, main_str, 4)
120 if off_main != 1 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
121 if nx_dwstr_size(s) != 6 { return __syscall(93, 21, 0, 0, 0, 0, 0) }
122
123 // Append "puts" -> offset 6, size 6+4+1=11.
124 let puts_str: *u8 = sys_mmap(8)
125 puts_str[0] = 0x70; puts_str[1] = 0x75; puts_str[2] = 0x74; puts_str[3] = 0x73
126 let off_puts: i64 = nx_dwstr_get(s, puts_str, 4)
127 if off_puts != 6 { return __syscall(93, 30, 0, 0, 0, 0, 0) }
128
129 // Re-append "main" -> SAME offset (dedup).
130 let off_main2: i64 = nx_dwstr_get(s, main_str, 4)
131 if off_main2 != 1 { return __syscall(93, 40, 0, 0, 0, 0, 0) }
132 if nx_dwstr_size(s) != 11 { return __syscall(93, 41, 0, 0, 0, 0, 0) }
133
134 // Read back at offset 1: "main\0".
135 let read_main: *u8 = nx_dwstr_at(s, 1)
136 if read_main[0] != 0x6D { return __syscall(93, 50, 0, 0, 0, 0, 0) }
137 if read_main[3] != 0x6E { return __syscall(93, 51, 0, 0, 0, 0, 0) }
138 if read_main[4] != 0 { return __syscall(93, 52, 0, 0, 0, 0, 0) }
139
140 // C-string convenience.
141 let c_str_raw: *u8 = sys_mmap(8)
142 c_str_raw[0] = 0x66; c_str_raw[1] = 0x6F; c_str_raw[2] = 0x6F // "foo\0"
143 c_str_raw[3] = 0
144 let off_foo: i64 = nx_dwstr_get_cstr(s, c_str_raw)
145 if off_foo != 11 { return __syscall(93, 60, 0, 0, 0, 0, 0) }
146
147 return 0
148}