nx_dwline_stmt_gate.nx source
↩ module page · 232 lines · 10955 B
1// nx_dwline_stmt_gate.nx -- DOES STATEMENT GRANULARITY ACTUALLY BEAT FUNCTION GRANULARITY?
2//
3// DDR-006 rung 2. Rung 1 emitted ONE .loc per function. This gate does not merely assert that the
4// new compiler emits line info -- rung 1 did that too, and a gate that both versions pass measures
5// nothing. It runs the SAME probe through BOTH compilers and measures the DIFFERENCE:
6//
7// OLD _offc/nx_cc_sovereign.elf (blessed 3bd47652, function granularity) -> lines {5, 9}
8// NEW ../nx_compile_x86.elf (statement granularity) -> must ALSO carry 6 and 10
9//
10// runtime/nx_probe_dbgtiny.nx:
11// L5 func dbgtiny_a() -> i64 { L6 return 1 L7 }
12// L9 func main() -> i64 { L10 return dbgtiny_a() L11 }
13// so lines 6 and 10 are STATEMENT lines that function granularity CANNOT produce, and lines 5 and 9
14// are the function lines it already produced. THE OLD COMPILER IS THE NEGATIVE CONTROL, and it is
15// what makes this a measurement of the improvement rather than a restatement of the feature.
16//
17// Deliberately reads the ".s" rather than the ELF: the claim under test is what the COMPILER emits.
18// nx_dwline_e2e_gate already proves the assembler and decoder carry it through to a real ELF, so
19// duplicating that here would test the wrong subject.
20//
21// license_tier: ORIGINAL expect_exit: 0
22import "syscalls.nx"
23import "nx_elf_read.nx"
24import "nx_addr2line_lib.nx"
25import "nx_gate_verdict.nx"
26
27const SG_CAP: i64 = 4194304
28const SG_OLD: *u8 = "_offc/nx_cc_sovereign.elf" as *u8
29const SG_NEW: *u8 = "../nx_compile_x86.elf" as *u8
30const SG_SRC: *u8 = "runtime/nx_probe_dbgtiny.nx" as *u8
31const SG_S_OLD: *u8 = "/tmp/nx_dwstmt_old.s" as *u8
32const SG_S_NEW: *u8 = "/tmp/nx_dwstmt_new.s" as *u8
33
34func sg_run_out(elf: *u8, args: *i64, nargs: i64, outfile: *u8) -> i64 {
35 let pid: i64 = sys_fork()
36 if pid == 0 {
37 let fd: i64 = sys_openat_wr(outfile, 0x1a4)
38 if fd >= 0 { sys_dup3(fd, 1, 0) }
39 let argv: *i64 = sys_mmap(8 * (nargs + 2)) as *i64
40 let envp: *i64 = sys_mmap(16) as *i64
41 envp[0] = 0
42 argv[0] = elf as i64
43 var i: i64 = 0
44 while i < nargs { argv[i+1] = args[i]; i = i + 1 }
45 argv[nargs+1] = 0
46 sys_execve(elf, argv, envp)
47 sys_exit(127)
48 }
49 let st: *i64 = sys_mmap(16) as *i64
50 sys_wait4(pid, st, 0)
51 if (st[0] % 128) != 0 { return 0 - 1 }
52 return (st[0] >> 8) & 0xff
53}
54
55func sg_read(path: *u8, buf: *u8, cap: i64) -> i64 {
56 let fd: i64 = sys_openat_rd(path)
57 if fd < 0 { return 0 - 1 }
58 var tot: i64 = 0
59 while tot < cap {
60 let n: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot)
61 if n <= 0 { break }
62 tot = tot + n
63 }
64 sys_close(fd)
65 return tot
66}
67
68// count occurrences of the exact directive " .loc 1 <line>\n"
69func sg_count_loc(buf: *u8, n: i64, line: i64) -> i64 {
70 let pat: *u8 = sys_mmap(64)
71 var p: i64 = 0
72 pat[p] = 46 as u8; p = p + 1 // .
73 pat[p] = 108 as u8; p = p + 1 // l
74 pat[p] = 111 as u8; p = p + 1 // o
75 pat[p] = 99 as u8; p = p + 1 // c
76 pat[p] = 32 as u8; p = p + 1 // space
77 pat[p] = 49 as u8; p = p + 1 // 1 (file index)
78 pat[p] = 32 as u8; p = p + 1 // space
79 // decimal line
80 var m: i64 = line
81 let t: *u8 = sys_mmap(32)
82 var k: i64 = 0
83 if m == 0 { t[0] = 48 as u8; k = 1 }
84 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
85 var j: i64 = 0
86 while j < k { pat[p] = t[k-1-j]; p = p + 1; j = j + 1 }
87 pat[p] = 10 as u8; p = p + 1 // newline -- anchors the match so ".loc 1 1" cannot match ".loc 1 10"
88 let plen: i64 = p
89
90 var hits: i64 = 0
91 var i: i64 = 0
92 while i + plen <= n {
93 var q: i64 = 0
94 var ok: i64 = 1
95 while q < plen { if buf[i+q] != pat[q] { ok = 0; q = plen } else { q = q + 1 } }
96 if ok == 1 { hits = hits + 1 }
97 i = i + 1
98 }
99 return hits
100}
101
102func main() -> i64 {
103 let ctr: *i64 = gv_ctr()
104 gv_head("=== NX-DWLINE-STMT GATE -- statement granularity vs the blessed function-granularity build ===" as *u8)
105
106 sys_chdir("buildroot" as *u8)
107
108 let args: *i64 = sys_mmap(64) as *i64
109 args[0] = "-g" as i64
110 args[1] = SG_SRC as i64
111
112 let rc_old: i64 = sg_run_out(SG_OLD, args, 2, SG_S_OLD)
113 let rc_new: i64 = sg_run_out(SG_NEW, args, 2, SG_S_NEW)
114 gv_check("old-compiler-runs" as *u8, (rc_old == 0) as i64, ctr)
115 gv_check("new-compiler-runs" as *u8, (rc_new == 0) as i64, ctr)
116
117 let ob: *u8 = sys_mmap(SG_CAP)
118 let nb: *u8 = sys_mmap(SG_CAP)
119 let on: i64 = sg_read(SG_S_OLD, ob, SG_CAP)
120 let nn: i64 = sg_read(SG_S_NEW, nb, SG_CAP)
121 gv_check("both-emitted-assembly" as *u8, ((on > 256) & (nn > 256)) as i64, ctr)
122
123 let o5: i64 = sg_count_loc(ob, on, 5)
124 let o6: i64 = sg_count_loc(ob, on, 6)
125 let o9: i64 = sg_count_loc(ob, on, 9)
126 let o10: i64 = sg_count_loc(ob, on, 10)
127 let n5: i64 = sg_count_loc(nb, nn, 5)
128 let n6: i64 = sg_count_loc(nb, nn, 6)
129 let n9: i64 = sg_count_loc(nb, nn, 9)
130 let n10: i64 = sg_count_loc(nb, nn, 10)
131
132 // BASELINE: the blessed compiler carries the FUNCTION lines. It used to be asserted here that it LACKED the
133 // statement lines -- the differential this gate was born to measure. MEASURED 2026-09-02: OLD(blessed) reads
134 // L6=2 L10=2, total=6, equal to NEW: statement granularity was PROMOTED into the toolchain and the differential
135 // collapsed, so the old neg-control read RED on every beat for a feature that had landed. The oracle is now the
136 // feature PINNED: the blessed compiler must keep carrying the statement rows (a toolchain that regressed them
137 // would fail here), and NEW must equal the blessed count rather than exceed it.
138 gv_check("baseline-has-function-lines-5-and-9" as *u8, ((o5 > 0) & (o9 > 0)) as i64, ctr)
139 gv_check("blessed-compiler-CARRIES-statement-lines-6-and-10-the-feature-is-promoted" as *u8, ((o6 > 0) & (o10 > 0)) as i64, ctr)
140
141 // THE IMPROVEMENT, measured: the new compiler keeps everything the old one had...
142 gv_check("new-keeps-function-lines-5-and-9" as *u8, ((n5 > 0) & (n9 > 0)) as i64, ctr)
143 // ...and ADDS the statement lines function granularity structurally cannot express.
144 gv_check("new-ADDS-statement-line-6" as *u8, (n6 > 0) as i64, ctr)
145 gv_check("new-ADDS-statement-line-10" as *u8, (n10 > 0) as i64, ctr)
146
147 // NO SWAP: a compiler that emitted 6 and 10 while LOSING 5 and 9 would pass the checks above and be a regression
148 // wearing a feature's clothes. With the feature blessed, NEW and OLD must agree on the row count (a strict-greater
149 // test would now demand the statement build ADD rows the blessed build already has -- RED forever by construction).
150 let o_tot: i64 = o5 + o6 + o9 + o10
151 let n_tot: i64 = n5 + n6 + n9 + n10
152 gv_check("new-row-count-equals-the-blessed-count-both-statement-granular" as *u8, (n_tot == o_tot) as i64, ctr)
153 gv_check("neg-control-row-counts-are-positive-not-an-empty-agreement" as *u8, (n_tot > 0) as i64, ctr)
154
155 // ---- AND THE CHAIN MUST CARRY THEM: .loc -> nxasm -> .debug_line -> decoder ----------------
156 // The counts above prove only what the COMPILER emitted. Whether the extra rows survive the
157 // assembler and come back out of a real line table is a separate claim, and inferring it from
158 // "the mechanism is the same" is exactly the kind of unmeasured composition this estate keeps
159 // paying for. So assemble the NEW .s and decode the ELF.
160 let aargs: *i64 = sys_mmap(64) as *i64
161 aargs[0] = SG_S_NEW as i64
162 aargs[1] = "/tmp/nx_dwstmt_new.elf" as i64
163 let rc_asm: i64 = sg_run_out("_offc/nxasm_x86_main.elf" as *u8, aargs, 2, "/tmp/nx_dwstmt_asm.log" as *u8)
164 gv_check("new-s-assembles" as *u8, (rc_asm == 0) as i64, ctr)
165
166 let eb2: *u8 = sys_mmap(SG_CAP)
167 let en2: i64 = sg_read("/tmp/nx_dwstmt_new.elf" as *u8, eb2, SG_CAP)
168 let hh: *NxElfHeader = nx_elf_parse_header(eb2, en2)
169 var dloff: i64 = 0
170 var dlsz: i64 = 0
171 if hh.valid == 1 {
172 let shstr: *NxElfShdr = nx_elf_read_shdr(eb2, hh, hh.e_shstrndx)
173 var si: i64 = 0
174 while si < hh.e_shnum {
175 let sh: *NxElfShdr = nx_elf_read_shdr(eb2, hh, si)
176 let nm: *u8 = nx_elf_strtab_at(eb2, shstr.sh_offset, sh.sh_name)
177 var q: i64 = 0
178 var same: i64 = 1
179 let want: *u8 = ".debug_line" as *u8
180 var qf: i64 = 0 // exit by FLAG (2026-09-02, nx_srclint rule 2: `q = 11` inside `while want[q]` relied on want[11] being the NUL)
181 while qf == 0 { if want[q] == (0 as u8) { qf = 1 } else { if nm[q] != want[q] { same = 0; qf = 1 } else { q = q + 1 } } }
182 if same == 1 { if nm[11] == (0 as u8) { dloff = sh.sh_offset; dlsz = sh.sh_size } }
183 si = si + 1
184 }
185 }
186 gv_check("new-elf-carries-debug-line" as *u8, (dlsz > 0) as i64, ctr)
187
188 // Scan the text range and collect which of the four source lines are RECOVERABLE from addresses.
189 let dsect: *u8 = ((eb2 as i64) + dloff) as *u8
190 var g5: i64 = 0
191 var g6: i64 = 0
192 var g9: i64 = 0
193 var g10: i64 = 0
194 var sa2: i64 = hh.e_entry
195 let se2: i64 = hh.e_entry + 512
196 while sa2 < se2 {
197 let rr: *NxA2LResult = nx_a2l_run_section(dsect, dlsz, sa2)
198 if rr.found == 1 {
199 if rr.line == 5 { g5 = 1 }
200 if rr.line == 6 { g6 = 1 }
201 if rr.line == 9 { g9 = 1 }
202 if rr.line == 10 { g10 = 1 }
203 }
204 sa2 = sa2 + 1
205 }
206 gv_check("decoder-recovers-function-lines-5-and-9" as *u8, ((g5 == 1) & (g9 == 1)) as i64, ctr)
207 gv_check("decoder-recovers-STATEMENT-lines-6-and-10" as *u8, ((g6 == 1) & (g10 == 1)) as i64, ctr)
208
209 gv_puts("\n --- end-to-end: lines recoverable from real addresses ---\n" as *u8)
210 gv_puts(" debug_line_bytes=" as *u8); gv_num(dlsz)
211 gv_puts(" L5=" as *u8); gv_num(g5)
212 gv_puts(" L6=" as *u8); gv_num(g6)
213 gv_puts(" L9=" as *u8); gv_num(g9)
214 gv_puts(" L10=" as *u8); gv_num(g10); gv_puts("\n" as *u8)
215
216 gv_puts("\n --- measured .loc rows for the same probe ---\n" as *u8)
217 gv_puts(" OLD(blessed): L5=" as *u8); gv_num(o5)
218 gv_puts(" L6=" as *u8); gv_num(o6)
219 gv_puts(" L9=" as *u8); gv_num(o9)
220 gv_puts(" L10=" as *u8); gv_num(o10)
221 gv_puts(" total=" as *u8); gv_num(o_tot); gv_puts("\n" as *u8)
222 gv_puts(" NEW(stmt): L5=" as *u8); gv_num(n5)
223 gv_puts(" L6=" as *u8); gv_num(n6)
224 gv_puts(" L9=" as *u8); gv_num(n9)
225 gv_puts(" L10=" as *u8); gv_num(n10)
226 gv_puts(" total=" as *u8); gv_num(n_tot); gv_puts("\n" as *u8)
227 gv_puts(" s_bytes old=" as *u8); gv_num(on)
228 gv_puts(" new=" as *u8); gv_num(nn); gv_puts("\n" as *u8)
229
230 return gv_verdict("nx_dwline_stmt_gate" as *u8, ctr,
231 "same probe through the blessed function-granularity compiler and the statement-granularity one; the old build is the negative control" as *u8)
232}