From e298ca109a47de330818e90eebd84dcf21abf622 Mon Sep 17 00:00:00 2001
From: Don Syme <donsyme@fastmail.com>
Date: Thu, 17 Jun 2021 14:12:20 +0100
Subject: [PATCH 1/6] fix boxing promotion for locally mutable values

---
 src/fsharp/Optimizer.fs  | 37 +++++++++++++++++++++++--------------
 src/fsharp/Optimizer.fsi |  6 +++++-
 src/fsharp/autobox.fs    |  2 +-
 3 files changed, 29 insertions(+), 16 deletions(-)

diff --git a/src/fsharp/Optimizer.fs b/src/fsharp/Optimizer.fs
index 88bc0e86ad8..ee0b11c9b3d 100644
--- a/src/fsharp/Optimizer.fs
+++ b/src/fsharp/Optimizer.fs
@@ -1256,7 +1256,7 @@ let AbstractAndRemapModulInfo msg g m (repackage, hidden) info =
 let [<Literal>] suffixForVariablesThatMayNotBeEliminated = "$cont"
 
 /// Indicates a ValRef generated to facilitate tuple eliminations
-let [<Literal>] suffixForTupleElementAssignmentTarget = "tupleElem"
+let [<Literal>] suffixForTupleElementAssignmentTarget = "$tupleElem"
 
 /// Type applications of F# "type functions" may cause side effects, e.g. 
 /// let x<'a> = printfn "hello"; typeof<'a> 
@@ -1278,7 +1278,16 @@ let ValueOfExpr expr =
       ConstExprValue(0, expr)
     else UnknownValue
 
-let IsMutableStructuralBindingForTupleElement (vref: ValRef) = vref.IsCompilerGenerated && vref.DisplayName.EndsWith suffixForTupleElementAssignmentTarget
+let IsMutableStructuralBindingForTupleElement (vref: ValRef) =
+    vref.IsCompilerGenerated &&
+    vref.DisplayName.EndsWith suffixForTupleElementAssignmentTarget
+
+let IsMutableForOutArg (vref: ValRef) =
+    (vref.IsCompilerGenerated && vref.DisplayName.StartsWith(PrettyNaming.outArgCompilerGeneratedName))
+
+let IsOnlyMutableBeforeUse (vref: ValRef) =
+    IsMutableStructuralBindingForTupleElement vref || 
+    IsMutableForOutArg vref
 
 //-------------------------------------------------------------------------
 // Dead binding elimination 
@@ -1581,10 +1590,11 @@ let MakeStructuralBindingTemp (v: Val) i (arg: Expr) argTy =
     let v, ve = mkCompGenLocal arg.Range name argTy
     ve, mkCompGenBind v arg
 
-let MakeMutableStructuralBindingForTupleElement (v: Val) i (arg: Expr) argTy =
-    let name = sprintf "%s_%d_%s" v.LogicalName i suffixForTupleElementAssignmentTarget
-    let v, ve = mkMutableCompGenLocal arg.Range name argTy
-    ve, mkCompGenBind v arg
+let MakeMutableStructuralBindingForTupleElement m (v: Val) i argTy =
+    let init = mkDefault (m, argTy)
+    let name = sprintf "%s_%d%s" v.LogicalName i suffixForTupleElementAssignmentTarget
+    let mutv, mutve = mkMutableCompGenLocal m name argTy
+    mutv, mutve, mkCompGenBind mutv init
 
 let ExpandStructuralBindingRaw cenv expr =
     assert cenv.settings.ExpandStructuralValues()
@@ -1689,16 +1699,15 @@ let TryRewriteBranchingTupleBinding g (v: Val) rhs tgtSeqPtOpt body m =
 
     let requisites = lazy (
         let argTys = destRefTupleTy g v.Type
-        let inits = argTys |> List.map (mkNull m)
-        let ves, binds = List.mapi2 (MakeMutableStructuralBindingForTupleElement v) inits argTys |> List.unzip
-        let vrefs = binds |> List.map (fun (TBind (v, _, _)) -> mkLocalValRef v)
-        argTys, ves, binds, vrefs)
+        let mutvs, mutves, mutinits = argTys |> List.mapi (MakeMutableStructuralBindingForTupleElement m v)  |> List.unzip3
+        let mutvrefs = mutvs |> List.map mkLocalValRef
+        argTys, mutves, mutinits, mutvrefs)
 
     match dive g m requisites rhs with
     | Some rewrittenRhs ->
-        let argTys, ves, binds, _ = requisites.Value
-        let rhsAndTupleBinding = mkCompGenSequential m rewrittenRhs (mkRefTupled g m ves argTys)
-        mkLetsBind m binds (mkLet tgtSeqPtOpt m v rhsAndTupleBinding body) |> Some
+        let argTys, mutves, mutinits, _ = requisites.Value
+        let rhsAndTupleBinding = mkCompGenSequential m rewrittenRhs (mkRefTupled g m mutves argTys)
+        mkLetsBind m mutinits (mkLet tgtSeqPtOpt m v rhsAndTupleBinding body) |> Some
     | _ -> None
 
 let ExpandStructuralBinding cenv expr =
