JIT: add NativeAOT support for non-shared GVM devirtualization#130202
JIT: add NativeAOT support for non-shared GVM devirtualization#130202hez2010 wants to merge 11 commits into
Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull request overview
Enables NativeAOT generic virtual method (GVM) devirtualization by preserving the helper-call-shaped indirection target long enough for the importer’s devirtualization logic to recognize it, while still supporting NativeAOT fat-pointer lowering later during indirect-call transformation.
Changes:
- Stop spilling NativeAOT GVM
ldvirtftntargets to a temp in the importer; instead only mark calls as fat-pointer candidates. - In indirect-call transformation, for generic-virtual fat-pointer candidates, split/spill as needed and then materialize the function pointer into a temp local before expanding the fat-pointer control-flow.
- Ensure fat-pointer-candidate marking is cleared when a call is devirtualized to a direct call; additionally, skip guarded devirtualization for generic-virtual calls.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/coreclr/jit/indirectcalltransformer.cpp | Adds late temp materialization of GVM fat-pointer call targets (post-import) prior to fat-pointer expansion. |
| src/coreclr/jit/importercalls.cpp | Removes early temp spill for NativeAOT GVM ldvirtftn calls; blocks GDV for generic-virtual calls; clears fat-pointer candidate on devirtualization. |
|
It's hitting IL scanner issues like cc: @MichalStrehovsky does this ring a bell? UPDATE: addressed in 2291537 |
|
The scanner change and Program.cs are working around a legit issue (the tests are still failing because of that). The whole program analysis of GVMs currently assumes the call will be lazy and will go through the type loader. The thing that is failing now is following test: class TestGvmDependencies
{
class Atom { }
class Foo
{
public virtual object Frob<T>()
{
return new T[0, 0];
}
}
class Bar : Foo
{
public override object Frob<T>()
{
return new T[0, 0, 0];
}
}
public static void Run()
{
{
Foo x = new Foo();
x.Frob<Atom>();
}
{
Foo x = new Bar();
x.Frob<Atom>();
}
}
}We don't expect We could in theory have a mode that does the full analysis and analyzes I think this will have to be limited to struct instantiations only for now. We could allow reference types if the other analysis mode is added. This is one of the things where RyuJIT-as-IL-scanner would help because then we would do this devirt during scanning already and GVM analysis never gets in the picture (it's not considered a GVM call in the first place). |
|
Bail the shared GVM devirt support for NativeAOT out right now.... |
|
It becomes more like a JIT change now. @jakobbotsch could you please take a look? |
| if (m_origCall->IsGenericVirtual(m_compiler) && m_origCall->IsFatPointerCandidate()) | ||
| { | ||
| Statement* firstNewStmt = nullptr; | ||
| GenTree** splitPointInUse = nullptr; | ||
|
|
||
| m_compiler->gtSplitTree(block, stmt, m_origCall->gtControlExpr, &firstNewStmt, &splitPointInUse, true); | ||
| const unsigned fptrLclNum = m_compiler->lvaGrabTemp(true DEBUGARG("fat pointer temp")); | ||
| GenTree* const store = m_compiler->gtNewTempStore(fptrLclNum, m_origCall->gtControlExpr); | ||
| Statement* const storeStmt = m_compiler->gtNewStmt(store, stmt->GetDebugInfo()); | ||
| m_compiler->fgInsertStmtBefore(block, stmt, storeStmt); | ||
| m_origCall->gtControlExpr = | ||
| m_compiler->gtNewLclvNode(fptrLclNum, genActualType(m_origCall->gtControlExpr->TypeGet())); | ||
| m_compiler->gtUpdateStmtSideEffects(storeStmt); | ||
| m_compiler->gtUpdateStmtSideEffects(stmt); | ||
| } |
There was a problem hiding this comment.
This looks odd to do in the constructor. Override Run and do it from there instead?
m_compiler->gtUpdateStmtSideEffects(storeStmt); looks unnecessary.
There was a problem hiding this comment.
Actually, you can do this in the same way as GuardedDevirtualizationTransformer does it in CreateCheck. This code can probably be shared between the two.
There was a problem hiding this comment.
Moved it to Run instead like GuardedDevirtualizationTransformer.
Under NativeAOT, only mark the fat pointer calls but defer the transformation until we are going to perform indirect calls transform, so that the JIT can recognize the tree for GVM devirtualization.
Unfortunately, we are not able to support late devirtualization and guarded devirtualization because fat pointer call transformation happens too early.
Shared GVMs are also not supported due to IL scanner limitations.
Example:
Before:
After:
Contributes to #112596