code wiki / (root) / nx_linecont_gate.nx

nx_linecont_gate.nx source

↩ module page · 231 lines · 12243 B

1// nx_linecont_gate.nx -- LN15 FREE MULTI-LINE EXPRESSIONS: does a continuation line 2// opening with a binary operator JOIN the expression above it? 3// 4// WHAT THIS MEASURES THAT THE EQUIVALENCE NET CANNOT. nx_cc_equiv_gate proves a 5// candidate compiler BROKE NOTHING (10 rows byte-identical + self-host fixpoint). 6// It is structurally incapable of proving a new capability WORKS: none of its ten 7// corpus rows contains a line continuation, so it would report GREEN for a compiler 8// in which LN15 was never implemented. A green from a subject that could never go 9// red is decoration. This gate is the half that can go red. 10// 11// HOW IT IS BITE-PROVEN, AND WHY THAT IS STRONGER THAN A SOURCE MUTANT. The gate 12// takes the compiler to test as argv[1]. Run it against the LN15 challenger and the 13// join teeth must PASS; run the SAME binary against the pre-LN15 live compiler and 14// those same teeth must FAIL. Two real compilers, not a synthesised mutant. 15// 16// NO SILENT DEFAULT COMPILER. nx_cc_equiv_gate learned this the expensive way on 17// 2026-08-14: with no argument it fell back to a default path, found a STALE binary 18// left by an earlier run, and returned a fully-formed verdict about the wrong 19// artifact. A gate whose instrument is missing must REFUSE, not guess. 20 21import "nx_syscalls.nx" 22import "nx_gate_verdict.nx" 23import "nx_ccbuild_lib.nx" 24 25const LC_EXIT_USAGE: i64 = 2 26const LC_EXIT_NOTREE: i64 = 4 27 28func lc_puts(s: *u8) -> i64 { 29 var n: i64 = 0 30 while s[n] != (0 as u8) { n = n + 1 } 31 sys_write(1, s, n) 32 return 0 33} 34 35func lc_putn(v: i64) -> i64 { 36 if v == 0 { lc_puts("0\x00" as *u8); return 0 } 37 var x: i64 = v 38 if x < 0 { lc_puts("-\x00" as *u8); x = 0 - x } 39 let t: *u8 = sys_mmap(32) 40 var n: i64 = 0 41 while x > 0 { t[n] = (48 + (x % 10)) as u8; n = n + 1; x = x / 10 } 42 let o: *u8 = sys_mmap(32) 43 var i: i64 = n 44 var j: i64 = 0 45 while i > 0 { i = i - 1; o[j] = t[i]; j = j + 1 } 46 o[j] = 0 as u8 47 lc_puts(o) 48 return 0 49} 50 51func lc_cat(dst: *u8, off: i64, s: *u8) -> i64 { 52 var o: i64 = off 53 var i: i64 = 0 54 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 } 55 return o 56} 57 58func lc_catn(dst: *u8, off: i64, v: i64) -> i64 { 59 if v == 0 { dst[off] = 48 as u8; return off + 1 } 60 let t: *u8 = sys_mmap(32) 61 var n: i64 = 0 62 var x: i64 = v 63 while x > 0 { t[n] = (48 + (x % 10)) as u8; n = n + 1; x = x / 10 } 64 var o: i64 = off 65 var i: i64 = n 66 while i > 0 { i = i - 1; dst[o] = t[i]; o = o + 1 } 67 return o 68} 69 70// SCRATCH LIVES IN /tmp/<gate>/, NEVER BESIDE A PRODUCTION BEAT. A gate that shares 71// its fixture with a sweeper measures the FIXTURE, not the code -- measured in this 72// estate as a gate reporting a real-looking RED that tracked a folded segment store. 73// Created at SETUP, because a teardown does not run when a run crashes. 74func lc_path(pre: *u8, idx: i64, suf: *u8) -> *u8 { 75 let b: *u8 = sys_mmap(256) 76 var o: i64 = 0 77 o = lc_cat(b, o, pre) 78 o = lc_catn(b, o, idx) 79 o = lc_cat(b, o, suf) 80 b[o] = 0 as u8 81 return b 82} 83 84// SCRATCH SPLITS BY FILESYSTEM, NOT BY TIDINESS. NAS /tmp is mounted NOEXEC, so an 85// ELF written there builds clean and then dies exec-127 on every run -- which reads 86// exactly like a miscompiling compiler, and cost this gate one full RED run in which 87// even the positive control failed. That is what the positive control is FOR: four 88// of these teeth pass on a REFUSAL, so without an ordinary program that must simply 89// work, an environment fault would have been read as a verdict about LN15. 90// nx_cc_equiv_gate already routes every .elf to _build/ for exactly this reason and 91// keeps .s/.out/.log in /tmp; this mirrors it rather than inventing a second policy. 92// The nxasm intermediate must share a filesystem with its final name too, or the 93// rename fails EXDEV. Sources and logs stay under /tmp/<gate>/ per the fixture law. 94func lc_tmp(idx: i64, suf: *u8) -> *u8 { 95 return lc_path("/tmp/nx_linecont_gate/c\x00" as *u8, idx, suf) 96} 97 98func lc_bld(idx: i64, suf: *u8) -> *u8 { 99 return lc_path("_build/nx_linecont_gate_c\x00" as *u8, idx, suf) 100} 101 102func lc_write_src(path: *u8, s: *u8) -> i64 { 103 let fd: i64 = sys_openat_wr(path, 0x1a4) 104 if fd < 0 { return 0 - 1 } 105 var n: i64 = 0 106 while s[n] != (0 as u8) { n = n + 1 } 107 sys_write(fd, s, n) 108 sys_close(fd) 109 return n 110} 111 112// Compile one fixture with `cc` and run it. out[0]=build rc, out[1]=raw run status. 113// The fixtures import NOTHING on purpose: they are written into /tmp, where the 114// import resolver cannot reach runtime/, so any import would fail for a reason that 115// has nothing to do with what is being measured. 116func lc_case(cc: *u8, idx: i64, src: *u8, envp: *i64, devnull: i64, out: *i64) -> i64 { 117 let p_nx: *u8 = lc_tmp(idx, ".nx\x00" as *u8) 118 let p_s: *u8 = lc_tmp(idx, ".s\x00" as *u8) 119 let p_e: *u8 = lc_bld(idx, ".elf\x00" as *u8) 120 let p_t: *u8 = lc_bld(idx, ".tmpelf\x00" as *u8) 121 let p_al: *u8 = lc_tmp(idx, ".asmlog\x00" as *u8) 122 let p_o: *u8 = lc_tmp(idx, ".out\x00" as *u8) 123 lc_write_src(p_nx, src) 124 let brc: i64 = cb_build(cc, p_nx, p_s, p_e, envp, devnull, p_t, p_al) 125 out[0] = brc 126 out[1] = 0 - 1 127 if brc == 0 { out[1] = cb_run_capture(p_e, p_o, envp, devnull) } 128 lc_puts("LINECONT case=\x00" as *u8); lc_putn(idx) 129 lc_puts(" build=\x00" as *u8); lc_putn(out[0]) 130 lc_puts(" run=\x00" as *u8); lc_putn(out[1]) 131 lc_puts(" elfbytes=\x00" as *u8); lc_putn(cb_fsize(p_e)) 132 lc_puts("\n\x00" as *u8) 133 return 0 134} 135 136func main(argc: i64, argv: *i64) -> i64 { 137 sys_ignore_sigpipe() 138 if argc < 2 { 139 lc_puts("LINECONT verdict=NO-COMPILER-GIVEN\n\x00" as *u8) 140 lc_puts(" this gate measures a COMPILER and cannot guess which one you mean.\n\x00" as *u8) 141 lc_puts(" usage: nx_linecont_gate <path-to-cc> (challenger: ../nx_compile_x86.sov.elf.new)\n\x00" as *u8) 142 lc_puts(" baseline for the bite: _offc/nx_cc_sovereign.elf (the live pre-LN15 compiler)\n\x00" as *u8) 143 sys_exit(LC_EXIT_USAGE) 144 } 145 if cb_anchor_root() == 0 { 146 lc_puts("LINECONT verdict=NO-TREE (buildroot/runtime/nx_compile_x86.nx not found from this cwd)\n\x00" as *u8) 147 sys_exit(LC_EXIT_NOTREE) 148 } 149 let cc: *u8 = argv[1] as *u8 150 151 sys_mkdir("/tmp/nx_linecont_gate\x00" as *u8, 0x1ed) 152 let envp: *i64 = sys_mmap(8 * 2) as *i64 153 envp[0] = "PATH=/usr/bin:/bin\x00" as *u8 as i64 154 envp[1] = 0 155 let devnull: i64 = sys_openat_wr("/dev/null\x00" as *u8, 0x1a4) 156 let r: *i64 = sys_mmap(8 * 2) as *i64 157 158 lc_puts("LINECONT cc=\x00" as *u8); lc_puts(cc) 159 lc_puts(" ccbytes=\x00" as *u8); lc_putn(cb_fsize(cc)) 160 lc_puts("\n\x00" as *u8) 161 162 let ctr: *i64 = gv_ctr() 163 164 // ---- JOIN TEETH: the capability LN15 adds ------------------------------- 165 // C0 is the det2x2 fixture itself: the shape that forced the runtime-wide 166 // sweep on 2026-07-09 by silently keeping only the first term. 167 lc_case(cc, 0, "func main(argc: i64, argv: *i64) -> i64 {\n let a: i64 = 7\n let b: i64 = 3\n let d: i64 = a\n - b\n if d == 4 { return 0 }\n return 1\n}\n\x00" as *u8, envp, devnull, r) 168 let b0: i64 = r[0] 169 gv_check("join-minus-det2x2-fixture-now-computes\x00" as *u8, ((r[0] == 0) & (r[1] == 0)) as i64, ctr) 170 171 lc_case(cc, 1, "func main(argc: i64, argv: *i64) -> i64 {\n let a: i64 = 7\n let b: i64 = 3\n let d: i64 = a\n + b\n if d == 10 { return 0 }\n return 1\n}\n\x00" as *u8, envp, devnull, r) 172 let b1: i64 = r[0] 173 gv_check("join-plus-continuation\x00" as *u8, ((r[0] == 0) & (r[1] == 0)) as i64, ctr) 174 175 lc_case(cc, 2, "func main(argc: i64, argv: *i64) -> i64 {\n let a: i64 = 12\n let b: i64 = 10\n let d: i64 = a\n & b\n if d == 8 { return 0 }\n return 1\n}\n\x00" as *u8, envp, devnull, r) 176 let b2: i64 = r[0] 177 gv_check("join-amp-bitwise-continuation\x00" as *u8, ((r[0] == 0) & (r[1] == 0)) as i64, ctr) 178 179 lc_case(cc, 3, "func main(argc: i64, argv: *i64) -> i64 {\n let a: i64 = 7\n let b: i64 = 3\n let d: i64 = a\n * b\n if d == 21 { return 0 }\n return 1\n}\n\x00" as *u8, envp, devnull, r) 180 let b3: i64 = r[0] 181 gv_check("join-star-multiply-when-no-store-follows\x00" as *u8, ((r[0] == 0) & (r[1] == 0)) as i64, ctr) 182 183 // ---- NEG CONTROLS: what must NOT change --------------------------------- 184 // The `*` disambiguation is the whole risk of LN15. If sb_star_starts_store 185 // ever answers JOIN here, `let q: i64 = 5` absorbs `* p`, the store never 186 // happens, and v silently keeps 1 -- a wrong value, which is the class this 187 // work exists to remove. This tooth is the one that would catch that. 188 lc_case(cc, 4, "func main(argc: i64, argv: *i64) -> i64 {\n var v: i64 = 1\n let p: *i64 = &v\n let q: i64 = 5\n *p = 100\n if v == 100 { if q == 5 { return 0 } }\n return 1\n}\n\x00" as *u8, envp, devnull, r) 189 let b4: i64 = r[0] 190 gv_check("neg-control-star-store-still-stores-not-absorbed\x00" as *u8, ((r[0] == 0) & (r[1] == 0)) as i64, ctr) 191 192 // A leading operator with NOTHING above it to join is not a continuation -- 193 // it is dead code, and it must still be REFUSED by name rather than silently 194 // computed and discarded. Build must FAIL. 195 lc_case(cc, 5, "func main(argc: i64, argv: *i64) -> i64 {\n let a: i64 = 7\n if a == 7 {\n - a\n }\n return 0\n}\n\x00" as *u8, envp, devnull, r) 196 gv_check("neg-control-lone-minus-nothing-to-join-REFUSED\x00" as *u8, (r[0] != 0) as i64, ctr) 197 198 // `~` is unary-ONLY: there is no binary form, so a line opening with it can 199 // never have been a continuation and must stay a boundary (hence refused as a 200 // discarded pure expression). This is the over-join control. 201 lc_case(cc, 6, "func main(argc: i64, argv: *i64) -> i64 {\n let a: i64 = 7\n if a == 7 {\n ~a\n }\n return 0\n}\n\x00" as *u8, envp, devnull, r) 202 gv_check("neg-control-tilde-unary-only-stays-boundary-REFUSED\x00" as *u8, (r[0] != 0) as i64, ctr) 203 204 // ---- REGRESSION: the 2026-05-16 always-binary class --------------------- 205 // Those fifteen kinds moved out of at_stmt_boundary into the shared ruler. 206 // If the extraction dropped one, this is what notices. Shape taken from the 207 // pack4_i16 body that motivated the original relaxation. 208 lc_case(cc, 7, "func main(argc: i64, argv: *i64) -> i64 {\n let a: i64 = 1\n let b: i64 = 2\n let d: i64 = (a & 255)\n | ((b & 255) << 16)\n if d == 131073 { return 0 }\n return 1\n}\n\x00" as *u8, envp, devnull, r) 209 let b7: i64 = r[0] 210 gv_check("regression-pipe-continuation-still-joins\x00" as *u8, ((r[0] == 0) & (r[1] == 0)) as i64, ctr) 211 212 // ---- POSITIVE CONTROL --------------------------------------------------- 213 // A GUARD THAT REFUSES EVERYTHING PASSES EVERY NEGATIVE TEST. Four of the 214 // teeth above are satisfied by a REFUSAL, so without this one a totally 215 // broken compiler that rejects all input would score well here. 216 lc_case(cc, 8, "func main(argc: i64, argv: *i64) -> i64 {\n let a: i64 = 7\n let b: i64 = 3\n let d: i64 = a - b\n if d == 4 { return 0 }\n return 1\n}\n\x00" as *u8, envp, devnull, r) 217 let b8: i64 = r[0] 218 gv_check("positive-control-ordinary-program-builds-and-runs\x00" as *u8, ((r[0] == 0) & (r[1] == 0)) as i64, ctr) 219 220 // ---- NON-VACUITY -------------------------------------------------------- 221 // ASSERT THE FIXTURE REACHED THE CONDITION BEFORE ASSERTING THE OUTCOME. 222 // Every fixture that is SUPPOSED to compile must actually have compiled; if 223 // one silently failed to build, its join tooth failed for a reason that has 224 // nothing to do with line continuation, and a RED for the wrong reason is not 225 // evidence. This separates "wrong answer" from "never ran". 226 gv_check("fixture-reached-condition-all-must-compile-fixtures-built\x00" as *u8, 227 ((b0 == 0) & (b1 == 0) & (b2 == 0) & (b3 == 0) & (b4 == 0) & (b7 == 0) & (b8 == 0)) as i64, ctr) 228 229 return gv_verdict("NX-LINECONT-GATE\x00" as *u8, ctr, 230 "LN15: continuation lines join; the star store and the unary-only prefixes do not\x00" as *u8) 231}