@@ -2594,7 +2603,7 @@ and AddValEqualityInfo g m (v: ValRef) info =
     // when their address is passed to the method call. Another exception are mutable variables
     // created for tuple elimination in branching tuple bindings because they are assigned to
     // exactly once.
-    if not v.IsMutable || IsMutableStructuralBindingForTupleElement v || (v.IsCompilerGenerated && v.DisplayName.StartsWith(PrettyNaming.outArgCompilerGeneratedName)) then 
+    if not v.IsMutable || IsOnlyMutableBeforeUse v then 
         { info with Info = MakeValueInfoForValue g m v info.Info }
     else
         info 
diff --git a/src/fsharp/Optimizer.fsi b/src/fsharp/Optimizer.fsi
index 0cf44522669..bde36499c6f 100644
--- a/src/fsharp/Optimizer.fsi
+++ b/src/fsharp/Optimizer.fsi
@@ -80,4 +80,8 @@ val UnionOptimizationInfos: seq<ImplFileOptimizationInfo> -> CcuOptimizationInfo
 /// Check if an expression has an effect
 val ExprHasEffect: TcGlobals -> Expr -> bool
 
-val internal u_CcuOptimizationInfo: ReaderState -> CcuOptimizationInfo
\ No newline at end of file
+val internal u_CcuOptimizationInfo: ReaderState -> CcuOptimizationInfo
+
+/// Indicates the value is only mutable during its initialization and before any access or capture
+val IsOnlyMutableBeforeUse: ValRef -> bool
+
diff --git a/src/fsharp/autobox.fs b/src/fsharp/autobox.fs
index 92ac4845c77..276bd483661 100644
--- a/src/fsharp/autobox.fs
+++ b/src/fsharp/autobox.fs
@@ -25,7 +25,7 @@ type cenv =
 let DecideEscapes syntacticArgs body =
     let cantBeFree v = 
         let passedIn = ListSet.contains valEq v syntacticArgs 
-        not passedIn && (v.IsMutable && v.ValReprInfo.IsNone) 
+        not passedIn && (v.IsMutable && v.ValReprInfo.IsNone) && not (Optimizer.IsOnlyMutableBeforeUse (mkLocalValRef v)) 
 
     let frees = freeInExpr CollectLocals body
     frees.FreeLocals |> Zset.filter cantBeFree 

From baa112d898f8c0dbdb4f9c360cb9799eef354fa0 Mon Sep 17 00:00:00 2001
From: Don Syme <donsyme@fastmail.com>
Date: Thu, 17 Jun 2021 14:32:54 +0100
Subject: [PATCH 2/6] add test

---
 src/fsharp/autobox.fs                         |   9 +-
 .../EmittedIL/TupleElimination.fs             | 273 +++++++++++++++++-
 2 files changed, 278 insertions(+), 4 deletions(-)

diff --git a/src/fsharp/autobox.fs b/src/fsharp/autobox.fs
index 276bd483661..3eab7ea1152 100644
--- a/src/fsharp/autobox.fs
+++ b/src/fsharp/autobox.fs
@@ -23,12 +23,15 @@ type cenv =
 
 /// Find all the mutable locals that escape a method, function or lambda expression
 let DecideEscapes syntacticArgs body =
-    let cantBeFree v = 
+    let isMutableEscape v = 
         let passedIn = ListSet.contains valEq v syntacticArgs 
-        not passedIn && (v.IsMutable && v.ValReprInfo.IsNone) && not (Optimizer.IsOnlyMutableBeforeUse (mkLocalValRef v)) 
+        not passedIn &&
+        v.IsMutable &&
+        v.ValReprInfo.IsNone &&
+        not (Optimizer.IsOnlyMutableBeforeUse (mkLocalValRef v))
 
     let frees = freeInExpr CollectLocals body
-    frees.FreeLocals |> Zset.filter cantBeFree 
+    frees.FreeLocals |> Zset.filter isMutableEscape 
 
 /// Find all the mutable locals that escape a lambda expression, ignoring the arguments to the lambda
 let DecideLambda exprF cenv topValInfo expr ety z   = 
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
index 5aaf72f4b69..3a184010db4 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
@@ -706,4 +706,275 @@ public static int z()
   IL_0012:  ldloc.1
   IL_0013:  add
   IL_0014:  ret
