Skip to content

Investigate: verify.rs opaque-float shared const name may unify distinct float ops (verifier soundness) #283

Description

@avrabe

Investigate: verify.rs opaque-float shared const name may unify DISTINCT float ops (potential verifier unsoundness)

Surfaced during the ordeal migration gap analysis of loom-core/src/verify.rs. Flagging for confirmation — if real, it's an unsoundness in the verifier itself (the check could accept a wrong transform), which is more serious than a pass bug.

The concern

Float ops are modeled as opaque bitvectors: each f32.*/f64.* op pushes a fixed-named fresh const, e.g. BV::new_const("f32_add_result", 32) (~verify.rs:4921-4927). The intent is that identical float ops in the original and optimized encodings unify to the same symbolic const so they compare equal. But a fixed name shared across all sites means two different float-op sites also unify: f32.add(a,b) (original) and f32.add(c,d) (optimized) both become the same const f32_add_result even when a,b ≠ c,d. The equivalence check orig ≠ opt could then be UNSAT for the wrong reason — proving two non-equivalent functions "equivalent" and accepting an unsound float transform.

Contrast

The havoc/opaque-call path is done right — it uses a deterministic per-encode call_id counter so corresponding calls unify but distinct calls don't (verify.rs:4652-4684). The float path appears to use a fixed name instead of the same discipline.

Ask

  1. Confirm whether a per-site counter (or operand-dependent encoding) actually mitigates this, or the fixed name is used verbatim.
  2. If unmitigated: model an opaque float op as an uninterpreted function over its operandsf32_add(a,b) via FuncDecl/congruence (equal only when operands equal), the same congruence trick the pure-call path uses — so distinct sites don't wrongly unify while identical sites still do. Add a regression: a fixture where a pass rewrites f32.add(a,b) to a non-equivalent f32.add(c,d) must be REJECTED by verify.
  3. Cross-check with the behavioral differential gate (Behavioral differential as an optimization gate (self-certify non-Z3-backstoppable transforms) #238) — it would catch a float miscompile by execution even if the SMT check is fooled.

Note: loom skips float load/store functions entirely (SkippedMemory), which may limit exposure — part of what needs confirming. Refs: gap analysis of verify.rs float modeling.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions