Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/fsharp/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1628,6 +1628,10 @@ let rec RearrangeTupleBindings expr fin =
| Some b -> Some (mkLetBind m bind b)
| None -> None
| Expr.Op (TOp.Tuple tupInfo, _, _, _) when not (evalTupInfoIsStruct tupInfo) -> Some (fin expr)
| Expr.Sequential (e1, e2, kind, sp, m) ->
match RearrangeTupleBindings e2 fin with
| Some b -> Some (Expr.Sequential (e1, b, kind, sp, m))
| None -> None
| _ -> None

let ExpandStructuralBinding cenv expr =
Expand Down
315 changes: 315 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Compiler.ComponentTests.EmittedIL

open Xunit
open FSharp.Test.Utilities.Compiler

module ``TupleElimination`` =

[<Fact>]
let ``Sequence expressions with potential side effects do not prevent tuple elimination``() =
FSharp """
module TupleElimination
open System.Runtime.CompilerServices

[<MethodImpl(MethodImplOptions.NoInlining)>]
let f () = 3

[<MethodImpl(MethodImplOptions.NoInlining)>]
let sideEffect () = ()

type Test =
[<MethodImpl(MethodImplOptions.NoInlining)>]
static member test(x: int32 * int32) = x

let v () =
let a, b =
"".ToString () |> ignore
System.DateTime.Now |> ignore
"3".ToString () |> ignore
2L, f ()
System.DateTime.Now |> ignore
a, b

let w () =
let (a, b) as t =
"".ToString () |> ignore
System.DateTime.Now |> ignore
"3".ToString () |> ignore
2, f ()
System.DateTime.Now |> ignore
let _ = Test.test(t)
a + b

let x () =
let a, b =
"".ToString () |> ignore
System.DateTime.Now |> ignore
"3".ToString () |> ignore
2, f ()
System.DateTime.Now |> ignore
a + b

let y () =
let a, b, c =
let a = f ()
sideEffect ()
a, f (), f ()
a + b + c

let z () =
let a, b, c =
let u, v = 3, 4
sideEffect ()
f (), f () + u, f () + v
a + b + c
"""
|> compile
|> shouldSucceed
|> verifyIL [

// public static Tuple<long, int> v()
// {
// string text = "".ToString();
// DateTime now = DateTime.Now;
// text = "3".ToString();
// int item = TupleElimination.f();
// now = DateTime.Now;
// return new Tuple<long, int>(2L, item);
// }
"""
.method public static class [runtime]System.Tuple`2<int64,int32>
v() cil managed
{

.maxstack 4
.locals init (string V_0,
valuetype [runtime]System.DateTime V_1,
int32 V_2)
IL_0000: ldstr ""
IL_0005: callvirt instance string [runtime]System.Object::ToString()
IL_000a: stloc.0
IL_000b: call valuetype [runtime]System.DateTime [runtime]System.DateTime::get_Now()
IL_0010: stloc.1
IL_0011: ldstr "3"
IL_0016: callvirt instance string [runtime]System.Object::ToString()
IL_001b: stloc.0
IL_001c: call int32 TupleElimination::f()
IL_0021: stloc.2
IL_0022: call valuetype [runtime]System.DateTime [runtime]System.DateTime::get_Now()
IL_0027: stloc.1
IL_0028: ldc.i4.2
IL_0029: conv.i8
IL_002a: ldloc.2
IL_002b: newobj instance void class [runtime]System.Tuple`2<int64,int32>::.ctor(!0,
!1)
IL_0030: ret
}
"""

// public static int w()
// {
// string text = "".ToString();
// DateTime now = DateTime.Now;
// text = "3".ToString();
// int num = TupleElimination.f();
// Tuple<int, int> x = new Tuple<int, int>(2, num);
// now = DateTime.Now;
// TupleElimination.Test.test(x);
// return 2 + num;
// }
"""
.method public static int32 w() cil managed
{

.maxstack 4
.locals init (string V_0,
valuetype [runtime]System.DateTime V_1,
int32 V_2,
class [runtime]System.Tuple`2<int32,int32> V_3)
IL_0000: ldstr ""
IL_0005: callvirt instance string [runtime]System.Object::ToString()
IL_000a: stloc.0
IL_000b: call valuetype [runtime]System.DateTime [runtime]System.DateTime::get_Now()
IL_0010: stloc.1
IL_0011: ldstr "3"
IL_0016: callvirt instance string [runtime]System.Object::ToString()
IL_001b: stloc.0
IL_001c: call int32 TupleElimination::f()
IL_0021: stloc.2
IL_0022: ldc.i4.2
IL_0023: ldloc.2
IL_0024: newobj instance void class [runtime]System.Tuple`2<int32,int32>::.ctor(!0,
!1)
IL_0029: stloc.3
IL_002a: call valuetype [runtime]System.DateTime [runtime]System.DateTime::get_Now()
IL_002f: stloc.1
IL_0030: ldloc.3
IL_0031: call class [runtime]System.Tuple`2<int32,int32> TupleElimination/Test::test(class [runtime]System.Tuple`2<int32,int32>)
IL_0036: pop
IL_0037: ldc.i4.2
IL_0038: ldloc.2
IL_0039: add
IL_003a: ret
}
"""

// public static int x()
Copy link
Contributor

@TIHan TIHan Apr 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: commented code should be removed
Or explain why the commented code is there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is there to show the C# representation of the IL below. Thought it would be helpful. Should I still remove it?

// {
// string text = "".ToString();
// DateTime now = DateTime.Now;
// text = "3".ToString();
// int num = TupleElimination.f();
// now = DateTime.Now;
// return 2 + num;
// }
"""
.method public static int32 x() cil managed
{

.maxstack 4
.locals init (string V_0,
valuetype [runtime]System.DateTime V_1,
int32 V_2)
IL_0000: ldstr ""
IL_0005: callvirt instance string [runtime]System.Object::ToString()
IL_000a: stloc.0
IL_000b: call valuetype [runtime]System.DateTime [runtime]System.DateTime::get_Now()
IL_0010: stloc.1
IL_0011: ldstr "3"
IL_0016: callvirt instance string [runtime]System.Object::ToString()
IL_001b: stloc.0
IL_001c: call int32 TupleElimination::f()
IL_0021: stloc.2
IL_0022: call valuetype [runtime]System.DateTime [runtime]System.DateTime::get_Now()
IL_0027: stloc.1
IL_0028: ldc.i4.2
IL_0029: ldloc.2
IL_002a: add
IL_002b: ret
}
"""

// public static int y()
// {
// int num = TupleElimination.f();
// TupleElimination.sideEffect();
// return num + TupleElimination.f() + TupleElimination.f();
// }
"""
.method public static int32 y() cil managed
{

.maxstack 4
.locals init (int32 V_0)
IL_0000: call int32 TupleElimination::f()
IL_0005: stloc.0
IL_0006: call void TupleElimination::sideEffect()
IL_000b: ldloc.0
IL_000c: call int32 TupleElimination::f()
IL_0011: add
IL_0012: call int32 TupleElimination::f()
IL_0017: add
IL_0018: ret
}
"""

// public static int z()
// {
// TupleElimination.sideEffect();
// return TupleElimination.f() + (TupleElimination.f() + 3) + (TupleElimination.f() + 4);
// }
"""
.method public static int32 z() cil managed
{

.maxstack 8
IL_0000: call void TupleElimination::sideEffect()
IL_0005: call int32 TupleElimination::f()
IL_000a: call int32 TupleElimination::f()
IL_000f: ldc.i4.3
IL_0010: add
IL_0011: add
IL_0012: call int32 TupleElimination::f()
IL_0017: ldc.i4.4
IL_0018: add
IL_0019: add
IL_001a: ret
}
""" ]

