diff options
Diffstat (limited to 'src/jit/flowgraph.cpp')
-rw-r--r-- | src/jit/flowgraph.cpp | 1797 |
1 files changed, 1791 insertions, 6 deletions
diff --git a/src/jit/flowgraph.cpp b/src/jit/flowgraph.cpp index 441569c339..50318b0940 100644 --- a/src/jit/flowgraph.cpp +++ b/src/jit/flowgraph.cpp @@ -8550,8 +8550,12 @@ void Compiler::fgAddInternal() GenTreeStmt* Compiler::fgNewStmtFromTree(GenTreePtr tree, BasicBlock* block, IL_OFFSETX offs) { GenTreeStmt* stmt = gtNewStmt(tree, offs); - gtSetStmtInfo(stmt); - fgSetStmtSeq(stmt); + + if (fgStmtListThreaded) + { + gtSetStmtInfo(stmt); + fgSetStmtSeq(stmt); + } #if DEBUG if (block != nullptr) @@ -11654,6 +11658,7 @@ DONE: void Compiler::fgClearFinallyTargetBit(BasicBlock* block) { + assert(fgComputePredsDone); assert((block->bbFlags & BBF_FINALLY_TARGET) != 0); for (flowList* pred = block->bbPreds; pred; pred = pred->flNext) @@ -12946,6 +12951,12 @@ bool Compiler::fgOptimizeBranchToEmptyUnconditional(BasicBlock* block, BasicBloc optimizeJump = false; } + // Don't optimize a jump to a cloned finally + if (bDest->bbFlags & BBF_CLONED_FINALLY_BEGIN) + { + optimizeJump = false; + } + #if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_) // Don't optimize a jump to a finally target. For BB1->BB2->BB3, where // BB2 is a finally target, if we changed BB1 to jump directly to BB3, @@ -13747,7 +13758,7 @@ bool Compiler::fgOptimizeBranchToNext(BasicBlock* block, BasicBlock* bNext, Basi { assert(block->bbJumpKind == BBJ_COND || block->bbJumpKind == BBJ_ALWAYS); assert(block->bbJumpDest == bNext); - assert(block->bbNext = bNext); + assert(block->bbNext == bNext); assert(block->bbPrev == bPrev); if (block->bbJumpKind == BBJ_ALWAYS) @@ -17782,7 +17793,7 @@ void Compiler::fgSetTreeSeqHelper(GenTreePtr tree, bool isLIR) if (kind & GTK_SMPOP) { GenTreePtr op1 = tree->gtOp.gtOp1; - GenTreePtr op2 = tree->gtGetOp2(); + GenTreePtr op2 = tree->gtGetOp2IfPresent(); // Special handling for GT_LIST if (tree->OperGet() == GT_LIST) @@ -18004,8 +18015,8 @@ void Compiler::fgSetTreeSeqHelper(GenTreePtr tree, bool isLIR) case GT_SIMD_CHK: #endif // FEATURE_SIMD // Evaluate the trees left to right - fgSetTreeSeqHelper(tree->gtBoundsChk.gtArrLen, isLIR); fgSetTreeSeqHelper(tree->gtBoundsChk.gtIndex, isLIR); + fgSetTreeSeqHelper(tree->gtBoundsChk.gtArrLen, isLIR); break; case GT_STORE_DYN_BLK: @@ -20318,7 +20329,7 @@ void Compiler::fgDebugCheckFlags(GenTreePtr tree) else if (kind & GTK_SMPOP) { GenTreePtr op1 = tree->gtOp.gtOp1; - GenTreePtr op2 = tree->gtGetOp2(); + GenTreePtr op2 = tree->gtGetOp2IfPresent(); // During GS work, we make shadow copies for params. // In gsParamsToShadows(), we create a shadow var of TYP_INT for every small type param. @@ -21970,6 +21981,13 @@ _Done: compNeedsGSSecurityCookie |= InlineeCompiler->compNeedsGSSecurityCookie; compGSReorderStackLayout |= InlineeCompiler->compGSReorderStackLayout; +#ifdef FEATURE_SIMD + if (InlineeCompiler->usesSIMDTypes()) + { + setUsesSIMDTypes(true); + } +#endif // FEATURE_SIMD + // Update unmanaged call count info.compCallUnmanaged += InlineeCompiler->info.compCallUnmanaged; @@ -22471,3 +22489,1770 @@ void Compiler::fgLclFldAssign(unsigned lclNum) lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_LocalField)); } } + +//------------------------------------------------------------------------ +// fgRemoveEmptyFinally: Remove try/finallys where the finally is empty +// +// Notes: +// Removes all try/finallys in the method with empty finallys. +// These typically arise from inlining empty Dispose methods. +// +// Converts callfinally to a jump to the finally continuation. +// Removes the finally, and reparents all blocks in the try to the +// enclosing try or method region. +// +// Currently limited to trivially empty finallys: those with one basic +// block containing only single RETFILT statement. It is possible but +// not likely that more complex-looking finallys will eventually become +// empty (from say subsequent optimization). An SPMI run with +// just the "detection" part of this phase run after optimization +// found only one example where a new empty finally was detected. + +void Compiler::fgRemoveEmptyFinally() +{ + JITDUMP("\n*************** In fgRemoveEmptyFinally()\n"); + + 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 fgRemoveEmptyFinally()\n"); + fgDispBasicBlocks(); + fgDispHandlerTab(); + printf("\n"); + } +#endif // DEBUG + + // Look for finallys or faults that are empty. + unsigned finallyCount = 0; + 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; + } + + finallyCount++; + + // Look at blocks involved. + BasicBlock* const firstBlock = HBtab->ebdHndBeg; + BasicBlock* const lastBlock = HBtab->ebdHndLast; + + // Limit for now to finallys that are single blocks. + if (firstBlock != lastBlock) + { + JITDUMP("EH#%u finally has multiple basic blocks; skipping.\n", XTnum); + XTnum++; + continue; + } + + // Limit for now to finallys that contain only a GT_RETFILT. + bool isEmpty = true; + + for (GenTreeStmt* stmt = firstBlock->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt) + { + GenTreePtr stmtExpr = stmt->gtStmtExpr; + + if (stmtExpr->gtOper != GT_RETFILT) + { + isEmpty = false; + break; + } + } + + if (!isEmpty) + { + JITDUMP("EH#%u finally is not empty; skipping.\n", XTnum); + XTnum++; + continue; + } + + JITDUMP("EH#%u has empty finally, removing the region.\n", XTnum); + + // Find all the call finallys that invoke this finally, + // and modify them to jump to the return point. + BasicBlock* firstCallFinallyRangeBlock = nullptr; + BasicBlock* endCallFinallyRangeBlock = nullptr; + ehGetCallFinallyBlockRange(XTnum, &firstCallFinallyRangeBlock, &endCallFinallyRangeBlock); + + BasicBlock* currentBlock = firstCallFinallyRangeBlock; + + while (currentBlock != endCallFinallyRangeBlock) + { + BasicBlock* nextBlock = currentBlock->bbNext; + + if ((currentBlock->bbJumpKind == BBJ_CALLFINALLY) && (currentBlock->bbJumpDest == firstBlock)) + { + // Retarget the call finally to jump to the return + // point. + // + // We don't expect to see retless finallys here, since + // the finally is empty. + noway_assert(currentBlock->isBBCallAlwaysPair()); + + BasicBlock* const leaveBlock = currentBlock->bbNext; + BasicBlock* const postTryFinallyBlock = leaveBlock->bbJumpDest; + + noway_assert(leaveBlock->bbJumpKind == BBJ_ALWAYS); + + currentBlock->bbJumpDest = postTryFinallyBlock; + currentBlock->bbJumpKind = BBJ_ALWAYS; + + // Ref count updates. + fgAddRefPred(postTryFinallyBlock, currentBlock); + // fgRemoveRefPred(firstBlock, currentBlock); + + // Delete the leave block, which should be marked as + // keep always. + assert((leaveBlock->bbFlags & BBF_KEEP_BBJ_ALWAYS) != 0); + nextBlock = leaveBlock->bbNext; + + leaveBlock->bbFlags &= ~BBF_KEEP_BBJ_ALWAYS; + fgRemoveBlock(leaveBlock, true); + + // Cleanup the postTryFinallyBlock + fgCleanupContinuation(postTryFinallyBlock); + + // Make sure iteration isn't going off the deep end. + assert(leaveBlock != endCallFinallyRangeBlock); + } + + currentBlock = nextBlock; + } + + // Handler block should now be unreferenced, since the only + // explicit references to it were in call finallys. + firstBlock->bbRefs = 0; + + // Remove the handler block. + const bool unreachable = true; + firstBlock->bbFlags &= ~BBF_DONT_REMOVE; + fgRemoveBlock(firstBlock, unreachable); + + // Find enclosing try region for the try, if any, and update + // the try region. Note the handler region (if any) won't + // change. + BasicBlock* const firstTryBlock = HBtab->ebdTryBeg; + BasicBlock* const lastTryBlock = HBtab->ebdTryLast; + assert(firstTryBlock->getTryIndex() == XTnum); + + for (BasicBlock* block = firstTryBlock; block != nullptr; block = block->bbNext) + { + // Look for blocks directly contained in this try, and + // update the try region appropriately. + // + // 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 (firstBlock->hasTryIndex()) + { + block->setTryIndex(firstBlock->getTryIndex()); + } + else + { + block->clearTryIndex(); + } + } + + if (block == firstTryBlock) + { + assert((block->bbFlags & BBF_TRY_BEG) != 0); + block->bbFlags &= ~BBF_TRY_BEG; + } + + if (block == lastTryBlock) + { + break; + } + } + + // Remove the try-finally EH region. This will compact the EH table + // so XTnum now points at the next entry. + fgRemoveEHTableEntry(XTnum); + + emptyCount++; + } + + if (emptyCount > 0) + { + JITDUMP("fgRemoveEmptyFinally() removed %u try-finally clauses from %u finallys\n", emptyCount, finallyCount); + fgOptimizedFinally = true; + +#ifdef DEBUG + if (verbose) + { + printf("\n*************** After fgRemoveEmptyFinally()\n"); + fgDispBasicBlocks(); + fgDispHandlerTab(); + printf("\n"); + } + + fgVerifyHandlerTab(); + fgDebugCheckBBlist(false, false); + +#endif // DEBUG + } +} + +//------------------------------------------------------------------------ +// 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); + fgOptimizedFinally = true; + +#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: +// Handles finallys that are not enclosed by or enclosing other +// handler regions. +// +// Converts the "normal exit" callfinally to a jump to a cloned copy +// of the finally, which in turn jumps to the finally continuation. +// +// If all callfinallys for a given finally are converted to jump to +// the clone, the try-finally is modified into a try-fault, +// distingushable from organic try-faults by handler type +// EH_HANDLER_FAULT_WAS_FINALLY vs the organic EH_HANDLER_FAULT. +// +// Does not yet handle thread abort. The open issues here are how +// to maintain the proper description of the cloned finally blocks +// as a handler (for thread abort purposes), how to prevent code +// motion in or out of these blocks, and how to report this cloned +// handler to the runtime. Some building blocks for thread abort +// exist (see below) but more work needed. +// +// The first and last blocks of the cloned finally are marked with +// BBF_CLONED_FINALLY_BEGIN and BBF_CLONED_FINALLY_END. However +// these markers currently can get lost during subsequent +// optimizations. + +void Compiler::fgCloneFinally() +{ + JITDUMP("\n*************** In fgCloneFinally()\n"); + +#ifdef FEATURE_CORECLR + bool enableCloning = true; +#else + // Finally cloning currently doesn't provide sufficient protection + // for the cloned code in the presence of thread abort. + bool enableCloning = false; +#endif // FEATURE_CORECLR + +#ifdef DEBUG + // Allow override to enable/disable. + enableCloning = (JitConfig.JitEnableFinallyCloning() == 1); +#endif // DEBUG + + if (!enableCloning) + { + JITDUMP("Finally cloning disabled.\n"); + return; + } + + if (compHndBBtabCount == 0) + { + JITDUMP("No EH in this method, no cloning.\n"); + return; + } + + if (opts.MinOpts()) + { + JITDUMP("Method compiled with minOpts, no cloning.\n"); + return; + } + + if (opts.compDbgCode) + { + JITDUMP("Method compiled with debug codegen, no cloning.\n"); + return; + } + +#ifdef DEBUG + if (verbose) + { + printf("\n*************** Before fgCloneFinally()\n"); + fgDispBasicBlocks(); + fgDispHandlerTab(); + printf("\n"); + } + + // Verify try-finally exits look good before we start. + fgDebugCheckTryFinallyExits(); + +#endif // DEBUG + + // Look for finallys that are not contained within other handlers, + // and which do not themselves contain EH. + // + // Note these cases potentially could be handled, but are less + // obviously profitable and require modification of the handler + // table. + unsigned XTnum = 0; + EHblkDsc* HBtab = compHndBBtab; + unsigned cloneCount = 0; + for (; XTnum < compHndBBtabCount; XTnum++, HBtab++) + { + // Check if this is a try/finally + if (!HBtab->HasFinallyHandler()) + { + JITDUMP("EH#%u is not a try-finally; skipping.\n", XTnum); + continue; + } + + // Check if enclosed by another handler. + const unsigned enclosingHandlerRegion = ehGetEnclosingHndIndex(XTnum); + + if (enclosingHandlerRegion != EHblkDsc::NO_ENCLOSING_INDEX) + { + JITDUMP("EH#%u is enclosed by handler EH#%u; skipping.\n", XTnum, enclosingHandlerRegion); + continue; + } + + bool containsEH = false; + unsigned exampleEnclosedHandlerRegion = 0; + + // Only need to look at lower numbered regions because the + // handler table is ordered by nesting. + for (unsigned i = 0; i < XTnum; i++) + { + if (ehGetEnclosingHndIndex(i) == XTnum) + { + exampleEnclosedHandlerRegion = i; + containsEH = true; + break; + } + } + + if (containsEH) + { + JITDUMP("Finally for EH#%u encloses handler EH#%u; skipping.\n", XTnum, exampleEnclosedHandlerRegion); + continue; + } + + // Look at blocks involved. + BasicBlock* const firstBlock = HBtab->ebdHndBeg; + BasicBlock* const lastBlock = HBtab->ebdHndLast; + assert(firstBlock != nullptr); + assert(lastBlock != nullptr); + BasicBlock* nextBlock = lastBlock->bbNext; + unsigned regionBBCount = 0; + unsigned regionStmtCount = 0; + bool hasFinallyRet = false; + bool isAllRare = true; + bool hasSwitch = false; + + for (const BasicBlock* block = firstBlock; block != nextBlock; block = block->bbNext) + { + if (block->bbJumpKind == BBJ_SWITCH) + { + hasSwitch = true; + break; + } + + regionBBCount++; + + // Should we compute statement cost here, or is it + // premature...? For now just count statements I guess. + for (GenTreeStmt* stmt = block->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt) + { + regionStmtCount++; + } + + hasFinallyRet = hasFinallyRet || (block->bbJumpKind == BBJ_EHFINALLYRET); + isAllRare = isAllRare && block->isRunRarely(); + } + + // Skip cloning if the finally has a switch. + if (hasSwitch) + { + JITDUMP("Finally in EH#%u has a switch; skipping.\n", XTnum); + continue; + } + + // Skip cloning if the finally must throw. + if (!hasFinallyRet) + { + JITDUMP("Finally in EH#%u does not return; skipping.\n", XTnum); + continue; + } + + // Skip cloning if the finally is rarely run code. + if (isAllRare) + { + JITDUMP("Finally in EH#%u is run rarely; skipping.\n", XTnum); + continue; + } + + // Empirical studies from CoreCLR and CoreFX show that less + // that 1% of finally regions have more than 15 + // statements. So, to avoid potentially excessive code growth, + // only clone finallys that have 15 or fewer statements. + const unsigned stmtCountLimit = 15; + if (regionStmtCount > stmtCountLimit) + { + JITDUMP("Finally in EH#%u has %u statements, limit is %u; skipping.\n", XTnum, regionStmtCount, + stmtCountLimit); + continue; + } + + JITDUMP("EH#%u is a candidate for finally cloning:" + " %u blocks, %u statements\n", + XTnum, regionBBCount, regionStmtCount); + + // Walk the try region backwards looking for the last block + // that transfers control to a callfinally. + BasicBlock* const firstTryBlock = HBtab->ebdTryBeg; + BasicBlock* const lastTryBlock = HBtab->ebdTryLast; + assert(firstTryBlock->getTryIndex() == XTnum); + assert(lastTryBlock->getTryIndex() == XTnum); + BasicBlock* const beforeTryBlock = firstTryBlock->bbPrev; + + BasicBlock* normalCallFinallyBlock = nullptr; + BasicBlock* normalCallFinallyReturn = nullptr; + BasicBlock* cloneInsertAfter = HBtab->ebdTryLast; + bool tryToRelocateCallFinally = false; + + for (BasicBlock* block = lastTryBlock; block != beforeTryBlock; block = block->bbPrev) + { +#if FEATURE_EH_CALLFINALLY_THUNKS + // Look for blocks that are always jumps to a call finally + // pair that targets our finally. + if (block->bbJumpKind != BBJ_ALWAYS) + { + continue; + } + + BasicBlock* const jumpDest = block->bbJumpDest; + + if (!jumpDest->isBBCallAlwaysPair() || (jumpDest->bbJumpDest != firstBlock)) + { + continue; + } +#else + // Look for call finally pair directly within the try + if (!block->isBBCallAlwaysPair() || (block->bbJumpDest != firstBlock)) + { + continue; + } + + BasicBlock* const jumpDest = block; +#endif // FEATURE_EH_CALLFINALLY_THUNKS + + // Found our block. + BasicBlock* const finallyReturnBlock = jumpDest->bbNext; + BasicBlock* const postTryFinallyBlock = finallyReturnBlock->bbJumpDest; + + normalCallFinallyBlock = jumpDest; + normalCallFinallyReturn = postTryFinallyBlock; + +#if FEATURE_EH_CALLFINALLY_THUNKS + // When there are callfinally thunks, we don't expect to see the + // callfinally within a handler region either. + assert(!jumpDest->hasHndIndex()); + + // Update the clone insertion point to just after the + // call always pair. + cloneInsertAfter = finallyReturnBlock; + + // We will consider moving the callfinally so we can fall + // through from the try into the clone. + tryToRelocateCallFinally = true; + + JITDUMP("Chose path to clone: try block BB%02u jumps to callfinally at BB%02u;" + " the call returns to BB%02u which jumps to BB%02u\n", + block->bbNum, jumpDest->bbNum, finallyReturnBlock->bbNum, postTryFinallyBlock->bbNum); +#else + JITDUMP("Chose path to clone: try block BB%02u is a callfinally;" + " the call returns to BB%02u which jumps to BB%02u\n", + block->bbNum, finallyReturnBlock->bbNum, postTryFinallyBlock->bbNum); +#endif // FEATURE_EH_CALLFINALLY_THUNKS + + break; + } + + // If there is no call to the finally, don't clone. + if (normalCallFinallyBlock == nullptr) + { + JITDUMP("EH#%u: no calls from the try to the finally, skipping.\n", XTnum); + continue; + } + + JITDUMP("Will update callfinally block BB%02u to jump to the clone;" + " clone will jump to BB%02u\n", + normalCallFinallyBlock->bbNum, normalCallFinallyReturn->bbNum); + + // If there are multiple callfinallys and we're in the + // callfinally thunk model, all the callfinallys are placed + // just outside the try region. We'd like our chosen + // callfinally to come first after the try, so we can fall out of the try + // into the clone. + BasicBlock* firstCallFinallyRangeBlock = nullptr; + BasicBlock* endCallFinallyRangeBlock = nullptr; + ehGetCallFinallyBlockRange(XTnum, &firstCallFinallyRangeBlock, &endCallFinallyRangeBlock); + + if (tryToRelocateCallFinally) + { + BasicBlock* firstCallFinallyBlock = nullptr; + + for (BasicBlock* block = firstCallFinallyRangeBlock; block != endCallFinallyRangeBlock; + block = block->bbNext) + { + if (block->isBBCallAlwaysPair()) + { + if (block->bbJumpDest == firstBlock) + { + firstCallFinallyBlock = block; + break; + } + } + } + + // We better have found at least one call finally. + assert(firstCallFinallyBlock != nullptr); + + // If there is more than one callfinally, move the one we are + // going to retarget to be first in the callfinally range. + if (firstCallFinallyBlock != normalCallFinallyBlock) + { + JITDUMP("Moving callfinally BB%02u to be first in line, before BB%02u\n", normalCallFinallyBlock->bbNum, + firstCallFinallyBlock->bbNum); + + BasicBlock* const firstToMove = normalCallFinallyBlock; + BasicBlock* const lastToMove = normalCallFinallyBlock->bbNext; + BasicBlock* const placeToMoveAfter = firstCallFinallyBlock->bbPrev; + + fgUnlinkRange(firstToMove, lastToMove); + fgMoveBlocksAfter(firstToMove, lastToMove, placeToMoveAfter); + +#ifdef DEBUG + // Sanity checks + fgDebugCheckBBlist(false, false); + fgVerifyHandlerTab(); +#endif // DEBUG + + assert(nextBlock == lastBlock->bbNext); + + // Update where the callfinally range begins, since we might + // have altered this with callfinally rearrangement, and/or + // the range begin might have been pretty loose to begin with. + firstCallFinallyRangeBlock = normalCallFinallyBlock; + } + } + + // Clone the finally and retarget the normal return path and + // any other path that happens to share that same return + // point. For instance a construct like: + // + // try { } catch { } finally { } + // + // will have two call finally blocks, one for the normal exit + // from the try, and the the other for the exit from the + // catch. They'll both pass the same return point which is the + // statement after the finally, so they can share the clone. + // + // Clone the finally body, and splice it into the flow graph + // within in the parent region of the try. + const unsigned finallyTryIndex = firstBlock->bbTryIndex; + BasicBlock* insertAfter = nullptr; + BlockToBlockMap blockMap(getAllocator()); + bool clonedOk = true; + unsigned cloneBBCount = 0; + + for (BasicBlock* block = firstBlock; block != nextBlock; block = block->bbNext) + { + BasicBlock* newBlock; + + if (block == firstBlock) + { + // Put first cloned finally block into the approprate + // region, somewhere within or after the range of + // callfinallys, depending on the EH implementation. + const unsigned hndIndex = 0; + BasicBlock* const nearBlk = cloneInsertAfter; + newBlock = fgNewBBinRegion(block->bbJumpKind, finallyTryIndex, hndIndex, nearBlk); + + // If the clone ends up just after the finally, adjust + // the stopping point for finally traversal. + if (newBlock->bbNext == nextBlock) + { + assert(newBlock->bbPrev == lastBlock); + nextBlock = newBlock; + } + } + else + { + // Put subsequent blocks in the same region... + const bool extendRegion = true; + newBlock = fgNewBBafter(block->bbJumpKind, insertAfter, extendRegion); + } + + cloneBBCount++; + assert(cloneBBCount <= regionBBCount); + + insertAfter = newBlock; + blockMap.Set(block, newBlock); + + clonedOk = BasicBlock::CloneBlockState(this, newBlock, block); + + if (!clonedOk) + { + break; + } + + // Update block flags. Note a block can be both first and last. + if (block == firstBlock) + { + // Mark the block as the start of the cloned finally. + newBlock->bbFlags |= BBF_CLONED_FINALLY_BEGIN; + } + + if (block == lastBlock) + { + // Mark the block as the end of the cloned finally. + newBlock->bbFlags |= BBF_CLONED_FINALLY_END; + } + + // Make sure clone block state hasn't munged the try region. + assert(newBlock->bbTryIndex == finallyTryIndex); + + // Cloned handler block is no longer within the handler. + newBlock->clearHndIndex(); + + // Jump dests are set in a post-pass; make sure CloneBlockState hasn't tried to set them. + assert(newBlock->bbJumpDest == nullptr); + } + + if (!clonedOk) + { + // TODO: cleanup the partial clone? + JITDUMP("Unable to clone the finally; skipping.\n"); + continue; + } + + // We should have cloned all the finally region blocks. + assert(cloneBBCount == regionBBCount); + + JITDUMP("Cloned finally blocks are: BB%2u ... BB%2u\n", blockMap[firstBlock]->bbNum, + blockMap[lastBlock]->bbNum); + + // Redirect redirect any branches within the newly-cloned + // finally, and any finally returns to jump to the return + // point. + for (BasicBlock* block = firstBlock; block != nextBlock; block = block->bbNext) + { + BasicBlock* newBlock = blockMap[block]; + + if (block->bbJumpKind == BBJ_EHFINALLYRET) + { + GenTreeStmt* finallyRet = newBlock->lastStmt(); + GenTreePtr finallyRetExpr = finallyRet->gtStmtExpr; + assert(finallyRetExpr->gtOper == GT_RETFILT); + fgRemoveStmt(newBlock, finallyRet); + newBlock->bbJumpKind = BBJ_ALWAYS; + newBlock->bbJumpDest = normalCallFinallyReturn; + + fgAddRefPred(normalCallFinallyReturn, newBlock); + } + else + { + optCopyBlkDest(block, newBlock); + optRedirectBlock(newBlock, &blockMap); + } + } + + // Modify the targeting call finallys to branch to the cloned + // finally. Make a note if we see some calls that can't be + // retargeted (since they want to return to other places). + BasicBlock* const firstCloneBlock = blockMap[firstBlock]; + bool retargetedAllCalls = true; + BasicBlock* currentBlock = firstCallFinallyRangeBlock; + + while (currentBlock != endCallFinallyRangeBlock) + { + BasicBlock* nextBlockToScan = currentBlock->bbNext; + + if (currentBlock->isBBCallAlwaysPair()) + { + if (currentBlock->bbJumpDest == firstBlock) + { + BasicBlock* const leaveBlock = currentBlock->bbNext; + BasicBlock* const postTryFinallyBlock = leaveBlock->bbJumpDest; + + // Note we must retarget all callfinallies that have this + // continuation, or we can't clean up the continuation + // block properly below, since it will be reachable both + // by the cloned finally and by the called finally. + if (postTryFinallyBlock == normalCallFinallyReturn) + { + // This call returns to the expected spot, so + // retarget it to branch to the clone. + currentBlock->bbJumpDest = firstCloneBlock; + currentBlock->bbJumpKind = BBJ_ALWAYS; + + // Ref count updates. + fgAddRefPred(firstCloneBlock, currentBlock); + // fgRemoveRefPred(firstBlock, currentBlock); + + // Delete the leave block, which should be marked as + // keep always. + assert((leaveBlock->bbFlags & BBF_KEEP_BBJ_ALWAYS) != 0); + nextBlock = leaveBlock->bbNext; + + leaveBlock->bbFlags &= ~BBF_KEEP_BBJ_ALWAYS; + fgRemoveBlock(leaveBlock, true); + + // Make sure iteration isn't going off the deep end. + assert(leaveBlock != endCallFinallyRangeBlock); + } + else + { + // We can't retarget this call since it + // returns somewhere else. + retargetedAllCalls = false; + } + } + } + + currentBlock = nextBlockToScan; + } + + // If we retargeted all calls, modify EH descriptor to be + // try-fault instead of try-finally, and then non-cloned + // finally catch type to be fault. + if (retargetedAllCalls) + { + JITDUMP("All callfinallys retargeted; changing finally to fault.\n"); + HBtab->ebdHandlerType = EH_HANDLER_FAULT_WAS_FINALLY; + firstBlock->bbCatchTyp = BBCT_FAULT; + } + else + { + JITDUMP("Some callfinallys *not* retargeted, so region must remain as a finally.\n"); + } + + // Modify first block of cloned finally to be a "normal" block. + BasicBlock* firstClonedBlock = blockMap[firstBlock]; + firstClonedBlock->bbCatchTyp = BBCT_NONE; + + // Cleanup the contination + fgCleanupContinuation(normalCallFinallyReturn); + + // Todo -- mark cloned blocks as a cloned finally.... + + // Done! + JITDUMP("\nDone with EH#%u\n\n", XTnum); + cloneCount++; + } + + if (cloneCount > 0) + { + JITDUMP("fgCloneFinally() cloned %u finally handlers\n", cloneCount); + fgOptimizedFinally = true; + +#ifdef DEBUG + if (verbose) + { + printf("\n*************** After fgCloneFinally()\n"); + fgDispBasicBlocks(); + fgDispHandlerTab(); + printf("\n"); + } + + fgVerifyHandlerTab(); + fgDebugCheckBBlist(false, false); + fgDebugCheckTryFinallyExits(); + +#endif // DEBUG + } +} + +#ifdef DEBUG + +//------------------------------------------------------------------------ +// fgDebugCheckTryFinallyExits: validate normal flow from try-finally +// or try-fault-was-finally. +// +// Notes: +// +// Normal control flow exiting the try block of a try-finally must +// pass through the finally. This checker attempts to verify that by +// looking at the control flow graph. +// +// Each path that exits the try of a try-finally (including try-faults +// that were optimized into try-finallys by fgCloneFinally) should +// thus either execute a callfinally to the associated finally or else +// jump to a block with the BBF_CLONED_FINALLY_BEGIN flag set. +// +// Depending on when this check is done, there may also be an empty +// block along the path. +// +// Depending on the model for invoking finallys, the callfinallies may +// lie within the try region (callfinally thunks) or in the enclosing +// region. + +void Compiler::fgDebugCheckTryFinallyExits() +{ + unsigned XTnum = 0; + EHblkDsc* HBtab = compHndBBtab; + unsigned cloneCount = 0; + bool allTryExitsValid = true; + for (; XTnum < compHndBBtabCount; XTnum++, HBtab++) + { + const EHHandlerType handlerType = HBtab->ebdHandlerType; + const bool isFinally = (handlerType == EH_HANDLER_FINALLY); + const bool wasFinally = (handlerType == EH_HANDLER_FAULT_WAS_FINALLY); + + // Screen out regions that are or were not finallys. + if (!isFinally && !wasFinally) + { + continue; + } + + // Walk blocks of the try, looking for normal control flow to + // an ancestor region. + + BasicBlock* const firstTryBlock = HBtab->ebdTryBeg; + BasicBlock* const lastTryBlock = HBtab->ebdTryLast; + assert(firstTryBlock->getTryIndex() <= XTnum); + assert(lastTryBlock->getTryIndex() <= XTnum); + BasicBlock* const afterTryBlock = lastTryBlock->bbNext; + BasicBlock* const finallyBlock = isFinally ? HBtab->ebdHndBeg : nullptr; + + for (BasicBlock* block = firstTryBlock; block != afterTryBlock; block = block->bbNext) + { + // Only check the directly contained blocks. + assert(block->hasTryIndex()); + + if (block->getTryIndex() != XTnum) + { + continue; + } + + // Look at each of the normal control flow possibilities. + const unsigned numSuccs = block->NumSucc(); + + for (unsigned i = 0; i < numSuccs; i++) + { + BasicBlock* const succBlock = block->GetSucc(i); + + if (succBlock->hasTryIndex() && succBlock->getTryIndex() <= XTnum) + { + // Successor does not exit this try region. + continue; + } + +#if FEATURE_EH_CALLFINALLY_THUNKS + + // When there are callfinally thunks, callfinallies + // logically "belong" to a child region and the exit + // path validity will be checked when looking at the + // try blocks in that region. + if (block->bbJumpKind == BBJ_CALLFINALLY) + { + continue; + } + +#endif // FEATURE_EH_CALLFINALLY_THUNKS + + // Now we know block lies directly within the try of a + // try-finally, and succBlock is in an enclosing + // region (possibly the method region). So this path + // represents flow out of the try and should be + // checked. + // + // There are various ways control can properly leave a + // try-finally (or try-fault-was-finally): + // + // (a1) via a jump to a callfinally (only for finallys, only for call finally thunks) + // (a2) via a callfinally (only for finallys, only for !call finally thunks) + // (b) via a jump to a begin finally clone block + // (c) via a jump to an empty block to (b) + // (d) via a fallthrough to an empty block to (b) + // (e) via the always half of a callfinally pair + // (f) via an always jump clonefinally exit + bool isCallToFinally = false; + +#if FEATURE_EH_CALLFINALLY_THUNKS + if (succBlock->bbJumpKind == BBJ_CALLFINALLY) + { + // case (a1) + isCallToFinally = isFinally && (succBlock->bbJumpDest == finallyBlock); + } +#else + if (block->bbJumpKind == BBJ_CALLFINALLY) + { + // case (a2) + isCallToFinally = isFinally && (block->bbJumpDest == finallyBlock); + } +#endif // FEATURE_EH_CALLFINALLY_THUNKS + + bool isJumpToClonedFinally = false; + + if (succBlock->bbFlags & BBF_CLONED_FINALLY_BEGIN) + { + // case (b) + isJumpToClonedFinally = true; + } + else if (succBlock->bbJumpKind == BBJ_ALWAYS) + { + if (succBlock->isEmpty()) + { + // case (c) + BasicBlock* const succSuccBlock = succBlock->bbJumpDest; + + if (succSuccBlock->bbFlags & BBF_CLONED_FINALLY_BEGIN) + { + isJumpToClonedFinally = true; + } + } + } + else if (succBlock->bbJumpKind == BBJ_NONE) + { + if (succBlock->isEmpty()) + { + BasicBlock* const succSuccBlock = succBlock->bbNext; + + // case (d) + if (succSuccBlock->bbFlags & BBF_CLONED_FINALLY_BEGIN) + { + isJumpToClonedFinally = true; + } + } + } + + bool isReturnFromFinally = false; + + // Case (e). Ideally we'd have something stronger to + // check here -- eg that we are returning from a call + // to the right finally -- but there are odd cases + // like orphaned second halves of callfinally pairs + // that we need to tolerate. + if (block->bbFlags & BBF_KEEP_BBJ_ALWAYS) + { + isReturnFromFinally = true; + } + + // Case (f) + if (block->bbFlags & BBF_CLONED_FINALLY_END) + { + isReturnFromFinally = true; + } + + const bool thisExitValid = isCallToFinally || isJumpToClonedFinally || isReturnFromFinally; + + if (!thisExitValid) + { + JITDUMP("fgCheckTryFinallyExitS: EH#%u exit via BB%02u -> BB%02u is invalid\n", XTnum, block->bbNum, + succBlock->bbNum); + } + + allTryExitsValid = allTryExitsValid & thisExitValid; + } + } + } + + if (!allTryExitsValid) + { + JITDUMP("fgCheckTryFinallyExits: method contains invalid try exit paths\n"); + assert(allTryExitsValid); + } +} + +#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. +// +// BBF_FINALLY_TARGET bbFlag is left unchanged by this method +// since it cannot be incrementally updated. Proper updates happen +// when fgUpdateFinallyTargetFlags runs after all finally optimizations. + +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 + // 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 +} + +//------------------------------------------------------------------------ +// fgUpdateFinallyTargetFlags: recompute BBF_FINALLY_TARGET bits for all blocks +// after finally optimizations have run. + +void Compiler::fgUpdateFinallyTargetFlags() +{ +#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_) + + // Any fixup required? + if (!fgOptimizedFinally) + { + JITDUMP("In fgUpdateFinallyTargetFlags - no finally opts, no fixup required\n"); + return; + } + + JITDUMP("In fgUpdateFinallyTargetFlags, updating finally target flag bits\n"); + + // Walk all blocks, and reset the target bits. + for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext) + { + block->bbFlags &= ~BBF_FINALLY_TARGET; + } + + // Walk all blocks again, and set the target bits. + for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext) + { + if (block->isBBCallAlwaysPair()) + { + BasicBlock* const leave = block->bbNext; + BasicBlock* const continuation = leave->bbJumpDest; + + if ((continuation->bbFlags & BBF_FINALLY_TARGET) == 0) + { + JITDUMP("Found callfinally BB%02u; setting finally target bit on BB%02u\n", block->bbNum, + continuation->bbNum); + + continuation->bbFlags |= BBF_FINALLY_TARGET; + } + } + } +#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_) +} + +// FatCalliTransformer transforms calli that can use fat function pointer. +// Fat function pointer is pointer with the second least significant bit set, +// if the bit is set, the pointer (after clearing the bit) actually points to +// a tuple <method pointer, instantiation argument pointer> where +// instantiationArgument is a hidden first argument required by method pointer. +// +// Fat pointers are used in CoreRT as a replacement for instantiating stubs, +// because CoreRT can't generate stubs in runtime. +// +// Jit is responsible for the checking the bit, do the regular call if it is not set +// or load hidden argument, fix the pointer and make a call with the fixed pointer and +// the instantiation argument. +// +// before: +// current block +// { +// previous statements +// transforming statement +// { +// call with GTF_CALL_M_FAT_POINTER_CHECK flag set in function ptr +// } +// subsequent statements +// } +// +// after: +// current block +// { +// previous statements +// } BBJ_NONE check block +// check block +// { +// jump to else if function ptr has GTF_CALL_M_FAT_POINTER_CHECK set. +// } BBJ_COND then block, else block +// then block +// { +// original statement +// } BBJ_ALWAYS remainder block +// else block +// { +// unset GTF_CALL_M_FAT_POINTER_CHECK +// load actual function pointer +// load instantiation argument +// create newArgList = (instantiation argument, original argList) +// call (actual function pointer, newArgList) +// } BBJ_NONE remainder block +// remainder block +// { +// subsequent statements +// } +// +class FatCalliTransformer +{ +public: + FatCalliTransformer(Compiler* compiler) : compiler(compiler) + { + } + + //------------------------------------------------------------------------ + // Run: run transformation for each block. + // + void Run() + { + for (BasicBlock* block = compiler->fgFirstBB; block != nullptr; block = block->bbNext) + { + TransformBlock(block); + } + } + +private: + //------------------------------------------------------------------------ + // TransformBlock: look through statements and transform statements with fat pointer calls. + // + void TransformBlock(BasicBlock* block) + { + for (GenTreeStmt* stmt = block->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt) + { + if (ContainsFatCalli(stmt)) + { + StatementTransformer stmtTransformer(compiler, block, stmt); + stmtTransformer.Run(); + } + } + } + + //------------------------------------------------------------------------ + // ContainsFatCalli: check does this statement contain fat pointer call. + // + // Checks fatPointerCandidate in form of call() or lclVar = call(). + // + // Return Value: + // true if contains, false otherwise. + // + bool ContainsFatCalli(GenTreeStmt* stmt) + { + GenTreePtr fatPointerCandidate = stmt->gtStmtExpr; + if (fatPointerCandidate->OperIsAssignment()) + { + fatPointerCandidate = fatPointerCandidate->gtGetOp2(); + } + return fatPointerCandidate->IsCall() && fatPointerCandidate->AsCall()->IsFatPointerCandidate(); + } + + class StatementTransformer + { + public: + StatementTransformer(Compiler* compiler, BasicBlock* block, GenTreeStmt* stmt) + : compiler(compiler), currBlock(block), stmt(stmt) + { + remainderBlock = nullptr; + checkBlock = nullptr; + thenBlock = nullptr; + elseBlock = nullptr; + doesReturnValue = stmt->gtStmtExpr->OperIsAssignment(); + origCall = GetCall(stmt); + fptrAddress = origCall->gtCallAddr; + pointerType = fptrAddress->TypeGet(); + } + + //------------------------------------------------------------------------ + // Run: transform the statement as described above. + // + void Run() + { + ClearFatFlag(); + CreateRemainder(); + CreateCheck(); + CreateThen(); + CreateElse(); + + RemoveOldStatement(); + SetWeights(); + ChainFlow(); + } + + private: + //------------------------------------------------------------------------ + // GetCall: find a call in a statement. + // + // Arguments: + // callStmt - the statement with the call inside. + // + // Return Value: + // call tree node pointer. + GenTreeCall* GetCall(GenTreeStmt* callStmt) + { + GenTreePtr tree = callStmt->gtStmtExpr; + GenTreeCall* call = nullptr; + if (doesReturnValue) + { + assert(tree->OperIsAssignment()); + call = tree->gtGetOp2()->AsCall(); + } + else + { + call = tree->AsCall(); // call with void return type. + } + return call; + } + + //------------------------------------------------------------------------ + // ClearFatFlag: clear fat pointer candidate flag from the original call. + // + void ClearFatFlag() + { + origCall->ClearFatPointerCandidate(); + } + + //------------------------------------------------------------------------ + // CreateRemainder: split current block at the fat call stmt and + // insert statements after the call into remainderBlock. + // + void CreateRemainder() + { + remainderBlock = compiler->fgSplitBlockAfterStatement(currBlock, stmt); + unsigned propagateFlags = currBlock->bbFlags & BBF_GC_SAFE_POINT; + remainderBlock->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL | propagateFlags; + } + + //------------------------------------------------------------------------ + // CreateCheck: create check block, that checks fat pointer bit set. + // + void CreateCheck() + { + checkBlock = CreateAndInsertBasicBlock(BBJ_COND, currBlock); + GenTreePtr fatPointerMask = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, FAT_POINTER_MASK); + GenTreePtr fptrAddressCopy = compiler->gtCloneExpr(fptrAddress); + GenTreePtr fatPointerAnd = compiler->gtNewOperNode(GT_AND, TYP_I_IMPL, fptrAddressCopy, fatPointerMask); + GenTreePtr zero = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, 0); + GenTreePtr fatPointerCmp = compiler->gtNewOperNode(GT_NE, TYP_INT, fatPointerAnd, zero); + GenTreePtr jmpTree = compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, fatPointerCmp); + GenTreePtr jmpStmt = compiler->fgNewStmtFromTree(jmpTree, stmt->gtStmt.gtStmtILoffsx); + compiler->fgInsertStmtAtEnd(checkBlock, jmpStmt); + } + + //------------------------------------------------------------------------ + // CreateCheck: create then block, that is executed if call address is not fat pointer. + // + void CreateThen() + { + thenBlock = CreateAndInsertBasicBlock(BBJ_ALWAYS, checkBlock); + GenTreePtr nonFatCallStmt = compiler->gtCloneExpr(stmt)->AsStmt(); + compiler->fgInsertStmtAtEnd(thenBlock, nonFatCallStmt); + } + + //------------------------------------------------------------------------ + // CreateCheck: create else block, that is executed if call address is fat pointer. + // + void CreateElse() + { + elseBlock = CreateAndInsertBasicBlock(BBJ_NONE, thenBlock); + + GenTreePtr fixedFptrAddress = GetFixedFptrAddress(); + GenTreePtr actualCallAddress = compiler->gtNewOperNode(GT_IND, pointerType, fixedFptrAddress); + GenTreePtr hiddenArgument = GetHiddenArgument(fixedFptrAddress); + + GenTreeStmt* fatStmt = CreateFatCallStmt(actualCallAddress, hiddenArgument); + compiler->fgInsertStmtAtEnd(elseBlock, fatStmt); + } + + //------------------------------------------------------------------------ + // CreateAndInsertBasicBlock: ask compiler to create new basic block. + // and insert in into the basic block list. + // + // Arguments: + // jumpKind - jump kind for the new basic block + // insertAfter - basic block, after which compiler has to insert the new one. + // + // Return Value: + // new basic block. + BasicBlock* CreateAndInsertBasicBlock(BBjumpKinds jumpKind, BasicBlock* insertAfter) + { + BasicBlock* block = compiler->fgNewBBafter(jumpKind, insertAfter, true); + if ((insertAfter->bbFlags & BBF_INTERNAL) == 0) + { + block->bbFlags &= ~BBF_INTERNAL; + block->bbFlags |= BBF_IMPORTED; + } + return block; + } + + //------------------------------------------------------------------------ + // GetFixedFptrAddress: clear fat pointer bit from fat pointer address. + // + // Return Value: + // address without fat pointer bit set. + GenTreePtr GetFixedFptrAddress() + { + GenTreePtr fptrAddressCopy = compiler->gtCloneExpr(fptrAddress); + GenTreePtr fatPointerMask = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, FAT_POINTER_MASK); + return compiler->gtNewOperNode(GT_XOR, pointerType, fptrAddressCopy, fatPointerMask); + } + + //------------------------------------------------------------------------ + // GetHiddenArgument: load hidden argument. + // + // Arguments: + // fixedFptrAddress - pointer to the tuple <methodPointer, instantiationArgumentPointer> + // + // Return Value: + // loaded hidden argument. + GenTreePtr GetHiddenArgument(GenTreePtr fixedFptrAddress) + { + GenTreePtr fixedFptrAddressCopy = compiler->gtCloneExpr(fixedFptrAddress); + GenTreePtr wordSize = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, genTypeSize(TYP_I_IMPL)); + GenTreePtr hiddenArgumentPtrPtr = + compiler->gtNewOperNode(GT_ADD, pointerType, fixedFptrAddressCopy, wordSize); + GenTreePtr hiddenArgumentPtr = compiler->gtNewOperNode(GT_IND, pointerType, hiddenArgumentPtrPtr); + return compiler->gtNewOperNode(GT_IND, fixedFptrAddressCopy->TypeGet(), hiddenArgumentPtr); + } + + //------------------------------------------------------------------------ + // CreateFatCallStmt: create call with fixed call address and hidden argument in the args list. + // + // Arguments: + // actualCallAddress - fixed call address + // hiddenArgument - loaded hidden argument + // + // Return Value: + // created call node. + GenTreeStmt* CreateFatCallStmt(GenTreePtr actualCallAddress, GenTreePtr hiddenArgument) + { + GenTreeStmt* fatStmt = compiler->gtCloneExpr(stmt)->AsStmt(); + GenTreePtr fatTree = fatStmt->gtStmtExpr; + GenTreeCall* fatCall = GetCall(fatStmt); + fatCall->gtCallAddr = actualCallAddress; + GenTreeArgList* args = fatCall->gtCallArgs; + args = compiler->gtNewListNode(hiddenArgument, args); + fatCall->gtCallArgs = args; + return fatStmt; + } + + //------------------------------------------------------------------------ + // RemoveOldStatement: remove original stmt from current block. + // + void RemoveOldStatement() + { + compiler->fgRemoveStmt(currBlock, stmt); + } + + //------------------------------------------------------------------------ + // SetWeights: set weights for new blocks. + // + void SetWeights() + { + remainderBlock->inheritWeight(currBlock); + checkBlock->inheritWeight(currBlock); + thenBlock->inheritWeightPercentage(currBlock, HIGH_PROBABILITY); + elseBlock->inheritWeightPercentage(currBlock, 100 - HIGH_PROBABILITY); + } + + //------------------------------------------------------------------------ + // ChainFlow: link new blocks into correct cfg. + // + void ChainFlow() + { + assert(!compiler->fgComputePredsDone); + checkBlock->bbJumpDest = elseBlock; + thenBlock->bbJumpDest = remainderBlock; + } + + Compiler* compiler; + BasicBlock* currBlock; + BasicBlock* remainderBlock; + BasicBlock* checkBlock; + BasicBlock* thenBlock; + BasicBlock* elseBlock; + GenTreeStmt* stmt; + GenTreeCall* origCall; + GenTreePtr fptrAddress; + var_types pointerType; + bool doesReturnValue; + + const int FAT_POINTER_MASK = 0x2; + const int HIGH_PROBABILITY = 80; + }; + + Compiler* compiler; +}; + +#ifdef DEBUG + +//------------------------------------------------------------------------ +// fgDebugCheckFatPointerCandidates: callback to make sure there are no more GTF_CALL_M_FAT_POINTER_CHECK calls. +// +Compiler::fgWalkResult Compiler::fgDebugCheckFatPointerCandidates(GenTreePtr* pTree, fgWalkData* data) +{ + GenTreePtr tree = *pTree; + if (tree->IsCall()) + { + assert(!tree->AsCall()->IsFatPointerCandidate()); + } + return WALK_CONTINUE; +} + +//------------------------------------------------------------------------ +// CheckNoFatPointerCandidatesLeft: walk through blocks and check that there are no fat pointer candidates left. +// +void Compiler::CheckNoFatPointerCandidatesLeft() +{ + assert(!doesMethodHaveFatPointer()); + for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext) + { + for (GenTreeStmt* stmt = fgFirstBB->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt) + { + fgWalkTreePre(&stmt->gtStmtExpr, fgDebugCheckFatPointerCandidates); + } + } +} +#endif + +//------------------------------------------------------------------------ +// fgTransformFatCalli: find and transform fat calls. +// +void Compiler::fgTransformFatCalli() +{ + assert(IsTargetAbi(CORINFO_CORERT_ABI)); + FatCalliTransformer fatCalliTransformer(this); + fatCalliTransformer.Run(); + clearMethodHasFatPointer(); +#ifdef DEBUG + CheckNoFatPointerCandidatesLeft(); +#endif +} |