nx_nxasm_neutral_gate.nx source
↩ module page · 165 lines · 8667 B
1// nx_nxasm_neutral_gate.nx -- DID ADDING THE FUNCTION-BOUNDS RECORDER CHANGE ONE EMITTED BYTE?
2//
3// DDR-009 step 1 added a `.type NAME, @function` recorder to nxasm so .debug_info can later carry
4// DW_AT_low_pc/high_pc. It RECORDS ONLY and emits nothing, so every ELF nxasm produces must be
5// BYTE-IDENTICAL to before. DDR-002 section 5 is explicit that nxasm is the crown jewel below the
6// compiler and that changes to it land in steps which each carry their own proof -- the compiler's
7// equivalence gate cannot speak here, because its subject is the COMPILER.
8//
9// Old = _offc/nxasm_x86_main.elf (the blessed 144,534-byte build, no recorder)
10// New = ../nxasm_x86_main.elf (148,308 bytes, recorder present)
11// Same .s in, byte-compare the ELFs out.
12//
13// ★ THE LOAD-BEARING TOOTH IS NOT "IDENTICAL" -- IT IS "THE RECORDER ACTUALLY RAN". A .s with no
14// .type directives would produce identical output from both assemblers while proving NOTHING,
15// which is the vacuous pass this estate keeps paying for. So the fixture is asserted to CONTAIN
16// .type directives before the comparison is believed.
17// license_tier: ORIGINAL expect_exit: 0
18import "syscalls.nx"
19import "nx_gate_verdict.nx"
20
21const NN_CAP: i64 = 4194304
22
23func nn_run_out(elf: *u8, args: *i64, nargs: i64, outfile: *u8) -> i64 {
24 let pid: i64 = sys_fork()
25 if pid == 0 {
26 let fd: i64 = sys_openat_wr(outfile, 0x1a4)
27 if fd >= 0 { sys_dup3(fd, 1, 0) }
28 let argv: *i64 = sys_mmap(8 * (nargs + 2)) as *i64
29 let envp: *i64 = sys_mmap(16) as *i64
30 envp[0] = 0
31 argv[0] = elf as i64
32 var i: i64 = 0
33 while i < nargs { argv[i+1] = args[i]; i = i + 1 }
34 argv[nargs+1] = 0
35 sys_execve(elf, argv, envp)
36 sys_exit(127)
37 }
38 let st: *i64 = sys_mmap(16) as *i64
39 sys_wait4(pid, st, 0)
40 if (st[0] % 128) != 0 { return 0 - 1 }
41 return (st[0] >> 8) & 0xff
42}
43func nn_read(path: *u8, buf: *u8, cap: i64) -> i64 {
44 let fd: i64 = sys_openat_rd(path)
45 if fd < 0 { return 0 - 1 }
46 var tot: i64 = 0
47 while tot < cap {
48 let n: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot)
49 if n <= 0 { break }
50 tot = tot + n
51 }
52 sys_close(fd)
53 return tot
54}
55func nn_has(b: *u8, n: i64, pat: *u8) -> i64 {
56 var m: i64 = 0
57 while pat[m] != (0 as u8) { m = m + 1 }
58 var i: i64 = 0
59 while i + m <= n {
60 var j: i64 = 0
61 var ok: i64 = 1
62 while j < m { if b[i+j] != pat[j] { ok = 0; j = m } else { j = j + 1 } }
63 if ok == 1 { return 1 }
64 i = i + 1
65 }
66 return 0
67}
68
69func main() -> i64 {
70 let ctr: *i64 = gv_ctr()
71 gv_head("=== NX-NXASM-NEUTRAL -- the .type recorder must not change one emitted byte ===" as *u8)
72 sys_chdir("buildroot" as *u8)
73
74 let args: *i64 = sys_mmap(64) as *i64
75
76 // ---- HALF 1: THE INVARIANT THAT MUST NEVER BREAK -------------------------------------------
77 // RETARGETED 2026-08-07 when DDR-009 step 2 landed. Step 1 only RECORDED, so every ELF stayed
78 // byte-identical. Step 2 EMITS .debug_info, so a -g build legitimately differs -- and the
79 // temptation is to loosen this gate. That would throw away the guarantee it exists for.
80 // THE REAL INVARIANT WAS NEVER "all output is identical", IT WAS "a NON-DEBUG build is
81 // identical" -- which is what protects the MEASURED 168-byte minimal static binary published on
82 // /compare/lang. So the fixture drops -g and the assertion stays exact.
83 // ★ WHEN A GATE GOES RED BECAUSE THE CONTRACT CHANGED, RE-AIM IT AT THE INVARIANT -- NEVER
84 // RELAX IT UNTIL IT PASSES. A gate loosened to accommodate a change stops defending anything.
85 args[0] = "runtime/nx_probe_dbgtiny.nx" as i64
86 let rcp: i64 = nn_run_out("_offc/nx_cc_sovereign.elf" as *u8, args, 1, "/tmp/nn_nog.s" as *u8)
87 gv_check("compiled-the-no-g-fixture" as *u8, (rcp == 0) as i64, ctr)
88 let pb: *u8 = sys_mmap(NN_CAP)
89 let pn: i64 = nn_read("/tmp/nn_nog.s" as *u8, pb, NN_CAP)
90 // CORRECTED EXPECTATION. `.type NAME, @function` is emitted ALWAYS -- it is ordinary gas symbol
91 // typing, not debug info. Only `.file`/`.loc` are gated on -g, and `.loc` is what the emission
92 // actually keys on (axc_n_loc > 0). Asserting the absence of @function tested the wrong token
93 // and failed on a correct compiler. THE FIXTURE WAS RIGHT AND MY EXPECTATION WAS WRONG.
94 gv_check("neg-control-no-g-fixture-has-NO-dot-loc" as *u8, (nn_has(pb, pn, ".loc " as *u8) == 0) as i64, ctr)
95
96 args[0] = "/tmp/nn_nog.s" as i64
97 args[1] = "/tmp/nn_nog_old.elf" as i64
98 let ro1: i64 = nn_run_out("_offc/nxasm_x86_main.elf" as *u8, args, 2, "/tmp/nn_o1.log" as *u8)
99 args[1] = "/tmp/nn_nog_new.elf" as i64
100 let ro2: i64 = nn_run_out("../nxasm_x86_main.elf" as *u8, args, 2, "/tmp/nn_o2.log" as *u8)
101 gv_check("no-g-both-assemblers-exit-0" as *u8, ((ro1 == 0) & (ro2 == 0)) as i64, ctr)
102 let p1: *u8 = sys_mmap(NN_CAP)
103 let p2: *u8 = sys_mmap(NN_CAP)
104 let l1: i64 = nn_read("/tmp/nn_nog_old.elf" as *u8, p1, NN_CAP)
105 let l2: i64 = nn_read("/tmp/nn_nog_new.elf" as *u8, p2, NN_CAP)
106 var pdiff: i64 = 0
107 var pi: i64 = 0
108 while pi < l1 { if pi < l2 { if p1[pi] != p2[pi] { pdiff = pdiff + 1 } } pi = pi + 1 }
109 gv_check("NO-G-BUILD-IS-BYTE-IDENTICAL" as *u8, ((l1 > 64) & (l1 == l2) & (pdiff == 0)) as i64, ctr)
110
111 // ---- HALF 2: THE NEW CAPABILITY ------------------------------------------------------------
112 args[0] = "-g" as i64
113 args[1] = "runtime/nx_probe_dbgtiny.nx" as i64
114 let rcc: i64 = nn_run_out("_offc/nx_cc_sovereign.elf" as *u8, args, 2, "/tmp/nn.s" as *u8)
115 gv_check("compiled-the-fixture" as *u8, (rcc == 0) as i64, ctr)
116
117 let sbuf: *u8 = sys_mmap(NN_CAP)
118 let sn: i64 = nn_read("/tmp/nn.s" as *u8, sbuf, NN_CAP)
119 gv_check("fixture-is-real-assembly" as *u8, (sn > 256) as i64, ctr)
120
121 // THE FIXTURE MUST REACH THE CONDITION UNDER TEST. Without .type in the .s the recorder never
122 // executes and "identical output" is a statement about a code path that did not run.
123 gv_check("fixture-CONTAINS-dot-type-directives" as *u8, nn_has(sbuf, sn, ".type " as *u8), ctr)
124 gv_check("fixture-CONTAINS-at-function" as *u8, nn_has(sbuf, sn, "@function" as *u8), ctr)
125
126 args[0] = "/tmp/nn.s" as i64
127 args[1] = "/tmp/nn_old.elf" as i64
128 let rold: i64 = nn_run_out("_offc/nxasm_x86_main.elf" as *u8, args, 2, "/tmp/nn_old.log" as *u8)
129 args[1] = "/tmp/nn_new.elf" as i64
130 let rnew: i64 = nn_run_out("../nxasm_x86_main.elf" as *u8, args, 2, "/tmp/nn_new.log" as *u8)
131 gv_check("old-assembler-exits-0" as *u8, (rold == 0) as i64, ctr)
132 gv_check("new-assembler-exits-0" as *u8, (rnew == 0) as i64, ctr)
133
134 let ob: *u8 = sys_mmap(NN_CAP)
135 let nb: *u8 = sys_mmap(NN_CAP)
136 let on: i64 = nn_read("/tmp/nn_old.elf" as *u8, ob, NN_CAP)
137 let nn2: i64 = nn_read("/tmp/nn_new.elf" as *u8, nb, NN_CAP)
138 gv_check("neg-control-both-elfs-non-empty" as *u8, ((on > 64) & (nn2 > 64)) as i64, ctr)
139 // (the step-1 "same-length" tooth is retired here: with .debug_info emitted, asserting equal
140 // length would assert the feature does NOT work. Its protective half now lives in the no-g
141 // half above, where the invariant actually is byte-identity.)
142
143 var diff: i64 = 0
144 var i: i64 = 0
145 while i < on {
146 if i < nn2 { if ob[i] != nb[i] { diff = diff + 1 } }
147 i = i + 1
148 }
149 // With -g the outputs MUST differ now: that difference IS .debug_info + .debug_abbrev.
150 // Asserting "identical" here would assert the feature does not work.
151 gv_check("g-build-DIFFERS-because-debug_info-was-added" as *u8, ((diff > 0) | (on != nn2)) as i64, ctr)
152 gv_check("g-build-is-LARGER" as *u8, (nn2 > on) as i64, ctr)
153 gv_check("new-elf-declares-debug_info-section" as *u8, nn_has(nb, nn2, ".debug_info" as *u8), ctr)
154 gv_check("new-elf-declares-debug_abbrev-section" as *u8, nn_has(nb, nn2, ".debug_abbrev" as *u8), ctr)
155 gv_check("new-elf-carries-a-subprogram-name" as *u8, nn_has(nb, nn2, "dbgtiny_a" as *u8), ctr)
156 gv_check("neg-control-old-elf-has-NO-debug_info" as *u8, (nn_has(ob, on, ".debug_info" as *u8) == 0) as i64, ctr)
157
158 gv_puts("\n --- measured ---\n" as *u8)
159 gv_puts(" s_bytes=" as *u8); gv_num(sn)
160 gv_puts(" old_elf=" as *u8); gv_num(on)
161 gv_puts(" new_elf=" as *u8); gv_num(nn2)
162 gv_puts(" differing_bytes=" as *u8); gv_num(diff); gv_puts("\n" as *u8)
163
164 return gv_verdict("nx_nxasm_neutral_gate" as *u8, ctr,
165 "blessed nxasm vs the recorder build on the same .s; the fixture is asserted to contain .type so the new path actually executes" as *u8)
166}