code wiki / (root) / nx_wat_refuse_gate.nx

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}