-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Fix fgNewBBinRegion when inserting into filters. #11134
Conversation
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 don't understand the extra refcounts, which may be just a commenting issue, but otherwise seems OK.
Would really like to see @BruceForstall review this.
src/jit/flowgraph.cpp
Outdated
DONE: | ||
|
||
// If we are inserting into a filter and the best block is the end of the filter region, we need to | ||
// insert after its predecessor instead: the CLR ABI that the terminal block of a filter region is |
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.
Should be "the CLR ABI states that"?
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, good catch.
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.
Done.
@@ -3189,9 +3189,15 @@ void Compiler::fgComputePreds() | |||
if (ehDsc->HasFilter()) | |||
{ | |||
ehDsc->ebdFilter->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL; | |||
|
|||
// The first block of a filter has an artifical extra refcount. | |||
ehDsc->ebdFilter->bbRefs++; |
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.
Why are these extra refcounts needed?
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.
They are expected by fgExtendEHRegionBefore
.
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.
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.
Thanks - so I gather that this is not something that is new, but was previously omitted. I was trying to figure out how that was a new requirement.
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 gather that this is not something that is new, but was previously omitted
Yes, that's right.
src/jit/flowgraph.cpp
Outdated
#if FEATURE_EH_FUNCLETS | ||
bPrev->bbFlags |= BBF_FUNCLET_BEG; | ||
#endif // FEATURE_EH_FUNCLETS | ||
|
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.
This doesn't look right. 'try' is not necessarily the beginning of a funclet
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.
Yeah, I have a fix for this locally.
Why are you addressing the case "2. The filter region consists of a single block"? Does anyone use that? I thought we discussed that wasn't necessary. In particular, if there is a single |
Yes: |
Please run desktop SPMI diffs |
src/jit/flowgraph.cpp
Outdated
} | ||
#endif // FEATURE_EH_FUNCLETS | ||
|
||
|
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.
Seems like this isn't necessary, because if block
is the beginning of a funclet, then it will also be the first block of a handler, and thus the handler check below will do the right thing. Plus, if you do this, I would think the handler check assert below would fail because you would have already cleared the bit.
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.
Done.
src/jit/flowgraph.cpp
Outdated
// insert after its predecessor instead: the CLR ABI that the terminal block of a filter region is | ||
// its exit block. If the filter region consists of a single block, a new block cannot be inserted | ||
// without either splitting the single block before inserting a new block or inserting the new block | ||
// before the single block and updating the filter description s.t. the inserted block is marked as |
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.
s.t. [](start = 67, length = 4)
Can you please spell out "s.t."?
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.
Done.
src/jit/flowgraph.cpp
Outdated
@@ -17069,6 +17098,9 @@ BasicBlock* Compiler::fgFindInsertPoint(unsigned regionIndex, | |||
regionIndex, dspBool(putInTryRegion), startBlk->bbNum, (endBlk == nullptr) ? 0 : endBlk->bbNum, | |||
(nearBlk == nullptr) ? 0 : nearBlk->bbNum, (jumpBlk == nullptr) ? 0 : jumpBlk->bbNum, dspBool(runRarely)); | |||
|
|||
const bool insertingIntoFilter = !putInTryRegion && (startBlk == ehGetDsc(regionIndex - 1)->ebdFilter) && | |||
(endBlk == ehGetDsc(regionIndex - 1)->ebdHndBeg); | |||
|
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 assume you hoisted this because it looked loop-insensitive. However, you've introduced a bug where we're accessing ebdFilter even if the EH region does not represent a filter, which is illegal (since it is a union; we really should assert if we could). Below, we can do this because we know at that point blk == startBlk
and blk
is the first block of a filter, and the function requires the caller to pass exactly a filter region as startBlk/endBlk in this case. You could also use !putInTryRegion && ehGetDsc(regionIndex - 1)->HasFilter() && ...
(but now the number of duplicated ehGetDsc()
is getting ridiculous)
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.
Could this be rewritten as const bool insertingIntoFilter = !putInTryRegion && (startBlk->bbCatchType == BBCT_FILTER) && (endBlk == ehGetDsc(regionIndex - 1)->ebdHndBeg);
?
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 seems like we should be checking to ensure the startBlk and endBlk are in the same EH table entry (even though the caller should presumably guarantee it)
In reply to: 113073328 [](ancestors = 113073328)
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.
Okay. How about
bool insertingIntoFilter = false;
if (!putInTryRegion)
{
EHblkDsc* const dsc = ehGetDsc(regionIndex - 1);
insertingIntoFilter = dsc->HasFilter() && (startBlk == dsc->ebdFilter) && (endBlk == dsc->ebdHndBeg);
}
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.
That works.
src/jit/flowgraph.cpp
Outdated
@@ -17437,6 +17481,21 @@ BasicBlock* Compiler::fgNewBBinRegion(BBjumpKinds jumpKind, | |||
// Now find the insertion point. | |||
afterBlk = fgFindInsertPoint(regionIndex, putInTryRegion, startBlk, endBlk, nearBlk, nullptr, runRarely); | |||
|
|||
// If afterBlk is nullptr, we must be inserting into a single-block filter region. Because the CLR ABI requires | |||
// that control exists a filter via the last instruction in the filter range, this situation requires logically |
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.
exists [](start = 20, length = 6)
exists => exits
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.
Done
src/jit/flowgraph.cpp
Outdated
blockRefs++; | ||
} | ||
} | ||
|
||
if (checkBBNum) |
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.
This looks expensive. Can you instead change the assert below from:
/* Check the bbRefs */
noway_assert(!checkBBRefs || block->bbRefs == blockRefs);
to:
if (checkBBRefs)
{
if (block->bbRefs != blockRefs)
{
// Check to see if this block is the beginning of a filter or a handler and adjust the ref count appropriately.
for (EHblkDsc *HBtab = compHndBBtab, *HBtabEnd = &compHndBBtab[compHndBBtabCount]; HBtab != HBtabEnd; HBtab++)
{
if (HBtab->ebdHndBeg == block)
{
blockRefs++;
}
if (HBtab->HasFilter() && (HBtab->ebdFilter == block))
{
blockRefs++;
}
}
assert(block->bbRefs == blockRefs);
}
}
Thus, it is only computed if required, on demand.
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.
Done
Thanks for cleaning up the bbRefs inconsistencies! |
@BruceForstall desktop SPMI reported no asm diffs. I have verified that there are diffs on x86 in the expected cases, however. |
In order to ensure that the GC operates properly when handling exceptions, the CLR ABI requires that control exits a filter region throguh its terminal instruction. `fgNewBBinRegion` was violating this invariant, however, which lead to GC stress failures when the GC was invoked immediately after a filter finished executing but before control entered any handlers. This change fixes the behavior of `fgNewBBinRegion` when inserting new blocks into filter regions. There are two cases to consider when inserting a new BB into a filter region: 1. The filter region consists of multiple blocks 2. The filter region consists of a single block The first case is straightforward: instead of inserting a new BB after the last block of the filter, insert it before the last block of the filter. Because the filter contains multiple blocks, the predecessor of the filter must also be in the filter region. The latter case requires splitting the single block, as inserting a new block before the single block would otherwise change control flow (the filter would begin with the new block rather than the single block). In order to acheive this with minimal complexity, this change first inserts a `BBJ_ALWAYS` block that transfers control to the existing single block, then inserts a second new block immediately before the existing single block. The second new block is then returned. To put it visually, the transformation is from this: ``` filter start: BBN (BBJ_EHFILTERRET) ``` to this: ``` filter start: BBN + 1 (BBJ_ALWAYS -> BBN) BBN + 2 (jumpKind) BBN (BBJ_EHFILTERRET) ``` and the function returns `BBN + 2`. Fixes VSO 406163.
Issue #11273 revealed an issue with the fix for VSO 406163: as of today, the compiler models the incoming exception object in a handler by looking for `GT_CATCH_ARG` in the first block of a handler and if found marking the register that contains the exception object as holding a GC pointer. Unfortunately, this breaks down under the transform that the original fix (dotnet#11134) was performing for single-block filters. That transformation was from this: ``` filter start: BBN (BBJ_EHFILTERRET) ``` to this: ``` filter start: BBN + 1 (BBJ_ALWAYS -> BBN) BBN + 2 (jumpKind) BBN (BBJ_EHFILTERRET) ``` After this transform, `BBN` still contains the `GT_CATCH_ARG`, but the register containing the exception object must be live through `BBN + 1` as well as live in to `BBN`. There are a number of possible solutions to this problem (e.g. modeling the liveness of the exception object in some fashion or splitting the single-block filter either after the catch arg or before the filter ret), but many of them are complicated by the fact that this transformation needs to operate on both HIR and LIR and must be be able to run in a variety of locations, including after registers have been allocated. Instead, this change takes the approach of ensuring that no single-block filter regions exist on platforms that target the JIT32 GC encoder by inserting a spill of the exception object to a local var in its own block at the start of a filter.
In order to ensure that the GC operates properly when handling
exceptions, the CLR ABI requires that control exits a filter region
throguh its terminal instruction.
fgNewBBinRegion
was violating thisinvariant, however, which lead to GC stress failures when the GC was
invoked immediately after a filter finished executing but before control
entered any handlers. This change fixes the behavior of
fgNewBBinRegion
when inserting new blocks into filter regions.There are two cases to consider when inserting a new BB into a filter
region:
The first case is straightforward: instead of inserting a new BB after
the last block of the filter, insert it before the last block of the
filter. Because the filter contains multiple blocks, the predecessor of
the filter must also be in the filter region.
The latter case requires splitting the single block, as inserting a new
block before the single block would otherwise change control flow (the
filter would begin with the new block rather than the single block). In
order to acheive this with minimal complexity, this change first inserts
a
BBJ_ALWAYS
block that transfers control to the existing singleblock, then inserts a second new block immediately before the existing
single block. The second new block is then returned. To put it
visually, the transformation is from this:
to this:
and the function returns
BBN + 2
.Fixes VSO 406163.