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.
// Simple PushStream
type 'T PushStream = ('T -> bool) -> bool
let inline ofArray (vs : _ array) : _ PushStream = fun ([<InlineIfLambda>] r) ->
let mutable i = 0
while i < vs.Length && r vs.[i] do
i <- i + 1
i = vs.Length
let inline fold ([<InlineIfLambda>] f) z ([<InlineIfLambda>] 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 (|>>) ([<InlineIfLambda>] v : _ -> _) ([<InlineIfLambda>] 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:
public static int thisIsInlined2()
{
int[] vs = SeqModule.ToArray<int>(Operators.CreateSequence<int>(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<int>(Operators.CreateSequence<int>(Operators.OperatorIntrinsics.RangeInt32(0, 1, 10000)));
FSharpFunc<FSharpFunc<int, bool>, bool> fsharpFunc = new Program.thisIsNotInlined1@27(vs);
FSharpRef<int> fsharpRef = new FSharpRef<int>(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:
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