JIT: improve codegen for inline candidates called only for effect#116123
JIT: improve codegen for inline candidates called only for effect#116123AndyAyersMS merged 2 commits intodotnet:mainfrom
Conversation
Keep track of unused GT_RET_EXPRs, and when we inline, substitute just the return value side effects for unused GT_RET_EXPRs. Reduces address exposure in some GDV cases, which can encourage physical promotion and other good things. In particular one case from dotnet#84872.
|
@EgorBo PTAL Modest diffs; will link to them when they're up. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull Request Overview
This PR enhances JIT inlining to handle GT_RET_EXPR nodes whose return values are unused, by tracking them and substituting side effects only when appropriate.
- Introduces a new
GTF_RET_EXPR_UNUSEDflag and helper methods (IsUnused,SetUnused) forGenTreeRetExpr. - Updates
IndirectCallTransformerand the inline substitution walker to convert unused or voidGT_RET_EXPRs into NOPs or extract side effects. - Refines IR validation and side‐effect analysis to skip over
GT_COMMA(NOP,NOP)patterns and nestedGT_RET_EXPRchains.
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/jit/morph.cpp | Adds IsCommaNop helper and special-cases comma-NOP nodes in tail-call IR validation. |
| src/coreclr/jit/indirectcalltransformer.cpp | Refactors inline-candidate handling to patch unused/void returns. |
| src/coreclr/jit/gentree.h | Defines GTF_RET_EXPR_UNUSED and methods on GenTreeRetExpr. |
| src/coreclr/jit/gentree.cpp | Prints unused markers and updates side-effect detection loop. |
| src/coreclr/jit/fginline.cpp | Enhances placeholder substitution to extract side effects for unused returns. |
| src/coreclr/jit/compiler.hpp | Marks GT_RET_EXPR as unused in gtUnusedValNode. |
Comments suppressed due to low confidence (3)
src/coreclr/jit/morph.cpp:5110
- The helper
IsCommaNopis defined below as a nested function, but C++ does not allow nested function definitions. MoveIsCommaNopout of the method body and declare it as a private member or free function.
else if (IsCommaNop(tree))
src/coreclr/jit/morph.cpp:5123
- This definition appears inside the implementation file rather than at file or class scope, leading to invalid C++ syntax. Refactor it into an appropriate scope (e.g. a private static method of
Compiler).
bool IsCommaNop(GenTree* node)
src/coreclr/jit/gentree.cpp:16886
- This
whileloop never updatespotentialCall, leading to an infinite loop whenever the initial node isGT_RET_EXPR. Consider changing it back toifor updatingpotentialCallwithin the loop.
while (potentialCall->OperIs(GT_RET_EXPR))
src/coreclr/jit/compiler.hpp
Outdated
| { | ||
| JITDUMP("\nmarking RET_EXPR [%06u] as unused\n", dspTreeID(expr)); | ||
| expr->AsRetExpr()->SetUnused(); | ||
| } |
There was a problem hiding this comment.
To be honest, it is a bit weird to mutate input/state in gtNew-like APIs (but I think we do that in a few others).
| return false; | ||
| } | ||
|
|
||
| return node->AsOp()->gtGetOp1()->OperIs(GT_NOP) && node->AsOp()->gtGetOp2()->OperIs(GT_NOP); |
There was a problem hiding this comment.
Can it be a recursive COMMA(nop, COMMA(nop, nop)) ? perhaps we should use some gtEffectiveVal-like api here?
There was a problem hiding this comment.
Good question. I haven't seen this.
src/coreclr/jit/gentree.cpp
Outdated
| GenTreeRetExpr* const retExpr = tree->AsRetExpr(); | ||
| GenTreeCall* const inlineCand = retExpr->gtInlineCandidate; | ||
|
|
||
| if ((retExpr->gtFlags & GTF_RET_EXPR_UNUSED) != 0) |
There was a problem hiding this comment.
haven't you just added IsUnused() API ?
src/coreclr/jit/fginline.cpp
Outdated
| // If this use is an unused ret expr, is the first child of a comma, the return value is ignored. | ||
| // Extract any side effects. | ||
| // | ||
| if (isUnused || ((parent != nullptr) && parent->OperIs(GT_COMMA) && (parent->gtGetOp1() == *use))) |
There was a problem hiding this comment.
Why is the parent check not sufficient? Adding a contextual flag on RET_EXPR seems weird and fragile.
There was a problem hiding this comment.
Point taken, but note RET_EXPRs are already weird and fragile (can't be cloned, etc).
The main motivation was to give the new RET_EXPR created during GDV expansion the same benefit as the original one; in that setting we don't know the RET_EXPR parent.
There was a problem hiding this comment.
FWIW this extra handing in GDV expansion is responsible for about 20% of the diffs in ASP.NET.
I suppose this early in the phase list it's likely the ret expr will be in the next statement. So GDV expansion can search there for its parent; if it fails it can just assume the ret expr is used. I'll give that a try.
There was a problem hiding this comment.
Got it, I think I understand the GDV case better now. Another +1 for inlining via tree splitting I guess...
|
Revised this to search for the ret expr during GDV instead of marking it up in the importer. Same behavior on the test case and asp.net. |
|
@EgorBo I've revised; take another look? |
Keep track of unused GT_RET_EXPRs, and when we inline, substitute just the return value side effects for unused GT_RET_EXPRs.
Reduces address exposure in some GDV cases, which can encourage physical promotion and other good things.
In particular one case from #84872.