-}""" ]
\ No newline at end of file
+}""" ]
+
+
+
+
+    [<Fact>]
+    let ``Branching let binding of tuple with capture doesn't promote``() =
+        FSharp """
+module TupleElimination
+open System.Runtime.CompilerServices
+
+let testFunction(a,b) =
+    let x,y = printfn "hello"; b*a,a*b
+    (fun () -> x + y)
+         """
+         |> compile
+         |> shouldSucceed
+         |> verifyIL [
+
+                      """
+.method public static class [runtime]System.Tuple`2<string,int32> 
+        x() cil managed
+{
+    
+  .maxstack  4
+  .locals init (string V_0,
+           int32 V_1,
+           int32 V_2)
+  IL_0000:  call       void TupleElimination::sideEffect()
+  IL_0005:  call       bool TupleElimination::cond()
+  IL_000a:  brfalse.s  IL_0022
+
+  IL_000c:  call       bool TupleElimination::cond()
+  IL_0011:  brfalse.s  IL_0016
+
+  IL_0013:  ldc.i4.1
+  IL_0014:  br.s       IL_0017
+
+  IL_0016:  ldc.i4.3
+  IL_0017:  stloc.2
+  IL_0018:  ldstr      "yep"
+  IL_001d:  stloc.0
+  IL_001e:  ldloc.2
+  IL_001f:  stloc.1
+  IL_0020:  br.s       IL_002e
+
+  IL_0022:  ldstr      ""
+  IL_0027:  stloc.0
+  IL_0028:  call       int32 TupleElimination::f()
+  IL_002d:  stloc.1
+  IL_002e:  ldloc.0
+  IL_002f:  ldloc.1
+  IL_0030:  newobj     instance void class [runtime]System.Tuple`2<string,int32>::.ctor(!0,
+                                                                                                 !1)
+  IL_0035:  ret
+}
+"""
+
+(*
+public static int y()
+{
+    int num;
+    int num2;
+    int num3;
+    if (TupleElimination.cond())
+    {
+        string text = "".ToString();
+        num = 1;
+        num2 = TupleElimination.f();
+        num3 = 3;
+    }
+    else if (TupleElimination.cond())
+    {
+        num = 2;
+        num2 = 2;
+        num3 = 3;
+    }
+    else
+    {
+        switch (1 / 0)
+        {
+        case 1:
+            if (2 / 3 == 1)
+            {
+                num = 5;
+                num2 = 6;
+                num3 = 7;
+            }
+            else
+            {
+                string text = "".ToString();
+                num = 6;
+                num2 = 5;
+                num3 = 4;
+            }
+            break;
+        case 2:
+            num = 6;
+            num2 = 6;
+            num3 = 6;
+            break;
+        case 3:
+            num = TupleElimination.f();
+            num2 = 7;
+            num3 = TupleElimination.f();
+            break;
+        default:
+            num = 8;
+            num2 = TupleElimination.y();
+            num3 = TupleElimination.y();
+            break;
+        }
+    }
+    return num + num2 + 2 * num3;
+}
+*)
+                      """
+.method public static int32  y() cil managed
+{
+
+  .maxstack  5
+  .locals init (int32 V_0,
+           int32 V_1,
+           int32 V_2,
+           string V_3)
+  IL_0000:  ldc.i4.0
+  IL_0001:  stloc.0
+  IL_0002:  ldc.i4.0
+  IL_0003:  stloc.1
+  IL_0004:  ldc.i4.0
+  IL_0005:  stloc.2
+  IL_0006:  call       bool TupleElimination::cond()
+  IL_000b:  brfalse.s  IL_0027
+
+  IL_000d:  ldstr      ""
+  IL_0012:  callvirt   instance string [runtime]System.Object::ToString()
+  IL_0017:  stloc.3
+  IL_0018:  ldc.i4.1
+  IL_0019:  stloc.0
+  IL_001a:  call       int32 TupleElimination::f()
+  IL_001f:  stloc.1
+  IL_0020:  ldc.i4.3
+  IL_0021:  stloc.2
+  IL_0022:  br         IL_0095
+  
+  IL_0027:  call       bool TupleElimination::cond()
+  IL_002c:  brfalse.s  IL_0036
+  
+  IL_002e:  ldc.i4.2
+  IL_002f:  stloc.0
+  IL_0030:  ldc.i4.2
+  IL_0031:  stloc.1
+  IL_0032:  ldc.i4.3
+  IL_0033:  stloc.2
+  IL_0034:  br.s       IL_0095
+  
+  IL_0036:  ldc.i4.1
+  IL_0037:  ldc.i4.0
+  IL_0038:  div
+  IL_0039:  ldc.i4.1
+  IL_003a:  sub
+  IL_003b:  switch     (
+                        IL_004e,
+                        IL_006f,
+                        IL_0077)
+  IL_004c:  br.s       IL_0087
+  
+  IL_004e:  ldc.i4.2
+  IL_004f:  ldc.i4.3
+  IL_0050:  div
+  IL_0051:  ldc.i4.1
+  IL_0052:  bne.un.s   IL_005c
+  
+  IL_0054:  ldc.i4.5
+  IL_0055:  stloc.0
+  IL_0056:  ldc.i4.6
+  IL_0057:  stloc.1
+  IL_0058:  ldc.i4.7
+  IL_0059:  stloc.2
+  IL_005a:  br.s       IL_0095
+  
+  IL_005c:  ldstr      ""
+  IL_0061:  callvirt   instance string [runtime]System.Object::ToString()
+  IL_0066:  stloc.3
+  IL_0067:  ldc.i4.6
+  IL_0068:  stloc.0
+  IL_0069:  ldc.i4.5
+  IL_006a:  stloc.1
+  IL_006b:  ldc.i4.4
+  IL_006c:  stloc.2
+  IL_006d:  br.s       IL_0095
+  
+  IL_006f:  ldc.i4.6
+  IL_0070:  stloc.0
+  IL_0071:  ldc.i4.6
+  IL_0072:  stloc.1
+  IL_0073:  ldc.i4.6
+  IL_0074:  stloc.2
+  IL_0075:  br.s       IL_0095
+  
+  IL_0077:  call       int32 TupleElimination::f()
+  IL_007c:  stloc.0
+  IL_007d:  ldc.i4.7
+  IL_007e:  stloc.1
+  IL_007f:  call       int32 TupleElimination::f()
+  IL_0084:  stloc.2
+  IL_0085:  br.s       IL_0095
+  
+  IL_0087:  ldc.i4.8
+  IL_0088:  stloc.0
+  IL_0089:  call       int32 TupleElimination::y()
+  IL_008e:  stloc.1
+  IL_008f:  call       int32 TupleElimination::y()
+  IL_0094:  stloc.2
+  IL_0095:  ldloc.0
+  IL_0096:  ldloc.1
+  IL_0097:  add
+  IL_0098:  ldc.i4.2
+  IL_0099:  ldloc.2
+  IL_009a:  mul
+  IL_009b:  add
+  IL_009c:  ret
+}
+"""
+
+(*
+public static int z()
+{
+    int num;
+    int num2;
+    if (TupleElimination.cond())
+    {
+        num = 1;
+        num2 = 3;
+    }
+    else
+    {
+        num = 3;
+        num2 = 4;
+    }
+    return num + num2;
+}
+*)
+                      """
+.method public static int32  z() cil managed
+{
+  
+  .maxstack  4
+  .locals init (int32 V_0,
+           int32 V_1)
+  IL_0000:  call       bool TupleElimination::cond()
+  IL_0005:  brfalse.s  IL_000d
+    
+  IL_0007:  ldc.i4.1
+  IL_0008:  stloc.0
+  IL_0009:  ldc.i4.3
+  IL_000a:  stloc.1
+  IL_000b:  br.s       IL_0011
+    
+  IL_000d:  ldc.i4.3
+  IL_000e:  stloc.0
+  IL_000f:  ldc.i4.4
+  IL_0010:  stloc.1
+  IL_0011:  ldloc.0
+  IL_0012:  ldloc.1
+  IL_0013:  add
+  IL_0014:  ret
+}""" ]
+
+
+
+

