Skip to content

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

@roboz0r

Description

@roboz0r

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:

type IntOrString =
    | I of int
    | S of string

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

    member inline _.Combine
        (
            [<InlineIfLambda>] f1: ResizeArray<IntOrString> -> unit,
            [<InlineIfLambda>] f2: ResizeArray<IntOrString> -> unit
        ) =
        fun (xs: ResizeArray<IntOrString>) ->
            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:

  public static List<Program.IntOrString> test1()
  {
    return new List<Program.IntOrString>()
    {
      Program.IntOrString.NewI(1),
      Program.IntOrString.NewS("two"),
      Program.IntOrString.NewI(3),
      Program.IntOrString.NewS("four")
    };
  }
  public static List<Program.IntOrString> test4()
  {
    Program.IntOrString intOrString = Program.IntOrString.NewS("two");
    return new List<Program.IntOrString>()
    {
      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

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

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

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

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

  internal sealed class test2\u004038\u002D2 : FSharpFunc<List<Program.IntOrString>, Unit>
  {
    public FSharpFunc<List<Program.IntOrString>, Unit> f1;

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

    public override Unit Invoke(List<Program.IntOrString> 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, FSharpFunc<List<Program.IntOrString>, Unit>>
  {
    internal static readonly Program.test2\u004038 \u0040_instance = new Program.test2\u004038();

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

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

test3 the last yield fails to inline

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

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

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

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

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

    internal test3\u004049()
    {
    }

    public override FSharpFunc<List<Program.IntOrString>, Unit> Invoke(Unit unitVar)
    {
      return (FSharpFunc<List<Program.IntOrString>, 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-Compiler-CodeGenIlxGen, ilwrite and things at the backendArea-ComputationExpressionsEnd-to-end experience for computation expressions (except async and state machine compilation)Bug

    Type

    Projects

    Status

    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions