Structural Simplifier for Symbolic Simulation #
It is a specialized simplifier designed for symbolic simulation workloads.
It addresses performance bottlenecks identified in the standard simp tactic
when applied to large terms typical in symbolic execution.
Design Goals #
- Efficient caching via pointer-based keys on maximally shared terms
- Fast pattern matching using the
Patterninfrastructure instead ofisDefEq - Minimal proof term overhead by using
shareCommonand efficient congruence lemma application
Key Performance Problems Addressed #
1. Cache Inefficiency (Hash Collisions) #
The standard simplifier uses structural equality for cache keys. With large terms, hash collisions cause O(n) comparisons that fail, leading to O(n²) behavior.
Solution: Require maximally shared input terms (shareCommon) and use
pointer-based cache keys. Structurally equal terms have equal pointers, making
cache lookup O(1).
2. isDefEq in Rewrite Matching #
Profiling shows that isDefEq dominates simplification time in many workloads.
For each candidate rewrite rule, definitional equality checking with metavariable unification
is performed, and sometimes substantial time is spent checking whether the scopes are
compatible.
Solution: Use the Pattern infrastructure for syntactic matching:
- No metavariable creation or assignment
- No occurs check (
CheckAssignment) - Direct de Bruijn index comparison
3. inferType in Proof Construction #
In the standard simplifier, building Eq.trans and congrArg proofs uses inferType on proof terms,
which reconstructs large expressions and destroys sharing. It often causes O(n²) allocation.
Solution:
- Never perform
inferTypeon proof terms when constructing congruence and transitivity proofs. - Use a pointer-based cache for
inferTypeon terms.
4. funext Proof Explosion #
Nested funext applications create O(n²) proof terms due to implicit arguments
{f} {g} of size O(n) repeated n times.
Solution: Generate funext_k for k binders at once, stating the implicit
arguments only once.
5. Binder Re-entry Cache Invalidation #
When a simplification theorem restructures a lambda, re-entering creates a fresh free variable, invalidating all cached results for subterms.
Solution: Reuse free variables across binder re-entry when safe:
- Stack-based approach: reuse when types match on re-entry path
- Instance-aware: track local type class instances separately from hypotheses
- If simplifier doesn't discharge hypotheses from local context, only instances matter
6. Contextual ite Handling #
The standard ite_congr theorem adds hypotheses when entering branches,
invalidating the cache and causing O(2^n) behavior on conditional trees.
Solution: Non-contextual ite handling for symbolic simulation:
- Only simplify the condition
- If condition reduces to
True/False, take the appropriate branch - Otherwise, keep
itesymbolic (let the simulator handle case-splitting)
Architecture #
Input Requirements #
- Terms must be maximally shared via
shareCommonlike every other module inSym. - Enables pointer-based cache keys throughout
Integration with SymM #
SimpM is designed to work within the SymM symbolic simulation framework:
- Uses
BackwardRulefor control-flow (monadic bind, match, combinators) SimpMhandles term simplification between control-flow steps- Avoids entering control-flow binders
Configuration options for the structural simplifier.
- maxSteps : Nat
Maximum number of steps that can be performed by the simplifier.
- maxDischargeDepth : Nat
Maximum depth of reentrant simplifier calls through dischargers. Prevents infinite loops when conditional rewrite rules trigger recursive discharge attempts.
Instances For
The result of simplifying an expression e.
The done flag controls whether simplification should continue after this result:
done = false(default): Continue with subsequent simplification stepsdone = true: Stop processing, return this result as final
Use cases for done = true #
In pre simprocs #
Skip simplification of certain subterms entirely:
def skipLambdas : Simproc := fun e =>
if e.isLambda then return .rfl (done := true)
else return .rfl
In post simprocs #
Perform single-pass normalization without recursive simplification:
def singlePassNormalize : Simproc := fun e =>
if let some (e', h) ← tryNormalize e then
return .step e' h (done := true)
else return .rfl
With done = true, the result e' won't be recursively simplified.
Behavior #
The done flag affects:
andThencomposition: If the first simproc returnsdone = true, the second simproc is skipped.- Recursive simplification: After
preorpostreturns.step e' h,simpnormally recurses one'. Withdone = true, recursion is skipped.
The flag is orthogonal to caching: both .rfl and .step results are cached
regardless of the done flag, and cached results are always treated as final.
- rfl
(done : Bool := false)
: Result
No change. If
done = true, skip remaining simplification steps for this term. - step
(e' proof : Expr)
(done : Bool := false)
: Result
Simplified to
e'with proofproof : e = e'. Ifdone = true, skip recursive simplification ofe'.
Instances For
Instances For
Cache mapping expressions (by pointer equality) to their simplified results.
Instances For
Monad for the structural simplifier, layered on top of SymM.
Equations
Instances For
Equations
- Lean.Meta.Sym.Simp.instInhabitedSimpM = { default := Lean.throwError (Lean.toMessageData "<default>") }
Equations
Instances For
- pre : Simproc
- post : Simproc
- wellBehavedMethods : Bool
wellBehavedMethodsmust not be set totrueIF their behavior depends on new hypotheses in the local context. For example, for applying conditional rewrite rules. Reason: it would prevent us from aggressively cachingsimpresults.
Instances For
Equations
Instances For
Equations
Instances For
Equations
- Lean.Meta.Sym.Simp.getMethods = do let __do_lift ← read pure __do_lift.toMethods
Instances For
Equations
- Lean.Meta.Sym.Simp.getConfig = do let __do_lift ← readThe Lean.Meta.Sym.Simp.Context pure __do_lift.config
Instances For
Equations
- Lean.Meta.Sym.Simp.getCache = do let __do_lift ← get pure __do_lift.cache
Instances For
Equations
- Lean.Meta.Sym.Simp.pre e = do let __do_lift ← Lean.Meta.Sym.Simp.getMethods __do_lift.pre e
Instances For
Equations
- Lean.Meta.Sym.Simp.post e = do let __do_lift ← Lean.Meta.Sym.Simp.getMethods __do_lift.post e
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- Lean.Meta.Sym.simp e methods config = (Lean.Meta.Sym.Simp.simp e).run' methods config