lean4-htt/src/Lean/Compiler/IR/RC.lean

310 lines
12 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

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

/-
Copyright (c) 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