code wiki / (root) / nx_parse_static_gate.nx

nx_parse_static_gate.nx source

↩ module page · 130 lines · 6541 B

1// nx_parse_static_gate.nx -- THE MISSING PARSER GATE for the static-as-argument defect. 2// 3// WHY THIS EXISTS: nx_cc rejects a STATIC passed directly as a function argument with 4// "nx_parse: argument type mismatch in call to 'F': arg N is a POINTER but the parameter 5// is an INTEGER" 6// even though the static is DECLARED i64 and the parameter IS i64. Located 2026-08-01 in 7// runtime/nx_parse.nx: inject_statics (line ~5187) registers a static as a Local whose `ty` 8// is the SLOT POINTER *(decl_ty), and the scalar-load path (~line 2690) then loads at L.ty, 9// producing a POINTER-typed value. The codebase already compensates for this at three 10// "Static-ptr STRIDE fix" sites (2751/3849/4002) for subscripts and field chains; the 11// call-argument path is the one that was never fixed. 12// 13// COST OF THE DEFECT, MEASURED: it made nx_browser (1,810,737 B) and nishi (1,129,654 B) 14// UNBUILDABLE -- complete working source that nobody could compile -- plus nx_js_conformance, 15// nx_js_es_census, nx_bugforge and latent instances reachable only by closure reordering 16// (nx_trimesh.tm_noise3 surfaced in a file nobody edited). Workarounds cost 24 hand casts. 17// 18// ⚠ THIS GATE IS EXPECTED TO BE **RED** UNTIL THE COMPILER IS FIXED. That is its job: T3 is 19// the defect, and it flips GREEN exactly when the fix lands. A gate that only ever passes 20// cannot tell you a fix worked. 21// 22// ⚠⚠ WARNING TO WHOEVER FIXES IT -- the obvious one-line fix is UNSAFE and I proved it before 23// shipping (debt 1785610588). Detecting a static by "ty_kind says scalar but ty.kind is 24// TY_PTR" MISFIRES ON EVERY POINTER LOCAL: nx_parse.nx:3427 registers an ordinary 25// `let p: *u8 = ...` as add_local(P, name, v, 0, TY_I64, let_ty) -- the IDENTICAL shape. 26// That change would silently unwrap every pointer load in the language. The defect fails 27// LOUDLY at parse; that fix would miscompile SILENTLY. Statics differ only in that value_id 28// is an IR global rather than an alloca, which the Local struct does not expose -- so a 29// correct fix needs a new is_static field or an IR is-global query. 30// 31// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 only when the fix lands. 32import "nx_syscalls.nx" 33import "nx_gate_verdict.nx" 34 35const PG_BUF: i64 = 65536 36const PG_MODE: i64 = 420 37 38// Write a source file, compile it with the LIVE sovereign compiler, return the child exit code. 39// 0 = compiled clean. Non-zero = nx_cc rejected it. 40static pg_argv: i64 = 0 41 42func pg_write(path: *u8, body: *u8) -> i64 { 43 let fd: i64 = sys_openat_wr(path, PG_MODE) 44 if fd < 0 { return 0 - 1 } 45 var n: i64 = 0 46 while body[n] != (0 as u8) { n = n + 1 } 47 sys_write(fd, body, n) 48 sys_close(fd) 49 return 0 50} 51 52// fork + exec the compiler on `src`, discarding its output; return WEXITSTATUS. 53func pg_compile() -> i64 { 54 let av: *i64 = pg_argv as *i64 55 let pid: i64 = sys_fork() 56 if pid < 0 { return 127 } 57 if pid == 0 { 58 let dn: i64 = sys_openat_wr("/dev/null" as *u8, PG_MODE) 59 if dn >= 0 { sys_dup3(dn, 1, 0) sys_dup3(dn, 2, 0) } 60 sys_execve("_offc/nx_cc_sovereign.elf" as *u8, av, 0 as *i64) 61 sys_exit(127) 62 } 63 let st: *i64 = sys_mmap(16) as *i64 64 sys_wait4(pid, st, 0) 65 let sig: i64 = st[0] & 0x7f 66 if sig != 0 { return 128 + sig } 67 return (st[0] >> 8) & 0xff 68} 69 70func pg_case(name: *u8, src: *u8, body: *u8, want_ok: i64, ctr: *i64) -> i64 { 71 pg_write(src, body) 72 let av: *i64 = pg_argv as *i64 73 av[0] = "_offc/nx_cc_sovereign.elf" as *u8 as i64 74 av[1] = src as i64 75 av[2] = 0 76 let rc: i64 = pg_compile() 77 var ok: i64 = 0 78 if want_ok == 1 { if rc == 0 { ok = 1 } } 79 if want_ok == 0 { if rc != 0 { ok = 1 } } 80 gv_check(name, ok, ctr) 81 return ok 82} 83 84func main(argc: i64, argv: *i64) -> i64 { 85 gv_head("nx_parse static-as-argument -- the call-arg path must load a static at its DECLARED type") 86 let ctr: *i64 = gv_ctr() 87 pg_argv = sys_mmap(64) as i64 88 89 // T1 CONTROL: a plain scalar LOCAL passed to an i64 parameter must compile. 90 // If this ever fails the gate itself is broken, not the compiler. 91 pg_case("T1 control: scalar LOCAL -> i64 param compiles" as *u8, 92 "/tmp/_pg_t1.nx" as *u8, 93 "func f(a: i64) -> i64 { return a }\nfunc main(argc: i64, argv: *i64) -> i64 { let x: i64 = 7\n return f(x) }\n" as *u8, 94 1, ctr) 95 96 // T2 CONTROL: a POINTER LOCAL passed to a pointer parameter must compile. 97 // This is the case the tempting one-line fix would have BROKEN (see header warning). 98 pg_case("T2 control: POINTER local -> ptr param compiles (the fix must not break this)" as *u8, 99 "/tmp/_pg_t2.nx" as *u8, 100 "func g(p: *u8) -> i64 { return p[0] as i64 }\nfunc main(argc: i64, argv: *i64) -> i64 { let b: *u8 = sys_mmap(8)\n return g(b) }\nimport \"nx_syscalls.nx\"\n" as *u8, 101 1, ctr) 102 103 // T3 THE DEFECT: a STATIC declared i64, passed to an i64 parameter, must compile. 104 // Currently REJECTED as "arg 1 is a POINTER but the parameter is an INTEGER". 105 // This tooth is RED today and flips GREEN when the compiler is fixed. 106 pg_case("T3 DEFECT: STATIC i64 -> i64 param must compile (RED until nx_cc is fixed)" as *u8, 107 "/tmp/_pg_t3.nx" as *u8, 108 "static s: i64\nfunc f(a: i64) -> i64 { return a }\nfunc main(argc: i64, argv: *i64) -> i64 { s = 7\n return f(s) }\n" as *u8, 109 1, ctr) 110 111 // T4 NON-VACUITY: the same static WITH an explicit cast must compile today. 112 // This proves the gate is measuring the missing deref and not something else -- 113 // the cast forces exactly the dereference the call-arg path fails to apply. 114 pg_case("T4 non-vacuity: STATIC with explicit `as i64` compiles TODAY" as *u8, 115 "/tmp/_pg_t4.nx" as *u8, 116 "static s: i64\nfunc f(a: i64) -> i64 { return a }\nfunc main(argc: i64, argv: *i64) -> i64 { s = 7\n return f(s as i64) }\n" as *u8, 117 1, ctr) 118 119 // T5 BITE: genuinely malformed source MUST be rejected. Without this the gate could 120 // pass by accepting everything, which is the failure mode of every green-only gate. 121 pg_case("T5 bite: malformed source is REJECTED (gate can fail)" as *u8, 122 "/tmp/_pg_t5.nx" as *u8, 123 "func f(a: i64) -> i64 { return zzz_undefined_symbol(a) }\n" as *u8, 124 0, ctr) 125 126 let rc: i64 = gv_verdict("PARSE-STATIC-ARG", ctr, 127 "a static passed directly as an argument must load at its DECLARED type, not its slot-pointer type") 128 sys_exit(rc) 129 return rc 130}