code wiki / _hdl_build / nx_eqsat_dsl_parity_test.nx

nx_eqsat_dsl_parity_test.nx source

↩ module page · 328 lines · 17810 B

1// nx_eqsat_dsl_parity_test.nx -- the 24th GATE: proves the egg-style DATA-driven 2// rule DSL + general e-matcher (nx_eqsat_apply_dsl_table) is BEHAVIORALLY-IDENTICAL 3// to the 7 hand-coded inline rules, AND proves ONE genuinely-new sound rule 4// ((xor x x)==0) is admitted-only-after-proof, applied via the DSL, and CERTIFIES 5// through membership-as-proof -- with a load-bearing NEGATIVE control showing an 6// UNPROVEN/un-admitted rule is REFUSED. 7// 8// WHY behavioral-parity (not byte-identity) for the DSL path: the inline saturate 9// runs verbatim whenever g.dsl==null (byte-identity by construction, proven by the 10// four frozen organs staying 1:1). This gate proves the OPT-IN DSL path produces 11// the SAME observable result -- same canonical partition, same extracted op+cost, 12// same provenance multiset, same saturate return code -- over multiple graphs, so 13// the data table genuinely DRIVES saturation (it is not a no-op shim). 14// 15// STRUCTURE (each step self-asserts; mismatch => sys_exit(nonzero)): 16// STEP 1 PARITY over eqsat_test Case 1 (add (mul x x) 0): inline vs DSL agree on 17// find-partition + recompute_best op/cost + provenance multiset + sat rc. 18// STEP 2 PARITY over eqsat_test Case 2 (mul x 8 -> shl x 3): same checks; this 19// exercises the mul_pow2 side-condition + real-merge counting AS DATA. 20// STEP 3 PARITY over a RULE-DENSE graph touching all 7 rules at once. 21// STEP 4 NEW RULE soundness: run the W=8 masked exhaustive two-witness battery 22// for xor_self; assert passed==total==256 (this JUSTIFIES the allow-list 23// arm). Without this passing, mp_is_sound_rule(9) must not be trusted. 24// STEP 5 NEW RULE applied via DSL + CERTIFIED: build (xor v v), enable prov+DSL 25// with the 8-row table (7 builtins + xor_self), saturate; the e-matcher 26// merges the xor-class with const-0 citing id 9; mp_certify_live==1. 27// STEP 6 NEGATIVE CONTROL (load-bearing): a graph whose only merge cites an 28// UN-admitted rule id must REFUSE. We drive the certifier's log scan with 29// a hand-forged log containing an un-admitted id (200) and assert 30// mp_log_all_sound==0 -> certificate refused; AND assert the engine's 31// xor_self merge, if its id were NOT in the allow-list, would refute 32// (mp_is_sound_rule on a bogus id == 0). Proves the gate is load-bearing. 33// 34// KNOWN ANSWER (FAIL LOUD), one line: 35// "<p1> <p2> <p3> <xor_passed> <xor_total> <xor_cert> <neg_refused>" 36// = "1 1 1 256 256 1 1 " exit 0 iff every assertion holds. 37// 38// SOVEREIGN: no SMT, no .sh; runs on the pinned NishiLang compiler. license_tier: ORIGINAL 39 40import "nx_gate_verdict.nx" 41import "nx_eqsat_membership_proof.nx" 42 43func _emit_num(v: i64) -> i64 { 44 let b: *u8 = sys_mmap(28); var n: i64 = v; if n < 0 { n = 0 - n } 45 let t2: *u8 = sys_mmap(28); var t: i64 = 0 46 if n == 0 { t2[0] = 48; t = 1 } 47 while n > 0 { t2[t] = 48 + (n % 10); n = n / 10; t = t + 1 } 48 var i: i64 = 0; while i < t { b[i] = t2[t - 1 - i]; i = i + 1 } 49 b[t] = 32; sys_write(1, b, t + 1); return 0 50} 51func _nl() -> i64 { let z: *u8 = sys_mmap(2); z[0] = 10; sys_write(1, z, 1); return 0 } 52 53// Per-rule-id histogram of a provenance log (ids 0..MP_RULE_N-1). Used to compare 54// the inline vs DSL logged-rule-id MULTISET exactly (order-independent equality). 55func _log_hist(g: *NxEGraph, hist: *i64) -> i64 { 56 var i: i64 = 0 57 while i < NX_EQSAT_RULE_N { hist[i] = 0; i = i + 1 } 58 var j: i64 = 0 59 while j < g.n_prov { 60 let id: i64 = g.prov[j] 61 if id >= 0 { if id < NX_EQSAT_RULE_N { hist[id] = hist[id] + 1 } } 62 j = j + 1 63 } 64 return g.n_prov 65} 66 67func _hist_eq(a: *i64, b: *i64) -> i64 { 68 var i: i64 = 0 69 while i < NX_EQSAT_RULE_N { 70 if a[i] != b[i] { return 0 } 71 i = i + 1 72 } 73 return 1 74} 75 76// Fresh e-graph factory (caller owns scratch via mmap). Returns the graph ptr. 77func _mk_graph(cap_nodes: i64, cap_cls: i64) -> *NxEGraph { 78 let nodes: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode 79 let classes: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass 80 let g: *NxEGraph = sys_mmap(NX_EQSAT_GRAPH_BYTES) as *NxEGraph 81 nx_eqsat_init(g, nodes, cap_nodes, classes, cap_cls) 82 return g 83} 84 85// Build the 7-row builtin table + (optionally) append xor_self as an 8th row. 86func _mk_table(with_xor: i64) -> *NxDslRule { 87 let table: *NxDslRule = sys_mmap(16 * 96) as *NxDslRule // 16 rows of >=88B 88 let n: i64 = nx_eqsat_builtin_dsl_table(table) 89 if with_xor == 1 { 90 // ONE new data row: (xor x x)==0. zero new matcher control flow. 91 // CNT_REAL_MERGE so saturation converges (the union is idempotent). 92 nx_eqsat_dsl_set_row(table, n, NX_EQSAT_RULE_XOR_SELF, NX_EQ_OP_XOR, 93 DSL_VAR, 0, DSL_SAME_AS_A, 0, 94 RHS_CONST, 0, SC_NONE, CNT_REAL_MERGE, 0) 95 } 96 return table 97} 98 99// Run ONE parity comparison: build the SAME graph twice (inline path then DSL 100// path), saturate both with provenance ON, and assert identical observable result. 101// graph_kind selects which input to build. Returns 1 iff every facet agrees. 102func _parity(graph_kind: i64) -> i64 { 103 let cap_nodes: i64 = 256 104 let cap_cls: i64 = 256 105 106 // ---------- INLINE path ---------- 107 let gi: *NxEGraph = _mk_graph(cap_nodes, cap_cls) 108 let plogi: *i64 = sys_mmap(256 * 8) as *i64 109 nx_eqsat_enable_prov(gi, plogi, 256) 110 // roots of interest (filled per graph kind) 111 var r0i: i64 = 0 - 1 112 var r1i: i64 = 0 - 1 113 if graph_kind == 1 { 114 // Case 1: (add (mul x x) 0) 115 let x: i64 = nx_eqsat_add_var(gi, 0) 116 let mxx: i64 = nx_eqsat_add_binary(gi, NX_EQ_OP_MUL, x, x) 117 let z: i64 = nx_eqsat_add_const(gi, 0) 118 r0i = nx_eqsat_add_binary(gi, NX_EQ_OP_ADD, mxx, z) 119 r1i = mxx 120 } 121 if graph_kind == 2 { 122 // Case 2: (mul x 8) 123 let x: i64 = nx_eqsat_add_var(gi, 0) 124 let c8: i64 = nx_eqsat_add_const(gi, 8) 125 r0i = nx_eqsat_add_binary(gi, NX_EQ_OP_MUL, x, c8) 126 r1i = r0i 127 } 128 if graph_kind == 3 { 129 // rule-dense: (add (sub y y) 0), (add w w), (and z z), (or u 0), (mul v 1), 130 // (mul v 16) -- touches add_zero, sub_self, add_self, and_self, or_zero, 131 // mul_one, mul_pow2 in ONE graph. 132 let y: i64 = nx_eqsat_add_var(gi, 1) 133 let suby: i64 = nx_eqsat_add_binary(gi, NX_EQ_OP_SUB, y, y) 134 let z0: i64 = nx_eqsat_add_const(gi, 0) 135 r0i = nx_eqsat_add_binary(gi, NX_EQ_OP_ADD, suby, z0) // add_zero + sub_self 136 let w: i64 = nx_eqsat_add_var(gi, 2) 137 r1i = nx_eqsat_add_binary(gi, NX_EQ_OP_ADD, w, w) // add_self 138 let zz: i64 = nx_eqsat_add_var(gi, 3) 139 nx_eqsat_add_binary(gi, NX_EQ_OP_AND, zz, zz) // and_self 140 let u: i64 = nx_eqsat_add_var(gi, 4) 141 let zc: i64 = nx_eqsat_add_const(gi, 0) 142 nx_eqsat_add_binary(gi, NX_EQ_OP_OR, u, zc) // or_zero 143 let v: i64 = nx_eqsat_add_var(gi, 5) 144 let one: i64 = nx_eqsat_add_const(gi, 1) 145 nx_eqsat_add_binary(gi, NX_EQ_OP_MUL, v, one) // mul_one 146 let c16: i64 = nx_eqsat_add_const(gi, 16) 147 nx_eqsat_add_binary(gi, NX_EQ_OP_MUL, v, c16) // mul_pow2 (k=4) 148 } 149 let sati: i64 = nx_eqsat_saturate(gi, 16) 150 nx_eqsat_recompute_best(gi) 151 let bni0: i64 = nx_eqsat_extract_best_node(gi, r0i) 152 let opi0: i64 = gi.nodes[bni0].op 153 let costi0: i64 = nx_eqsat_best_cost(gi, r0i) 154 let memi: i64 = if nx_eqsat_find(gi, r0i) == nx_eqsat_find(gi, r1i) then 1 else 0 155 let histi: *i64 = sys_mmap(NX_EQSAT_RULE_N * 8) as *i64 156 _log_hist(gi, histi) 157 let nprovi: i64 = gi.n_prov 158 159 // ---------- DSL path (SAME graph, opt-in table) ---------- 160 let gd: *NxEGraph = _mk_graph(cap_nodes, cap_cls) 161 let plogd: *i64 = sys_mmap(256 * 8) as *i64 162 nx_eqsat_enable_prov(gd, plogd, 256) 163 let table: *NxDslRule = _mk_table(0) // 7 builtins only (parity set) 164 nx_eqsat_enable_dsl(gd, table, 7, 16) 165 var r0d: i64 = 0 - 1 166 var r1d: i64 = 0 - 1 167 if graph_kind == 1 { 168 let x: i64 = nx_eqsat_add_var(gd, 0) 169 let mxx: i64 = nx_eqsat_add_binary(gd, NX_EQ_OP_MUL, x, x) 170 let z: i64 = nx_eqsat_add_const(gd, 0) 171 r0d = nx_eqsat_add_binary(gd, NX_EQ_OP_ADD, mxx, z) 172 r1d = mxx 173 } 174 if graph_kind == 2 { 175 let x: i64 = nx_eqsat_add_var(gd, 0) 176 let c8: i64 = nx_eqsat_add_const(gd, 8) 177 r0d = nx_eqsat_add_binary(gd, NX_EQ_OP_MUL, x, c8) 178 r1d = r0d 179 } 180 if graph_kind == 3 { 181 let y: i64 = nx_eqsat_add_var(gd, 1) 182 let suby: i64 = nx_eqsat_add_binary(gd, NX_EQ_OP_SUB, y, y) 183 let z0: i64 = nx_eqsat_add_const(gd, 0) 184 r0d = nx_eqsat_add_binary(gd, NX_EQ_OP_ADD, suby, z0) 185 let w: i64 = nx_eqsat_add_var(gd, 2) 186 r1d = nx_eqsat_add_binary(gd, NX_EQ_OP_ADD, w, w) 187 let zz: i64 = nx_eqsat_add_var(gd, 3) 188 nx_eqsat_add_binary(gd, NX_EQ_OP_AND, zz, zz) 189 let u: i64 = nx_eqsat_add_var(gd, 4) 190 let zc: i64 = nx_eqsat_add_const(gd, 0) 191 nx_eqsat_add_binary(gd, NX_EQ_OP_OR, u, zc) 192 let v: i64 = nx_eqsat_add_var(gd, 5) 193 let one: i64 = nx_eqsat_add_const(gd, 1) 194 nx_eqsat_add_binary(gd, NX_EQ_OP_MUL, v, one) 195 let c16: i64 = nx_eqsat_add_const(gd, 16) 196 nx_eqsat_add_binary(gd, NX_EQ_OP_MUL, v, c16) 197 } 198 let satd: i64 = nx_eqsat_saturate(gd, 16) 199 nx_eqsat_recompute_best(gd) 200 let bnd0: i64 = nx_eqsat_extract_best_node(gd, r0d) 201 let opd0: i64 = gd.nodes[bnd0].op 202 let costd0: i64 = nx_eqsat_best_cost(gd, r0d) 203 let memd: i64 = if nx_eqsat_find(gd, r0d) == nx_eqsat_find(gd, r1d) then 1 else 0 204 let histd: *i64 = sys_mmap(NX_EQSAT_RULE_N * 8) as *i64 205 _log_hist(gd, histd) 206 let nprovd: i64 = gd.n_prov 207 208 // ---------- compare every observable facet ---------- 209 if sati != satd { return 0 } // same saturate return code 210 if opi0 != opd0 { return 0 } // same extracted root op 211 if costi0 != costd0 { return 0 } // same extracted cost 212 if memi != memd { return 0 } // same canonical partition (root pair) 213 if nprovi != nprovd { return 0 } // same number of logged merges 214 if _hist_eq(histi, histd) != 1 { return 0 } // same logged-rule-id MULTISET 215 return 1 216} 217 218func main() -> i64 { 219 // MIGRATED OFF A HAND-ROLLED VERDICT 2026-08-14, and NEVER COMPILED BEFORE THAT DAY -- nx_eqsat.nx 220 // itself did not compile, so this proof and its siblings had all sat on disk unbuilt. The teeth were 221 // already among the strongest in the estate; they had no way to report to anything outside the process, 222 // and an exit-code-as-assertion-number protocol stops at the FIRST failure and hides the rest. 223 gv_head("nx_eqsat DSL-parity gate -- the data-driven rule table is behaviourally identical to the hand-written rules" as *u8) 224 let ctr: *i64 = gv_ctr() 225 226 // shared gsim scratch for the soundness battery 227 let vals: *i64 = sys_mmap(64 * 8) as *i64 228 let cells: *NxGsimCell = sys_mmap(64 * 48) as *NxGsimCell 229 let gs: *NxGsim = sys_mmap(64) as *NxGsim 230 231 // STEP 0: lockstep (engine ids == membership ids, incl the new xor_self id 9). 232 let lockstep: i64 = mp_rule_ids_lockstep() 233 234 // ===================== STEP 1-3: behavioral PARITY ======================== 235 let p1: i64 = _parity(1) 236 let p2: i64 = _parity(2) 237 let p3: i64 = _parity(3) 238 239 // ===================== STEP 4: NEW RULE soundness battery ================= 240 // The W=8 masked exhaustive two-witness battery for xor_self MUST be unanimous; 241 // this is what JUSTIFIES admitting MP_RULE_XOR_SELF to the proven-sound set. 242 let t: *NxTriTally = sys_mmap(64) as *NxTriTally 243 nx_tri_tally_init(t) 244 let xor_total: i64 = mp_battery_xor_self(gs, vals, cells, t) 245 let xor_passed: i64 = t.passed 246 247 // ===================== STEP 5: NEW RULE applied + CERTIFIED =============== 248 // Build (xor v v), enable prov + DSL with the 8-row table, run the REAL 249 // saturator. The e-matcher merges the xor-class with const-0 citing id 9. 250 let cap_nodes: i64 = 64 251 let cap_cls: i64 = 64 252 let g5: *NxEGraph = _mk_graph(cap_nodes, cap_cls) 253 let plog5: *i64 = sys_mmap(64 * 8) as *i64 254 if nx_eqsat_enable_prov(g5, plog5, 64) != NX_EQSAT_OK { sys_exit(50); return 50 } 255 let table8: *NxDslRule = _mk_table(1) // 7 builtins + xor_self 256 if nx_eqsat_enable_dsl(g5, table8, 8, 16) != NX_EQSAT_OK { sys_exit(51); return 51 } 257 let v: i64 = nx_eqsat_add_var(g5, 0) 258 let xorvv: i64 = nx_eqsat_add_binary(g5, NX_EQ_OP_XOR, v, v) // (xor v v) 259 let zero5: i64 = nx_eqsat_add_const(g5, 0) // 0 260 // pre-merge: distinct classes 261 // ANTI-VACUITY: if the xor class were ALREADY merged with zero before saturation, the whole of 262 // STEP 5 would prove nothing about the DSL rule. 263 var pre5_distinct: i64 = 1 264 if nx_eqsat_find(g5, xorvv) == nx_eqsat_find(g5, zero5) { pre5_distinct = 0 } 265 let sat5: i64 = nx_eqsat_saturate(g5, 16) 266 var sat5_fixpoint: i64 = 0 267 if sat5 == NX_EQSAT_SATURATED { sat5_fixpoint = 1 } 268 // certify (xor v v) == 0 strictly from the engine's log. 269 let cert5: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate 270 let xor_cert: i64 = mp_certify_live(g5, xorvv, zero5, cert5) 271 // values captured; asserted as teeth at the end so EVERY one is reported in a single run 272 // the logged merge cited id 9 (xor_self), proving the DSL rule fired. 273 var saw_xor: i64 = 0 274 var qi: i64 = 0 275 while qi < g5.n_prov { if g5.prov[qi] == NX_EQSAT_RULE_XOR_SELF { saw_xor = 1 } qi = qi + 1 } 276 // saw_xor asserted as a tooth below 277 278 // ===================== STEP 6: NEGATIVE CONTROL (load-bearing) ============ 279 // (6a) An UN-admitted rule id in the log must REFUSE the certificate. Forge a 280 // log on a fresh graph whose only "merge" cites a bogus id (200) and assert the 281 // certifier's log scan refuses it. This is the same poison path RULE_NONE uses. 282 let g6: *NxEGraph = _mk_graph(cap_nodes, cap_cls) 283 let plog6: *i64 = sys_mmap(64 * 8) as *i64 284 nx_eqsat_enable_prov(g6, plog6, 64) 285 // hand-forge ONE un-admitted citation directly into the live log buffer. 286 plog6[0] = 200 // an id NOT in the proven-sound allow-list 287 g6.n_prov = 1 288 let neg_scan: i64 = mp_log_all_sound(g6) // must be 0 (refused) 289 // (6b) prove the allow-list arm is load-bearing: mp_is_sound_rule on a bogus id 290 // is 0, so had xor_self's id NOT been admitted (battery never run), certify would 291 // refute -- the membership check is a real consequence of admission, not free. 292 let bogus_sound: i64 = mp_is_sound_rule(200) // must be 0 293 let xor_admitted: i64 = mp_is_sound_rule(NX_EQSAT_RULE_XOR_SELF) // must be 1 (after battery) 294 var neg_refused: i64 = 0 295 if neg_scan == 0 { if bogus_sound == 0 { if xor_admitted == 1 { neg_refused = 1 } } } 296 297 // ===================== known answer + FAIL-LOUD ========================== 298 _emit_num(p1); _emit_num(p2); _emit_num(p3); 299 _emit_num(xor_passed); _emit_num(xor_total); _emit_num(xor_cert); _emit_num(neg_refused); _nl() 300 301 gv_check("engine rule ids and membership-prover ids are in lockstep" as *u8, lockstep == 1, ctr) 302 // THE PARITY RESULT: the data-driven table and the hand-written inline rules produce the SAME 303 // merge history, across three graph shapes. This is what lets rules become DATA without becoming 304 // a second, differently-behaved engine. 305 gv_check("DSL and inline rules agree on graph shape 1" as *u8, p1 == 1, ctr) 306 gv_check("DSL and inline rules agree on graph shape 2" as *u8, p2 == 1, ctr) 307 gv_check("DSL and inline rules agree on the rule-dense shape" as *u8, p3 == 1, ctr) 308 // ADMISSION BY PROOF: the new rule is allowed into the sound set only because an exhaustive 309 // two-witness battery was unanimous over the whole masked domain. 310 gv_check("the xor-self battery swept the full 2^W vector domain" as *u8, xor_total == 256, ctr) 311 gv_check("every vector in the battery passed on BOTH witnesses" as *u8, xor_passed == xor_total, ctr) 312 // THE NEW RULE, END TO END: distinct before, merged after, cited, certified. 313 gv_check("pre-state: the xor class and zero start in DIFFERENT classes" as *u8, pre5_distinct == 1, ctr) 314 gv_check("saturation reached a fixpoint" as *u8, sat5_fixpoint == 1, ctr) 315 gv_check("the xor class really was merged with zero" as *u8, cert5.member == 1, ctr) 316 gv_check("every id in the log is sound, including the newly admitted one" as *u8, cert5.sound == 1, ctr) 317 gv_check("the equivalence CERTIFIES from the log alone" as *u8, xor_cert == 1, ctr) 318 gv_check("a real merge was actually logged" as *u8, g5.n_prov >= 1, ctr) 319 gv_check("the provenance log is complete, not overflowed" as *u8, g5.prov_overflow == 0, ctr) 320 gv_check("the logged merge cites the DSL rule, proving the data-driven path fired" as *u8, saw_xor == 1, ctr) 321 // THE LOAD-BEARING CONTROL. Without it every tooth above would pass for a certifier that 322 // approved anything: a hand-forged citation of an UN-ADMITTED rule id must be refused, the bogus 323 // id must not be in the allow-list, and the new rule must be there only because its battery ran. 324 gv_check("neg-control-a-forged-unadmitted-citation-is-REFUSED-by-the-certifier" as *u8, neg_refused == 1, ctr) 325 326 return gv_verdict("nx_eqsat_dsl_parity_test" as *u8, ctr, 327 "data-driven rule table proven behaviourally identical to the hand-written rules on three shapes, plus one genuinely new rule admitted only after an exhaustive battery, applied through the DSL, cited and certified, against a forged-citation control" as *u8) 328}