Skip to main content

synth_verify/
fact_spec.rs

1//! Proof-carrying specialization — VCR-PERF-002 / #494 **Phase 2**: the
2//! single-elision prototype (value-range facts ⇒ dead conditional-branch
3//! elision, the `gust_mix` clamp shape).
4//!
5//! Design source of truth: `docs/design/proof-carrying-specialization.md`
6//! ("How synth consumes facts: the per-elision proof obligation"). loom is the
7//! PROVER (its validator discharged the `wsc.facts` invariants upstream);
8//! synth is a CONDITIONAL optimizer: it never re-derives a fact — it proves
9//! the correctness of its OWN transformation *given* the fact, per elision
10//! site, BEFORE emission, through the ordeal-backed [`BvSolver`]
11//! (certificate-checked pure-Rust QF_BV; every `Unsat` verdict carries an
12//! LRAT proof validated by the trusted `ordeal-lrat` checker before it is
13//! reported).
14//!
15//! # The transformation
16//!
17//! Working on the decoded [`WasmOp`] stream (backend-agnostic — the rewritten
18//! stream feeds whichever selector the driver picks), the pass walks the
19//! function top-level with a symbolic state (stack + locals as QF_BV terms via
20//! the existing [`WasmSemantics`] encoder) and, at every no-`else`
21//! `if … end`, discharges the obligation
22//!
23//! ```text
24//! premise    P   (every value-range fact reached so far, asserted as a hypothesis)
25//! obligation UNSAT( P ∧ cond ≠ 0 )
26//! ```
27//!
28//! `UNSAT(P ∧ cond ≠ 0)` implies the design doc's
29//! `UNSAT(P ∧ general_lowering(x) ≠ specialized_lowering(x))`: a no-`else`
30//! `if` whose condition is 0 on every P-admissible input never executes its
31//! body, and wasm validation forces a no-`else` `if` blocktype to have
32//! identical param/result types, so the not-taken path is the identity — the
33//! general and specialized lowerings agree on every input P admits. The
34//! stronger query is deliberately used because it is decided in the pure
35//! QF_BV fragment with no control-flow encoding.
36//!
37//! - **UNSAT (certificate-checked)** → the elision is ADMITTED: the
38//!   condition-producing op slice (proven pure and contiguous) plus the whole
39//!   `if … end` region are deleted; the certificate line is logged per
40//!   function (the evidence trail).
41//! - **Sat / Unknown / conflict-budget exceeded / any shape outside the
42//!   tracked fragment** → **DECLINE LOUDLY**: the general lowering is
43//!   emitted. There is no silent wrong-code path; the conservative fallback
44//!   is today's codegen.
45//!
46//! # Soundness of the symbolic tracking (over-approximation discipline)
47//!
48//! * Values produced by ops outside the tracked i32 fragment never enter the
49//!   state: the walk STOPS at the first untracked op (no further elisions in
50//!   the function; everything already admitted was justified independently).
51//! * Locals are seeded as fresh unconstrained variables (a superset of both
52//!   parameter values and the zero-init of non-param locals — sound).
53//! * A DECLINED `if` region may or may not execute: every local it assigns
54//!   (at any nesting depth) is havocked to a fresh variable, and its block
55//!   results are pushed as fresh variables.
56//! * An ADMITTED `if` region provably never executes, so state is unchanged.
57//! * `div`/`rem` (i32 AND i64) are tracked since Phase 2b (#494
58//!   divisor-nonzero), but NEVER deleted: they can trap, and a deleted
59//!   condition slice must be effect-free under ALL inputs, not just
60//!   P-admissible ones — so a div result always carries `start = None`
61//!   (non-erasable) and its value is havocked to a fresh variable. What
62//!   Phase 2b adds is per-site TRAP-GUARD elision marks consumed by the
63//!   direct selector (see "The div/rem guard obligations" below).
64//!
65//! # The div/rem guard obligations (Phase 2b, #494 divisor-nonzero)
66//!
67//! A `div`/`rem` lowering carries up to TWO trap guards, and they fall to two
68//! INDEPENDENT obligations — this is the #633/#634 two-guard distinction:
69//!
70//! ```text
71//! divide-by-zero guard (div_u/div_s/rem_u/rem_s, i32+i64):
72//!     UNSAT( P ∧ divisor == 0 )
73//! INT_MIN/-1 overflow guard (div_s ONLY; rem_s(INT_MIN,-1)==0 never traps):
74//!     UNSAT( P ∧ dividend == INT_MIN ∧ divisor == -1 )
75//! ```
76//!
77//! A divisor-nonzero fact (kind 3) discharges the first but NOT the second —
78//! `divisor ≠ 0` does not exclude `divisor == -1`, so the overflow guard is
79//! RETAINED unless the premises independently prove the second obligation
80//! (e.g. a value-range fact `divisor ∈ [1, N]` proves both). Each discharged
81//! obligation becomes a per-site elision mark ([`FactSpecResult::elide_div_zero`]
82//! / [`FactSpecResult::elide_div_ovf`], indices into the RETURNED stream);
83//! the driver threads them to the direct selector, which omits exactly that
84//! guard. Sat / Unknown / no-premise ⇒ loud decline, the guard is emitted.
85//!
86//! # The memory bounds-guard obligation (#494 × #390 `guard_bool`)
87//!
88//! Under `--safety-bounds software` every i32 linear-memory access carries a
89//! 4-instruction inline guard (`ADD ip, addr, #(offset+size-1); CMP ip, R10;
90//! BLO +0; UDF #0` — see `generate_load_with_bounds_check`). When a premise
91//! on the INDEX proves the access in-bounds, the guard is provably dead:
92//!
93//! ```text
94//! UNSAT( P ∧ trap_mem_oob(zext64(index) + offset, size, min_memory_bytes) )
95//! ```
96//!
97//! The encoding is [`crate::trap::trap_mem_oob`] (ordeal 0.9.1's shape,
98//! `addr + size >u mem_bound` wraparound-safe), posed at width 64 — the
99//! 32-bit index is ZERO-extended and the static memarg `offset` added at 64
100//! bits, so the `index + offset` sum can never wrap the obligation into a
101//! false Unsat (WASM's effective address is `i + offset` at infinite
102//! precision for a 32-bit memory). `min_memory_bytes` is the module's
103//! DECLARED minimum linear-memory size: the runtime extent (R10) is always
104//! ≥ the declared minimum, so an access proven inside the minimum is inside
105//! every reachable runtime bound. Like div/rem, a memory op can trap, so it
106//! is TRACKED but never DELETED — the discharged obligation becomes a
107//! per-site mark ([`FactSpecResult::elide_mem_bounds`]) the direct selector
108//! consumes by emitting the access WITHOUT the software guard. Sat /
109//! Unknown / no-premise / unknown memory size ⇒ loud decline, the guard is
110//! emitted. Loaded values are havocked to fresh variables (memory contents
111//! are not modeled); stores need no memory model for the same reason.
112//!
113//! # Flag gating
114//!
115//! The driver only invokes this pass when `SYNTH_FACT_SPEC` is set (default
116//! OFF) AND the module carried a parseable `wsc.facts` section. Frozen
117//! fixtures carry no facts section, so every frozen anchor is bit-identical
118//! trivially; with the flag off the pass does not run at all.
119
120use crate::solver::{CheckOutcome, new_solver};
121use crate::term::{BV, Bool};
122use crate::wasm_semantics::WasmSemantics;
123use std::collections::{HashMap, HashSet};
124use synth_core::WasmOp;
125use synth_core::wsc_facts::{FactKind, WscFact};
126
127/// Outcome of specializing one function. `ops`/`block_arity`/`kept` are only
128/// meaningful when [`changed`](Self::changed) — otherwise they echo the input.
129#[derive(Debug)]
130pub struct FactSpecResult {
131    /// The (possibly rewritten) op stream.
132    pub ops: Vec<WasmOp>,
133    /// The blocktype-arity side-table matching `ops` (one entry per
134    /// `Block`/`Loop`/`If` in op order — entries of deleted openers removed).
135    pub block_arity: Vec<(u8, u8)>,
136    /// Indices into the ORIGINAL op stream that were kept, in order. Lets the
137    /// driver filter parallel side-tables (e.g. `op_offsets` for DWARF).
138    pub kept: Vec<usize>,
139    /// One certificate line per ADMITTED elision (logged per function).
140    pub admitted: Vec<String>,
141    /// One line per LOUD DECLINE (the general lowering is emitted for these).
142    pub declined: Vec<String>,
143    /// #494 phase 2b: indices (into the RETURNED `ops` stream) of div/rem
144    /// ops whose divide-by-zero trap guard was certificate-elided
145    /// (`UNSAT(P ∧ divisor == 0)` discharged per site).
146    pub elide_div_zero: Vec<usize>,
147    /// #494 phase 2b: indices (into the RETURNED `ops` stream) of `div_s`
148    /// ops whose `INT_MIN / -1` overflow guard was certificate-elided — a
149    /// SEPARATE obligation (`UNSAT(P ∧ dividend == INT_MIN ∧ divisor == -1)`);
150    /// a divisor-nonzero fact alone never lands here (#633/#634).
151    pub elide_div_ovf: Vec<usize>,
152    /// #494 bounds-elision (#390 `guard_bool`): indices (into the RETURNED
153    /// `ops` stream) of i32 memory accesses whose `--safety-bounds software`
154    /// guard was certificate-elided —
155    /// `UNSAT(P ∧ trap_mem_oob(zext64(index) + offset, size,
156    /// min_memory_bytes))` discharged per site (ordeal 0.9.1 `trap_mem_oob`
157    /// shape, wraparound-safe 64-bit extension).
158    pub elide_mem_bounds: Vec<usize>,
159    /// True when `ops` differs from the input (at least one region deletion).
160    stream_changed: bool,
161}
162
163impl FactSpecResult {
164    /// True when the op STREAM was rewritten (region deletions). Guard-elision
165    /// marks do not rewrite the stream — check
166    /// [`elide_div_zero`](Self::elide_div_zero) /
167    /// [`elide_div_ovf`](Self::elide_div_ovf) separately.
168    pub fn changed(&self) -> bool {
169        self.stream_changed
170    }
171}
172
173/// A symbolic operand-stack slot.
174#[derive(Clone)]
175struct Val {
176    bv: BV,
177    /// Start of the contiguous, side-effect-free op range that produced this
178    /// value — `None` when the producing slice is impure (`local.tee`) or not
179    /// provably contiguous. Only a `Some` slice may be deleted.
180    start: Option<usize>,
181    /// Index of the op that (last) produced this value.
182    created: usize,
183}
184
185/// Specialize one function's op stream against its `wsc.facts` premises.
186///
187/// `block_arity` is the decoder's ordinal side-table (one `(params, results)`
188/// entry per `Block`/`Loop`/`If` in op order); `facts` is the per-function
189/// slice (`CompileConfig::current_func_facts`); `params_i64` is the declared
190/// param-width table (`CompileConfig::current_func_params_i64` — `true` ⇒
191/// param `k` is 64-bit), which fixes the symbolic width of a param
192/// `local.get` (Phase 2b tracks i64 divisors); `linear_memory_bytes` is the
193/// module's DECLARED minimum linear-memory size in bytes
194/// (`CompileConfig::linear_memory_bytes`; `0` = unknown — every memory
195/// bounds-guard obligation then declines loudly). Total: every input yields
196/// a result — inapplicable shapes surface as loud declines, never errors.
197pub fn specialize_function(
198    func_name: &str,
199    ops: &[WasmOp],
200    block_arity: &[(u8, u8)],
201    facts: &[WscFact],
202    params_i64: &[bool],
203    linear_memory_bytes: u32,
204) -> FactSpecResult {
205    let mut pass = Pass::new(
206        func_name,
207        ops,
208        block_arity,
209        facts,
210        params_i64,
211        linear_memory_bytes,
212    );
213    pass.walk();
214    pass.finish()
215}
216
217/// #494 phase 2b RED-TEAM lever (debug builds ONLY): treat a Sat verdict on
218/// the divide-by-zero guard obligation as an admit anyway. Exists so the
219/// differential oracle can DEMONSTRATE the divergence an unsound admit would
220/// cause (wasmtime traps at divisor == 0, the forced build does not) and then
221/// show the Sat-decline restoring the guard byte-identically. Compiled out of
222/// release builds; every forced admit screams in its certificate line.
223#[cfg(debug_assertions)]
224fn force_admit_unsound() -> bool {
225    std::env::var("SYNTH_FACT_SPEC_FORCE_ADMIT").is_ok_and(|v| v != "0")
226}
227
228#[cfg(not(debug_assertions))]
229fn force_admit_unsound() -> bool {
230    false
231}
232
233struct Pass<'a> {
234    func: &'a str,
235    ops: &'a [WasmOp],
236    block_arity: &'a [(u8, u8)],
237    /// op index → ordinal into `block_arity` (for `Block`/`Loop`/`If` ops).
238    opener_ordinal: HashMap<usize, usize>,
239    /// op index → signed range fact attached to that op's result (raw s64
240    /// bounds; clamped to the value's width at attach time).
241    range_facts: HashMap<usize, (i64, i64)>,
242    /// op indices carrying a divisor-nonzero fact (kind 3): `value ≠ 0`.
243    nonzero_facts: HashSet<usize>,
244    /// Declared param widths (`true` ⇒ 64-bit) — fixes `local.get` widths.
245    params_i64: &'a [bool],
246    /// The module's DECLARED minimum linear-memory size in bytes; `0` =
247    /// unknown (bounds-guard obligations decline).
248    mem_bound: u32,
249    sem: WasmSemantics,
250    stack: Vec<Val>,
251    locals: HashMap<u32, BV>,
252    /// Every fresh variable created (name order), for Sat counterexamples.
253    vars: Vec<BV>,
254    fresh: u32,
255    premises: Vec<Bool>,
256    premise_desc: Vec<String>,
257    /// Inclusive op-index ranges to delete (disjoint, ascending).
258    deletions: Vec<(usize, usize)>,
259    admitted: Vec<String>,
260    declined: Vec<String>,
261    /// #494 phase 2b: ORIGINAL op indices marked for zero-guard elision.
262    zero_marks: Vec<usize>,
263    /// #494 phase 2b: ORIGINAL op indices marked for overflow-guard elision.
264    ovf_marks: Vec<usize>,
265    /// #494 bounds-elision: ORIGINAL op indices of memory accesses whose
266    /// software bounds guard was certificate-proven dead.
267    mem_marks: Vec<usize>,
268}
269
270impl<'a> Pass<'a> {
271    fn new(
272        func: &'a str,
273        ops: &'a [WasmOp],
274        block_arity: &'a [(u8, u8)],
275        facts: &'a [WscFact],
276        params_i64: &'a [bool],
277        mem_bound: u32,
278    ) -> Self {
279        let mut opener_ordinal = HashMap::new();
280        let mut ord = 0usize;
281        for (i, op) in ops.iter().enumerate() {
282            if matches!(op, WasmOp::Block | WasmOp::Loop | WasmOp::If) {
283                opener_ordinal.insert(i, ord);
284                ord += 1;
285            }
286        }
287        let mut range_facts = HashMap::new();
288        let mut nonzero_facts = HashSet::new();
289        for f in facts {
290            // Out-of-range value_id is vacuous (encoding doc's rule).
291            if (f.value_id as usize) >= ops.len() {
292                continue;
293            }
294            match f.kind {
295                FactKind::ValueRange { lo, hi } => {
296                    // Raw s64 bounds; clamped to the value's width when the
297                    // walk attaches the premise. An inverted bound is vacuous.
298                    if lo <= hi {
299                        range_facts.insert(f.value_id as usize, (lo, hi));
300                    }
301                }
302                // #494 phase 2b: divisor-nonzero (kind 3) — `value ≠ 0`.
303                FactKind::DivisorNonZero => {
304                    nonzero_facts.insert(f.value_id as usize);
305                }
306                _ => {}
307            }
308        }
309        Self {
310            func,
311            ops,
312            block_arity,
313            opener_ordinal,
314            range_facts,
315            nonzero_facts,
316            params_i64,
317            mem_bound,
318            // No memory model needed: loaded values are havocked to fresh
319            // variables (only the ACCESS BOUND is reasoned about, never the
320            // contents), so stores need no modeling either.
321            sem: WasmSemantics::new_with_memory(Vec::new()),
322            stack: Vec::new(),
323            locals: HashMap::new(),
324            vars: Vec::new(),
325            fresh: 0,
326            premises: Vec::new(),
327            premise_desc: Vec::new(),
328            deletions: Vec::new(),
329            admitted: Vec::new(),
330            declined: Vec::new(),
331            zero_marks: Vec::new(),
332            ovf_marks: Vec::new(),
333            mem_marks: Vec::new(),
334        }
335    }
336
337    fn fresh_var(&mut self, name: String) -> BV {
338        let v = BV::new_const(name, 32);
339        self.vars.push(v.clone());
340        v
341    }
342
343    fn local_bv(&mut self, idx: u32) -> BV {
344        if let Some(bv) = self.locals.get(&idx) {
345            return bv.clone();
346        }
347        // A not-yet-seen local's width comes from the declared param table
348        // (#494 phase 2b tracks i64 divisors); non-param locals default to
349        // 32 bits — an i64 op reading one fails the width check and declines.
350        let width = if self.params_i64.get(idx as usize).copied().unwrap_or(false) {
351            64
352        } else {
353            32
354        };
355        let v = BV::new_const(format!("fs_l{idx}"), width);
356        self.vars.push(v.clone());
357        self.locals.insert(idx, v.clone());
358        v
359    }
360
361    /// Attach the premises of every fact naming op `i`'s result, at the
362    /// value's own width.
363    fn attach_fact(&mut self, i: usize, bv: &BV) {
364        let width = bv.get_size();
365        if let Some(&(lo, hi)) = self.range_facts.get(&i) {
366            // Clamp the s64 bound to the value's width (the phase-2 rule for
367            // 32-bit values; 64-bit values take the bound verbatim). A bound
368            // that inverts after clamping is impossible for a genuine value
369            // of this width — fact validity is loom's obligation (trust
370            // split), so we keep the phase-2 clamp semantics unchanged.
371            let (lo, hi) = if width == 32 {
372                (
373                    lo.clamp(i64::from(i32::MIN), i64::from(i32::MAX)),
374                    hi.clamp(i64::from(i32::MIN), i64::from(i32::MAX)),
375                )
376            } else {
377                (lo, hi)
378            };
379            let lo_bv = BV::from_i64(lo, width);
380            let hi_bv = BV::from_i64(hi, width);
381            let p = Bool::and(&[&bv.bvsge(&lo_bv), &bv.bvsle(&hi_bv)]);
382            self.premises.push(p);
383            self.premise_desc
384                .push(format!("value(op#{i}) ∈ [{lo}, {hi}] (signed, i{width})"));
385        }
386        if self.nonzero_facts.contains(&i) {
387            // #494 phase 2b: divisor-nonzero (kind 3).
388            let p = bv.ne(BV::from_i64(0, width));
389            self.premises.push(p);
390            self.premise_desc
391                .push(format!("value(op#{i}) ≠ 0 (i{width})"));
392        }
393    }
394
395    fn push(&mut self, bv: BV, start: Option<usize>, created: usize) {
396        self.stack.push(Val { bv, start, created });
397    }
398
399    /// Find the matching `End` for the opener at `i`; also reports whether a
400    /// top-level `Else` occurs. `None` = malformed nesting (stop the walk).
401    fn matching_end(&self, i: usize) -> Option<(usize, bool)> {
402        let mut depth = 0usize;
403        let mut has_else = false;
404        for (j, op) in self.ops.iter().enumerate().skip(i + 1) {
405            match op {
406                WasmOp::Block | WasmOp::Loop | WasmOp::If => depth += 1,
407                WasmOp::Else if depth == 0 => has_else = true,
408                WasmOp::End => {
409                    if depth == 0 {
410                        return Some((j, has_else));
411                    }
412                    depth -= 1;
413                }
414                _ => {}
415            }
416        }
417        None
418    }
419
420    /// Continuation after a DECLINED `if` region `[i..=end]`: the body may or
421    /// may not run, so havoc every local it assigns (any depth) and model its
422    /// block results as fresh variables.
423    fn havoc_region(&mut self, i: usize, end: usize, arity: (u8, u8)) {
424        let ops = self.ops;
425        for op in &ops[i + 1..end] {
426            if let WasmOp::LocalSet(idx) | WasmOp::LocalTee(idx) = op {
427                let n = self.fresh;
428                self.fresh += 1;
429                let v = self.fresh_var(format!("fs_h{n}"));
430                self.locals.insert(*idx, v);
431            }
432        }
433        for _ in 0..arity.0 {
434            self.stack.pop();
435        }
436        for k in 0..arity.1 {
437            let n = self.fresh;
438            self.fresh += 1;
439            let v = self.fresh_var(format!("fs_r{n}_{k}"));
440            self.push(v, None, end);
441        }
442    }
443
444    fn decline(&mut self, msg: String) {
445        self.declined
446            .push(format!("{}: {} — general lowering emitted", self.func, msg));
447    }
448
449    /// #494 phase 2b: discharge the per-site div/rem trap-guard obligations
450    /// for the op at `i` (`op_name`, operand width `width`, dividend `a`,
451    /// divisor `b`), recording elision marks for the lowering. TWO independent
452    /// obligations (the #633/#634 two-guard distinction):
453    ///
454    /// - zero guard (every div/rem): `UNSAT(P ∧ divisor == 0)`;
455    /// - overflow guard (`div_s` only): `UNSAT(P ∧ dividend == INT_MIN ∧
456    ///   divisor == -1)` — divisor-nonzero alone NEVER discharges this.
457    ///
458    /// Sat / Unknown / no-premise ⇒ loud decline; the guard is emitted.
459    fn try_elide_div_guards(&mut self, i: usize, op_name: &str, is_div_s: bool, a: &Val, b: &Val) {
460        let width = b.bv.get_size();
461        if self.premises.is_empty() {
462            self.decline(format!(
463                "op#{i} {op_name} — no premise reaches this site; both trap guards retained"
464            ));
465            return;
466        }
467        // Obligation 1: the divide-by-zero guard.
468        let mut solver = new_solver();
469        for p in &self.premises {
470            solver.assert(p);
471        }
472        solver.assert(&b.bv.eq(BV::from_i64(0, width)));
473        match solver.check() {
474            CheckOutcome::Unsat => {
475                self.zero_marks.push(i);
476                self.admitted.push(format!(
477                    "{}: op#{i} {op_name} — divide-by-zero guard elided:                      UNSAT(P ∧ divisor == 0) via {} (certificate-checked QF_BV;                      every Unsat carries an LRAT proof validated by ordeal-lrat);                      P = {{{}}}; divisor = {}",
478                    self.func,
479                    solver.name(),
480                    self.premise_desc.join(" ∧ "),
481                    b.bv,
482                ));
483            }
484            CheckOutcome::Sat => {
485                let cex = self.counterexample(solver.as_ref());
486                if force_admit_unsound() {
487                    // RED-TEAM lever (debug builds only): admit the Sat site
488                    // anyway so the differential oracle can demonstrate the
489                    // divergence. Screams, and still logs the model.
490                    self.zero_marks.push(i);
491                    self.admitted.push(format!(
492                        "{}: op#{i} {op_name} — divide-by-zero guard elided by                          UNSOUND FORCED ADMIT (SYNTH_FACT_SPEC_FORCE_ADMIT,                          red-team oracle lever, debug builds only) — obligation                          was Sat (counterexample: {cex}); NEVER use in production",
493                        self.func,
494                    ));
495                } else {
496                    self.decline(format!(
497                        "op#{i} {op_name} — zero-guard obligation Sat (divisor can                          be 0 under P; counterexample: {cex}); guard retained"
498                    ));
499                }
500            }
501            CheckOutcome::Unknown(reason) => {
502                self.decline(format!(
503                    "op#{i} {op_name} — zero-guard obligation Unknown ({reason});                      conservative decline, guard retained"
504                ));
505            }
506        }
507        // Obligation 2: the INT_MIN/-1 overflow guard — div_s only, and a
508        // SEPARATE proof (#633/#634): divisor ≠ 0 does not exclude -1.
509        if !is_div_s {
510            return;
511        }
512        let int_min = if width == 64 {
513            i64::MIN
514        } else {
515            i64::from(i32::MIN)
516        };
517        let mut solver = new_solver();
518        for p in &self.premises {
519            solver.assert(p);
520        }
521        solver.assert(&a.bv.eq(BV::from_i64(int_min, width)));
522        solver.assert(&b.bv.eq(BV::from_i64(-1, width)));
523        match solver.check() {
524            CheckOutcome::Unsat => {
525                self.ovf_marks.push(i);
526                self.admitted.push(format!(
527                    "{}: op#{i} {op_name} — INT{width}_MIN/-1 overflow guard elided:                      UNSAT(P ∧ dividend == INT{width}_MIN ∧ divisor == -1) via {}                      (certificate-checked QF_BV; every Unsat carries an LRAT proof                      validated by ordeal-lrat); P = {{{}}}",
528                    self.func,
529                    solver.name(),
530                    self.premise_desc.join(" ∧ "),
531                ));
532            }
533            CheckOutcome::Sat => {
534                let cex = self.counterexample(solver.as_ref());
535                self.decline(format!(
536                    "op#{i} {op_name} — overflow-guard obligation Sat (dividend ==                      INT{width}_MIN with divisor == -1 is possible under P;                      counterexample: {cex}); the #633 overflow guard is RETAINED —                      a divisor-nonzero premise alone never elides it"
537                ));
538            }
539            CheckOutcome::Unknown(reason) => {
540                self.decline(format!(
541                    "op#{i} {op_name} — overflow-guard obligation Unknown ({reason});                      conservative decline, the #633 overflow guard is RETAINED"
542                ));
543            }
544        }
545    }
546
547    /// #494 bounds-elision (#390 `guard_bool`): discharge the software
548    /// bounds-guard obligation for the i32 memory access at `i` (`op_name`,
549    /// byte width `access_size`, static memarg `offset`, index value `addr`),
550    /// recording an elision mark for the lowering:
551    ///
552    /// ```text
553    /// UNSAT( P ∧ trap_mem_oob(zext64(addr) + offset, access_size,
554    ///                         min_memory_bytes) )
555    /// ```
556    ///
557    /// The trap condition is [`crate::trap::trap_mem_oob`] (ordeal 0.9.1's
558    /// `addr + size >u mem_bound`, wraparound-safe), posed at width 64: the
559    /// 32-bit index is ZERO-extended and `offset` added at 64 bits, so the
560    /// effective-address sum cannot wrap into a false Unsat — exactly WASM's
561    /// infinite-precision `i + offset`. Proving the access inside the
562    /// DECLARED minimum memory (`mem_bound`) proves it inside every runtime
563    /// extent R10 can hold (runtime size ≥ declared minimum). Sat / Unknown
564    /// / no-premise / unknown memory size ⇒ loud decline; the guard is
565    /// emitted.
566    fn try_elide_mem_bounds(
567        &mut self,
568        i: usize,
569        op_name: &str,
570        access_size: u32,
571        offset: u32,
572        addr: &Val,
573    ) {
574        if self.mem_bound == 0 {
575            self.decline(format!(
576                "op#{i} {op_name} — linear-memory size unknown (no module memory \
577                 context); bounds guard retained"
578            ));
579            return;
580        }
581        if self.premises.is_empty() {
582            self.decline(format!(
583                "op#{i} {op_name} — no premise reaches this site; bounds guard retained"
584            ));
585            return;
586        }
587        // Wraparound-safe width extension: zext the 32-bit index to 64 bits,
588        // add the static offset at 64 bits (no 2^64 wrap is reachable:
589        // index < 2^32, offset < 2^32, size ≤ 8).
590        let ea = addr
591            .bv
592            .zero_ext(32)
593            .bvadd(BV::from_u64(u64::from(offset), 64));
594        let trap = crate::trap::trap_mem_oob(
595            &ea,
596            &BV::from_u64(u64::from(access_size), 64),
597            &BV::from_u64(u64::from(self.mem_bound), 64),
598        );
599        let mut solver = new_solver();
600        for p in &self.premises {
601            solver.assert(p);
602        }
603        solver.assert(&trap);
604        match solver.check() {
605            CheckOutcome::Unsat => {
606                self.mem_marks.push(i);
607                self.admitted.push(format!(
608                    "{}: op#{i} {op_name} (offset={offset}, {access_size} B) — software \
609                     bounds guard elided: UNSAT(P ∧ trap_mem_oob(zext64(index) + offset, \
610                     size, {} B declared-min memory)) via {} (certificate-checked QF_BV; \
611                     every Unsat carries an LRAT proof validated by ordeal-lrat); \
612                     P = {{{}}}; index = {}",
613                    self.func,
614                    self.mem_bound,
615                    solver.name(),
616                    self.premise_desc.join(" ∧ "),
617                    addr.bv,
618                ));
619            }
620            CheckOutcome::Sat => {
621                let cex = self.counterexample(solver.as_ref());
622                if force_admit_unsound() {
623                    // RED-TEAM lever (debug builds only): admit the Sat site
624                    // anyway so the differential oracle can demonstrate the
625                    // divergence. Screams, and still logs the model.
626                    self.mem_marks.push(i);
627                    self.admitted.push(format!(
628                        "{}: op#{i} {op_name} — bounds guard elided by UNSOUND FORCED \
629                         ADMIT (SYNTH_FACT_SPEC_FORCE_ADMIT, red-team oracle lever, \
630                         debug builds only) — obligation was Sat (counterexample: \
631                         {cex}); NEVER use in production",
632                        self.func,
633                    ));
634                } else {
635                    self.decline(format!(
636                        "op#{i} {op_name} — bounds-guard obligation Sat (the access can \
637                         exceed the {} B declared-min memory under P; counterexample: \
638                         {cex}); guard retained",
639                        self.mem_bound,
640                    ));
641                }
642            }
643            CheckOutcome::Unknown(reason) => {
644                self.decline(format!(
645                    "op#{i} {op_name} — bounds-guard obligation Unknown ({reason}); \
646                     conservative decline, guard retained"
647                ));
648            }
649        }
650    }
651
652    /// Read the model back for an actionable counterexample string.
653    fn counterexample(&self, solver: &dyn crate::solver::BvSolver) -> String {
654        let cex: Vec<String> = self
655            .vars
656            .iter()
657            .filter_map(|v| {
658                let name = format!("{v}");
659                solver.value(v).map(|x| {
660                    if v.get_size() == 64 {
661                        format!("{name}={}", x as u64 as i64)
662                    } else {
663                        format!("{name}={}", x as u32 as i32)
664                    }
665                })
666            })
667            .collect();
668        if cex.is_empty() {
669            "<no model>".to_string()
670        } else {
671            cex.join(", ")
672        }
673    }
674
675    /// Discharge the per-elision obligation for the no-`else` `if` at `i`
676    /// (matching `End` at `end`, condition `cond`). Returns true iff admitted.
677    fn try_elide(&mut self, i: usize, end: usize, cond: &Val) -> bool {
678        if self.premises.is_empty() {
679            self.decline(format!(
680                "op#{i} `if` — no premise reaches this site (no usable value-range fact)"
681            ));
682            return false;
683        }
684        let mut solver = new_solver();
685        for p in &self.premises {
686            solver.assert(p);
687        }
688        let taken = cond.bv.ne(BV::from_i64(0, 32));
689        solver.assert(&taken);
690        match solver.check() {
691            CheckOutcome::Unsat => {
692                let Some(start) = cond.start else {
693                    // Proven dead, but the condition slice has a side effect
694                    // (`local.tee`) or is not provably contiguous — deleting
695                    // it could drop live work. Conservative: keep everything.
696                    self.decline(format!(
697                        "op#{i} `if` proven dead (UNSAT) but its condition slice is not \
698                         erasable (impure or non-contiguous producer)"
699                    ));
700                    return false;
701                };
702                self.deletions.push((start, end));
703                self.admitted.push(format!(
704                    "{}: op#{i} `if` (+condition slice) — ops [{start}..={end}] elided \
705                     ({} ops): UNSAT(P ∧ cond ≠ 0) via {} (certificate-checked QF_BV; \
706                     every Unsat carries an LRAT proof validated by ordeal-lrat); \
707                     P = {{{}}}; cond = {}",
708                    self.func,
709                    end - start + 1,
710                    solver.name(),
711                    self.premise_desc.join(" ∧ "),
712                    cond.bv,
713                ));
714                true
715            }
716            CheckOutcome::Sat => {
717                // Read the model back for an actionable counterexample.
718                let cex: Vec<String> = self
719                    .vars
720                    .iter()
721                    .filter_map(|v| {
722                        let name = format!("{v}");
723                        solver
724                            .value(v)
725                            .map(|x| format!("{name}={}", x as u32 as i32))
726                    })
727                    .collect();
728                self.decline(format!(
729                    "op#{i} `if` — obligation Sat (branch reachable under P; \
730                     counterexample: {})",
731                    if cex.is_empty() {
732                        "<no model>".to_string()
733                    } else {
734                        cex.join(", ")
735                    }
736                ));
737                false
738            }
739            CheckOutcome::Unknown(reason) => {
740                self.decline(format!(
741                    "op#{i} `if` — obligation Unknown ({reason}); conservative decline"
742                ));
743                false
744            }
745        }
746    }
747
748    /// Discharge the select-collapse obligation for the branchless `select`
749    /// at op `i`. The operands are `(val1, val2, cond)` in wasm select order
750    /// (`val1` deepest, `cond` on top); the runtime result is
751    /// `(cond != 0) ? val1 : val2`. When a value-range premise pins the
752    /// condition CONSTANT under `P` the select collapses to one operand — the
753    /// branchless sibling of the Phase-2 no-else `if` elision, and the shape
754    /// gust_mix's `clamp` actually uses (`max`/`min` via `select`).
755    ///
756    /// Two mutually exclusive obligations, both certificate-checked QF_BV.
757    /// `UNSAT(P ∧ cond ≠ 0)` means `cond` is always 0, so the result is `val2`
758    /// (delete `val1`'s producer slice plus the condition-slice-through-
759    /// `select`). `UNSAT(P ∧ cond == 0)` means `cond` is always non-zero, so
760    /// the result is `val1` (delete the `val2`+condition+`select` contiguous
761    /// slice). Anything else (Sat both ways = genuinely non-constant; Unknown;
762    /// an impure/non-contiguous erasable slice) DECLINES LOUDLY and the general
763    /// branchless select stands. Returns the surviving operand's `Val` on
764    /// admit, `None` on decline.
765    fn try_collapse_select(&mut self, i: usize, val1: &Val, val2: &Val, cond: &Val) -> Option<Val> {
766        if self.premises.is_empty() {
767            self.decline(format!(
768                "op#{i} select — no premise reaches this site (no usable value-range fact)"
769            ));
770            return None;
771        }
772        // The condition slice must be a pure, contiguous producer ending
773        // immediately before the `select` — otherwise deleting it could drop
774        // live work (`local.tee`) or leave a gap.
775        let Some(sc) = cond.start else {
776            self.decline(format!(
777                "op#{i} select — condition slice is impure or non-contiguous (not erasable)"
778            ));
779            return None;
780        };
781        if cond.created + 1 != i {
782            self.decline(format!(
783                "op#{i} select — condition is not produced immediately before the select \
784                 (non-contiguous)"
785            ));
786            return None;
787        }
788
789        // Obligation B first (the clamp shape: the guard condition is
790        // constant-FALSE, so the identity operand `val2` survives).
791        let mut solver = new_solver();
792        for p in &self.premises {
793            solver.assert(p);
794        }
795        solver.assert(&cond.bv.ne(BV::from_i64(0, 32)));
796        match solver.check() {
797            CheckOutcome::Unsat => {
798                // cond ≡ 0 ⇒ result = val2. Delete val1's slice AND the
799                // condition-slice-through-select. val2 (kept) sits between.
800                let Some(s1) = val1.start else {
801                    self.decline(format!(
802                        "op#{i} select proven false-arm (UNSAT cond ≠ 0) but the val1 slice \
803                         is not erasable (impure/non-contiguous) — collapse declined"
804                    ));
805                    return None;
806                };
807                if val1.created >= sc {
808                    self.decline(format!(
809                        "op#{i} select — val1 slice overlaps the condition slice; collapse \
810                         declined"
811                    ));
812                    return None;
813                }
814                self.deletions.push((s1, val1.created));
815                self.deletions.push((sc, i));
816                self.admitted.push(format!(
817                    "{}: op#{i} select — collapsed to the false-arm (val2): \
818                     UNSAT(P ∧ cond ≠ 0) via {} (certificate-checked QF_BV; every Unsat \
819                     carries an LRAT proof validated by ordeal-lrat); deleted val1 slice \
820                     [{s1}..={}] + condition/select [{sc}..={i}]; P = {{{}}}; cond = {}",
821                    self.func,
822                    solver.name(),
823                    val1.created,
824                    self.premise_desc.join(" ∧ "),
825                    cond.bv,
826                ));
827                return Some(val2.clone());
828            }
829            CheckOutcome::Sat => { /* cond can be non-zero — try obligation A */ }
830            CheckOutcome::Unknown(reason) => {
831                self.decline(format!(
832                    "op#{i} select — false-arm obligation Unknown ({reason}); conservative \
833                     decline"
834                ));
835                return None;
836            }
837        }
838
839        // Obligation A: cond ≡ non-zero ⇒ result = val1. Delete the
840        // val2+condition+select contiguous slice.
841        let mut solver = new_solver();
842        for p in &self.premises {
843            solver.assert(p);
844        }
845        solver.assert(&cond.bv.eq(BV::from_i64(0, 32)));
846        match solver.check() {
847            CheckOutcome::Unsat => {
848                let Some(s2) = val2.start else {
849                    self.decline(format!(
850                        "op#{i} select proven true-arm (UNSAT cond == 0) but the val2 slice \
851                         is not erasable (impure/non-contiguous) — collapse declined"
852                    ));
853                    return None;
854                };
855                if val2.created + 1 != sc {
856                    self.decline(format!(
857                        "op#{i} select — val2 slice not adjacent to the condition slice \
858                         (non-contiguous); collapse declined"
859                    ));
860                    return None;
861                }
862                // [s2 ..= i] is one contiguous pure range (val2 + cond + select).
863                self.deletions.push((s2, i));
864                self.admitted.push(format!(
865                    "{}: op#{i} select — collapsed to the true-arm (val1): \
866                     UNSAT(P ∧ cond == 0) via {} (certificate-checked QF_BV; every Unsat \
867                     carries an LRAT proof validated by ordeal-lrat); deleted val2/condition/\
868                     select [{s2}..={i}]; P = {{{}}}; cond = {}",
869                    self.func,
870                    solver.name(),
871                    self.premise_desc.join(" ∧ "),
872                    cond.bv,
873                ));
874                Some(val1.clone())
875            }
876            CheckOutcome::Sat => {
877                let cex = self.counterexample(solver.as_ref());
878                self.decline(format!(
879                    "op#{i} select — condition is not constant under P (both arms reachable; \
880                     counterexample: {cex}); branchless select retained"
881                ));
882                None
883            }
884            CheckOutcome::Unknown(reason) => {
885                self.decline(format!(
886                    "op#{i} select — true-arm obligation Unknown ({reason}); conservative \
887                     decline"
888                ));
889                None
890            }
891        }
892    }
893
894    /// #494 Phase 3+: redundant-mask (narrowing) elision. When the value `a`
895    /// is proven narrow enough that `a & b == a` on every P-admissible input
896    /// (`UNSAT(P ∧ (a & b) ≠ a)`), the `i32.and` is the identity — its mask
897    /// operand `b` and the `and` op itself are deleted and `a` flows through
898    /// unchanged. This is the "known-narrow-value ⇒ drop the mask" class: a
899    /// dissolved primitive gives LLVM no range on the value, so it keeps the
900    /// `and`/`uxtb`; the fact makes the mask provably dead. Only the common
901    /// wasm order (`value ; const-mask ; and`, keep `a`) is handled — anything
902    /// else DECLINES LOUDLY and the general `and` stands. Returns the surviving
903    /// operand's `Val` on admit (deletion recorded), `None` on decline.
904    fn try_elide_mask(&mut self, i: usize, a: &Val, b: &Val, result_bv: &BV) -> Option<Val> {
905        if self.premises.is_empty() {
906            self.decline(format!(
907                "op#{i} i32.and — no premise reaches this site (no usable value-range fact)"
908            ));
909            return None;
910        }
911        // The mask operand `b` must be a pure, contiguous producer sitting
912        // immediately between `a`'s slice and the `and` — otherwise deleting
913        // it could drop live work (`local.tee`) or leave a gap.
914        let Some(sb) = b.start else {
915            self.decline(format!(
916                "op#{i} i32.and — mask slice is impure or non-contiguous (not erasable)"
917            ));
918            return None;
919        };
920        if a.created + 1 != sb || b.created + 1 != i {
921            self.decline(format!(
922                "op#{i} i32.and — mask operand is not produced immediately before the and \
923                 (non-contiguous)"
924            ));
925            return None;
926        }
927        let mut solver = new_solver();
928        for p in &self.premises {
929            solver.assert(p);
930        }
931        // UNSAT(P ∧ (value & mask) ≠ value) ⇒ the mask never clears a bit of
932        // the value under P ⇒ the `and` is the identity, so the general and
933        // the specialized lowerings (result = value) agree on every P input.
934        solver.assert(&result_bv.ne(&a.bv));
935        match solver.check() {
936            CheckOutcome::Unsat => {
937                self.deletions.push((sb, i));
938                self.admitted.push(format!(
939                    "{}: op#{i} i32.and — redundant mask elided (value proven narrow): \
940                     UNSAT(P ∧ (value & mask) ≠ value) via {} (certificate-checked QF_BV; \
941                     every Unsat carries an LRAT proof validated by ordeal-lrat); deleted \
942                     mask/and [{sb}..={i}]; P = {{{}}}; value = {}",
943                    self.func,
944                    solver.name(),
945                    self.premise_desc.join(" ∧ "),
946                    a.bv,
947                ));
948                Some(a.clone())
949            }
950            CheckOutcome::Sat => {
951                let cex = self.counterexample(solver.as_ref());
952                self.decline(format!(
953                    "op#{i} i32.and — mask is not redundant under P (value can carry a bit \
954                     outside the mask; counterexample: {cex}); general and retained"
955                ));
956                None
957            }
958            CheckOutcome::Unknown(reason) => {
959                self.decline(format!(
960                    "op#{i} i32.and — mask-redundancy obligation Unknown ({reason}); \
961                     conservative decline, general and retained"
962                ));
963                None
964            }
965        }
966    }
967
968    /// #494 Phase 3+ (beyond-parity): constant-divisor `i32.rem_u` IDENTITY
969    /// elision. When the divisor is a *literal* `i32.const C` with `C != 0` and
970    /// the dividend `a` is proven small enough that `a rem_u C == a` on every
971    /// P-admissible input (`UNSAT(P ∧ (a bvurem C) ≠ a)`), the whole `rem_u`
972    /// dissolves to the identity — its const-divisor operand and the op itself
973    /// are deleted and `a` flows through unchanged.
974    ///
975    /// This is the ONE total subset of the "div/rem never deleted" rule: WASM
976    /// `i32.rem_u` traps ONLY on divisor == 0, and rem has no INT_MIN/-1
977    /// overflow (that's `div_s`). A LITERAL nonzero constant divisor makes the
978    /// op unconditionally total — no-trap comes from the literal directly, NOT
979    /// from P — so unlike the variable-divisor path (which only elides the
980    /// *guard* and keeps the op), the whole effect-free op is deletable exactly
981    /// like the redundant mask. A variable divisor proven nonzero would NOT
982    /// qualify (its zero-guard is a control-flow effect); only a syntactic
983    /// `i32.const C, C != 0` is accepted.
984    ///
985    /// clang cannot do this: a dissolved primitive gives LLVM no range on the
986    /// dividend, so `-Os` lowers `x % C` (non-pow2 C) to the full
987    /// reciprocal-multiply-subtract sequence (movw+movt+umull+lsr+mov+mls,
988    /// ~22 B on Thumb-2); the WASM-level fact makes the entire sequence dead.
989    ///
990    /// Only the common wasm order (`dividend ; i32.const C ; rem_u`) is handled;
991    /// anything else DECLINES LOUDLY and the general div/rem path stands.
992    /// Returns the surviving dividend's `Val` on admit (deletion recorded),
993    /// `None` on decline.
994    fn try_elide_const_rem(&mut self, i: usize, a: &Val, b: &Val, result_bv: &BV) -> Option<Val> {
995        // Divisor MUST be a syntactic literal `i32.const C` with `C != 0` — the
996        // no-trap obligation is discharged by the literal itself, never by P.
997        let WasmOp::I32Const(c) = self.ops[b.created] else {
998            self.decline(format!(
999                "op#{i} i32.rem_u — divisor is not a literal i32.const (a variable divisor \
1000                 carries a trapping zero-guard; the whole op is not effect-free — general \
1001                 rem_u retained)"
1002            ));
1003            return None;
1004        };
1005        if c == 0 {
1006            self.decline(format!(
1007                "op#{i} i32.rem_u — literal divisor is 0 (traps unconditionally); general \
1008                 rem_u retained"
1009            ));
1010            return None;
1011        }
1012        if self.premises.is_empty() {
1013            self.decline(format!(
1014                "op#{i} i32.rem_u — no premise reaches this site (no usable value-range fact \
1015                 on the dividend)"
1016            ));
1017            return None;
1018        }
1019        // The const-divisor operand `b` must be a pure, contiguous producer
1020        // sitting immediately between `a`'s slice and the `rem_u` — otherwise
1021        // deleting it could drop live work or leave a gap. (An `i32.const`
1022        // always has a pure single-op slice; this mirrors try_elide_mask.)
1023        let Some(sb) = b.start else {
1024            self.decline(format!(
1025                "op#{i} i32.rem_u — const-divisor slice is impure or non-contiguous \
1026                 (not erasable)"
1027            ));
1028            return None;
1029        };
1030        if a.created + 1 != sb || b.created + 1 != i {
1031            self.decline(format!(
1032                "op#{i} i32.rem_u — const divisor is not produced immediately before the \
1033                 rem_u (non-contiguous)"
1034            ));
1035            return None;
1036        }
1037        let mut solver = new_solver();
1038        for p in &self.premises {
1039            solver.assert(p);
1040        }
1041        // UNSAT(P ∧ (dividend bvurem C) ≠ dividend) ⇒ the modulo never changes
1042        // the dividend under P ⇒ the rem_u is the identity, so the general and
1043        // the specialized (result = dividend) lowerings agree on every P input.
1044        // `result_bv` is the real `a.bv.bvurem(C)` term (WasmSemantics), so the
1045        // check is non-vacuous.
1046        solver.assert(&result_bv.ne(&a.bv));
1047        match solver.check() {
1048            CheckOutcome::Unsat => {
1049                self.deletions.push((sb, i));
1050                self.admitted.push(format!(
1051                    "{}: op#{i} i32.rem_u — constant-divisor modulo elided to identity \
1052                     (dividend proven < divisor): divisor is literal i32.const {c} (≠0 ⇒ \
1053                     no trap), UNSAT(P ∧ (dividend bvurem {c}) ≠ dividend) via {} \
1054                     (certificate-checked QF_BV; every Unsat carries an LRAT proof validated \
1055                     by ordeal-lrat); deleted const/rem_u [{sb}..={i}]; P = {{{}}}; \
1056                     dividend = {}",
1057                    self.func,
1058                    solver.name(),
1059                    self.premise_desc.join(" ∧ "),
1060                    a.bv,
1061                ));
1062                Some(a.clone())
1063            }
1064            CheckOutcome::Sat => {
1065                let cex = self.counterexample(solver.as_ref());
1066                self.decline(format!(
1067                    "op#{i} i32.rem_u — modulo is not the identity under P (dividend can \
1068                     reach or exceed the divisor {c}; counterexample: {cex}); general rem_u \
1069                     retained"
1070                ));
1071                None
1072            }
1073            CheckOutcome::Unknown(reason) => {
1074                self.decline(format!(
1075                    "op#{i} i32.rem_u — identity obligation Unknown ({reason}); conservative \
1076                     decline, general rem_u retained"
1077                ));
1078                None
1079            }
1080        }
1081    }
1082
1083    fn walk(&mut self) {
1084        if self.range_facts.is_empty() && self.nonzero_facts.is_empty() {
1085            self.decline(
1086                "no usable value-range or divisor-nonzero fact targets this function".to_string(),
1087            );
1088            return;
1089        }
1090        let ops = self.ops;
1091        let mut i = 0usize;
1092        while i < ops.len() {
1093            let op = &ops[i];
1094            match op {
1095                WasmOp::Nop => {}
1096                WasmOp::I32Const(v) => {
1097                    let bv = self.sem.encode_op(&WasmOp::I32Const(*v), &[]);
1098                    self.attach_fact(i, &bv);
1099                    self.push(bv, Some(i), i);
1100                }
1101                // #494 phase 2b: tracked so an i64 divisor term can be built
1102                // (the i64 fragment is const/local/div-rem only — anything
1103                // else stops the walk as before).
1104                WasmOp::I64Const(v) => {
1105                    let bv = self.sem.encode_op(&WasmOp::I64Const(*v), &[]);
1106                    self.attach_fact(i, &bv);
1107                    self.push(bv, Some(i), i);
1108                }
1109                WasmOp::LocalGet(idx) => {
1110                    let bv = self.local_bv(*idx);
1111                    self.attach_fact(i, &bv);
1112                    self.push(bv, Some(i), i);
1113                }
1114                WasmOp::LocalSet(idx) => {
1115                    let Some(v) = self.stack.pop() else {
1116                        self.decline(format!("op#{i} local.set on empty symbolic stack"));
1117                        return;
1118                    };
1119                    self.locals.insert(*idx, v.bv);
1120                }
1121                WasmOp::LocalTee(idx) => {
1122                    let Some(top) = self.stack.last_mut() else {
1123                        self.decline(format!("op#{i} local.tee on empty symbolic stack"));
1124                        return;
1125                    };
1126                    // The tee is a side effect: its slice must never be
1127                    // deleted as a "pure condition producer".
1128                    top.start = None;
1129                    top.created = i;
1130                    let bv = top.bv.clone();
1131                    self.locals.insert(*idx, bv.clone());
1132                    self.attach_fact(i, &bv);
1133                }
1134                WasmOp::Drop => {
1135                    if self.stack.pop().is_none() {
1136                        self.decline(format!("op#{i} drop on empty symbolic stack"));
1137                        return;
1138                    }
1139                }
1140                WasmOp::I32Eqz => {
1141                    let Some(a) = self.stack.pop() else {
1142                        self.decline(format!("op#{i} unary op on empty symbolic stack"));
1143                        return;
1144                    };
1145                    if a.bv.get_size() != 32 {
1146                        self.decline(format!("op#{i} i32.eqz on a non-32-bit operand"));
1147                        return;
1148                    }
1149                    let bv = self.sem.encode_op(op, &[a.bv]);
1150                    self.attach_fact(i, &bv);
1151                    let start = a.start.filter(|_| a.created + 1 == i);
1152                    self.push(bv, start, i);
1153                }
1154                // #494 Phase 3+: `i32.and` — tracked like the other trap-free
1155                // binops, but additionally attempts the redundant-mask elision
1156                // (a proven-narrow value makes the mask the identity). A
1157                // NON-eliding `and` pushes the EXACT `(bv, start, i)` the
1158                // general binop arm below produces — so flag-off stays
1159                // byte-identical.
1160                WasmOp::I32And => {
1161                    let (Some(b), Some(a)) = (self.stack.pop(), self.stack.pop()) else {
1162                        self.decline(format!("op#{i} binop on underflowing symbolic stack"));
1163                        return;
1164                    };
1165                    if a.bv.get_size() != 32 || b.bv.get_size() != 32 {
1166                        self.decline(format!("op#{i} i32 binop on a non-32-bit operand"));
1167                        return;
1168                    }
1169                    let bv = self.sem.encode_op(op, &[a.bv.clone(), b.bv.clone()]);
1170                    self.attach_fact(i, &bv);
1171                    // The general binop arm's contiguity proof, verbatim.
1172                    let start = match (a.start, b.start) {
1173                        (Some(sa), Some(sb)) if a.created + 1 == sb && b.created + 1 == i => {
1174                            Some(sa)
1175                        }
1176                        _ => None,
1177                    };
1178                    match self.try_elide_mask(i, &a, &b, &bv) {
1179                        // Admitted: the mask/and slice was recorded for
1180                        // deletion; the surviving value flows through. `start =
1181                        // None` keeps the collapsed result out of any later
1182                        // erasable slice (conservative, like select-collapse).
1183                        Some(surviving) => self.push(surviving.bv, None, i),
1184                        // Declined (loud): the general `and` stands, pushed
1185                        // exactly as the general binop arm would.
1186                        None => self.push(bv, start, i),
1187                    }
1188                }
1189                // Tracked, trap-free i32 binops (div/rem excluded on purpose:
1190                // they can trap, and a deleted slice must be effect-free).
1191                WasmOp::I32Add
1192                | WasmOp::I32Sub
1193                | WasmOp::I32Mul
1194                | WasmOp::I32Or
1195                | WasmOp::I32Xor
1196                | WasmOp::I32Shl
1197                | WasmOp::I32ShrS
1198                | WasmOp::I32ShrU
1199                | WasmOp::I32Rotl
1200                | WasmOp::I32Rotr
1201                | WasmOp::I32Eq
1202                | WasmOp::I32Ne
1203                | WasmOp::I32LtS
1204                | WasmOp::I32LtU
1205                | WasmOp::I32LeS
1206                | WasmOp::I32LeU
1207                | WasmOp::I32GtS
1208                | WasmOp::I32GtU
1209                | WasmOp::I32GeS
1210                | WasmOp::I32GeU => {
1211                    let (Some(b), Some(a)) = (self.stack.pop(), self.stack.pop()) else {
1212                        self.decline(format!("op#{i} binop on underflowing symbolic stack"));
1213                        return;
1214                    };
1215                    if a.bv.get_size() != 32 || b.bv.get_size() != 32 {
1216                        self.decline(format!("op#{i} i32 binop on a non-32-bit operand"));
1217                        return;
1218                    }
1219                    let bv = self.sem.encode_op(op, &[a.bv, b.bv]);
1220                    self.attach_fact(i, &bv);
1221                    // Contiguity proof for the combined producer slice:
1222                    // a's slice, immediately followed by b's, immediately
1223                    // followed by this op. Anything else ⇒ not erasable.
1224                    let start = match (a.start, b.start) {
1225                        (Some(sa), Some(sb)) if a.created + 1 == sb && b.created + 1 == i => {
1226                            Some(sa)
1227                        }
1228                        _ => None,
1229                    };
1230                    self.push(bv, start, i);
1231                }
1232                // #494 phase 2b: i32/i64 div/rem — TRACKED (upgrading the
1233                // phase-2 hard stop), never DELETED. The op can trap, so its
1234                // result carries `start = None` (it can never sit inside an
1235                // erasable condition slice); the walk instead discharges the
1236                // per-site guard obligations (see the module docs' two-guard
1237                // distinction) and marks the op for the lowering. Downstream
1238                // soundness: if the op traps, nothing after it executes (any
1239                // later admitted elision is vacuous on that path); if it does
1240                // not, its result is havocked to a fresh variable.
1241                // #494 Phase 3+ (beyond-parity): `i32.rem_u` — tracked like the
1242                // other div/rem ops, but additionally attempts the
1243                // constant-divisor IDENTITY elision (a literal nonzero divisor
1244                // + a proven-narrow dividend makes the modulo the identity, and
1245                // the whole effect-free op is deletable — the ONE total subset
1246                // of the "div/rem never deleted" rule). A NON-eliding rem_u
1247                // takes the EXACT general div/rem path below (guard obligations
1248                // + havoc), so flag-off stays byte-identical.
1249                WasmOp::I32RemU => {
1250                    let (Some(b), Some(a)) = (self.stack.pop(), self.stack.pop()) else {
1251                        self.decline(format!("op#{i} div/rem on underflowing symbolic stack"));
1252                        return;
1253                    };
1254                    if a.bv.get_size() != 32 || b.bv.get_size() != 32 {
1255                        self.decline(format!(
1256                            "op#{i} i32.rem_u on operands of unexpected width (symbolic widths \
1257                             {}/{}, expected 32)",
1258                            a.bv.get_size(),
1259                            b.bv.get_size()
1260                        ));
1261                        return;
1262                    }
1263                    // The real bvurem term (WasmSemantics) — non-vacuous VC.
1264                    let bv = self.sem.encode_op(op, &[a.bv.clone(), b.bv.clone()]);
1265                    match self.try_elide_const_rem(i, &a, &b, &bv) {
1266                        // Admitted: the const/rem_u slice was recorded for
1267                        // deletion; the surviving dividend flows through.
1268                        // `start = None` keeps the collapsed result out of any
1269                        // later erasable slice (conservative, like the mask
1270                        // and select-collapse admits).
1271                        Some(surviving) => self.push(surviving.bv, None, i),
1272                        // Declined (loud): fall to the EXACT general div/rem
1273                        // path — discharge the guard obligations, then havoc.
1274                        None => {
1275                            self.try_elide_div_guards(i, "i32.rem_u", false, &a, &b);
1276                            let n = self.fresh;
1277                            self.fresh += 1;
1278                            let v = self.fresh_var(format!("fs_d{n}"));
1279                            self.attach_fact(i, &v);
1280                            self.push(v, None, i);
1281                        }
1282                    }
1283                }
1284                // #494 phase 2b: i32/i64 div/rem — TRACKED (upgrading the
1285                // phase-2 hard stop), never DELETED. The op can trap, so its
1286                // result carries `start = None` (it can never sit inside an
1287                // erasable condition slice); the walk instead discharges the
1288                // per-site guard obligations (see the module docs' two-guard
1289                // distinction) and marks the op for the lowering.
1290                WasmOp::I32DivU
1291                | WasmOp::I32DivS
1292                | WasmOp::I32RemS
1293                | WasmOp::I64DivU
1294                | WasmOp::I64DivS
1295                | WasmOp::I64RemU
1296                | WasmOp::I64RemS => {
1297                    let (Some(b), Some(a)) = (self.stack.pop(), self.stack.pop()) else {
1298                        self.decline(format!("op#{i} div/rem on underflowing symbolic stack"));
1299                        return;
1300                    };
1301                    let (op_name, expect, is_div_s) = match op {
1302                        WasmOp::I32DivU => ("i32.div_u", 32, false),
1303                        WasmOp::I32DivS => ("i32.div_s", 32, true),
1304                        WasmOp::I32RemS => ("i32.rem_s", 32, false),
1305                        WasmOp::I64DivU => ("i64.div_u", 64, false),
1306                        WasmOp::I64DivS => ("i64.div_s", 64, true),
1307                        WasmOp::I64RemU => ("i64.rem_u", 64, false),
1308                        _ => ("i64.rem_s", 64, false),
1309                    };
1310                    if a.bv.get_size() != expect || b.bv.get_size() != expect {
1311                        self.decline(format!(
1312                            "op#{i} {op_name} on operands of unexpected width                              (symbolic widths {}/{}, expected {expect})",
1313                            a.bv.get_size(),
1314                            b.bv.get_size()
1315                        ));
1316                        return;
1317                    }
1318                    self.try_elide_div_guards(i, op_name, is_div_s, &a, &b);
1319                    // Havoc the result; `start = None` keeps a possibly-
1320                    // trapping op out of every erasable condition slice.
1321                    let n = self.fresh;
1322                    self.fresh += 1;
1323                    let v = self.fresh_var(format!("fs_d{n}"));
1324                    self.attach_fact(i, &v);
1325                    self.push(v, None, i);
1326                }
1327                // #494 bounds-elision: i32 memory LOADS — TRACKED, never
1328                // DELETED (an OOB access traps, so the op is not effect-free;
1329                // same discipline as div/rem). The walk discharges the
1330                // per-site software bounds-guard obligation and marks the op
1331                // for the lowering; the loaded value is havocked to a fresh
1332                // variable (memory contents are not modeled) with
1333                // `start = None` (a possibly-trapping op never sits inside an
1334                // erasable condition slice). Downstream soundness: if the
1335                // access traps, nothing after it executes (any later admitted
1336                // elision is vacuous on that path).
1337                WasmOp::I32Load { offset, .. }
1338                | WasmOp::I32Load8S { offset, .. }
1339                | WasmOp::I32Load8U { offset, .. }
1340                | WasmOp::I32Load16S { offset, .. }
1341                | WasmOp::I32Load16U { offset, .. } => {
1342                    let Some(a) = self.stack.pop() else {
1343                        self.decline(format!("op#{i} memory load on empty symbolic stack"));
1344                        return;
1345                    };
1346                    if a.bv.get_size() != 32 {
1347                        self.decline(format!("op#{i} memory load on a non-32-bit index"));
1348                        return;
1349                    }
1350                    let (op_name, size) = match op {
1351                        WasmOp::I32Load { .. } => ("i32.load", 4),
1352                        WasmOp::I32Load8S { .. } => ("i32.load8_s", 1),
1353                        WasmOp::I32Load8U { .. } => ("i32.load8_u", 1),
1354                        WasmOp::I32Load16S { .. } => ("i32.load16_s", 2),
1355                        _ => ("i32.load16_u", 2),
1356                    };
1357                    self.try_elide_mem_bounds(i, op_name, size, *offset, &a);
1358                    // Havoc the loaded value; `start = None` keeps a possibly-
1359                    // trapping op out of every erasable condition slice.
1360                    let n = self.fresh;
1361                    self.fresh += 1;
1362                    let v = self.fresh_var(format!("fs_m{n}"));
1363                    self.attach_fact(i, &v);
1364                    self.push(v, None, i);
1365                }
1366                // #494 bounds-elision: i32 memory STORES — same obligation
1367                // over the index. No memory model is needed: loads are always
1368                // havocked, so a store's effect on the symbolic state is
1369                // vacuous; only the guard obligation is discharged.
1370                WasmOp::I32Store { offset, .. }
1371                | WasmOp::I32Store8 { offset, .. }
1372                | WasmOp::I32Store16 { offset, .. } => {
1373                    let (Some(val), Some(a)) = (self.stack.pop(), self.stack.pop()) else {
1374                        self.decline(format!(
1375                            "op#{i} memory store on underflowing symbolic stack"
1376                        ));
1377                        return;
1378                    };
1379                    if a.bv.get_size() != 32 || val.bv.get_size() != 32 {
1380                        self.decline(format!("op#{i} memory store on a non-32-bit operand"));
1381                        return;
1382                    }
1383                    let (op_name, size) = match op {
1384                        WasmOp::I32Store { .. } => ("i32.store", 4),
1385                        WasmOp::I32Store8 { .. } => ("i32.store8", 1),
1386                        _ => ("i32.store16", 2),
1387                    };
1388                    self.try_elide_mem_bounds(i, op_name, size, *offset, &a);
1389                    // Store pushes nothing.
1390                }
1391                WasmOp::If => {
1392                    let Some(cond) = self.stack.pop() else {
1393                        self.decline(format!("op#{i} `if` on empty symbolic stack"));
1394                        return;
1395                    };
1396                    if cond.bv.get_size() != 32 {
1397                        self.decline(format!("op#{i} `if` condition is not 32-bit"));
1398                        return;
1399                    }
1400                    let Some((end, has_else)) = self.matching_end(i) else {
1401                        self.decline(format!("op#{i} `if` without matching `end`"));
1402                        return;
1403                    };
1404                    let Some(&ord) = self.opener_ordinal.get(&i) else {
1405                        self.decline(format!("op#{i} `if` missing from the opener ordinal map"));
1406                        return;
1407                    };
1408                    let Some(&arity) = self.block_arity.get(ord) else {
1409                        self.decline(format!(
1410                            "op#{i} `if` has no block_arity entry (side-table desync)"
1411                        ));
1412                        return;
1413                    };
1414                    if has_else {
1415                        self.decline(format!(
1416                            "op#{i} `if`/`else` — only no-else `if` is in Phase-2 scope"
1417                        ));
1418                        self.havoc_region(i, end, arity);
1419                    } else if self.try_elide(i, end, &cond) {
1420                        // Region provably never executes: state unchanged
1421                        // (params-as-results pass-through is the identity for
1422                        // a no-else `if`, whose blocktype has equal
1423                        // param/result types by wasm validation).
1424                    } else {
1425                        self.havoc_region(i, end, arity);
1426                    }
1427                    i = end + 1;
1428                    continue;
1429                }
1430                // #494 Phase 3: branchless `select` — the sibling of the
1431                // Phase-2 no-else `if` elision, and the shape gust_mix's
1432                // clamp actually lowers to (`max`/`min` via select). A
1433                // value-range premise that pins the condition constant
1434                // collapses it to one operand (stream deletion, like `if`).
1435                WasmOp::Select => {
1436                    let (Some(cond), Some(val2), Some(val1)) =
1437                        (self.stack.pop(), self.stack.pop(), self.stack.pop())
1438                    else {
1439                        self.decline(format!("op#{i} select on underflowing symbolic stack"));
1440                        return;
1441                    };
1442                    // Only the plain i32 `select` (0x1B) is tracked; a typed
1443                    // select over i64/f-operands declines and havocs.
1444                    if cond.bv.get_size() != 32
1445                        || val1.bv.get_size() != 32
1446                        || val2.bv.get_size() != 32
1447                    {
1448                        self.decline(format!(
1449                            "op#{i} select on non-32-bit operand(s) — only i32 select is tracked"
1450                        ));
1451                        let v = self.fresh_var(format!("fs_sel{i}"));
1452                        self.push(v, None, i);
1453                        i += 1;
1454                        continue;
1455                    }
1456                    match self.try_collapse_select(i, &val1, &val2, &cond) {
1457                        // Admitted: the surviving operand's producer slice
1458                        // stays; the other operand + condition slice + the
1459                        // `select` were recorded for deletion. `start = None`
1460                        // keeps the collapsed result out of any later erasable
1461                        // slice (conservative).
1462                        Some(surviving) => self.push(surviving.bv, None, i),
1463                        // Declined (loud): the branchless select stands. Havoc
1464                        // the result — a fresh var means any obligation over it
1465                        // downstream is Sat, so it can never seed an unsound
1466                        // chained collapse.
1467                        None => {
1468                            let v = self.fresh_var(format!("fs_sel{i}"));
1469                            self.push(v, None, i);
1470                        }
1471                    }
1472                }
1473                // Function-final `End` (top-level): done.
1474                WasmOp::End => break,
1475                WasmOp::Return => break,
1476                other => {
1477                    // First op outside the tracked fragment: stop. Everything
1478                    // already admitted was justified independently of what
1479                    // follows; declining the REST loudly keeps honesty.
1480                    self.decline(format!(
1481                        "op#{i} {other:?} is outside the tracked i32 fragment — \
1482                         fact tracking stops here (no further elisions in this function)"
1483                    ));
1484                    return;
1485                }
1486            }
1487            i += 1;
1488        }
1489    }
1490
1491    fn finish(self) -> FactSpecResult {
1492        let Pass {
1493            ops,
1494            block_arity,
1495            deletions,
1496            admitted,
1497            declined,
1498            zero_marks,
1499            ovf_marks,
1500            mem_marks,
1501            ..
1502        } = self;
1503        if deletions.is_empty() {
1504            return FactSpecResult {
1505                ops: ops.to_vec(),
1506                block_arity: block_arity.to_vec(),
1507                kept: (0..ops.len()).collect(),
1508                admitted,
1509                declined,
1510                // No rewrite ⇒ original indices ARE the output indices.
1511                elide_div_zero: zero_marks,
1512                elide_div_ovf: ovf_marks,
1513                elide_mem_bounds: mem_marks,
1514                stream_changed: false,
1515            };
1516        }
1517        let deleted = |i: usize| deletions.iter().any(|&(s, e)| i >= s && i <= e);
1518        let mut out_ops = Vec::with_capacity(ops.len());
1519        let mut out_arity = Vec::with_capacity(block_arity.len());
1520        let mut kept = Vec::with_capacity(ops.len());
1521        let mut ord = 0usize;
1522        for (i, op) in ops.iter().enumerate() {
1523            let is_opener = matches!(op, WasmOp::Block | WasmOp::Loop | WasmOp::If);
1524            if !deleted(i) {
1525                out_ops.push(op.clone());
1526                kept.push(i);
1527                if is_opener && let Some(&a) = block_arity.get(ord) {
1528                    out_arity.push(a);
1529                }
1530            }
1531            if is_opener {
1532                ord += 1;
1533            }
1534        }
1535        // Remap the guard-elision marks into the REWRITTEN index space. A
1536        // marked div/rem can never sit inside a deleted range (deleted ranges
1537        // are contiguous PURE condition slices plus proven-dead `if` regions
1538        // the walk skipped over; a div result's `start = None` bars it from
1539        // any erasable slice) — the filter below is defense in depth.
1540        let remap = |marks: Vec<usize>| -> Vec<usize> {
1541            marks
1542                .into_iter()
1543                .filter_map(|m| {
1544                    debug_assert!(!deleted(m), "guard mark op#{m} inside a deleted range");
1545                    kept.binary_search(&m).ok()
1546                })
1547                .collect()
1548        };
1549        FactSpecResult {
1550            ops: out_ops,
1551            block_arity: out_arity,
1552            elide_div_zero: remap(zero_marks),
1553            elide_div_ovf: remap(ovf_marks),
1554            elide_mem_bounds: remap(mem_marks),
1555            kept,
1556            admitted,
1557            declined,
1558            stream_changed: true,
1559        }
1560    }
1561}
1562
1563#[cfg(test)]
1564mod tests {
1565    use super::*;
1566    use WasmOp::*;
1567
1568    fn fact(value_id: u32, lo: i64, hi: i64) -> WscFact {
1569        WscFact {
1570            func_index: 0,
1571            value_id,
1572            kind: FactKind::ValueRange { lo, hi },
1573        }
1574    }
1575
1576    /// The gust_mix clamp shape: clamp(ch + 476, 1000, 2000) via two
1577    /// no-else `if`s over a local.
1578    fn clamp_ops() -> Vec<WasmOp> {
1579        vec![
1580            LocalGet(0),    // 0   ch          ← fact target
1581            I32Const(476),  // 1
1582            I32Add,         // 2   v = ch+476
1583            LocalSet(1),    // 3
1584            LocalGet(1),    // 4
1585            I32Const(1000), // 5
1586            I32LtS,         // 6
1587            If,             // 7
1588            I32Const(1000), // 8
1589            LocalSet(1),    // 9
1590            End,            // 10
1591            LocalGet(1),    // 11
1592            I32Const(2000), // 12
1593            I32GtS,         // 13
1594            If,             // 14
1595            I32Const(2000), // 15
1596            LocalSet(1),    // 16
1597            End,            // 17
1598            LocalGet(1),    // 18
1599            End,            // 19
1600        ]
1601    }
1602
1603    const CLAMP_ARITY: &[(u8, u8)] = &[(0, 0), (0, 0)];
1604
1605    #[test]
1606    fn clamp_shape_elides_both_branches_under_the_proven_bound_494() {
1607        let ops = clamp_ops();
1608        let r = specialize_function("gust_mix", &ops, CLAMP_ARITY, &[fact(0, 524, 1524)], &[], 0);
1609        assert_eq!(r.admitted.len(), 2, "declines: {:?}", r.declined);
1610        assert!(r.changed());
1611        assert_eq!(
1612            r.ops,
1613            vec![
1614                LocalGet(0),
1615                I32Const(476),
1616                I32Add,
1617                LocalSet(1),
1618                LocalGet(1),
1619                End
1620            ],
1621            "both clamp comparisons + branches + bodies must be gone"
1622        );
1623        assert_eq!(r.block_arity, vec![], "both If arity entries removed");
1624        assert_eq!(r.kept, vec![0, 1, 2, 3, 18, 19]);
1625        // The certificate evidence trail names the engine and the premise.
1626        for line in &r.admitted {
1627            assert!(line.contains("UNSAT"), "{line}");
1628            assert!(line.contains("certificate-checked"), "{line}");
1629            assert!(line.contains("[524, 1524]"), "{line}");
1630        }
1631    }
1632
1633    #[test]
1634    fn wrong_wide_bound_is_sat_and_declines_loudly_494() {
1635        // ch ∈ [0, 4000] does NOT make the clamp dead (ch=0 → v=476 < 1000):
1636        // the obligation is Sat and BOTH sites decline with a counterexample.
1637        let ops = clamp_ops();
1638        let r = specialize_function("gust_mix", &ops, CLAMP_ARITY, &[fact(0, 0, 4000)], &[], 0);
1639        assert_eq!(r.admitted.len(), 0);
1640        assert!(!r.changed());
1641        assert_eq!(r.ops, ops, "declined ⇒ byte-identical op stream");
1642        assert_eq!(r.block_arity, CLAMP_ARITY.to_vec());
1643        assert!(
1644            r.declined
1645                .iter()
1646                .any(|d| d.contains("Sat") && d.contains("counterexample")),
1647            "declines must be loud and carry a model: {:?}",
1648            r.declined
1649        );
1650    }
1651
1652    #[test]
1653    fn partially_dead_bound_elides_only_the_proven_branch_494() {
1654        // ch ∈ [524, 4000]: v ≥ 1000 so the LOW clamp is dead, but v can
1655        // exceed 2000 so the HIGH clamp must survive.
1656        let ops = clamp_ops();
1657        let r = specialize_function("gust_mix", &ops, CLAMP_ARITY, &[fact(0, 524, 4000)], &[], 0);
1658        assert_eq!(r.admitted.len(), 1, "declines: {:?}", r.declined);
1659        assert_eq!(r.declined.len(), 1);
1660        assert_eq!(
1661            r.ops,
1662            vec![
1663                LocalGet(0),
1664                I32Const(476),
1665                I32Add,
1666                LocalSet(1),
1667                LocalGet(1),
1668                I32Const(2000),
1669                I32GtS,
1670                If,
1671                I32Const(2000),
1672                LocalSet(1),
1673                End,
1674                LocalGet(1),
1675                End,
1676            ]
1677        );
1678        assert_eq!(r.block_arity, vec![(0, 0)], "one If survives");
1679    }
1680
1681    #[test]
1682    fn no_facts_changes_nothing_494() {
1683        let ops = clamp_ops();
1684        let r = specialize_function("gust_mix", &ops, CLAMP_ARITY, &[], &[], 0);
1685        assert!(!r.changed());
1686        assert_eq!(r.ops, ops);
1687        assert!(!r.declined.is_empty(), "the no-fact case is a loud decline");
1688    }
1689
1690    // ---- #494 Phase 3: branchless select-collapse ----
1691
1692    /// gust_mix's clamp lowered branchlessly via `select` (the shape LLVM
1693    /// emits): `max(v,1000)` = `(v<1000)?1000:v`, `min(v,2000)` =
1694    /// `(v>2000)?2000:v`. No `If`/`End` — no block_arity entries.
1695    fn select_clamp_ops() -> Vec<WasmOp> {
1696        vec![
1697            LocalGet(0),    // 0   ch          ← fact target
1698            I32Const(476),  // 1
1699            I32Add,         // 2   v = ch+476
1700            LocalSet(1),    // 3
1701            I32Const(1000), // 4   val1 (low clamp)
1702            LocalGet(1),    // 5   val2 = v
1703            LocalGet(1),    // 6   cond slice
1704            I32Const(1000), // 7
1705            I32LtS,         // 8   cond = v < 1000
1706            Select,         // 9   → max(v,1000)
1707            LocalSet(1),    // 10
1708            I32Const(2000), // 11  val1 (high clamp)
1709            LocalGet(1),    // 12  val2 = v
1710            LocalGet(1),    // 13  cond slice
1711            I32Const(2000), // 14
1712            I32GtS,         // 15  cond = v > 2000
1713            Select,         // 16  → min(v,2000) = result
1714            End,            // 17
1715        ]
1716    }
1717
1718    #[test]
1719    fn select_clamp_collapses_both_selects_under_the_proven_bound_494() {
1720        let ops = select_clamp_ops();
1721        let r = specialize_function("gust_mix", &ops, &[], &[fact(0, 524, 1524)], &[], 0);
1722        assert_eq!(r.admitted.len(), 2, "declines: {:?}", r.declined);
1723        assert!(r.changed());
1724        assert_eq!(
1725            r.ops,
1726            vec![
1727                LocalGet(0),
1728                I32Const(476),
1729                I32Add,
1730                LocalSet(1),
1731                LocalGet(1), // val2 of select 1 (identity survives)
1732                LocalSet(1),
1733                LocalGet(1), // val2 of select 2 (identity survives)
1734                End,
1735            ],
1736            "both branchless clamps must collapse to the identity operand"
1737        );
1738        assert_eq!(r.kept, vec![0, 1, 2, 3, 5, 10, 12, 17]);
1739        for line in &r.admitted {
1740            assert!(line.contains("UNSAT"), "{line}");
1741            assert!(line.contains("certificate-checked"), "{line}");
1742            assert!(line.contains("select"), "{line}");
1743            assert!(line.contains("[524, 1524]"), "{line}");
1744        }
1745    }
1746
1747    #[test]
1748    fn select_clamp_wrong_bound_is_sat_and_declines_byte_identically_494() {
1749        // ch ∈ [0, 4000]: ch=0 → v=476 < 1000 so the low clamp genuinely
1750        // fires; both selects are non-constant ⇒ loud Sat decline, no rewrite.
1751        let ops = select_clamp_ops();
1752        let r = specialize_function("gust_mix", &ops, &[], &[fact(0, 0, 4000)], &[], 0);
1753        assert_eq!(r.admitted.len(), 0);
1754        assert!(!r.changed());
1755        assert_eq!(r.ops, ops, "declined ⇒ byte-identical op stream");
1756        assert!(
1757            r.declined
1758                .iter()
1759                .any(|d| d.contains("not constant") && d.contains("counterexample")),
1760            "declines must be loud and carry a model: {:?}",
1761            r.declined
1762        );
1763    }
1764
1765    #[test]
1766    fn select_collapses_to_true_arm_when_condition_proven_nonzero_494() {
1767        // result = cond ? val1 : val2 with cond ≡ 1 (fact ∈ [1,1]) ⇒ keep val1.
1768        let ops = vec![
1769            I32Const(111), // 0  val1
1770            I32Const(222), // 1  val2
1771            LocalGet(0),   // 2  cond ← fact ∈ [1,1] (always non-zero)
1772            Select,        // 3  → val1
1773            End,           // 4
1774        ];
1775        let r = specialize_function("f", &ops, &[], &[fact(2, 1, 1)], &[], 0);
1776        assert_eq!(r.admitted.len(), 1, "declines: {:?}", r.declined);
1777        assert_eq!(r.ops, vec![I32Const(111), End]);
1778        assert!(
1779            r.admitted[0].contains("true-arm") && r.admitted[0].contains("cond == 0"),
1780            "{}",
1781            r.admitted[0]
1782        );
1783    }
1784
1785    #[test]
1786    fn select_without_constraining_premise_declines_no_false_collapse_494() {
1787        // A TRUE ValueRange fact exists (so the walk runs) but targets val1,
1788        // not the condition; the select's condition carries no premise ⇒
1789        // non-constant ⇒ Sat decline, byte-identical.
1790        let ops = vec![
1791            I32Const(111), // 0  val1 ← fact ∈ [111,111] (true, non-constraining)
1792            I32Const(222), // 1  val2
1793            LocalGet(0),   // 2  cond — unconstrained
1794            Select,        // 3
1795            End,           // 4
1796        ];
1797        let r = specialize_function("f", &ops, &[], &[fact(0, 111, 111)], &[], 0);
1798        assert_eq!(r.admitted.len(), 0);
1799        assert!(!r.changed());
1800        assert_eq!(r.ops, ops);
1801    }
1802
1803    #[test]
1804    fn declined_if_havocs_its_locals_no_false_admit_downstream_494() {
1805        // The FIRST if is undecidable (condition on an unconstrained local),
1806        // and its body rewrites local 1 — so the SECOND if (which would be
1807        // dead under the fact alone) must NOT be admitted: local 1 is
1808        // havocked by the declined region.
1809        let ops = vec![
1810            LocalGet(0),    // 0  ← fact ch ∈ [524, 1524]
1811            I32Const(476),  // 1
1812            I32Add,         // 2
1813            LocalSet(1),    // 3
1814            LocalGet(2),    // 4  unconstrained
1815            If,             // 5
1816            I32Const(-9),   // 6
1817            LocalSet(1),    // 7  havocs local 1
1818            End,            // 8
1819            LocalGet(1),    // 9
1820            I32Const(2000), // 10
1821            I32GtS,         // 11
1822            If,             // 12
1823            I32Const(2000), // 13
1824            LocalSet(1),    // 14
1825            End,            // 15
1826            LocalGet(1),    // 16
1827            End,            // 17
1828        ];
1829        let r = specialize_function("f", &ops, CLAMP_ARITY, &[fact(0, 524, 1524)], &[], 0);
1830        assert_eq!(
1831            r.admitted.len(),
1832            0,
1833            "havocked local must block the downstream elision: {:?}",
1834            r.admitted
1835        );
1836        assert_eq!(r.ops, ops);
1837    }
1838
1839    #[test]
1840    fn if_with_else_declines_494() {
1841        let ops = vec![
1842            LocalGet(0), // 0 ← fact forces cond = 0
1843            If,          // 1
1844            I32Const(1), // 2
1845            LocalSet(1), // 3
1846            Else,        // 4
1847            I32Const(2), // 5
1848            LocalSet(1), // 6
1849            End,         // 7
1850            LocalGet(1), // 8
1851            End,         // 9
1852        ];
1853        let r = specialize_function("f", &ops, &[(0, 0)], &[fact(0, 0, 0)], &[], 0);
1854        assert_eq!(r.admitted.len(), 0);
1855        assert!(
1856            r.declined.iter().any(|d| d.contains("else")),
1857            "{:?}",
1858            r.declined
1859        );
1860        assert_eq!(r.ops, ops);
1861    }
1862
1863    #[test]
1864    fn nested_opener_inside_elided_body_fixes_block_arity_ordinals_494() {
1865        // A dead outer if contains a nested if: BOTH arity entries vanish and
1866        // the SURVIVING later block keeps its (translated) entry.
1867        let ops = vec![
1868            LocalGet(0), // 0 ← fact [5,5] ⇒ eqz = 0
1869            I32Eqz,      // 1
1870            If,          // 2   (ordinal 0)
1871            LocalGet(0), // 3
1872            If,          // 4   (ordinal 1, nested)
1873            I32Const(7), // 5
1874            LocalSet(1), // 6
1875            End,         // 7
1876            End,         // 8
1877            Block,       // 9   (ordinal 2, survives)
1878            End,         // 10
1879            End,         // 11
1880        ];
1881        let arity = &[(0, 0), (0, 0), (0, 1)];
1882        let r = specialize_function("f", &ops, arity, &[fact(0, 5, 5)], &[], 0);
1883        assert_eq!(r.admitted.len(), 1, "declines: {:?}", r.declined);
1884        // The condition slice starts at op 0 (LocalGet feeds the eqz), so the
1885        // whole deleted range is [0..=8]; only the trailing block survives.
1886        assert_eq!(r.ops, vec![Block, End, End]);
1887        assert_eq!(r.kept, vec![9, 10, 11]);
1888        assert_eq!(
1889            r.block_arity,
1890            vec![(0, 1)],
1891            "only the surviving Block's entry"
1892        );
1893    }
1894
1895    #[test]
1896    fn tee_condition_slice_is_not_erasable_494() {
1897        // cond built through local.tee: proven dead, but deleting the slice
1898        // would lose the local write ⇒ decline (loud), stream unchanged.
1899        let ops = vec![
1900            LocalGet(0), // 0 ← fact [1,1]
1901            LocalTee(1), // 1  side effect in the slice
1902            I32Eqz,      // 2  = 0 under the fact
1903            If,          // 3
1904            I32Const(9), // 4
1905            LocalSet(2), // 5
1906            End,         // 6
1907            End,         // 7
1908        ];
1909        let r = specialize_function("f", &ops, &[(0, 0)], &[fact(0, 1, 1)], &[], 0);
1910        assert_eq!(r.admitted.len(), 0);
1911        assert!(
1912            r.declined
1913                .iter()
1914                .any(|d| d.contains("not") && d.contains("erasable")),
1915            "{:?}",
1916            r.declined
1917        );
1918        assert_eq!(r.ops, ops);
1919    }
1920
1921    #[test]
1922    fn untracked_op_stops_tracking_loudly_494() {
1923        let ops = vec![
1924            LocalGet(0),   // 0 ← fact
1925            I64ExtendI32S, // 1  untracked ⇒ stop
1926            Drop,          // 2
1927            End,           // 3
1928        ];
1929        let r = specialize_function("f", &ops, &[], &[fact(0, 1, 2)], &[], 0);
1930        assert!(!r.changed());
1931        assert!(
1932            r.declined.iter().any(|d| d.contains("outside the tracked")),
1933            "{:?}",
1934            r.declined
1935        );
1936    }
1937
1938    fn nonzero_fact(value_id: u32) -> WscFact {
1939        WscFact {
1940            func_index: 0,
1941            value_id,
1942            kind: FactKind::DivisorNonZero,
1943        }
1944    }
1945
1946    // ================= #494 phase 2b: div/rem trap-guard elision =================
1947
1948    #[test]
1949    fn divisor_range_excluding_zero_elides_zero_guard_all_rem_div_494() {
1950        // div_u, rem_u, rem_s by a param divisor proven ∈ [1, 100]: every
1951        // zero guard falls to UNSAT(P ∧ divisor == 0); the stream itself is
1952        // untouched (marks only).
1953        let ops = vec![
1954            LocalGet(0), // 0  n
1955            LocalGet(1), // 1  d  ← fact [1,100]
1956            I32DivU,     // 2  → zero mark
1957            Drop,        // 3
1958            LocalGet(0), // 4
1959            LocalGet(1), // 5  ← fact [1,100]
1960            I32RemU,     // 6  → zero mark
1961            Drop,        // 7
1962            LocalGet(0), // 8
1963            LocalGet(1), // 9  ← fact [1,100]
1964            I32RemS,     // 10 → zero mark
1965            End,         // 11
1966        ];
1967        let facts = [fact(1, 1, 100), fact(5, 1, 100), fact(9, 1, 100)];
1968        let r = specialize_function("f", &ops, &[], &facts, &[], 0);
1969        assert_eq!(
1970            r.elide_div_zero,
1971            vec![2, 6, 10],
1972            "declines: {:?}",
1973            r.declined
1974        );
1975        assert_eq!(
1976            r.elide_div_ovf,
1977            Vec::<usize>::new(),
1978            "no div_s in the stream"
1979        );
1980        assert!(!r.changed(), "guard marks never rewrite the op stream");
1981        assert_eq!(r.ops, ops);
1982        assert_eq!(r.admitted.len(), 3);
1983        for line in &r.admitted {
1984            assert!(line.contains("divide-by-zero guard elided"), "{line}");
1985            assert!(line.contains("UNSAT(P ∧ divisor == 0)"), "{line}");
1986            assert!(line.contains("certificate-checked"), "{line}");
1987        }
1988    }
1989
1990    #[test]
1991    fn nonzero_fact_elides_zero_guard_but_retains_div_s_overflow_guard_494() {
1992        // THE TWO-GUARD DISTINCTION (#633/#634): a divisor-nonzero fact (kind
1993        // 3) discharges UNSAT(P ∧ divisor == 0) but NOT the overflow
1994        // obligation — divisor ≠ 0 still admits divisor == -1 with dividend
1995        // == INT_MIN, so the overflow guard is RETAINED with a loud decline.
1996        let ops = vec![
1997            LocalGet(0), // 0
1998            LocalGet(1), // 1 ← divisor-nonzero fact
1999            I32DivS,     // 2
2000            End,         // 3
2001        ];
2002        let r = specialize_function("f", &ops, &[], &[nonzero_fact(1)], &[], 0);
2003        assert_eq!(r.elide_div_zero, vec![2], "declines: {:?}", r.declined);
2004        assert_eq!(
2005            r.elide_div_ovf,
2006            Vec::<usize>::new(),
2007            "divisor ≠ 0 must NOT elide the INT_MIN/-1 overflow guard"
2008        );
2009        assert!(
2010            r.declined
2011                .iter()
2012                .any(|d| d.contains("overflow-guard obligation Sat") && d.contains("RETAINED")),
2013            "{:?}",
2014            r.declined
2015        );
2016    }
2017
2018    #[test]
2019    fn positive_range_discharges_both_div_s_obligations_494() {
2020        // divisor ∈ [1, 100] excludes BOTH 0 and -1 — the two obligations
2021        // are discharged independently and both guards fall.
2022        let ops = vec![LocalGet(0), LocalGet(1), I32DivS, End];
2023        let r = specialize_function("f", &ops, &[], &[fact(1, 1, 100)], &[], 0);
2024        assert_eq!(r.elide_div_zero, vec![2], "declines: {:?}", r.declined);
2025        assert_eq!(r.elide_div_ovf, vec![2]);
2026        assert_eq!(r.admitted.len(), 2, "one certificate line per obligation");
2027        assert!(
2028            r.admitted
2029                .iter()
2030                .any(|a| a.contains("overflow guard elided")
2031                    && a.contains("dividend == INT32_MIN ∧ divisor == -1")),
2032            "{:?}",
2033            r.admitted
2034        );
2035    }
2036
2037    #[test]
2038    fn range_including_zero_is_sat_and_declines_the_zero_guard_494() {
2039        // divisor ∈ [0, 100]: divisor == 0 is P-admissible — the obligation
2040        // is Sat, the decline is loud and carries a model, no mark is set.
2041        let ops = vec![LocalGet(0), LocalGet(1), I32DivU, End];
2042        let r = specialize_function("f", &ops, &[], &[fact(1, 0, 100)], &[], 0);
2043        assert_eq!(r.elide_div_zero, Vec::<usize>::new());
2044        assert!(
2045            r.declined
2046                .iter()
2047                .any(|d| d.contains("zero-guard obligation Sat") && d.contains("counterexample")),
2048            "{:?}",
2049            r.declined
2050        );
2051    }
2052
2053    #[test]
2054    fn i64_div_s_nonzero_fact_zero_guard_only_overflow_retained_494() {
2055        // Oracle 5 at the pass level: i64.div_s with an i64 param divisor
2056        // carrying a divisor-nonzero fact — the zero guard is proven dead,
2057        // the INT64_MIN/-1 overflow guard (#633/#634) is RETAINED.
2058        let ops = vec![LocalGet(0), LocalGet(1), I64DivS, End];
2059        let r = specialize_function("f", &ops, &[], &[nonzero_fact(1)], &[true, true], 0);
2060        assert_eq!(r.elide_div_zero, vec![2], "declines: {:?}", r.declined);
2061        assert_eq!(
2062            r.elide_div_ovf,
2063            Vec::<usize>::new(),
2064            "i64 overflow guard retained"
2065        );
2066        assert!(
2067            r.declined.iter().any(|d| d.contains("RETAINED")),
2068            "{:?}",
2069            r.declined
2070        );
2071    }
2072
2073    #[test]
2074    fn i64_div_s_positive_range_discharges_both_obligations_494() {
2075        let ops = vec![LocalGet(0), LocalGet(1), I64DivS, End];
2076        let r = specialize_function("f", &ops, &[], &[fact(1, 1, 1000)], &[true, true], 0);
2077        assert_eq!(r.elide_div_zero, vec![2], "declines: {:?}", r.declined);
2078        assert_eq!(r.elide_div_ovf, vec![2]);
2079    }
2080
2081    #[test]
2082    fn i64_div_on_undeclared_width_declines_no_marks_494() {
2083        // Without the params_i64 table the divisor local is symbolically
2084        // 32-bit — the width check declines rather than building a
2085        // wrong-width obligation.
2086        let ops = vec![LocalGet(0), LocalGet(1), I64DivU, End];
2087        let r = specialize_function("f", &ops, &[], &[nonzero_fact(1)], &[], 0);
2088        assert_eq!(r.elide_div_zero, Vec::<usize>::new());
2089        assert!(
2090            r.declined.iter().any(|d| d.contains("unexpected width")),
2091            "{:?}",
2092            r.declined
2093        );
2094    }
2095
2096    #[test]
2097    fn div_with_no_premise_declines_loudly_494() {
2098        // The function carries a fact, but no premise reaches the divisor —
2099        // the obligation cannot even be posed; both guards stay.
2100        let ops = vec![
2101            LocalGet(0), // 0 ← fact on the DIVIDEND, not the divisor
2102            LocalGet(1), // 1 unconstrained divisor
2103            I32DivU,     // 2
2104            End,         // 3
2105        ];
2106        // A fact on op 0 (the dividend): premises exist but do not constrain
2107        // the divisor — Sat, decline.
2108        let r = specialize_function("f", &ops, &[], &[fact(0, 1, 5)], &[], 0);
2109        assert_eq!(r.elide_div_zero, Vec::<usize>::new());
2110        assert!(
2111            r.declined
2112                .iter()
2113                .any(|d| d.contains("zero-guard obligation Sat")),
2114            "{:?}",
2115            r.declined
2116        );
2117    }
2118
2119    #[test]
2120    fn guard_marks_are_remapped_through_a_clamp_elision_494() {
2121        // A clamp elision rewrites the stream; a downstream div's mark must
2122        // land on the REWRITTEN index (the driver feeds the rewritten stream
2123        // to the selector, which keys guards by its own op index).
2124        let ops = vec![
2125            LocalGet(0),    // 0  ← fact [524, 1524]
2126            I32Const(476),  // 1
2127            I32Add,         // 2
2128            LocalSet(1),    // 3
2129            LocalGet(1),    // 4  -+ low clamp (elided 4..=10)
2130            I32Const(1000), // 5   |
2131            I32LtS,         // 6   |
2132            If,             // 7   |
2133            I32Const(1000), // 8   |
2134            LocalSet(1),    // 9   |
2135            End,            // 10 -+
2136            LocalGet(1),    // 11
2137            LocalGet(0),    // 12  divisor = ch ∈ [524, 1524] ⇒ nonzero
2138            I32DivU,        // 13  → zero mark (rewritten index 6)
2139            End,            // 14
2140        ];
2141        let r = specialize_function("f", &ops, &[(0, 0)], &[fact(0, 524, 1524)], &[], 0);
2142        assert!(r.changed(), "declines: {:?}", r.declined);
2143        assert_eq!(r.kept, vec![0, 1, 2, 3, 11, 12, 13, 14]);
2144        assert_eq!(
2145            r.ops,
2146            vec![
2147                LocalGet(0),
2148                I32Const(476),
2149                I32Add,
2150                LocalSet(1),
2151                LocalGet(1),
2152                LocalGet(0),
2153                I32DivU,
2154                End
2155            ]
2156        );
2157        assert_eq!(
2158            r.elide_div_zero,
2159            vec![6],
2160            "mark remapped from original op#13 to rewritten op#6"
2161        );
2162    }
2163
2164    // ============ #494 Phase 3+: redundant-mask (narrowing) elision ============
2165
2166    /// A representative dissolved DSP kernel: pack two proven-11-bit lanes,
2167    /// `lo | (hi << 11)`. Both `& 0x7FF` masks are redundant under the lane
2168    /// bounds — LLVM keeps them (no range on the params); the facts drop them.
2169    fn pack_lanes_ops() -> Vec<WasmOp> {
2170        vec![
2171            LocalGet(0),     // 0  lo    ← fact [0, 2047]
2172            I32Const(0x7FF), // 1
2173            I32And,          // 2  lo & 0x7FF  (redundant)
2174            LocalGet(1),     // 3  hi    ← fact [0, 2047]
2175            I32Const(0x7FF), // 4
2176            I32And,          // 5  hi & 0x7FF  (redundant)
2177            I32Const(11),    // 6
2178            I32Shl,          // 7  hi << 11
2179            I32Or,           // 8  lo | (hi << 11)
2180            End,             // 9
2181        ]
2182    }
2183
2184    #[test]
2185    fn narrow_value_elides_redundant_mask_494() {
2186        // lo, hi ∈ [0, 2047] ⇒ `x & 0x7FF == x`: both masks fall to
2187        // UNSAT(P ∧ (value & mask) ≠ value); each deletes its `const;and` pair.
2188        let ops = pack_lanes_ops();
2189        let r = specialize_function(
2190            "gust_kernel",
2191            &ops,
2192            &[],
2193            &[fact(0, 0, 2047), fact(3, 0, 2047)],
2194            &[],
2195            0,
2196        );
2197        assert_eq!(r.admitted.len(), 2, "declines: {:?}", r.declined);
2198        assert!(r.changed());
2199        assert_eq!(
2200            r.ops,
2201            vec![
2202                LocalGet(0), // lo flows through the elided mask
2203                LocalGet(1), // hi flows through the elided mask
2204                I32Const(11),
2205                I32Shl,
2206                I32Or,
2207                End,
2208            ],
2209            "both redundant masks must be gone, the arithmetic intact"
2210        );
2211        assert_eq!(r.kept, vec![0, 3, 6, 7, 8, 9]);
2212        for line in &r.admitted {
2213            assert!(line.contains("UNSAT(P ∧ (value & mask) ≠ value)"), "{line}");
2214            assert!(line.contains("certificate-checked"), "{line}");
2215            assert!(line.contains("redundant mask elided"), "{line}");
2216        }
2217    }
2218
2219    #[test]
2220    fn wide_bound_makes_mask_live_and_declines_byte_identically_494() {
2221        // lo ∈ [0, 0xFFF]: value 0x800 has bit 11 set, OUTSIDE the 0x7FF mask,
2222        // so `x & 0x7FF != x` is Sat — the mask is genuinely live. BOTH sites
2223        // decline loudly with a counterexample; the stream is byte-identical.
2224        let ops = pack_lanes_ops();
2225        let r = specialize_function(
2226            "gust_kernel",
2227            &ops,
2228            &[],
2229            &[fact(0, 0, 0xFFF), fact(3, 0, 0xFFF)],
2230            &[],
2231            0,
2232        );
2233        assert_eq!(r.admitted.len(), 0);
2234        assert!(!r.changed());
2235        assert_eq!(r.ops, ops, "declined ⇒ byte-identical op stream");
2236        assert!(
2237            r.declined
2238                .iter()
2239                .any(|d| d.contains("not redundant") && d.contains("counterexample")),
2240            "declines must be loud and carry a model: {:?}",
2241            r.declined
2242        );
2243    }
2244
2245    #[test]
2246    fn mask_without_constraining_premise_declines_no_false_elision_494() {
2247        // A TRUE ValueRange fact exists (so the walk runs) but targets the mask
2248        // const, not the value; the masked value carries no premise ⇒ the
2249        // obligation is Sat ⇒ loud decline, byte-identical.
2250        let ops = vec![
2251            LocalGet(0),     // 0  value — unconstrained
2252            I32Const(0x7FF), // 1  mask ← fact [0x7FF, 0x7FF] (true, non-constraining)
2253            I32And,          // 2
2254            End,             // 3
2255        ];
2256        let r = specialize_function("f", &ops, &[], &[fact(1, 0x7FF, 0x7FF)], &[], 0);
2257        assert_eq!(r.admitted.len(), 0);
2258        assert!(!r.changed());
2259        assert_eq!(r.ops, ops);
2260    }
2261
2262    #[test]
2263    fn signed_narrow_bound_that_admits_negative_keeps_the_mask_494() {
2264        // value ∈ [-1, 2047]: -1 is all-ones, so `-1 & 0x7FF = 0x7FF != -1` —
2265        // the obligation is Sat and the mask is (correctly) retained. Guards
2266        // against a naive "hi ≤ mask" shortcut that ignores the sign bit.
2267        let ops = vec![
2268            LocalGet(0),     // 0  ← fact [-1, 2047]
2269            I32Const(0x7FF), // 1
2270            I32And,          // 2
2271            End,             // 3
2272        ];
2273        let r = specialize_function("f", &ops, &[], &[fact(0, -1, 2047)], &[], 0);
2274        assert_eq!(
2275            r.admitted.len(),
2276            0,
2277            "a negative value fails the mask identity"
2278        );
2279        assert!(!r.changed());
2280        assert_eq!(r.ops, ops);
2281    }
2282
2283    #[test]
2284    fn mask_elision_no_facts_changes_nothing_494() {
2285        let ops = pack_lanes_ops();
2286        let r = specialize_function("gust_kernel", &ops, &[], &[], &[], 0);
2287        assert!(!r.changed());
2288        assert_eq!(r.ops, ops);
2289        assert!(!r.declined.is_empty(), "the no-fact case is a loud decline");
2290    }
2291
2292    #[test]
2293    fn out_of_range_value_id_is_vacuous_494() {
2294        let ops = clamp_ops();
2295        let r = specialize_function("f", &ops, CLAMP_ARITY, &[fact(999, 524, 1524)], &[], 0);
2296        assert!(!r.changed());
2297        assert_eq!(r.ops, ops);
2298    }
2299
2300    // ========= #494 bounds-elision (#390 guard_bool): memory bounds guards =========
2301
2302    /// The gust_poll shape: a record array indexed by a proven-bounded slot —
2303    /// `base = slot*16 + 256`, then field loads/stores at static offsets.
2304    fn poll_ops() -> Vec<WasmOp> {
2305        vec![
2306            LocalGet(0),   // 0  slot ← fact target
2307            I32Const(4),   // 1
2308            I32Shl,        // 2  slot*16
2309            I32Const(256), // 3
2310            I32Add,        // 4  base
2311            LocalSet(1),   // 5
2312            LocalGet(1),   // 6
2313            I32Load8U {
2314                offset: 0,
2315                align: 0,
2316            }, // 7  → mark (byte)
2317            LocalGet(1),   // 8
2318            I32Load {
2319                offset: 4,
2320                align: 2,
2321            }, // 9  → mark (word)
2322            I32Add,        // 10
2323            LocalGet(1),   // 11
2324            I32Const(7),   // 12
2325            I32Store16 {
2326                offset: 2,
2327                align: 1,
2328            }, // 13 → mark (halfword store)
2329            End,           // 14
2330        ]
2331    }
2332
2333    #[test]
2334    fn bounded_index_elides_all_mem_bounds_guards_494() {
2335        // slot ∈ [0, 63] ⇒ base ∈ [256, 1264]; every access's last byte is
2336        // < 65536, so all three obligations fall to
2337        // UNSAT(P ∧ trap_mem_oob(...)). Marks only — the stream is untouched.
2338        let ops = poll_ops();
2339        let r = specialize_function("poll", &ops, &[], &[fact(0, 0, 63)], &[], 65536);
2340        assert_eq!(
2341            r.elide_mem_bounds,
2342            vec![7, 9, 13],
2343            "declines: {:?}",
2344            r.declined
2345        );
2346        assert!(!r.changed(), "guard marks never rewrite the op stream");
2347        assert_eq!(r.ops, ops);
2348        assert_eq!(r.admitted.len(), 3);
2349        for line in &r.admitted {
2350            assert!(line.contains("bounds guard elided"), "{line}");
2351            assert!(line.contains("trap_mem_oob"), "{line}");
2352            assert!(line.contains("certificate-checked"), "{line}");
2353            assert!(line.contains("[0, 63]"), "{line}");
2354        }
2355    }
2356
2357    #[test]
2358    fn oob_admissible_bound_is_sat_and_declines_494() {
2359        // slot ∈ [0, 8192]: slot = 4096 ⇒ base = 65792 > 65536 — the access
2360        // can genuinely escape, the obligation is Sat, the guard stays.
2361        let ops = poll_ops();
2362        let r = specialize_function("poll", &ops, &[], &[fact(0, 0, 8192)], &[], 65536);
2363        assert_eq!(r.elide_mem_bounds, Vec::<usize>::new());
2364        assert_eq!(r.admitted.len(), 0);
2365        assert!(
2366            r.declined
2367                .iter()
2368                .any(|d| d.contains("bounds-guard obligation Sat") && d.contains("counterexample")),
2369            "declines must be loud and carry a model: {:?}",
2370            r.declined
2371        );
2372    }
2373
2374    #[test]
2375    fn unknown_memory_size_declines_mem_bounds_494() {
2376        // linear_memory_bytes == 0 (no module memory context): the bound of
2377        // the obligation does not exist — decline loudly, never guess.
2378        let ops = poll_ops();
2379        let r = specialize_function("poll", &ops, &[], &[fact(0, 0, 63)], &[], 0);
2380        assert_eq!(r.elide_mem_bounds, Vec::<usize>::new());
2381        assert!(
2382            r.declined
2383                .iter()
2384                .any(|d| d.contains("linear-memory size unknown")),
2385            "{:?}",
2386            r.declined
2387        );
2388    }
2389
2390    #[test]
2391    fn unconstrained_index_declines_mem_bounds_494() {
2392        // A TRUE fact exists (so the walk runs) but targets the stored VALUE,
2393        // not the index — the index is unconstrained ⇒ Sat ⇒ loud decline.
2394        let ops = vec![
2395            LocalGet(0), // 0  index — unconstrained
2396            LocalGet(1), // 1  value ← fact (non-constraining)
2397            I32Store {
2398                offset: 0,
2399                align: 2,
2400            }, // 2
2401            End,         // 3
2402        ];
2403        let r = specialize_function("f", &ops, &[], &[fact(1, 0, 63)], &[], 65536);
2404        assert_eq!(r.elide_mem_bounds, Vec::<usize>::new());
2405        assert!(
2406            r.declined
2407                .iter()
2408                .any(|d| d.contains("bounds-guard obligation Sat")),
2409            "{:?}",
2410            r.declined
2411        );
2412    }
2413
2414    #[test]
2415    fn wraparound_index_plus_offset_never_falsely_unsat_494() {
2416        // THE WIDTH-EXTENSION GOTCHA: index = -256 (0xFFFFFF00 unsigned) with
2417        // offset = 0x200. A naive 32-bit encoding wraps the effective address
2418        // to 0x104 — "in bounds" — and would falsely elide; WASM's effective
2419        // address is infinite-precision (4294967040 + 512 > 65536 ⇒ TRAPS).
2420        // The 64-bit zero-extension keeps the obligation Sat ⇒ guard retained.
2421        let ops = vec![
2422            LocalGet(0), // 0 ← fact [-256, -256]
2423            I32Load {
2424                offset: 0x200,
2425                align: 2,
2426            }, // 1
2427            End,         // 2
2428        ];
2429        let r = specialize_function("f", &ops, &[], &[fact(0, -256, -256)], &[], 65536);
2430        assert_eq!(
2431            r.elide_mem_bounds,
2432            Vec::<usize>::new(),
2433            "a wrapped effective address must NOT elide the guard: {:?}",
2434            r.admitted
2435        );
2436        assert!(
2437            r.declined
2438                .iter()
2439                .any(|d| d.contains("bounds-guard obligation Sat")),
2440            "{:?}",
2441            r.declined
2442        );
2443    }
2444
2445    #[test]
2446    fn access_ending_exactly_at_bound_is_in_bounds_boundary_494() {
2447        // Boundary semantics: a 4-byte load at index 65532 ends exactly AT
2448        // the bound (addr + size == mem_bound) — in bounds per WASM (§4.4.5:
2449        // trap iff ea + size > mem size) and per the guard's `>u`. One past
2450        // (65533) must decline.
2451        let ops = vec![
2452            LocalGet(0), // 0 ← fact
2453            I32Load {
2454                offset: 0,
2455                align: 2,
2456            }, // 1
2457            End,         // 2
2458        ];
2459        let ok = specialize_function("f", &ops, &[], &[fact(0, 0, 65532)], &[], 65536);
2460        assert_eq!(ok.elide_mem_bounds, vec![1], "declines: {:?}", ok.declined);
2461        let over = specialize_function("f", &ops, &[], &[fact(0, 0, 65533)], &[], 65536);
2462        assert_eq!(over.elide_mem_bounds, Vec::<usize>::new());
2463    }
2464
2465    #[test]
2466    fn mem_marks_are_remapped_through_a_clamp_elision_494() {
2467        // A clamp elision rewrites the stream; a downstream load's mark must
2468        // land on the REWRITTEN index (the selector keys by its own op index).
2469        let ops = vec![
2470            LocalGet(0),    // 0  ← fact [524, 1524]
2471            I32Const(476),  // 1
2472            I32Add,         // 2
2473            LocalSet(1),    // 3
2474            LocalGet(1),    // 4  -+ low clamp (elided 4..=10)
2475            I32Const(1000), // 5   |
2476            I32LtS,         // 6   |
2477            If,             // 7   |
2478            I32Const(1000), // 8   |
2479            LocalSet(1),    // 9   |
2480            End,            // 10 -+
2481            LocalGet(0),    // 11  index = ch ∈ [524, 1524]
2482            I32Load {
2483                offset: 0,
2484                align: 2,
2485            }, // 12  → mark (rewritten index 5)
2486            End,            // 13
2487        ];
2488        let r = specialize_function("f", &ops, &[(0, 0)], &[fact(0, 524, 1524)], &[], 65536);
2489        assert!(r.changed(), "declines: {:?}", r.declined);
2490        assert_eq!(r.kept, vec![0, 1, 2, 3, 11, 12, 13]);
2491        assert_eq!(
2492            r.elide_mem_bounds,
2493            vec![5],
2494            "mark remapped from original op#12 to rewritten op#5"
2495        );
2496    }
2497
2498    // ============ #494 Phase 3+: constant-divisor rem_u identity =============
2499
2500    fn const_rem_ops() -> Vec<WasmOp> {
2501        // `x rem_u 1000` — under x ∈ [0, 999] the modulo is the identity.
2502        vec![
2503            LocalGet(0),    // 0  x  ← fact target
2504            I32Const(1000), // 1  divisor (literal, ≠0)
2505            I32RemU,        // 2  x % 1000
2506            End,            // 3
2507        ]
2508    }
2509
2510    #[test]
2511    fn const_rem_u_identity_elided_under_proven_narrow_dividend_494() {
2512        let ops = const_rem_ops();
2513        let r = specialize_function("gust_scale", &ops, &[], &[fact(0, 0, 999)], &[], 0);
2514        assert_eq!(r.admitted.len(), 1, "declines: {:?}", r.declined);
2515        assert!(r.changed());
2516        assert_eq!(
2517            r.ops,
2518            vec![LocalGet(0), End],
2519            "the const-divisor + rem_u dissolve to the identity dividend"
2520        );
2521        assert_eq!(r.kept, vec![0, 3]);
2522        // Never a div-guard mark: the op was DELETED, not guarded.
2523        assert!(r.elide_div_zero.is_empty());
2524        assert!(r.elide_div_ovf.is_empty());
2525        let line = &r.admitted[0];
2526        assert!(line.contains("i32.rem_u"), "{line}");
2527        assert!(line.contains("elided to identity"), "{line}");
2528        assert!(line.contains("literal i32.const 1000"), "{line}");
2529        assert!(
2530            line.contains("UNSAT(P ∧ (dividend bvurem 1000) ≠ dividend)"),
2531            "{line}"
2532        );
2533        assert!(line.contains("certificate-checked"), "{line}");
2534    }
2535
2536    #[test]
2537    fn const_rem_u_wrong_bound_is_sat_and_declines_byte_identically_494() {
2538        // x ∈ [0, 4000] admits x ≥ 1000, so x % 1000 ≠ x — Sat ⇒ loud decline.
2539        // The stream is untouched and the general div/rem path runs (marks the
2540        // zero guard? no — divisor is a nonzero literal, so the zero-guard
2541        // obligation is discharged by the guard elision, but the OP survives).
2542        let ops = const_rem_ops();
2543        let r = specialize_function("gust_scale", &ops, &[], &[fact(0, 0, 4000)], &[], 0);
2544        assert_eq!(
2545            r.admitted
2546                .iter()
2547                .filter(|l| l.contains("elided to identity"))
2548                .count(),
2549            0,
2550            "identity must NOT be admitted under a too-wide bound"
2551        );
2552        assert!(!r.stream_changed, "declined identity ⇒ op stream untouched");
2553        assert_eq!(r.ops, ops, "declined ⇒ byte-identical op stream");
2554        assert!(
2555            r.declined
2556                .iter()
2557                .any(|d| d.contains("not the identity") && d.contains("counterexample")),
2558            "identity decline must be loud and carry a model: {:?}",
2559            r.declined
2560        );
2561    }
2562
2563    #[test]
2564    fn const_rem_u_variable_divisor_declines_identity_and_keeps_op_494() {
2565        // Divisor is a param (local.get 1), NOT a literal — the op carries a
2566        // trapping zero-guard, so the whole op is NOT deletable. Identity
2567        // declines; the general div/rem path stands (op survives). A
2568        // divisor-nonzero fact still discharges the ZERO GUARD (guard elision),
2569        // but never the op deletion.
2570        let ops = vec![
2571            LocalGet(0), // 0  x   ← range fact [0, 999]
2572            LocalGet(1), // 1  d   ← divisor-nonzero fact
2573            I32RemU,     // 2  x % d
2574            End,         // 3
2575        ];
2576        let facts = [fact(0, 0, 999), nonzero_fact(1)];
2577        let r = specialize_function("f", &ops, &[], &facts, &[], 0);
2578        assert!(!r.stream_changed, "variable divisor ⇒ op never deleted");
2579        assert_eq!(r.ops, ops);
2580        assert_eq!(
2581            r.elide_div_zero,
2582            vec![2],
2583            "zero guard still discharged by the nonzero fact"
2584        );
2585        assert!(
2586            r.declined
2587                .iter()
2588                .any(|d| d.contains("not a literal i32.const")),
2589            "identity must decline on a non-literal divisor: {:?}",
2590            r.declined
2591        );
2592    }
2593
2594    #[test]
2595    fn const_rem_u_no_fact_declines_byte_identically_494() {
2596        // No range fact on the dividend ⇒ identity declines; the general path
2597        // runs. The literal divisor is nonzero so the zero-guard is elidable
2598        // ONLY with a fact — here there's none, so nothing is admitted and the
2599        // stream is untouched.
2600        let ops = const_rem_ops();
2601        let r = specialize_function("gust_scale", &ops, &[], &[fact(99, 0, 0)], &[], 0);
2602        assert!(!r.stream_changed, "no dividend fact ⇒ op stream untouched");
2603        assert_eq!(r.ops, ops);
2604        assert_eq!(
2605            r.admitted
2606                .iter()
2607                .filter(|l| l.contains("elided to identity"))
2608                .count(),
2609            0
2610        );
2611    }
2612
2613    #[test]
2614    fn const_rem_u_pow2_divisor_still_elides_under_narrow_bound_494() {
2615        // Even a power-of-two divisor elides when the dividend is proven narrow
2616        // (the win is smaller — clang folds pow2 rem to an `and` — but the
2617        // proof is identical and sound). x ∈ [0, 255], x rem_u 256 == x.
2618        let ops = vec![
2619            LocalGet(0),   // 0  ← fact [0, 255]
2620            I32Const(256), // 1
2621            I32RemU,       // 2
2622            End,           // 3
2623        ];
2624        let r = specialize_function("f", &ops, &[], &[fact(0, 0, 255)], &[], 0);
2625        assert_eq!(r.admitted.len(), 1, "declines: {:?}", r.declined);
2626        assert_eq!(r.ops, vec![LocalGet(0), End]);
2627    }
2628}