dotnet / dotnet/runtime

JIT: (bug) GDV: truncated class-profile histogram inflates the top candidate's likelihood to fill 100%

Closed
#133,973 2 comments 0 reactions 1 assignee Assigned to @AndyAyersMS View on GitHub
area-CodeGen-coreclr
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.