mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-11 10:18:21 +09:00
JIT: Remove empty try regions (dotnet/coreclr#8949)
In runtimes that do not support thread abort, a try-finally with
an empty try can be optimized to simply the content of the finally.
See documentation update for more details.
Closes dotnet/coreclr#8924.
Commit migrated from 119b04c1d3
This commit is contained in:
parent
9916a38125
commit
ac42a80586
6 changed files with 403 additions and 59 deletions
|
@ -18,8 +18,8 @@ which is almost like a regular function with a prolog and epilog. A
|
|||
custom calling convention gives the funclet access to the parent stack
|
||||
frame.
|
||||
|
||||
In this proposal we outline two optimizations for finallys: removing
|
||||
empty finallys and finally cloning.
|
||||
In this proposal we outline three optimizations for finallys: removing
|
||||
empty trys, removing empty finallys and finally cloning.
|
||||
|
||||
Empty Finally Removal
|
||||
---------------------
|
||||
|
@ -175,6 +175,42 @@ G_M60484_IG06:
|
|||
Empty finally removal is unconditionally profitable: it should always
|
||||
reduce code size and improve code speed.
|
||||
|
||||
Empty Try Removal
|
||||
---------------------
|
||||
|
||||
If the try region of a try-finally is empty, and the jitted code will
|
||||
execute on a runtime that does not protect finally execution from
|
||||
thread abort, then the try-finally can be replaced with just the
|
||||
content of the finally.
|
||||
|
||||
Empty trys with non-empty finallys often exist in code that must run
|
||||
under both thread-abort aware and non-thread-abort aware runtimes. In
|
||||
the former case the placement of cleanup code in the finally ensures
|
||||
that the cleanup code will execute fully. But if thread abort is not
|
||||
possible, the extra protection offered by the finally is not needed.
|
||||
|
||||
Empty try removal looks for try-finallys where the try region does
|
||||
nothing except invoke the finally. There are currently two different
|
||||
EH implementation models, so the try screening has two cases:
|
||||
|
||||
* callfinally thunks (x64/arm64): the try must be a single empty
|
||||
basic block that always jumps to a callfinally that is the first
|
||||
half of a callfinally/always pair;
|
||||
* non-callfinally thunks (x86/arm32): the try must be a
|
||||
callfinally/always pair where the first block is an empty callfinally.
|
||||
|
||||
The screening then verifies that the callfinally identified above is
|
||||
the only callfinally for the try. No other callfinallys are expected
|
||||
because this try cannot have multiple leaves and its handler cannot be
|
||||
reached by nested exit paths.
|
||||
|
||||
When the empty try is identified, the jit modifies the
|
||||
callfinally/always pair to branch to the handler, modifies the
|
||||
handler's return to branch directly to the continuation (the
|
||||
branch target of the second half of the callfinally/always pair),
|
||||
updates various status flags on the blocks, and then removes the
|
||||
try-finally region.
|
||||
|
||||
Finally Cloning
|
||||
---------------
|
||||
|
||||
|
|
|
@ -3495,10 +3495,14 @@ public:
|
|||
|
||||
void fgInline();
|
||||
|
||||
void fgRemoveEmptyTry();
|
||||
|
||||
void fgRemoveEmptyFinally();
|
||||
|
||||
void fgCloneFinally();
|
||||
|
||||
void fgCleanupContinuation(BasicBlock* continuation);
|
||||
|
||||
GenTreePtr fgGetCritSectOfStaticMethod();
|
||||
|
||||
#if !defined(_TARGET_X86_)
|
||||
|
|
|
@ -26,6 +26,7 @@ CompPhaseNameMacro(PHASE_POST_IMPORT, "Post-import",
|
|||
CompPhaseNameMacro(PHASE_MORPH_INIT, "Morph - Init", "MOR-INIT" ,false, -1)
|
||||
CompPhaseNameMacro(PHASE_MORPH_INLINE, "Morph - Inlining", "MOR-INL", false, -1)
|
||||
CompPhaseNameMacro(PHASE_MORPH_IMPBYREF, "Morph - ByRefs", "MOR-BYREF",false, -1)
|
||||
CompPhaseNameMacro(PHASE_EMPTY_TRY, "Remove empty try", "EMPTYTRY", false, -1)
|
||||
CompPhaseNameMacro(PHASE_EMPTY_FINALLY, "Remove empty finally", "EMPTYFIN", false, -1)
|
||||
CompPhaseNameMacro(PHASE_CLONE_FINALLY, "Clone finally", "CLONEFIN", false, -1)
|
||||
CompPhaseNameMacro(PHASE_STR_ADRLCL, "Morph - Structs/AddrExp", "MOR-STRAL",false, -1)
|
||||
|
|
|
@ -22635,33 +22635,8 @@ void Compiler::fgRemoveEmptyFinally()
|
|||
leaveBlock->bbFlags &= ~BBF_KEEP_BBJ_ALWAYS;
|
||||
fgRemoveBlock(leaveBlock, true);
|
||||
|
||||
// The postTryFinallyBlock may be a finalStep block.
|
||||
// It is now a normal block, so clear the special keep
|
||||
// always flag.
|
||||
postTryFinallyBlock->bbFlags &= ~BBF_KEEP_BBJ_ALWAYS;
|
||||
|
||||
#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
|
||||
// Also, clear the finally target bit for arm
|
||||
fgClearFinallyTargetBit(postTryFinallyBlock);
|
||||
#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
|
||||
|
||||
#if !FEATURE_EH_FUNCLETS
|
||||
// Remove the GT_END_LFIN from the post-try-finally block.
|
||||
// remove it since there is no finally anymore. Note we only
|
||||
// expect to see one statement.
|
||||
bool foundEndLFin = false;
|
||||
for (GenTreeStmt* stmt = postTryFinallyBlock->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt)
|
||||
{
|
||||
GenTreePtr expr = stmt->gtStmtExpr;
|
||||
if (expr->gtOper == GT_END_LFIN)
|
||||
{
|
||||
assert(!foundEndLFin);
|
||||
fgRemoveStmt(postTryFinallyBlock, stmt);
|
||||
foundEndLFin = true;
|
||||
}
|
||||
}
|
||||
assert(foundEndLFin);
|
||||
#endif // !FEATURE_EH_FUNCLETS
|
||||
// Cleanup the postTryFinallyBlock
|
||||
fgCleanupContinuation(postTryFinallyBlock);
|
||||
|
||||
// Make sure iteration isn't going off the deep end.
|
||||
assert(leaveBlock != endCallFinallyRangeBlock);
|
||||
|
@ -22745,6 +22720,313 @@ void Compiler::fgRemoveEmptyFinally()
|
|||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// fgRemoveEmptyTry: Optimize try/finallys where the try is empty
|
||||
//
|
||||
// Notes:
|
||||
// In runtimes where thread abort is not possible, `try {} finally {S}`
|
||||
// can be optimized to simply `S`. This method looks for such
|
||||
// cases and removes the try-finally from the EH table, making
|
||||
// suitable flow, block flag, statement, and region updates.
|
||||
//
|
||||
// This optimization is not legal in runtimes that support thread
|
||||
// abort because those runtimes ensure that a finally is completely
|
||||
// executed before continuing to process the thread abort. With
|
||||
// this optimization, the code block `S` can lose special
|
||||
// within-finally status and so complete execution is no longer
|
||||
// guaranteed.
|
||||
|
||||
void Compiler::fgRemoveEmptyTry()
|
||||
{
|
||||
JITDUMP("\n*************** In fgRemoveEmptyTry()\n");
|
||||
|
||||
#ifdef FEATURE_CORECLR
|
||||
bool enableRemoveEmptyTry = true;
|
||||
#else
|
||||
// Code in a finally gets special treatment in the presence of
|
||||
// thread abort.
|
||||
bool enableRemoveEmptyTry = false;
|
||||
#endif // FEATURE_CORECLR
|
||||
|
||||
#ifdef DEBUG
|
||||
// Allow override to enable/disable.
|
||||
enableRemoveEmptyTry = (JitConfig.JitEnableRemoveEmptyTry() == 1);
|
||||
#endif // DEBUG
|
||||
|
||||
if (!enableRemoveEmptyTry)
|
||||
{
|
||||
JITDUMP("Empty try removal disabled.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (compHndBBtabCount == 0)
|
||||
{
|
||||
JITDUMP("No EH in this method, nothing to remove.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.MinOpts())
|
||||
{
|
||||
JITDUMP("Method compiled with minOpts, no removal.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.compDbgCode)
|
||||
{
|
||||
JITDUMP("Method compiled with debug codegen, no removal.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if (verbose)
|
||||
{
|
||||
printf("\n*************** Before fgRemoveEmptyTry()\n");
|
||||
fgDispBasicBlocks();
|
||||
fgDispHandlerTab();
|
||||
printf("\n");
|
||||
}
|
||||
#endif // DEBUG
|
||||
|
||||
// Look for try-finallys where the try is empty.
|
||||
unsigned emptyCount = 0;
|
||||
unsigned XTnum = 0;
|
||||
while (XTnum < compHndBBtabCount)
|
||||
{
|
||||
EHblkDsc* const HBtab = &compHndBBtab[XTnum];
|
||||
|
||||
// Check if this is a try/finally. We could also look for empty
|
||||
// try/fault but presumably those are rare.
|
||||
if (!HBtab->HasFinallyHandler())
|
||||
{
|
||||
JITDUMP("EH#%u is not a try-finally; skipping.\n", XTnum);
|
||||
XTnum++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Examine the try region
|
||||
BasicBlock* const firstTryBlock = HBtab->ebdTryBeg;
|
||||
BasicBlock* const lastTryBlock = HBtab->ebdTryLast;
|
||||
BasicBlock* const firstHandlerBlock = HBtab->ebdHndBeg;
|
||||
BasicBlock* const lastHandlerBlock = HBtab->ebdHndLast;
|
||||
BasicBlock* const endHandlerBlock = lastHandlerBlock->bbNext;
|
||||
|
||||
assert(firstTryBlock->getTryIndex() == XTnum);
|
||||
|
||||
// Limit for now to trys that contain only a callfinally pair
|
||||
// or branch to same.
|
||||
if (!firstTryBlock->isEmpty())
|
||||
{
|
||||
JITDUMP("EH#%u first try block BB%02u not empty; skipping.\n", XTnum, firstTryBlock->bbNum);
|
||||
XTnum++;
|
||||
continue;
|
||||
}
|
||||
|
||||
#if FEATURE_EH_CALLFINALLY_THUNKS
|
||||
|
||||
// Look for blocks that are always jumps to a call finally
|
||||
// pair that targets the finally
|
||||
if (firstTryBlock->bbJumpKind != BBJ_ALWAYS)
|
||||
{
|
||||
JITDUMP("EH#%u first try block BB%02u not jump to a callfinally; skipping.\n", XTnum, firstTryBlock->bbNum);
|
||||
XTnum++;
|
||||
continue;
|
||||
}
|
||||
|
||||
BasicBlock* const callFinally = firstTryBlock->bbJumpDest;
|
||||
|
||||
// Look for call always pair. Note this will also disqualify
|
||||
// empty try removal in cases where the finally doesn't
|
||||
// return.
|
||||
if (!callFinally->isBBCallAlwaysPair() || (callFinally->bbJumpDest != firstHandlerBlock))
|
||||
{
|
||||
JITDUMP("EH#%u first try block BB%02u always jumps but not to a callfinally; skipping.\n", XTnum,
|
||||
firstTryBlock->bbNum);
|
||||
XTnum++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try itself must be a single block.
|
||||
if (firstTryBlock != lastTryBlock)
|
||||
{
|
||||
JITDUMP("EH#%u first try block BB%02u not only block in try; skipping.\n", XTnum,
|
||||
firstTryBlock->bbNext->bbNum);
|
||||
XTnum++;
|
||||
continue;
|
||||
}
|
||||
|
||||
#else
|
||||
// Look for call always pair within the try itself. Note this
|
||||
// will also disqualify empty try removal in cases where the
|
||||
// finally doesn't return.
|
||||
if (!firstTryBlock->isBBCallAlwaysPair() || (firstTryBlock->bbJumpDest != firstHandlerBlock))
|
||||
{
|
||||
JITDUMP("EH#%u first try block BB%02u not a callfinally; skipping.\n", XTnum, firstTryBlock->bbNum);
|
||||
XTnum++;
|
||||
continue;
|
||||
}
|
||||
|
||||
BasicBlock* const callFinally = firstTryBlock;
|
||||
|
||||
// Try must be a callalways pair of blocks.
|
||||
if (firstTryBlock->bbNext != lastTryBlock)
|
||||
{
|
||||
JITDUMP("EH#%u block BB%02u not last block in try; skipping.\n", XTnum, firstTryBlock->bbNext->bbNum);
|
||||
XTnum++;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endif // FEATURE_EH_CALLFINALLY_THUNKS
|
||||
|
||||
JITDUMP("EH#%u has empty try, removing the try region and promoting the finally.\n", XTnum);
|
||||
|
||||
// There should be just one callfinally that invokes this
|
||||
// finally, the one we found above. Verify this.
|
||||
BasicBlock* firstCallFinallyRangeBlock = nullptr;
|
||||
BasicBlock* endCallFinallyRangeBlock = nullptr;
|
||||
bool verifiedSingleCallfinally = true;
|
||||
ehGetCallFinallyBlockRange(XTnum, &firstCallFinallyRangeBlock, &endCallFinallyRangeBlock);
|
||||
|
||||
for (BasicBlock* block = firstCallFinallyRangeBlock; block != endCallFinallyRangeBlock; block = block->bbNext)
|
||||
{
|
||||
if ((block->bbJumpKind == BBJ_CALLFINALLY) && (block->bbJumpDest == firstHandlerBlock))
|
||||
{
|
||||
assert(block->isBBCallAlwaysPair());
|
||||
|
||||
if (block != callFinally)
|
||||
{
|
||||
JITDUMP("EH#%u found unexpected callfinally BB%02u; skipping.\n");
|
||||
verifiedSingleCallfinally = false;
|
||||
break;
|
||||
}
|
||||
|
||||
block = block->bbNext;
|
||||
}
|
||||
}
|
||||
|
||||
if (!verifiedSingleCallfinally)
|
||||
{
|
||||
JITDUMP("EH#%u -- unexpectedly -- has multiple callfinallys; skipping.\n");
|
||||
XTnum++;
|
||||
assert(verifiedSingleCallfinally);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Time to optimize.
|
||||
//
|
||||
// (1) Convert the callfinally to a normal jump to the handler
|
||||
callFinally->bbJumpKind = BBJ_ALWAYS;
|
||||
|
||||
// Identify the leave block and the continuation
|
||||
BasicBlock* const leave = callFinally->bbNext;
|
||||
BasicBlock* const continuation = leave->bbJumpDest;
|
||||
|
||||
// (2) Cleanup the leave so it can be deleted by subsequent opts
|
||||
assert((leave->bbFlags & BBF_KEEP_BBJ_ALWAYS) != 0);
|
||||
leave->bbFlags &= ~BBF_KEEP_BBJ_ALWAYS;
|
||||
|
||||
// (3) Cleanup the continuation
|
||||
fgCleanupContinuation(continuation);
|
||||
|
||||
// (4) Find enclosing try region for the try, if any, and
|
||||
// update the try region for the blocks in the try. Note the
|
||||
// handler region (if any) won't change.
|
||||
//
|
||||
// Kind of overkill to loop here, but hey.
|
||||
for (BasicBlock* block = firstTryBlock; block != nullptr; block = block->bbNext)
|
||||
{
|
||||
// Look for blocks directly contained in this try, and
|
||||
// update the try region appropriately.
|
||||
//
|
||||
// The try region for blocks transitively contained (say in a
|
||||
// child try) will get updated by the subsequent call to
|
||||
// fgRemoveEHTableEntry.
|
||||
if (block->getTryIndex() == XTnum)
|
||||
{
|
||||
if (firstHandlerBlock->hasTryIndex())
|
||||
{
|
||||
block->setTryIndex(firstHandlerBlock->getTryIndex());
|
||||
}
|
||||
else
|
||||
{
|
||||
block->clearTryIndex();
|
||||
}
|
||||
}
|
||||
|
||||
if (block == firstTryBlock)
|
||||
{
|
||||
assert((block->bbFlags & BBF_TRY_BEG) != 0);
|
||||
block->bbFlags &= ~BBF_TRY_BEG;
|
||||
}
|
||||
|
||||
if (block == lastTryBlock)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// (5) Update the directly contained handler blocks' handler index.
|
||||
// Handler index of any nested blocks will update when we
|
||||
// remove the EH table entry. Change handler exits to jump to
|
||||
// the continuation. Clear catch type on handler entry.
|
||||
for (BasicBlock* block = firstHandlerBlock; block != endHandlerBlock; block = block->bbNext)
|
||||
{
|
||||
if (block == firstHandlerBlock)
|
||||
{
|
||||
block->bbCatchTyp = BBCT_NONE;
|
||||
}
|
||||
|
||||
if (block->getHndIndex() == XTnum)
|
||||
{
|
||||
if (firstTryBlock->hasHndIndex())
|
||||
{
|
||||
block->setHndIndex(firstTryBlock->getHndIndex());
|
||||
}
|
||||
else
|
||||
{
|
||||
block->clearHndIndex();
|
||||
}
|
||||
|
||||
if (block->bbJumpKind == BBJ_EHFINALLYRET)
|
||||
{
|
||||
GenTreeStmt* finallyRet = block->lastStmt();
|
||||
GenTreePtr finallyRetExpr = finallyRet->gtStmtExpr;
|
||||
assert(finallyRetExpr->gtOper == GT_RETFILT);
|
||||
fgRemoveStmt(block, finallyRet);
|
||||
block->bbJumpKind = BBJ_ALWAYS;
|
||||
block->bbJumpDest = continuation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (6) Remove the try-finally EH region. This will compact the
|
||||
// EH table so XTnum now points at the next entry and will update
|
||||
// the EH region indices of any nested EH in the (former) handler.
|
||||
fgRemoveEHTableEntry(XTnum);
|
||||
|
||||
// Another one bites the dust...
|
||||
emptyCount++;
|
||||
}
|
||||
|
||||
if (emptyCount > 0)
|
||||
{
|
||||
JITDUMP("fgRemoveEmptyTry() optimized %u empty-try try-finally clauses\n", emptyCount);
|
||||
|
||||
#ifdef DEBUG
|
||||
if (verbose)
|
||||
{
|
||||
printf("\n*************** After fgRemoveEmptyTry()\n");
|
||||
fgDispBasicBlocks();
|
||||
fgDispHandlerTab();
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
fgVerifyHandlerTab();
|
||||
fgDebugCheckBBlist(false, false);
|
||||
|
||||
#endif // DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// fgCloneFinally: Optimize normal exit path from a try/finally
|
||||
//
|
||||
|
@ -22776,7 +23058,7 @@ void Compiler::fgCloneFinally()
|
|||
{
|
||||
JITDUMP("\n*************** In fgCloneFinally()\n");
|
||||
|
||||
#if FEATURE_CORECLR
|
||||
#ifdef FEATURE_CORECLR
|
||||
bool enableCloning = true;
|
||||
#else
|
||||
// Finally cloning currently doesn't provide sufficient protection
|
||||
|
@ -22784,7 +23066,7 @@ void Compiler::fgCloneFinally()
|
|||
bool enableCloning = false;
|
||||
#endif // FEATURE_CORECLR
|
||||
|
||||
#if DEBUG
|
||||
#ifdef DEBUG
|
||||
// Allow override to enable/disable.
|
||||
enableCloning = (JitConfig.JitEnableFinallyCloning() == 1);
|
||||
#endif // DEBUG
|
||||
|
@ -23280,34 +23562,8 @@ void Compiler::fgCloneFinally()
|
|||
BasicBlock* firstClonedBlock = blockMap[firstBlock];
|
||||
firstClonedBlock->bbCatchTyp = BBCT_NONE;
|
||||
|
||||
// The normalCallFinallyReturn may be a finalStep block. It
|
||||
// is now a normal block, since all the callfinallies that
|
||||
// return to it are now going via the clone, so clear the
|
||||
// special keep always flag.
|
||||
normalCallFinallyReturn->bbFlags &= ~BBF_KEEP_BBJ_ALWAYS;
|
||||
|
||||
#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
|
||||
// Also, clear the finally target bit for arm
|
||||
fgClearFinallyTargetBit(normalCallFinallyReturn);
|
||||
#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
|
||||
|
||||
#if !FEATURE_EH_FUNCLETS
|
||||
// Remove the GT_END_LFIN from the post-try-finally block.
|
||||
// remove it since there is no finally anymore. Note we only
|
||||
// expect to see one statement.
|
||||
bool foundEndLFin = false;
|
||||
for (GenTreeStmt* stmt = normalCallFinallyReturn->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt)
|
||||
{
|
||||
GenTreePtr expr = stmt->gtStmtExpr;
|
||||
if (expr->gtOper == GT_END_LFIN)
|
||||
{
|
||||
assert(!foundEndLFin);
|
||||
fgRemoveStmt(normalCallFinallyReturn, stmt);
|
||||
foundEndLFin = true;
|
||||
}
|
||||
}
|
||||
assert(foundEndLFin);
|
||||
#endif
|
||||
// Cleanup the contination
|
||||
fgCleanupContinuation(normalCallFinallyReturn);
|
||||
|
||||
// Todo -- mark cloned blocks as a cloned finally....
|
||||
|
||||
|
@ -23530,3 +23786,44 @@ void Compiler::fgDebugCheckTryFinallyExits()
|
|||
}
|
||||
|
||||
#endif // DEBUG
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// fgCleanupContinuation: cleanup a finally continuation after a
|
||||
// finally is removed or converted to normal control flow.
|
||||
//
|
||||
// Notes:
|
||||
// The continuation is the block targeted by the second half of
|
||||
// a callfinally/always pair.
|
||||
//
|
||||
// Used by finally cloning, empty try removal, and empty
|
||||
// finally removal. Resets flags and removes finally artifacts.
|
||||
|
||||
void Compiler::fgCleanupContinuation(BasicBlock* continuation)
|
||||
{
|
||||
// The continuation may be a finalStep block.
|
||||
// It is now a normal block, so clear the special keep
|
||||
// always flag.
|
||||
continuation->bbFlags &= ~BBF_KEEP_BBJ_ALWAYS;
|
||||
|
||||
#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
|
||||
// Also, clear the finally target bit for arm
|
||||
fgClearFinallyTargetBit(continuation);
|
||||
#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
|
||||
|
||||
#if !FEATURE_EH_FUNCLETS
|
||||
// Remove the GT_END_LFIN from the continuation,
|
||||
// Note we only expect to see one such statement.
|
||||
bool foundEndLFin = false;
|
||||
for (GenTreeStmt* stmt = continuation->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt)
|
||||
{
|
||||
GenTreePtr expr = stmt->gtStmtExpr;
|
||||
if (expr->gtOper == GT_END_LFIN)
|
||||
{
|
||||
assert(!foundEndLFin);
|
||||
fgRemoveStmt(continuation, stmt);
|
||||
foundEndLFin = true;
|
||||
}
|
||||
}
|
||||
assert(foundEndLFin);
|
||||
#endif // !FEATURE_EH_FUNCLETS
|
||||
}
|
||||
|
|
|
@ -277,8 +277,10 @@ CONFIG_INTEGER(JitEECallTimingInfo, W("JitEECallTimingInfo"), 0)
|
|||
#if defined(DEBUG)
|
||||
#if defined(FEATURE_CORECLR)
|
||||
CONFIG_INTEGER(JitEnableFinallyCloning, W("JitEnableFinallyCloning"), 1)
|
||||
CONFIG_INTEGER(JitEnableRemoveEmptyTry, W("JitEnableRemoveEmptyTry"), 1)
|
||||
#else
|
||||
CONFIG_INTEGER(JitEnableFinallyCloning, W("JitEnableFinallyCloning"), 0)
|
||||
CONFIG_INTEGER(JitEnableRemoveEmptyTry, W("JitEnableRemoveEmptyTry"), 0)
|
||||
#endif // defined(FEATURE_CORECLR)
|
||||
#endif // DEBUG
|
||||
|
||||
|
|
|
@ -16917,6 +16917,10 @@ void Compiler::fgMorph()
|
|||
|
||||
// RemoveEmptyFinally is disabled on ARM due to github issue #9013
|
||||
#ifndef _TARGET_ARM_
|
||||
fgRemoveEmptyTry();
|
||||
|
||||
EndPhase(PHASE_EMPTY_TRY);
|
||||
|
||||
fgRemoveEmptyFinally();
|
||||
|
||||
EndPhase(PHASE_EMPTY_FINALLY);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue