LeanSAS is a Lean library for experimenting with a specialization and simplification compilation pre-pass.
The core idea is to take a Lean definition, generate a specialized version of it, recursively specialize the functions it calls, and run simp on generated bodies. Users can then guide the generated code by providing ordinary simp theorems and simprocs.
Lean functions are often written in a generic, reusable style. That style is good for proofs and source code, but it can leave generated code with unnecessary abstraction:
- calls where some arguments are statically known,
- helper functions that could be specialized to concrete arguments,
- generic definitions that simplify substantially after specialization,
- terms that become computationally useful only after user-provided
simprules fire.
LeanSAS aims to provide a small pre-pass that turns such definitions into simpler specialized definitions before later compilation or extraction steps.
The intended command is:
#sas fFor a definition f, this command creates a generated definition named:
f._specThe generated definition is extensionally the same computation as f, but its body is transformed by the specialization pass.
For each generated specialization, LeanSAS also tries to add an equality theorem:
f._spec.eq_thmThe theorem states that the generated specialization computes the same value as the original declaration at each runtime argument.
Given a function f, LeanSAS processes its body as follows:
- Run
simpon expressions during transformation. - Recursively inspect function applications in the body.
- When a call
g x1 ... xnis found, decide whethergcan be specialized. - Arguments known at specialization time are baked into a generated specialization of
g. - Arguments that still depend on runtime variables remain parameters of the generated specialization.
- Lambda arguments are treated as static templates; their captured free variables become deduplicated runtime parameters of the specialization.
- Replace the original call with a call to the generated specialization.
- Repeat the process recursively for newly generated specializations.
For example, a source call like this:
scaleAdd 2 xmay produce a generated specialization equivalent to:
def scaleAdd._spec_1 (x : Nat) :=
scaleAdd 2 xand the original call site is rewritten to:
scaleAdd._spec_1 xThe body of scaleAdd._spec_1 is then processed by the same specialization and simplification pass.
Higher-order arguments are specialized too. For example:
def functionArgCallee (f : Nat → Nat) (x : Nat) :=
f x
def functionArgTransform (x z : Nat) :=
functionArgCallee (fun y => 0 + y*z + x) xmay generate a callee specialization shaped like:
def functionArgCallee_sas_1 (x z : Nat) :=
x * z + xThe lambda itself is baked into the specialization, while the captured variables x and z become generated parameters. Captures are deduplicated against ordinary runtime arguments.
When a specialized lambda uses its parameter nonlinearly, LeanSAS preserves sharing for nontrivial arguments by introducing a let before beta reduction. For example, specializing f (expensive x) with fun y => a*y + b*y produces a body shaped like:
let y := expensive._spec x
a * y + b * yinstead of duplicating expensive._spec x.
LeanSAS is designed to use Lean's existing simplifier as the user extension mechanism.
The intended command syntax includes normal simp configuration and theorem selection:
#sas (config := {...}) only [thm1, thm2] fThe command supports ordinary simp attribute selection and configuration, including only [...], added theorem sets, removed theorem sets, and (config := ...).
The current version is deliberately small and conservative, but it already supports the core specialization pass.
In scope:
- Generate
f._specfor a Lean definitionf. - Recursively specialize non-recursive function calls.
- Bake compile-time-known arguments into generated definitions.
- Keep runtime-dependent arguments as parameters.
- Run
simpduring transformation. - Generate real Lean declarations.
- Generate equality theorems for generated specializations when proof construction succeeds.
- Compose proof terms across simplification, application argument rewriting, lambda congruence, let congruence, and projection congruence.
- Specialize lambda-valued arguments by extracting captured runtime variables.
- Preserve sharing for nonlinear beta-redexes with nontrivial arguments.
Still out of scope:
- Recursive function specialization.
- Complex type encoding.
- General splitting of source arguments into multiple generated arguments, except for lambda captures used by higher-order specialization.
- OpenCL-specific behavior.
- Primitive interpreter evaluation.
- Direct compiler-pipeline integration.
Type encoding is not part of the first version.
When type encoding is added later, LeanSAS should use a simple one-type-to-one-type model. A source type may be encoded into another type, but it should remain one argument slot.
For example:
Option A ~~> A × Boolnot:
Option A ~~> A, BoolThis differs from the previous prototype, which tried to flatten structure-like values into multiple arguments in some cases. The simpler model keeps generated function arities more predictable and should make the pass easier to maintain.
Simple examples supported by the current implementation:
def addOne (x : Nat) := x + 1
def useAddOne (n : Nat) := addOne n
#sas useAddOne
#check useAddOne._specdef scaleAdd (k x : Nat) := k * x + 1
def byTwo (x : Nat) := scaleAdd 2 x
#sas byTwo
#check byTwo._specdef f (a b : Nat) := a + b
def g (x : Nat) := f 10 x
def h (x : Nat) := g (x + 1)
#sas h
#check h._specHigher-order lambda specialization:
def functionArgCallee (f : Nat → Nat) (x : Nat) :=
f x
def functionArgTransform (x z : Nat) :=
functionArgCallee (fun y => 0 + y*z + x) x
#sas functionArgTransform
#print functionArgTransform._spec
#print functionArgCallee_sas_1Sharing-preserving nonlinear beta specialization:
def expensiveInput (x : Nat) := x + 1
def nonlinearFunctionArgCallee (f : Nat → Nat) (x : Nat) :=
f (expensiveInput x)
def nonlinearFunctionArgTransform (a b x : Nat) :=
nonlinearFunctionArgCallee (fun y => a * y + b * y) x
#sas (config := { zeta := false }) nonlinearFunctionArgTransform
#print nonlinearFunctionArgCallee_sas_1The previous and more ambitious prototype is located at:
~/Documents/HouLean/HouLean/Meta/SpecializeAndSimp2/
Partially broken tests and examples are located at:
~/Documents/HouLean/Tests/Meta/SpecAndSimp/
That implementation contains useful ideas, especially the recursive specialization request queue, but it also includes complex type encoding, vector/matrix-specific machinery, primitive evaluation hooks, and HouLean-specific dependencies. LeanSAS should first extract the small core and build from there.
See PLAN.md for the implementation plan and milestone list.