code wiki / _hdl_build / nx_eqsat_membership_proof.nx
nx_eqsat_membership_proof.nx
buildroot/runtime/_hdl_build/nx_eqsat_membership_proof.nx
about
nx_eqsat_membership_proof.nx -- MEMBERSHIP-AS-PROOF CERTIFICATE for the eqsat
e-graph (the sovereign analogue of ROVER's "verified rewriting": no external
SMT oracle; the rewrite rules' soundness + e-class membership IS the proof).
THE CLAIM BEING CERTIFIED:
Two expressions a, b are equivalent-for-all-inputs (mod 2^W) iff
(1) MEMBERSHIP: nx_eqsat_find(g, a) == nx_eqsat_find(g, b)
(they landed in the same canonical e-class after saturation), AND
(2) PROVENANCE: every union on the rewrite path that merged them used a
rule from the PROVEN-SOUND set.
When BOTH hold, a == b for all inputs -- membership is a rewrite-path
equivalence PROOF, not a heuristic.
LIVE CERTIFICATION (mp_certify_live): the engine (nx_eqsat.nx) now logs the
citing rule-id of EVERY real merge at its single union chokepoint
(nx_eqsat_union_cited) when provenance is enabled (nx_eqsat_enable_prov). The
live certifier runs the REAL nx_eqsat_saturate and checks the GLOBAL property
"every logged rule-id is in the proven-sound set" + find(a)==find(b) + the log
did not overflow (fail-closed). Because every merge flows through the one
function that mutates canon, a merge cannot happen without a rule-id being
logged -- so an unlogged/un-cited merge (RULE_NONE) poisons the certificate.
The gated recorder (mp_prov_admit_union) is RETAINED for the load-bearing
negatives' belt-and-suspenders legs + the k<MP_W boundary side-condition.
THE SOUND SET (justified, not asserted): a rule is admitted to the sound set
ONLY after its W-bit (mod 2^W) algebraic identity is machine-checked
EXHAUSTIVELY at a representative width W=8 -- the same width-independent
exhaustive-small-width discipline as nx_alu_divider_proof_test.nx. Every
evaluator output is MASKED to W bits before comparison (mask = (1<<W)-1) so we
prove the true mod-2^W theorem, NOT a coincidental full-i64 identity (the
truncation gap). The oracle is also reduced mod 2^W and is independent of both
the MUL and SHL nodes (computed by repeated doubling), so the two netlist legs
+ oracle are three independent witnesses fed through nx_triangulate.
LOAD-BEARING (defense in depth): the certificate must FAIL when
(NEG-A) a NON-sound rule is used (it is not in the allow-list AND its W=8
battery DISAGREES, so it can never enter the sound set), or
(NEG-B) the two endpoints are NOT in the same e-class (membership fails).
Both negatives are exercised + asserted in the test (FAIL LOUD known answer).
dependencies 3 imports · 4 importers
imports: nx_eqsat.nxnx_alu_divider.nxnx_triangulate.nx
imported by: nx_eqsat_constfold_test.nxnx_eqsat_dsl_parity_test.nxnx_eqsat_membership_proof_test.nxnx_rule_soundness_test.nx
structs
| 514 | struct NxMpProvenance |
| 556 | struct NxMpCertificate |
consts
| 52 | const MP_MAGIC_999999: i64 = 999999 |
| 56 | const MP_W: i64 = 8 // representative width: sweep x in [0, 2^W) |
| 68 | const MP_RULE_NONE: i64 = 0 // sentinel: no/unknown rule -> NOT sound |
| 69 | const MP_RULE_ADD_ZERO: i64 = 1 |
| 70 | const MP_RULE_SUB_SELF: i64 = 2 |
| 71 | const MP_RULE_ADD_SELF: i64 = 3 |
| 72 | const MP_RULE_AND_SELF: i64 = 4 |
| 73 | const MP_RULE_OR_ZERO: i64 = 5 |
| 74 | const MP_RULE_MUL_ONE: i64 = 6 |
| 75 | const MP_RULE_MUL_POW2: i64 = 7 |
| 82 | const MP_RULE_CONGRUENCE: i64 = 8 |
| 90 | const MP_RULE_XOR_SELF: i64 = 9 |
| 104 | const MP_RULE_CONSTFOLD: i64 = 10 |
| 112 | const MP_RULE_SHIFT_MERGE: i64 = 11 |
| 113 | const MP_RULE_N: i64 = 12 |
| 118 | const MP_RULE_BOGUS_MUL3: i64 = 100 |
functions
| 58 | func mp_mask() -> i64 { return (1 << MP_W) - 1 } // (1<<W)-1 = mod-2^W reducer |
| 59 | func mp_count() -> i64 { return 1 << MP_W } // 2^W vectors per unary rule |
| 124 | func mp_rule_ids_lockstep() -> i64 |
| 146 | func _mp_reset(g: *NxGsim, vals: *i64, cells: *NxGsimCell) -> i64 |
| 152 | func _mp_eval_op_const(g: *NxGsim, vals: *i64, cells: *NxGsimCell, |
| 164 | func _mp_eval_op_self(g: *NxGsim, vals: *i64, cells: *NxGsimCell, |
| 178 | func _mp_oracle_identity(x: i64) -> i64 { return x & mp_mask() } // == x |
| 179 | func _mp_oracle_zero() -> i64 { return 0 } // sub_self called by 1: mp_battery_unary |
| 181 | func _mp_oracle_double_k(x: i64, k: i64) -> i64 |
| 197 | func _mp_witnessA(rule: i64, x: i64, k: i64) -> i64 |
| 222 | func _mp_shiftmerge_witnessA(x: i64, i: i64, j: i64) -> i64 |
| 234 | func _mp_shl_wbit(x: i64, k: i64) -> i64 |
| 247 | func mp_battery_shift_merge(g: *NxGsim, vals: *i64, cells: *NxGsimCell, t: *NxTriTally) -> i64 |
| 303 | func mp_shiftmerge_boundary_unsound(x: i64, i: i64, j: i64) -> i64 |
| 325 | func mp_battery_unary(g: *NxGsim, vals: *i64, cells: *NxGsimCell, |
| 393 | func mp_battery_mul_pow2_inrange(g: *NxGsim, vals: *i64, cells: *NxGsimCell, |
| 432 | func mp_battery_xor_self(g: *NxGsim, vals: *i64, cells: *NxGsimCell, t: *NxTriTally) -> i64 |
| 443 | func mp_is_sound_rule(rule: i64) -> i64 |
| 488 | func mp_shift_merge_ij_ok(i: i64, j: i64) -> i64 |
| 500 | func mp_mul_pow2_k_ok(k: i64) -> i64 |
| 521 | func mp_prov_init(p: *NxMpProvenance) -> i64 called by 1: main |
| 533 | func mp_prov_admit_union(p: *NxMpProvenance, g: *NxEGraph, |
| 564 | func mp_certify(g: *NxEGraph, a: i64, b: i64, |
| 597 | func mp_log_all_sound(g: *NxEGraph) -> i64 |
| 606 | func mp_certify_live(g: *NxEGraph, a: i64, b: i64, out: *NxMpCertificate) -> i64 |