Skip to content

Commit 76ae08d

Browse files
zeuxlatkin
authored andcommitted
Eliminate tuple allocation for implicitly returned formal arguments
closes #335 fixes #330 commit 05175e2a00a05dd444b9c5420946047d77167742 Author: mrange <marten_range@hotmail.com> Date: Wed Mar 25 20:15:44 2015 +0100 Added Tuple elimination IL test commit 15c32975781bbd66626b05bd1834390e02d8b17f Author: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Date: Thu Mar 26 00:20:45 2015 -0700 Eliminate tuple allocation for implicitly returned formal arguments The expression generated for implicitly returned formal arguments does not match the structure that tuple allocation elimination can understand. In particular, it generates a structure: let t = (let a0=v0 in let a1=v1 in ... in e0,e1,...) in expr However, tuple allocation elimination expects the tuple value to be a tupled expression for elimination to work. When t is an immutable local variable, we can rewrite the expression into: let a0=v0 in let a1=v1 in ... in (let t = e0,e1,... in expr) This results in a more direct binding for t, enabling allocation elimination. As a result, using match constructs on System.Collections.Generic.Dictionary TryGetValue as follows: match d.TryGetValue(key) with true, value -> ... | _ -> ... generates the same IL in optimized build as more complicated code with explicit output parameters. Other code patterns may get a similar improvement in reduced GC pressure and increased performance.
1 parent 049d871 commit 76ae08d

File tree

4 files changed

+218
-6
lines changed

4 files changed

+218
-6
lines changed

src/fsharp/opt.fs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,15 +1416,18 @@ let rec (|KnownValApp|_|) expr =
14161416
// This transform encourages that by allowing projections to be simplified.
14171417
//-------------------------------------------------------------------------
14181418

