feat: non exponential codegen for reset-reuse (#12665)

This PR ports the expand reset/reuse pass from IR to LCNF. In addition
it prevents exponential code generation unlike the old one. This results
in a ~15% decrease in binary size and slight speedups across the board.

The change also removes the "is this reset actually used" syntactic
approximation as the previous passes guarantee (at the moment) that all
uses are in the continuation and will thus be caught by this.
This commit is contained in:
Henrik Böving
2026-02-26 10:35:45 +01:00
committed by GitHub
parent 805060c0a8
commit d88ac25bd1
17 changed files with 791 additions and 929 deletions

View File

@@ -32,6 +32,89 @@ unsafe axiom lcAny : Type
/-- Internal representation of `Void` in the compiler. -/
unsafe axiom lcVoid : Type
set_option bootstrap.inductiveCheckResultingUniverse false in
/--
The canonical universe-polymorphic type with just one element.
It should be used in contexts that require a type to be universe polymorphic, thus disallowing
`Unit`.
-/
inductive PUnit : Sort u where
/-- The only element of the universe-polymorphic unit type. -/
| unit : PUnit
/--
The equality relation. It has one introduction rule, `Eq.refl`.
We use `a = b` as notation for `Eq a b`.
A fundamental property of equality is that it is an equivalence relation.
```
variable (α : Type) (a b c d : α)
variable (hab : a = b) (hcb : c = b) (hcd : c = d)
example : a = d :=
Eq.trans (Eq.trans hab (Eq.symm hcb)) hcd
```
Equality is much more than an equivalence relation, however. It has the important property that every assertion
respects the equivalence, in the sense that we can substitute equal expressions without changing the truth value.
That is, given `h1 : a = b` and `h2 : p a`, we can construct a proof for `p b` using substitution: `Eq.subst h1 h2`.
Example:
```
example (α : Type) (a b : α) (p : α → Prop)
(h1 : a = b) (h2 : p a) : p b :=
Eq.subst h1 h2
example (α : Type) (a b : α) (p : α → Prop)
(h1 : a = b) (h2 : p a) : p b :=
h1 ▸ h2
```
The triangle in the second presentation is a macro built on top of `Eq.subst` and `Eq.symm`, and you can enter it by typing `\t`.
For more information: [Equality](https://lean-lang.org/theorem_proving_in_lean4/quantifiers_and_equality.html#equality)
-/
inductive Eq : α α Prop where
/-- `Eq.refl a : a = a` is reflexivity, the unique constructor of the
equality type. See also `rfl`, which is usually used instead. -/
| refl (a : α) : Eq a a
/-- Non-dependent recursor for the equality type. -/
@[simp] abbrev Eq.ndrec.{u1, u2} {α : Sort u2} {a : α} {motive : α Sort u1} (m : motive a) {b : α} (h : Eq a b) : motive b :=
h.rec m
/--
Heterogeneous equality. `a ≍ b` asserts that `a` and `b` have the same
type, and casting `a` across the equality yields `b`, and vice versa.
You should avoid using this type if you can. Heterogeneous equality does not
have all the same properties as `Eq`, because the assumption that the types of
`a` and `b` are equal is often too weak to prove theorems of interest. One
public important non-theorem is the analogue of `congr`: If `f ≍ g` and `x ≍ y`
and `f x` and `g y` are well typed it does not follow that `f x ≍ g y`.
(This does follow if you have `f = g` instead.) However if `a` and `b` have
the same type then `a = b` and `a ≍ b` are equivalent.
-/
inductive HEq : {α : Sort u} α {β : Sort u} β Prop where
/-- Reflexivity of heterogeneous equality. -/
| refl (a : α) : HEq a a
/--
The Boolean values, `true` and `false`.
Logically speaking, this is equivalent to `Prop` (the type of propositions). The distinction is
public important for programming: both propositions and their proofs are erased in the code generator,
while `Bool` corresponds to the Boolean type in most programming languages and carries precisely one
bit of run-time information.
-/
inductive Bool : Type where
/-- The Boolean value `false`, not to be confused with the proposition `False`. -/
| false : Bool
/-- The Boolean value `true`, not to be confused with the proposition `True`. -/
| true : Bool
export Bool (false true)
/-- Compute whether `x` is a tagged pointer or not. -/
@[extern "lean_is_scalar"]
unsafe axiom isScalarObj {α : Type u} (x : α) : Bool
/--
The identity function. `id` takes an implicit argument `α : Sort u`
@@ -115,16 +198,7 @@ does.) Example:
-/
abbrev inferInstanceAs (α : Sort u) [i : α] : α := i
set_option bootstrap.inductiveCheckResultingUniverse false in
/--
The canonical universe-polymorphic type with just one element.
It should be used in contexts that require a type to be universe polymorphic, thus disallowing
`Unit`.
-/
inductive PUnit : Sort u where
/-- The only element of the universe-polymorphic unit type. -/
| unit : PUnit
/--
The canonical type with one element. This element is written `()`.
@@ -245,42 +319,6 @@ For more information: [Propositional Logic](https://lean-lang.org/theorem_provin
@[macro_inline] def absurd {a : Prop} {b : Sort v} (h₁ : a) (h₂ : Not a) : b :=
(h₂ h₁).rec
/--
The equality relation. It has one introduction rule, `Eq.refl`.
We use `a = b` as notation for `Eq a b`.
A fundamental property of equality is that it is an equivalence relation.
```
variable (α : Type) (a b c d : α)
variable (hab : a = b) (hcb : c = b) (hcd : c = d)
example : a = d :=
Eq.trans (Eq.trans hab (Eq.symm hcb)) hcd
```
Equality is much more than an equivalence relation, however. It has the important property that every assertion
respects the equivalence, in the sense that we can substitute equal expressions without changing the truth value.
That is, given `h1 : a = b` and `h2 : p a`, we can construct a proof for `p b` using substitution: `Eq.subst h1 h2`.
Example:
```
example (α : Type) (a b : α) (p : α → Prop)
(h1 : a = b) (h2 : p a) : p b :=
Eq.subst h1 h2
example (α : Type) (a b : α) (p : α → Prop)
(h1 : a = b) (h2 : p a) : p b :=
h1 ▸ h2
```
The triangle in the second presentation is a macro built on top of `Eq.subst` and `Eq.symm`, and you can enter it by typing `\t`.
For more information: [Equality](https://lean-lang.org/theorem_proving_in_lean4/quantifiers_and_equality.html#equality)
-/
inductive Eq : α α Prop where
/-- `Eq.refl a : a = a` is reflexivity, the unique constructor of the
equality type. See also `rfl`, which is usually used instead. -/
| refl (a : α) : Eq a a
/-- Non-dependent recursor for the equality type. -/
@[simp] abbrev Eq.ndrec.{u1, u2} {α : Sort u2} {a : α} {motive : α Sort u1} (m : motive a) {b : α} (h : Eq a b) : motive b :=
h.rec m
/--
`rfl : a = a` is the unique constructor of the equality type. This is the
same as `Eq.refl` except that it takes `a` implicitly instead of explicitly.
@@ -477,21 +515,6 @@ Unsafe auxiliary constant used by the compiler to erase `Quot.lift`.
-/
unsafe axiom Quot.lcInv {α : Sort u} {r : α α Prop} (q : Quot r) : α
/--
Heterogeneous equality. `a ≍ b` asserts that `a` and `b` have the same
type, and casting `a` across the equality yields `b`, and vice versa.
You should avoid using this type if you can. Heterogeneous equality does not
have all the same properties as `Eq`, because the assumption that the types of
`a` and `b` are equal is often too weak to prove theorems of interest. One
public important non-theorem is the analogue of `congr`: If `f ≍ g` and `x ≍ y`
and `f x` and `g y` are well typed it does not follow that `f x ≍ g y`.
(This does follow if you have `f = g` instead.) However if `a` and `b` have
the same type then `a = b` and `a ≍ b` are equivalent.
-/
inductive HEq : {α : Sort u} α {β : Sort u} β Prop where
/-- Reflexivity of heterogeneous equality. -/
| refl (a : α) : HEq a a
/-- A version of `HEq.refl` with an implicit argument. -/
@[match_pattern] protected def HEq.rfl {α : Sort u} {a : α} : HEq a a :=
@@ -599,23 +622,6 @@ theorem Or.resolve_left (h: Or a b) (na : Not a) : b := h.elim (absurd · na) i
theorem Or.resolve_right (h: Or a b) (nb : Not b) : a := h.elim id (absurd · nb)
theorem Or.neg_resolve_left (h : Or (Not a) b) (ha : a) : b := h.elim (absurd ha) id
theorem Or.neg_resolve_right (h : Or a (Not b)) (nb : b) : a := h.elim id (absurd nb)
/--
The Boolean values, `true` and `false`.
Logically speaking, this is equivalent to `Prop` (the type of propositions). The distinction is
public important for programming: both propositions and their proofs are erased in the code generator,
while `Bool` corresponds to the Boolean type in most programming languages and carries precisely one
bit of run-time information.
-/
inductive Bool : Type where
/-- The Boolean value `false`, not to be confused with the proposition `False`. -/
| false : Bool
/-- The Boolean value `true`, not to be confused with the proposition `True`. -/
| true : Bool
export Bool (false true)
/--
All the elements of a type that satisfy a predicate.

View File

@@ -10,10 +10,8 @@ public import Lean.Compiler.IR.AddExtern
public import Lean.Compiler.IR.Basic
public import Lean.Compiler.IR.Format
public import Lean.Compiler.IR.CompilerM
public import Lean.Compiler.IR.PushProj
public import Lean.Compiler.IR.NormIds
public import Lean.Compiler.IR.Checker
public import Lean.Compiler.IR.ExpandResetReuse
public import Lean.Compiler.IR.UnboxResult
public import Lean.Compiler.IR.EmitC
public import Lean.Compiler.IR.Sorry
@@ -21,7 +19,6 @@ public import Lean.Compiler.IR.ToIR
public import Lean.Compiler.IR.ToIRType
public import Lean.Compiler.IR.Meta
public import Lean.Compiler.IR.SimpleGroundExpr
public import Lean.Compiler.IR.ElimDeadVars
-- The following imports are not required by the compiler. They are here to ensure that there
-- are no orphaned modules.
@@ -36,11 +33,6 @@ def compile (decls : Array Decl) : CompilerM (Array Decl) := do
logDecls `init decls
checkDecls decls
let mut decls := decls
if Compiler.LCNF.compiler.reuse.get ( getOptions) then
decls := decls.map Decl.expandResetReuse
logDecls `expand_reset_reuse decls
decls := decls.map Decl.pushProj
logDecls `push_proj decls
decls updateSorryDep decls
logDecls `result decls
checkDecls decls

View File

@@ -1,72 +0,0 @@
/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Compiler.IR.FreeVars
public section
namespace Lean.IR
/--
This function implements a simple heuristic for let values that we know may be dropped because they
are pure.
-/
private def safeToElim (e : Expr) : Bool :=
match e with
| .ctor .. | .reset .. | .reuse .. | .proj .. | .uproj .. | .sproj .. | .box .. | .unbox ..
| .lit .. | .isShared .. | .pap .. => true
-- 0-ary full applications are considered constants
| .fap _ args => args.isEmpty
| .ap .. => false
partial def reshapeWithoutDead (bs : Array FnBody) (term : FnBody) : FnBody :=
let rec reshape (bs : Array FnBody) (b : FnBody) (used : IndexSet) :=
if bs.isEmpty then b
else
let curr := bs.back!
let bs := bs.pop
let keep (_ : Unit) :=
let used := curr.collectFreeIndices used
let b := curr.setBody b
reshape bs b used
let keepIfUsedJp (vidx : Index) :=
if used.contains vidx then
keep ()
else
reshape bs b used
let keepIfUsedLet (vidx : Index) (val : Expr) :=
if used.contains vidx || !safeToElim val then
keep ()
else
reshape bs b used
match curr with
| FnBody.vdecl x _ e _ => keepIfUsedLet x.idx e
-- TODO: we should keep all struct/union projections because they are used to ensure struct/union values are fully consumed.
| FnBody.jdecl j _ _ _ => keepIfUsedJp j.idx
| _ => keep ()
reshape bs term term.freeIndices
partial def FnBody.elimDead (b : FnBody) : FnBody :=
let (bs, term) := b.flatten
let bs := modifyJPs bs elimDead
let term := match term with
| FnBody.case tid x xType alts =>
let alts := alts.map fun alt => alt.modifyBody elimDead
FnBody.case tid x xType alts
| other => other
reshapeWithoutDead bs term
/-- Eliminate dead let-declarations and join points -/
def Decl.elimDead (d : Decl) : Decl :=
match d with
| .fdecl (body := b) .. => d.updateBody! b.elimDead
| other => other
builtin_initialize registerTraceClass `compiler.ir.elim_dead (inherited := true)
end Lean.IR

View File

@@ -470,7 +470,7 @@ def emitDec (x : VarId) (n : Nat) (checkRef : Bool) : M Unit := do
emitLn ");"
def emitDel (x : VarId) : M Unit := do
emit "lean_free_object("; emit x; emitLn ");"
emit "lean_del_object("; emit x; emitLn ");"
def emitSetTag (x : VarId) (i : Nat) : M Unit := do
emit "lean_ctor_set_tag("; emit x; emit ", "; emit i; emitLn ");"

View File

@@ -1081,7 +1081,7 @@ def emitSSet (builder : LLVM.Builder llvmctx) (x : VarId) (n : Nat) (offset : Na
def emitDel (builder : LLVM.Builder llvmctx) (x : VarId) : M llvmctx Unit := do
let argtys := #[ LLVM.voidPtrType llvmctx]
let retty LLVM.voidType llvmctx
let fn getOrCreateFunctionPrototype ( getLLVMModule) retty "lean_free_object" argtys
let fn getOrCreateFunctionPrototype ( getLLVMModule) retty "lean_del_object" argtys
let xv emitLhsVal builder x
let fnty LLVM.functionType retty argtys
let _ LLVM.buildCall2 builder fnty fn #[xv]

View File

@@ -1,288 +0,0 @@
/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Compiler.IR.CompilerM
public import Lean.Compiler.IR.NormIds
public import Lean.Compiler.IR.FreeVars
import Init.Omega
public section
namespace Lean.IR.ExpandResetReuse
/-- Mapping from variable to projections -/
abbrev ProjMap := Std.HashMap VarId Expr
namespace CollectProjMap
abbrev Collector := ProjMap ProjMap
@[inline] def collectVDecl (x : VarId) (v : Expr) : Collector := fun m =>
match v with
| .proj .. => m.insert x v
| .sproj .. => m.insert x v
| .uproj .. => m.insert x v
| _ => m
partial def collectFnBody : FnBody Collector
| .vdecl x _ v b => collectVDecl x v collectFnBody b
| .jdecl _ _ v b => collectFnBody v collectFnBody b
| .case _ _ _ alts => fun s => alts.foldl (fun s alt => collectFnBody alt.body s) s
| e => if e.isTerminal then id else collectFnBody e.body
end CollectProjMap
/-- Create a mapping from variables to projections.
This function assumes variable ids have been normalized -/
def mkProjMap (d : Decl) : ProjMap :=
match d with
| .fdecl (body := b) .. => CollectProjMap.collectFnBody b {}
| _ => {}
structure Context where
projMap : ProjMap
/-- Return true iff `x` is consumed in all branches of the current block.
Here consumption means the block contains a `dec x` or `reuse x ...`. -/
partial def consumed (x : VarId) : FnBody Bool
| .vdecl _ _ v b =>
match v with
| Expr.reuse y _ _ _ => x == y || consumed x b
| _ => consumed x b
| .dec y _ _ _ b => x == y || consumed x b
| .case _ _ _ alts => alts.all fun alt => consumed x alt.body
| e => !e.isTerminal && consumed x e.body
abbrev Mask := Array (Option VarId)
/-- Auxiliary function for eraseProjIncFor -/
partial def eraseProjIncForAux (y : VarId) (bs : Array FnBody) (mask : Mask) (keep : Array FnBody) : Array FnBody × Mask :=
let done (_ : Unit) := (bs ++ keep.reverse, mask)
let keepInstr (b : FnBody) := eraseProjIncForAux y bs.pop mask (keep.push b)
if h : bs.size < 2 then done ()
else
let b := bs.back!
match b with
| .vdecl _ _ (.sproj _ _ _) _ => keepInstr b
| .vdecl _ _ (.uproj _ _) _ => keepInstr b
| .inc z n c p _ =>
if n == 0 then done () else
let b' := bs[bs.size - 2]
match b' with
| .vdecl w _ (.proj i x) _ =>
if w == z && y == x then
/- Found
```
let z := proj[i] y
inc z n c
```
We keep `proj`, and `inc` when `n > 1`
-/
let bs := bs.pop.pop
let mask := mask.set! i (some z)
let keep := keep.push b'
let keep := if n == 1 then keep else keep.push (FnBody.inc z (n-1) c p FnBody.nil)
eraseProjIncForAux y bs mask keep
else done ()
| _ => done ()
| _ => done ()
/-- Try to erase `inc` instructions on projections of `y` occurring in the tail of `bs`.
Return the updated `bs` and a bit mask specifying which `inc`s have been removed. -/
def eraseProjIncFor (n : Nat) (y : VarId) (bs : Array FnBody) : Array FnBody × Mask :=
eraseProjIncForAux y bs (.replicate n none) #[]
/-- Replace `reuse x ctor ...` with `ctor ...`, and remove `dec x` -/
partial def reuseToCtor (x : VarId) : FnBody FnBody
| FnBody.dec y n c p b =>
if x == y then b -- n must be 1 since `x := reset ...`
else FnBody.dec y n c p (reuseToCtor x b)
| FnBody.vdecl z t v b =>
match v with
| Expr.reuse y c _ xs =>
if x == y then FnBody.vdecl z t (Expr.ctor c xs) b
else FnBody.vdecl z t v (reuseToCtor x b)
| _ =>
FnBody.vdecl z t v (reuseToCtor x b)
| FnBody.case tid y yType alts =>
let alts := alts.map fun alt => alt.modifyBody (reuseToCtor x)
FnBody.case tid y yType alts
| e =>
if e.isTerminal then
e
else
let (instr, b) := e.split
let b := reuseToCtor x b
instr.setBody b
/--
replace
```
x := reset y; b
```
with
```
inc z_1; ...; inc z_i; dec y; b'
```
where `z_i`'s are the variables in `mask`,
and `b'` is `b` where we removed `dec x` and replaced `reuse x ctor_i ...` with `ctor_i ...`.
-/
def mkSlowPath (x y : VarId) (mask : Mask) (b : FnBody) : FnBody :=
let b := reuseToCtor x b
let b := FnBody.dec y 1 true false b
mask.foldl (init := b) fun b m => match m with
| some z => FnBody.inc z 1 true false b
| none => b
abbrev M := ReaderT Context (StateM Nat)
def mkFresh : M VarId :=
modifyGet fun n => ({ idx := n }, n + 1)
def releaseUnreadFields (y : VarId) (mask : Mask) (b : FnBody) : M FnBody :=
mask.size.foldM (init := b) fun i _ b =>
match mask[i] with
| some _ => pure b -- code took ownership of this field
| none => do
let fld mkFresh
pure (FnBody.vdecl fld .tobject (Expr.proj i y) (FnBody.dec fld 1 true false b))
def setFields (y : VarId) (zs : Array Arg) (b : FnBody) : FnBody :=
zs.size.fold (init := b) fun i _ b => FnBody.set y i zs[i] b
/-- Given `set x[i] := y`, return true iff `y := proj[i] x` -/
def isSelfSet (ctx : Context) (x : VarId) (i : Nat) (y : Arg) : Bool :=
match y with
| .var y =>
match ctx.projMap[y]? with
| some (Expr.proj j w) => j == i && w == x
| _ => false
| .erased => false
/-- Given `uset x[i] := y`, return true iff `y := uproj[i] x` -/
def isSelfUSet (ctx : Context) (x : VarId) (i : Nat) (y : VarId) : Bool :=
match ctx.projMap[y]? with
| some (Expr.uproj j w) => j == i && w == x
| _ => false
/-- Given `sset x[n, i] := y`, return true iff `y := sproj[n, i] x` -/
def isSelfSSet (ctx : Context) (x : VarId) (n : Nat) (i : Nat) (y : VarId) : Bool :=
match ctx.projMap[y]? with
| some (Expr.sproj m j w) => n == m && j == i && w == x
| _ => false
/-- Remove unnecessary `set/uset/sset` operations -/
partial def removeSelfSet (ctx : Context) : FnBody FnBody
| FnBody.set x i y b =>
if isSelfSet ctx x i y then removeSelfSet ctx b
else FnBody.set x i y (removeSelfSet ctx b)
| FnBody.uset x i y b =>
if isSelfUSet ctx x i y then removeSelfSet ctx b
else FnBody.uset x i y (removeSelfSet ctx b)
| FnBody.sset x n i y t b =>
if isSelfSSet ctx x n i y then removeSelfSet ctx b
else FnBody.sset x n i y t (removeSelfSet ctx b)
| FnBody.case tid y yType alts =>
let alts := alts.map fun alt => alt.modifyBody (removeSelfSet ctx)
FnBody.case tid y yType alts
| e =>
if e.isTerminal then e
else
let (instr, b) := e.split
let b := removeSelfSet ctx b
instr.setBody b
partial def reuseToSet (ctx : Context) (x y : VarId) : FnBody FnBody
| FnBody.dec z n c p b =>
if x == z then FnBody.del y b
else FnBody.dec z n c p (reuseToSet ctx x y b)
| FnBody.vdecl z t v b =>
match v with
| Expr.reuse w c u zs =>
if x == w then
let b := setFields y zs (b.replaceVar z y)
let b := if u then FnBody.setTag y c.cidx b else b
removeSelfSet ctx b
else FnBody.vdecl z t v (reuseToSet ctx x y b)
| _ => FnBody.vdecl z t v (reuseToSet ctx x y b)
| FnBody.case tid z zType alts =>
let alts := alts.map fun alt => alt.modifyBody (reuseToSet ctx x y)
FnBody.case tid z zType alts
| e =>
if e.isTerminal then e
else
let (instr, b) := e.split
let b := reuseToSet ctx x y b
instr.setBody b
/--
replace
```
x := reset y; b
```
with
```
let f_i_1 := proj[i_1] y;
...
let f_i_k := proj[i_k] y;
b'
```
where `i_j`s are the field indexes
that the code did not touch immediately before the reset.
That is `mask[j] == none`.
`b'` is `b` where `y` `dec x` is replaced with `del y`,
and `z := reuse x ctor_i ws; F` is replaced with
`set x i ws[i]` operations, and we replace `z` with `x` in `F`
-/
def mkFastPath (x y : VarId) (mask : Mask) (b : FnBody) : M FnBody := do
let ctx read
let b := reuseToSet ctx x y b
releaseUnreadFields y mask b
-- Expand `bs; x := reset[n] y; b`
partial def expand (mainFn : FnBody Array FnBody M FnBody)
(bs : Array FnBody) (x : VarId) (n : Nat) (y : VarId) (b : FnBody) : M FnBody := do
let (bs, mask) := eraseProjIncFor n y bs
/- Remark: we may be duplicating variable/JP indices. That is, `bSlow` and `bFast` may
have duplicate indices. We run `normalizeIds` to fix the ids after we have expand them. -/
let bSlow := mkSlowPath x y mask b
let bFast mkFastPath x y mask b
/- We only optimize recursively the fast. -/
let bFast mainFn bFast #[]
let c mkFresh
let b := FnBody.vdecl c IRType.uint8 (Expr.isShared y) (mkIf c bSlow bFast)
return reshape bs b
partial def searchAndExpand : FnBody Array FnBody M FnBody
| d@(FnBody.vdecl x _ (Expr.reset n y) b), bs =>
if consumed x b then do
expand searchAndExpand bs x n y b
else
searchAndExpand b (push bs d)
| FnBody.jdecl j xs v b, bs => do
let v searchAndExpand v #[]
searchAndExpand b (push bs (FnBody.jdecl j xs v FnBody.nil))
| FnBody.case tid x xType alts, bs => do
let alts alts.mapM fun alt => alt.modifyBodyM fun b => searchAndExpand b #[]
return reshape bs (FnBody.case tid x xType alts)
| b, bs =>
if b.isTerminal then return reshape bs b
else searchAndExpand b.body (push bs b)
def main (d : Decl) : Decl :=
match d with
| .fdecl (body := b) .. =>
let m := mkProjMap d
let nextIdx := d.maxIndex + 1
let bNew := (searchAndExpand b #[] { projMap := m }).run' nextIdx
d.updateBody! bNew
| d => d
end ExpandResetReuse
/-- (Try to) expand `reset` and `reuse` instructions. -/
def Decl.expandResetReuse (d : Decl) : Decl :=
(ExpandResetReuse.main d).normalizeIds
builtin_initialize registerTraceClass `compiler.ir.expand_reset_reuse (inherited := true)
end Lean.IR

View File

@@ -1,245 +0,0 @@
/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Compiler.IR.Basic
public section
namespace Lean.IR
namespace MaxIndex
/-! Compute the maximum index `M` used in a declaration.
We `M` to initialize the fresh index generator used to create fresh
variable and join point names.
Recall that we variable and join points share the same namespace in
our implementation.
-/
structure State where
currentMax : Nat := 0
abbrev M := StateM State
private def visitIndex (x : Index) : M Unit := do
modify fun s => { s with currentMax := s.currentMax.max x }
private def visitVar (x : VarId) : M Unit :=
visitIndex x.idx
private def visitJP (j : JoinPointId) : M Unit :=
visitIndex j.idx
private def visitArg (arg : Arg) : M Unit :=
match arg with
| .var x => visitVar x
| .erased => pure ()
private def visitParam (p : Param) : M Unit :=
visitVar p.x
private def visitExpr (e : Expr) : M Unit := do
match e with
| .proj _ x | .uproj _ x | .sproj _ _ x | .box _ x | .unbox x | .reset _ x | .isShared x =>
visitVar x
| .ctor _ ys | .fap _ ys | .pap _ ys =>
ys.forM visitArg
| .ap x ys | .reuse x _ _ ys =>
visitVar x
ys.forM visitArg
| .lit _ => pure ()
partial def visitFnBody (fnBody : FnBody) : M Unit := do
match fnBody with
| .vdecl x _ v b =>
visitVar x
visitExpr v
visitFnBody b
| .jdecl j ys v b =>
visitJP j
visitFnBody v
ys.forM visitParam
visitFnBody b
| .set x _ y b =>
visitVar x
visitArg y
visitFnBody b
| .uset x _ y b | .sset x _ _ y _ b =>
visitVar x
visitVar y
visitFnBody b
| .setTag x _ b | .inc x _ _ _ b | .dec x _ _ _ b | .del x b =>
visitVar x
visitFnBody b
| .case _ x _ alts =>
visitVar x
alts.forM (visitFnBody ·.body)
| .jmp j ys =>
visitJP j
ys.forM visitArg
| .ret x =>
visitArg x
| .unreachable => pure ()
private def visitDecl (decl : Decl) : M Unit := do
match decl with
| .fdecl (xs := xs) (body := b) .. =>
xs.forM visitParam
visitFnBody b
| .extern (xs := xs) .. =>
xs.forM visitParam
end MaxIndex
def FnBody.maxIndex (b : FnBody) : Index := Id.run do
let _, { currentMax } := MaxIndex.visitFnBody b |>.run {}
return currentMax
def Decl.maxIndex (d : Decl) : Index := Id.run do
let _, { currentMax } := MaxIndex.visitDecl d |>.run {}
return currentMax
namespace FreeIndices
/-! We say a variable (join point) index (aka name) is free in a function body
if there isn't a `FnBody.vdecl` (`Fnbody.jdecl`) binding it. -/
structure State where
freeIndices : IndexSet := {}
abbrev M := StateM State
private def visitIndex (x : Index) : M Unit := do
modify fun s => { s with freeIndices := s.freeIndices.insert x }
private def visitVar (x : VarId) : M Unit :=
visitIndex x.idx
private def visitJP (j : JoinPointId) : M Unit :=
visitIndex j.idx
private def visitArg (arg : Arg) : M Unit :=
match arg with
| .var x => visitVar x
| .erased => pure ()
private def visitParam (p : Param) : M Unit :=
visitVar p.x
private def visitExpr (e : Expr) : M Unit := do
match e with
| .proj _ x | .uproj _ x | .sproj _ _ x | .box _ x | .unbox x | .reset _ x | .isShared x =>
visitVar x
| .ctor _ ys | .fap _ ys | .pap _ ys =>
ys.forM visitArg
| .ap x ys | .reuse x _ _ ys =>
visitVar x
ys.forM visitArg
| .lit _ => pure ()
partial def visitFnBody (fnBody : FnBody) : M Unit := do
match fnBody with
| .vdecl x _ v b =>
visitVar x
visitExpr v
visitFnBody b
| .jdecl j ys v b =>
visitJP j
visitFnBody v
ys.forM visitParam
visitFnBody b
| .set x _ y b =>
visitVar x
visitArg y
visitFnBody b
| .uset x _ y b | .sset x _ _ y _ b =>
visitVar x
visitVar y
visitFnBody b
| .setTag x _ b | .inc x _ _ _ b | .dec x _ _ _ b | .del x b =>
visitVar x
visitFnBody b
| .case _ x _ alts =>
visitVar x
alts.forM (visitFnBody ·.body)
| .jmp j ys =>
visitJP j
ys.forM visitArg
| .ret x =>
visitArg x
| .unreachable => pure ()
private def visitDecl (decl : Decl) : M Unit := do
match decl with
| .fdecl (xs := xs) (body := b) .. =>
xs.forM visitParam
visitFnBody b
| .extern (xs := xs) .. =>
xs.forM visitParam
end FreeIndices
def FnBody.collectFreeIndices (b : FnBody) (init : IndexSet) : IndexSet := Id.run do
let _, { freeIndices } := FreeIndices.visitFnBody b |>.run { freeIndices := init }
return freeIndices
def FnBody.freeIndices (b : FnBody) : IndexSet :=
b.collectFreeIndices {}
namespace HasIndex
/-! In principle, we can check whether a function body `b` contains an index `i` using
`b.freeIndices.contains i`, but it is more efficient to avoid the construction
of the set of freeIndices and just search whether `i` occurs in `b` or not.
-/
def visitVar (w : Index) (x : VarId) : Bool := w == x.idx
def visitJP (w : Index) (x : JoinPointId) : Bool := w == x.idx
def visitArg (w : Index) : Arg Bool
| .var x => visitVar w x
| .erased => false
def visitArgs (w : Index) (xs : Array Arg) : Bool :=
xs.any (visitArg w)
def visitParams (w : Index) (ps : Array Param) : Bool :=
ps.any (fun p => w == p.x.idx)
def visitExpr (w : Index) : Expr Bool
| .proj _ x | .uproj _ x | .sproj _ _ x | .box _ x | .unbox x | .reset _ x | .isShared x =>
visitVar w x
| .ctor _ ys | .fap _ ys | .pap _ ys =>
visitArgs w ys
| .ap x ys | .reuse x _ _ ys =>
visitVar w x || visitArgs w ys
| .lit _ => false
partial def visitFnBody (w : Index) : FnBody Bool
| .vdecl _ _ v b =>
visitExpr w v || visitFnBody w b
| .jdecl _ _ v b =>
visitFnBody w v || visitFnBody w b
| FnBody.set x _ y b =>
visitVar w x || visitArg w y || visitFnBody w b
| .uset x _ y b | .sset x _ _ y _ b =>
visitVar w x || visitVar w y || visitFnBody w b
| .setTag x _ b | .inc x _ _ _ b | .dec x _ _ _ b | .del x b =>
visitVar w x || visitFnBody w b
| .case _ x _ alts =>
visitVar w x || alts.any (fun alt => visitFnBody w alt.body)
| .jmp j ys =>
visitJP w j || visitArgs w ys
| .ret x =>
visitArg w x
| .unreachable => false
end HasIndex
def Arg.hasFreeVar (arg : Arg) (x : VarId) : Bool := HasIndex.visitArg x.idx arg
def Expr.hasFreeVar (e : Expr) (x : VarId) : Bool := HasIndex.visitExpr x.idx e
def FnBody.hasFreeVar (b : FnBody) (x : VarId) : Bool := HasIndex.visitFnBody x.idx b
end Lean.IR

View File

@@ -1,62 +0,0 @@
/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Lean.Compiler.IR.FreeVars
public import Lean.Compiler.IR.NormIds
public section
namespace Lean.IR
partial def pushProjs (bs : Array FnBody) (alts : Array Alt) (altsF : Array IndexSet) (ctx : Array FnBody) (ctxF : IndexSet) : Array FnBody × Array Alt :=
if bs.isEmpty then (ctx.reverse, alts)
else
let b := bs.back!
let bs := bs.pop
let done (_ : Unit) := (bs.push b ++ ctx.reverse, alts)
let skip (_ : Unit) := pushProjs bs alts altsF (ctx.push b) (b.collectFreeIndices ctxF)
let push (x : VarId) :=
if !ctxF.contains x.idx then
let alts := alts.mapIdx fun i alt => alt.modifyBody fun b' =>
if altsF[i]!.contains x.idx then b.setBody b'
else b'
let altsF := altsF.map fun s => if s.contains x.idx then b.collectFreeIndices s else s
pushProjs bs alts altsF ctx ctxF
else
skip ()
match b with
| FnBody.vdecl x _ v _ =>
match v with
| Expr.proj _ _ => push x
| Expr.uproj _ _ => push x
| Expr.sproj _ _ _ => push x
| Expr.isShared _ => skip ()
| _ => done ()
| _ => done ()
partial def FnBody.pushProj (b : FnBody) : FnBody :=
let (bs, term) := b.flatten
let bs := modifyJPs bs pushProj
match term with
| .case tid x xType alts =>
let altsF := alts.map fun alt => alt.body.freeIndices
let (bs, alts) := pushProjs bs alts altsF #[] (mkIndexSet x.idx)
let alts := alts.map fun alt => alt.modifyBody pushProj
let term := FnBody.case tid x xType alts
reshape bs term
| _ => reshape bs term
/-- Push projections inside `case` branches. -/
def Decl.pushProj (d : Decl) : Decl :=
match d with
| .fdecl (body := b) .. => d.updateBody! b.pushProj |>.normalizeIds
| other => other
builtin_initialize registerTraceClass `compiler.ir.push_proj (inherited := true)
end Lean.IR

View File

@@ -0,0 +1,369 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
module
prelude
public import Lean.Compiler.LCNF.PassManager
import Init.While
/-!
This pass expands pairs of reset-reuse instructions into explicit hot and cold paths. We do this on
the LCNF level rather than letting the backend do it because we can apply domain specific
optimizations at this point in time.
Whenever we encounter a `let token := reset nfields orig; k`, we create code of the shape (not
showing reference counting instructions):
```
jp resetjp token isShared :=
k
cases isShared orig with
| false -> jmp resetjp orig true
| true -> jmp resetjp box(0) false
```
Then within the join point body `k` we turn `dec` instructions on `token` into `del` and expand
`let final := reuse token arg; k'` into another join point:
```
jp reusejp final :=
k'
cases isShared with
| false -> jmp reusejp token
| true ->
let x := alloc args
jmp reusejp x
```
In addition to this we perform optimizations specific to the hot path for both the `resetjp` and
`reusejp`. For the former, we will frequently encounter the pattern:
```
let x_0 = proj[0] orig
inc x_0
...
let x_i = proj[i] orig
inc x_i
let token := reset nfields orig
```
On the hot path we do not free `orig`, thus there is no need to increment the reference counts of
the projections because the reference coming from `orig` will keep all of the projections alive
naturally (a form of "dynamic derived borrows" if you wish). On the cold path the reference counts
still have to happen though.
For `resetjp` we frequently encounter the pattern:
```
let final := reuse token args
set final[0] := x0
...
set final[i] := xi
```
On the hot path we know that `token` and `orig` refer to the same value. Thus, if we can detect that
one of the `xi` is of the shape `let xi := proj[i] orig`, we can omit the store on the hot path.
Just like with `reusejp` on the cold path we have to perform all the stores.
-/
namespace Lean.Compiler.LCNF
open ImpureType
abbrev Mask := Array (Option FVarId)
/--
Try to erase `inc` instructions on projections of `targetId` occuring in the tail of `ds`.
Return the updated `ds` and mask contianing the `FVarId`s whose `inc` was removed.
-/
partial def eraseProjIncFor (nFields : Nat) (targetId : FVarId) (ds : Array (CodeDecl .impure)) :
CompilerM (Array (CodeDecl .impure) × Mask) := do
let mut ds := ds
let mut keep := #[]
let mut mask := Array.replicate nFields none
while ds.size 2 do
let d := ds.back!
match d with
| .let { value := .sproj .., .. } | .let { value := .uproj .., .. } =>
ds := ds.pop
keep := keep.push d
| .inc z n c p =>
assert! n > 0 -- 0 incs should not be happening
let d' := ds[ds.size - 2]!
let .let { fvarId := w, value := .oproj i x _, .. } := d'
| break
if !(w == z && targetId == x) then
break
/-
Found
```
let z := proj[i] targetId
inc z n c
```
We keep `proj`, and `inc` when `n > 1`
-/
ds := ds.pop.pop
mask := mask.set! i (some z)
keep := keep.push d'
keep := if n == 1 then keep else keep.push (.inc z (n - 1) c p)
| _ => break
return (ds ++ keep.reverse, mask)
def mkIf (discr : FVarId) (discrType : Expr) (resultType : Expr) (t e : Code .impure) :
CompilerM (Code .impure) := do
return .cases <| .mk discrType.getAppFn.constName! resultType discr #[
.ctorAlt { name := ``Bool.false, cidx := 0, size := 0, usize := 0, ssize := 0 } e,
.ctorAlt { name := ``Bool.true, cidx := 1, size := 0, usize := 0, ssize := 0 } t,
]
def remapSets (targetId : FVarId) (sets : Array (CodeDecl .impure)) :
CompilerM (Array (CodeDecl .impure)) :=
return sets.map fun
| .oset fvarId i y => .oset targetId i y
| .sset fvarId i offset y ty => .sset targetId i offset y ty
| .uset fvarId i y => .uset targetId i y
| _ => unreachable!
def isSelfOset (fvarId : FVarId) (i : Nat) (y : Arg .impure) : CompilerM Bool := do
match y with
| .fvar y =>
let some value findLetValue? (pu := .impure) y | return false
let .oproj i' fvarId' := value | return false
return i == i' && fvarId == fvarId'
| .erased => return false
def isSelfUset (fvarId : FVarId) (i : Nat) (y : FVarId) : CompilerM Bool := do
let some value findLetValue? (pu := .impure) y | return false
let .uproj i' fvarId' := value | return false
return i == i' && fvarId == fvarId'
def isSelfSset (fvarId : FVarId) (i : Nat) (offset : Nat) (y : FVarId) :
CompilerM Bool := do
let some value findLetValue? (pu := .impure) y | return false
let .sproj i' offset' fvarId' := value | return false
return i == i' && offset == offset' && fvarId == fvarId'
/--
Partition the set instructions in `sets` into a pair `(selfSets, necessarySets)` where `selfSets`
contain instructions that perform a set with the same value projected from `selfId` and
`necessarySets` all others.
-/
def partitionSelfSets (selfId : FVarId) (sets : Array (CodeDecl .impure)) :
CompilerM (Array (CodeDecl .impure) × Array (CodeDecl .impure)) := do
let mut necessarySets := #[]
let mut selfSets := #[]
for set in sets do
let isSelfSet :=
match set with
| .oset _ i y => isSelfOset selfId i y
| .uset _ i y => isSelfUset selfId i y
| .sset _ i offset y _ => isSelfSset selfId i offset y
| _ => unreachable!
if isSelfSet then
selfSets := selfSets.push set
else
necessarySets := necessarySets.push set
return (selfSets, necessarySets)
def collectSucceedingSets (target : FVarId) (k : Code .impure) :
CompilerM (Array (CodeDecl .impure) × Code .impure) := do
let mut sets := #[]
let mut k := k
while true do
match k with
| .oset (fvarId := fvarId) (k := k') .. | .sset (fvarId := fvarId) (k := k') ..
| .uset (fvarId := fvarId) (k := k') .. =>
if target != fvarId then
break
sets := sets.push k.toCodeDecl!
k := k'
| _ => break
return (sets, k)
mutual
/--
Expand the matching `reuse`/`dec` for the allocation in `origAllocId` whose `reset` token is in
`resetTokenId`.
-/
partial def processResetCont (resetTokenId : FVarId) (code : Code .impure) (origAllocId : FVarId)
(isSharedId : FVarId) (currentRetType : Expr) : CompilerM (Code .impure) := do
match code with
| .dec y n _ _ k =>
if resetTokenId == y then
assert! n == 1 -- n must be one since `resetToken := reset ...`
return .del resetTokenId k
else
let k processResetCont resetTokenId k origAllocId isSharedId currentRetType
return code.updateCont! k
| .let decl k =>
match decl.value with
| .reuse y c u xs =>
if resetTokenId != y then
let k processResetCont resetTokenId k origAllocId isSharedId currentRetType
return code.updateCont! k
let (succeedingSets, k) collectSucceedingSets decl.fvarId k
let (selfSets, necessarySets) partitionSelfSets origAllocId succeedingSets
let k := attachCodeDecls necessarySets k
let param := {
fvarId := decl.fvarId,
binderName := decl.binderName,
type := decl.type,
borrow := false
}
let contJp mkFunDecl ( mkFreshBinderName `reusejp) currentRetType #[param] k
let slowPath mkSlowPath decl c xs contJp.fvarId selfSets
let fastPath mkFastPath resetTokenId c u xs contJp.fvarId origAllocId
eraseLetDecl decl
let reuse mkIf isSharedId uint8 currentRetType slowPath fastPath
return .jp contJp reuse
| _ =>
let k processResetCont resetTokenId k origAllocId isSharedId currentRetType
return code.updateCont! k
| .cases cs =>
return code.updateAlts! ( cs.alts.mapMonoM (·.mapCodeM (processResetCont resetTokenId · origAllocId isSharedId cs.resultType)))
| .jp decl k =>
let decl decl.updateValue ( processResetCont resetTokenId decl.value origAllocId isSharedId decl.type)
let k processResetCont resetTokenId k origAllocId isSharedId currentRetType
return code.updateFun! decl k
| .uset (k := k) .. | .sset (k := k) .. | .inc (k := k) .. | .setTag (k := k) ..
| .del (k := k) .. | .oset (k := k) .. =>
let k processResetCont resetTokenId k origAllocId isSharedId currentRetType
return code.updateCont! k
| .jmp .. | .return .. | .unreach .. => return code
where
/--
On the slow path we have to:
1. Make a fresh alloation
2. Apply all the self sets as the fresh allocation is of course not in sync with the original one.
3. Pass the fresh allocation to the joinpoint.
-/
mkSlowPath (decl : LetDecl .impure) (info : CtorInfo) (args : Array (Arg .impure))
(contJpId : FVarId) (selfSets : Array (CodeDecl .impure)) : CompilerM (Code .impure) := do
let allocDecl mkLetDecl ( mkFreshBinderName `reuseFailAlloc) decl.type (.ctor info args)
let mut code := .jmp contJpId #[.fvar allocDecl.fvarId]
code := attachCodeDecls ( remapSets allocDecl.fvarId selfSets) code
code := .let allocDecl code
return code
/--
On the fast path path we have to:
1. Make all non-self object sets to "simulate" the allocation (the remaining necessary sets will
be made in the continuation)
2. Pass the reused allocation to the joinpoint.
-/
mkFastPath (resetTokenId : FVarId) (info : CtorInfo) (update : Bool) (args : Array (Arg .impure))
(contJpId : FVarId) (origAllocId : FVarId) : CompilerM (Code .impure) := do
let mut code := .jmp contJpId #[.fvar resetTokenId]
for h : idx in 0...args.size do
if !( isSelfOset origAllocId idx args[idx]) then
code := .oset resetTokenId idx args[idx] code
if update then
code := .setTag resetTokenId info.cidx code
return code
/--
Traverse `code` looking for reset-reuse pairs to expand while `ds` holds the instructions up to the
last branching point.
-/
partial def Code.expandResetReuse (code : Code .impure) (ds : Array (CodeDecl .impure))
(currentRetType : Expr) : CompilerM (Code .impure) := do
let collectAndGo (code : Code .impure) (ds : Array (CodeDecl .impure)) (k : Code .impure) :=
let d := code.toCodeDecl!
k.expandResetReuse (ds.push d) currentRetType
match code with
| .let decl k =>
match decl.value with
| .reset nFields origAllocId => expand ds decl nFields origAllocId k
| _ => collectAndGo code ds k
| .jp decl k =>
let value decl.value.expandResetReuse #[] decl.type
let decl decl.updateValue value
k.expandResetReuse (ds.push (.jp decl)) currentRetType
| .cases cs =>
let alts cs.alts.mapMonoM (·.mapCodeM (·.expandResetReuse #[] cs.resultType))
let code := code.updateAlts! alts
return attachCodeDecls ds code
| .uset (k := k) .. | .sset (k := k) .. | .inc (k := k) .. | .setTag (k := k) ..
| .dec (k := k) .. | .del (k := k) .. | .oset (k := k) .. =>
collectAndGo code ds k
| .jmp .. | .return .. | .unreach .. =>
return attachCodeDecls ds code
where
/--
Expand the reset in `decl` together with its matching `reuse`/`dec`s in its continuation `k`.
-/
expand (ds : Array (CodeDecl .impure)) (decl : LetDecl .impure) (nFields : Nat)
(origAllocId : FVarId) (k : Code .impure) : CompilerM (Code .impure) := do
let (ds, mask) eraseProjIncFor nFields origAllocId ds
let isSharedParam mkParam ( mkFreshBinderName `isShared) uint8 false
let k processResetCont decl.fvarId k origAllocId isSharedParam.fvarId currentRetType
let k k.expandResetReuse #[] currentRetType
let allocParam := {
fvarId := decl.fvarId,
binderName := decl.binderName,
type := tobject,
borrow := false
}
let resetJp mkFunDecl ( mkFreshBinderName `resetjp) currentRetType #[allocParam, isSharedParam] k
let isSharedDecl mkLetDecl ( mkFreshBinderName `isSharedCheck) uint8 (.isShared origAllocId)
let slowPath mkSlowPath origAllocId mask resetJp.fvarId isSharedDecl.fvarId
let fastPath mkFastPath origAllocId mask resetJp.fvarId isSharedDecl.fvarId
let mut reset mkIf isSharedDecl.fvarId uint8 currentRetType slowPath fastPath
reset := .let isSharedDecl reset
eraseLetDecl decl
return attachCodeDecls ds (.jp resetJp reset)
/--
On the slow path we cannot reuse the allocation, this means we have to:
1. Increments all variables projected from `origAllocId` that have not been incremented yet by
the shared prologue. On the fast path they are kept alive naturally by the original allocation
but here that is not necessarily the case.
2. Decrement the value being reset (the natural behavior of a failed reset)
3. Pass box(0) as a reuse value into the continuation join point
-/
mkSlowPath (origAllocId : FVarId) (mask : Mask) (resetJpId : FVarId) (isSharedId : FVarId) :
CompilerM (Code .impure) := do
let mut code := .jmp resetJpId #[.erased, .fvar isSharedId]
code := .dec origAllocId 1 true false code
for fvarId? in mask do
let some fvarId := fvarId? | continue
code := .inc fvarId 1 true false code
return code
/--
On the fast path we can reuse the allocation, this means we have to:
1. decrement all unread fields as their parent allocation would usually be dropped at this point
and we want to be garbage free.
2. Pass the original allocation as a reuse value into the continuation join point
-/
mkFastPath (origAllocId : FVarId) (mask : Mask) (resetJpId : FVarId) (isSharedId : FVarId) :
CompilerM (Code .impure) := do
let mut code := .jmp resetJpId #[.fvar origAllocId, .fvar isSharedId]
for h : idx in 0...mask.size do
if mask[idx].isSome then
continue
let fieldDecl mkLetDecl ( mkFreshBinderName `unused) tobject (.oproj idx origAllocId)
code := .let fieldDecl (.dec fieldDecl.fvarId 1 true false code)
return code
end
def Decl.expandResetReuse (decl : Decl .impure) : CompilerM (Decl .impure) := do
if ( getConfig).resetReuse then
let value decl.value.mapCodeM (·.expandResetReuse #[] decl.type)
let decl := { decl with value }
return decl
else
return decl
public def expandResetReuse : Pass :=
Pass.mkPerDeclaration `expandResetReuse .impure Decl.expandResetReuse
builtin_initialize
registerTraceClass `Compiler.expandResetReuse (inherited := true)
end Lean.Compiler.LCNF

View File

@@ -27,6 +27,7 @@ public import Lean.Compiler.LCNF.InferBorrow
public import Lean.Compiler.LCNF.ExplicitBoxing
public import Lean.Compiler.LCNF.ExplicitRC
public import Lean.Compiler.LCNF.Toposort
public import Lean.Compiler.LCNF.ExpandResetReuse
public section
@@ -154,6 +155,8 @@ def builtinPassManager : PassManager := {
inferBorrow,
explicitBoxing,
explicitRc,
expandResetReuse,
pushProj (occurrence := 1),
inferVisibility (phase := .impure),
saveImpure, -- End of impure phase
toposortPass,

View File

@@ -309,7 +309,7 @@ typedef struct {
void * m_data;
} lean_external_object;
static inline LEAN_ALWAYS_INLINE bool lean_is_scalar(lean_object * o) { return ((size_t)(o) & 1) == 1; }
static inline LEAN_ALWAYS_INLINE uint8_t lean_is_scalar(lean_object * o) { return ((size_t)(o) & 1) == 1; }
static inline lean_object * lean_box(size_t n) { return (lean_object*)(((size_t)(n) << 1) | 1); }
static inline size_t lean_unbox(lean_object * o) { return (size_t)(o) >> 1; }
@@ -441,6 +441,13 @@ static inline void lean_free_small_object(lean_object * o) {
LEAN_EXPORT lean_object * lean_alloc_object(size_t sz);
LEAN_EXPORT void lean_free_object(lean_object * o);
static inline void lean_del_object(lean_object * o) {
if (!lean_is_scalar(o)) {
lean_free_object(o);
}
}
static inline uint8_t lean_ptr_tag(lean_object * o) {
return o->m_tag;
}

View File

@@ -751,7 +751,7 @@ private:
break;
}
case fn_body_kind::Del: // delete object of unique reference
lean_free_object(var(fn_body_del_var(b)).m_obj);
lean_del_object(var(fn_body_del_var(b)).m_obj);
b = fn_body_del_cont(b);
break;
case fn_body_kind::Case: { // branch according to constructor tag

View File

@@ -32,6 +32,89 @@ unsafe axiom lcAny : Type
/-- Internal representation of `Void` in the compiler. -/
unsafe axiom lcVoid : Type
set_option bootstrap.inductiveCheckResultingUniverse false in
/--
The canonical universe-polymorphic type with just one element.
It should be used in contexts that require a type to be universe polymorphic, thus disallowing
`Unit`.
-/
inductive PUnit : Sort u where
/-- The only element of the universe-polymorphic unit type. -/
| unit : PUnit
/--
The equality relation. It has one introduction rule, `Eq.refl`.
We use `a = b` as notation for `Eq a b`.
A fundamental property of equality is that it is an equivalence relation.
```
variable (α : Type) (a b c d : α)
variable (hab : a = b) (hcb : c = b) (hcd : c = d)
example : a = d :=
Eq.trans (Eq.trans hab (Eq.symm hcb)) hcd
```
Equality is much more than an equivalence relation, however. It has the important property that every assertion
respects the equivalence, in the sense that we can substitute equal expressions without changing the truth value.
That is, given `h1 : a = b` and `h2 : p a`, we can construct a proof for `p b` using substitution: `Eq.subst h1 h2`.
Example:
```
example (α : Type) (a b : α) (p : α → Prop)
(h1 : a = b) (h2 : p a) : p b :=
Eq.subst h1 h2
example (α : Type) (a b : α) (p : α → Prop)
(h1 : a = b) (h2 : p a) : p b :=
h1 ▸ h2
```
The triangle in the second presentation is a macro built on top of `Eq.subst` and `Eq.symm`, and you can enter it by typing `\t`.
For more information: [Equality](https://lean-lang.org/theorem_proving_in_lean4/quantifiers_and_equality.html#equality)
-/
inductive Eq : αα → Prop where
/-- `Eq.refl a : a = a` is reflexivity, the unique constructor of the
equality type. See also `rfl`, which is usually used instead. -/
| refl (a : α) : Eq a a
/-- Non-dependent recursor for the equality type. -/
@[simp] abbrev Eq.ndrec.{u1, u2} {α : Sort u2} {a : α} {motive : α → Sort u1} (m : motive a) {b : α} (h : Eq a b) : motive b :=
h.rec m
/--
Heterogeneous equality. `a ≍ b` asserts that `a` and `b` have the same
type, and casting `a` across the equality yields `b`, and vice versa.
You should avoid using this type if you can. Heterogeneous equality does not
have all the same properties as `Eq`, because the assumption that the types of
`a` and `b` are equal is often too weak to prove theorems of interest. One
public important non-theorem is the analogue of `congr`: If `f ≍ g` and `x ≍ y`
and `f x` and `g y` are well typed it does not follow that `f x ≍ g y`.
(This does follow if you have `f = g` instead.) However if `a` and `b` have
the same type then `a = b` and `a ≍ b` are equivalent.
-/
inductive HEq : {α : Sort u} → α → {β : Sort u} → β → Prop where
/-- Reflexivity of heterogeneous equality. -/
| refl (a : α) : HEq a a
/--
The Boolean values, `true` and `false`.
Logically speaking, this is equivalent to `Prop` (the type of propositions). The distinction is
public important for programming: both propositions and their proofs are erased in the code generator,
while `Bool` corresponds to the Boolean type in most programming languages and carries precisely one
bit of run-time information.
-/
inductive Bool : Type where
/-- The Boolean value `false`, not to be confused with the proposition `False`. -/
| false : Bool
/-- The Boolean value `true`, not to be confused with the proposition `True`. -/
| true : Bool
export Bool (false true)
/-- Compute whether `x` is a tagged pointer or not. -/
@[extern "lean_is_scalar"]
unsafe axiom isScalarObj {α : Type u} (x : α) : Bool
/--
The identity function. `id` takes an implicit argument `α : Sort u`
@@ -115,16 +198,7 @@ does.) Example:
-/
abbrev inferInstanceAs (α : Sort u) [i : α] : α := i
set_option bootstrap.inductiveCheckResultingUniverse false in
/--
The canonical universe-polymorphic type with just one element.
It should be used in contexts that require a type to be universe polymorphic, thus disallowing
`Unit`.
-/
inductive PUnit : Sort u where
/-- The only element of the universe-polymorphic unit type. -/
| unit : PUnit
/--
The canonical type with one element. This element is written `()`.
@@ -244,42 +318,6 @@ For more information: [Propositional Logic](https://lean-lang.org/theorem_provin
-/
@[macro_inline] def absurd {a : Prop} {b : Sort v} (h₁ : a) (h₂ : Not a) : b :=( h₂ h₁).rec
/--
The equality relation. It has one introduction rule, `Eq.refl`.
We use `a = b` as notation for `Eq a b`.
A fundamental property of equality is that it is an equivalence relation.
```
variable (α : Type) (a b c d : α)
variable (hab : a = b) (hcb : c = b) (hcd : c = d)
example : a = d :=
Eq.trans (Eq.trans hab (Eq.symm hcb)) hcd
```
Equality is much more than an equivalence relation, however. It has the important property that every assertion
respects the equivalence, in the sense that we can substitute equal expressions without changing the truth value.
That is, given `h1 : a = b` and `h2 : p a`, we can construct a proof for `p b` using substitution: `Eq.subst h1 h2`.
Example:
```
example (α : Type) (a b : α) (p : α → Prop)
(h1 : a = b) (h2 : p a) : p b :=
Eq.subst h1 h2
example (α : Type) (a b : α) (p : α → Prop)
(h1 : a = b) (h2 : p a) : p b :=
h1 ▸ h2
```
The triangle in the second presentation is a macro built on top of `Eq.subst` and `Eq.symm`, and you can enter it by typing `\t`.
For more information: [Equality](https://lean-lang.org/theorem_proving_in_lean4/quantifiers_and_equality.html#equality)
-/
inductive Eq : αα → Prop where
/-- `Eq.refl a : a = a` is reflexivity, the unique constructor of the
equality type. See also `rfl`, which is usually used instead. -/
| refl (a : α) : Eq a a
/-- Non-dependent recursor for the equality type. -/
@[simp] abbrev Eq.ndrec.{u1, u2} {α : Sort u2} {a : α} {motive : α → Sort u1} (m : motive a) {b : α} (h : Eq a b) : motive b :=
h.rec m
/--
`rfl : a = a` is the unique constructor of the equality type. This is the
same as `Eq.refl` except that it takes `a` implicitly instead of explicitly.
@@ -476,21 +514,6 @@ Unsafe auxiliary constant used by the compiler to erase `Quot.lift`.
-/
unsafe axiom Quot.lcInv {α : Sort u} {r : αα → Prop} (q : Quot r) : α
/--
Heterogeneous equality. `a ≍ b` asserts that `a` and `b` have the same
type, and casting `a` across the equality yields `b`, and vice versa.
You should avoid using this type if you can. Heterogeneous equality does not
have all the same properties as `Eq`, because the assumption that the types of
`a` and `b` are equal is often too weak to prove theorems of interest. One
public important non-theorem is the analogue of `congr`: If `f ≍ g` and `x ≍ y`
and `f x` and `g y` are well typed it does not follow that `f x ≍ g y`.
(This does follow if you have `f = g` instead.) However if `a` and `b` have
the same type then `a = b` and `a ≍ b` are equivalent.
-/
inductive HEq : {α : Sort u} → α → {β : Sort u} → β → Prop where
/-- Reflexivity of heterogeneous equality. -/
| refl (a : α) : HEq a a
/-- A version of `HEq.refl` with an implicit argument. -/
@[match_pattern] protected def HEq.rfl {α : Sort u} {a : α} : HEq a a :=
@@ -598,23 +621,6 @@ theorem Or.resolve_left (h: Or a b) (na : Not a) : b := h.elim ( absurd · na)
theorem Or.resolve_right (h: Or a b) (nb : Not b) : a := h.elim id ( absurd · nb)
theorem Or.neg_resolve_left (h : Or ( Not a)b) (ha : a) : b := h.elim ( absurd ha)id
theorem Or.neg_resolve_right (h : Or a ( Not b)) (nb : b) : a := h.elim id ( absurd nb)
/--
The Boolean values, `true` and `false`.
Logically speaking, this is equivalent to `Prop` (the type of propositions). The distinction is
public important for programming: both propositions and their proofs are erased in the code generator,
while `Bool` corresponds to the Boolean type in most programming languages and carries precisely one
bit of run-time information.
-/
inductive Bool : Type where
/-- The Boolean value `false`, not to be confused with the proposition `False`. -/
| false : Bool
/-- The Boolean value `true`, not to be confused with the proposition `True`. -/
| true : Bool
export Bool (false true)
/--
All the elements of a type that satisfy a predicate.

View File

@@ -11,6 +11,21 @@ trace: [Compiler.pushProj] size: 5
| Option.some =>
let val.2 : tobj := oproj[0] a;
return val.2
[Compiler.pushProj] size: 6
def test1 @&a : tobj :=
cases a : tobj
| Option.none =>
let _x.1 : tagged := 0;
return _x.1
| Option.some =>
let val.2 : tobj := oproj[0] a;
inc val.2;
return val.2
[Compiler.pushProj] size: 2
def test1._boxed a : tobj :=
let res : tobj := test1 a;
dec a;
return res
-/
#guard_msgs in
set_option pp.letVarTypes true in
@@ -37,6 +52,45 @@ trace: [Compiler.pushProj] size: 10
let _x.3 : tobj := Nat.add val.1 val.2;
let _x.4 : obj := ctor_1[Option.some] _x.3;
return _x.4
[Compiler.pushProj] size: 27
def test2 @&a b : tobj :=
cases a : tobj
| Option.none =>
dec b;
return a
| Option.some =>
cases b : tobj
| Option.none =>
inc a;
return a
| Option.some =>
let val.1 : tobj := oproj[0] a;
let val.2 : tobj := oproj[0] b;
jp resetjp.3 _x.4 isShared.5 : tobj :=
let _x.6 : tobj := Nat.add val.1 val.2;
dec val.2;
jp reusejp.7 _x.8 : tobj :=
return _x.8;
cases isShared.5 : tobj
| Bool.false =>
oset _x.4 [0] := _x.6;
goto reusejp.7 _x.4
| Bool.true =>
let reuseFailAlloc.9 : obj := ctor_1[Option.some] _x.6;
goto reusejp.7 reuseFailAlloc.9;
let isSharedCheck.10 : UInt8 := isShared b;
cases isSharedCheck.10 : tobj
| Bool.false =>
goto resetjp.3 b isSharedCheck.10
| Bool.true =>
inc val.2;
dec b;
goto resetjp.3 ◾ isSharedCheck.10
[Compiler.pushProj] size: 2
def test2._boxed a b : tobj :=
let res : tobj := test2 a b;
dec a;
return res
-/
#guard_msgs in
set_option pp.letVarTypes true in
@@ -69,6 +123,63 @@ trace: [Compiler.pushProj] size: 14
let _x.7 : tobj := Nat.add val.5 val.6;
let _x.8 : obj := ctor_1[Option.some] _x.7;
return _x.8
[Compiler.pushProj] size: 48
def test3 a b : tobj :=
cases a : tobj
| Option.none =>
dec b;
return a
| Option.some =>
cases b : tobj
| Option.none =>
let val.1 : tobj := oproj[0] a;
jp resetjp.2 _x.3 isShared.4 : tobj :=
let _x.5 : tagged := 1;
let _x.6 : tobj := Nat.add val.1 _x.5;
dec val.1;
jp reusejp.7 _x.8 : tobj :=
return _x.8;
cases isShared.4 : tobj
| Bool.false =>
oset _x.3 [0] := _x.6;
goto reusejp.7 _x.3
| Bool.true =>
let reuseFailAlloc.9 : obj := ctor_1[Option.some] _x.6;
goto reusejp.7 reuseFailAlloc.9;
let isSharedCheck.10 : UInt8 := isShared a;
cases isSharedCheck.10 : tobj
| Bool.false =>
goto resetjp.2 a isSharedCheck.10
| Bool.true =>
inc val.1;
dec a;
goto resetjp.2 ◾ isSharedCheck.10
| Option.some =>
let val.11 : tobj := oproj[0] a;
inc val.11;
dec a;
let val.12 : tobj := oproj[0] b;
jp resetjp.13 _x.14 isShared.15 : tobj :=
let _x.16 : tobj := Nat.add val.11 val.12;
dec val.12;
dec val.11;
jp reusejp.17 _x.18 : tobj :=
return _x.18;
cases isShared.15 : tobj
| Bool.false =>
oset _x.14 [0] := _x.16;
goto reusejp.17 _x.14
| Bool.true =>
let reuseFailAlloc.19 : obj := ctor_1[Option.some] _x.16;
goto reusejp.17 reuseFailAlloc.19;
let isSharedCheck.20 : UInt8 := isShared b;
cases isSharedCheck.20 : tobj
| Bool.false =>
goto resetjp.13 b isSharedCheck.20
| Bool.true =>
inc val.12;
dec b;
goto resetjp.13 ◾ isSharedCheck.20
-/
#guard_msgs in
set_option pp.letVarTypes true in
@@ -106,6 +217,75 @@ trace: [Compiler.pushProj] size: 18
let _x.8 : tobj := Nat.add val.6 val.7;
let _x.9 : obj := ctor_1[Option.some] _x.8;
return _x.9
[Compiler.pushProj] size: 54
def test4 a b c : tobj :=
cases a : tobj
| Option.none =>
dec b;
return a
| Option.some =>
cases b : tobj
| Option.none =>
let val.1 : tobj := oproj[0] a;
jp resetjp.2 _x.3 isShared.4 : tobj :=
let _x.5 : tagged := 1;
let _x.6 : tobj := Nat.add val.1 _x.5;
dec val.1;
jp reusejp.7 _x.8 : tobj :=
return _x.8;
cases isShared.4 : tobj
| Bool.false =>
oset _x.3 [0] := _x.6;
goto reusejp.7 _x.3
| Bool.true =>
let reuseFailAlloc.9 : obj := ctor_1[Option.some] _x.6;
goto reusejp.7 reuseFailAlloc.9;
let isSharedCheck.10 : UInt8 := isShared a;
cases isSharedCheck.10 : tobj
| Bool.false =>
goto resetjp.2 a isSharedCheck.10
| Bool.true =>
inc val.1;
dec a;
goto resetjp.2 ◾ isSharedCheck.10
| Option.some =>
cases c : tobj
| Bool.false =>
dec b;
dec a;
let _x.11 : tagged := ctor_0[Option.none];
return _x.11
| Bool.true =>
let val.12 : tobj := oproj[0] a;
inc val.12;
dec a;
let val.13 : tobj := oproj[0] b;
jp resetjp.14 _x.15 isShared.16 : tobj :=
let _x.17 : tobj := Nat.add val.12 val.13;
dec val.13;
dec val.12;
jp reusejp.18 _x.19 : tobj :=
return _x.19;
cases isShared.16 : tobj
| Bool.false =>
oset _x.15 [0] := _x.17;
goto reusejp.18 _x.15
| Bool.true =>
let reuseFailAlloc.20 : obj := ctor_1[Option.some] _x.17;
goto reusejp.18 reuseFailAlloc.20;
let isSharedCheck.21 : UInt8 := isShared b;
cases isSharedCheck.21 : tobj
| Bool.false =>
goto resetjp.14 b isSharedCheck.21
| Bool.true =>
inc val.13;
dec b;
goto resetjp.14 ◾ isSharedCheck.21
[Compiler.pushProj] size: 2
def test4._boxed a b c : tobj :=
let c.boxed : UInt8 := unbox c;
let res : tobj := test4 a b c.boxed;
return res
-/
#guard_msgs in
set_option pp.letVarTypes true in

View File

@@ -33,6 +33,21 @@ trace: [Compiler.saveMono] size: 5
| Option.some =>
let _x.2 := 0;
return _x.2
[Compiler.pushProj] size: 5
def isNone @&x : UInt8 :=
cases x : UInt8
| Option.none =>
let _x.1 := 1;
return _x.1
| Option.some =>
let _x.2 := 0;
return _x.2
[Compiler.pushProj] size: 3
def isNone._boxed x : tagged :=
let res := isNone x;
dec x;
let r := box res;
return r
-/
#guard_msgs in
set_option trace.Compiler.saveMono true in

View File

@@ -1,5 +1,5 @@
set_option trace.compiler.ir.result true
set_option trace.Compiler.saveMono true
def g (ys : List Nat) : IO Nat := do
let x := 0

View File

@@ -1,95 +1,46 @@
[Compiler.IR] [result]
def IO.print._at_.IO.println._at_.g.spec_0.spec_0 (x_1 : obj) (x_2 : void) : obj :=
let x_3 : obj := IO.getStdout ;
let x_4 : obj := proj[4] x_3;
inc x_4;
dec x_3;
let x_5 : obj := app x_4 x_1 ◾;
ret x_5
def IO.print._at_.IO.println._at_.g.spec_0.spec_0._boxed (x_1 : obj) (x_2 : tagged) : obj :=
let x_3 : obj := IO.print._at_.IO.println._at_.g.spec_0.spec_0 x_1 x_2;
ret x_3
[Compiler.IR] [result]
def IO.println._at_.g.spec_0 (x_1 : tobj) (x_2 : void) : obj :=
let x_3 : obj := Nat.reprFast x_1;
let x_4 : u32 := 10;
let x_5 : obj := String.push x_3 x_4;
let x_6 : obj := IO.print._at_.IO.println._at_.g.spec_0.spec_0 x_5 ◾;
ret x_6
def IO.println._at_.g.spec_0._boxed (x_1 : tobj) (x_2 : tagged) : obj :=
let x_3 : obj := IO.println._at_.g.spec_0 x_1 x_2;
ret x_3
[Compiler.IR] [result]
def List.forM._at_.g.spec_1 (x_1 : tobj) (x_2 : tobj) (x_3 : void) : obj :=
case x_1 : tobj of
List.nil →
let x_4 : tagged := ctor_0[PUnit.unit];
let x_5 : obj := ctor_0[Prod.mk] x_4 x_2;
let x_6 : obj := ctor_0[EST.Out.ok] x_5;
ret x_6
List.cons →
let x_7 : tobj := proj[0] x_1;
inc x_7;
let x_8 : tobj := proj[1] x_1;
inc x_8;
dec x_1;
let x_9 : obj := IO.println._at_.g.spec_0 x_7 ◾;
case x_9 : obj of
EST.Out.ok →
dec x_9;
let x_10 : obj := List.forM._at_.g.spec_1 x_8 x_2 ◾;
ret x_10
EST.Out.error →
dec x_8;
dec x_2;
let x_11 : u8 := isShared x_9;
case x_11 : u8 of
Bool.false →
ret x_9
Bool.true →
let x_12 : tobj := proj[0] x_9;
inc x_12;
dec x_9;
let x_13 : obj := ctor_1[EST.Out.error] x_12;
ret x_13
def List.forM._at_.g.spec_1._boxed (x_1 : tobj) (x_2 : tobj) (x_3 : tagged) : obj :=
let x_4 : obj := List.forM._at_.g.spec_1 x_1 x_2 x_3;
ret x_4
[Compiler.IR] [result]
def g (x_1 : tobj) (x_2 : void) : obj :=
let x_3 : tagged := 0;
let x_4 : obj := List.forM._at_.g.spec_1 x_1 x_3 ◾;
case x_4 : obj of
EST.Out.ok →
let x_5 : u8 := isShared x_4;
case x_5 : u8 of
Bool.false →
let x_6 : tobj := proj[0] x_4;
let x_7 : tobj := proj[1] x_6;
inc x_7;
dec x_6;
set x_4[0] := x_7;
ret x_4
Bool.true →
let x_8 : tobj := proj[0] x_4;
inc x_8;
dec x_4;
let x_9 : tobj := proj[1] x_8;
inc x_9;
dec x_8;
let x_10 : obj := ctor_0[EST.Out.ok] x_9;
ret x_10
EST.Out.error →
let x_11 : u8 := isShared x_4;
case x_11 : u8 of
Bool.false →
ret x_4
Bool.true →
let x_12 : tobj := proj[0] x_4;
inc x_12;
dec x_4;
let x_13 : obj := ctor_1[EST.Out.error] x_12;
ret x_13
def g._boxed (x_1 : tobj) (x_2 : tagged) : obj :=
let x_3 : obj := g x_1 x_2;
ret x_3
[Compiler.saveMono] size: 6
def IO.print._at_.IO.println._at_.g.spec_0.spec_0 s a.1 : EST.Out IO.Error lcAny PUnit :=
let _x.2 := IO.getStdout a.1;
cases _x.2 : EST.Out IO.Error lcAny PUnit
| ST.Out.mk val.3 state.4 =>
cases val.3 : EST.Out IO.Error lcAny PUnit
| IO.FS.Stream.mk flush read write getLine putStr isTty =>
let _x.5 := putStr s state.4;
return _x.5
[Compiler.saveMono] size: 4
def IO.println._at_.g.spec_0 s a.1 : EST.Out IO.Error lcAny PUnit :=
let _x.2 := Nat.reprFast s;
let _x.3 := 10;
let _x.4 := String.push _x.2 _x.3;
let _x.5 := IO.print._at_.IO.println._at_.g.spec_0.spec_0 _x.4 a.1;
return _x.5
[Compiler.saveMono] size: 12
def List.forM._at_.g.spec_1 as _y.1 _y.2 : EST.Out IO.Error lcAny (Prod PUnit Nat) :=
cases as : EST.Out IO.Error lcAny (Prod PUnit Nat)
| List.nil =>
let _x.3 := PUnit.unit;
let _x.4 := Prod.mk ◾ ◾ _x.3 _y.1;
let _x.5 := @EST.Out.ok ◾ ◾ ◾ _x.4 _y.2;
return _x.5
| List.cons head.6 tail.7 =>
let _x.8 := IO.println._at_.g.spec_0 head.6 _y.2;
cases _x.8 : EST.Out IO.Error lcAny (Prod PUnit Nat)
| EST.Out.ok a.9 a.10 =>
let _x.11 := List.forM._at_.g.spec_1 tail.7 _y.1 a.10;
return _x.11
| EST.Out.error a.12 a.13 =>
let _x.14 := @EST.Out.error ◾ ◾ ◾ a.12 a.13;
return _x.14
[Compiler.saveMono] size: 9
def g ys a.1 : EST.Out IO.Error lcAny Nat :=
let x := 0;
let _x.2 := List.forM._at_.g.spec_1 ys x a.1;
cases _x.2 : EST.Out IO.Error lcAny Nat
| EST.Out.ok a.3 a.4 =>
cases a.3 : EST.Out IO.Error lcAny Nat
| Prod.mk fst.5 snd.6 =>
let _x.7 := @EST.Out.ok ◾ ◾ ◾ snd.6 a.4;
return _x.7
| EST.Out.error a.8 a.9 =>
let _x.10 := @EST.Out.error ◾ ◾ ◾ a.8 a.9;
return _x.10