-
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: strengthen checking of the loop table #71184
JIT: strengthen checking of the loop table #71184
Conversation
Add loop table checking to the post-phase list, conditional on whether the table is expected to be valid. Declare that the table is valid from the end of the find loops phase to the end of the optimization phases. Add checks that sibling loops are fully disjoint, no child shares top with its parent, and all top-entry loops have at most one non-loop backedge. Closes dotnet#71084.
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsAdd loop table checking to the post-phase list, conditional on whether the Declare that the table is valid from the end of the find loops phase to the Add checks that sibling loops are fully disjoint, no child shares top with its Closes #71084.
|
@BruceForstall PTAL This probably won't survive some of the more ambitious CI legs (like libraries-pgo). Let's see if it gets through innerloop first. |
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.
Looks great! A couple nits and one major question about how often the new checking actually gets invoked.
src/coreclr/jit/phase.cpp
Outdated
@@ -232,6 +232,7 @@ void Phase::PostPhase(PhaseStatus status) | |||
comp->fgDebugCheckLinks(); | |||
comp->fgDebugCheckNodesUniqueness(); | |||
comp->fgVerifyHandlerTab(); | |||
comp->fgDebugCheckLoopTable(); |
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.
I love having this here. (In fact, I wrote this exact change back in December but never got it in :-( ).
IIRC, not too many of the optimization phases actually go down this code path, so it won't get run much before the loop table is marked as not valid. I tried expanding the number of optimization phases that were "true" phase object phases, and used post-phase processing, and they kept hitting cases where the loop table was invalid.
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.
VN and hoisting are dependent on proper loop structure, so it would be good to be confident that things are ok there. Let me try converting them to true phases (along with ssa and early prop)
I added proper post-phase checks for all (most?) of the phases that run between find loops and hoisting. This passes local SPMI. Some of these newly updated phases may not really deserve dumping like we're doing now (eg I now see a few small SPMI diffs where we are no longer unmarking loops and so doing some alignment padding, because |
/azp run runtime-coreclr jitstress |
Azure Pipelines successfully started running 1 pipeline(s). |
@BruceForstall this has evolved quite a bit since you approved, can you look again? Main thing is that many more phases opt into the default post phase checks, including all the ones that I know of that are critically dependent on the loop table being valid. In some cases this required adding/exposing change tracking within the phases. |
Also verified this addresses the AV hit in #71071 as we no longer try and unmark loops during the Optimize layout phase. |
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.
It feels like we should have an assert that if a phase returns MODIFIED_NOTHING
, we could actually verify that. Think: clone IR/lcl vars/EH/loop table, run phase, compare against cache. Yes, expensive and lots of new code.
src/coreclr/jit/flowgraph.cpp
Outdated
#endif // DEBUG | ||
|
||
return PhaseStatus::MODIFIED_NOTHING; |
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.
Shouldn't this be MODIFIED_EVERYTHING
?
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.
Sure. Currently dumping and checking are tied together. I was trying to avoid redundant seeming dumps, but I suppose we want the checks.
src/coreclr/jit/lclvars.cpp
Outdated
@@ -4590,6 +4592,8 @@ void Compiler::lvaMarkLocalVars() | |||
} | |||
} | |||
|
|||
bool addedLocal = false; |
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.
It might be simpler to cache lvaCount
here and then check lvaCount
against the cached value at the end, instead of interspersing addedLocal = true
calls and hoping you didn't miss any place.
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.
Yes, that would be more robust. Will do.
@@ -604,6 +604,8 @@ void Compiler::optClearLoopIterInfo() | |||
loop.lpConstInit = -1; | |||
loop.lpTestTree = nullptr; | |||
} | |||
|
|||
return PhaseStatus::MODIFIED_NOTHING; |
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.
Well, NOTHING
is a misnomer. So maybe you could at least add a comment at the definition of MODIFIED_NOTHING
that says what counts as something and what counts as nothing?
@@ -7645,9 +7665,6 @@ void Compiler::optHoistCandidate(GenTree* tree, BasicBlock* treeBb, unsigned lnu | |||
return; | |||
} | |||
|
|||
// Create a loop pre-header in which to put the hoisted code. | |||
fgCreateLoopPreHeader(lnum); |
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.
You can't move this down because the next check depends on the pre-header already being created.
src/coreclr/jit/compiler.cpp
Outdated
@@ -4840,11 +4840,13 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl | |||
|
|||
if (opts.OptimizationEnabled()) | |||
{ | |||
// xxx |
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.
Fix comment
We have some of this checking of this already, see |
@BruceForstall updated per your feedback. |
@@ -6792,6 +6809,9 @@ void Compiler::optHoistThisLoop(unsigned lnum, LoopHoistContext* hoistCtxt, Basi | |||
defExec.Push(pLoopDsc->lpEntry); | |||
|
|||
optHoistLoopBlocks(lnum, &defExec, hoistCtxt); | |||
|
|||
const unsigned numHoisted = pLoopDsc->lpHoistedFPExprCount + pLoopDsc->lpHoistedExprCount; | |||
return pLoopDsc->lpHoistAddedPreheader || (numHoisted > 0); |
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.
Sorry for getting back late, but what is the scenario where lpHoistAddedPreheader == true
but numHoisted == 0
?
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.
I know that there is one scenario:
runtime/src/coreclr/jit/optimizer.cpp
Lines 7674 to 7680 in 7af23d4
if (!BasicBlock::sameTryRegion(optLoopTable[lnum].lpHead, treeBb)) | |
{ | |
JITDUMP(" ... not hoisting in " FMT_LP ", eh region constraint (pre-header try index %d, candidate " FMT_BB | |
" try index %d\n", | |
lnum, optLoopTable[lnum].lpHead->bbTryIndex, treeBb->bbNum, treeBb->bbTryIndex); | |
return; | |
} |
But in that case, we should just return false
that no hoisting was done?
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.
Yes, there were a few cases in SPMI where we created a preheader and then failed the eh check.
Since we've modified the flow graph (even though we might not have hoisted anything) we need to return true.
@@ -5958,17 +5960,10 @@ class Compiler | |||
|
|||
// Do hoisting of all loops nested within loop "lnum" (an index into the optLoopTable), followed | |||
// by the loop "lnum" itself. | |||
// | |||
// "m_pHoistedInCurLoop" helps a lot in eliminating duplicate expressions getting hoisted |
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.
Any reason this comment was deleted?
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.
Looks like an inadvertent edit -- at one point I had modified the signature of optHoistLoopNest.
Add loop table checking to the post-phase list, conditional on whether the
table is expected to be valid.
Declare that the table is valid from the end of the find loops phase to the
end of the optimization phases.
Add checks that sibling loops are fully disjoint, no child shares top with its
parent, and all top-entry loops have at most one non-loop backedge.
Closes #71084.