1419+
let CanExpandStructuralBinding (v: Val) =
1420+
not v.IsCompiledAsTopLevel &&
1421+
not v.IsMember &&
1422+
not v.IsTypeFunction &&
1423+
not v.IsMutable
1424+
14191425
let ExprIsValue = function Expr.Val _ -> true | _ -> false
1420-
let ExpandStructuralBinding cenv expr =
1426+
let ExpandStructuralBindingRaw cenv expr =
14211427
match expr with
14221428
| Expr.Let (TBind(v,rhs,tgtSeqPtOpt),body,m,_)
1423-
when (isTupleExpr rhs &&
1424-
not v.IsCompiledAsTopLevel &&
1425-
not v.IsMember &&
1426-
not v.IsTypeFunction &&
1427-
not v.IsMutable) ->
1429+
when (isTupleExpr rhs &&
1430+
CanExpandStructuralBinding v) ->
14281431
let args = tryDestTuple rhs
14291432
if List.forall ExprIsValue args then
14301433
expr (* avoid re-expanding when recursion hits original binding *)
@@ -1440,6 +1443,35 @@ let ExpandStructuralBinding cenv expr =
14401443
mkLetsBind m binds (mkLet tgtSeqPtOpt m v tuple body)
14411444
(* REVIEW: other cases - records, explicit lists etc. *)
14421445
| expr -> expr
1446+
1447+
// Moves outer tuple binding inside near the tupled expression:
1448+
// let t = (let a0=v0 in let a1=v1 in ... in let an=vn in e0,e1,...,em) in body
1449+
// let a0=v0 in let a1=v1 in ... in let an=vn in (let t = e0,e1,...,em in body)
1450+
// This way ExpandStructuralBinding can replace expressions in constants, t is directly bound
1451+
// to a tuple expression so that other optimizations such as OptimizeTupleFieldGet work,
1452+
// and the tuple allocation can be eliminated.
1453+
// Most importantly, this successfully eliminates tuple allocations for implicitly returned
1454+
// formal arguments in method calls.
1455+
let rec RearrangeTupleBindings expr fin =
1456+
match expr with
1457+
| Expr.Let (bind,body,m,_) ->
1458+
match RearrangeTupleBindings body fin with
1459+
| Some b -> Some (mkLetBind m bind b)
1460+
| None -> None
1461+
| Expr.Op (TOp.Tuple,_,_,_) -> Some (fin expr)
1462+
| _ -> None
1463+
1464+
let ExpandStructuralBinding cenv expr =
1465+
match expr with
1466+
| Expr.Let (TBind(v,rhs,tgtSeqPtOpt),body,m,_)
1467+
when (isTupleTy cenv.g v.Type &&
1468+
not (isTupleExpr rhs) &&
1469+
CanExpandStructuralBinding v) ->
1470+
match RearrangeTupleBindings rhs (fun top -> mkLet tgtSeqPtOpt m v top body) with
1471+
| Some e -> ExpandStructuralBindingRaw cenv e
1472+
| None -> expr
1473+
| e -> ExpandStructuralBindingRaw cenv e
1474+
14431475
//-------------------------------------------------------------------------
14441476
// QueryBuilder.Run elimination helpers
14451477
//-------------------------------------------------------------------------
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// #NoMono #NoMT #CodeGen #EmittedIL #Tuples
2+
3+
[<EntryPoint>]
4+
let main argv =
5+
let p v = printfn "%A" v
6+
7+
let dic = System.Collections.Generic.Dictionary<int, int>()
8+
// Tests that the Tuple is eliminated as intended for instance methods
9+
let (b : bool, i : int) = dic.TryGetValue 1
10+
p b
11+
p i
12+
13+
// Tests that the Tuple is eliminated as intended for static methods
14+
let (b : bool, l : int64) as t = System.Int64.TryParse "123"
15+
p b
16+
p l
17+
18+
let tt : bool*int64 = t
19+
20+
// Tests that a Tuple is created as needed when calling p
21+
p tt
22+
23+
0
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
2+
// Microsoft (R) .NET Framework IL Disassembler. Version 4.0.30319.33440
3+
// Copyright (c) Microsoft Corporation. All rights reserved.
4+
5+
6+
7+
// Metadata version: v4.0.30319
8+
.assembly extern mscorlib
9+
{
10+
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
11+
.ver 4:0:0:0
12+
}
13+
.assembly extern FSharp.Core
14+
{
15+
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
16+
.ver 4:4:0:9055
17+
}
18+
.assembly TupleElimination
19+
{
20+
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
21+
int32,
22+
int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
23+
24+
// --- The following custom attribute is added automatically, do not uncomment -------
25+
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 03 00 00 00 00 00 )
26+
27+
.hash algorithm 0x00008004
28+
.ver 0:0:0:0
29+
}
30+
.mresource public FSharpSignatureData.TupleElimination
31+
{
32+
// Offset: 0x00000000 Length: 0x0000022E
33+
}
34+
.mresource public FSharpOptimizationData.TupleElimination
35+
{
36+
// Offset: 0x00000238 Length: 0x0000007B
37+
}
38+
.module TupleElimination.exe
39+
// MVID: {551307BF-DFDD-92DF-A745-0383BF071355}
40+
.imagebase 0x00400000
41+
.file alignment 0x00000200
42+
.stackreserve 0x00100000
43+
.subsystem 0x0003 // WINDOWS_CUI
44+
.corflags 0x00000001 // ILONLY
45+
// Image base: 0x00460000
46+
47+
48+
// =============== CLASS MEMBERS DECLARATION ===================
49+
50+
.class public abstract auto ansi sealed TupleElimination
51+
extends [mscorlib]System.Object
52+
{
53+
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
54+
.method assembly static void p@5<a>(!!a v) cil managed
55+
{
56+
// Code size 31 (0x1f)
57+
.maxstack 4
58+
.locals init ([0] class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!a,class [FSharp.Core]Microsoft.FSharp.Core.Unit>,class [mscorlib]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit> V_0)
59+
.language '{AB4F38C9-B6E6-43BA-BE3B-58080B2CCCE3}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
60+
.line 5,5 : 15,27 ''
61+
IL_0000: nop
62+
IL_0001: ldstr "%A"
63+
IL_0006: newobj instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!a,class [FSharp.Core]Microsoft.FSharp.Core.Unit>,class [mscorlib]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit,!!a>::.ctor(string)
64+
IL_000b: stloc.0
65+
IL_000c: call class [mscorlib]System.IO.TextWriter [mscorlib]System.Console::get_Out()
66+
IL_0011: ldloc.0
67+
IL_0012: call !!0 [FSharp.Core]Microsoft.FSharp.Core.PrintfModule::PrintFormatLineToTextWriter<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!0,class [FSharp.Core]Microsoft.FSharp.Core.Unit>>(class [mscorlib]System.IO.TextWriter,
68+
class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0,class [mscorlib]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
69+
IL_0017: ldarg.0
70+
IL_0018: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!a,class [FSharp.Core]Microsoft.FSharp.Core.Unit>::Invoke(!0)
71+
IL_001d: pop
72+
IL_001e: ret
73+
} // end of method TupleElimination::p@5
74+
75+
.method public static int32 main(string[] argv) cil managed
76+
{
77+
.entrypoint
78+
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor() = ( 01 00 00 00 )
79+
// Code size 101 (0x65)
80+
.maxstack 5
81+
.locals init ([0] class [mscorlib]System.Collections.Generic.Dictionary`2<int32,int32> dic,
82+
[1] int32 V_1,
83+
[2] bool V_2,
84+
[3] int32 V_3,
85+
[4] int64 V_4,
86+
[5] bool V_5,
87+
[6] int64 V_6,
88+
[7] class [mscorlib]System.Tuple`2<bool,int64> t)
89+
.line 7,7 : 5,64 ''
90+
IL_0000: nop
91+
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<int32,int32>::.ctor()
92+
IL_0006: stloc.0
93+
.line 9,9 : 31,48 ''
94+
IL_0007: ldc.i4.0
95+
IL_0008: stloc.1
96+
IL_0009: ldloc.0
97+
IL_000a: ldc.i4.1
98+
IL_000b: ldloca.s V_1
99+
IL_000d: callvirt instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<int32,int32>::TryGetValue(!0,
100+
!1&)
101+
IL_0012: stloc.2
102+
IL_0013: ldloc.1
103+
IL_0014: stloc.3
104+
.line 10,10 : 5,6 ''
105+
IL_0015: ldloc.2
106+
IL_0016: call void TupleElimination::p@5<bool>(!!0)
107+
IL_001b: nop
108+
.line 11,11 : 5,6 ''
109+
IL_001c: ldloc.3
110+
IL_001d: call void TupleElimination::p@5<int32>(!!0)
111+
IL_0022: nop
112+
.line 14,14 : 38,65 ''
113+
IL_0023: ldc.i8 0x0
114+
IL_002c: stloc.s V_4
115+
IL_002e: ldstr "123"
116+
IL_0033: ldloca.s V_4
117+
IL_0035: call bool [mscorlib]System.Int64::TryParse(string,
118+
int64&)
119+
IL_003a: stloc.s V_5
120+
IL_003c: ldloc.s V_4
121+
IL_003e: stloc.s V_6
122+
.line 14,14 : 5,65 ''
123+
IL_0040: ldloc.s V_5
124+
IL_0042: ldloc.s V_6
125+
IL_0044: newobj instance void class [mscorlib]System.Tuple`2<bool,int64>::.ctor(!0,
126+
!1)
127+
IL_0049: stloc.s t
128+
.line 15,15 : 5,6 ''
129+
IL_004b: ldloc.s V_5
130+
IL_004d: call void TupleElimination::p@5<bool>(!!0)
131+
IL_0052: nop
132+
.line 16,16 : 5,6 ''
133+
IL_0053: ldloc.s V_6
134+
IL_0055: call void TupleElimination::p@5<int64>(!!0)
135+
IL_005a: nop
136+
.line 21,21 : 5,6 ''
137+
IL_005b: ldloc.s t
138+
IL_005d: call void TupleElimination::p@5<class [mscorlib]System.Tuple`2<bool,int64>>(!!0)
139+
IL_0062: nop
140+
.line 23,23 : 5,6 ''
141+
IL_0063: ldc.i4.0
142+
IL_0064: ret
143+
} // end of method TupleElimination::main
144+
145+
} // end of class TupleElimination
146+
147+
.class private abstract auto ansi sealed '<StartupCode$TupleElimination>'.$TupleElimination
148+
extends [mscorlib]System.Object
149+
{
150+
} // end of class '<StartupCode$TupleElimination>'.$TupleElimination
151+
152+
153+
// =============================================================
154+
155+
// *********** DISASSEMBLY COMPLETE ***********************

tests/fsharpqa/Source/CodeGen/EmittedIL/Tuples/env.lst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@
99
SOURCE=Tuple08.fs ISCFLAGS="-g --test:EmitFeeFeeAs100001 --optimize-" COMPILE_ONLY=1 POSTCMD="..\\CompareIL.cmd Tuple08.exe NetFx40" # Tuple08.fs - NetFx40
1010

1111
SOURCE=TupleMonster.fs ISCFLAGS="-g --test:EmitFeeFeeAs100001 --optimize-" COMPILE_ONLY=1 POSTCMD="..\\CompareIL.cmd TupleMonster.exe NetFx40" # TupleMonster.fs - NetFx40
12+
13+
SOURCE=TupleElimination.fs ISCFLAGS="-g --test:EmitFeeFeeAs100001 --optimize+" COMPILE_ONLY=1 POSTCMD="..\\CompareIL.cmd TupleElimination.exe NetFx40" # TupleElimination.fs - NetFx40

0 commit comments

Comments
 (0)