Skip to content

[IL Emit] Improve analysis to reduce emit of tail.  #12137

@En3Tho

Description

@En3Tho

https://github.com/En3Tho/RandomBenchmarks/blob/master/src/RandomBenchmarks/FSharpBenchmarks/UnexpectedTailCalls.fs - here is the benchmark with repro

There are 2 GSeq modules: first is in file with benchmark and second is in another project, but they basically have same code inside them

For some reason F# compiler emits no tailcalls (as expected) when calling GSeq functions from the same file
But it emits .tailcall prefix when calling same GSeq functions from another project

Examples:

.method public static int32
    noTailCalls(
      class [System.Collections]System.Collections.Generic.List`1<int32> list
    ) cil managed
  {
    .maxstack 8

    // [29 8 - 29 17]
    IL_0000: ldc.i4.0
    IL_0001: ldsfld       class Tailcalls/noTailCalls@29 Tailcalls/noTailCalls@29::@_instance
    IL_0006: ldarg.0      // list
    IL_0007: callvirt     instance valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<!0/*int32*/> class [System.Collections]System.Collections.Generic.List`1<int32>::GetEnumerator()
    IL_000c: call         !!0/*int32*/ Tailcalls/GSeq::fold<int32, int32, valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>>(!!0/*int32*/, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!0/*int32*/, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!1/*int32*/, !!0/*int32*/>>, !!2/*valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>*/)
    IL_0011: ret

  } // end of method Tailcalls::noTailCalls
.method public static int32
    whyTailCalls(
      class [System.Collections]System.Collections.Generic.List`1<int32> list
    ) cil managed
  {
    .maxstack 8

    // [34 8 - 34 47]
    IL_0000: ldc.i4.0
    IL_0001: ldsfld       class Tailcalls/whyTailCalls@34 Tailcalls/whyTailCalls@34::@_instance
    IL_0006: ldarg.0      // list
    IL_0007: callvirt     instance valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<!0/*int32*/> class [System.Collections]System.Collections.Generic.List`1<int32>::GetEnumerator()
    IL_000c: tail.
    IL_000e: call         !!0/*int32*/ [Lib.FSharp]Lib.FSharp.GenericEnumerators.GSeq::fold<int32, int32, valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>>(!!0/*int32*/, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!0/*int32*/, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!1/*int32*/, !!0/*int32*/>>, !!2/*valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>*/)
    IL_0013: ret

  } // end of method Tailcalls::whyTailCalls

Unexpected tailcall makes function run 2-3 times slower and blows asm size 2x because of additional tailcall dispatch helpers being generated by jit

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions