blob: c4a9d425edd4528578ae7614fc9d364677bd6311 [file]
/-
Copyright (c) 2022 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.Compiler.LCNF.InferType
namespace Lean.Compiler.LCNF
/-- Helper class for lifting `CompilerM.codeBind` -/
class MonadCodeBind (m : Type → Type) where
codeBind : (c : Code) → (f : FVarId → m Code) → m Code
/--
Return code that is equivalent to `c >>= f`. That is, executes `c`, and then `f x`, where
`x` is a variable that contains the result of `c`'s computation.
If `c` contains a jump to a join point `jp_i` not declared in `c`, we throw an exception because
an invalid block would be generated. It would be invalid because `f` would not
be applied to `jp_i`. Note that, we could have decided to create a copy of `jp_i` where we apply `f` to it,
by we decided to not do it to avoid code duplication.
-/
abbrev Code.bind [MonadCodeBind m] (c : Code) (f : FVarId → m Code) : m Code :=
MonadCodeBind.codeBind c f
partial def CompilerM.codeBind (c : Code) (f : FVarId → CompilerM Code) : CompilerM Code := do
go c |>.run {}
where
go (c : Code) : ReaderT FVarIdSet CompilerM Code := do
match c with
| .let decl k => return .let decl (← go k)
| .fun decl k => return .fun decl (← go k)
| .jp decl k =>
let value ← go decl.value
let type ← value.inferParamType decl.params
let decl ← decl.update' type value
withReader (fun s => s.insert decl.fvarId) do
return .jp decl (← go k)
| .cases c =>
let alts ← c.alts.mapM fun
| .alt ctorName params k => return .alt ctorName params (← go k)
| .default k => return .default (← go k)
if alts.isEmpty then
throwError "`Code.bind` failed, empty `cases` found"
let resultType ← mkCasesResultType alts
return .cases { c with alts, resultType }
| .return fvarId => f fvarId
| .jmp fvarId .. =>
unless (← read).contains fvarId do
throwError "`Code.bind` failed, it contains a out of scope join point"
return c
| .unreach type =>
/-
Create an auxiliary parameter `aux : type` to compute the resulting type of `f aux`.
This code is not very efficient, we could ask caller to provide the type of `c >>= f`,
but this is more convenient, and this case is seldom reached.
-/
let auxParam ← mkAuxParam type
let k ← f auxParam.fvarId
let typeNew ← k.inferType
eraseCode k
eraseParam auxParam
return .unreach typeNew
instance : MonadCodeBind CompilerM where
codeBind := CompilerM.codeBind
instance [MonadCodeBind m] : MonadCodeBind (ReaderT ρ m) where
codeBind c f ctx := c.bind fun fvarId => f fvarId ctx
instance [STWorld ω m] [MonadCodeBind m] : MonadCodeBind (StateRefT' ω σ m) where
codeBind c f sref := c.bind fun fvarId => f fvarId sref
/--
Create new parameters for the given arrow type.
Example: if `type` is `Nat → Bool → Int`, the result is
an array containing two new parameters with types `Nat` and `Bool`.
-/
partial def mkNewParams (type : Expr) : CompilerM (Array Param) :=
go type #[] #[]
where
go (type : Expr) (xs : Array Expr) (ps : Array Param) : CompilerM (Array Param) := do
match type with
| .forallE _ d b _ =>
let d := d.instantiateRev xs
let p ← mkAuxParam d
go b (xs.push (.fvar p.fvarId)) (ps.push p)
| _ =>
let type := type.instantiateRev xs
let type' := type.headBeta
if type' != type then
go type' #[] ps
else
return ps
def isEtaExpandCandidateCore (type : Expr) (params : Array Param) : Bool :=
let typeArity := getArrowArity type
let valueArity := params.size
typeArity > valueArity
abbrev FunDeclCore.isEtaExpandCandidate (decl : FunDecl) : Bool :=
isEtaExpandCandidateCore decl.type decl.params
def etaExpandCore (type : Expr) (params : Array Param) (value : Code) : CompilerM (Array Param × Code) := do
let valueType ← instantiateForall type (params.map (mkFVar ·.fvarId))
let psNew ← mkNewParams valueType
let params := params ++ psNew
let xs := psNew.map fun p => .fvar p.fvarId
let value ← value.bind fun fvarId => do
let auxDecl ← mkAuxLetDecl (.fvar fvarId xs)
return .let auxDecl (.return auxDecl.fvarId)
return (params, value)
def etaExpandCore? (type : Expr) (params : Array Param) (value : Code) : CompilerM (Option (Array Param × Code)) := do
if isEtaExpandCandidateCore type params then
etaExpandCore type params value
else
return none
def FunDeclCore.etaExpand (decl : FunDecl) : CompilerM FunDecl := do
let some (params, value) ← etaExpandCore? decl.type decl.params decl.value | return decl
decl.update decl.type params value
def Decl.etaExpand (decl : Decl) : CompilerM Decl := do
let some (params, value) ← etaExpandCore? decl.type decl.params decl.value | return decl
return { decl with params, value }
end Lean.Compiler.LCNF