-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JIT: Give up on the tail call if there are unexpected blocks after it #50806
Conversation
Can you share out a dump? I am curious how this happens (perhaps GDV of a tail call candidate?) |
Ah, yes, it happens because of multiple GDVs, trying to make a quick repro - I guess we just need to reset the tailcall prefix in such cases? |
Surprised we haven't run across this already somewhere. Seems like it ought to be easy to repro.
We could also try and fix the GDV transform to not add blocks after the call, if the call is a tail call candidate. |
I couldn't manage to create a repro for it 🙁 maybe I'm missing something.
Do you mean to not inline tail calls after GDV? Minimal repro: create a net5.0 console app, add using System;
using System.Threading;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem;
class Program
{
static void Main()
{
var decompiler = new CSharpDecompiler(typeof(object).Assembly.Location, new DecompilerSettings());
for (int i = 0; i < 100; i++)
{
decompiler.DecompileTypeAsString(new FullTypeName("System.__ComObject"));
Thread.Sleep(16);
}
Console.WriteLine("done");
}
} To get a jit dump:
|
Here's a relatively simple repro using System;
using System.Runtime.CompilerServices;
using System.Threading;
class B
{
public virtual int F(B[] b, bool p)
{
int r = 0;
for (int i = 0; i < 10; i++)
{
r = i * i;
}
for (int i = 0; i < 10; i++)
{
r = i * i;
}
return r;
}
}
class D : B
{
public override int F(B[] b, bool p) => p ? b[2].F(b, p) : 5;
}
class E : D
{
public override int F(B[] b, bool p) => b[1].F(b, p);
}
class X
{
static B[] b = { new E(), new D(), new B() };
public static int Main()
{
int r = 0;
for (int i = 0; i < 100; i++)
{
r += b[0].F(b, true);
Thread.Sleep(15);
}
for (int i = 0; i < 100; i++)
{
r += b[0].F(b, true);
Thread.Sleep(15);
}
Console.WriteLine($"r = {r}");
return r;
}
} The problem arises in In the example above, Since all this happens on the happy path, I think we should try and fix this instead of bailing out on the tail call, either by coalescing temps during GDV (if the return value of the original call is already going into a return value temp, just use that for the GDV temp, instead of creating a new one), or by making the check in morph recognize that we can see these sorts of chains of returns. |
14bf44f
to
7f750ab
Compare
Thanks for the repro! I've implemented the second option but will check if I can easily track the fact that GT_RET_EXPR is already assigned to a temp so we don't need to introduce another one. |
I have a fix for the first option in the works. It leverages the |
Here's what I came up with: main...AndyAyersMS:FixGDVTailCallIssue. It might make sense to take both our changes... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider picking up my fix as well.
Inlinee guarded devirtualization candidates that are also tail call candidates can share the return temp with the parent inline. Blocks containing only GT_NOP or only GT_PHI are empty.
Nice! Thanks, I've cherry-picked it to my branch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes LGTM, but since some of this is my code, maybe we should get a second review.
@dotnet/jit-contrib anyone else want to review? This has changes from both me and @EgorBo. |
FYI we also hit this issue in weekend PGO testing... |
The detection of blocks with only PHI assignments was broken by dotnet#50806. Fix by using existing helper to find the first non-PHI assignment. Closes dotnet#51326.
Fixes #50492 assert.
It only reproduces with the following env. vars:
on ILSpy app (https://github.com/icsharpcode/AvaloniaILSpy) after some time randomly clicking, doesn't repro with
TieredPGO=0
JitDump of a failed method https://gist.github.com/EgorBo/f0f58d04ec537ae815203f947309df65
(initially I wanted to perform fgCompactBlocks in such cases, but the sequence of blocks after the taill call is more complicated than fgCompact can handle)
I don't know how to make a unit test out of it, perhaps I can write it in IL.
diffs are empty.
/cc @AndyAyersMS @dotnet/jit-contrib