[<Fact>]
let ``First class use of tuple prevents elimination``() =
FSharp """
module TupleElimination
open System.Runtime.CompilerServices

[<MethodImpl(MethodImplOptions.NoInlining)>]
let f () = 3

type Test =
[<MethodImpl(MethodImplOptions.NoInlining)>]
static member test(x: int32 * int32) = x

let z () =
let a, b =
"".ToString () |> ignore
System.DateTime.Now |> ignore
"3".ToString () |> ignore
Test.test(2, f ())
System.DateTime.Now |> ignore
a + b
"""
|> compile
|> shouldSucceed
|> verifyIL [

// public static int z()
// {
// string text = "".ToString();
// DateTime now = DateTime.Now;
// text = "3".ToString();
// Tuple<int, int> tuple = TupleElimination.Test.test(new Tuple<int, int>(2, TupleElimination.f()));
// int item = tuple.Item2;
// int item2 = tuple.Item1;
// now = DateTime.Now;
// return item2 + item;
// }
"""
.method public static int32 z() cil managed
{

.maxstack 4
.locals init (class [runtime]System.Tuple`2<int32,int32> V_0,
string V_1,
valuetype [runtime]System.DateTime V_2,
int32 V_3,
int32 V_4)
IL_0000: ldstr ""
IL_0005: callvirt instance string [runtime]System.Object::ToString()
IL_000a: stloc.1
IL_000b: call valuetype [runtime]System.DateTime [runtime]System.DateTime::get_Now()
IL_0010: stloc.2
IL_0011: ldstr "3"
IL_0016: callvirt instance string [runtime]System.Object::ToString()
IL_001b: stloc.1
IL_001c: ldc.i4.2
IL_001d: call int32 TupleElimination::f()
IL_0022: newobj instance void class [runtime]System.Tuple`2<int32,int32>::.ctor(!0,
!1)
IL_0027: call class [runtime]System.Tuple`2<int32,int32> TupleElimination/Test::test(class [runtime]System.Tuple`2<int32,int32>)
IL_002c: stloc.0
IL_002d: ldloc.0
IL_002e: call instance !1 class [runtime]System.Tuple`2<int32,int32>::get_Item2()
IL_0033: stloc.3
IL_0034: ldloc.0
IL_0035: call instance !0 class [runtime]System.Tuple`2<int32,int32>::get_Item1()
IL_003a: stloc.s V_4
IL_003c: call valuetype [runtime]System.DateTime [runtime]System.DateTime::get_Now()
IL_0041: stloc.2
IL_0042: ldloc.s V_4
IL_0044: ldloc.3
IL_0045: add
IL_0046: ret
}""" ]
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<Compile Include="EmittedIL\Operators.fs" />
<Compile Include="EmittedIL\Literals.fs" />
<Compile Include="EmittedIL\TailCalls.fs" />
<Compile Include="EmittedIL\TupleElimination.fs" />
<Compile Include="ErrorMessages\TypeEqualsMissingTests.fs" />
<Compile Include="ErrorMessages\AccessOfTypeAbbreviationTests.fs" />
<Compile Include="ErrorMessages\AssignmentErrorTests.fs" />
Expand Down