Skip to content

Commit 8945bb6

Browse files
committed
add zetaHave to meta config, refactor
1 parent 8520b25 commit 8945bb6

File tree

7 files changed

+139
-104
lines changed

7 files changed

+139
-104
lines changed

src/Init/MetaTypes.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ structure Config where
252252
/--
253253
When `true` (default : `true`), then `simp` will remove unused `let` and `have` expressions:
254254
`let x := v; e` simplifies to `e` when `x` does not occur in `e`.
255+
This option takes precedence over `zeta` and `zetaHave`.
255256
-/
256257
zetaUnused : Bool := true
257258
/--

src/Lean/Meta/Basic.lean

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ structure Config where
158158
/-- Control projection reduction at `whnfCore`. -/
159159
proj : ProjReductionKind := .yesWithDelta
160160
/--
161-
Zeta reduction: `let x := v; e[x]` reduces to `e[v]`.
162-
We say a let-declaration `let x := v; e` is non dependent if it is equivalent to `(fun x => e) v`.
161+
Zeta reduction: `let x := v; e[x]` and `have x := v; e[x]` reduce to `e[v]`.
162+
We say a let-declaration `let x := v; e` is nondependent if it is equivalent to `(fun x => e) v`.
163163
Recall that
164164
```
165165
fun x : BitVec 5 => let n := 5; fun y : BitVec n => x = y
@@ -169,6 +169,7 @@ structure Config where
169169
fun x : BitVec 5 => (fun n => fun y : BitVec n => x = y) 5
170170
```
171171
is not.
172+
See also `zetaHave`, for disabling the reduction nondependent lets (`have` expressions).
172173
-/
173174
zeta : Bool := true
174175
/--
@@ -178,8 +179,13 @@ structure Config where
178179
/--
179180
Zeta reduction for unused let-declarations: `let x := v; e` reduces to `e` when `x` does not occur
180181
in `e`.
182+
This option takes precedence over `zeta` and `zetaHave`.
181183
-/
182184
zetaUnused : Bool := true
185+
/--
186+
When `zeta := true`, then `zetaHave := false` disables zeta reduction of `have` expressions.
187+
-/
188+
zetaHave : Bool := true
183189
deriving Inhabited, Repr
184190

185191
/-- Convert `isDefEq` and `WHNF` relevant parts into a key for caching results -/
@@ -200,7 +206,8 @@ private def Config.toKey (c : Config) : UInt64 :=
200206
(c.zetaDelta.toUInt64 <<< 14) |||
201207
(c.univApprox.toUInt64 <<< 15) |||
202208
(c.etaStruct.toUInt64 <<< 16) |||
203-
(c.proj.toUInt64 <<< 18)
209+
(c.proj.toUInt64 <<< 18) |||
210+
(c.zetaHave.toUInt64 <<< 20)
204211

205212
/-- Configuration with key produced by `Config.toKey`. -/
206213
structure ConfigWithKey where

src/Lean/Meta/ExprDefEq.lean

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,20 +1641,14 @@ private def isDefEqMVarSelf (mvar : Expr) (args₁ args₂ : Array Expr) : MetaM
16411641
pure false
16421642

16431643
/--
1644-
Removes unnecessary let-decls (both true `let`s and `let_fun`s).
1644+
Consumes unused lets/haves, depending on the current configuration.
1645+
- When `zetaUnused`, all unused lets may be consumed.
1646+
- Otherwise, when `zeta` is true, then unused lets can be consumed, unless they are nondependent and `cfg.zetaHave` is false.
16451647
-/
1646-
private partial def consumeLet : Expr → Expr
1647-
| e@(.letE _ _ _ b _) => if b.hasLooseBVars then e else consumeLet b
1648-
| e =>
1649-
if let some (_, _, _, b) := e.letFun? then
1650-
if b.hasLooseBVars then e else consumeLet b
1651-
else
1652-
e
1653-
16541648
private partial def consumeLetIfZeta (e : Expr) : MetaM Expr := do
16551649
let cfg ← getConfig
16561650
if cfg.zeta || cfg.zetaUnused then
1657-
return consumeLet e
1651+
return consumeUnusedLet e (consumeNondep := cfg.zetaUnused || cfg.zetaHave)
16581652
else
16591653
return e
16601654

src/Lean/Meta/InferType.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,10 @@ because it overrides unrelated configurations.
189189
@[inline] def withInferTypeConfig (x : MetaM α) : MetaM α :=
190190
withAtLeastTransparency .default do
191191
let cfg ← getConfig
192-
if cfg.beta && cfg.iota && cfg.zeta && cfg.zetaDelta && cfg.proj == .yesWithDelta then
192+
if cfg.beta && cfg.iota && cfg.zeta && cfg.zetaHave && cfg.zetaDelta && cfg.proj == .yesWithDelta then
193193
x
194194
else
195-
withConfig (fun cfg => { cfg with beta := true, iota := true, zeta := true, zetaDelta := true, proj := .yesWithDelta }) x
195+
withConfig (fun cfg => { cfg with beta := true, iota := true, zeta := true, zetaHave := true, zetaDelta := true, proj := .yesWithDelta }) x
196196

