code wiki / (root) / nx_dwline_stmt_gate.nx

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}