-
Notifications
You must be signed in to change notification settings - Fork 5.2k
JIT: introduce durable EH region ID #113497
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -452,7 +452,7 @@ The code this finally returns to looks like this: | |
|
||
In this case, it zeros out the ShadowSP slot that it previously set to 0xFC, then jumps to the address that is the actual target of the leave from the finally. | ||
|
||
The JIT does this "end finally restore" by creating a GT_END_LFIN tree node, with the appropriate stack level as an operand, that generates this code. | ||
The JIT does this "end finally restore" by creating a GT_END_LFIN tree node, with the appropriate EH region ID as an operand, that generates this code. | ||
|
||
In the case of an exceptional 'finally' invocation, the VM sets up the 'return address' to whatever address it wants the JIT to return to. | ||
|
||
|
@@ -476,7 +476,7 @@ The VM walks the ShadowSP slots in the function `GetHandlerFrameInfo()`, and set | |
|
||
An aside on the JIT implementation for x86. | ||
|
||
The JIT creates BBJ_CALLFINALLY/BBJ_ALWAYS pairs for calling the 'finally' clause. The BBJ_CALLFINALLY block will have a series of CORINFO_JIT_ENDCATCH calls appended at the end, if we need to "leave" a series of nested catches before calling the finally handler (due to a single 'leave' opcode attempting to leave multiple levels of different types of handlers). Then, a GT_END_LFIN statement with the finally clause handler nesting level as an argument is added to the step block where the finally returns to. This is used to generate code to zero out the appropriate level of the ShadowSP slot array after the finally has been executed. The BBJ_CALLFINALLY block itself generates the code to insert the 0xFC value into the ShadowSP slot array. If the 'finally' is invoked by the VM, in exceptional cases, then the VM itself updates the ShadowSP slot array before invoking the 'finally'. | ||
The JIT creates BBJ_CALLFINALLY/BBJ_ALWAYS pairs for calling the 'finally' clause. The BBJ_CALLFINALLY block will have a series of CORINFO_JIT_ENDCATCH calls appended at the end, if we need to "leave" a series of nested catches before calling the finally handler (due to a single 'leave' opcode attempting to leave multiple levels of different types of handlers). Then, a GT_END_LFIN statement with EH region ID as an argument is added to the step block where the finally returns to. This is used to generate code to zero out the appropriate level of the ShadowSP slot array after the finally has been executed and the final EH nesting depth is known. The BBJ_CALLFINALLY block itself generates the code to insert the 0xFC value into the ShadowSP slot array. If the 'finally' is invoked by the VM, in exceptional cases, then the VM itself updates the ShadowSP slot array before invoking the 'finally'. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may be beneficial to briefly explain how using the EH region ID contributes to determining the final EH nesting depth for improved clarity. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
|
||
At the end of a finally or filter, a GT_RETFILT is inserted. For a finally, this is a TYP_VOID which is just a placeholder. For a filter, it takes an argument which evaluates to the return value from the filter. On legacy JIT, this tree triggers the generation of both the return value load (for filters) and the "funclet" exit sequence, which is either a "pop eax; jmp eax" for a finally, or a "ret" for a filter. When processing the BBJ_EHFINALLYRET or BBJ_EHFILTERRET block itself (at the end of code generation for the block), nothing is generated. In RyuJIT, the GT_RETFILT only loads up the return value (for filters) and does nothing for finally, and the block type processing after all the tree processing triggers the exit sequence to be generated. There is no real difference between these, except to centralize all "exit sequence" generation in the same place. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2200,13 +2200,22 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) | |
|
||
#if defined(FEATURE_EH_WINDOWS_X86) | ||
case GT_END_LFIN: | ||
{ | ||
// Find the eh table entry via the eh ID | ||
// | ||
unsigned const ehID = (unsigned)treeNode->AsVal()->gtVal1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we just store There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or perhaps these pointers won't be stable after #112998? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The EH table can be reallocated when it grows so these addresses aren't durable. |
||
assert(ehID < compiler->compEHID); | ||
assert(compiler->m_EHIDtoEHblkDsc != nullptr); | ||
|
||
EHblkDsc* HBtab = nullptr; | ||
bool found = compiler->m_EHIDtoEHblkDsc->Lookup(ehID, &HBtab); | ||
assert(found); | ||
assert(HBtab != nullptr); | ||
|
||
// Have to clear the ShadowSP of the nesting level which encloses the finally. Generates: | ||
// mov dword ptr [ebp-0xC], 0 // for some slot of the ShadowSP local var | ||
|
||
size_t finallyNesting; | ||
finallyNesting = treeNode->AsVal()->gtVal1; | ||
noway_assert(treeNode->AsVal()->gtVal1 < compiler->compHndBBtabCount); | ||
// | ||
const size_t finallyNesting = HBtab->ebdHandlerNestingLevel; | ||
noway_assert(finallyNesting < compiler->compHndBBtabCount); | ||
|
||
// The last slot is reserved for ICodeManager::FixContext(ppEndRegion) | ||
|
@@ -2220,6 +2229,7 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) | |
GetEmitter()->emitIns_S_I(INS_mov, EA_PTRSIZE, compiler->lvaShadowSPslotsVar, (unsigned)curNestingSlotOffs, | ||
0); | ||
break; | ||
} | ||
#endif // FEATURE_EH_WINDOWS_X86 | ||
|
||
case GT_PINVOKE_PROLOG: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -241,7 +241,7 @@ bool EHblkDsc::ebdIsSameTry(BasicBlock* ebdTryBeg, BasicBlock* ebdTryLast) | |
|
||
void EHblkDsc::DispEntry(unsigned XTnum) | ||
{ | ||
printf(" %2u ::", XTnum); | ||
printf(" %2u %2u ::", ebdID, XTnum); | ||
|
||
#if defined(FEATURE_EH_WINDOWS_X86) | ||
if (ebdHandlerNestingLevel == 0) | ||
|
@@ -1258,6 +1258,30 @@ EHblkDsc* Compiler::ehInitTryBlockRange(BasicBlock* blk, BasicBlock** tryBeg, Ba | |
return tryTab; | ||
} | ||
|
||
//------------------------------------------------------------------------ | ||
// ehFindEHblkDscById: find an eh table entry by its ID | ||
// | ||
// Argument: | ||
// ID to use in search | ||
// | ||
// Returns: | ||
// Pointer to the entry, or nullptr | ||
// | ||
EHblkDsc* Compiler::ehFindEHblkDscById(unsigned short id) | ||
{ | ||
EHblkDsc* result = nullptr; | ||
for (EHblkDsc* const xtab : EHClauses(this)) | ||
{ | ||
if (xtab->ebdID == id) | ||
{ | ||
result = xtab; | ||
break; | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/***************************************************************************** | ||
* This method updates the value of ebdTryBeg | ||
*/ | ||
|
@@ -3230,13 +3254,21 @@ void Compiler::fgVerifyHandlerTab() | |
// block (case 3)? | ||
bool multipleLastBlockNormalizationDone = false; // Currently disabled | ||
|
||
BitVecTraits traits(impInlineRoot()->compEHID, this); | ||
BitVec ids(BitVecOps::MakeEmpty(&traits)); | ||
|
||
assert(compHndBBtabCount <= compHndBBtabAllocCount); | ||
|
||
unsigned XTnum; | ||
EHblkDsc* HBtab; | ||
|
||
for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++) | ||
{ | ||
// EH IDs should be unique and in range | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe there should be some validation of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, though maybe in a follow-up? I actually envision bigger changes in the EH representation, but have been holding off. For instance keeping this map live throughout compilation and using it for all targets, as it can simplify some of the other EH maintenance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sounds good. |
||
// | ||
assert(HBtab->ebdID < impInlineRoot()->compEHID); | ||
assert(BitVecOps::TryAddElemD(&traits, ids, HBtab->ebdID)); | ||
|
||
assert(HBtab->ebdTryBeg != nullptr); | ||
assert(HBtab->ebdTryLast != nullptr); | ||
assert(HBtab->ebdHndBeg != nullptr); | ||
|
@@ -3763,7 +3795,7 @@ void Compiler::fgDispHandlerTab() | |
return; | ||
} | ||
|
||
printf("\nindex "); | ||
printf("\n id, index "); | ||
#if defined(FEATURE_EH_WINDOWS_X86) | ||
if (!UsesFunclets()) | ||
{ | ||
|
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.
Consider adding a brief note or reference indicating where the definition and derivation of the EH region ID is provided, to help guide readers unfamiliar with this change.
Copilot uses AI. Check for mistakes.