JIT: (bug) GDV: truncated class-profile histogram inflates the top candidate's likelihood to fill 100%
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
`getLikelyClasses` reports at most five entries, then adds the missing probability mass to entry 0 even when the histogram was truncated. Dropped receiver types are credited to the top candidate, making highly polymorphic calls look dominated by one type.
### Minimal Repro
```csharp
using System;
using System.Runtime.CompilerServices;
interface IFoo { int F(); }
class C0 : IFoo { public int F() => 0; }
class C1 : IFoo { public int F() => 1; }
class C2 : IFoo { public int F() => 2; }
class C3 : IFoo { public int F() => 3; }
class C4 : IFoo { public int F() => 4; }
class C5 : IFoo { public int F() => 5; }
class C6 : IFoo { public int F() => 6; }
class C7 : IFoo { public int F() => 7; }
public class Program
{
static IFoo[] s = { new C0(), new C1(), new C2(), new C3(), new C4(), new C5(), new C6(), new C7() };
[MethodImpl(MethodImplOptions.NoInlining)]
static int Test(IFoo f) => f.F();
public static void Main()
{
int acc = 0;
for (int j = 0; j < 40; j++)
{
for (int i = 0; i < 100000; i++)
acc += Test(s[i & 7]);
System.Threading.Thread.Sleep(15);
}
Console.WriteLine(acc);
}
}
```
Requires tiering/TieredPGO (default); use a Checked JIT with `DOTNET_JitDump=Test` or `DOTNET_JitDisasm=Test` to observe the inflated likelihood and guard.
### Expected
Each of the eight implementations is hit 12.5% of the time. The retained likelihoods should sum to roughly the retained mass, no type should clear the 25% interface-GDV threshold, and no guarded devirtualization should be emitted.
### Actual
```
Likely classes for call [000001] on class ... (IFoo)
1) ... (C4) [likelihood:49%]
2) ... (C3) [likelihood:18%]
3) ... (C5) [likelihood:12%]
4) ... (C0) [likelihood:12%]
5) ... (C6) [likelihood:9%]
Accepting type C4 with likelihood 49 as a candidate
Marking call [000001] as guarded devirtualization candidate; will guess for class C4
Likelihood of correct guess is 49
```
A class-handle equality guard is generated for a type that is actually only about 12.5% likely, and CFG weights treat the fast path as roughly half-likely.
### Notes
In `src\coreclr\jit\likelyclass.cpp`, `getLikelyClassesOrMethods` computes `numberOfClasses = min(knownHandles, maxLikelyClasses)` but still applies `pLikelyEntries[0].likelihood += 100 - totalLikelihood` whenever there are no unknown handles.
When `knownHandles > maxLikelyClasses`, that difference includes observations from truncated known types, not just integer-rounding slack.
Consumers include GDV in `importercalls.cpp`, indirect-call transformation weights, cast helper expansion, and other callers of the likely-class API.
Contributor guide
Research direction
Start in src\coreclr\jit\likelyclass.cpp, focusing on getLikelyClassesOrMethods and the truncated known-handle path. Run the supplied C# repro with tiering/TieredPGO under a Checked JIT using DOTNET_JitDump=Test or DOTNET_JitDisasm=Test, then inspect GDV handling in importercalls.cpp. Done means retained likelihoods reflect retained mass and the repro no longer emits a guarded devirtualization candidate for the 12.5% type.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- compilers, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100