dotnet / dotnet/fsharp

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

Open
#12,137 3 comments 0 reactions 0 assignees View on GitHub
Area-Compiler-CodeGen Feature Improvement
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 11h
Merged PRs (30d)
131

Description

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:

``` il
.method public static int32
noTailCalls(
class [System.Collections]System.Collections.Generic.List`1 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 class [System.Collections]System.Collections.Generic.List`1::GetEnumerator()
IL_000c: call !!0/*int32*/ Tailcalls/GSeq::fold>(!!0/*int32*/, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, !!2/*valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator*/)
IL_0011: ret

} // end of method Tailcalls::noTailCalls
```

```
.method public static int32
whyTailCalls(
class [System.Collections]System.Collections.Generic.List`1 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 class [System.Collections]System.Collections.Generic.List`1::GetEnumerator()
IL_000c: tail.
IL_000e: call !!0/*int32*/ [Lib.FSharp]Lib.FSharp.GenericEnumerators.GSeq::fold>(!!0/*int32*/, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, !!2/*valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator*/)
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

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.