Skip to content

Reenable β-reduction of immediately-invoked F#-defined generic delegates #18401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 31, 2025
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
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/9.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* Fix duplicate parse error reporting for GetBackgroundCheckResultsForFileInProject ([Issue #18379](https://github.com/dotnet/fsharp/issues/18379) [PR #18380](https://github.com/dotnet/fsharp/pull/18380))
* Fix MethodDefNotFound when compiling code invoking delegate with option parameter ([Issue #5171](https://github.com/dotnet/fsharp/issues/5171), [PR #18385](https://github.com/dotnet/fsharp/pull/18385))
* Fix #r nuget ..." downloads unneeded packages ([Issue #18231](https://github.com/dotnet/fsharp/issues/18231), [PR #18393](https://github.com/dotnet/fsharp/pull/18393))
* Reenable β-reduction and subsequent reoptimization of immediately-invoked F#-defined generic delegates. ([PR #18401](https://github.com/dotnet/fsharp/pull/18401))

### Added
* Added missing type constraints in FCS. ([PR #18241](https://github.com/dotnet/fsharp/pull/18241))
Expand Down
14 changes: 7 additions & 7 deletions src/Compiler/Optimize/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1734,9 +1734,9 @@ let TryEliminateBinding cenv _env bind e2 _m =

// Immediate consumption of delegate via an application in a sequential, e.g. 'let part1 = e in part1.Invoke(args); rest'
// See https://github.com/fsharp/fslang-design/blob/master/tooling/FST-1034-lambda-optimizations.md
| Expr.Sequential(DebugPoints(DelegateInvokeExpr g (delInvokeRef, delInvokeTy, DebugPoints (Expr.Val (VRefLocal vspec2, _, _), recreate2), delInvokeArg, _), recreate1), rest, NormalSeq, m)
| Expr.Sequential(DebugPoints(DelegateInvokeExpr g (delInvokeRef, delInvokeTy, tyargs, DebugPoints (Expr.Val (VRefLocal vspec2, _, _), recreate2), delInvokeArg, _), recreate1), rest, NormalSeq, m)
when IsUniqueUse vspec2 [rest;delInvokeArg] ->
let invoke = MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, recreate2 e1, delInvokeTy, delInvokeArg, m)
let invoke = MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, recreate2 e1, delInvokeTy, tyargs, delInvokeArg, m)
Some (Expr.Sequential(recreate1 invoke, rest, NormalSeq, m) |> recreate0)

// Immediate consumption of value by a pattern match 'let x = e in match x with ...'
Expand Down Expand Up @@ -2397,8 +2397,8 @@ let rec OptimizeExpr cenv (env: IncrementalOptimizationEnv) expr =

| Expr.App (f, fty, tyargs, argsl, m) ->
match expr with
| DelegateInvokeExpr g (delInvokeRef, delInvokeTy, delExpr, delInvokeArg, m) ->
OptimizeFSharpDelegateInvoke cenv env (delInvokeRef, delExpr, delInvokeTy, delInvokeArg, m)
| DelegateInvokeExpr g (delInvokeRef, delInvokeTy, tyargs, delExpr, delInvokeArg, m) ->
OptimizeFSharpDelegateInvoke cenv env (delInvokeRef, delExpr, delInvokeTy, tyargs, delInvokeArg, m)
| _ ->
let attempt =
if IsDebugPipeRightExpr cenv expr then
Expand Down Expand Up @@ -3799,18 +3799,18 @@ and OptimizeDebugPipeRights cenv env expr =
pipesExprR
expr, { pipesInfo with HasEffect=true}

and OptimizeFSharpDelegateInvoke cenv env (delInvokeRef, delExpr, delInvokeTy, delInvokeArg, m) =
and OptimizeFSharpDelegateInvoke cenv env (delInvokeRef, delExpr, delInvokeTy, tyargs, delInvokeArg, m) =
let g = cenv.g
let optf0, finfo = OptimizeExpr cenv env delExpr

match StripPreComputationsFromComputedFunction g optf0 [delInvokeArg] (fun f delInvokeArgsR -> MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, f, delInvokeTy, List.head delInvokeArgsR, m)) with
match StripPreComputationsFromComputedFunction g optf0 [delInvokeArg] (fun f delInvokeArgsR -> MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, f, delInvokeTy, tyargs, List.head delInvokeArgsR, m)) with
| Choice1Of2 remade ->
OptimizeExpr cenv env remade
| Choice2Of2 (newf0, remake) ->

let newDelInvokeArgs, arginfos = OptimizeExprsThenConsiderSplits cenv env [delInvokeArg]
let newDelInvokeArg = List.head newDelInvokeArgs
let reducedExpr = MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, newf0, delInvokeTy, newDelInvokeArg, m)
let reducedExpr = MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, newf0, delInvokeTy, tyargs, newDelInvokeArg, m)
let newExpr = reducedExpr |> remake
match newf0, reducedExpr with
| Expr.Obj _, Expr.Let _ ->
Expand Down
12 changes: 6 additions & 6 deletions src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8476,9 +8476,9 @@ let (|NewDelegateExpr|_|) g expr =
[<return: Struct>]
let (|DelegateInvokeExpr|_|) g expr =
match expr with
| Expr.App ((Expr.Val (invokeRef, _, _)) as delInvokeRef, delInvokeTy, [], [delExpr;delInvokeArg], m)
| Expr.App ((Expr.Val (invokeRef, _, _)) as delInvokeRef, delInvokeTy, tyargs, [delExpr;delInvokeArg], m)
when invokeRef.LogicalName = "Invoke" && isFSharpDelegateTy g (tyOfExpr g delExpr) ->
ValueSome(delInvokeRef, delInvokeTy, delExpr, delInvokeArg, m)
ValueSome(delInvokeRef, delInvokeTy, tyargs, delExpr, delInvokeArg, m)
| _ -> ValueNone

