310 lines
12 KiB
Text
310 lines
12 KiB
Text
/-
|
||
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.Runtime
|
||
public import Lean.Compiler.IR.CompilerM
|
||
public import Lean.Compiler.IR.LiveVars
|
||
|
||
public section
|
||
|
||
namespace Lean.IR.ExplicitRC
|
||
/-!
|
||
Insert explicit RC instructions. So, it assumes the input code does not contain `inc` nor `dec` instructions.
|
||
This transformation is applied before lower level optimizations
|
||
that introduce the instructions `release` and `set`
|
||
-/
|
||
|
||
structure VarInfo where
|
||
isPossibleRef : Bool
|
||
isDefiniteRef: Bool
|
||
persistent : Bool
|
||
inheritsBorrowFromParam : Bool
|
||
deriving Inhabited
|
||
|
||
abbrev VarMap := Std.TreeMap VarId VarInfo (fun x y => compare x.idx y.idx)
|
||
|
||
structure Context where
|
||
env : Environment
|
||
decls : Array Decl
|
||
varMap : VarMap := {}
|
||
jpLiveVarMap : JPLiveVarMap := {} -- map: join point => live variables
|
||
localCtx : LocalContext := {} -- we use it to store the join point declarations
|
||
|
||
def getDecl (ctx : Context) (fid : FunId) : Decl :=
|
||
findEnvDecl' ctx.env fid ctx.decls |>.get!
|
||
|
||
def getVarInfo (ctx : Context) (x : VarId) : VarInfo :=
|
||
ctx.varMap.get! x
|
||
|
||
def getJPParams (ctx : Context) (j : JoinPointId) : Array Param :=
|
||
ctx.localCtx.getJPParams j |>.get!
|
||
|
||
def getJPLiveVars (ctx : Context) (j : JoinPointId) : LiveVarSet :=
|
||
ctx.jpLiveVarMap.get? j |>.getD {}
|
||
|
||
def mustConsume (ctx : Context) (x : VarId) : Bool :=
|
||
let info := getVarInfo ctx x
|
||
info.isPossibleRef && !info.inheritsBorrowFromParam
|
||
|
||
@[inline] def addInc (ctx : Context) (x : VarId) (b : FnBody) (n := 1) : FnBody :=
|
||
let info := getVarInfo ctx x
|
||
if n == 0 then b else .inc x n (!info.isDefiniteRef) info.persistent b
|
||
|
||
@[inline] def addDec (ctx : Context) (x : VarId) (b : FnBody) : FnBody :=
|
||
let info := getVarInfo ctx x
|
||
.dec x 1 (!info.isDefiniteRef) info.persistent b
|
||
|
||
private def updateRefUsingCtorInfo (ctx : Context) (x : VarId) (c : CtorInfo) : Context :=
|
||
let m := ctx.varMap
|
||
{ ctx with
|
||
varMap := match m.get? x with
|
||
| some info =>
|
||
let isPossibleRef := c.type.isPossibleRef
|
||
let isDefiniteRef := c.type.isDefiniteRef
|
||
m.insert x { info with isPossibleRef, isDefiniteRef }
|
||
| none => m
|
||
}
|
||
|
||
private def addDecForAlt (ctx : Context) (caseLiveVars altLiveVars : LiveVarSet) (b : FnBody) : FnBody :=
|
||
caseLiveVars.foldl (init := b) fun b x =>
|
||
if !altLiveVars.contains x && mustConsume ctx x then addDec ctx x b else b
|
||
|
||
/-- `isFirstOcc xs x i = true` if `xs[i]` is the first occurrence of `xs[i]` in `xs` -/
|
||
private def isFirstOcc (xs : Array Arg) (i : Nat) : Bool :=
|
||
let x := xs[i]!
|
||
i.all fun j _ => xs[j]! != x
|
||
|
||
/-- Return true if `x` also occurs in `ys` in a position that is not consumed.
|
||
That is, it is also passed as a borrow reference. -/
|
||
private def isBorrowParamAux (x : VarId) (ys : Array Arg) (consumeParamPred : Nat → Bool) : Bool :=
|
||
ys.size.any fun i _ =>
|
||
let y := ys[i]
|
||
match y with
|
||
| .erased => false
|
||
| .var y => x == y && !consumeParamPred i
|
||
|
||
private def isBorrowParam (x : VarId) (ys : Array Arg) (ps : Array Param) : Bool :=
|
||
isBorrowParamAux x ys fun i => ! ps[i]!.borrow
|
||
|
||
/--
|
||
Return `n`, the number of times `x` is consumed.
|
||
- `ys` is a sequence of instruction parameters where we search for `x`.
|
||
- `consumeParamPred i = true` if parameter `i` is consumed.
|
||
-/
|
||
private def getNumConsumptions (x : VarId) (ys : Array Arg) (consumeParamPred : Nat → Bool) : Nat :=
|
||
ys.size.fold (init := 0) fun i _ n =>
|
||
let y := ys[i]
|
||
match y with
|
||
| .erased => n
|
||
| .var y => if x == y && consumeParamPred i then n+1 else n
|
||
|
||
private def addIncBeforeAux (ctx : Context) (xs : Array Arg) (consumeParamPred : Nat → Bool) (b : FnBody) (liveVarsAfter : LiveVarSet) : FnBody :=
|
||
xs.size.fold (init := b) fun i _ b =>
|
||
let x := xs[i]
|
||
match x with
|
||
| .erased => b
|
||
| .var x =>
|
||
let info := getVarInfo ctx x
|
||
if !info.isPossibleRef || !isFirstOcc xs i then b
|
||
else
|
||
let numConsumptions := getNumConsumptions x xs consumeParamPred
|
||
let numIncs :=
|
||
if info.inheritsBorrowFromParam ||
|
||
liveVarsAfter.contains x || -- `x` is live after executing instruction
|
||
isBorrowParamAux x xs consumeParamPred -- `x` is used in a position that is passed as a borrow reference
|
||
then numConsumptions
|
||
else numConsumptions - 1
|
||
addInc ctx x b numIncs
|
||
|
||
private def addIncBefore (ctx : Context) (xs : Array Arg) (ps : Array Param) (b : FnBody) (liveVarsAfter : LiveVarSet) : FnBody :=
|
||
addIncBeforeAux ctx xs (fun i => ! ps[i]!.borrow) b liveVarsAfter
|
||
|
||
/-- See `addIncBeforeAux`/`addIncBefore` for the procedure that inserts `inc` operations before an application. -/
|
||
private def addDecAfterFullApp (ctx : Context) (xs : Array Arg) (ps : Array Param) (b : FnBody) (bLiveVars : LiveVarSet) : FnBody :=
|
||
xs.size.fold (init := b) fun i _ b =>
|
||
match xs[i] with
|
||
| .erased => b
|
||
| .var x =>
|
||
/- We must add a `dec` if `x` must be consumed, it is alive after the application,
|
||
and it has been borrowed by the application.
|
||
Remark: `x` may occur multiple times in the application (e.g., `f x y x`).
|
||
This is why we check whether it is the first occurrence. -/
|
||
if mustConsume ctx x && isFirstOcc xs i && isBorrowParam x xs ps && !bLiveVars.contains x then
|
||
addDec ctx x b
|
||
else b
|
||
|
||
private def addIncBeforeConsumeAll (ctx : Context) (xs : Array Arg) (b : FnBody) (liveVarsAfter : LiveVarSet) : FnBody :=
|
||
addIncBeforeAux ctx xs (fun _ => true) b liveVarsAfter
|
||
|
||
/-- Add `dec` instructions for parameters that are references, are not alive in `b`, and are not borrow.
|
||
That is, we must make sure these parameters are consumed. -/
|
||
private def addDecForDeadParams (ctx : Context) (ps : Array Param) (b : FnBody) (bLiveVars : LiveVarSet) : FnBody × LiveVarSet :=
|
||
ps.foldl (init := ⟨b, bLiveVars⟩) fun ⟨b, bLiveVars⟩ p =>
|
||
let b :=
|
||
if !p.borrow && p.ty.isPossibleRef && !bLiveVars.contains p.x then
|
||
addDec ctx p.x b
|
||
else b
|
||
let bLiveVars := bLiveVars.erase p.x
|
||
⟨b, bLiveVars⟩
|
||
|
||
private def isPersistent : Expr → Bool
|
||
| .fap _ xs => xs.isEmpty -- all global constants are persistent objects
|
||
| _ => false
|
||
|
||
/-- Return true iff `v` at runtime is a scalar value stored in a tagged pointer.
|
||
We do not need RC operations for this kind of value. -/
|
||
private def typeForScalarBoxedInTaggedPtr? (v : Expr) : Option IRType :=
|
||
match v with
|
||
| .ctor c _ =>
|
||
some c.type
|
||
| .lit (.num n) =>
|
||
if n ≤ maxSmallNat then
|
||
some .tagged
|
||
else
|
||
some .tobject
|
||
| _ => none
|
||
|
||
private def updateVarInfo (ctx : Context) (x : VarId) (t : IRType) (v : Expr) : Context :=
|
||
let inheritsBorrowFromParam :=
|
||
match v with
|
||
| .proj _ x => match ctx.varMap.get? x with
|
||
| some info => info.inheritsBorrowFromParam
|
||
| none => false
|
||
| _ => false
|
||
let type := typeForScalarBoxedInTaggedPtr? v |>.getD t
|
||
let isPossibleRef := type.isPossibleRef
|
||
let isDefiniteRef := type.isDefiniteRef
|
||
{ ctx with
|
||
varMap := ctx.varMap.insert x {
|
||
isPossibleRef
|
||
isDefiniteRef
|
||
persistent := isPersistent v,
|
||
inheritsBorrowFromParam
|
||
}
|
||
}
|
||
|
||
private def addDecIfNeeded (ctx : Context) (x : VarId) (b : FnBody) (bLiveVars : LiveVarSet) : FnBody :=
|
||
if mustConsume ctx x && !bLiveVars.contains x then addDec ctx x b else b
|
||
|
||
private def processVDecl (ctx : Context) (z : VarId) (t : IRType) (v : Expr) (b : FnBody) (bLiveVars : LiveVarSet) : FnBody × LiveVarSet :=
|
||
let b := match v with
|
||
| .ctor _ ys | .reuse _ _ _ ys | .pap _ ys =>
|
||
addIncBeforeConsumeAll ctx ys (.vdecl z t v b) bLiveVars
|
||
| .proj _ x =>
|
||
let b := addDecIfNeeded ctx x b bLiveVars
|
||
let b := if !(getVarInfo ctx x).inheritsBorrowFromParam then addInc ctx z b else b
|
||
.vdecl z t v b
|
||
| .uproj _ x | .sproj _ _ x | .unbox x =>
|
||
.vdecl z t v (addDecIfNeeded ctx x b bLiveVars)
|
||
| .fap f ys =>
|
||
let ps := (getDecl ctx f).params
|
||
let b := addDecAfterFullApp ctx ys ps b bLiveVars
|
||
let b := .vdecl z t v b
|
||
addIncBefore ctx ys ps b bLiveVars
|
||
| .ap x ys =>
|
||
let ysx := ys.push (.var x) -- TODO: avoid temporary array allocation
|
||
addIncBeforeConsumeAll ctx ysx (.vdecl z t v b) bLiveVars
|
||
| .lit _ | .box .. | .reset .. | .isShared _ =>
|
||
.vdecl z t v b
|
||
let liveVars := updateLiveVars v bLiveVars
|
||
let liveVars := liveVars.erase z
|
||
⟨b, liveVars⟩
|
||
|
||
def updateVarInfoWithParams (ctx : Context) (ps : Array Param) : Context :=
|
||
let m := ps.foldl (init := ctx.varMap) fun m p =>
|
||
m.insert p.x {
|
||
isPossibleRef := p.ty.isPossibleRef
|
||
isDefiniteRef := p.ty.isDefiniteRef
|
||
persistent := false
|
||
inheritsBorrowFromParam := p.borrow }
|
||
{ ctx with varMap := m }
|
||
|
||
partial def visitFnBody (b : FnBody) (ctx : Context) : FnBody × LiveVarSet :=
|
||
match b with
|
||
| .vdecl x t v b =>
|
||
let ctx := updateVarInfo ctx x t v
|
||
let ⟨b, bLiveVars⟩ := visitFnBody b ctx
|
||
processVDecl ctx x t v b bLiveVars
|
||
| .jdecl j xs v b =>
|
||
let ctxAtV := updateVarInfoWithParams ctx xs
|
||
let ⟨v, vLiveVars⟩ := visitFnBody v ctxAtV
|
||
let ⟨v, vLiveVars⟩ := addDecForDeadParams ctxAtV xs v vLiveVars
|
||
let ctx := { ctx with
|
||
localCtx := ctx.localCtx.addJP j xs v
|
||
jpLiveVarMap := ctx.jpLiveVarMap.insert j vLiveVars
|
||
}
|
||
let ⟨b, bLiveVars⟩ := visitFnBody b ctx
|
||
⟨.jdecl j xs v b, bLiveVars⟩
|
||
| .uset x i y b =>
|
||
let ⟨b, s⟩ := visitFnBody b ctx
|
||
-- We don't need to insert `y` since we only need to track live variables that are references at runtime
|
||
let s := s.insert x
|
||
⟨.uset x i y b, s⟩
|
||
| .sset x i o y t b =>
|
||
let ⟨b, s⟩ := visitFnBody b ctx
|
||
-- We don't need to insert `y` since we only need to track live variables that are references at runtime
|
||
let s := s.insert x
|
||
⟨.sset x i o y t b, s⟩
|
||
| .case tid x xType alts =>
|
||
let alts : Array (Alt × LiveVarSet) := alts.map fun alt => match alt with
|
||
| .ctor c b =>
|
||
let ctx := updateRefUsingCtorInfo ctx x c
|
||
let ⟨b, altLiveVars⟩ := visitFnBody b ctx
|
||
⟨.ctor c b, altLiveVars⟩
|
||
| .default b =>
|
||
let ⟨b, altLiveVars⟩ := visitFnBody b ctx
|
||
⟨.default b, altLiveVars⟩
|
||
let caseLiveVars : LiveVarSet := alts.foldl (init := {}) fun liveVars ⟨_, altLiveVars⟩ =>
|
||
liveVars.merge altLiveVars
|
||
let caseLiveVars := caseLiveVars.insert x
|
||
let alts := alts.map fun ⟨alt, altLiveVars⟩ => match alt with
|
||
| .ctor c b =>
|
||
let ctx := updateRefUsingCtorInfo ctx x c
|
||
let b := addDecForAlt ctx caseLiveVars altLiveVars b
|
||
.ctor c b
|
||
| .default b =>
|
||
let b := addDecForAlt ctx caseLiveVars altLiveVars b
|
||
.default b
|
||
⟨.case tid x xType alts, caseLiveVars⟩
|
||
| .ret x =>
|
||
match x with
|
||
| .var x =>
|
||
let info := getVarInfo ctx x
|
||
let b :=
|
||
if info.isPossibleRef && info.inheritsBorrowFromParam then
|
||
addInc ctx x b
|
||
else b
|
||
⟨b, mkLiveVarSet x⟩
|
||
| .erased => ⟨b, {}⟩
|
||
| .jmp j xs =>
|
||
let jLiveVars := getJPLiveVars ctx j
|
||
let ps := getJPParams ctx j
|
||
let b := addIncBefore ctx xs ps b jLiveVars
|
||
let bLiveVars := collectLiveVars b ctx.jpLiveVarMap
|
||
⟨b, bLiveVars⟩
|
||
| .unreachable => ⟨.unreachable, {}⟩
|
||
| .set .. | .setTag .. | .inc .. | .dec .. | .del .. => unreachable!
|
||
|
||
partial def visitDecl (env : Environment) (decls : Array Decl) (d : Decl) : Decl :=
|
||
match d with
|
||
| .fdecl (xs := xs) (body := b) .. =>
|
||
let ctx := updateVarInfoWithParams { env, decls } xs
|
||
let ⟨b, bLiveVars⟩ := visitFnBody b ctx
|
||
let ⟨b, _⟩ := addDecForDeadParams ctx xs b bLiveVars
|
||
d.updateBody! b
|
||
| other => other
|
||
|
||
end ExplicitRC
|
||
|
||
def explicitRC (decls : Array Decl) : CompilerM (Array Decl) := do
|
||
let env ← getEnv
|
||
return decls.map (ExplicitRC.visitDecl env decls)
|
||
|
||
builtin_initialize registerTraceClass `compiler.ir.rc (inherited := true)
|
||
|
||
end Lean.IR
|