Skip to content

Commit 229f6ac

Browse files
authored
JIT: introduce durable EH region ID (#113497)
Give each EH clause a permanent ID unique to that clause. Use this to revise how we model GT_END_LFIN's nesting depth -- the node now refers to a region ID, and we use that plus a lookup table built during `FinalizeEH` to figure out the nesting depth. Contributes to #108900.
1 parent 212c79d commit 229f6ac

11 files changed

Lines changed: 107 additions & 37 deletions

File tree

docs/design/coreclr/botr/clr-abi.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ The code this finally returns to looks like this:
452452

453453
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.
454454

455-
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.
455+
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.
456456

457457
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.
458458

@@ -476,7 +476,7 @@ The VM walks the ShadowSP slots in the function `GetHandlerFrameInfo()`, and set
476476

477477
An aside on the JIT implementation for x86.
478478

479-
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'.
479+
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'.
480480

481481
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.
482482

src/coreclr/jit/codegenxarch.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,13 +2200,22 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
22002200

22012201
#if defined(FEATURE_EH_WINDOWS_X86)
22022202
case GT_END_LFIN:
2203+
{
2204+
// Find the eh table entry via the eh ID
2205+
//
2206+
unsigned const ehID = (unsigned)treeNode->AsVal()->gtVal1;
2207+
assert(ehID < compiler->compEHID);
2208+
assert(compiler->m_EHIDtoEHblkDsc != nullptr);
2209+
2210+
EHblkDsc* HBtab = nullptr;
2211+
bool found = compiler->m_EHIDtoEHblkDsc->Lookup(ehID, &HBtab);
2212+
assert(found);
2213+
assert(HBtab != nullptr);
22032214

22042215
// Have to clear the ShadowSP of the nesting level which encloses the finally. Generates:
22052216
// mov dword ptr [ebp-0xC], 0 // for some slot of the ShadowSP local var
2206-
2207-
size_t finallyNesting;
2208-
finallyNesting = treeNode->AsVal()->gtVal1;
2209-
noway_assert(treeNode->AsVal()->gtVal1 < compiler->compHndBBtabCount);
2217+
//
2218+
const size_t finallyNesting = HBtab->ebdHandlerNestingLevel;
22102219
noway_assert(finallyNesting < compiler->compHndBBtabCount);
22112220

22122221
// The last slot is reserved for ICodeManager::FixContext(ppEndRegion)
@@ -2220,6 +2229,7 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
22202229
GetEmitter()->emitIns_S_I(INS_mov, EA_PTRSIZE, compiler->lvaShadowSPslotsVar, (unsigned)curNestingSlotOffs,
22212230
0);
22222231
break;
2232+
}
22232233
#endif // FEATURE_EH_WINDOWS_X86
22242234

22252235
case GT_PINVOKE_PROLOG:

src/coreclr/jit/compiler.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5207,6 +5207,21 @@ void Compiler::FinalizeEH()
52075207
lvaSetVarAddrExposed(lvaShadowSPslotsVar DEBUGARG(AddressExposedReason::EXTERNALLY_VISIBLE_IMPLICITLY));
52085208
}
52095209

5210+
// Build up a mapping from EH IDs to EHblkDsc*
5211+
//
5212+
assert(m_EHIDtoEHblkDsc == nullptr);
5213+
5214+
if (compHndBBtabCount > 0)
5215+
{
5216+
m_EHIDtoEHblkDsc = new (getAllocator()) EHIDtoEHblkDscMap(getAllocator());
5217+
5218+
for (unsigned XTnum = 0; XTnum < compHndBBtabCount; XTnum++)
5219+
{
5220+
EHblkDsc* const HBtab = &compHndBBtab[XTnum];
5221+
m_EHIDtoEHblkDsc->Set(HBtab->ebdID, HBtab);
5222+
}
5223+
}
5224+
52105225
#endif // FEATURE_EH_WINDOWS_X86
52115226

52125227
// We should not make any more alterations to the EH table structure.

src/coreclr/jit/compiler.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2722,8 +2722,12 @@ class Compiler
27222722
// etc.
27232723
unsigned ehMaxHndNestingCount = 0;
27242724

2725+
typedef JitHashTable<unsigned, JitSmallPrimitiveKeyFuncs<unsigned>, EHblkDsc*> EHIDtoEHblkDscMap;
2726+
EHIDtoEHblkDscMap* m_EHIDtoEHblkDsc = nullptr;
2727+
27252728
#endif // FEATURE_EH_WINDOWS_X86
27262729

2730+
EHblkDsc* ehFindEHblkDscById(unsigned short ehID);
27272731
bool ehTableFinalized = false;
27282732
void FinalizeEH();
27292733

@@ -10977,9 +10981,10 @@ class Compiler
1097710981
size_t compInfoBlkSize;
1097810982
BYTE* compInfoBlkAddr;
1097910983

