3 ms·
But it adds .tail prefix before every callvirt instruction and this breaks some JIT optimizations. E.g. generic type specialization 'if(typeof(T) == typeof(...)
by buybackoff 6y ago
But it adds .tail prefix before every callvirt instruction and this breaks some JIT optimizations. E.g. generic type specialization 'if(typeof(T) == typeof(...))' breaks, I had to dig into compiler source code to find out that it doesn't add .tail before ctors, used a struct wrapper and called a method in the struct ctor to avoid .tail prefix, but it adds its own overhead.
Also, most simple tail calls are rewritten by a compiler to a loop, but if you look at the generated IL, there are multiple temp variables, locals, needless assignments. Maybe JIT is able to eliminate those, or maybe not... In C# I could get IL that reflects what I write, without surprises.
- buybackoff 6y agoSlight correction: .tail prefix prevents inlining by JIT, it's mentioned in the second link in my sibling comment as "explicit tail prefix in callee". So even if that type specialization JIT optimization worked, the small performance-critical method containing it was not inlined. But the point of the specialization was to make the call for primitives faster than a method call, and fallback to a default implementation via callvirt. And there is no way to disable adding the .tail prefix to the calls that do not need it. This also means that a lot of F# functions that are not inlined by F# source-code inlining itself effectively have NoInlining attribute. E.g. some helper method `if x then y.call() else z.call()`, if not inlined by F#, will never be inlined by JIT due to .tail prefixes before the methods calls.