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);
}
//------------------------------------------------------------------------
+// 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
//
// Notes:
{
JITDUMP("\n*************** In fgCloneFinally()\n");
-#if FEATURE_CORECLR
+#ifdef FEATURE_CORECLR
bool enableCloning = true;
#else
// Finally cloning currently doesn't provide sufficient protection
bool enableCloning = false;
#endif // FEATURE_CORECLR
-#if DEBUG
+#ifdef DEBUG
// Allow override to enable/disable.
enableCloning = (JitConfig.JitEnableFinallyCloning() == 1);
#endif // DEBUG
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....
}
#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
+}