From 2798914e96724bbfaff18d5274a12a4e5ab25266 Mon Sep 17 00:00:00 2001
From: Don Syme <donsyme@fastmail.com>
Date: Thu, 17 Jun 2021 14:51:50 +0100
Subject: [PATCH 3/6] reduce fix

---
 src/fsharp/Optimizer.fs | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/src/fsharp/Optimizer.fs b/src/fsharp/Optimizer.fs
index ee0b11c9b3d..9323598d482 100644
--- a/src/fsharp/Optimizer.fs
+++ b/src/fsharp/Optimizer.fs
@@ -1283,7 +1283,8 @@ let IsMutableStructuralBindingForTupleElement (vref: ValRef) =
     vref.DisplayName.EndsWith suffixForTupleElementAssignmentTarget
 
 let IsMutableForOutArg (vref: ValRef) =
-    (vref.IsCompilerGenerated && vref.DisplayName.StartsWith(PrettyNaming.outArgCompilerGeneratedName))
+    vref.IsCompilerGenerated &&
+    vref.DisplayName.StartsWith(PrettyNaming.outArgCompilerGeneratedName)
 
 let IsOnlyMutableBeforeUse (vref: ValRef) =
     IsMutableStructuralBindingForTupleElement vref || 
@@ -1590,11 +1591,10 @@ let MakeStructuralBindingTemp (v: Val) i (arg: Expr) argTy =
     let v, ve = mkCompGenLocal arg.Range name argTy
     ve, mkCompGenBind v arg
 
-let MakeMutableStructuralBindingForTupleElement m (v: Val) i argTy =
-    let init = mkDefault (m, argTy)
-    let name = sprintf "%s_%d%s" v.LogicalName i suffixForTupleElementAssignmentTarget
-    let mutv, mutve = mkMutableCompGenLocal m name argTy
-    mutv, mutve, mkCompGenBind mutv init
+let MakeMutableStructuralBindingForTupleElement (v: Val) i (arg: Expr) argTy =
+    let name = sprintf "%s_%d_%s" v.LogicalName i suffixForTupleElementAssignmentTarget
+    let v, ve = mkMutableCompGenLocal arg.Range name argTy
+    ve, mkCompGenBind v arg
 
 let ExpandStructuralBindingRaw cenv expr =
     assert cenv.settings.ExpandStructuralValues()