10980-
EHblkDsc* compHndBBtab = nullptr; // array of EH data
10981-
unsigned compHndBBtabCount = 0; // element count of used elements in EH data array
10982-
unsigned compHndBBtabAllocCount = 0; // element count of allocated elements in EH data array
10984+
EHblkDsc* compHndBBtab = nullptr; // array of EH data
10985+
unsigned compHndBBtabCount = 0; // element count of used elements in EH data array
10986+
unsigned compHndBBtabAllocCount = 0; // element count of allocated elements in EH data array
10987+
unsigned short compEHID = 0; // unique ID for EH data array entries
1098310988

1098410989
#if defined(FEATURE_EH_WINDOWS_X86)
1098510990

src/coreclr/jit/fgbasic.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3646,6 +3646,7 @@ void Compiler::fgFindBasicBlocks()
36463646
BADCODE3("end of hnd block beyond end of method for try", " at offset %04X", tryBegOff);
36473647
}
36483648

3649+
HBtab->ebdID = impInlineRoot()->compEHID++;
36493650
HBtab->ebdTryBegOffset = tryBegOff;
36503651
HBtab->ebdTryEndOffset = tryEndOff;
36513652
HBtab->ebdFilterBegOffset = filterBegOff;

src/coreclr/jit/fgehopt.cpp

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,9 @@ PhaseStatus Compiler::fgRemoveEmptyTry()
690690
// Handler index of any nested blocks will update when we
691691
// remove the EH table entry. Change handler exits to jump to
692692
// the continuation. Clear catch type on handler entry.
693-
// Decrement nesting level of enclosed GT_END_LFINs.
693+
//
694+
// GT_END_LFIN no longer need updates here, now their gtVal1 fields refer to EH IDs.
695+
//
694696
for (BasicBlock* const block : Blocks(firstHandlerBlock, lastHandlerBlock))
695697
{
696698
if (block == firstHandlerBlock)
@@ -725,25 +727,6 @@ PhaseStatus Compiler::fgRemoveEmptyTry()
725727
}
726728
}
727729
}
728-
729-
#if defined(FEATURE_EH_WINDOWS_X86)
730-
if (!UsesFunclets())
731-
{
732-
// If we're in a non-funclet model, decrement the nesting
733-
// level of any GT_END_LFIN we find in the handler region,
734-
// since we're removing the enclosing handler.
735-
for (Statement* const stmt : block->Statements())
736-
{
737-
GenTree* expr = stmt->GetRootNode();
738-
if (expr->gtOper == GT_END_LFIN)
739-
{
740-
const size_t nestLevel = expr->AsVal()->gtVal1;
741-
assert(nestLevel > 0);
742-
expr->AsVal()->gtVal1 = nestLevel - 1;
743-
}
744-
}
745-
}
746-
#endif // FEATURE_EH_WINDOWS_X86
747730
}
748731

749732
// (6) Update any impacted ACDs.
@@ -2870,6 +2853,8 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
28702853
compHndBBtab[XTnum] = compHndBBtab[originalXTnum];
28712854
EHblkDsc* const ebd = &compHndBBtab[XTnum];
28722855

2856+
ebd->ebdID = impInlineRoot()->compEHID++;
2857+
28732858
// Note the outermost region enclosing indices stay the same, because the original
28742859
// clause entries got adjusted when we inserted the new clauses.
28752860
//
@@ -3028,6 +3013,22 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
30283013
newBlock->bbRefs++;
30293014
}
30303015
}
3016+
3017+
#if defined(FEATURE_EH_WINDOWS_X86)
3018+
// Update the EH ID for any cloned GT_END_LFIN.
3019+
//
3020+
for (Statement* const stmt : newBlock->Statements())
3021+
{
3022+
GenTree* const rootNode = stmt->GetRootNode();
3023+
if (rootNode->OperIs(GT_END_LFIN))
3024+
{
3025+
GenTreeVal* const endNode = rootNode->AsVal();
3026+
EHblkDsc* const oldEbd = ehFindEHblkDscById((unsigned short)endNode->gtVal1);
3027+
EHblkDsc* const newEbd = oldEbd + indexShift;
3028+
endNode->gtVal1 = newEbd->ebdID;
3029+
}
3030+
}
3031+
#endif
30313032
}
30323033
JITDUMP("Done fixing region indices\n");
30333034

src/coreclr/jit/flowgraph.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,7 @@ void Compiler::fgAddSyncMethodEnterExit()
14631463

14641464
// Initialize the new entry
14651465

1466+
newEntry->ebdID = impInlineRoot()->compEHID++;
14661467
newEntry->ebdHandlerType = EH_HANDLER_FAULT;
14671468

14681469
newEntry->ebdTryBeg = tryBegBB;

