Skip to content

Source-level Z3 :pattern hints for user-written quantifiers #263

@wirerhino

Description

@wirerhino

Summary

Request: expose SMT-LIB :pattern annotation on user-written \A/\E quantifiers via a TLAPS surface-syntax pragma, so users can guide Z3's MBQI quantifier instantiation on heavily-quantified obligations.

Motivation (empirical, from production HFT verification project)

We maintain ~81 TLA+ specs / 564 invariants / 17 modules, including Raft consensus, matching engine (AMM with curve executors), and lock-tracking proof families. Recent verification campaigns surfaced a recurring SMT cascade pattern on heavily-quantified TypeOK-class obligations:

  • ChronosRaftProofs.tla TypeOK QED — 9-fact SMT cascade taking 15-30 minutes cold per backend (Z3 / cvc4 / Spass)
  • 26→0 axiom-removal campaign — apex obligations (LeaderElectionLogPrefixOfLog, ClientRequestNoForwardEntry, LogMatchingLocalAxiom) routinely require 6-axis defense-in-depth (TLAPS + Coq + Lean + Why3 + Z3-standalone + Apalache) because Z3's MBQI loops on \A s \in Server: P(s) shapes when Server carries multiple per-element typing facts (currentTerm[s] \in Nat, votedFor[s] \in Server \cup {NULL}, log[s] \in Seq(LogEntry), role[s] \in {Follower, Candidate, Leader}).

Empirically, the structural workarounds we've discovered (per-element typing, drop-SUFFICES-use-ASSUME/PROVE, function-builder per-conjunct case split, lift-typing-facts-as-explicit-sub-step) buy 30-90% wall-clock improvements but require restructuring proofs around what is fundamentally an SMT-instantiation issue. A source-level :pattern hint would let users keep proof structure idiomatic and instead constrain Z3's instantiator directly.

Current state

The internal SMT translator already wires :pattern annotations end-to-end:

  • src/expr/e_t.ml:496pattern_prop : expr list pfuncs, the property attached to expressions
  • src/expr/e_t.ml:499-518add_pats / remove_pats / map_pats / fold_pats operators
  • src/backend/smt.ml:618-637 — internal add_patterns injects pattern_prop on translator-emitted \A bs : Mem(_, ex) <=> _ shapes for abstracted operators
  • src/backend/smtlib.ml:236-250pp_print_pat emits (! <expr> :pattern (<pat>)) SMT-LIB output

So the SMT-emitter is fully working — but the only writer is the internal add_patterns site, which fires a hard-coded shape on translator-injected operators. Empirically (verified in our .tlacache/<spec>.tlaps/tlapm_*.smt output), :pattern clauses appear ONLY on built-in TLA+ axioms (smt__TLA____SetExtTrigger, smt__TLA____Mem, smt__TLA____FunIsafcn, etc.) — never on user-defined operators (Server, currentTerm, LogEntry).

Pragma-key parser routes only prover / timeout / tactic (src/backend/prep.ml:1117-1181); any other key raises \"Unknown keyword in proof method definition\" (prep.ml:1182). The grammar (src/expr/e_parser.ml:1132-1134 read_method_arg) accepts arbitrary identifier-keyed args and string_or_float_of_expr-typed values.

Proposed surface syntax

Two compatible options, in order of cleanness:

Option A — pragma-attached BY (preferred, smallest delta)

Pattern(p) == TRUE (*{ by (prover:\"smt3\"; pattern:@) }*)

THEOREM TypeOK
  PROOF
  <1>1. \A s \in Server: currentTerm[s] \in Nat
    BY DEF Init, Next, vars, Pattern(<<currentTerm[s]>>)
  <1>2. ...

The pattern: key takes a structured-expression argument (currently read_method_arg accepts only Bstring/Bfloat/Bdef); needs grammar widening to accept Btuple/Bexpr of patterns.

Option B — pattern-attached label (alternative)

THEOREM \A s \in Server: P(s) /\ Q(s)
  PROOF (*{ pattern: <<P(s)>> }*)
  ...

Requires a new pragma site distinct from method-args (currently pragma is consumed only on operator-defs and BY).

We'd recommend Option A as a strictly additive parser change (read_method_arg extension) + new pragma key in compute_meth (route pattern: <expr> to attach pattern_prop on the surrounding obligation).

Empirical wallclock targets

Based on our tlapsProveChronosRaft campaign (commit 2080dc3f8 ClientRequestNoForwardEntry, 13456 obligations PROVED, 6-axis defense):

  • Z3 MBQI cold path on TypeOK: 15-30min observed; target with explicit :pattern (<<currentTerm[s]>>): <2min (4-10× speedup, comparable to documented Z3-pattern speedups in IronFleet/Verus).
  • Total Raft module wallclock (tlapsProveChronosRaftProofs): currently ~50min cold; target <15min.
  • Apex axiom-removal campaign: 6-axis defense requirement could collapse to TLAPS-alone for ~40% of axioms if MBQI patterns were guidable.

Why this matters for the broader TLA+ community

The IronFleet / Verus / Dafny ecosystems all expose source-level pattern hints precisely because heavily-quantified specifications hit MBQI saturation in practice. TLAPS is currently the only major TLA+/proof-management toolchain that emits SMT-LIB but blocks users from controlling the pattern annotations — even though the internal infrastructure (pattern_prop, pp_print_pat) is fully operational. Adding a source-level frontend would make TLAPS competitive with these systems on quantifier-heavy proofs.

Acceptance criteria suggestion

  1. New pragma key pattern: <expr-list> accepted in read_method_arg grammar.
  2. compute_meth (prep.ml) routes the new key to attach Smt.pattern_prop on the obligation's quantifier-bound conclusion (mirroring the internal add_patterns mechanism).
  3. New stdlib operator in library/TLAPS.tla:
    Pattern(p) == TRUE (*{ by (pattern:@) }*)
  4. Documentation example in doc/web/ showing pattern-hint usage on a TypeOK-shaped THEOREM.
  5. Round-trip test: cited pattern appears in .tlacache/<spec>.tlaps/tlapm_*.smt :pattern (...) output.
  6. No regression on existing test corpus (make test).

We would be happy to contribute a PR if the design direction is acceptable to the maintainers — wanted to validate the approach + namespace choice (pattern: vs trigger: vs qid:) before opening one.

References

  • src/expr/e_t.ml:496-518pattern_prop infrastructure (already exists)
  • src/backend/smt.ml:618-637 — internal add_patterns site (only current writer)
  • src/backend/smtlib.ml:236-250:pattern SMT-LIB emitter (already wired)
  • src/expr/e_parser.ml:1128-1154 — pragma grammar (extension target)
  • src/backend/prep.ml:1105-1186 — pragma key consumer / compute_meth (extension target)
  • library/TLAPS.tla:39-115 — current pragma surface (one new operator to add)

Empirical context: #261 (LSP proof decomposition, also targets quantifier obligation handling) is adjacent work in the same neighborhood.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions