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}