Skip to content

Commit af5b4eb

Browse files
dsymeKevinRansom
authored andcommitted
Permit reduction of Some allocations for optional arguments (#6533)
* Optimization of inlined code doesn't always reduce `Some` allocations for optional arguments * add codegen test * add baseline * Update OptionalArg01.fs
1 parent 7c0c71f commit af5b4eb

7 files changed

Lines changed: 382 additions & 18 deletions

File tree

src/fsharp/Optimizer.fs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ type OptimizationSettings =
354354
/// eliminate non-compiler generated immediate bindings
355355
member x.EliminateImmediatelyConsumedLocals() = x.localOpt ()
356356

357-
/// expand "let x = (exp1, exp2, ...)" bind fields as prior tmps
357+
/// expand "let x = (exp1, exp2, ...)" bindings as prior tmps
358+
/// expand "let x = Some exp1" bindings as prior tmps
358359
member x.ExpandStructrualValues() = x.localOpt ()
359360

360361
type cenv =
@@ -1552,6 +1553,8 @@ let rec CombineBoolLogic expr =
15521553
// Similarly for other structural constructions, like records...
15531554
// If the item is only projected from then the construction (allocation) can be eliminated.
15541555
// This transform encourages that by allowing projections to be simplified.
1556+
//
1557+
// Apply the same to 'Some(x)' constructions
15551558
//-------------------------------------------------------------------------
15561559

15571560
let CanExpandStructuralBinding (v: Val) =
@@ -1562,7 +1565,13 @@ let CanExpandStructuralBinding (v: Val) =
15621565

15631566
let ExprIsValue = function Expr.Val _ -> true | _ -> false
15641567

1568+
let MakeStructuralBindingTemp (v: Val) i (arg: Expr) argTy =
1569+
let name = v.LogicalName + "_" + string i
1570+
let v, ve = mkCompGenLocal arg.Range name argTy
1571+
ve, mkCompGenBind v arg
1572+
15651573
let ExpandStructuralBindingRaw cenv expr =
1574+
assert cenv.settings.ExpandStructrualValues()
15661575
match expr with
15671576
| Expr.Let (TBind(v, rhs, tgtSeqPtOpt), body, m, _)
15681577
when (isRefTupleExpr rhs &&
@@ -1572,19 +1581,16 @@ let ExpandStructuralBindingRaw cenv expr =
15721581
expr (* avoid re-expanding when recursion hits original binding *)
15731582
else
15741583
let argTys = destRefTupleTy cenv.g v.Type
1575-
let argBind i (arg: Expr) argTy =
1576-
let name = v.LogicalName + "_" + string i
1577-
let v, ve = mkCompGenLocal arg.Range name argTy
1578-
ve, mkCompGenBind v arg
1579-
1580-
let ves, binds = List.mapi2 argBind args argTys |> List.unzip
1584+
let ves, binds = List.mapi2 (MakeStructuralBindingTemp v) args argTys |> List.unzip
15811585
let tuple = mkRefTupled cenv.g m ves argTys
15821586
mkLetsBind m binds (mkLet tgtSeqPtOpt m v tuple body)
15831587
| expr -> expr
15841588

15851589
// Moves outer tuple binding inside near the tupled expression:
1586-
// let t = (let a0=v0 in let a1=v1 in ... in let an=vn in e0, e1, ..., em) in body
1587-
// let a0=v0 in let a1=v1 in ... in let an=vn in (let t = e0, e1, ..., em in body)
1590+
// let t = (let a0=v0 in let a1=v1 in ... in let an=vn in e0, e1, ..., em) in body
1591+
// becomes
1592+
// let a0=v0 in let a1=v1 in ... in let an=vn in (let t = e0, e1, ..., em in body)
1593+
//
15881594
// This way ExpandStructuralBinding can replace expressions in constants, t is directly bound
15891595
// to a tuple expression so that other optimizations such as OptimizeTupleFieldGet work,
15901596
// and the tuple allocation can be eliminated.
@@ -1600,6 +1606,7 @@ let rec RearrangeTupleBindings expr fin =
16001606
| _ -> None
16011607

16021608
let ExpandStructuralBinding cenv expr =
1609+
assert cenv.settings.ExpandStructrualValues()
16031610
match expr with
16041611
| Expr.Let (TBind(v, rhs, tgtSeqPtOpt), body, m, _)
16051612
when (isRefTupleTy cenv.g v.Type &&
@@ -1608,7 +1615,20 @@ let ExpandStructuralBinding cenv expr =
16081615
match RearrangeTupleBindings rhs (fun top -> mkLet tgtSeqPtOpt m v top body) with
16091616
| Some e -> ExpandStructuralBindingRaw cenv e
16101617
| None -> expr
1611-
| e -> ExpandStructuralBindingRaw cenv e
1618+
1619+
// Expand 'let v = Some arg in ...' to 'let tmp = arg in let v = Some tp in ...'
1620+
// Used to give names to values of optional arguments prior as we inline.
1621+
| Expr.Let (TBind(v, Expr.Op(TOp.UnionCase uc, _, [arg], _), tgtSeqPtOpt), body, m, _)
1622+
when isOptionTy cenv.g v.Type &&
1623+
not (ExprIsValue arg) &&
1624+
cenv.g.unionCaseRefEq uc (mkSomeCase cenv.g) &&
1625+
CanExpandStructuralBinding v ->
1626+
let argTy = destOptionTy cenv.g v.Type
1627+
let ve, bind = MakeStructuralBindingTemp v 0 arg argTy
1628+
let newExpr = mkSome cenv.g argTy ve m
1629+
mkLetBind m bind (mkLet tgtSeqPtOpt m v newExpr body)
1630+
| e ->
1631+
ExpandStructuralBindingRaw cenv e
16121632

16131633
/// Detect a query { ... }
16141634
let (|QueryRun|_|) g expr =

src/fsharp/TastOps.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3143,6 +3143,10 @@ let destLinqExpressionTy g ty =
31433143
let mkNoneCase (g: TcGlobals) = mkUnionCaseRef g.option_tcr_canon "None"
31443144
let mkSomeCase (g: TcGlobals) = mkUnionCaseRef g.option_tcr_canon "Some"
31453145

3146+
let mkSome g ty arg m = mkUnionCaseExpr(mkSomeCase g, [ty], [arg], m)
3147+
3148+
let mkNone g ty m = mkUnionCaseExpr(mkNoneCase g, [ty], [], m)
3149+
31463150
type ValRef with
31473151
member vref.IsDispatchSlot =
31483152
match vref.MemberInfo with

src/fsharp/TastOps.fsi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,6 +1648,10 @@ val mkNil : TcGlobals -> range -> TType -> Expr
16481648

16491649
val mkCons : TcGlobals -> TType -> Expr -> Expr -> Expr
16501650

1651+
val mkSome : TcGlobals -> TType -> Expr -> range -> Expr
1652+
1653+
val mkNone: TcGlobals -> TType -> range -> Expr
1654+
16511655
//-------------------------------------------------------------------------
16521656
// Make a few more expressions
16531657
//-------------------------------------------------------------------------

src/fsharp/TypeChecker.fs

100755100644
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10164,16 +10164,16 @@ and TcMethodApplication
1016410164

1016510165
match calledArg.CallerInfo, env.eCallerMemberName with
1016610166
| CallerLineNumber, _ when typeEquiv cenv.g calledNonOptTy cenv.g.int_ty ->
10167-
let lineExpr = Expr.Const (Const.Int32(mMethExpr.StartLine), mMethExpr, calledNonOptTy)
10168-
emptyPreBinder, mkUnionCaseExpr(mkSomeCase cenv.g, [calledNonOptTy], [lineExpr], mMethExpr)
10167+
let lineExpr = Expr.Const(Const.Int32 mMethExpr.StartLine, mMethExpr, calledNonOptTy)
10168+
emptyPreBinder, mkSome cenv.g calledNonOptTy lineExpr mMethExpr
1016910169
| CallerFilePath, _ when typeEquiv cenv.g calledNonOptTy cenv.g.string_ty ->
10170-
let filePathExpr = Expr.Const (Const.String(FileSystem.GetFullPathShim(mMethExpr.FileName)), mMethExpr, calledNonOptTy)
10171-
emptyPreBinder, mkUnionCaseExpr(mkSomeCase cenv.g, [calledNonOptTy], [filePathExpr], mMethExpr)
10172-
| CallerMemberName, Some callerName when typeEquiv cenv.g calledNonOptTy cenv.g.string_ty ->
10170+
let filePathExpr = Expr.Const (Const.String (FileSystem.GetFullPathShim(mMethExpr.FileName)), mMethExpr, calledNonOptTy)
10171+
emptyPreBinder, mkSome cenv.g calledNonOptTy filePathExpr mMethExpr
10172+
| CallerMemberName, Some(callerName) when typeEquiv cenv.g calledNonOptTy cenv.g.string_ty ->
1017310173
let memberNameExpr = Expr.Const (Const.String callerName, mMethExpr, calledNonOptTy)
10174-
emptyPreBinder, mkUnionCaseExpr(mkSomeCase cenv.g, [calledNonOptTy], [memberNameExpr], mMethExpr)
10174+
emptyPreBinder, mkSome cenv.g calledNonOptTy memberNameExpr mMethExpr
1017510175
| _ ->
10176-
emptyPreBinder, mkUnionCaseExpr(mkNoneCase cenv.g, [calledNonOptTy], [], mMethExpr)
10176+
emptyPreBinder, mkNone cenv.g calledNonOptTy mMethExpr
1017710177

1017810178
// Combine the variable allocators (if any)
1017910179
let wrapper = (wrapper >> wrapper2)
@@ -10207,7 +10207,7 @@ and TcMethodApplication
1020710207
let calledArgTy = assignedArg.CalledArg.CalledArgumentType
1020810208
if isOptionTy cenv.g calledArgTy then
1020910209
let calledNonOptTy = destOptionTy cenv.g calledArgTy
10210-
mkUnionCaseExpr(mkSomeCase cenv.g, [calledNonOptTy], [mkCoerceIfNeeded cenv.g calledNonOptTy callerArgTy expr], m)
10210+
mkSome cenv.g calledNonOptTy (mkCoerceIfNeeded cenv.g calledNonOptTy callerArgTy expr) m
1021110211
else
1021210212
expr // should be unreachable
1021310213

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// #NoMono #NoMT #CodeGen #EmittedIL #Tuples
2+
type A() = class end
3+
4+
// A code+optimization pattern, see https://github.com/Microsoft/visualfsharp/issues/6532
5+
type C() =
6+
static member inline F (?x1: A, ?x2: A) =
7+
let count = 0
8+
let count = match x1 with None -> count | Some _ -> count + 1
9+
let count = match x2 with None -> count | Some _ -> count + 1
10+
let attribs = ResizeArray<_>(count)
11+
match x1 with None -> () | Some v1 -> attribs.Add(v1)
12+
match x2 with None -> () | Some v2 -> attribs.Add(v2)
13+
attribs
14+
15+
//Expect rough equivalent of:
16+
// let d = ResizeArray<_>(0)
17+
// d
18+
let test() =
19+
C.F ()
20+
21+
//Expect rough equivalent of:
22+
// let x1 = A()
23+
// let d = ResizeArray<_>(1)
24+
// d.Add(x1)
25+
// d
26+
let test2() =
27+
C.F (x1=A())
28+
29+
//Expect rough equivalent of:
30+
// let x2 = A()
31+
// let d = ResizeArray<_>(1)
32+
// d.Add(x2)
33+
// d
34+
let test3() =
35+
C.F (x2=A())
36+
37+
//Expect rough equivalent of:
38+
// let x1 = A()
39+
// let x2 = A()
40+
// let d = ResizeArray<_>(2)
41+
// d.Add(x1)
42+
// d.Add(x2)
43+
// d
44+
let test4() =
45+
C.F (x1=A(), x2=A())
46+

0 commit comments

Comments
 (0)