dotnet / dotnet/fsharp

Inlining in CEs is prevented by DU constructor in the CE block

Open
#18,753 0 comments 0 reactions 0 assignees View on GitHub
Area-Compiler-CodeGen Area-ComputationExpressions Bug
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 11h
Merged PRs (30d)
131

Description

Please provide a succinct description of the issue.

When using a CE, if a yielded item was constructed as a DU case in place, it prevents inlining of subsequent yields in that CE.

Provide the steps required to reproduce the problem:

```fsharp
type IntOrString =
| I of int
| S of string

type IntOrStringBuilder() =
member inline _.Zero() = ignore
member inline _.Yield(x: int) = fun (xs: ResizeArray) -> xs.Add(I x)
member inline _.Yield(x: string) = fun (xs: ResizeArray) -> xs.Add(S x)
member inline _.Yield(x: IntOrString) = fun (xs: ResizeArray) -> xs.Add(x)
member inline _.Run([] f: ResizeArray -> unit) =
let xs = ResizeArray()
f xs
xs
member inline _.Delay([] f: unit -> ResizeArray -> unit) =
fun (xs: ResizeArray) -> f () xs

member inline _.Combine
(
[] f1: ResizeArray -> unit,
[] f2: ResizeArray -> unit
) =
fun (xs: ResizeArray) ->
f1 xs
f2 xs

let builder = IntOrStringBuilder()

let test1 () =
builder {
1
"two"
3
"four"
}

let test2 () =
builder {
I 1
"two"
3
"four"
}

let test3 () =
builder {
1
"two"
I 3
"four"
}

let test4 () =

let a = 1
let b = S "two"
builder {
a
b
3
"four"
}

let xs1 = test1()
let xs2 = test2()
let xs3 = test3()
let xs4 = test4()

printfn "Test 1: %A" xs1
printfn "Test 2: %A" xs2
printfn "Test 3: %A" xs3
printfn "Test 4: %A" xs4
```

**Expected behavior**

It is expected that each of these examples results in essentially the same codegen

**Actual behavior**

(decompiled using dotPeek). `test1` and `test4` are inlined as expected:

```csharp
public static List test1()
{
return new List()
{
Program.IntOrString.NewI(1),
Program.IntOrString.NewS("two"),
Program.IntOrString.NewI(3),
Program.IntOrString.NewS("four")
};
}
public static List test4()
{
Program.IntOrString intOrString = Program.IntOrString.NewS("two");
return new List()
{
Program.IntOrString.NewI(1),
intOrString,
Program.IntOrString.NewI(3),
Program.IntOrString.NewS("four")
};
}
```

`test2` The first yield `I 1` fails to inline and subsequent yields of primitives are inlined into the generated lambdas

```csharp
public static List test2()
{
List intOrStringList = new List();
FSharpFunc>.InvokeFast((FSharpFunc, Unit>>) Program.test2\u004038.\u0040_instance, (Unit) null, intOrStringList);
return intOrStringList;
}

internal sealed class test2\u004038\u002D1 : FSharpFunc, Unit>
{
public Program.IntOrString x;

internal test2\u004038\u002D1(Program.IntOrString x) => this.x = x;

public override Unit Invoke(List xs)
{
xs.Add(this.x);
return (Unit) null;
}
}

internal sealed class test2\u004038\u002D2 : FSharpFunc, Unit>
{
public FSharpFunc, Unit> f1;

internal test2\u004038\u002D2(FSharpFunc, Unit> f1) => this.f1 = f1;

public override Unit Invoke(List xs)
{
this.f1.Invoke(xs);
xs.Add(Program.IntOrString.NewS("two"));
xs.Add(Program.IntOrString.NewI(3));
xs.Add(Program.IntOrString.NewS("four"));
return (Unit) null;
}
}

internal sealed class test2\u004038 : FSharpFunc, Unit>>
{
internal static readonly Program.test2\u004038 \u0040_instance = new Program.test2\u004038();

[CompilerGenerated]
[DebuggerNonUserCode]
internal test2\u004038()
{
}

public override FSharpFunc, Unit> Invoke(Unit unitVar)
{
return (FSharpFunc, Unit>) new Program.test2\u004038\u002D2((FSharpFunc, Unit>) new Program.test2\u004038\u002D1(Program.IntOrString.NewI(1)));
}
}
```

`test3` the last yield fails to inline

```csharp
public static List test3()
{
List intOrStringList = new List();
intOrStringList.Add(Program.IntOrString.NewI(1));
intOrStringList.Add(Program.IntOrString.NewS("two"));
intOrStringList.Add(Program.IntOrString.NewI(3));
FSharpFunc>.InvokeFast((FSharpFunc, Unit>>) Program.test3\u004049.\u0040_instance, (Unit) null, intOrStringList);
return intOrStringList;
}

internal sealed class test3\u004049\u002D1 : FSharpFunc, Unit>
{
public Program.IntOrString x;

internal test3\u004049\u002D1(Program.IntOrString x) => this.x = x;

public override Unit Invoke(List xs)
{
xs.Add(this.x);
return (Unit) null;
}
}

internal sealed class test3\u004049 : FSharpFunc, Unit>>
{
internal static readonly Program.test3\u004049 \u0040_instance = new Program.test3\u004049();

internal test3\u004049()
{
}

public override FSharpFunc, Unit> Invoke(Unit unitVar)
{
return (FSharpFunc, Unit>) new Program.test3\u004049\u002D1(Program.IntOrString.NewS("four"));
}
}
```

**Known workarounds**

`test4` shows that creating the DU value outside the CE allows for inlining but this is not intuitive

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.