Skip to content

Commit e3e3ff8

Browse files
committed
perf(to_dual): first try translating without inserting casts (#35131)
This PR addresses the performance regression introduced by #32438. The problem is that deciding where casts need to be inserted is an expensive operation (similarly expensive to kernel type checking). I expect that in category theory this will be particularly significant. Most uses of `to_dual` don't need any casts to be inserted at all. So, it is more efficient to simply first try translating without inserting casts. And if that fails, we insert casts and then try again. So after this PR, in the failure case, `to_dual` will check the term 4 times: - first try the translation without casts - then determine where casts should go - then try the translation of this - then use `Meta.check` to get a nicer error message. This is OK, since the failure case is not the performance critical case.
1 parent cb07be6 commit e3e3ff8

File tree

3 files changed

+60
-47
lines changed

3 files changed

+60
-47
lines changed

Mathlib/Tactic/Translate/Core.lean

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -563,26 +563,68 @@ def applyReplacementLambda (t : TranslateData) (dontTranslate : List Nat) (e : E
563563

564564
/-- Run `applyReplacementFun` on the given `srcDecl` to make a new declaration with name `tgt`. -/
565565
def updateDecl (t : TranslateData) (tgt : Name) (srcDecl : ConstantInfo)
566-
(reorder : Reorder) (dont : List Nat) : MetaM ConstantInfo := do
566+
(reorder : Reorder) (dont : List Nat)
567+
(unfoldBoundaries? : Option UnfoldBoundary.UnfoldBoundaries) : MetaM ConstantInfo := do
567568
let mut decl := srcDecl.updateName tgt
568569
if reorder.any (·.contains 0) then
569570
decl := decl.updateLevelParams decl.levelParams.swapFirstTwo
570571
let mut value := decl.value! (allowOpaque := true)
571-
if let some b := t.unfoldBoundaries? then
572+
if let some b := unfoldBoundaries? then
572573
value ← b.cast (← b.insertBoundaries value t.attrName) decl.type t.attrName
573574
trace[translate] "Value before translation:{indentExpr value}"
574575
value ← reorderLambda reorder <| ← applyReplacementLambda t dont value
575-
if let some b := t.unfoldBoundaries? then
576+
if let some b := unfoldBoundaries? then
576577
value ← b.unfoldInsertions value
577578
decl := decl.updateValue value
578579
let mut type := decl.type
579-
if let some b := t.unfoldBoundaries? then
580+
if let some b := unfoldBoundaries? then
580581
type ← b.insertBoundaries decl.type t.attrName
581582
type ← reorderForall reorder <| ← applyReplacementForall t dont <| renameBinderNames t type
582-
if let some b := t.unfoldBoundaries? then
583+
if let some b := unfoldBoundaries? then
583584
type ← b.unfoldInsertions type
584585
return decl.updateType type
585586

587+
/-- Translate the source declaration and then run `addDecl`. If the kernel throws an error,
588+
try to emit a better error message.
589+
590+
For efficiency in `to_dual`, we first run `updateDecl` without any `UnfoldBoundaries`,
591+
and only if that fails do we try to include them.
592+
The reason is that in the most common case, `to_dual` succeeds without needing to insert
593+
unfold boundaries, and figuring out whether to insert them can be quite expensive. -/
594+
def updateAndAddDecl (t : TranslateData) (tgt : Name) (srcDecl : ConstantInfo)
595+
(reorder : Reorder) (dont : List Nat) : MetaM ConstantInfo :=
596+
-- Set `Elab.async` to `false` so that we can catch kernel errors.
597+
withOptions (Elab.async.set · false) do
598+
let decl ←
599+
if let some unfoldBoundaries := t.unfoldBoundaries? then
600+
let env ← getEnv
601+
-- First attempt to generate the translation without unfold boundaries.
602+
let declAttempt ← updateDecl t tgt srcDecl reorder dont none
603+
try
604+
addDecl declAttempt.toDeclaration!
605+
trace[translate] "generating\n{tgt} : {declAttempt.type} :=\
606+
{indentExpr <| declAttempt.value! (allowOpaque := true)}"
607+
return declAttempt -- early return
608+
catch _ =>
609+
setEnv env
610+
updateDecl t tgt srcDecl reorder dont (unfoldBoundaries.getState env)
611+
else
612+
updateDecl t tgt srcDecl reorder dont none
613+
trace[translate] "generating\n{tgt} : {decl.type} :=\
614+
{indentExpr <| decl.value! (allowOpaque := true)}"
615+
try
616+
addDecl decl.toDeclaration!
617+
return decl
618+
catch ex =>
619+
try
620+
withoutExporting <| check (decl.value! (allowOpaque := true))
621+
catch ex =>
622+
throwError "@[{t.attrName}] failed to add declaration `{decl.name}`.\n \
623+
The translated value is not type correct.\n \
624+
For help, see the docstring of `to_additive`, section `Troubleshooting`.\n\
625+
{ex.toMessageData}"
626+
throwError "@[{t.attrName}] failed. Nested error message:\n{ex.toMessageData}"
627+
586628
/--
587629
Find the argument of `nm` that appears in the first translatable (type-class) argument.
588630
Returns 1 if there are no types with a translatable class as arguments.
@@ -702,26 +744,10 @@ partial def transformDeclRec (t : TranslateData) (ref : Syntax) (pre tgt_pre src
702744
let namesSrc := (← getConstInfo src).type.getForallBinderNames
703745
pure <| dontTranslate.filterMap (namesPre[·]? >>= namesSrc.idxOf?)
704746
-- now transform the source declaration
705-
let trgDecl ← MetaM.run' <| updateDecl t tgt srcDecl reorder dontTranslate
706-
if src == pre && srcDecl.isThm && trgDecl.type == srcDecl.type then
747+
let tgtDecl ← MetaM.run' <| updateAndAddDecl t tgt srcDecl reorder dontTranslate
748+
if src == pre && srcDecl.isThm && tgtDecl.type == srcDecl.type then
707749
Linter.logLintIf linter.translateRedundant ref m!"`{t.attrName}` did not change the type \
708750
of theorem `{.ofConstName src}`. Please remove the attribute."
709-
let value := trgDecl.value! (allowOpaque := true)
710-
trace[translate] "generating\n{tgt} : {trgDecl.type} :=\n {value}"
711-
try
712-
-- set `Elab.async` to `false` in order to be able to catch kernel errors
713-
withOptions (Elab.async.set · false) do
714-
addDecl trgDecl.toDeclaration!
715-
catch ex =>
716-
-- Try to emit a better error message if the kernel throws an error.
717-
try
718-
withoutExporting <| MetaM.run' <| check value
719-
catch ex =>
720-
throwError "@[{t.attrName}] failed. \
721-
The translated value is not type correct. For help, see the docstring \
722-
of `to_additive`, section `Troubleshooting`. \
723-
Failed to add declaration\n{tgt}:\n{ex.toMessageData}"
724-
throwError "@[{t.attrName}] failed. Nested error message:\n{ex.toMessageData}"
725751
/- If `src` is explicitly marked as `noncomputable`, then add the new decl as a declaration but
726752
do not compile it, and mark is as noncomputable. Otherwise, only log errors in compiling if `src`
727753
has executable code.
@@ -740,8 +766,8 @@ partial def transformDeclRec (t : TranslateData) (ref : Syntax) (pre tgt_pre src
740766
if isMarkedMeta (← getEnv) src then
741767
-- We need to mark `tgt` as `meta` before running `compileDecl`
742768
modifyEnv (markMeta · tgt)
743-
compileDecl trgDecl.toDeclaration! (logErrors := (IR.findEnvDecl (← getEnv) src).isSome)
744-
if let .defnInfo { hints := .abbrev, .. } := trgDecl then
769+
compileDecl tgtDecl.toDeclaration! (logErrors := (IR.findEnvDecl (← getEnv) src).isSome)
770+
if let .defnInfo { hints := .abbrev, .. } := tgtDecl then
745771
if (← getReducibilityStatus src) == .reducible then
746772
setReducibilityStatus tgt .reducible
747773
if Compiler.getInlineAttribute? (← getEnv) src == some .inline then
@@ -917,7 +943,8 @@ partial def checkExistingType (t : TranslateData) (src tgt : Name) (cfg : Config
917943
throwError "`{t.attrName}` validation failed:\n expected {srcDecl.levelParams.length} \
918944
universe levels, but '{tgt}' has {tgtDecl.levelParams.length} universe levels"
919945
let mut srcType := srcDecl.type
920-
if let some b := t.unfoldBoundaries? then
946+
let unfoldBoundaries? ← t.unfoldBoundaries?.mapM (return ·.getState (← getEnv))
947+
if let some b := unfoldBoundaries? then
921948
srcType ← b.insertBoundaries srcType t.attrName
922949
srcType ← applyReplacementForall t cfg.dontTranslate srcType
923950
let reorder' := guessReorder srcType tgtDecl.type
@@ -942,7 +969,7 @@ partial def checkExistingType (t : TranslateData) (src tgt : Name) (cfg : Config
942969
If you need to give a hint to `{t.attrName}` to translate expressions involving `{src}`,\n\
943970
use `{t.attrName}_do_translate` instead"
944971
srcType ← reorderForall reorder srcType
945-
if let some b := t.unfoldBoundaries? then
972+
if let some b := unfoldBoundaries? then
946973
srcType ← b.unfoldInsertions srcType
947974
if reorder.any (·.contains 0) then
948975
srcDecl := srcDecl.updateLevelParams srcDecl.levelParams.swapFirstTwo

Mathlib/Tactic/Translate/UnfoldBoundary.lean

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def mkAppWithCast (b : UnfoldBoundaries) (f a : Expr) : SimpM Expr :=
123123
return f.app (← mkCast b a d)
124124

125125
/-- Modify `e` so that it has type `expectedType` if the constants in `b` cannot be unfolded. -/
126-
def UnfoldBoundaries.cast (b : UnfoldBoundaries) (e expectedType : Expr) (attr : Name) :
126+
public def UnfoldBoundaries.cast (b : UnfoldBoundaries) (e expectedType : Expr) (attr : Name) :
127127
MetaM Expr :=
128128
run b <|
129129
try
@@ -137,7 +137,7 @@ def UnfoldBoundaries.cast (b : UnfoldBoundaries) (e expectedType : Expr) (attr :
137137
Note: it may be that `e` contains some constant whose type is not well typed in this setting.
138138
We don't make an effort to replace such constants.
139139
It seems that this approximation works well enough. -/
140-
def UnfoldBoundaries.insertBoundaries (b : UnfoldBoundaries) (e : Expr) (attr : Name) :
140+
public def UnfoldBoundaries.insertBoundaries (b : UnfoldBoundaries) (e : Expr) (attr : Name) :
141141
MetaM Expr :=
142142
run b <| Meta.transform e (post := fun e ↦ e.withApp fun f args =>
143143
try
@@ -147,7 +147,7 @@ def UnfoldBoundaries.insertBoundaries (b : UnfoldBoundaries) (e : Expr) (attr :
147147
well typed\n\n{ex.toMessageData}")
148148

149149
/-- Unfold all of the auxiliary functions that were inserted as unfold boundaries. -/
150-
def UnfoldBoundaries.unfoldInsertions (e : Expr) (b : UnfoldBoundaries) : CoreM Expr :=
150+
public def UnfoldBoundaries.unfoldInsertions (e : Expr) (b : UnfoldBoundaries) : CoreM Expr :=
151151
-- This is the same as `Meta.deltaExpand`, but with an extra beta reduction.
152152
Core.transform e fun e => do
153153
if let some e ← delta? e b.insertionFuns.contains then
@@ -182,19 +182,4 @@ public def registerUnfoldBoundaryExt : IO UnfoldBoundaryExt := do
182182
addImportedFn as := as.foldl (Array.foldl (·.insert ·)) {}
183183
}
184184

185-
@[inherit_doc UnfoldBoundaries.cast]
186-
public def UnfoldBoundaryExt.cast (b : UnfoldBoundaryExt) (e expectedType : Expr) (attr : Name) :
187-
MetaM Expr := do
188-
(b.getState (← getEnv)).cast e expectedType attr
189-
190-
@[inherit_doc UnfoldBoundaries.insertBoundaries]
191-
public def UnfoldBoundaryExt.insertBoundaries (b : UnfoldBoundaryExt) (e : Expr) (attr : Name) :
192-
MetaM Expr := do
193-
(b.getState (← getEnv)).insertBoundaries e attr
194-
195-
@[inherit_doc UnfoldBoundaries.unfoldInsertions]
196-
public def UnfoldBoundaryExt.unfoldInsertions (e : Expr) (b : UnfoldBoundaryExt) : CoreM Expr := do
197-
(b.getState (← getEnv)).unfoldInsertions e
198-
199-
200185
end Mathlib.Tactic.UnfoldBoundary

MathlibTest/ToDual.lean

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ attribute [to_dual existing] Lattice.toSemilatticeInf
3232

3333
-- we still cannot reorder arguments of arguments, so `SemilatticeInf.mk` is not translatable
3434
/--
35-
error: @[to_dual] failed. The translated value is not type correct. For help, see the docstring of `to_additive`, section `Troubleshooting`. Failed to add declaration
36-
instSemilatticeSupOfForallLeForallMax:
35+
error: @[to_dual] failed to add declaration `instSemilatticeSupOfForallLeForallMax`.
36+
The translated value is not type correct.
37+
For help, see the docstring of `to_additive`, section `Troubleshooting`.
3738
Application type mismatch: The argument
3839
le_inf
3940
has type

0 commit comments

Comments
 (0)