nx_wat_refuse_gate.nx source
↩ module page · 163 lines · 9745 B
1// nx_wat_refuse_gate.nx -- THE REFEREE FOR LN13a: THE WAT LANE MUST REFUSE WHAT IT CANNOT LOWER.
2//
3// WHAT THIS PROVES, AND WHY A GREEN HERE IS WORTH SOMETHING. Until 2026-09-04 the wat backend answered an
4// IR opcode it had no arm for with a WAT COMMENT and `return 0`, without storing a result local. The module
5// assembled, instantiated and RAN, silently computing zero at that instruction. Nothing in the build said a
6// word. That is the worst shape a compiler defect can take, and it is invisible to every instrument that
7// reads exit codes or artifact sizes -- the pre-fix compiler exits 0 and emits a perfectly well-formed
8// module. So the only referee that can see it is one that compiles a KNOWN-UNLOWERABLE construct and
9// demands a NAMED refusal.
10//
11// THE SUBJECT IS A BINARY, NOT A SOURCE. argv[1] is the compiler under test, so this gate can be pointed at
12// a staged build before it is promoted (the nx_cc_equiv_gate / nx_langdiag_gate shape). It judges the
13// compiler by the BEHAVIOUR of what it emits, never by inspecting its assembly.
14//
15// THE NEGATIVE CONTROLS ARE THE LOAD-BEARING HALF. A compiler that refused EVERY module would pass every
16// refusal tooth here and be catastrophically wrong; the estate has bite-proved exactly that class on
17// nx_langdiag_gate. So the direct-call control must still compile, still emit a module, and still NOT be
18// refused. Without those three, this gate scores a guard-that-refuses-everything at 100%.
19//
20// FIXTURES ARE ASSEMBLED AT RUNTIME into a per-RUN directory from gk_fixture_dir. Two reasons, both learned
21// the hard way: a gate that shares a production fixture tracks the FIXTURE and not the code, and two
22// concurrent runs of a gate with a hardcoded /tmp path return false SKIPs. The old silent marker is built
23// from two halves at runtime for a third reason -- written whole it would be a source literal, and a
24// detector that scans sources finds its own fixture.
25// license_tier: ORIGINAL. Forks the subject compiler, writes only under its own per-run /tmp dir. No hw
26// writes (Rule 26).
27import "nx_syscalls.nx"
28import "nx_gate_verdict.nx"
29import "nx_gatekit_lib.nx"
30import "nx_tool_run.nx"
31
32// Capture cap: these fixtures emit ~2 KB of wat, so 256 KB is three orders of headroom over the largest
33// output this gate can provoke. It is a capture bound, not a guess about the subject.
34const WR_CAP: i64 = 262144
35const WR_PATH: i64 = 4096
36const WR_SPAN: i64 = 8
37const WR_ARGV_SLOTS: i64 = 4
38const WR_MARKER_CAP: i64 = 64
39// OP_CALL_INDIRECT, from nx_types.nx. Every function-pointer call in the language lowers to it, and the wat
40// backend has no arm for it -- which is precisely why it is the fixture that can prove the refusal fires.
41const WR_OPCODE_CALL_INDIRECT: i64 = 144
42const WR_SUBJECT_DEFAULT: *u8 = "_build/nx_compile_wat.sov.elf"
43const WR_EXIT_REFUSE: i64 = 3
44
45// Run the subject compiler on one fixture. tr_run_capture merges the child's stdout AND stderr into one
46// pipe, so the refusal (stderr) and the emitted wat (stdout) both land here and every tooth must scan the
47// whole buffer rather than its head.
48func wr_run(subject: *u8, src: *u8, buf: *u8, ln: *i64) -> i64 {
49 let av: *i64 = sys_mmap(WR_SPAN * WR_ARGV_SLOTS) as *i64
50 av[0] = subject as i64
51 av[1] = src as i64
52 av[2] = 0
53 let rc: i64 = tr_run_capture(subject, av, buf, WR_CAP - 1, ln)
54 var n: i64 = ln[0]
55 if n < 0 { n = 0 }
56 if n > WR_CAP - 1 { n = WR_CAP - 1 }
57 buf[n] = 0 as u8
58 return rc
59}
60
61func main(argc: i64, argv: **u8) -> i64 {
62 let ctr: *i64 = gv_ctr()
63 gv_head("NX-WAT-REFUSE-GATE" as *u8)
64
65 var subject: *u8 = WR_SUBJECT_DEFAULT
66 if argc > 1 { subject = argv[1] as *u8 }
67
68 // ---- SETUP -------------------------------------------------------------------------------------
69 let dir: *u8 = sys_mmap(WR_PATH)
70 gk_fixture_dir("nx_wat_refuse_gate" as *u8, dir)
71
72 let p_ind: *u8 = sys_mmap(WR_PATH)
73 let p_amp: *u8 = sys_mmap(WR_PATH)
74 let p_dir: *u8 = sys_mmap(WR_PATH)
75 gk_join(p_ind, dir, "indirect_bare.nx" as *u8)
76 gk_join(p_amp, dir, "indirect_addrof.nx" as *u8)
77 gk_join(p_dir, dir, "direct_control.nx" as *u8)
78
79 // The two indirect fixtures differ ONLY in how the function address is taken (bare name vs &name), and
80 // both reach the same OP_CALL_INDIRECT. Carrying both means a fix that handles one spelling and misses
81 // the other cannot score a pass.
82 gk_write(p_ind, "func twice(x: i64) -> i64 { return x + x }\nfunc main(argc: i64, argv: **u8) -> i64 {\n let f: func(i64) -> i64 = twice\n return f(21)\n}\n" as *u8)
83 gk_write(p_amp, "func twice(x: i64) -> i64 { return x + x }\nfunc main(argc: i64, argv: **u8) -> i64 {\n let f: func(i64) -> i64 = &twice\n return f(21)\n}\n" as *u8)
84 gk_write(p_dir, "func twice(x: i64) -> i64 { return x + x }\nfunc main(argc: i64, argv: **u8) -> i64 {\n return twice(21)\n}\n" as *u8)
85
86 // The pre-fix silent marker, assembled at runtime from two halves so a source scan for it does not
87 // convict this gate's own text.
88 let marker: *u8 = sys_mmap(WR_MARKER_CAP)
89 var mo: i64 = gk_cat(marker, 0, ";; TODO op" as *u8)
90 mo = gk_cat(marker, mo, "code" as *u8)
91 marker[mo] = 0 as u8
92
93 let b_ind: *u8 = sys_mmap(WR_CAP)
94 let b_amp: *u8 = sys_mmap(WR_CAP)
95 let b_dir: *u8 = sys_mmap(WR_CAP)
96 let ln: *i64 = sys_mmap(WR_SPAN) as *i64
97
98 // ---- T0: the subject exists. Without this the whole run would fail for a reason that has nothing to
99 // do with the compiler, and report it as if the compiler were at fault.
100 gv_need("subject-compiler-present" as *u8, gk_exists(subject), ctr)
101
102 // ---- T1..T3: FIXTURE REACHED THE CONDITION, asserted FIRST and by measurement, not by assumption.
103 // A fixture that lost its indirect call would be refused by nothing and would score a silent pass on
104 // every refusal tooth below.
105 gv_check("fixture-reached-indirect-bare-declares-a-function-typed-local" as *u8,
106 gk_contains(p_ind, "let f: func(i64) -> i64 = twice" as *u8), ctr)
107 gv_check("fixture-reached-indirect-addrof-declares-a-function-typed-local" as *u8,
108 gk_contains(p_amp, "let f: func(i64) -> i64 = &twice" as *u8), ctr)
109 gv_check("fixture-reached-control-has-NO-function-typed-local" as *u8,
110 (gk_contains(p_dir, "func(i64) -> i64 =" as *u8) == 0) as i64, ctr)
111
112 let rc_ind: i64 = wr_run(subject, p_ind, b_ind, ln)
113 let n_ind: i64 = ln[0]
114 let rc_amp: i64 = wr_run(subject, p_amp, b_amp, ln)
115 let rc_dir: i64 = wr_run(subject, p_dir, b_dir, ln)
116 let n_dir: i64 = ln[0]
117
118 // ---- T4..T7: THE REFUSAL. Exit code AND named cause AND the opcode number, because a non-zero exit
119 // alone would also be produced by a compiler that simply crashed.
120 gv_check_eq("indirect-call-is-refused-not-silently-emitted" as *u8, rc_ind, WR_EXIT_REFUSE, ctr)
121 gv_check_eq("indirect-call-addrof-spelling-is-refused-too" as *u8, rc_amp, WR_EXIT_REFUSE, ctr)
122 gv_check("refusal-names-its-cause-in-words" as *u8,
123 gk_has(b_ind, "cannot lower IR opcode" as *u8), ctr)
124 // The opcode number must appear INSIDE the refusal sentence, not merely somewhere in the buffer.
125 // Bite-measured 2026-09-04: a bare search for "144" PASSED against the pre-fix compiler, because the
126 // silent marker it emits is ";; TODO opcode 144" -- a tooth that passes for the wrong reason on the
127 // very binary it exists to convict. The contiguous phrase is the only form that discriminates.
128 gv_check("refusal-names-the-opcode-number-in-the-refusal-sentence" as *u8,
129 gk_has(b_ind, "cannot lower IR opcode 144" as *u8), ctr)
130
131 // ---- T8/T9: THE SILENT MARKER IS GONE. This is the defect itself, stated as an absence: the old
132 // backend answered the same input with this comment and exit 0.
133 gv_check("neg-control-no-silent-marker-on-the-refused-module" as *u8,
134 (gk_has(b_ind, marker) == 0) as i64, ctr)
135 gv_check("neg-control-refused-module-emits-no-wasm-body" as *u8,
136 (gk_has(b_ind, "(module" as *u8) == 0) as i64, ctr)
137
138 // ---- T10..T13: THE NEGATIVE CONTROLS THAT STOP A REFUSE-EVERYTHING COMPILER SCORING 100%.
139 gv_check_eq("neg-control-ordinary-direct-call-still-compiles" as *u8, rc_dir, 0, ctr)
140 gv_check("neg-control-ordinary-direct-call-still-emits-a-module" as *u8,
141 gk_has(b_dir, "(module" as *u8), ctr)
142 gv_check("neg-control-ordinary-direct-call-is-NOT-refused" as *u8,
143 (gk_has(b_dir, "cannot lower IR opcode" as *u8) == 0) as i64, ctr)
144 gv_check("neg-control-ordinary-direct-call-carries-no-silent-marker-either" as *u8,
145 (gk_has(b_dir, marker) == 0) as i64, ctr)
146
147 // ---- T14: the control genuinely produced output. A zero-byte capture would pass "does not contain"
148 // teeth for free -- the vacuous-on-the-empty-set defect, bound into the condition rather than printed
149 // beside it.
150 gv_check("neg-control-output-is-non-empty-so-the-absence-teeth-are-not-vacuous" as *u8,
151 (n_dir > 0) as i64, ctr)
152
153 gv_values_head()
154 gv_kv("opcode_under_test" as *u8, WR_OPCODE_CALL_INDIRECT)
155 gv_kv("refused_exit_code" as *u8, rc_ind)
156 gv_kv("refused_addrof_exit_code" as *u8, rc_amp)
157 gv_kv("control_exit_code" as *u8, rc_dir)
158 gv_kv("refused_capture_bytes" as *u8, n_ind)
159 gv_kv("control_capture_bytes" as *u8, n_dir)
160
161 return gv_verdict("WAT-REFUSE-GATE" as *u8, ctr,
162 "a function-pointer call, which lowers to OP_CALL_INDIRECT and has no arm in the wat backend, is refused by name with its opcode number and emits no module; an ordinary direct call still compiles and is not refused, so the refusal discriminates rather than blocking everything" as *u8)
163}