197197
@[export lean_infer_type]
198198
def inferTypeImp (e : Expr) : MetaM Expr :=

src/Lean/Meta/Tactic/Simp/Main.lean

Lines changed: 77 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,16 @@ private def reduceStep (e : Expr) : SimpM Expr := do
205205
match (← reduceRecMatcher? e) with
206206
| some e => return e
207207
| none => pure ()
208-
if cfg.zeta then
209-
if cfg.zetaHave then
210-
if let some (args, _, _, v, b) := e.letFunAppArgs? then
211-
return mkAppN (b.instantiate1 v) args
212-
if let .letE _ _ v b nondep := e then
213-
if !nondep || cfg.zetaHave then
214-
return b.instantiate1 v
208+
if let .letE _ _ v b nondep := e then
209+
if cfg.zeta && (!nondep || cfg.zetaHave) then
210+
return expandLet b #[v] (zetaHave := cfg.zetaHave)
211+
else if cfg.zetaUnused && !b.hasLooseBVars then
212+
return consumeUnusedLet b
213+
if let some (args, _, _, v, b) := e.letFunAppArgs? then
214+
if cfg.zeta && cfg.zetaHave then
215+
return mkAppN (expandLet b #[v] (zetaHave := cfg.zetaHave)) args
216+
else if cfg.zetaUnused && !b.hasLooseBVars then
217+
return mkAppN (consumeUnusedLet b) args
215218
match (← unfold? e) with
216219
| some e' =>
217220
trace[Meta.Tactic.simp.rewrite] "unfold {.ofConst e.getAppFn}, {e} ==> {e'}"
@@ -462,19 +465,18 @@ where
462465

463466
/--
464467
Computes which `have`s in the telescope are fixed and which are unused.
465-
The unused array might be too short: use `unused.getD i true`.
468+
The length of the unused array may be less than the number of `have`s: use `unused.getD i true`.
466469
-/
467470
def HaveTelescopeInfo.computeFixedUsed (info : HaveTelescopeInfo) (keepUnused : Bool) :
468471
MetaM (Std.HashSet Nat × Array Bool) := do
469472
if keepUnused then
470473
return (info.bodyTypeDeps, #[])
471474
let numHaves := info.haveInfo.size
472475
let updateArrayFromBackDeps (arr : Array Bool) (s : Std.HashSet Nat) : Array Bool :=
473-
if s.isEmpty then arr
474-
else s.fold (init := arr) fun arr idx => arr.set! idx true
476+
s.fold (init := arr) fun arr idx => arr.set! idx true
475477
let mut used : Array Bool := Array.replicate numHaves false
476478
-- Initialize `used` with the body's dependencies.
477-
-- There is no need to consider `info.bodyTypeDeps`.
479+
-- There is no need to consider `info.bodyTypeDeps` in this computation.
478480
used := updateArrayFromBackDeps used info.bodyDeps
479481
-- For each used `have`, in reverse order, update `used`.
480482
for i in [0:numHaves] do
@@ -515,7 +517,9 @@ private def SimpHaveResult.toResult : SimpHaveResult → Result
515517
| { expr, proof, modified, .. } => { expr, proof? := if modified then some proof else none }
516518

517519
/--
518-
Routine for simplifying `let` expressions.
520+
Routine for simplifying `have` telescopes. Used by `simpLet`.
521+
This is optimized to be able to handle deep `have` telescopes while avoiding quadratic complexity
522+
arising from the locally nameless expression representations.
519523
520524
Consider a `have` telescope:
521525
```
@@ -530,84 +534,38 @@ If `xᵢ` neither appears in `b` nor any `Tⱼ` nor any `vⱼ`, then its `have`
530534
Unused `have`s can be eliminated, which we do if `cfg.zetaUnused` is true.
531535
Note that it is best to clear unused `have`s from the right,
532536
to eliminate any uses from unused `have`s.
537+
This is why we honor `zetaUnused` here even though `reduceStep` is also responsible for it.
533538
534-
We assume that dependent `let`s are dependent.
535-
At the first `let` in a `let`/`have` telescope,
536-
we attempt to transform it into a `have` if `cfg.letToHave` is true.
537-
If that does not change it, then it is only `dsimp`ed.
539+
If `debug.simp.check` is enabled then we typecheck the resulting expression and proof.
538540
-/
539-
partial def simpLet (e : Expr) : SimpM Result := do
540-
assert! e.isLet
541-
trace[Debug.Meta.Tactic.simp] "let{indentExpr e}"
542-
let cfg ← getConfig
543-
/-
544-
In `zeta` mode, we zeta reduce the whole telescope,
545-
unless `zetaHave` is false, in which case we zeta reduce up to the first `have`.
546-
The `if` makes sure that reduction will make progress.
547-
-/
548-
if cfg.zeta && (!e.letNondep! || cfg.zetaHave) then
549-
let expandLet (e : Expr) (vs : Array Expr) : Expr :=
550-
match e with
551-
| .letE _ _ v b nondep =>
552-
if !nondep || cfg.zetaHave then
553-
expandLet b (vs.push v)
554-
else
555-
e.instantiateRev vs
556-
| _ => e.instantiateRev vs
557-
return { expr := expandLet e #[] }
558-
else if !e.letNondep! then
559-
/-
560-
This is a `let` and not a `have`.
561-
Ideally non-dependent `let`s have already been transformed to `have`s.
562-
First, we can still respect `cfg.zetaUnused`:
563-
-/
564-
if cfg.zetaUnused && !e.letBody!.hasLooseBVars then
565-
return ← simp e.letBody!
566-
/-
567-
When `cfg.letToHave` is true we apply the transformation, which we commit to only if
568-
the head `let` becomes a `have`.
569-
Note: we applied `cfg.zetaUnused` ourselves above even though `dsimp` does it so that we can avoid an unnecessary `letToHave`.
570-
-/
571-
if cfg.letToHave then
572-
let e' ← letToHave e
573-
if e'.isLet && e'.letNondep! then
574-
trace[Debug.Meta.Tactic.simp] "let => have{indentExpr e'}"
575-
return ← simpHaveTelescope e'
576-
/-
577-
At this point, this is (very likely) a dependent `let`.
578-
We fall back to doing only definitional simplification.
579-
580-
Note that for `let x := v; b`, if we had a rewrite `h : b = b'` given `x := v` in the local context,
581-
we could abstract `x` to get `(let x := v; h) : (let x := v; b = b')` and then use the fact that
582-
this type is definitionally equal to `(let x := v; b) = (let x := v; b')`.
583-
-/
584-
return { expr := (← dsimp e) }
585-
else
586-
simpHaveTelescope e
587-
where
588-
simpHaveTelescope (e : Expr) : SimpM Result := do
589-
let cfg ← getConfig
541+
def simpHaveTelescope (e : Expr) : SimpM Result := do
542+
Prod.fst <$> withTraceNode `Debug.Meta.Tactic.simp (fun
543+
| .ok (_, used, fixed, modified) => pure m!"{checkEmoji} have telescope; used: {used}; fixed: {fixed.toArray}; modified: {modified}"
544+
| .error ex => pure m!"{crossEmoji} {ex.toMessageData}") do
590545
let info ← getHaveTelescopeInfo e
591546
assert! !info.haveInfo.isEmpty
592-
let (fixed, used) ← info.computeFixedUsed (keepUnused := !cfg.zetaUnused)
593-
trace[Debug.Meta.Tactic.simp] "have telescope; used: {used}; fixed: {fixed.toArray}"
594-
let sz := info.haveInfo.size
595-
let r ← simpHaveTelescopeAux info fixed used e 0 (Array.mkEmpty sz)
596-
trace[Debug.Meta.Tactic.simp] "have telescope; used: {used}; fixed: {fixed.toArray}; (modified: {r.modified})"
547+
let (fixed, used) ← info.computeFixedUsed (keepUnused := !(← getConfig).zetaUnused)
548+
let r ← simpHaveTelescopeAux info fixed used e 0 (Array.mkEmpty info.haveInfo.size)
597549
if r.modified && debug.simp.check.get (← getOptions) then
598550
check r.expr
599551
check r.proof
600-
return r.toResult
552+
return (r.toResult, used, fixed, r.modified)
553+
where
601554
/-
602-
Re-enters the telescope in `info` while simplifying according to `fixed`/`used`.
555+
Re-enters the telescope recorded in `info` using `withExistingLocalDecls` while simplifying according to `fixed`/`used`.
603556
Note that we must use the low-level `Expr` APIs because the expressions may contain loose bound variables.
557+
Note also that we don't enter the body's local context all at once, since we need to be sure that
558+
when we simplify values they have their correct local context.
604559
-/
605560
simpHaveTelescopeAux (info : HaveTelescopeInfo) (fixed : Std.HashSet Nat) (used : Array Bool) (e : Expr) (i : Nat) (xs : Array Expr) : SimpM SimpHaveResult := do
606561
if h : i < info.haveInfo.size then
607562
let hinfo := info.haveInfo[i]
563+
-- `x` and `val` are the fvar and value with respect to the local context.
608564
let x := hinfo.decl.toExpr
609565
let val := hinfo.decl.value true
566+
-- Invariant: `v == val.abstract xs`.
610567
let .letE n t v b true := e | unreachable!
568+
-- Universe levels to use for each of the `have_*` theorems. It's the level of `val` and the level of the body.
611569
let us := [hinfo.level, info.level]
612570
if !used.getD i true then
613571
/-
@@ -618,7 +576,7 @@ where
618576
Complication: Unused `have`s might only be transitively unused.
619577
This means that `b.hasLooseBVar 0` might actually be true.
620578
This matters because `t` and `v` appear in the proof term.
621-
We use a dummy `x` for debugging purposes. (Recall that `Expr.abstract` ignores non-fvar/mvars.)
579+
We use a dummy `x` for debugging purposes. (Recall that `Expr.abstract` skips non-fvar/mvars.)
622580
-/
623581
let x := Expr.const `_simp_let_unused_dummy []
624582
let { expr, exprType, proof, modified } ← simpHaveTelescopeAux info fixed used b (i + 1) (xs.push x)
@@ -629,7 +587,7 @@ where
629587
(mkLambda n .default t proof)
630588
return { expr, exprType, proof, modified := true }
631589
else
632-
-- If not modified, this must have been a truly unused `have`.
590+
-- If not modified, this must have been a non-transitively unused `have`.
633591
let proof := mkApp6 (mkConst ``have_unused us) t exprType v expr expr
634592
(mkApp2 (mkConst ``Eq.refl [info.level]) exprType expr)
635593
return { expr, exprType, proof, modified := true }
@@ -699,6 +657,49 @@ where
699657
let proof := (← r.getProof).abstract xs
700658
return { expr, exprType, proof, modified := true }
701659

660+
/--
661+
Routine for simplifying `let` expressions.
662+
663+
If it is a `have`, we use `simpHaveTelescope` to simplify entire telescopes at once, to avoid quadratic behavior
664+
arising from locally nameless expression representations.
665+
666+
We assume that dependent `let`s are dependent,
667+
but if `Config.letToHave` is enabled then we attempt to transform it into a `have`.
668+
If that does not change it, then it is only `dsimp`ed.
669+
-/
670+
def simpLet (e : Expr) : SimpM Result := do
671+
withTraceNode `Debug.Meta.Tactic.simp (return m!"{exceptEmoji ·} let{indentExpr e}") do
672+
assert! e.isLet
673+
/-
674+
Recall: `simpLet` is called after `reduceStep` is applied, so `simpLet` is not responsible for zeta reduction.
675+
Hence, the expression is a `let` or `have` that is not reducible in the current configuration.
676+
-/
677+
if e.letNondep! then
678+
simpHaveTelescope e
679+
else
680+
/-
681+
When `cfg.letToHave` is true, we use `letToHave` to decide whether or not this `let` is dependent.
682+
If it becomes a `have`, then we can jump right into simplifying the `have` telescope.
683+
-/
684+
let e ←
685+
if (← getConfig).letToHave then
686+
let eNew ← letToHave e
687+
if eNew.isLet && eNew.letNondep! then
688+
trace[Debug.Meta.Tactic.simp] "letToHave ==>{indentExpr eNew}"
689+
return ← simpHaveTelescope eNew
690+
pure eNew
691+
else
692+
pure e
693+
/-
694+
The `let` is dependent.
695+
We fall back to doing only definitional simplification.
696+
697+
Note that for `let x := v; b`, if we had a rewrite `h : b = b'` given `x := v` in the local context,
698+
we could abstract `x` to get `(let x := v; h) : (let x := v; b = b')` and then use the fact that
699+
this type is definitionally equal to `(let x := v; b) = (let x := v; b')`.
700+
-/
701+
return { expr := (← dsimp e) }
702+
702703
private def dsimpReduce : DSimproc := fun e => do
703704
let mut eNew ← reduce e
704705
if eNew.isFVar then

src/Lean/Meta/Tactic/Simp/Types.lean

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ private def mkIndexConfig (c : Config) : MetaM ConfigWithKey := do
134134
beta := c.beta
135135
iota := c.iota
136136
zeta := c.zeta
137+
zetaHave := c.zetaHave
137138
zetaUnused := c.zetaUnused
138139
zetaDelta := c.zetaDelta
139140
etaStruct := c.etaStruct
@@ -152,8 +153,9 @@ private def mkMetaConfig (c : Config) : MetaM ConfigWithKey := do
152153
let curr ← Meta.getConfig
153154
return { curr with
154155
beta := c.beta
155-
zeta := c.zeta
156156
iota := c.iota
157+
zeta := c.zeta
158+
zetaHave := c.zetaHave
157159
zetaUnused := c.zetaUnused
158160
zetaDelta := c.zetaDelta
159161
etaStruct := c.etaStruct

0 commit comments

Comments
 (0)