Maintain sorted preds part 1 - #13322
Conversation
This adds asserts to enforce sorted pred lists and changes fgReplacePred to correctly maintain sorted order when replacing a pred in the list.
d743c36 to
db3cb2c
Compare
|
No longer a WIP. Open question on whether to merge until we can assert preds are sorted or not. |
CarolEidt
left a comment
There was a problem hiding this comment.
It would be useful to see some throughput numbers. Does this method show up at all? Might it be worthwhile to first handle the simple case where the oldPred and newPred have the same sort order?
|
|
||
| for (flowList* pred = block->bbPreds; pred != nullptr; pred = pred->flNext) | ||
| { | ||
| // TODO: Assert here when preds are actually sorted. |
There was a problem hiding this comment.
Nit: when you say "Assert here when preds are actually sorted." it sounds like you want an assert to fire if they are sorted. I would change it to say "Enable this assert when preds are actually sorted."
| assert(!fgCheapPredsValid); | ||
|
|
||
| flowList* pred; | ||
| // Make sure the list is sorted before manipulating. |
There was a problem hiding this comment.
For now I would say:
// Check to see whether the list is sorted before manipulating.
// Note that the predecessors are supposed to be sorted, but currently they are not consistently kept in
// that state. In order to ensure that the existing state is maintained, we will check to see whether it is
// sorted before we start, and then when we are done, if it was originally sorted, we will assert that it still is.
| if (*elementBeforeMovingPred) | ||
| { | ||
| *elementBeforeMovingPred = movingPred->flNext; | ||
| } |
There was a problem hiding this comment.
I wonder how often it is the case that the new block will be in the same place in the sorted list, since it is presumably often the case that we are eliminating blocks that have close bbNums. In that case, you're deleting and reinserting unnecessarily.
There was a problem hiding this comment.
But since the list is singly-linked, you can't look before you. You could only avoid unlink/re-link if the replacement block is > movingPred->flBlock->bbNum and < movingPred->flNext->flBlock->bbNum (which of course would require a null check on movingPred->flNext.)
| // Set the block to the new pred. | ||
| movingPred->flBlock = newPred; | ||
|
|
||
| // If the list is empty or the item is to be inserted at the head. |
There was a problem hiding this comment.
I would change this comment to:
// If there are no other predecessors, make this the first (and last) element in the list.
Note that, if you checked for the simple case first (that newPred sorts in the same place as oldPred, this case would simply fall out.
|
|
||
| // If refCounts were correct coming in, but modifying incorrectly | ||
| // going out assert. | ||
| if (refCountCorrect) |
There was a problem hiding this comment.
Nit: You could add this check above and avoid doing this if we already know it wasn't correct coming in.
| //------------------------------------------------------------------------ | ||
| // fgExpensiveSortedPredCheck: Assert that the pred list for all Basic Blocks is sorted | ||
| // | ||
| // Assumptions: |
There was a problem hiding this comment.
Grammar ("make sense" => "makes sense", remove "when"). Suggestion:
// This check is not applicable to the cheap preds lists.
| // | ||
| // Assumptions: | ||
| // -- This only make sense to check when with the full predecessor lists, not the cheap preds lists. | ||
| // |
| bool sorted = true; | ||
|
|
||
| for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext) | ||
| { |
There was a problem hiding this comment.
Needs to be if (!fgExpensiveSortedPredCheck(block)) return false;.
Currently, it only checks the first block.
| return true; | ||
| } | ||
|
|
||
| //------------------------------------------------------------------------ |
There was a problem hiding this comment.
Comment is wrong: is function checks one block, not all
| //------------------------------------------------------------------------ | ||
| // fgExpensiveSortedPredCheck: Assert that the pred list for all Basic Blocks is sorted | ||
| // | ||
| // Assumptions: |
| noway_assert(newPred != nullptr); | ||
| assert(!fgCheapPredsValid); | ||
|
|
||
| flowList* pred; |
There was a problem hiding this comment.
Since you are making so many changes to this function, how about replacing the function header comment with one matching the current standard format.
| if (oldPred == pred->flBlock) | ||
| unsigned refCount = 0; | ||
|
|
||
| // Make sure the ref count to this block is still correct. |
There was a problem hiding this comment.
This comment doesn't make sense here
| #if DEBUG | ||
| // Also make sure that our refCount is correct before modifying. | ||
| // If it is not, keep track of that so that we know replacePreds is | ||
| // not the problem. |
There was a problem hiding this comment.
I presume there are cases currently where the ref count is not correct?
There should be a TODO of some kind here to either add code like assert(fgRefCountMatchesPredRefCount(block)), or similar. I would hope either this whole DEBUG clause goes away, or gets replaced by a function call / assert.
| // and change this to an assert. | ||
| // | ||
| // Tracked with https://github.com/dotnet/coreclr/issues/13345 | ||
| if (!movingPred) |
There was a problem hiding this comment.
CC: if (!movingPred) => if (movingPred == nullptr)
| return; | ||
|
|
||
| // Splice out the node that we are modifying. | ||
| if (*elementBeforeMovingPred) |
There was a problem hiding this comment.
You should not have this if; if movingPred != nullptr, then elementBeforeMovingPred is correctly set.
|
|
||
| #endif // DEBUG | ||
|
|
||
| flowList** elementBeforeMovingPred; |
There was a problem hiding this comment.
nit: I wouldn't call this "elementBeforeMovingPred" because "element" might be construed to be a predecessor, which it is not. I would call it "ptrToPred", or "ptrToMovingPred".
| // If the list is empty or the item is to be inserted at the head. | ||
| if (block->bbPreds == nullptr) | ||
| { | ||
| // We are inserting the newPred at the end of the list.AddArgumentToTail |
There was a problem hiding this comment.
This comment doesn't make sense here.
| noway_assert(newPred != nullptr); | ||
| assert(!fgCheapPredsValid); | ||
|
|
||
| flowList* pred; |
There was a problem hiding this comment.
Should we also have:
assert(oldPred != newPred);
?
| // Set the block to the new pred. | ||
| movingPred->flBlock = newPred; | ||
|
|
||
| // If the list is empty or the item is to be inserted at the head. |
There was a problem hiding this comment.
There are a bunch of cases coming up; it would be useful to have a comment at the top that enumerates them, and says how each will be handled.
| // back in the pred list. | ||
|
|
||
| unsigned largerCount = pred->flDupCount; | ||
| largerCount = movingPred->flDupCount > largerCount ? movingPred->flDupCount : largerCount; |
There was a problem hiding this comment.
Doesn't the flDupCount need to be added between the existing and new preds?
| { | ||
| prevPred->flNext = movingPred; | ||
| movingPred->flNext = nullptr; | ||
| } |
There was a problem hiding this comment.
I think all the code above could be more simply written as:
flowList** ptrToPred = &block->bbPreds;
for(;;)
{
if ((*ptrToPred == nullptr) || ((*ptrToPred)->flBlock->bbNum > newPred->bbNum))
{
// Insert after *ptrToPred (might be at end).
movingPred->flNext = *ptrToPred;
*ptrToPred = movingPred;
break;
}
else if ((*ptrToPred)->flBlock->bbNum == newPred->bbNum)
{
// The replacement pred block is already in the list! Add the ref counts together.
(*ptrToPred)->flDupCount += movingPred->flDupCount;
break;
}
ptrToPred = &((*ptrToPred)->flNext);
}
fgExpensiveSortedPredCheck(block);
|
@jashook Can you add a short writeup on why it's desirable to keep the preds sorted? Perhaps open an issue that talks about the benefits and costs? |
|
@AndyAyersMS That would be useful, but, IMO, is out of scope for this fix. The only "documentation" on this requirement is, I believe, the following in fgAddRefPred: Lines 1048 to 1056 in 8c8523c quoting: The cost/complexity of this is the reason the "cheap preds" list was invented, for early use. I haven't investigated if this comment is still valid (namely, does |
|
I actually didn't intend to ask quite so generally. What I meant to ask was, what motivated this particular bit of work -- did we find a bug, or were we trying to add a checker, did somebody just happen to notice preds were not always sorted, ...? It is plausible loop recognition relies on sorted preds, since it is triggered by lexically backwards branches. Not clear if this is a hard requirement since given two loops with the same "first" there are likely cheap checks to decide which one is the inner loop (compare lexical extents, etc). |
|
This work was motivated by a bug found by jitstress. Because removing a pred relies on a sorted pred list. There was an assert fired that a block (although removed) still had a pred in another node's pred list. This happened because removing the pred did not take into account possible duplicates or that the pred list is currently unsorted when assumed to be sorted. |
|
Quoting @AndyAyersMS over in #13314 (comment),
I just wanted to confirm that yes I have removed that dependence of I don't know what that means for this change... |
|
It seems that as long as we maintain an edge dup count, on edge insertion we need to either search the entire edge list, or keep it sorted to allow early-out. |
|
What is status of this PR? Looks like no activity for last 2 weeks ... |
|
That is a good question. It is worth more discussion to determine what the fix is. It sounds like the answer is no longer maintain sorted preds, but instead remove the dependency. I will close this and open an issue to track the bug. |
This adds asserts to enforce sorted pred lists and changes fgReplacePred
to correctly maintain sorted order when replacing a pred in the list.
Note there are other areas that will break the sorted order of the Pred list. Therefore, this change should not yet be merged. However, it is worth starting a review for what it already fixes.
ptal @BruceForstall @CarolEidt
/cc @RussKeldorph