@@ -1699,15 +1699,16 @@ let TryRewriteBranchingTupleBinding g (v: Val) rhs tgtSeqPtOpt body m =
 
     let requisites = lazy (
         let argTys = destRefTupleTy g v.Type
-        let mutvs, mutves, mutinits = argTys |> List.mapi (MakeMutableStructuralBindingForTupleElement m v)  |> List.unzip3
-        let mutvrefs = mutvs |> List.map mkLocalValRef
-        argTys, mutves, mutinits, mutvrefs)
+        let inits = argTys |> List.map (mkNull m)
+        let ves, binds = List.mapi2 (MakeMutableStructuralBindingForTupleElement v) inits argTys |> List.unzip
+        let vrefs = binds |> List.map (fun (TBind (v, _, _)) -> mkLocalValRef v)
+        argTys, ves, binds, vrefs)
 
     match dive g m requisites rhs with
     | Some rewrittenRhs ->
-        let argTys, mutves, mutinits, _ = requisites.Value
-        let rhsAndTupleBinding = mkCompGenSequential m rewrittenRhs (mkRefTupled g m mutves argTys)
-        mkLetsBind m mutinits (mkLet tgtSeqPtOpt m v rhsAndTupleBinding body) |> Some
+        let argTys, ves, binds, _ = requisites.Value
+        let rhsAndTupleBinding = mkCompGenSequential m rewrittenRhs (mkRefTupled g m ves argTys)
+        mkLetsBind m binds (mkLet tgtSeqPtOpt m v rhsAndTupleBinding body) |> Some
     | _ -> None
 
 let ExpandStructuralBinding cenv expr =

From 405b16ff9d11a04a75675c6fa8d534c2c900f760 Mon Sep 17 00:00:00 2001
From: Don Syme <donsyme@fastmail.com>
Date: Thu, 17 Jun 2021 14:54:14 +0100
Subject: [PATCH 4/6] simplify fix

---
 .../EmittedIL/TupleElimination.fs             | 270 ------------------
 1 file changed, 270 deletions(-)

diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
index 3a184010db4..350bb4a30f8 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
@@ -708,273 +708,3 @@ public static int z()
   IL_0014:  ret
 }""" ]
 
-
-
-
-    [<Fact>]
-    let ``Branching let binding of tuple with capture doesn't promote``() =
-        FSharp """
-module TupleElimination
-open System.Runtime.CompilerServices
-
-let testFunction(a,b) =
-    let x,y = printfn "hello"; b*a,a*b
-    (fun () -> x + y)
-         """
-         |> compile
-         |> shouldSucceed
-         |> verifyIL [
-
-                      """
-.method public static class [runtime]System.Tuple`2<string,int32> 
-        x() cil managed
-{
-    
-  .maxstack  4
-  .locals init (string V_0,
-           int32 V_1,
-           int32 V_2)
-  IL_0000:  call       void TupleElimination::sideEffect()
-  IL_0005:  call       bool TupleElimination::cond()
-  IL_000a:  brfalse.s  IL_0022
-
-  IL_000c:  call       bool TupleElimination::cond()
-  IL_0011:  brfalse.s  IL_0016
-
-  IL_0013:  ldc.i4.1
-  IL_0014:  br.s       IL_0017
-
-  IL_0016:  ldc.i4.3
-  IL_0017:  stloc.2
-  IL_0018:  ldstr      "yep"
-  IL_001d:  stloc.0
-  IL_001e:  ldloc.2
-  IL_001f:  stloc.1
-  IL_0020:  br.s       IL_002e
-
-  IL_0022:  ldstr      ""
-  IL_0027:  stloc.0
-  IL_0028:  call       int32 TupleElimination::f()
-  IL_002d:  stloc.1
-  IL_002e:  ldloc.0
-  IL_002f:  ldloc.1
-  IL_0030:  newobj     instance void class [runtime]System.Tuple`2<string,int32>::.ctor(!0,
-                                                                                                 !1)
-  IL_0035:  ret
-}
-"""
-
-(*
-public static int y()
-{
-    int num;
-    int num2;
-    int num3;
-    if (TupleElimination.cond())
-    {
-        string text = "".ToString();
-        num = 1;
-        num2 = TupleElimination.f();
-        num3 = 3;
-    }
-    else if (TupleElimination.cond())
-    {
-        num = 2;
-        num2 = 2;
-        num3 = 3;
-    }
-    else
-    {
-        switch (1 / 0)
-        {
-        case 1:
-            if (2 / 3 == 1)
-            {
-                num = 5;
-                num2 = 6;
-                num3 = 7;
-            }
-            else
-            {
-                string text = "".ToString();
-                num = 6;
-                num2 = 5;
-                num3 = 4;
-            }
-            break;
-        case 2:
-            num = 6;
-            num2 = 6;
-            num3 = 6;
-            break;
-        case 3:
-            num = TupleElimination.f();
-            num2 = 7;
-            num3 = TupleElimination.f();
-            break;
-        default:
-            num = 8;
-            num2 = TupleElimination.y();
-            num3 = TupleElimination.y();
-            break;
-        }
-    }
-    return num + num2 + 2 * num3;
-}
-*)
-                      """
-.method public static int32  y() cil managed
-{
-
-  .maxstack  5
-  .locals init (int32 V_0,
-           int32 V_1,
-           int32 V_2,
-           string V_3)
-  IL_0000:  ldc.i4.0
-  IL_0001:  stloc.0
-  IL_0002:  ldc.i4.0
-  IL_0003:  stloc.1
-  IL_0004:  ldc.i4.0
-  IL_0005:  stloc.2
-  IL_0006:  call       bool TupleElimination::cond()
-  IL_000b:  brfalse.s  IL_0027
-
-  IL_000d:  ldstr      ""
-  IL_0012:  callvirt   instance string [runtime]System.Object::ToString()
-  IL_0017:  stloc.3
-  IL_0018:  ldc.i4.1
-  IL_0019:  stloc.0
-  IL_001a:  call       int32 TupleElimination::f()
-  IL_001f:  stloc.1
-  IL_0020:  ldc.i4.3
-  IL_0021:  stloc.2
-  IL_0022:  br         IL_0095
-  
-  IL_0027:  call       bool TupleElimination::cond()
-  IL_002c:  brfalse.s  IL_0036
-  
-  IL_002e:  ldc.i4.2
-  IL_002f:  stloc.0
-  IL_0030:  ldc.i4.2
-  IL_0031:  stloc.1
-  IL_0032:  ldc.i4.3
-  IL_0033:  stloc.2
-  IL_0034:  br.s       IL_0095
-  
-  IL_0036:  ldc.i4.1
-  IL_0037:  ldc.i4.0
-  IL_0038:  div
-  IL_0039:  ldc.i4.1
-  IL_003a:  sub
-  IL_003b:  switch     (
-                        IL_004e,
-                        IL_006f,
-                        IL_0077)
-  IL_004c:  br.s       IL_0087
-  
-  IL_004e:  ldc.i4.2
-  IL_004f:  ldc.i4.3
-  IL_0050:  div
-  IL_0051:  ldc.i4.1
-  IL_0052:  bne.un.s   IL_005c
-  
-  IL_0054:  ldc.i4.5
-  IL_0055:  stloc.0
-  IL_0056:  ldc.i4.6
-  IL_0057:  stloc.1
-  IL_0058:  ldc.i4.7
-  IL_0059:  stloc.2
-  IL_005a:  br.s       IL_0095
-  
-  IL_005c:  ldstr      ""
-  IL_0061:  callvirt   instance string [runtime]System.Object::ToString()
-  IL_0066:  stloc.3
-  IL_0067:  ldc.i4.6
-  IL_0068:  stloc.0
-  IL_0069:  ldc.i4.5
-  IL_006a:  stloc.1
-  IL_006b:  ldc.i4.4
-  IL_006c:  stloc.2
-  IL_006d:  br.s       IL_0095
-  
-  IL_006f:  ldc.i4.6
-  IL_0070:  stloc.0
-  IL_0071:  ldc.i4.6
-  IL_0072:  stloc.1
-  IL_0073:  ldc.i4.6
-  IL_0074:  stloc.2
-  IL_0075:  br.s       IL_0095
-  
-  IL_0077:  call       int32 TupleElimination::f()
-  IL_007c:  stloc.0
-  IL_007d:  ldc.i4.7
-  IL_007e:  stloc.1
-  IL_007f:  call       int32 TupleElimination::f()
-  IL_0084:  stloc.2
-  IL_0085:  br.s       IL_0095
-  
-  IL_0087:  ldc.i4.8
-  IL_0088:  stloc.0
-  IL_0089:  call       int32 TupleElimination::y()
-  IL_008e:  stloc.1
-  IL_008f:  call       int32 TupleElimination::y()
-  IL_0094:  stloc.2
-  IL_0095:  ldloc.0
-  IL_0096:  ldloc.1
-  IL_0097:  add
-  IL_0098:  ldc.i4.2
-  IL_0099:  ldloc.2
-  IL_009a:  mul
-  IL_009b:  add
-  IL_009c:  ret
-}
-"""
-
-(*
-public static int z()
-{
-    int num;
-    int num2;
-    if (TupleElimination.cond())
-    {
-        num = 1;
-        num2 = 3;
-    }
-    else
-    {
-        num = 3;
-        num2 = 4;
-    }
-    return num + num2;
-}
-*)
-                      """
-.method public static int32  z() cil managed
-{
-  
-  .maxstack  4
-  .locals init (int32 V_0,
-           int32 V_1)
-  IL_0000:  call       bool TupleElimination::cond()
-  IL_0005:  brfalse.s  IL_000d
-    
-  IL_0007:  ldc.i4.1
-  IL_0008:  stloc.0
-  IL_0009:  ldc.i4.3
-  IL_000a:  stloc.1
-  IL_000b:  br.s       IL_0011
-    
-  IL_000d:  ldc.i4.3
-  IL_000e:  stloc.0
-  IL_000f:  ldc.i4.4
-  IL_0010:  stloc.1
-  IL_0011:  ldloc.0
-  IL_0012:  ldloc.1
-  IL_0013:  add
-  IL_0014:  ret
-}""" ]
-
-
-
-

From ad53eb773ec3ce309bffa709cfaf19061b69b623 Mon Sep 17 00:00:00 2001
From: Don Syme <donsyme@fastmail.com>
Date: Thu, 17 Jun 2021 14:58:35 +0100
Subject: [PATCH 5/6] simplify fix

---
 src/fsharp/Optimizer.fs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/fsharp/Optimizer.fs b/src/fsharp/Optimizer.fs
index 9323598d482..f4b6897281c 100644
--- a/src/fsharp/Optimizer.fs
+++ b/src/fsharp/Optimizer.fs
@@ -1256,7 +1256,7 @@ let AbstractAndRemapModulInfo msg g m (repackage, hidden) info =
 let [<Literal>] suffixForVariablesThatMayNotBeEliminated = "$cont"
 
 /// Indicates a ValRef generated to facilitate tuple eliminations
-let [<Literal>] suffixForTupleElementAssignmentTarget = "$tupleElem"
+let [<Literal>] suffixForTupleElementAssignmentTarget = "tupleElem"
 
 /// Type applications of F# "type functions" may cause side effects, e.g. 
 /// let x<'a> = printfn "hello"; typeof<'a> 

From 1c8ddd6b144ba56530f267f42466cd3a4b73a132 Mon Sep 17 00:00:00 2001
From: Don Syme <donsyme@fastmail.com>
Date: Thu, 17 Jun 2021 15:20:39 +0100
Subject: [PATCH 6/6] add test

---
 src/fsharp/Optimizer.fs                       | 12 +--
 src/fsharp/Optimizer.fsi                      |  2 +-
 src/fsharp/autobox.fs                         |  2 +-
 .../EmittedIL/TupleElimination.fs             | 73 +++++++++++++++++++
 4 files changed, 81 insertions(+), 8 deletions(-)

diff --git a/src/fsharp/Optimizer.fs b/src/fsharp/Optimizer.fs
index f4b6897281c..c4f376027ca 100644
--- a/src/fsharp/Optimizer.fs
+++ b/src/fsharp/Optimizer.fs
@@ -1256,7 +1256,7 @@ let AbstractAndRemapModulInfo msg g m (repackage, hidden) info =
 let [<Literal>] suffixForVariablesThatMayNotBeEliminated = "$cont"
 
 /// Indicates a ValRef generated to facilitate tuple eliminations
-let [<Literal>] suffixForTupleElementAssignmentTarget = "tupleElem"
+let [<Literal>] suffixForTupleElementAssignmentTarget = "$tupleElem"
 
 /// Type applications of F# "type functions" may cause side effects, e.g. 
 /// let x<'a> = printfn "hello"; typeof<'a> 
@@ -1280,13 +1280,13 @@ let ValueOfExpr expr =
 
 let IsMutableStructuralBindingForTupleElement (vref: ValRef) =
     vref.IsCompilerGenerated &&
-    vref.DisplayName.EndsWith suffixForTupleElementAssignmentTarget
+    vref.LogicalName.EndsWith suffixForTupleElementAssignmentTarget
 
 let IsMutableForOutArg (vref: ValRef) =
     vref.IsCompilerGenerated &&
-    vref.DisplayName.StartsWith(PrettyNaming.outArgCompilerGeneratedName)
+    vref.LogicalName.StartsWith(PrettyNaming.outArgCompilerGeneratedName)
 
-let IsOnlyMutableBeforeUse (vref: ValRef) =
+let IsKnownOnlyMutableBeforeUse (vref: ValRef) =
     IsMutableStructuralBindingForTupleElement vref || 
     IsMutableForOutArg vref
 
@@ -1592,7 +1592,7 @@ let MakeStructuralBindingTemp (v: Val) i (arg: Expr) argTy =
     ve, mkCompGenBind v arg
 
 let MakeMutableStructuralBindingForTupleElement (v: Val) i (arg: Expr) argTy =
-    let name = sprintf "%s_%d_%s" v.LogicalName i suffixForTupleElementAssignmentTarget
+    let name = sprintf "%s_%d%s" v.LogicalName i suffixForTupleElementAssignmentTarget
     let v, ve = mkMutableCompGenLocal arg.Range name argTy
     ve, mkCompGenBind v arg
 
@@ -2604,7 +2604,7 @@ and AddValEqualityInfo g m (v: ValRef) info =
     // when their address is passed to the method call. Another exception are mutable variables
     // created for tuple elimination in branching tuple bindings because they are assigned to
     // exactly once.
-    if not v.IsMutable || IsOnlyMutableBeforeUse v then 
+    if not v.IsMutable || IsKnownOnlyMutableBeforeUse v then 
         { info with Info = MakeValueInfoForValue g m v info.Info }
     else
         info 
diff --git a/src/fsharp/Optimizer.fsi b/src/fsharp/Optimizer.fsi
index bde36499c6f..85504d02628 100644
--- a/src/fsharp/Optimizer.fsi
+++ b/src/fsharp/Optimizer.fsi
@@ -83,5 +83,5 @@ val ExprHasEffect: TcGlobals -> Expr -> bool
 val internal u_CcuOptimizationInfo: ReaderState -> CcuOptimizationInfo
 
 /// Indicates the value is only mutable during its initialization and before any access or capture
-val IsOnlyMutableBeforeUse: ValRef -> bool
+val IsKnownOnlyMutableBeforeUse: ValRef -> bool
 
diff --git a/src/fsharp/autobox.fs b/src/fsharp/autobox.fs
index 3eab7ea1152..d5854daa0ac 100644
--- a/src/fsharp/autobox.fs
+++ b/src/fsharp/autobox.fs
@@ -28,7 +28,7 @@ let DecideEscapes syntacticArgs body =
         not passedIn &&
         v.IsMutable &&
         v.ValReprInfo.IsNone &&
-        not (Optimizer.IsOnlyMutableBeforeUse (mkLocalValRef v))
+        not (Optimizer.IsKnownOnlyMutableBeforeUse (mkLocalValRef v))
 
     let frees = freeInExpr CollectLocals body
     frees.FreeLocals |> Zset.filter isMutableEscape 
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
index 350bb4a30f8..aac6ae87332 100644
--- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TupleElimination.fs
@@ -708,3 +708,76 @@ public static int z()
   IL_0014:  ret
 }""" ]
 
