code wiki / _hdl_build / nx_eqsat_constfold_test.nx
nx_eqsat_constfold_test.nx source
↩ module page · 211 lines · 13585 B
1// nx_eqsat_constfold_test.nx -- the CONST-FOLD GATE: proves the egg-style e-class
2// ANALYSIS (constant folding / propagation) newly integrated into nx_eqsat.nx is
3// (a) SOUND BY EVALUATION (folds via the PROVEN nx_gsim_eval_cell oracle),
4// (b) CERTIFIED through membership-as-proof (cites NX_EQSAT_RULE_CONSTFOLD, admitted
5// to the proven-sound allow-list + lockstep), and
6// (c) LOAD-BEARING (a wrong fold / unmapped op / unproven rule is REFUSED).
7//
8// FAIL LOUD known-answer; exit 0 iff every check holds.
9//
10// STEPS (each self-asserts; mismatch => sys_exit(nonzero)):
11// STEP 0 LOCKSTEP: MP_RULE_CONSTFOLD == NX_EQSAT_RULE_CONSTFOLD (and the whole
12// id table agrees), so the live log the engine writes is never mislabeled.
13// Also: MP_RULE_CONSTFOLD must be in the proven-sound allow-list.
14// STEP 1 POSITIVE (add 2 3)->5, LIVE + CERTIFIED: enable prov + constfold, run the
15// REAL saturator, assert find(add23)==find(5), the log contains RULE_CONSTFOLD,
16// and mp_certify_live CERTIFIES (member && sound).
17// STEP 2 MORE OPS: (mul 6 7)->42 and (shl 1 4)->16 fold + certify (same engine path).
18// STEP 3 NEGATIVE -- WRONG FOLD REFUSED: (a) an unmapped op (DIV) never folds even
19// with const children (nx_eqsat_op_to_gate_kind(DIV) < 0) -- find stays
20// distinct from any const; (b) an UNPROVEN rule id (BOGUS) is NOT in the
21// allow-list so mp_log_all_sound would refuse it -- a back-door wrong fold
22// cannot certify; (c) folding is OFF by default (no enable => no fold => no
23// CONSTFOLD in the log) so the feature is opt-in by construction.
24//
25// Known answer (FAIL LOUD): prints "5 42 16 1 1 1 " == folded(add 2 3), folded(mul 6 7),
26// folded(shl 1 4), cert_add, cert_mul, cert_shl. exit 0 iff all steps hold.
27
28import "nx_gate_verdict.nx"
29import "nx_eqsat_membership_proof.nx"
30
31func _emit_num(v: i64) -> i64 {
32 let b: *u8 = sys_mmap(28); var n: i64 = v; if n < 0 { n = 0 - n }
33 let t2: *u8 = sys_mmap(28); var t: i64 = 0
34 if n == 0 { t2[0] = 48; t = 1 }
35 while n > 0 { t2[t] = 48 + (n % 10); n = n / 10; t = t + 1 }
36 var i: i64 = 0; while i < t { b[i] = t2[t - 1 - i]; i = i + 1 }
37 b[t] = 32; sys_write(1, b, t + 1); return 0
38}
39func _nl() -> i64 { let z: *u8 = sys_mmap(2); z[0] = 10; sys_write(1, z, 1); return 0 }
40
41// Does the live provenance log contain rule-id `want`?
42func _log_has(g: *NxEGraph, want: i64) -> i64 {
43 var i: i64 = 0
44 while i < g.n_prov {
45 if g.prov[i] == want { return 1 }
46 i = i + 1
47 }
48 return 0
49}
50
51// Build a fresh e-graph that folds a binary op of two constants, return the
52// folded value read from the resulting class's best_node + whether it CERTIFIES.
53// cert_out[0] = mp_certify_live verdict. Returns the folded constant value.
54func _fold_binop(op: i64, a: i64, b: i64, cert_out: *i64) -> i64 {
55 let cap_nodes: i64 = 64
56 let cap_cls: i64 = 64
57 let nodes: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode
58 let classes: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass
59 let eg: *NxEGraph = sys_mmap(NX_EQSAT_GRAPH_BYTES) as *NxEGraph
60 if nx_eqsat_init(eg, nodes, cap_nodes, classes, cap_cls) != NX_EQSAT_OK { sys_exit(80); return 80 }
61 // enable provenance + const-fold BEFORE adding the op node (the hook fires at add)
62 let plog: *i64 = sys_mmap(64 * 8) as *i64
63 if nx_eqsat_enable_prov(eg, plog, 64) != NX_EQSAT_OK { sys_exit(81); return 81 }
64 if nx_eqsat_enable_constfold(eg) != NX_EQSAT_OK { sys_exit(82); return 82 }
65 let ca: i64 = nx_eqsat_add_const(eg, a)
66 let cb: i64 = nx_eqsat_add_const(eg, b)
67 let top: i64 = nx_eqsat_add_binary(eg, op, ca, cb) // hook folds here
68 // The expected constant (added separately) for the membership endpoint.
69 // gsim is the engine's oracle; recompute the same value via the SAME oracle.
70 let expect: i64 = nx_gsim_eval_cell(nx_eqsat_op_to_gate_kind(op), a, b, 0)
71 let cexp: i64 = nx_eqsat_add_const(eg, expect)
72 // run the real saturator (idempotent; the fold already merged at add-time)
73 nx_eqsat_saturate(eg, 16)
74 // certify the equivalence top == CONST(expect) STRICTLY FROM THE LOG.
75 let cert: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate
76 let cok: i64 = mp_certify_live(eg, top, cexp, cert)
77 cert_out[0] = cok
78 // sanity: the log must contain RULE_CONSTFOLD (the merge was const-fold)
79 if _log_has(eg, NX_EQSAT_RULE_CONSTFOLD) != 1 { sys_exit(83); return 83 }
80 // read the folded value off top's canonical best_node
81 let canon: i64 = nx_eqsat_find(eg, top)
82 let bn: i64 = eg.classes[canon].best_node
83 if eg.nodes[bn].op != NX_EQ_OP_CONST { sys_exit(84); return 84 }
84 return eg.nodes[bn].payload
85}
86
87func main() -> i64 {
88 // ====================================================================
89 // STEP 0: LOCKSTEP + allow-list admission.
90 // ====================================================================
91 // MIGRATED OFF A HAND-ROLLED VERDICT 2026-08-14, and NEVER COMPILED BEFORE THAT DAY (nx_eqsat.nx
92 // itself did not compile). The teeth were already strong -- this file carries more negative controls
93 // than positives -- they simply had no way to report to anything outside the process, and an
94 // exit-code-as-assertion-number protocol stops at the FIRST failure, hiding the rest.
95 gv_head("nx_eqsat const-fold gate -- e-class analysis folds only ops with a faithful oracle cell" as *u8)
96 let ctr: *i64 = gv_ctr()
97
98 gv_check("rule ids are in lockstep between the engine and the membership prover" as *u8, mp_rule_ids_lockstep() == 1, ctr)
99 gv_check("MP_RULE_CONSTFOLD and NX_EQSAT_RULE_CONSTFOLD are the same id" as *u8, MP_RULE_CONSTFOLD == NX_EQSAT_RULE_CONSTFOLD, ctr)
100 gv_check("const-fold is admitted to the proven-sound allow-list" as *u8, mp_is_sound_rule(MP_RULE_CONSTFOLD) == 1, ctr)
101 // 12 is a KNOWN-ANSWER oracle, not a magic number: it pins the rule-table size so that appending a
102 // rule without extending this gate is a RED rather than a silent widening of what counts as sound.
103 gv_check("the rule table is still the size this gate was written against" as *u8, NX_EQSAT_RULE_N == 12, ctr)
104
105 // ====================================================================
106 // STEP 1 + STEP 2: positive folds + certificates.
107 // ====================================================================
108 let ca: *i64 = sys_mmap(8) as *i64
109 let cm: *i64 = sys_mmap(8) as *i64
110 let cs: *i64 = sys_mmap(8) as *i64
111 let f_add: i64 = _fold_binop(NX_EQ_OP_ADD, 2, 3, ca) // 2+3 = 5
112 let f_mul: i64 = _fold_binop(NX_EQ_OP_MUL, 6, 7, cm) // 6*7 = 42
113 let f_shl: i64 = _fold_binop(NX_EQ_OP_SHL, 1, 4, cs) // 1<<4 = 16
114
115 _emit_num(f_add); _emit_num(f_mul); _emit_num(f_shl)
116 _emit_num(ca[0]); _emit_num(cm[0]); _emit_num(cs[0])
117 _nl()
118
119 // ====================================================================
120 // STEP 3a: NEGATIVE -- an UNMAPPED op (DIV) never folds (no faithful cell).
121 // ====================================================================
122 // FIVE OPS THE FOLDER MUST REFUSE because no gate cell computes them faithfully. These are the
123 // teeth that stop const-fold from becoming fold-everything-and-hope.
124 gv_check("neg-control-DIV-has-no-faithful-cell-and-is-refused" as *u8, nx_eqsat_op_to_gate_kind(NX_EQ_OP_DIV) < 0, ctr)
125 gv_check("neg-control-REM-is-refused" as *u8, nx_eqsat_op_to_gate_kind(NX_EQ_OP_REM) < 0, ctr)
126 gv_check("neg-control-SHR-is-refused-logical-is-not-arithmetic" as *u8, nx_eqsat_op_to_gate_kind(NX_EQ_OP_SHR) < 0, ctr)
127 gv_check("neg-control-SAR-is-refused" as *u8, nx_eqsat_op_to_gate_kind(NX_EQ_OP_SAR) < 0, ctr)
128 gv_check("neg-control-NEG-is-refused-NOT-is-not-negate" as *u8, nx_eqsat_op_to_gate_kind(NX_EQ_OP_NEG) < 0, ctr)
129 // build (div 12 3) with const children + const-fold ON: it must NOT fold.
130 let cap_nodes: i64 = 64
131 let cap_cls: i64 = 64
132 let n2: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode
133 let c2: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass
134 let eg2: *NxEGraph = sys_mmap(NX_EQSAT_GRAPH_BYTES) as *NxEGraph
135 if nx_eqsat_init(eg2, n2, cap_nodes, c2, cap_cls) != NX_EQSAT_OK { sys_exit(15); return 15 }
136 let plog2: *i64 = sys_mmap(64 * 8) as *i64
137 nx_eqsat_enable_prov(eg2, plog2, 64)
138 nx_eqsat_enable_constfold(eg2)
139 let d12: i64 = nx_eqsat_add_const(eg2, 12)
140 let d3: i64 = nx_eqsat_add_const(eg2, 3)
141 let divv: i64 = nx_eqsat_add_binary(eg2, NX_EQ_OP_DIV, d12, d3) // would-be 4, but REFUSED
142 let four: i64 = nx_eqsat_add_const(eg2, 4)
143 nx_eqsat_saturate(eg2, 16)
144 gv_check("neg-control-a-refused-op-with-const-children-does-NOT-fold-to-its-value" as *u8, nx_eqsat_find(eg2, divv) != nx_eqsat_find(eg2, four), ctr)
145 gv_check("neg-control-a-refused-fold-logs-NO-constfold-id" as *u8, _log_has(eg2, NX_EQSAT_RULE_CONSTFOLD) == 0, ctr)
146 // the DIV node's best_node must still be a DIV (no const merged in)
147 let dc: i64 = nx_eqsat_find(eg2, divv)
148 gv_check("neg-control-the-refused-node-still-extracts-as-DIV-not-a-constant" as *u8, eg2.nodes[eg2.classes[dc].best_node].op == NX_EQ_OP_DIV, ctr)
149
150 // ====================================================================
151 // STEP 3b: NEGATIVE -- an UNPROVEN rule id is refused by the certifier. A
152 // back-door merge citing RULE_NONE (the legacy 2-arg union) poisons the log,
153 // so mp_log_all_sound refuses it -> the certificate cannot pass even though
154 // the endpoints ARE in the same class (membership holds).
155 // ====================================================================
156 let n3: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode
157 let c3a: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass
158 let eg3: *NxEGraph = sys_mmap(NX_EQSAT_GRAPH_BYTES) as *NxEGraph
159 if nx_eqsat_init(eg3, n3, cap_nodes, c3a, cap_cls) != NX_EQSAT_OK { sys_exit(20); return 20 }
160 let plog3: *i64 = sys_mmap(64 * 8) as *i64
161 nx_eqsat_enable_prov(eg3, plog3, 64)
162 let va: i64 = nx_eqsat_add_var(eg3, 0)
163 let vb: i64 = nx_eqsat_add_var(eg3, 1)
164 // merge two UNEQUAL vars through the RULE_NONE back door (an unsound merge).
165 nx_eqsat_union(eg3, va, vb)
166 let certN: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate
167 let cokN: i64 = mp_certify_live(eg3, va, vb, certN)
168 // THE SHARPEST CONTROL IN THE FILE: membership and soundness are SEPARATED. A back-door union
169 // really does put the two vars in one class, so a certifier that only checked membership would
170 // pass -- and it must not, because the merge cites no proven rule.
171 gv_check("back-door merged endpoints ARE in one class, so membership genuinely holds" as *u8, certN.member == 1, ctr)
172 gv_check("neg-control-an-uncited-RULE_NONE-merge-poisons-soundness" as *u8, certN.sound == 0, ctr)
173 gv_check("neg-control-the-certificate-FAILS-despite-membership-holding" as *u8, cokN == 0, ctr)
174 gv_check("neg-control-RULE_NONE-is-not-in-the-proven-sound-allow-list" as *u8, mp_is_sound_rule(MP_RULE_NONE) == 0, ctr)
175 gv_check("neg-control-a-deliberately-bogus-rule-is-not-admitted" as *u8, mp_is_sound_rule(MP_RULE_BOGUS_MUL3) == 0, ctr)
176
177 // ====================================================================
178 // STEP 3c: const-fold is OFF by default (opt-in). With NO enable call, an
179 // all-const op must NOT fold and must NOT log a CONSTFOLD id (byte-identical
180 // backward-compat by construction).
181 // ====================================================================
182 let n4: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode
183 let c4: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass
184 let eg4: *NxEGraph = sys_mmap(NX_EQSAT_GRAPH_BYTES) as *NxEGraph
185 if nx_eqsat_init(eg4, n4, cap_nodes, c4, cap_cls) != NX_EQSAT_OK { sys_exit(30); return 30 }
186 let plog4: *i64 = sys_mmap(64 * 8) as *i64
187 nx_eqsat_enable_prov(eg4, plog4, 64)
188 // NOTE: NO nx_eqsat_enable_constfold call -- folding stays OFF.
189 let oa: i64 = nx_eqsat_add_const(eg4, 2)
190 let ob: i64 = nx_eqsat_add_const(eg4, 3)
191 let osum: i64 = nx_eqsat_add_binary(eg4, NX_EQ_OP_ADD, oa, ob)
192 let ofive: i64 = nx_eqsat_add_const(eg4, 5)
193 nx_eqsat_saturate(eg4, 16)
194 gv_check("neg-control-with-const-fold-OFF-an-all-const-op-does-NOT-fold" as *u8, nx_eqsat_find(eg4, osum) != nx_eqsat_find(eg4, ofive), ctr)
195 gv_check("neg-control-with-const-fold-OFF-nothing-is-logged" as *u8, _log_has(eg4, NX_EQSAT_RULE_CONSTFOLD) == 0, ctr)
196
197 // ====================================================================
198 // FINAL known-answer gate (FAIL LOUD).
199 // ====================================================================
200 // THE POSITIVES. Every tooth above asserts a REFUSAL, and a folder that folded NOTHING would pass
201 // all of them -- these three are the discrimination controls that make the refusals mean something.
202 gv_check("folds add: 2 plus 3 is 5" as *u8, f_add == 5, ctr)
203 gv_check("folds mul: 6 times 7 is 42" as *u8, f_mul == 42, ctr)
204 gv_check("folds shl: 1 shifted left 4 is 16" as *u8, f_shl == 16, ctr)
205 gv_check("the add fold CERTIFIES live from the provenance log" as *u8, ca[0] == 1, ctr)
206 gv_check("the mul fold CERTIFIES live" as *u8, cm[0] == 1, ctr)
207 gv_check("the shl fold CERTIFIES live" as *u8, cs[0] == 1, ctr)
208
209 return gv_verdict("nx_eqsat_constfold_test" as *u8, ctr,
210 "const-fold e-class analysis: three certified folds against ten refusal controls, including an uncited back-door merge that holds membership yet fails its certificate" as *u8)
211}