lean4-htt/src/Lean/Meta/Tactic/Simp/SimpAll.lean
Kim Morrison 72e952eadc
chore: avoid runtime array bounds checks (#6134)
This PR avoids runtime array bounds checks in places where it can
trivially be done at compile time.

None of these changes are of particular consequence: I mostly wanted to
learn how much we do this, and what the obstacles are to doing it less.
2024-11-21 05:04:52 +00:00

160 lines
6 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Clear
import Lean.Meta.Tactic.Util
import Lean.Meta.Tactic.Simp.Main
namespace Lean.Meta
open Simp (Stats SimprocsArray)
namespace SimpAll
structure Entry where
fvarId : FVarId -- original fvarId
userName : Name
id : Origin -- id of the theorem at `SimpTheorems`
origType : Expr
type : Expr
proof : Expr
deriving Inhabited
structure State where
modified : Bool := false
mvarId : MVarId
entries : Array Entry := #[]
ctx : Simp.Context
simprocs : SimprocsArray
usedTheorems : Simp.UsedSimps := {}
diag : Simp.Diagnostics := {}
abbrev M := StateRefT State MetaM
private def initEntries : M Unit := do
let hs ← (← get).mvarId.withContext do getPropHyps
let hsNonDeps ← (← get).mvarId.getNondepPropHyps
let mut simpThms := (← get).ctx.simpTheorems
for h in hs do
unless simpThms.isErased (.fvar h) do
let localDecl ← h.getDecl
let proof := localDecl.toExpr
let ctx := (← get).ctx
simpThms ← simpThms.addTheorem (.fvar h) proof (config := ctx.indexConfig)
modify fun s => { s with ctx := s.ctx.setSimpTheorems simpThms }
if hsNonDeps.contains h then
-- We only simplify nondependent hypotheses
let type ← instantiateMVars localDecl.type
let entry : Entry := { fvarId := h, userName := localDecl.userName, id := .fvar h, origType := type, type, proof }
modify fun s => { s with entries := s.entries.push entry }
private abbrev getSimpTheorems : M SimpTheoremsArray :=
return (← get).ctx.simpTheorems
private partial def loop : M Bool := do
modify fun s => { s with modified := false }
let simprocs := (← get).simprocs
-- simplify entries
let entries := (← get).entries
for h : i in [:entries.size] do
let entry := entries[i]
let ctx := (← get).ctx
-- We disable the current entry to prevent it to be simplified to `True`
let simpThmsWithoutEntry := (← getSimpTheorems).eraseTheorem entry.id
let ctx := ctx.setSimpTheorems simpThmsWithoutEntry
let (r, stats) ← simpStep (← get).mvarId entry.proof entry.type ctx simprocs (stats := { (← get) with })
modify fun s => { s with usedTheorems := stats.usedTheorems, diag := stats.diag }
match r with
| none => return true -- closed the goal
| some (proofNew, typeNew) =>
unless typeNew == entry.type do
/- We must erase the `id` for the simplified theorem. Otherwise,
the previous versions can be used to self-simplify the new version. For example, suppose we have
```
x : Nat
h : x ≠ 0
⊢ Unit
```
In the first round, `h : x ≠ 0` is simplified to `h : ¬ x = 0`.
It is also important for avoiding identical hypotheses to simplify each other to `True`.
Example
```
...
h₁ : p a
h₂ : p a
⊢ q a
```
`h₁` is first simplified to `True`. If we don't remove `h₁` from the set of simp theorems, it will
be used to simplify `h₂` to `True` and information is lost.
We must use `mkExpectedTypeHint` because `inferType proofNew` may not be equal to `typeNew` when
we have theorems marked with `rfl`.
-/
trace[Meta.Tactic.simp.all] "entry.id: {← ppOrigin entry.id}, {entry.type} => {typeNew}"
let mut simpThmsNew := (← getSimpTheorems).eraseTheorem (.fvar entry.fvarId)
let idNew ← mkFreshId
simpThmsNew ← simpThmsNew.addTheorem (.other idNew) (← mkExpectedTypeHint proofNew typeNew) (config := ctx.indexConfig)
modify fun s => { s with
modified := true
ctx := ctx.setSimpTheorems simpThmsNew
entries[i] := { entry with type := typeNew, proof := proofNew, id := .other idNew }
}
-- simplify target
let mvarId := (← get).mvarId
let (r, stats) ← simpTarget mvarId (← get).ctx simprocs (stats := { (← get) with })
modify fun s => { s with usedTheorems := stats.usedTheorems, diag := stats.diag }
match r with
| none => return true
| some mvarIdNew =>
unless mvarId == mvarIdNew do
modify fun s => { s with
modified := true
mvarId := mvarIdNew
}
if (← get).modified then
loop
else
return false
def main : M (Option MVarId) := do
initEntries
if (← loop) then
return none -- close the goal
else
let mvarId := (← get).mvarId
-- Prior to #2334, the logic here was to re-assert all hypotheses and call `tryClearMany` on them all.
-- This had the effect that the order of hypotheses was sometimes modified, whether or not any where simplified.
-- Now we only re-assert the first modified hypothesis,
-- along with all subsequent hypotheses, so as to preserve the order of hypotheses.
let mut toAssert := #[]
let mut toClear := #[]
let mut modified := false
for e in (← get).entries do
if e.type.isTrue then
-- Do not assert `True` hypotheses
toClear := toClear.push e.fvarId
else if modified || e.type != e.origType then
toClear := toClear.push e.fvarId
toAssert := toAssert.push { userName := e.userName, type := e.type, value := e.proof }
modified := true
let (_, mvarId) ← mvarId.assertHypotheses toAssert
mvarId.tryClearMany toClear
end SimpAll
def simpAll (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (stats : Stats := {}) : MetaM (Option MVarId × Stats) := do
mvarId.withContext do
let (r, s) ← SimpAll.main.run { stats with mvarId, ctx, simprocs }
if let .some mvarIdNew := r then
if ctx.config.failIfUnchanged && mvarId == mvarIdNew then
throwError "simp_all made no progress"
return (r, { s with })
builtin_initialize
registerTraceClass `Meta.Tactic.simp.all
end Lean.Meta