code wiki / (root) / nx_nxasm_neutral_gate.nx

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}