dotnet / dotnet/fsharp

Optimization inlining applied inconsistently with piping

Open
#12,416 6 comments 0 reactions 0 assignees View on GitHub
Area-Compiler-CodeGen Area-Compiler-Optimization Feature Improvement Impact-Low Theme-Performance
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 11h
Merged PRs (30d)
131

Description

**Repro steps**

I have a simple F# program. I expect in all cases the lambdas should be inlined but they don't seem to be for reasons that I can't make sense of.

```fsharp
// Simple PushStream
type 'T PushStream = ('T -> bool) -> bool

let inline ofArray (vs : _ array) : _ PushStream = fun ([] r) ->
let mutable i = 0
while i < vs.Length && r vs.[i] do
i <- i + 1
i = vs.Length

let inline fold ([] f) z ([] ps : _ PushStream) =
let mutable s = z
let _ = ps (fun v -> s <- f s v; true)
s

// It turns out that if we pipe using |> the F# compiler don't inlines
// the lambdas as we like it to
// So define a more restrictive version of |> that applies function f to a function v
// As both f and v are restricted to lambas we can apply InlineIfLambda
let inline (|>>) ([] v : _ -> _) ([] f : _ -> _) = f v

let values = [|0..10000|]

let thisIsInlined1 () = ofArray values |>> fold (+) 0
let thisIsInlined2 () =
let vs = [|0..10000|]
ofArray vs |>> fold (+) 0

let thisIsNotInlined1 () = ofArray [|0..10000|] |>> fold (+) 0

type Test() =
let _values = [|0..10000|]
static let _svalues = [|0..10000|]

let array vs =
ofArray vs |>> fold (+) 0

member x.thisIsInlined2 () = ofArray values |>> fold (+) 0
member x.thisIsInlined3 () = array _values

member x.thisIsNotInlined2 () = ofArray _values |>> fold (+) 0
member x.thisIsNotInlined3 () = ofArray _svalues |>> fold (+) 0
```

**Expected behavior**

All `thisIs*` methods should have similar inlined version of the data pipeline.

**Actual behavior**

All `thisIsInlined*` methods have similar inlined version of the data pipeline.
All `thisIsNotInlined*` methods have non-inlined code which is how it looks like if we didn't specify `InlineIfLambda`

Example decompiled:

```csharp
public static int thisIsInlined2()
{
int[] vs = SeqModule.ToArray(Operators.CreateSequence(Operators.OperatorIntrinsics.RangeInt32(0, 1, 10000)));
int num = 0;
int num2 = 0;
for (;;)
{
bool flag;
if (num2 < vs.Length)
{
int num3 = vs[num2];
num += num3;
flag = true;
}
else
{
flag = false;
}
if (!flag)
{
break;
}
num2++;
}
bool flag2 = num2 == vs.Length;
return num;
}

public static int thisIsNotInlined1()
{
int[] vs = SeqModule.ToArray(Operators.CreateSequence(Operators.OperatorIntrinsics.RangeInt32(0, 1, 10000)));
FSharpFunc, bool> fsharpFunc = new Program.thisIsNotInlined1@27(vs);
FSharpRef fsharpRef = new FSharpRef(0);
fsharpFunc.Invoke(new Program.thisIsNotInlined1@27-1(fsharpRef));
return fsharpRef.contents;
}
```

But the only difference is storing the generated source array in a variable or not:

```fsharp
let thisIsInlined2 () =
let vs = [|0..10000|]
ofArray vs |>> fold (+) 0

let thisIsNotInlined1 () = ofArray [|0..10000|] |>> fold (+) 0
```

Perhaps there are good reasons for this but I don't understand them.

**Known workarounds**

Before invoking functions that uses `InlineIfLambda` make sure the input args to them (except the lambdas) are stored in intermediate bindings.

**Related information**

* Operating system : Windows 10
* .NET Runtime kind : .NET 6 : 6.0.100
* Editing Tools: Visual Studio 2022

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.