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

    Type

    Projects

    Status

    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions