code wiki / _hdl_build / nx_rule_soundness.nx
nx_rule_soundness.nx source
↩ module page · 281 lines · 12466 B
1// nx_rule_soundness.nx -- SOVEREIGN soundness prover for the nx_eqsat rewrite
2// rules. Each rule in nx_eqsat.nx does a union that ASSERTS an algebraic
3// equivalence; this organ PROVES that equivalence holds for ALL inputs in W-bit
4// (mod 2^W) arithmetic, via the winning strategy:
5//
6// (1) WIDTH-INDEPENDENT INDUCTION (reuses the nx_alu_divider_proof_test.nx
7// template): prove a per-element invariant EXHAUSTIVELY at a representative
8// width W, plus a width-independence argument (per_rule comments) that
9// lifts the W=8 machine-check to ANY W. For the SIX width-trivial unary
10// rules {add_zero, sub_self, add_self, and_self, or_zero, mul_one} the
11// lift is genuine: identity-element / group-inverse / per-bit-idempotence /
12// k=1-shift laws DO NOT depend on W. For mul_pow2 it is k-induction with
13// the side-condition k+1<W (base k=0, step *2 == <<1 mod 2^W).
14//
15// (2) EXHAUSTIVE SMALL-WIDTH: enumerate every x in [0,2^W) (and, for mul_pow2,
16// every k in [0,W)) -- a real proof over the rule's full local operand
17// space at W, NOT sampling.
18//
19// (3) nx_triangulate AS INDEPENDENT CROSS-CHECK: two independent gsim legs
20// (LHS-eval, RHS-eval) plus an independent oracle, fed to nx_tri_pass_strict
21// (unanimity, k_min = n_legs). Defense in depth.
22//
23// TWO INDEPENDENT WITNESSES, ANDed (honoring the design's two-witness claim):
24// - WITNESS A (algebraic, SEPARATELY CODED): a NishiLang-native masked-arith
25// computation of the rule's invariant, computed WITHOUT reading the gsim legs
26// (so it is independent of the gsim truncation behavior). Asserts the masked
27// identity directly.
28// - WITNESS B (triangulation over gsim legs): the two netlist evaluators must
29// BOTH equal the masked oracle.
30// A rule vector is CERTIFIED iff WITNESS A holds AND WITNESS B passes.
31//
32// TRUNCATION-GAP DEFENSE (adversarial trap #1): nx_gsim_eval_cell computes a*b,
33// a<<b, a+b, a-b in FULL i64 with ZERO truncation. So this organ MASKS every
34// evaluator output (and the oracle) to (1<<W)-1 BEFORE comparison -- the check
35// then tests the real mod-2^W identity, not a coincidental i64 identity. The
36// algebraic witness also masks. No rule is certified on raw unmasked i64.
37//
38// k>=W BOUNDARY DEFENSE (adversarial trap #2): mul_pow2 fires unbounded in
39// nx_eqsat.nx (NO k<W guard -- owed). For k>=W the identity is FALSE
40// (2^k mod 2^W == 0 so x*2^k mod 2^W == 0 for all x, but x<<k is an out-of-range
41// shift). Masked equality at the boundary is a deceptive 0==0, so the boundary
42// is proven via a SEMANTIC out-of-range witness (nx_rule_shl_wbit returns an
43// out-of-range sentinel for k>=W) that DISAGREES with the all-zero multiply
44// result -- proving the guard is real and locating where it belongs. The gsim's
45// own SHL is NEVER used at k>=W (its x86 count-mask would wrongly return a<<0).
46//
47// SOVEREIGN: no SMT, no foreign oracle, no .sh; evaluates on the trusted gsim
48// organ + native masked arithmetic, all through the pinned NishiLang compiler.
49// license_tier: ORIGINAL
50
51import "nx_alu_divider.nx"
52import "nx_triangulate.nx"
53
54// Rule ids (data-driven dispatch; no magic numbers at call sites).
55const NX_RS_ADD_ZERO: i64 = 1
56const NX_RS_SUB_SELF: i64 = 2
57const NX_RS_ADD_SELF: i64 = 3
58const NX_RS_AND_SELF: i64 = 4
59const NX_RS_OR_ZERO: i64 = 5
60const NX_RS_MUL_ONE: i64 = 6
61
62// Sentinel for an out-of-range W-bit shift (k >= W). Distinct from any real
63// masked value in [0, 2^W), so it can never coincide with a multiply result.
64const NX_RS_OOR: i64 = 0 - 999999
65
66// width-mask helper: (1<<W) - 1 for W in [1,62].
67func nx_rs_mask(w: i64) -> i64 { return (1 << w) - 1 }
68
69// Reset a single-input gsim with net 0 = x (primary input). Mirrors the green
70// nx_eqsat_rule_proof_test.nx _mk so the proof maps 1:1 onto the template.
71func nx_rs_mk(vals: *i64, cells: *NxGsimCell, g: *NxGsim) -> i64 {
72 g.vals = vals; g.n_nets = 1; g.cells = cells; g.n_cells = 0; return 0
73}
74
75// ---- WITNESS A: native, masked, gsim-INDEPENDENT algebraic invariant. --------
76// Returns 1 iff the rule's masked identity holds for this x (computed with
77// native `*`/`+`/`-`/`<<`/`&`/`|` reduced mod 2^W). This is a SEPARATE proof
78// path from the gsim legs -- it does not read g.vals at all.
79func nx_rs_witness_a(rule: i64, x: i64, w: i64) -> i64 {
80 let m: i64 = nx_rs_mask(w)
81 let xm: i64 = x & m
82 if rule == NX_RS_ADD_ZERO {
83 if ((xm + 0) & m) == xm { return 1 } return 0
84 }
85 if rule == NX_RS_SUB_SELF {
86 if ((xm - xm) & m) == 0 { return 1 } return 0
87 }
88 if rule == NX_RS_ADD_SELF {
89 // (x + x) mod 2^W == (x << 1) mod 2^W
90 if ((xm + xm) & m) == ((xm << 1) & m) { return 1 } return 0
91 }
92 if rule == NX_RS_AND_SELF {
93 if (xm & xm) == xm { return 1 } return 0
94 }
95 if rule == NX_RS_OR_ZERO {
96 if (xm | 0) == xm { return 1 } return 0
97 }
98 if rule == NX_RS_MUL_ONE {
99 if ((xm * 1) & m) == xm { return 1 } return 0
100 }
101 return 0
102}
103
104// Independent oracle (third source, neither LHS nor RHS netlist), masked.
105// add_zero/and_self/or_zero/mul_one -> x (literal x)
106// sub_self -> 0 (constant)
107// add_self -> 2x by repeated addition (independent of <<)
108func nx_rs_oracle(rule: i64, x: i64, w: i64) -> i64 {
109 let m: i64 = nx_rs_mask(w)
110 let xm: i64 = x & m
111 if rule == NX_RS_SUB_SELF { return 0 }
112 if rule == NX_RS_ADD_SELF {
113 var acc: i64 = 0
114 acc = acc + xm
115 acc = acc + xm // 2x by repeated addition, NOT << and NOT *
116 return acc & m
117 }
118 return xm // add_zero / and_self / or_zero / mul_one
119}
120
121// Build LHS netlist for a unary rule on net 0 = x, run it, return the MASKED out.
122func nx_rs_eval_lhs(rule: i64, x: i64, w: i64, vals: *i64, cells: *NxGsimCell, g: *NxGsim) -> i64 {
123 let m: i64 = nx_rs_mask(w)
124 nx_rs_mk(vals, cells, g)
125 var outn: i64 = 0
126 if rule == NX_RS_ADD_ZERO {
127 let c0: i64 = div_const(g, 0)
128 outn = div_op2(g, NX_GATE_KIND_ADD, 0, c0)
129 }
130 if rule == NX_RS_SUB_SELF {
131 outn = div_op2(g, NX_GATE_KIND_SUB, 0, 0) // x - x (same net)
132 }
133 if rule == NX_RS_ADD_SELF {
134 outn = div_op2(g, NX_GATE_KIND_ADD, 0, 0) // x + x (same net)
135 }
136 if rule == NX_RS_AND_SELF {
137 outn = div_op2(g, NX_GATE_KIND_AND, 0, 0) // x & x
138 }
139 if rule == NX_RS_OR_ZERO {
140 let c0b: i64 = div_const(g, 0)
141 outn = div_op2(g, NX_GATE_KIND_OR, 0, c0b)
142 }
143 if rule == NX_RS_MUL_ONE {
144 let c1: i64 = div_const(g, 1)
145 outn = div_op2(g, NX_GATE_KIND_MUL, 0, c1)
146 }
147 g.vals[0] = x & m
148 if nx_gsim_run(g) != NX_GSIM_OK { return NX_RS_OOR }
149 return g.vals[outn] & m
150}
151
152// Build RHS netlist for a unary rule on net 0 = x, run it, return the MASKED out.
153func nx_rs_eval_rhs(rule: i64, x: i64, w: i64, vals: *i64, cells: *NxGsimCell, g: *NxGsim) -> i64 {
154 let m: i64 = nx_rs_mask(w)
155 nx_rs_mk(vals, cells, g)
156 var outn: i64 = 0
157 if rule == NX_RS_ADD_ZERO { outn = 0 } // RHS = x
158 if rule == NX_RS_AND_SELF { outn = 0 } // RHS = x
159 if rule == NX_RS_OR_ZERO { outn = 0 } // RHS = x
160 if rule == NX_RS_MUL_ONE { outn = 0 } // RHS = x
161 if rule == NX_RS_SUB_SELF {
162 outn = div_const(g, 0) // RHS = const 0
163 }
164 if rule == NX_RS_ADD_SELF {
165 let c1: i64 = div_const(g, 1)
166 outn = div_op2(g, NX_GATE_KIND_SHL, 0, c1) // RHS = x << 1
167 }
168 g.vals[0] = x & m
169 if nx_gsim_run(g) != NX_GSIM_OK { return NX_RS_OOR }
170 return g.vals[outn] & m
171}
172
173// Certify ONE unary-rule vector. Returns 1 iff WITNESS A holds AND WITNESS B
174// (triangulation of the two gsim legs vs the oracle) passes -- BOTH required.
175// The verdict v is written for the 5W1H diagnostic on the first failure.
176func nx_rs_certify_unary(rule: i64, x: i64, w: i64,
177 vals: *i64, cells: *NxGsimCell, g: *NxGsim,
178 legs: *i64, v: *NxTriVerdict) -> i64 {
179 let oracle: i64 = nx_rs_oracle(rule, x, w)
180 let lhs: i64 = nx_rs_eval_lhs(rule, x, w, vals, cells, g)
181 let rhs: i64 = nx_rs_eval_rhs(rule, x, w, vals, cells, g)
182 // WITNESS B: both netlist legs must equal the independent oracle.
183 legs[0] = lhs
184 legs[1] = rhs
185 let b_pass: i64 = nx_tri_pass_strict(legs, 2, oracle, 2, v)
186 // WITNESS A: independent algebraic invariant.
187 let a_ok: i64 = nx_rs_witness_a(rule, x, w)
188 if a_ok != 1 { return 0 }
189 if b_pass != 1 { return 0 }
190 return 1
191}
192
193// ---- mul_pow2 (k-parameterised) ---------------------------------------------
194// In-range theorem: for 0<=k<W, (x * 2^k) mod 2^W == (x << k) mod 2^W.
195// 2^k by repeated doubling (independent of native shift), masked.
196func nx_rs_pow2(k: i64, w: i64) -> i64 {
197 let m: i64 = nx_rs_mask(w)
198 var p: i64 = 1
199 var j: i64 = 0
200 while j < k { p = (p * 2) & m; j = j + 1 }
201 return p & m
202}
203
204// Independent oracle for mul_pow2: (x * 2^k) mod 2^W by k DOUBLINGS of x
205// (neither the MUL node nor the SHL node).
206func nx_rs_mulpow2_oracle(x: i64, k: i64, w: i64) -> i64 {
207 let m: i64 = nx_rs_mask(w)
208 var acc: i64 = x & m
209 var j: i64 = 0
210 while j < k { acc = (acc + acc) & m; j = j + 1 } // double k times
211 return acc & m
212}
213
214// WITNESS A for mul_pow2 (algebraic, gsim-independent), masked.
215func nx_rs_mulpow2_witness_a(x: i64, k: i64, w: i64) -> i64 {
216 let m: i64 = nx_rs_mask(w)
217 let xm: i64 = x & m
218 let pw: i64 = nx_rs_pow2(k, w)
219 if ((xm * pw) & m) == ((xm << k) & m) { return 1 }
220 return 0
221}
222
223// A faithful W-bit logical shifter: k>=W is OUT OF RANGE (sentinel), NOT a
224// wrapped x86 shift. This is the semantic ground truth the boundary witness
225// compares against (NEVER the gsim SHL, whose count mask would lie at k>=W).
226func nx_rule_shl_wbit(x: i64, k: i64, w: i64) -> i64 {
227 if k < 0 { return NX_RS_OOR }
228 if k >= w { return NX_RS_OOR }
229 let m: i64 = nx_rs_mask(w)
230 return ((x & m) << k) & m
231}
232
233// Certify ONE in-range mul_pow2 vector (0<=k<W). WITNESS A (algebraic) AND
234// WITNESS B (gsim legs MUL vs SHL vs oracle) must BOTH hold.
235func nx_rs_certify_mulpow2(x: i64, k: i64, w: i64,
236 vals: *i64, cells: *NxGsimCell, g: *NxGsim,
237 legs: *i64, v: *NxTriVerdict) -> i64 {
238 let m: i64 = nx_rs_mask(w)
239 let pw: i64 = nx_rs_pow2(k, w)
240 let oracle: i64 = nx_rs_mulpow2_oracle(x, k, w)
241
242 // LEG A: MUL x 2^k via netlist.
243 nx_rs_mk(vals, cells, g)
244 let cpw: i64 = div_const(g, pw)
245 let prod: i64 = div_op2(g, NX_GATE_KIND_MUL, 0, cpw)
246 g.vals[0] = x & m
247 if nx_gsim_run(g) != NX_GSIM_OK { return 0 }
248 let lhs: i64 = g.vals[prod] & m
249
250 // LEG B: SHL x k via netlist (k<W so the gsim count is in range).
251 nx_rs_mk(vals, cells, g)
252 let ck: i64 = div_const(g, k)
253 let shft: i64 = div_op2(g, NX_GATE_KIND_SHL, 0, ck)
254 g.vals[0] = x & m
255 if nx_gsim_run(g) != NX_GSIM_OK { return 0 }
256 let rhs: i64 = g.vals[shft] & m
257
258 legs[0] = lhs
259 legs[1] = rhs
260 let b_pass: i64 = nx_tri_pass_strict(legs, 2, oracle, 2, v)
261 let a_ok: i64 = nx_rs_mulpow2_witness_a(x, k, w)
262 if a_ok != 1 { return 0 }
263 if b_pass != 1 { return 0 }
264 return 1
265}
266
267// Boundary witness: for k>=W the rule is UNSOUND. Returns 1 iff this (x,k)
268// DEMONSTRATES the unsoundness, i.e. the true semantic multiply result
269// ((x*2^k) mod 2^W, which is 0 since 2^k mod 2^W == 0) DISAGREES with a
270// faithful W-bit shift of x by k (which is OUT OF RANGE). This is where the
271// owed k<W guard belongs. Does NOT use the gsim SHL (count-mask would lie).
272func nx_rs_mulpow2_boundary_unsound(x: i64, k: i64, w: i64) -> i64 {
273 let m: i64 = nx_rs_mask(w)
274 // True semantic multiply mod 2^W: 2^k mod 2^W == 0 for k>=W -> product is 0.
275 let mul_sem: i64 = nx_rs_mulpow2_oracle(x, k, w) // doublings k>=W collapse to 0
276 // Faithful W-bit shift: k>=W is out of range (the rule would emit shl x k
277 // with k>=W -- undefined). It must NOT equal the all-zero multiply result.
278 let shl_sem: i64 = nx_rule_shl_wbit(x, k, w) // == NX_RS_OOR for k>=W
279 if mul_sem != shl_sem { return 1 } // disagreement => unsound => guard needed
280 return 0
281}