Filed from synth (issue-hunt loop, not gale) in response to the "should we replace Z3?" question. These are synth's concrete requirements so an ecosystem solver decision — which loom is already partly making (#231 forwards Z3-discharged wsc.facts invariants to synth; #169 tracks Rocq gating) — is grounded in what synth actually needs.
TL;DR
synth's Z3 usage is the simplest possible SMT fragment, and the pain is the build, not the solver. Any decision should optimize for hermetic/reproducible builds and CI stability, because the solver capability is a non-issue at synth's fragment.
What synth uses Z3 for
synth-verify is a translation validator: for each lowering it asserts wasm_op_semantics ≠ arm_instruction_sequence_semantics and expects UNSAT (i.e. they're equivalent). Concretely:
- Theory: QF_BV only (quantifier-free bitvectors). Active op surface in the validator:
bvadd/bvsub/bvmul/bvudiv, bvand/bvor/bvxor, bvshl/bvlshr/bvashr, bvrotr, extract/concat, zero_ext/sign_ext, and signed+unsigned comparisons (bvult/bvule/bvugt/bvuge/bvslt/bvsgt). 32- and 64-bit widths.
- Query model: one-shot
check-sat + a counterexample model. No quantifiers, no arrays, no floating-point, no Optimize, no incremental push/pop/assumptions (verified by grep over synth-verify/src).
- Pulled as the
z3 crate 0.19 with features = ["static-link-z3"] — i.e. it compiles Z3 from C++ source via z3-sys's build.rs.
Why replace it (the actual pain)
It is a build/hermeticity problem, not a solver-quality problem:
z3-sys build.rs breaks local cargo test --workspace (panics around build.rs:374); synth's own docs tell contributors to --exclude synth-verify locally.
- CI's Z3 Verification job flakes and the "comprehensive verification" step runs ~16 min and intermittently times out (seen this week on a RISC-V-only PR that can't even affect ARM semantics — pure infra flake, forced a re-run).
- The from-source C++ compile is the long pole and the fragile part. The solver answers synth's QF_BV queries instantly and correctly; z3-the-build is what hurts.
Requirements for a replacement (synth side)
MUST
- Decide QF_BV
check-sat with a returned model (counterexample) for the op set above, 32/64-bit.
- Hermetic, reproducible build — no from-source C++ compile in the critical path (prebuilt/vendored binary, or pure-Rust, or shell-out to an already-present solver binary). This is the whole point.
- Fast enough to gate CI without multi-minute timeouts at synth's query sizes (per-op equivalence checks are tiny).
- Permissive license (Z3 is MIT today — don't regress).
- Stable, thin Rust API (build a BV term graph, assert, check, read model).
NICE
- Solver-agnostic interface (so the choice isn't a one-way door).
- Deterministic results across platforms (macOS/Linux CI parity).
NOT required today (don't over-buy): quantifiers, arrays, floating-point, optimization, incremental solving. One flag for the future: the #369 FPU work (hard-float f32/f64) may eventually want QF_FP verification — worth keeping on the radar, but it is not a current dependency and shouldn't gate the decision.
Directions (synth has no strong preference — loom decides the ecosystem story)
Coordination ask
Whatever loom picks for discharging wsc.facts invariants (#231), synth would prefer the same solver/interface so the ecosystem has one solver story and one build story. If loom standardizes on an SMT-LIB2-text contract, synth can adopt it directly for translation validation.
Refs: loom #231, loom #169; synth #242 (VCR North Star), synth #73, synth #369.
Filed from synth (issue-hunt loop, not gale) in response to the "should we replace Z3?" question. These are synth's concrete requirements so an ecosystem solver decision — which loom is already partly making (#231 forwards Z3-discharged
wsc.factsinvariants to synth; #169 tracks Rocq gating) — is grounded in what synth actually needs.TL;DR
synth's Z3 usage is the simplest possible SMT fragment, and the pain is the build, not the solver. Any decision should optimize for hermetic/reproducible builds and CI stability, because the solver capability is a non-issue at synth's fragment.
What synth uses Z3 for
synth-verifyis a translation validator: for each lowering it assertswasm_op_semantics ≠ arm_instruction_sequence_semanticsand expects UNSAT (i.e. they're equivalent). Concretely:bvadd/bvsub/bvmul/bvudiv,bvand/bvor/bvxor,bvshl/bvlshr/bvashr,bvrotr,extract/concat,zero_ext/sign_ext, and signed+unsigned comparisons (bvult/bvule/bvugt/bvuge/bvslt/bvsgt). 32- and 64-bit widths.check-sat+ a counterexample model. No quantifiers, no arrays, no floating-point, noOptimize, no incrementalpush/pop/assumptions (verified by grep oversynth-verify/src).z3crate0.19withfeatures = ["static-link-z3"]— i.e. it compiles Z3 from C++ source viaz3-sys'sbuild.rs.Why replace it (the actual pain)
It is a build/hermeticity problem, not a solver-quality problem:
z3-sysbuild.rsbreaks localcargo test --workspace(panics aroundbuild.rs:374); synth's own docs tell contributors to--exclude synth-verifylocally.Requirements for a replacement (synth side)
MUST
check-satwith a returned model (counterexample) for the op set above, 32/64-bit.NICE
NOT required today (don't over-buy): quantifiers, arrays, floating-point, optimization, incremental solving. One flag for the future: the #369 FPU work (hard-float f32/f64) may eventually want QF_FP verification — worth keeping on the radar, but it is not a current dependency and shouldn't gate the decision.
Directions (synth has no strong preference — loom decides the ecosystem story)
static-linkbuild pain and makes the solver interchangeable. Lowest-risk, most portable.Coordination ask
Whatever loom picks for discharging
wsc.factsinvariants (#231), synth would prefer the same solver/interface so the ecosystem has one solver story and one build story. If loom standardizes on an SMT-LIB2-text contract, synth can adopt it directly for translation validation.Refs: loom #231, loom #169; synth #242 (VCR North Star), synth #73, synth #369.