[<return: Struct>]
Expand All @@ -8505,17 +8505,17 @@ let (|OpPipeRight3|_|) g expr =
ValueSome(resType, arg1, arg2, arg3, fExpr, m)
| _ -> ValueNone

let rec MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, delExpr, delInvokeTy, delInvokeArg, m) =
let rec MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, delExpr, delInvokeTy, tyargs, delInvokeArg, m) =
match delExpr with
| Expr.Let (bind, body, mLet, _) ->
mkLetBind mLet bind (MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, body, delInvokeTy, delInvokeArg, m))
| NewDelegateExpr g (_, argvs, body, m, _) when argvs.Length > 0 ->
mkLetBind mLet bind (MakeFSharpDelegateInvokeAndTryBetaReduce g (delInvokeRef, body, delInvokeTy, tyargs, delInvokeArg, m))
| NewDelegateExpr g (_, argvs & _ :: _, body, m, _) ->
let pairs, body = MultiLambdaToTupledLambdaIfNeeded g (argvs, delInvokeArg) body
let argvs2, args2 = List.unzip pairs
mkLetsBind m (mkCompGenBinds argvs2 args2) body
| _ ->
// Remake the delegate invoke
Expr.App (delInvokeRef, delInvokeTy, [], [delExpr; delInvokeArg], m)
Expr.App (delInvokeRef, delInvokeTy, tyargs, [delExpr; delInvokeArg], m)

//---------------------------------------------------------------------------
// Adjust for expected usage
Expand Down
6 changes: 4 additions & 2 deletions src/Compiler/TypedTree/TypedTreeOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1438,7 +1438,9 @@ val MakeApplicationAndBetaReduce: TcGlobals -> Expr * TType * TypeInst list * Ex
/// Make a delegate invoke expression for an F# delegate type, doing beta reduction by introducing let-bindings
/// if the delegate expression is a construction of a delegate.
val MakeFSharpDelegateInvokeAndTryBetaReduce:
TcGlobals -> delInvokeRef: Expr * delExpr: Expr * delInvokeTy: TType * delInvokeArg: Expr * m: range -> Expr
TcGlobals ->
delInvokeRef: Expr * delExpr: Expr * delInvokeTy: TType * tyargs: TypeInst * delInvokeArg: Expr * m: range ->
Expr

