code wiki / _hdl_build / nx_rule_soundness_test.nx

nx_rule_soundness_test.nx source

↩ module page · 229 lines · 11851 B

1// nx_rule_soundness_test.nx -- FAIL-LOUD driver that PROVES the nx_eqsat rewrite 2// rules SOUND (algebraic identities mod 2^W) via nx_rule_soundness.nx. 3// 4// Strategy (winning): width-independent induction (reuses the divider-proof 5// template) + EXHAUSTIVE small-width at W=8, with nx_triangulate as the 6// independent cross-check, and TWO independent witnesses per vector ANDed. 7// 8// CERTIFIED-LIVE SET (this rung): the SIX width-trivial unary rules 9// {add_zero, sub_self, add_self, and_self, or_zero, mul_one} 10// swept exhaustively over x in [0,2^8). They have NO k boundary and ZERO owed 11// guards, so they are fully certifiable now. Known answer: total_unary == 1536 12// (= 6 * 256), all certified. 13// 14// mul_pow2 (NEXT rung -- theorem proven here but NOT in the certified-live set): 15// IN-RANGE: for all x in [0,2^8) and all k in [0,8), MUL(x,2^k) == SHL(x,k) 16// mod 2^8. Known answer: in_range == 2048 (= 256 * 8), all certified. 17// BOUNDARY (k>=W): the rule MUST NOT fire -- the identity is FALSE. We PROVE 18// unsoundness for k in [8,16) over all x (the owed k<W guard's location). 19// Known answer: neg_caught == neg_total == 2048 (= 256 * 8). mul_pow2 stays 20// OUT of the live-certified set until the k<W guard lands in nx_eqsat.nx. 21// 22// EXIT CODES (FAIL LOUD): 23// 1 a certified-unary rule vector failed (proven_unary != total_unary) 24// 2 unary total mismatch (total_unary != 1536) 25// 3 a mul_pow2 in-range vector failed (proven_ir != in_range) 26// 4 mul_pow2 in-range total mismatch (in_range != 2048) 27// 5 a mul_pow2 boundary vector did NOT demonstrate unsoundness (neg_caught != neg_total) 28// 6 boundary total mismatch (neg_total != 2048) 29// 0 ALL pass. 30// 31// KNOWN-ANSWER LINE printed (FAIL LOUD): the per-rule pass counts then the 32// exhaustive totals: 33// "<add_zero> <sub_self> <add_self> <and_self> <or_zero> <mul_one> | <rules_proven> <proven_unary> <total_unary> | <proven_ir> <in_range> | <neg_caught> <neg_total>" 34// 35// SOVEREIGN: no SMT, no .sh, no foreign oracle -- runs on the pinned compiler. 36 37import "nx_rule_soundness.nx" 38// ALSO import the membership proof: it owns the shift-merge in-range two-witness 39// battery (mp_battery_shift_merge), the i+j>=W boundary-unsound witness 40// (mp_shiftmerge_boundary_unsound), the proven-sound allow-list (mp_is_sound_rule), 41// and the live certifier (mp_certify_live). This soundness test PROVES the certified 42// shift-merge rule (shl (shl x i) j)->(shl x (i+j)) here, BEFORE the allow-list arm 43// is trusted -- the SAME discipline that admitted mul_pow2/xor_self. Both libraries 44// share the same transitive deps (nx_alu_divider/nx_triangulate); import-dedup-safe. 45// The shift-merge proof is asserted INTERNALLY (new exit codes) so the printed 46// known-answer line stays byte-identical (the mandated regression). 47import "nx_eqsat_membership_proof.nx" 48 49const RW: i64 = 8 // representative width: sweep x in [0,2^8) 50 51func _emit_num(v: i64) -> i64 { 52 let b: *u8 = sys_mmap(28); var n: i64 = v; if n < 0 { n = 0 - n } 53 let t2: *u8 = sys_mmap(28); var t: i64 = 0 54 if n == 0 { t2[0] = 48; t = 1 } 55 while n > 0 { t2[t] = 48 + (n % 10); n = n / 10; t = t + 1 } 56 var i: i64 = 0; while i < t { b[i] = t2[t - 1 - i]; i = i + 1 } 57 b[t] = 32; sys_write(1, b, t + 1); return 0 58} 59func _bar() -> i64 { let z: *u8 = sys_mmap(4); z[0] = 124; z[1] = 32; sys_write(1, z, 2); return 0 } 60func _nl() -> i64 { let z: *u8 = sys_mmap(2); z[0] = 10; sys_write(1, z, 1); return 0 } 61 62// Exhaustively certify ONE unary rule over x in [0,2^RW). Returns the pass count 63// (should equal hi). Accumulates the shared gsim scratch via the caller's bufs. 64func sweep_unary(rule: i64, hi: i64, 65 vals: *i64, cells: *NxGsimCell, g: *NxGsim, 66 legs: *i64, v: *NxTriVerdict) -> i64 { 67 var pass: i64 = 0 68 var x: i64 = 0 69 while x < hi { 70 if nx_rs_certify_unary(rule, x, RW, vals, cells, g, legs, v) == 1 { 71 pass = pass + 1 72 } 73 x = x + 1 74 } 75 return pass 76} 77 78func main() -> i64 { 79 // Shared scratch (caller-owned, reset per vector by nx_rs_mk). 80 let vals: *i64 = sys_mmap(64 * 8) as *i64 81 let cells: *NxGsimCell = sys_mmap(64 * 48) as *NxGsimCell 82 let g: *NxGsim = sys_mmap(64) as *NxGsim 83 let legs: *i64 = sys_mmap(2 * 8) as *i64 84 let v: *NxTriVerdict = sys_mmap(64) as *NxTriVerdict 85 86 let hi: i64 = 1 << RW // 2^RW = 256 87 88 // ---- SIX width-trivial unary rules (the certified-live rung) ------------- 89 let p_addz: i64 = sweep_unary(NX_RS_ADD_ZERO, hi, vals, cells, g, legs, v) 90 let p_subs: i64 = sweep_unary(NX_RS_SUB_SELF, hi, vals, cells, g, legs, v) 91 let p_adds: i64 = sweep_unary(NX_RS_ADD_SELF, hi, vals, cells, g, legs, v) 92 let p_ands: i64 = sweep_unary(NX_RS_AND_SELF, hi, vals, cells, g, legs, v) 93 let p_orz: i64 = sweep_unary(NX_RS_OR_ZERO, hi, vals, cells, g, legs, v) 94 let p_mul1: i64 = sweep_unary(NX_RS_MUL_ONE, hi, vals, cells, g, legs, v) 95 96 let proven_unary: i64 = p_addz + p_subs + p_adds + p_ands + p_orz + p_mul1 97 let total_unary: i64 = 6 * hi // 6 * 256 = 1536 98 99 // rules_proven = number of the six rules that passed ALL 256 vectors. 100 var rules_proven: i64 = 0 101 if p_addz == hi { rules_proven = rules_proven + 1 } 102 if p_subs == hi { rules_proven = rules_proven + 1 } 103 if p_adds == hi { rules_proven = rules_proven + 1 } 104 if p_ands == hi { rules_proven = rules_proven + 1 } 105 if p_orz == hi { rules_proven = rules_proven + 1 } 106 if p_mul1 == hi { rules_proven = rules_proven + 1 } 107 108 // ---- mul_pow2 IN-RANGE theorem (NEXT rung; proven, not live-certified) --- 109 var proven_ir: i64 = 0 110 var in_range: i64 = 0 111 var x: i64 = 0 112 while x < hi { 113 var k: i64 = 0 114 while k < RW { // k in [0,W) 115 in_range = in_range + 1 116 if nx_rs_certify_mulpow2(x, k, RW, vals, cells, g, legs, v) == 1 { 117 proven_ir = proven_ir + 1 118 } 119 k = k + 1 120 } 121 x = x + 1 122 } 123 124 // ---- mul_pow2 BOUNDARY: prove UNSOUND for k>=W (the owed guard's home) --- 125 var neg_caught: i64 = 0 126 var neg_total: i64 = 0 127 var bx: i64 = 0 128 while bx < hi { 129 var bk: i64 = RW // k in [W, 2W) 130 while bk < (2 * RW) { 131 neg_total = neg_total + 1 132 if nx_rs_mulpow2_boundary_unsound(bx, bk, RW) == 1 { 133 neg_caught = neg_caught + 1 134 } 135 bk = bk + 1 136 } 137 bx = bx + 1 138 } 139 140 // ===== CERTIFIED SHIFT-MERGE RULE: prove sound BEFORE the engine cites it ===== 141 // (shl (shl x i) j) == (shl x (i+j)) for i,j CONST, i+j<W. Proven via the SAME 142 // method: W=8 masked exhaustive two-witness IN-RANGE battery + i+j>=W BOUNDARY 143 // unsound + a LIVE certification of a real shift-merge merge. Asserted INTERNALLY 144 // (new exit codes 7..12) so the printed known-answer line above stays byte-identical 145 // (the mandated regression). Uses the membership proof's owned batteries. 146 let mp_t: *NxTriTally = sys_mmap(64) as *NxTriTally 147 nx_tri_tally_init(mp_t) 148 let sm_total: i64 = mp_battery_shift_merge(g, vals, cells, mp_t) // 256 * 36 == 9216 149 let sm_passed: i64 = mp_t.passed 150 151 // BOUNDARY: prove UNSOUND for i+j>=W over all x and all (i,j) in [0,W)x[0,W) 152 // with i+j>=W (the i+j<W guard's home). lockstep i+j<W (in matcher) iff certified. 153 var sm_neg_caught: i64 = 0 154 var sm_neg_total: i64 = 0 155 var sx: i64 = 0 156 while sx < hi { 157 var si: i64 = 0 158 while si < RW { 159 var sj: i64 = 0 160 while sj < RW { 161 if (si + sj) >= RW { // boundary region only 162 sm_neg_total = sm_neg_total + 1 163 if mp_shiftmerge_boundary_unsound(sx, si, sj) == 1 { 164 sm_neg_caught = sm_neg_caught + 1 165 } 166 } 167 sj = sj + 1 168 } 169 si = si + 1 170 } 171 sx = sx + 1 172 } 173 174 // LIVE CERTIFICATION: build (shl (shl v 2) 3), enable prov + the DSL table (7 175 // builtins + shift-merge row), run the REAL saturator; the e-matcher merges the 176 // nested-shift class with (shl v 5) citing id 11; mp_certify_live must CERTIFY. 177 let sm_nodes: *NxENode = sys_mmap(128 * 64) as *NxENode 178 let sm_cls: *NxEClass = sys_mmap(128 * 32) as *NxEClass 179 let g5: *NxEGraph = sys_mmap(256) as *NxEGraph 180 nx_eqsat_init(g5, sm_nodes, 128, sm_cls, 128) 181 let sm_prov: *i64 = sys_mmap(128 * 8) as *i64 182 nx_eqsat_enable_prov(g5, sm_prov, 128) 183 let sm_tab: *NxDslRule = sys_mmap(16 * 96) as *NxDslRule 184 let sm_n: i64 = nx_eqsat_builtin_dsl_table(sm_tab) 185 nx_eqsat_dsl_set_row(sm_tab, sm_n, NX_EQSAT_RULE_SHIFT_MERGE, NX_EQ_OP_SHL, 186 DSL_VAR, 0, DSL_VAR, 0, 187 RHS_SHL_A_BY_IADDED_J, 0, 188 SC_SHIFT_MERGE_IJW, CNT_REAL_MERGE, 0) 189 nx_eqsat_enable_dsl(g5, sm_tab, 8, 16) 190 let sm_v: i64 = nx_eqsat_add_var(g5, 0) 191 let sm_c2: i64 = nx_eqsat_add_const(g5, 2) 192 let sm_inner: i64 = nx_eqsat_add_binary(g5, NX_EQ_OP_SHL, sm_v, sm_c2) // (shl v 2) 193 let sm_c3: i64 = nx_eqsat_add_const(g5, 3) 194 let sm_outer: i64 = nx_eqsat_add_binary(g5, NX_EQ_OP_SHL, sm_inner, sm_c3) // (shl (shl v 2) 3) 195 let sm_c5: i64 = nx_eqsat_add_const(g5, 5) 196 let sm_merged: i64 = nx_eqsat_add_binary(g5, NX_EQ_OP_SHL, sm_v, sm_c5) // (shl v 5) 197 let sm_sat: i64 = nx_eqsat_saturate(g5, 16) 198 let sm_cert_o: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate 199 let sm_cert: i64 = mp_certify_live(g5, sm_outer, sm_merged, sm_cert_o) 200 // did a shift-merge citation (id 11) actually get logged? 201 var sm_saw11: i64 = 0 202 var sq: i64 = 0 203 while sq < g5.n_prov { if g5.prov[sq] == NX_EQSAT_RULE_SHIFT_MERGE { sm_saw11 = 1 } sq = sq + 1 } 204 205 // ---- KNOWN-ANSWER LINE (FAIL LOUD) -------------------------------------- 206 _emit_num(p_addz); _emit_num(p_subs); _emit_num(p_adds); 207 _emit_num(p_ands); _emit_num(p_orz); _emit_num(p_mul1); _bar(); 208 _emit_num(rules_proven); _emit_num(proven_unary); _emit_num(total_unary); _bar(); 209 _emit_num(proven_ir); _emit_num(in_range); _bar(); 210 _emit_num(neg_caught); _emit_num(neg_total); _nl(); 211 212 // ---- FAIL-LOUD assertions on the known answers -------------------------- 213 if proven_unary != total_unary { sys_exit(1); return 1 } // every unary vector certified 214 if total_unary != 1536 { sys_exit(2); return 2 } // 6 * 256 215 if proven_ir != in_range { sys_exit(3); return 3 } // every mul_pow2 in-range vector certified 216 if in_range != 2048 { sys_exit(4); return 4 } // 256 * 8 217 if neg_caught != neg_total { sys_exit(5); return 5 } // every boundary vector proven unsound 218 if neg_total != 2048 { sys_exit(6); return 6 } // 256 * 8 219 // ---- SHIFT-MERGE internal assertions (not printed; KA byte-identical) ---- 220 if sm_total != 9216 { sys_exit(7); return 7 } // 256 * 36 (i+j<W triangle @W=8) 221 if sm_passed != sm_total { sys_exit(8); return 8 } // every in-range vector both-witness pass 222 if sm_neg_total != 7168 { sys_exit(9); return 9 } // 256 * 28 (i+j>=W region @W=8: 64-36 pairs) 223 if sm_neg_caught != sm_neg_total { sys_exit(10); return 10 } // every boundary vector proven unsound 224 if mp_is_sound_rule(NX_EQSAT_RULE_SHIFT_MERGE) != 1 { sys_exit(11); return 11 } // admitted only after battery 225 if sm_sat != NX_EQSAT_SATURATED { sys_exit(12); return 12 } // engine converged 226 if sm_saw11 != 1 { sys_exit(13); return 13 } // the rule actually fired (id 11 logged) 227 if sm_cert != 1 { sys_exit(14); return 14 } // membership-proof CERTIFIES the merge 228 sys_exit(0); return 0 229}