src/coreclr/jit/gentree.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12333,7 +12333,7 @@ void Compiler::gtDispLeaf(GenTree* tree, IndentStack* indentStack)
1233312333

1233412334
#if defined(FEATURE_EH_WINDOWS_X86)
1233512335
case GT_END_LFIN:
12336-
printf(" endNstLvl=%d", tree->AsVal()->gtVal1);
12336+
printf(" ehID=%d", tree->AsVal()->gtVal1);
1233712337
break;
1233812338
#endif // FEATURE_EH_WINDOWS_X86
1233912339

src/coreclr/jit/importer.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4747,12 +4747,15 @@ void Compiler::impImportLeaveEHRegions(BasicBlock* block)
47474747
}
47484748
#endif
47494749

4750-
unsigned finallyNesting = compHndBBtab[XTnum].ebdHandlerNestingLevel;
4751-
assert(finallyNesting <= compHndBBtabCount);
4750+
// We now record the EH region ID on GT_END_LFIN instead of the finally nesting depth,
4751+
// as the later can change as we optimize the code.
4752+
//
4753+
unsigned const ehID = compHndBBtab[XTnum].ebdID;
4754+
assert(ehID <= impInlineRoot()->compEHID);
47524755

4753-
GenTree* endLFin = new (this, GT_END_LFIN) GenTreeVal(GT_END_LFIN, TYP_VOID, finallyNesting);
4754-
endLFinStmt = gtNewStmt(endLFin);
4755-
endCatches = NULL;
4756+
GenTree* const endLFin = new (this, GT_END_LFIN) GenTreeVal(GT_END_LFIN, TYP_VOID, ehID);
4757+
endLFinStmt = gtNewStmt(endLFin);
4758+
endCatches = NULL;
47564759

47574760
encFinallies++;
47584761
}

src/coreclr/jit/jiteh.cpp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ bool EHblkDsc::ebdIsSameTry(BasicBlock* ebdTryBeg, BasicBlock* ebdTryLast)
241241

242242
void EHblkDsc::DispEntry(unsigned XTnum)
243243
{
244-
printf(" %2u ::", XTnum);
244+
printf(" %2u %2u ::", ebdID, XTnum);
245245

246246
#if defined(FEATURE_EH_WINDOWS_X86)
247247
if (ebdHandlerNestingLevel == 0)
@@ -1258,6 +1258,30 @@ EHblkDsc* Compiler::ehInitTryBlockRange(BasicBlock* blk, BasicBlock** tryBeg, Ba
12581258
return tryTab;
12591259
}
12601260

1261+
//------------------------------------------------------------------------
1262+
// ehFindEHblkDscById: find an eh table entry by its ID
1263+
//
1264+
// Argument:
1265+
// ID to use in search
1266+
//
1267+
// Returns:
1268+
// Pointer to the entry, or nullptr
1269+
//
1270+
EHblkDsc* Compiler::ehFindEHblkDscById(unsigned short id)
1271+
{
1272+
EHblkDsc* result = nullptr;
1273+
for (EHblkDsc* const xtab : EHClauses(this))
1274+
{
1275+
if (xtab->ebdID == id)
1276+
{
1277+
result = xtab;
1278+
break;
1279+
}
1280+
}
1281+
1282+
return result;
1283+
}
1284+
12611285
/*****************************************************************************
12621286
* This method updates the value of ebdTryBeg
12631287
*/
@@ -3230,13 +3254,21 @@ void Compiler::fgVerifyHandlerTab()
32303254
// block (case 3)?
32313255
bool multipleLastBlockNormalizationDone = false; // Currently disabled
32323256

3257+
BitVecTraits traits(impInlineRoot()->compEHID, this);
3258+
BitVec ids(BitVecOps::MakeEmpty(&traits));
3259+
32333260
assert(compHndBBtabCount <= compHndBBtabAllocCount);
32343261

32353262
unsigned XTnum;
32363263
EHblkDsc* HBtab;
32373264

32383265
for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
32393266
{
3267+
// EH IDs should be unique and in range
3268+
//
3269+
assert(HBtab->ebdID < impInlineRoot()->compEHID);
3270+
assert(BitVecOps::TryAddElemD(&traits, ids, HBtab->ebdID));
3271+
32403272
assert(HBtab->ebdTryBeg != nullptr);
32413273
assert(HBtab->ebdTryLast != nullptr);
32423274
assert(HBtab->ebdHndBeg != nullptr);
@@ -3763,7 +3795,7 @@ void Compiler::fgDispHandlerTab()
37633795
return;
37643796
}
37653797

3766-
printf("\nindex ");
3798+
printf("\n id, index ");
37673799
#if defined(FEATURE_EH_WINDOWS_X86)
37683800
if (!UsesFunclets())
37693801
{

0 commit comments

Comments
 (0)