/// Combine two static-resolution requirements on a type parameter
val JoinTyparStaticReq: TyparStaticReq -> TyparStaticReq -> TyparStaticReq
Expand Down Expand Up @@ -2751,7 +2753,7 @@ val (|NewDelegateExpr|_|): TcGlobals -> Expr -> (Unique * Val list * Expr * rang

/// Match a .Invoke on a delegate
[<return: Struct>]
val (|DelegateInvokeExpr|_|): TcGlobals -> Expr -> (Expr * TType * Expr * Expr * range) voption
val (|DelegateInvokeExpr|_|): TcGlobals -> Expr -> (Expr * TType * TypeInst * Expr * Expr * range) voption

/// Match 'if __useResumableCode then ... else ...' expressions
[<return: Struct>]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,16 @@ module ComputationExpressions =
compilation
|> getCompilation
|> verifyCompilation

[<Theory; FileInlineData("CustomCollectionBuilderComputationExpr.fs", Optimize=BooleanOptions.Both)>]
let ``CustomCollectionBuilderComputationExpr_fs_OptimizeOff`` compilation =
compilation
|> getCompilation
|> asExe
|> withReferences [
FsFromPath (Path.Combine (__SOURCE_DIRECTORY__, "CustomCollectionBuilderComputationExprLibrary.fs"))
|> withName "CustomCollectionBuilderComputationExprLibrary"
]
|> withEmbeddedPdb
|> withEmbedAllSource
|> verifyILBaseline
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module ComputationExpressions.Program

let f0 () =
let xs = ResizeArray ()
xs.Add 1
xs.Add 2
xs.Add 3
xs

let xs = f0 ()

let f1 () = resizeArray { 1; 2; 3 }
let f2 () = resizeArray { yield! xs }
let f3 () = resizeArray { for x in xs -> x * x }
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@





.assembly extern runtime { }
.assembly extern FSharp.Core { }
.assembly extern assemblyLibrary
{
.ver 0:0:0:0
}
.assembly assembly
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
int32,
int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )




.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module assembly.exe

.imagebase {value}
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003
.corflags 0x00000001