+
+
+
+    [<Fact>]
+    let ``Branching let binding of tuple with capture doesn't promote``() =
+        FSharp """
+module TupleElimination
+open System.Runtime.CompilerServices
+
+let testFunction(a,b) =
+    let x,y = printfn "hello"; b*a,a*b
+    (fun () -> x + y)
+         """
+         |> compile
+         |> shouldSucceed
+         |> verifyIL [
+         // Checks the captured 'x' and 'y' are not promoted onto the heap
+                      """
+.method public static class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit,int32> 
+        testFunction(int32 a,
+                     int32 b) cil managed
+{
+  
+  .maxstack  4
+  .locals init (class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [runtime]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit> V_0,
+           int32 V_1,
+           int32 V_2)
+  IL_0000:  ldstr      "hello"
+  IL_0005:  newobj     instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [runtime]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit>::.ctor(string)
+  IL_000a:  stloc.0
+  IL_000b:  call       class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()
+  IL_0010:  ldloc.0
+  IL_0011:  call       !!0 [FSharp.Core]Microsoft.FSharp.Core.PrintfModule::PrintFormatLineToTextWriter<class [FSharp.Core]Microsoft.FSharp.Core.Unit>(class [runtime]System.IO.TextWriter,
+                                                                                                                                                       class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0,class [runtime]System.IO.TextWriter,class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
+  IL_0016:  pop
+  IL_0017:  ldarg.1
+  IL_0018:  ldarg.0
+  IL_0019:  mul
+  IL_001a:  stloc.1
+  IL_001b:  ldarg.0
+  IL_001c:  ldarg.1
+  IL_001d:  mul
+  IL_001e:  stloc.2
+  IL_001f:  ldloc.1
+  IL_0020:  ldloc.2
+  IL_0021:  newobj     instance void TupleElimination/testFunction@7::.ctor(int32,
+                                                                            int32)
+  IL_0026:  ret
+} 
+""" 
+
+         // Checks the names of captured 'x' and 'y'. Currently these are not
+         // good names, see https://github.com/dotnet/fsharp/issues/11689,
+         // this test will need updating when this is fixed
+                      """
+  .method public strict virtual instance int32 
+          Invoke(class [FSharp.Core]Microsoft.FSharp.Core.Unit unitVar0) cil managed
+  {
+    
+    .maxstack  8
+    IL_0000:  ldarg.0
+    IL_0001:  ldfld      int32 TupleElimination/testFunction@7::patternInput_0
+    IL_0006:  ldarg.0
+    IL_0007:  ldfld      int32 TupleElimination/testFunction@7::patternInput_1
+    IL_000c:  add
+    IL_000d:  ret
+  } 
+
+""" ]
+
+
+
+