.class public abstract auto ansi sealed ComputationExpressions.Program
extends [runtime]System.Object
{
.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 )
.field static assembly class [runtime]System.Collections.Generic.List`1<int32> xs@10
.custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 )
.method public static class [runtime]System.Collections.Generic.List`1<int32> f0() cil managed
{

.maxstack 4
.locals init (class [runtime]System.Collections.Generic.List`1<int32> V_0)
IL_0000: newobj instance void class [runtime]System.Collections.Generic.List`1<int32>::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldc.i4.1
IL_0008: callvirt instance void class [runtime]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000d: ldloc.0
IL_000e: ldc.i4.2
IL_000f: callvirt instance void class [runtime]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0014: ldloc.0
IL_0015: ldc.i4.3
IL_0016: callvirt instance void class [runtime]System.Collections.Generic.List`1<int32>::Add(!0)
IL_001b: ldloc.0
IL_001c: ret
}

.method public specialname static class [runtime]System.Collections.Generic.List`1<int32> get_xs() cil managed
{

.maxstack 8
IL_0000: ldsfld class [runtime]System.Collections.Generic.List`1<int32> ComputationExpressions.Program::xs@10
IL_0005: ret
}

.method public static class [runtime]System.Collections.Generic.List`1<int32> f1() cil managed
{

.maxstack 4
.locals init (class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<int32> V_0,
class [runtime]System.Collections.Generic.List`1<int32> V_1,
class [runtime]System.Collections.Generic.List`1<int32>& V_2)
IL_0000: call class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<!0> class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<int32>::get_Instance()
IL_0005: stloc.0
IL_0006: newobj instance void class [runtime]System.Collections.Generic.List`1<int32>::.ctor()
IL_000b: stloc.1
IL_000c: ldloca.s V_1
IL_000e: stloc.2
IL_000f: ldloc.2
IL_0010: ldobj class [runtime]System.Collections.Generic.List`1<int32>
IL_0015: ldc.i4.1
IL_0016: callvirt instance void class [runtime]System.Collections.Generic.List`1<int32>::Add(!0)
IL_001b: ldloca.s V_1
IL_001d: stloc.2
IL_001e: ldloc.2
IL_001f: ldobj class [runtime]System.Collections.Generic.List`1<int32>
IL_0024: ldc.i4.2
IL_0025: callvirt instance void class [runtime]System.Collections.Generic.List`1<int32>::Add(!0)
IL_002a: ldloca.s V_1
IL_002c: stloc.2
IL_002d: ldloc.2
IL_002e: ldobj class [runtime]System.Collections.Generic.List`1<int32>
IL_0033: ldc.i4.3
IL_0034: callvirt instance void class [runtime]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0039: ldloc.1
IL_003a: ret
}

.method public static class [runtime]System.Collections.Generic.List`1<int32> f2() cil managed
{

.maxstack 4
.locals init (class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<int32> V_0,
class [runtime]System.Collections.Generic.List`1<int32> V_1,
class [runtime]System.Collections.Generic.List`1<int32>& V_2)
IL_0000: call class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<!0> class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<int32>::get_Instance()
IL_0005: stloc.0
IL_0006: newobj instance void class [runtime]System.Collections.Generic.List`1<int32>::.ctor()
IL_000b: stloc.1
IL_000c: ldloca.s V_1
IL_000e: stloc.2
IL_000f: ldloc.2
IL_0010: ldobj class [runtime]System.Collections.Generic.List`1<int32>
IL_0015: call class [runtime]System.Collections.Generic.List`1<int32> ComputationExpressions.Program::get_xs()
IL_001a: callvirt instance void class [runtime]System.Collections.Generic.List`1<int32>::AddRange(class [runtime]System.Collections.Generic.IEnumerable`1<!0>)
IL_001f: ldloc.1
IL_0020: ret
}

.method public static class [runtime]System.Collections.Generic.List`1<int32> f3() cil managed
{

.maxstack 5
.locals init (class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<int32> V_0,
class [runtime]System.Collections.Generic.List`1<int32> V_1,
int32 V_2,
int32 V_3,
int32 V_4,
int32 V_5,
class [runtime]System.Collections.Generic.List`1<int32>& V_6)
IL_0000: call class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<!0> class [assemblyLibrary]ComputationExpressions.Library/ResizeArrayBuilder`1<int32>::get_Instance()
IL_0005: stloc.0
IL_0006: newobj instance void class [runtime]System.Collections.Generic.List`1<int32>::.ctor()
IL_000b: stloc.1
IL_000c: ldc.i4.0
IL_000d: stloc.3
IL_000e: call class [runtime]System.Collections.Generic.List`1<int32> ComputationExpressions.Program::get_xs()
IL_0013: callvirt instance int32 class [runtime]System.Collections.Generic.List`1<int32>::get_Count()
IL_0018: ldc.i4.1
IL_0019: sub
IL_001a: stloc.2
IL_001b: ldloc.2
IL_001c: ldloc.3
IL_001d: blt.s IL_004f

IL_001f: call class [runtime]System.Collections.Generic.List`1<int32> ComputationExpressions.Program::get_xs()
IL_0024: ldloc.3
IL_0025: callvirt instance !0 class [runtime]System.Collections.Generic.List`1<int32>::get_Item(int32)
IL_002a: stloc.s V_4
IL_002c: ldloc.s V_4
IL_002e: ldloc.s V_4
IL_0030: mul
IL_0031: stloc.s V_5
IL_0033: ldloca.s V_1
IL_0035: stloc.s V_6
IL_0037: ldloc.s V_6
IL_0039: ldobj class [runtime]System.Collections.Generic.List`1<int32>
IL_003e: ldloc.s V_5
IL_0040: callvirt instance void class [runtime]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0045: ldloc.3
IL_0046: ldc.i4.1
IL_0047: add
IL_0048: stloc.3
IL_0049: ldloc.3
IL_004a: ldloc.2
IL_004b: ldc.i4.1
IL_004c: add
IL_004d: bne.un.s IL_001f

IL_004f: ldloc.1
IL_0050: ret
}

.method private specialname rtspecialname static void .cctor() cil managed
{

.maxstack 8
IL_0000: ldc.i4.0
IL_0001: stsfld int32 '<StartupCode$assembly>.$ComputationExpressions'.Program::init@
IL_0006: ldsfld int32 '<StartupCode$assembly>.$ComputationExpressions'.Program::init@
IL_000b: pop
IL_000c: ret
}

.method assembly specialname static void staticInitialization@() cil managed
{

.maxstack 8
IL_0000: call class [runtime]System.Collections.Generic.List`1<int32> ComputationExpressions.Program::f0()
IL_0005: stsfld class [runtime]System.Collections.Generic.List`1<int32> ComputationExpressions.Program::xs@10
IL_000a: ret
}

.property class [runtime]System.Collections.Generic.List`1<int32>
xs()
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 09 00 00 00 00 00 )
.get class [runtime]System.Collections.Generic.List`1<int32> ComputationExpressions.Program::get_xs()
}
}

.class private abstract auto ansi sealed '<StartupCode$assembly>.$ComputationExpressions'.Program
extends [runtime]System.Object
{
.field static assembly int32 init@
.custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 )
.custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
.custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 )
.method public static void main@() cil managed
{
.entrypoint

.maxstack 8
IL_0000: call void ComputationExpressions.Program::staticInitialization@()
IL_0005: ret
}

}






Loading