summaryrefslogtreecommitdiff
path: root/src/jit/flowgraph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/flowgraph.cpp')
-rw-r--r--src/jit/flowgraph.cpp22276
1 files changed, 22276 insertions, 0 deletions
diff --git a/src/jit/flowgraph.cpp b/src/jit/flowgraph.cpp
new file mode 100644
index 0000000000..1c68bfd96a
--- /dev/null
+++ b/src/jit/flowgraph.cpp
@@ -0,0 +1,22276 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XX XX
+XX FlowGraph XX
+XX XX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+*/
+
+#include "jitpch.h"
+#ifdef _MSC_VER
+#pragma hdrstop
+#endif
+
+#include "allocacheck.h" // for alloca
+
+/*****************************************************************************/
+
+void Compiler::fgInit()
+{
+ impInit();
+
+ /* Initialization for fgWalkTreePre() and fgWalkTreePost() */
+
+ fgFirstBBScratch = nullptr;
+
+#ifdef DEBUG
+ fgPrintInlinedMethods = JitConfig.JitPrintInlinedMethods() == 1;
+#endif // DEBUG
+
+ /* We haven't yet computed the bbPreds lists */
+ fgComputePredsDone = false;
+
+ /* We haven't yet computed the bbCheapPreds lists */
+ fgCheapPredsValid = false;
+
+ /* We haven't yet computed the edge weight */
+ fgEdgeWeightsComputed = false;
+ fgHaveValidEdgeWeights = false;
+ fgSlopUsedInEdgeWeights = false;
+ fgRangeUsedInEdgeWeights = true;
+ fgNeedsUpdateFlowGraph = false;
+ fgCalledWeight = BB_ZERO_WEIGHT;
+
+ /* We haven't yet computed the dominator sets */
+ fgDomsComputed = false;
+
+#ifdef DEBUG
+ fgReachabilitySetsValid = false;
+#endif // DEBUG
+
+ /* We don't know yet which loops will always execute calls */
+ fgLoopCallMarked = false;
+
+ /* We haven't created GC Poll blocks yet. */
+ fgGCPollsCreated = false;
+
+ /* Initialize the basic block list */
+
+ fgFirstBB = nullptr;
+ fgLastBB = nullptr;
+ fgFirstColdBlock = nullptr;
+
+#if FEATURE_EH_FUNCLETS
+ fgFirstFuncletBB = nullptr;
+ fgFuncletsCreated = false;
+#endif // FEATURE_EH_FUNCLETS
+
+ fgBBcount = 0;
+
+#ifdef DEBUG
+ fgBBcountAtCodegen = 0;
+#endif // DEBUG
+
+ fgBBNumMax = 0;
+ fgEdgeCount = 0;
+ fgDomBBcount = 0;
+ fgBBVarSetsInited = false;
+ fgReturnCount = 0;
+
+ // Initialize BlockSet data.
+ fgCurBBEpoch = 0;
+ fgCurBBEpochSize = 0;
+ fgBBSetCountInSizeTUnits = 0;
+
+ genReturnBB = nullptr;
+
+ /* We haven't reached the global morphing phase */
+ fgGlobalMorph = false;
+ fgExpandInline = false;
+ fgModified = false;
+
+#ifdef DEBUG
+ fgSafeBasicBlockCreation = true;
+#endif // DEBUG
+
+ fgLocalVarLivenessDone = false;
+
+ /* Statement list is not threaded yet */
+
+ fgStmtListThreaded = false;
+
+ // Initialize the logic for adding code. This is used to insert code such
+ // as the code that raises an exception when an array range check fails.
+
+ fgAddCodeList = nullptr;
+ fgAddCodeModf = false;
+
+ for (int i = 0; i < SCK_COUNT; i++)
+ {
+ fgExcptnTargetCache[i] = nullptr;
+ }
+
+ /* Keep track of the max count of pointer arguments */
+
+ fgPtrArgCntCur = 0;
+ fgPtrArgCntMax = 0;
+
+ /* This global flag is set whenever we remove a statement */
+ fgStmtRemoved = false;
+
+ /* This global flag is set whenever we add a throw block for a RngChk */
+ fgRngChkThrowAdded = false; /* reset flag for fgIsCodeAdded() */
+
+ fgIncrCount = 0;
+
+ /* We will record a list of all BBJ_RETURN blocks here */
+ fgReturnBlocks = nullptr;
+
+ /* This is set by fgComputeReachability */
+ fgEnterBlks = BlockSetOps::UninitVal();
+
+#ifdef DEBUG
+ fgEnterBlksSetValid = false;
+#endif // DEBUG
+
+#if !FEATURE_EH_FUNCLETS
+ ehMaxHndNestingCount = 0;
+#endif // !FEATURE_EH_FUNCLETS
+
+ /* Init the fgBigOffsetMorphingTemps to be BAD_VAR_NUM. */
+ for (int i = 0; i < TYP_COUNT; i++)
+ {
+ fgBigOffsetMorphingTemps[i] = BAD_VAR_NUM;
+ }
+
+ fgNoStructPromotion = false;
+ fgNoStructParamPromotion = false;
+
+ optValnumCSE_phase = false; // referenced in fgMorphSmpOp()
+
+#ifdef DEBUG
+ fgNormalizeEHDone = false;
+#endif // DEBUG
+
+#ifdef DEBUG
+ if (!compIsForInlining())
+ {
+ if ((JitConfig.JitNoStructPromotion() & 1) == 1)
+ {
+ fgNoStructPromotion = true;
+ }
+ if ((JitConfig.JitNoStructPromotion() & 2) == 2)
+ {
+ fgNoStructParamPromotion = true;
+ }
+ }
+#endif // DEBUG
+
+ if (!compIsForInlining())
+ {
+ m_promotedStructDeathVars = nullptr;
+ }
+#ifdef FEATURE_SIMD
+ fgPreviousCandidateSIMDFieldAsgStmt = nullptr;
+#endif
+}
+
+bool Compiler::fgHaveProfileData()
+{
+ if (compIsForInlining() || compIsForImportOnly())
+ {
+ return false;
+ }
+
+ return (fgProfileBuffer != nullptr);
+}
+
+bool Compiler::fgGetProfileWeightForBasicBlock(IL_OFFSET offset, unsigned* weightWB)
+{
+ noway_assert(weightWB != nullptr);
+ unsigned weight = 0;
+
+#ifdef DEBUG
+ unsigned hashSeed = fgStressBBProf();
+ if (hashSeed != 0)
+ {
+ unsigned hash = (info.compMethodHash() * hashSeed) ^ (offset * 1027);
+
+ // We need to especially stress the procedure splitting codepath. Therefore
+ // one third the time we should return a weight of zero.
+ // Otherwise we should return some random weight (usually between 0 and 288).
+ // The below gives a weight of zero, 44% of the time
+
+ if (hash % 3 == 0)
+ {
+ weight = 0;
+ }
+ else if (hash % 11 == 0)
+ {
+ weight = (hash % 23) * (hash % 29) * (hash % 31);
+ }
+ else
+ {
+ weight = (hash % 17) * (hash % 19);
+ }
+
+ // The first block is never given a weight of zero
+ if ((offset == 0) && (weight == 0))
+ {
+ weight = 1 + (hash % 5);
+ }
+
+ *weightWB = weight;
+ return true;
+ }
+#endif // DEBUG
+
+ if (fgHaveProfileData() == false)
+ {
+ return false;
+ }
+
+ noway_assert(!compIsForInlining());
+ for (unsigned i = 0; i < fgProfileBufferCount; i++)
+ {
+ if (fgProfileBuffer[i].ILOffset == offset)
+ {
+ weight = fgProfileBuffer[i].ExecutionCount;
+
+ *weightWB = weight;
+ return true;
+ }
+ }
+
+ *weightWB = 0;
+ return true;
+}
+
+void Compiler::fgInstrumentMethod()
+{
+ noway_assert(!compIsForInlining());
+
+ // Count the number of basic blocks in the method
+
+ int countOfBlocks = 0;
+ BasicBlock* block;
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ if (!(block->bbFlags & BBF_IMPORTED) || (block->bbFlags & BBF_INTERNAL))
+ {
+ continue;
+ }
+ countOfBlocks++;
+ }
+
+ // Allocate the profile buffer
+
+ ICorJitInfo::ProfileBuffer* bbProfileBuffer;
+
+ HRESULT res = info.compCompHnd->allocBBProfileBuffer(countOfBlocks, &bbProfileBuffer);
+
+ ICorJitInfo::ProfileBuffer* bbProfileBufferStart = bbProfileBuffer;
+
+ GenTreePtr stmt;
+
+ if (!SUCCEEDED(res))
+ {
+ // The E_NOTIMPL status is returned when we are profiling a generic method from a different assembly
+ if (res == E_NOTIMPL)
+ {
+ // In such cases we still want to add the method entry callback node
+
+ GenTreeArgList* args = gtNewArgList(gtNewIconEmbMethHndNode(info.compMethodHnd));
+ GenTreePtr call = gtNewHelperCallNode(CORINFO_HELP_BBT_FCN_ENTER, TYP_VOID, 0, args);
+
+ stmt = gtNewStmt(call);
+ }
+ else
+ {
+ noway_assert(!"Error: failed to allocate bbProfileBuffer");
+ return;
+ }
+ }
+ else
+ {
+ // Assign a buffer entry for each basic block
+
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ if (!(block->bbFlags & BBF_IMPORTED) || (block->bbFlags & BBF_INTERNAL))
+ {
+ continue;
+ }
+
+ bbProfileBuffer->ILOffset = block->bbCodeOffs;
+
+ GenTreePtr addr;
+ GenTreePtr value;
+
+ value = gtNewOperNode(GT_IND, TYP_INT, gtNewIconEmbHndNode((void*)&bbProfileBuffer->ExecutionCount, nullptr,
+ GTF_ICON_BBC_PTR));
+ value = gtNewOperNode(GT_ADD, TYP_INT, value, gtNewIconNode(1));
+
+ addr = gtNewOperNode(GT_IND, TYP_INT, gtNewIconEmbHndNode((void*)&bbProfileBuffer->ExecutionCount, nullptr,
+ GTF_ICON_BBC_PTR));
+
+ addr = gtNewAssignNode(addr, value);
+
+ fgInsertStmtAtBeg(block, addr);
+
+ countOfBlocks--;
+ bbProfileBuffer++;
+ }
+ noway_assert(countOfBlocks == 0);
+
+ // Add the method entry callback node
+
+ GenTreeArgList* args = gtNewArgList(gtNewIconEmbMethHndNode(info.compMethodHnd));
+ GenTreePtr call = gtNewHelperCallNode(CORINFO_HELP_BBT_FCN_ENTER, TYP_VOID, 0, args);
+
+ GenTreePtr handle =
+ gtNewIconEmbHndNode((void*)&bbProfileBufferStart->ExecutionCount, nullptr, GTF_ICON_BBC_PTR);
+ GenTreePtr value = gtNewOperNode(GT_IND, TYP_INT, handle);
+ GenTreePtr relop = gtNewOperNode(GT_NE, TYP_INT, value, gtNewIconNode(0, TYP_INT));
+ relop->gtFlags |= GTF_RELOP_QMARK;
+ GenTreePtr colon = new (this, GT_COLON) GenTreeColon(TYP_VOID, gtNewNothingNode(), call);
+ GenTreePtr cond = gtNewQmarkNode(TYP_VOID, relop, colon);
+ stmt = gtNewStmt(cond);
+ }
+
+ fgEnsureFirstBBisScratch();
+
+ fgInsertStmtAtEnd(fgFirstBB, stmt);
+}
+
+/*****************************************************************************
+ *
+ * Create a basic block and append it to the current BB list.
+ */
+
+BasicBlock* Compiler::fgNewBasicBlock(BBjumpKinds jumpKind)
+{
+ // This method must not be called after the exception table has been
+ // constructed, because it doesn't not provide support for patching
+ // the exception table.
+
+ noway_assert(compHndBBtabCount == 0);
+
+ BasicBlock* block;
+
+ /* Allocate the block descriptor */
+
+ block = bbNewBasicBlock(jumpKind);
+ noway_assert(block->bbJumpKind == jumpKind);
+
+ /* Append the block to the end of the global basic block list */
+
+ if (fgFirstBB)
+ {
+ fgLastBB->setNext(block);
+ }
+ else
+ {
+ fgFirstBB = block;
+ block->bbPrev = nullptr;
+ }
+
+ fgLastBB = block;
+
+ return block;
+}
+
+/*****************************************************************************
+ *
+ * Ensures that fgFirstBB is a scratch BasicBlock that we have added.
+ * This can be used to add initialization code (without worrying
+ * about other blocks jumping to it).
+ *
+ * Callers have to be careful that they do not mess up the order of things
+ * added to fgEnsureFirstBBisScratch in a way as to change semantics.
+ */
+
+void Compiler::fgEnsureFirstBBisScratch()
+{
+ // Have we already allocated a scratch block?
+
+ if (fgFirstBBisScratch())
+ {
+ return;
+ }
+
+ assert(fgFirstBBScratch == nullptr);
+
+ BasicBlock* block = bbNewBasicBlock(BBJ_NONE);
+
+ if (fgFirstBB != nullptr)
+ {
+ // If we have profile data the new block will inherit fgFirstBlock's weight
+ if (fgFirstBB->bbFlags & BBF_PROF_WEIGHT)
+ {
+ block->inheritWeight(fgFirstBB);
+ }
+ fgInsertBBbefore(fgFirstBB, block);
+ }
+ else
+ {
+ noway_assert(fgLastBB == nullptr);
+ fgFirstBB = block;
+ fgLastBB = block;
+ }
+
+ noway_assert(fgLastBB != nullptr);
+
+ block->bbFlags |= (BBF_INTERNAL | BBF_IMPORTED);
+
+ fgFirstBBScratch = fgFirstBB;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("New scratch BB%02u\n", block->bbNum);
+ }
+#endif
+}
+
+bool Compiler::fgFirstBBisScratch()
+{
+ if (fgFirstBBScratch != nullptr)
+ {
+ assert(fgFirstBBScratch == fgFirstBB);
+ assert(fgFirstBBScratch->bbFlags & BBF_INTERNAL);
+ assert(fgFirstBBScratch->countOfInEdges() == 1);
+
+ // Normally, the first scratch block is a fall-through block. However, if the block after it was an empty
+ // BBJ_ALWAYS block, it might get removed, and the code that removes it will make the first scratch block
+ // a BBJ_ALWAYS block.
+ assert((fgFirstBBScratch->bbJumpKind == BBJ_NONE) || (fgFirstBBScratch->bbJumpKind == BBJ_ALWAYS));
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool Compiler::fgBBisScratch(BasicBlock* block)
+{
+ return fgFirstBBisScratch() && (block == fgFirstBB);
+}
+
+#ifdef DEBUG
+// Check to see if block contains a statement but don't spend more than a certain
+// budget doing this per method compiled.
+// If the budget is exceeded, return 'answerOnBoundExceeded' as the answer.
+/* static */
+bool Compiler::fgBlockContainsStatementBounded(BasicBlock* block, GenTree* stmt, bool answerOnBoundExceeded /*= true*/)
+{
+ const __int64 maxLinks = 1000000000;
+
+ assert(stmt->gtOper == GT_STMT);
+
+ __int64* numTraversed = &JitTls::GetCompiler()->compNumStatementLinksTraversed;
+
+ if (*numTraversed > maxLinks)
+ {
+ return answerOnBoundExceeded;
+ }
+
+ GenTree* curr = block->firstStmt();
+ do
+ {
+ (*numTraversed)++;
+ if (curr == stmt)
+ {
+ break;
+ }
+ curr = curr->gtNext;
+ } while (curr);
+ return curr != nullptr;
+}
+#endif // DEBUG
+
+//------------------------------------------------------------------------
+// fgInsertStmtAtBeg: Insert the given tree or statement at the start of the given basic block.
+//
+// Arguments:
+// block - The block into which 'stmt' will be inserted.
+// stmt - The statement to be inserted.
+//
+// Return Value:
+// Returns the new (potentially) GT_STMT node.
+//
+// Notes:
+// If 'stmt' is not already a statement, a new statement is created from it.
+// We always insert phi statements at the beginning.
+// In other cases, if there are any phi assignments and/or an assignment of
+// the GT_CATCH_ARG, we insert after those.
+
+GenTreePtr Compiler::fgInsertStmtAtBeg(BasicBlock* block, GenTreePtr stmt)
+{
+ if (stmt->gtOper != GT_STMT)
+ {
+ stmt = gtNewStmt(stmt);
+ }
+
+ GenTreePtr list = block->firstStmt();
+
+ if (!stmt->IsPhiDefnStmt())
+ {
+ GenTreePtr insertBeforeStmt = block->FirstNonPhiDefOrCatchArgAsg();
+ if (insertBeforeStmt != nullptr)
+ {
+ return fgInsertStmtBefore(block, insertBeforeStmt, stmt);
+ }
+ else if (list != nullptr)
+ {
+ return fgInsertStmtAtEnd(block, stmt);
+ }
+ // Otherwise, we will simply insert at the beginning, below.
+ }
+
+ /* The new tree will now be the first one of the block */
+
+ block->bbTreeList = stmt;
+ stmt->gtNext = list;
+
+ /* Are there any statements in the block? */
+
+ if (list)
+ {
+ GenTreePtr last;
+
+ /* There is at least one statement already */
+
+ last = list->gtPrev;
+ noway_assert(last && last->gtNext == nullptr);
+
+ /* Insert the statement in front of the first one */
+
+ list->gtPrev = stmt;
+ stmt->gtPrev = last;
+ }
+ else
+ {
+ /* The block was completely empty */
+
+ stmt->gtPrev = stmt;
+ }
+
+ return stmt;
+}
+
+/*****************************************************************************
+ *
+ * Insert the given tree or statement at the end of the given basic block.
+ * Returns the (potentially) new GT_STMT node.
+ * If the block can be a conditional block, use fgInsertStmtNearEnd.
+ */
+
+GenTreeStmt* Compiler::fgInsertStmtAtEnd(BasicBlock* block, GenTreePtr node)
+{
+ GenTreePtr list = block->firstStmt();
+ GenTreeStmt* stmt;
+
+ if (node->gtOper != GT_STMT)
+ {
+ stmt = gtNewStmt(node);
+ }
+ else
+ {
+ stmt = node->AsStmt();
+ }
+
+ assert(stmt->gtNext == nullptr); // We don't set it, and it needs to be this after the insert
+
+ if (list)
+ {
+ GenTreePtr last;
+
+ /* There is at least one statement already */
+
+ last = list->gtPrev;
+ noway_assert(last && last->gtNext == nullptr);
+
+ /* Append the statement after the last one */
+
+ last->gtNext = stmt;
+ stmt->gtPrev = last;
+ list->gtPrev = stmt;
+ }
+ else
+ {
+ /* The block is completely empty */
+
+ block->bbTreeList = stmt;
+ stmt->gtPrev = stmt;
+ }
+
+ return stmt;
+}
+
+/*****************************************************************************
+ *
+ * Insert the given tree or statement at the end of the given basic block, but before
+ * the GT_JTRUE, if present.
+ * Returns the (potentially) new GT_STMT node.
+ */
+
+GenTreeStmt* Compiler::fgInsertStmtNearEnd(BasicBlock* block, GenTreePtr node)
+{
+ GenTreeStmt* stmt;
+
+ // This routine can only be used when in tree order.
+ assert(fgOrder == FGOrderTree);
+
+ if ((block->bbJumpKind == BBJ_COND) || (block->bbJumpKind == BBJ_SWITCH) || (block->bbJumpKind == BBJ_RETURN))
+ {
+ if (node->gtOper != GT_STMT)
+ {
+ stmt = gtNewStmt(node);
+ }
+ else
+ {
+ stmt = node->AsStmt();
+ }
+
+ GenTreeStmt* first = block->firstStmt();
+ noway_assert(first);
+ GenTreeStmt* last = block->lastStmt();
+ noway_assert(last && last->gtNext == nullptr);
+ GenTreePtr after = last->gtPrev;
+
+#if DEBUG
+ if (block->bbJumpKind == BBJ_COND)
+ {
+ noway_assert(last->gtStmtExpr->gtOper == GT_JTRUE);
+ }
+ else if (block->bbJumpKind == BBJ_RETURN)
+ {
+ noway_assert((last->gtStmtExpr->gtOper == GT_RETURN) || (last->gtStmtExpr->gtOper == GT_JMP) ||
+ // BBJ_RETURN blocks in functions returning void do not get a GT_RETURN node if they
+ // have a .tail prefix (even if canTailCall returns false for these calls)
+ // code:Compiler::impImportBlockCode (search for the RET: label)
+ // Ditto for real tail calls (all code after them has been removed)
+ ((last->gtStmtExpr->gtOper == GT_CALL) &&
+ ((info.compRetType == TYP_VOID) || last->gtStmtExpr->AsCall()->IsTailCall())));
+ }
+ else
+ {
+ noway_assert(block->bbJumpKind == BBJ_SWITCH);
+ noway_assert(last->gtStmtExpr->gtOper == GT_SWITCH);
+ }
+#endif // DEBUG
+
+ /* Append 'stmt' before 'last' */
+
+ stmt->gtNext = last;
+ last->gtPrev = stmt;
+
+ if (first == last)
+ {
+ /* There is only one stmt in the block */
+
+ block->bbTreeList = stmt;
+ stmt->gtPrev = last;
+ }
+ else
+ {
+ noway_assert(after && (after->gtNext == last));
+
+ /* Append 'stmt' after 'after' */
+
+ after->gtNext = stmt;
+ stmt->gtPrev = after;
+ }
+
+ return stmt;
+ }
+ else
+ {
+ return fgInsertStmtAtEnd(block, node);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Insert the given statement "stmt" after GT_STMT node "insertionPoint".
+ * Returns the newly inserted GT_STMT node.
+ * Note that the gtPrev list of statement nodes is circular, but the gtNext list is not.
+ */
+
+GenTreePtr Compiler::fgInsertStmtAfter(BasicBlock* block, GenTreePtr insertionPoint, GenTreePtr stmt)
+{
+ assert(block->bbTreeList != nullptr);
+ noway_assert(insertionPoint->gtOper == GT_STMT);
+ noway_assert(stmt->gtOper == GT_STMT);
+ assert(fgBlockContainsStatementBounded(block, insertionPoint));
+ assert(!fgBlockContainsStatementBounded(block, stmt, false));
+
+ if (insertionPoint->gtNext == nullptr)
+ {
+ // Ok, we want to insert after the last statement of the block.
+ stmt->gtNext = nullptr;
+ stmt->gtPrev = insertionPoint;
+
+ insertionPoint->gtNext = stmt;
+
+ // Update the backward link of the first statement of the block
+ // to point to the new last statement.
+ assert(block->bbTreeList->gtPrev == insertionPoint);
+ block->bbTreeList->gtPrev = stmt;
+ }
+ else
+ {
+ stmt->gtNext = insertionPoint->gtNext;
+ stmt->gtPrev = insertionPoint;
+
+ insertionPoint->gtNext->gtPrev = stmt;
+ insertionPoint->gtNext = stmt;
+ }
+
+ return stmt;
+}
+
+// Insert the given tree or statement before GT_STMT node "insertionPoint".
+// Returns the newly inserted GT_STMT node.
+
+GenTreePtr Compiler::fgInsertStmtBefore(BasicBlock* block, GenTreePtr insertionPoint, GenTreePtr stmt)
+{
+ assert(block->bbTreeList != nullptr);
+ noway_assert(insertionPoint->gtOper == GT_STMT);
+ noway_assert(stmt->gtOper == GT_STMT);
+ assert(fgBlockContainsStatementBounded(block, insertionPoint));
+ assert(!fgBlockContainsStatementBounded(block, stmt, false));
+
+ if (insertionPoint == block->bbTreeList)
+ {
+ // We're inserting before the first statement in the block.
+ GenTreePtr list = block->bbTreeList;
+ GenTreePtr last = list->gtPrev;
+
+ stmt->gtNext = list;
+ stmt->gtPrev = last;
+
+ block->bbTreeList = stmt;
+ list->gtPrev = stmt;
+ }
+ else
+ {
+ stmt->gtNext = insertionPoint;
+ stmt->gtPrev = insertionPoint->gtPrev;
+
+ insertionPoint->gtPrev->gtNext = stmt;
+ insertionPoint->gtPrev = stmt;
+ }
+
+ return stmt;
+}
+
+/*****************************************************************************
+ *
+ * Insert the list of statements stmtList after the stmtAfter in block.
+ * Return the last statement stmtList.
+ */
+
+GenTreePtr Compiler::fgInsertStmtListAfter(BasicBlock* block, // the block where stmtAfter is in.
+ GenTreePtr stmtAfter, // the statement where stmtList should be inserted
+ // after.
+ GenTreePtr stmtList)
+{
+ // Currently we can handle when stmtAfter and stmtList are non-NULL. This makes everything easy.
+ noway_assert(stmtAfter && stmtAfter->gtOper == GT_STMT);
+ noway_assert(stmtList && stmtList->gtOper == GT_STMT);
+
+ GenTreePtr stmtLast = stmtList->gtPrev; // Last statement in a non-empty list, circular in the gtPrev list.
+ noway_assert(stmtLast);
+ noway_assert(stmtLast->gtNext == nullptr);
+
+ GenTreePtr stmtNext = stmtAfter->gtNext;
+
+ if (!stmtNext)
+ {
+ stmtAfter->gtNext = stmtList;
+ stmtList->gtPrev = stmtAfter;
+ block->bbTreeList->gtPrev = stmtLast;
+ goto _Done;
+ }
+
+ stmtAfter->gtNext = stmtList;
+ stmtList->gtPrev = stmtAfter;
+
+ stmtLast->gtNext = stmtNext;
+ stmtNext->gtPrev = stmtLast;
+
+_Done:
+
+ noway_assert(block->bbTreeList == nullptr || block->bbTreeList->gtPrev->gtNext == nullptr);
+
+ return stmtLast;
+}
+
+/*
+ Removes a block from the return block list
+*/
+void Compiler::fgRemoveReturnBlock(BasicBlock* block)
+{
+ if (fgReturnBlocks == nullptr)
+ {
+ return;
+ }
+
+ if (fgReturnBlocks->block == block)
+ {
+ // It's the 1st entry, assign new head of list.
+ fgReturnBlocks = fgReturnBlocks->next;
+ return;
+ }
+
+ for (BasicBlockList* retBlocks = fgReturnBlocks; retBlocks->next != nullptr; retBlocks = retBlocks->next)
+ {
+ if (retBlocks->next->block == block)
+ {
+ // Found it; splice it out.
+ retBlocks->next = retBlocks->next->next;
+ return;
+ }
+ }
+}
+
+//------------------------------------------------------------------------
+// fgGetPredForBlock: Find and return the predecessor edge corresponding to a given predecessor block.
+//
+// Arguments:
+// block -- The block with the predecessor list to operate on.
+// blockPred -- The predecessor block to find in the predecessor list.
+//
+// Return Value:
+// The flowList edge corresponding to "blockPred". If "blockPred" is not in the predecessor list of "block",
+// then returns nullptr.
+//
+// Assumptions:
+// -- This only works on the full predecessor lists, not the cheap preds lists.
+
+flowList* Compiler::fgGetPredForBlock(BasicBlock* block, BasicBlock* blockPred)
+{
+ noway_assert(block);
+ noway_assert(blockPred);
+ assert(!fgCheapPredsValid);
+
+ flowList* pred;
+
+ for (pred = block->bbPreds; pred != nullptr; pred = pred->flNext)
+ {
+ if (blockPred == pred->flBlock)
+ {
+ return pred;
+ }
+ }
+
+ return nullptr;
+}
+
+//------------------------------------------------------------------------
+// fgGetPredForBlock: Find and return the predecessor edge corresponding to a given predecessor block.
+// Also returns the address of the pointer that points to this edge, to make it possible to remove this edge from the
+// predecessor list without doing another linear search over the edge list.
+//
+// Arguments:
+// block -- The block with the predecessor list to operate on.
+// blockPred -- The predecessor block to find in the predecessor list.
+// ptrToPred -- Out parameter: set to the address of the pointer that points to the returned predecessor edge.
+//
+// Return Value:
+// The flowList edge corresponding to "blockPred". If "blockPred" is not in the predecessor list of "block",
+// then returns nullptr.
+//
+// Assumptions:
+// -- This only works on the full predecessor lists, not the cheap preds lists.
+
+flowList* Compiler::fgGetPredForBlock(BasicBlock* block, BasicBlock* blockPred, flowList*** ptrToPred)
+{
+ assert(block);
+ assert(blockPred);
+ assert(ptrToPred);
+ assert(!fgCheapPredsValid);
+
+ flowList** predPrevAddr;
+ flowList* pred;
+
+ for (predPrevAddr = &block->bbPreds, pred = *predPrevAddr; pred != nullptr;
+ predPrevAddr = &pred->flNext, pred = *predPrevAddr)
+ {
+ if (blockPred == pred->flBlock)
+ {
+ *ptrToPred = predPrevAddr;
+ return pred;
+ }
+ }
+
+ *ptrToPred = nullptr;
+ return nullptr;
+}
+
+//------------------------------------------------------------------------
+// fgSpliceOutPred: Removes a predecessor edge for a block from the predecessor list.
+//
+// Arguments:
+// block -- The block with the predecessor list to operate on.
+// blockPred -- The predecessor block to remove from the predecessor list. It must be a predecessor of "block".
+//
+// Return Value:
+// The flowList edge that was removed.
+//
+// Assumptions:
+// -- "blockPred" must be a predecessor block of "block".
+// -- This simply splices out the flowList object. It doesn't update block ref counts, handle duplicate counts, etc.
+// For that, use fgRemoveRefPred() or fgRemoveAllRefPred().
+// -- This only works on the full predecessor lists, not the cheap preds lists.
+//
+// Notes:
+// -- This must walk the predecessor list to find the block in question. If the predecessor edge
+// is found using fgGetPredForBlock(), consider using the version that hands back the predecessor pointer
+// address instead, to avoid this search.
+// -- Marks fgModified = true, since the flow graph has changed.
+
+flowList* Compiler::fgSpliceOutPred(BasicBlock* block, BasicBlock* blockPred)
+{
+ assert(!fgCheapPredsValid);
+ noway_assert(block->bbPreds);
+
+ flowList* oldEdge = nullptr;
+
+ // Is this the first block in the pred list?
+ if (blockPred == block->bbPreds->flBlock)
+ {
+ oldEdge = block->bbPreds;
+ block->bbPreds = block->bbPreds->flNext;
+ }
+ else
+ {
+ flowList* pred;
+ for (pred = block->bbPreds; (pred->flNext != nullptr) && (blockPred != pred->flNext->flBlock);
+ pred = pred->flNext)
+ {
+ // empty
+ }
+ oldEdge = pred->flNext;
+ if (oldEdge == nullptr)
+ {
+ noway_assert(!"Should always find the blockPred");
+ }
+ pred->flNext = pred->flNext->flNext;
+ }
+
+ // Any changes to the flow graph invalidate the dominator sets.
+ fgModified = true;
+
+ return oldEdge;
+}
+
+//------------------------------------------------------------------------
+// fgAddRefPred: Increment block->bbRefs by one and add "blockPred" to the predecessor list of "block".
+//
+// Arguments:
+// block -- A block to operate on.
+// blockPred -- The predecessor block to add to the predecessor list.
+// oldEdge -- Optional (default: nullptr). If non-nullptr, and a new edge is created (and the dup count
+// of an existing edge is not just incremented), the edge weights are copied from this edge.
+// initializingPreds -- Optional (default: false). Only set to "true" when the initial preds computation is
+// happening.
+//
+// Return Value:
+// The flow edge representing the predecessor.
+//
+// Assumptions:
+// -- This only works on the full predecessor lists, not the cheap preds lists.
+//
+// Notes:
+// -- block->bbRefs is incremented by one to account for the reduction in incoming edges.
+// -- block->bbRefs is adjusted even if preds haven't been computed. If preds haven't been computed,
+// the preds themselves aren't touched.
+// -- fgModified is set if a new flow edge is created (but not if an existing flow edge dup count is incremented),
+// indicating that the flow graph shape has changed.
+
+flowList* Compiler::fgAddRefPred(BasicBlock* block,
+ BasicBlock* blockPred,
+ flowList* oldEdge /* = nullptr */,
+ bool initializingPreds /* = false */)
+{
+ assert(block != nullptr);
+ assert(blockPred != nullptr);
+
+ block->bbRefs++;
+
+ if (!fgComputePredsDone && !initializingPreds)
+ {
+ // Why is someone trying to update the preds list when the preds haven't been created?
+ // Ignore them! This can happen when fgMorph is called before the preds list is created.
+ return nullptr;
+ }
+
+ assert(!fgCheapPredsValid);
+
+ flowList* flow = fgGetPredForBlock(block, blockPred);
+
+ if (flow)
+ {
+ noway_assert(flow->flDupCount > 0);
+ flow->flDupCount++;
+ }
+ else
+ {
+ flow = new (this, CMK_FlowList) flowList();
+
+#if MEASURE_BLOCK_SIZE
+ genFlowNodeCnt += 1;
+ genFlowNodeSize += sizeof(flowList);
+#endif // MEASURE_BLOCK_SIZE
+
+ // Any changes to the flow graph invalidate the dominator sets.
+ fgModified = true;
+
+ // Keep the predecessor list in lowest to highest bbNum order
+ // This allows us to discover the loops in optFindNaturalLoops
+ // from innermost to outermost.
+
+ // TODO-Throughput: This search is quadratic if you have many jumps
+ // to the same target. We need to either not bother sorting for
+ // debuggable code, or sort in optFindNaturalLoops, or better, make
+ // the code in optFindNaturalLoops not depend on order.
+
+ flowList** listp = &block->bbPreds;
+ while (*listp && ((*listp)->flBlock->bbNum < blockPred->bbNum))
+ {
+ listp = &(*listp)->flNext;
+ }
+
+ flow->flNext = *listp;
+ *listp = flow;
+
+ flow->flBlock = blockPred;
+ flow->flDupCount = 1;
+
+ if (fgHaveValidEdgeWeights)
+ {
+ // We are creating an edge from blockPred to block
+ // and we have already computed the edge weights, so
+ // we will try to setup this new edge with valid edge weights.
+ //
+ if (oldEdge != nullptr)
+ {
+ // If our caller has given us the old edge weights
+ // then we will use them.
+ //
+ flow->flEdgeWeightMin = oldEdge->flEdgeWeightMin;
+ flow->flEdgeWeightMax = oldEdge->flEdgeWeightMax;
+ }
+ else
+ {
+ // Set the max edge weight to be the minimum of block's or blockPred's weight
+ //
+ flow->flEdgeWeightMax = min(block->bbWeight, blockPred->bbWeight);
+
+ // If we are inserting a conditional block the minimum weight is zero,
+ // otherwise it is the same as the edge's max weight.
+ if (blockPred->NumSucc() > 1)
+ {
+ flow->flEdgeWeightMin = BB_ZERO_WEIGHT;
+ }
+ else
+ {
+ flow->flEdgeWeightMin = flow->flEdgeWeightMax;
+ }
+ }
+ }
+ else
+ {
+ flow->flEdgeWeightMin = BB_ZERO_WEIGHT;
+ flow->flEdgeWeightMax = BB_MAX_WEIGHT;
+ }
+ }
+ return flow;
+}
+
+//------------------------------------------------------------------------
+// fgRemoveRefPred: Decrements the reference count of a predecessor edge from "blockPred" to "block",
+// removing the edge if it is no longer necessary.
+//
+// Arguments:
+// block -- A block to operate on.
+// blockPred -- The predecessor block to remove from the predecessor list. It must be a predecessor of "block".
+//
+// Return Value:
+// If the flow edge was removed (the predecessor has a "dup count" of 1),
+// returns the flow graph edge that was removed. This means "blockPred" is no longer a predecessor of "block".
+// Otherwise, returns nullptr. This means that "blockPred" is still a predecessor of "block" (because "blockPred"
+// is a switch with multiple cases jumping to "block", or a BBJ_COND with both conditional and fall-through
+// paths leading to "block").
+//
+// Assumptions:
+// -- "blockPred" must be a predecessor block of "block".
+// -- This only works on the full predecessor lists, not the cheap preds lists.
+//
+// Notes:
+// -- block->bbRefs is decremented by one to account for the reduction in incoming edges.
+// -- block->bbRefs is adjusted even if preds haven't been computed. If preds haven't been computed,
+// the preds themselves aren't touched.
+// -- fgModified is set if a flow edge is removed (but not if an existing flow edge dup count is decremented),
+// indicating that the flow graph shape has changed.
+
+flowList* Compiler::fgRemoveRefPred(BasicBlock* block, BasicBlock* blockPred)
+{
+ noway_assert(block != nullptr);
+ noway_assert(blockPred != nullptr);
+
+ noway_assert(block->countOfInEdges() > 0);
+ block->bbRefs--;
+
+ // Do nothing if we haven't calculated the predecessor list yet.
+ // Yes, this does happen.
+ // For example the predecessor lists haven't been created yet when we do fgMorph.
+ // But fgMorph calls fgFoldConditional, which in turn calls fgRemoveRefPred.
+ if (!fgComputePredsDone)
+ {
+ return nullptr;
+ }
+
+ assert(!fgCheapPredsValid);
+
+ flowList** ptrToPred;
+ flowList* pred = fgGetPredForBlock(block, blockPred, &ptrToPred);
+ noway_assert(pred);
+ noway_assert(pred->flDupCount > 0);
+
+ pred->flDupCount--;
+
+ if (pred->flDupCount == 0)
+ {
+ // Splice out the predecessor edge since it's no longer necessary.
+ *ptrToPred = pred->flNext;
+
+ // Any changes to the flow graph invalidate the dominator sets.
+ fgModified = true;
+
+ return pred;
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+//------------------------------------------------------------------------
+// fgRemoveAllRefPreds: Removes a predecessor edge from one block to another, no matter what the "dup count" is.
+//
+// Arguments:
+// block -- A block to operate on.
+// blockPred -- The predecessor block to remove from the predecessor list. It must be a predecessor of "block".
+//
+// Return Value:
+// Returns the flow graph edge that was removed. The dup count on the edge is no longer valid.
+//
+// Assumptions:
+// -- "blockPred" must be a predecessor block of "block".
+// -- This only works on the full predecessor lists, not the cheap preds lists.
+//
+// Notes:
+// block->bbRefs is decremented to account for the reduction in incoming edges.
+
+flowList* Compiler::fgRemoveAllRefPreds(BasicBlock* block, BasicBlock* blockPred)
+{
+ assert(block != nullptr);
+ assert(blockPred != nullptr);
+ assert(fgComputePredsDone);
+ assert(!fgCheapPredsValid);
+ assert(block->countOfInEdges() > 0);
+
+ flowList** ptrToPred;
+ flowList* pred = fgGetPredForBlock(block, blockPred, &ptrToPred);
+ assert(pred != nullptr);
+ assert(pred->flDupCount > 0);
+
+ assert(block->bbRefs >= pred->flDupCount);
+ block->bbRefs -= pred->flDupCount;
+
+ // Now splice out the predecessor edge.
+ *ptrToPred = pred->flNext;
+
+ // Any changes to the flow graph invalidate the dominator sets.
+ fgModified = true;
+
+ return pred;
+}
+
+//------------------------------------------------------------------------
+// fgRemoveAllRefPreds: Remove a predecessor edge, given the address of a pointer to it in the
+// predecessor list, no matter what the "dup count" is.
+//
+// Arguments:
+// block -- A block with the predecessor list to operate on.
+// ptrToPred -- The address of a pointer to the predecessor to remove.
+//
+// Return Value:
+// The removed predecessor edge. The dup count on the edge is no longer valid.
+//
+// Assumptions:
+// -- The predecessor edge must be in the predecessor list for "block".
+// -- This only works on the full predecessor lists, not the cheap preds lists.
+//
+// Notes:
+// block->bbRefs is decremented by the dup count of the predecessor edge, to account for the reduction in incoming
+// edges.
+
+flowList* Compiler::fgRemoveAllRefPreds(BasicBlock* block, flowList** ptrToPred)
+{
+ assert(block != nullptr);
+ assert(ptrToPred != nullptr);
+ assert(fgComputePredsDone);
+ assert(!fgCheapPredsValid);
+ assert(block->countOfInEdges() > 0);
+
+ flowList* pred = *ptrToPred;
+ assert(pred != nullptr);
+ assert(pred->flDupCount > 0);
+
+ assert(block->bbRefs >= pred->flDupCount);
+ block->bbRefs -= pred->flDupCount;
+
+ // Now splice out the predecessor edge.
+ *ptrToPred = pred->flNext;
+
+ // Any changes to the flow graph invalidate the dominator sets.
+ fgModified = true;
+
+ return pred;
+}
+
+/*
+ Removes all the appearances of block as predecessor of others
+*/
+
+void Compiler::fgRemoveBlockAsPred(BasicBlock* block)
+{
+ assert(!fgCheapPredsValid);
+
+ PREFIX_ASSUME(block != nullptr);
+
+ BasicBlock* bNext;
+
+ switch (block->bbJumpKind)
+ {
+ case BBJ_CALLFINALLY:
+ if (!(block->bbFlags & BBF_RETLESS_CALL))
+ {
+ assert(block->isBBCallAlwaysPair());
+
+ /* The block after the BBJ_CALLFINALLY block is not reachable */
+ bNext = block->bbNext;
+
+ /* bNext is an unreachable BBJ_ALWAYS block */
+ noway_assert(bNext->bbJumpKind == BBJ_ALWAYS);
+
+ while (bNext->countOfInEdges() > 0)
+ {
+ fgRemoveRefPred(bNext, bNext->bbPreds->flBlock);
+ }
+ }
+
+ __fallthrough;
+
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+
+ /* Update the predecessor list for 'block->bbJumpDest' and 'block->bbNext' */
+ fgRemoveRefPred(block->bbJumpDest, block);
+
+ if (block->bbJumpKind != BBJ_COND)
+ {
+ break;
+ }
+
+ /* If BBJ_COND fall through */
+ __fallthrough;
+
+ case BBJ_NONE:
+
+ /* Update the predecessor list for 'block->bbNext' */
+ fgRemoveRefPred(block->bbNext, block);
+ break;
+
+ case BBJ_EHFILTERRET:
+
+ block->bbJumpDest->bbRefs++; // To compensate the bbRefs-- inside fgRemoveRefPred
+ fgRemoveRefPred(block->bbJumpDest, block);
+ break;
+
+ case BBJ_EHFINALLYRET:
+ {
+ /* Remove block as the predecessor of the bbNext of all
+ BBJ_CALLFINALLY blocks calling this finally. No need
+ to look for BBJ_CALLFINALLY for fault handlers. */
+
+ unsigned hndIndex = block->getHndIndex();
+ EHblkDsc* ehDsc = ehGetDsc(hndIndex);
+
+ if (ehDsc->HasFinallyHandler())
+ {
+ BasicBlock* begBlk;
+ BasicBlock* endBlk;
+ ehGetCallFinallyBlockRange(hndIndex, &begBlk, &endBlk);
+
+ BasicBlock* finBeg = ehDsc->ebdHndBeg;
+
+ for (BasicBlock* bcall = begBlk; bcall != endBlk; bcall = bcall->bbNext)
+ {
+ if ((bcall->bbFlags & BBF_REMOVED) || bcall->bbJumpKind != BBJ_CALLFINALLY ||
+ bcall->bbJumpDest != finBeg)
+ {
+ continue;
+ }
+
+ assert(bcall->isBBCallAlwaysPair());
+ fgRemoveRefPred(bcall->bbNext, block);
+ }
+ }
+ }
+ break;
+
+ case BBJ_THROW:
+ case BBJ_RETURN:
+ break;
+
+ case BBJ_SWITCH:
+ {
+ unsigned jumpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab = block->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ fgRemoveRefPred(*jumpTab, block);
+ } while (++jumpTab, --jumpCnt);
+
+ break;
+ }
+
+ default:
+ noway_assert(!"Block doesn't have a valid bbJumpKind!!!!");
+ break;
+ }
+}
+
+/*****************************************************************************
+ * fgChangeSwitchBlock:
+ *
+ * We have a BBJ_SWITCH jump at 'oldSwitchBlock' and we want to move this
+ * switch jump over to 'newSwitchBlock'. All of the blocks that are jumped
+ * to from jumpTab[] need to have their predecessor lists updated by removing
+ * the 'oldSwitchBlock' and adding 'newSwitchBlock'.
+ */
+
+void Compiler::fgChangeSwitchBlock(BasicBlock* oldSwitchBlock, BasicBlock* newSwitchBlock)
+{
+ noway_assert(oldSwitchBlock != nullptr);
+ noway_assert(newSwitchBlock != nullptr);
+ noway_assert(oldSwitchBlock->bbJumpKind == BBJ_SWITCH);
+
+ unsigned jumpCnt = oldSwitchBlock->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab = oldSwitchBlock->bbJumpSwt->bbsDstTab;
+
+ unsigned i;
+
+ // Walk the switch's jump table, updating the predecessor for each branch.
+ for (i = 0; i < jumpCnt; i++)
+ {
+ BasicBlock* bJump = jumpTab[i];
+ noway_assert(bJump != nullptr);
+
+ // Note that if there are duplicate branch targets in the switch jump table,
+ // fgRemoveRefPred()/fgAddRefPred() will do the right thing: the second and
+ // subsequent duplicates will simply subtract from and add to the duplicate
+ // count (respectively).
+
+ //
+ // Remove the old edge [oldSwitchBlock => bJump]
+ //
+ fgRemoveRefPred(bJump, oldSwitchBlock);
+
+ //
+ // Create the new edge [newSwitchBlock => bJump]
+ //
+ fgAddRefPred(bJump, newSwitchBlock);
+ }
+
+ if (m_switchDescMap != nullptr)
+ {
+ SwitchUniqueSuccSet uniqueSuccSet;
+
+ // If already computed and cached the unique descriptors for the old block, let's
+ // update those for the new block.
+ if (m_switchDescMap->Lookup(oldSwitchBlock, &uniqueSuccSet))
+ {
+ m_switchDescMap->Set(newSwitchBlock, uniqueSuccSet);
+ }
+ else
+ {
+ fgInvalidateSwitchDescMapEntry(newSwitchBlock);
+ }
+ fgInvalidateSwitchDescMapEntry(oldSwitchBlock);
+ }
+}
+
+/*****************************************************************************
+ * fgReplaceSwitchJumpTarget:
+ *
+ * We have a BBJ_SWITCH at 'blockSwitch' and we want to replace all entries
+ * in the jumpTab[] such that so that jumps that previously went to
+ * 'oldTarget' now go to 'newTarget'.
+ * We also must update the predecessor lists for 'oldTarget' and 'newPred'.
+ */
+
+void Compiler::fgReplaceSwitchJumpTarget(BasicBlock* blockSwitch, BasicBlock* newTarget, BasicBlock* oldTarget)
+{
+ noway_assert(blockSwitch != nullptr);
+ noway_assert(newTarget != nullptr);
+ noway_assert(oldTarget != nullptr);
+ noway_assert(blockSwitch->bbJumpKind == BBJ_SWITCH);
+
+ // For the jump targets values that match oldTarget of our BBJ_SWITCH
+ // replace predecessor 'blockSwitch' with 'newTarget'
+ //
+
+ unsigned jumpCnt = blockSwitch->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab = blockSwitch->bbJumpSwt->bbsDstTab;
+
+ unsigned i = 0;
+
+ // Walk the switch's jump table looking for blocks to update the preds for
+ while (i < jumpCnt)
+ {
+ if (jumpTab[i] == oldTarget) // We will update when jumpTab[i] matches
+ {
+ // Remove the old edge [oldTarget from blockSwitch]
+ //
+ fgRemoveAllRefPreds(oldTarget, blockSwitch);
+
+ //
+ // Change the jumpTab entry to branch to the new location
+ //
+ jumpTab[i] = newTarget;
+
+ //
+ // Create the new edge [newTarget from blockSwitch]
+ //
+ flowList* newEdge = fgAddRefPred(newTarget, blockSwitch);
+
+ // Now set the correct value of newEdge->flDupCount
+ // and replace any other jumps in jumpTab[] that go to oldTarget.
+ //
+ i++;
+ while (i < jumpCnt)
+ {
+ if (jumpTab[i] == oldTarget)
+ {
+ //
+ // We also must update this entry in the jumpTab
+ //
+ jumpTab[i] = newTarget;
+ newTarget->bbRefs++;
+
+ //
+ // Increment the flDupCount
+ //
+ newEdge->flDupCount++;
+ }
+ i++; // Check the next entry in jumpTab[]
+ }
+
+ // Maintain, if necessary, the set of unique targets of "block."
+ UpdateSwitchTableTarget(blockSwitch, oldTarget, newTarget);
+
+ // Make sure the new target has the proper bits set for being a branch target.
+ newTarget->bbFlags |= BBF_HAS_LABEL | BBF_JMP_TARGET;
+
+ return; // We have replaced the jumps to oldTarget with newTarget
+ }
+ i++; // Check the next entry in jumpTab[] for a match
+ }
+ noway_assert(!"Did not find oldTarget in jumpTab[]");
+}
+
+//------------------------------------------------------------------------
+// Compiler::fgReplaceJumpTarget: For a given block, replace the target 'oldTarget' with 'newTarget'.
+//
+// Arguments:
+// block - the block in which a jump target will be replaced.
+// newTarget - the new branch target of the block.
+// oldTarget - the old branch target of the block.
+//
+// Notes:
+// 1. Only branches are changed: BBJ_ALWAYS, the non-fallthrough path of BBJ_COND, BBJ_SWITCH, etc.
+// We ignore other block types.
+// 2. Only the first target found is updated. If there are multiple ways for a block
+// to reach 'oldTarget' (e.g., multiple arms of a switch), only the first one found is changed.
+// 3. The predecessor lists are not changed.
+// 4. The switch table "unique successor" cache is invalidated.
+//
+// This function is most useful early, before the full predecessor lists have been computed.
+//
+void Compiler::fgReplaceJumpTarget(BasicBlock* block, BasicBlock* newTarget, BasicBlock* oldTarget)
+{
+ assert(block != nullptr);
+
+ switch (block->bbJumpKind)
+ {
+ case BBJ_CALLFINALLY:
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ case BBJ_EHFILTERRET:
+ case BBJ_LEAVE: // This function will be called before import, so we still have BBJ_LEAVE
+
+ if (block->bbJumpDest == oldTarget)
+ {
+ block->bbJumpDest = newTarget;
+ }
+ break;
+
+ case BBJ_NONE:
+ case BBJ_EHFINALLYRET:
+ case BBJ_THROW:
+ case BBJ_RETURN:
+ break;
+
+ case BBJ_SWITCH:
+ unsigned jumpCnt;
+ jumpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab;
+ jumpTab = block->bbJumpSwt->bbsDstTab;
+
+ for (unsigned i = 0; i < jumpCnt; i++)
+ {
+ if (jumpTab[i] == oldTarget)
+ {
+ jumpTab[i] = newTarget;
+ break;
+ }
+ }
+ break;
+
+ default:
+ assert(!"Block doesn't have a valid bbJumpKind!!!!");
+ unreached();
+ break;
+ }
+}
+
+/*****************************************************************************
+ * Updates the predecessor list for 'block' by replacing 'oldPred' with 'newPred'.
+ * Note that a block can only appear once in the preds list (for normal preds, not
+ * cheap preds): if a predecessor has multiple ways to get to this block, then
+ * flDupCount will be >1, but the block will still appear exactly once. Thus, this
+ * function assumes that all branches from the predecessor (practically, that all
+ * switch cases that target this block) are changed to branch from the new predecessor,
+ * with the same dup count.
+ *
+ * Note that the block bbRefs is not changed, since 'block' has the same number of
+ * references as before, just from a different predecessor block.
+ */
+
+void Compiler::fgReplacePred(BasicBlock* block, BasicBlock* oldPred, BasicBlock* newPred)
+{
+ noway_assert(block != nullptr);
+ noway_assert(oldPred != nullptr);
+ noway_assert(newPred != nullptr);
+ assert(!fgCheapPredsValid);
+
+ flowList* pred;
+
+ for (pred = block->bbPreds; pred != nullptr; pred = pred->flNext)
+ {
+ if (oldPred == pred->flBlock)
+ {
+ pred->flBlock = newPred;
+ break;
+ }
+ }
+}
+
+/*****************************************************************************
+ *
+ * Returns true if block b1 dominates block b2.
+ */
+
+bool Compiler::fgDominate(BasicBlock* b1, BasicBlock* b2)
+{
+ noway_assert(fgDomsComputed);
+ assert(!fgCheapPredsValid);
+
+ //
+ // If the fgModified flag is false then we made some modifications to
+ // the flow graph, like adding a new block or changing a conditional branch
+ // into an unconditional branch.
+ //
+ // We can continue to use the dominator and reachable information to
+ // unmark loops as long as we haven't renumbered the blocks or we aren't
+ // asking for information about a new block
+ //
+
+ if (b2->bbNum > fgDomBBcount)
+ {
+ if (b1 == b2)
+ {
+ return true;
+ }
+
+ for (flowList* pred = b2->bbPreds; pred != nullptr; pred = pred->flNext)
+ {
+ if (!fgDominate(b1, pred->flBlock))
+ {
+ return false;
+ }
+ }
+
+ return b2->bbPreds != nullptr;
+ }
+
+ if (b1->bbNum > fgDomBBcount)
+ {
+ // if b1 is a loop preheader and Succ is its only successor, then all predecessors of
+ // Succ either are b1 itself or are dominated by Succ. Under these conditions, b1
+ // dominates b2 if and only if Succ dominates b2 (or if b2 == b1, but we already tested
+ // for this case)
+ if (b1->bbFlags & BBF_LOOP_PREHEADER)
+ {
+ noway_assert(b1->bbFlags & BBF_INTERNAL);
+ noway_assert(b1->bbJumpKind == BBJ_NONE);
+ return fgDominate(b1->bbNext, b2);
+ }
+
+ // unknown dominators; err on the safe side and return false
+ return false;
+ }
+
+ /* Check if b1 dominates b2 */
+ unsigned numA = b1->bbNum;
+ noway_assert(numA <= fgDomBBcount);
+ unsigned numB = b2->bbNum;
+ noway_assert(numB <= fgDomBBcount);
+
+ // What we want to ask here is basically if A is in the middle of the path from B to the root (the entry node)
+ // in the dominator tree. Turns out that can be translated as:
+ //
+ // A dom B <-> preorder(A) <= preorder(B) && postorder(A) >= postorder(B)
+ //
+ // where the equality holds when you ask if A dominates itself.
+ bool treeDom =
+ fgDomTreePreOrder[numA] <= fgDomTreePreOrder[numB] && fgDomTreePostOrder[numA] >= fgDomTreePostOrder[numB];
+
+ return treeDom;
+}
+
+/*****************************************************************************
+ *
+ * Returns true if block b1 can reach block b2.
+ */
+
+bool Compiler::fgReachable(BasicBlock* b1, BasicBlock* b2)
+{
+ noway_assert(fgDomsComputed);
+ assert(!fgCheapPredsValid);
+
+ //
+ // If the fgModified flag is false then we made some modifications to
+ // the flow graph, like adding a new block or changing a conditional branch
+ // into an unconditional branch.
+ //
+ // We can continue to use the dominator and reachable information to
+ // unmark loops as long as we haven't renumbered the blocks or we aren't
+ // asking for information about a new block
+ //
+
+ if (b2->bbNum > fgDomBBcount)
+ {
+ if (b1 == b2)
+ {
+ return true;
+ }
+
+ for (flowList* pred = b2->bbPreds; pred != nullptr; pred = pred->flNext)
+ {
+ if (fgReachable(b1, pred->flBlock))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (b1->bbNum > fgDomBBcount)
+ {
+ noway_assert(b1->bbJumpKind == BBJ_NONE || b1->bbJumpKind == BBJ_ALWAYS || b1->bbJumpKind == BBJ_COND);
+
+ if (b1->bbFallsThrough() && fgReachable(b1->bbNext, b2))
+ {
+ return true;
+ }
+
+ if (b1->bbJumpKind == BBJ_ALWAYS || b1->bbJumpKind == BBJ_COND)
+ {
+ return fgReachable(b1->bbJumpDest, b2);
+ }
+
+ return false;
+ }
+
+ /* Check if b1 can reach b2 */
+ assert(fgReachabilitySetsValid);
+ assert(BasicBlockBitSetTraits::GetSize(this) == fgDomBBcount + 1);
+ return BlockSetOps::IsMember(this, b2->bbReach, b1->bbNum);
+}
+
+/*****************************************************************************
+ * Update changed flow graph information.
+ *
+ * If the flow graph has changed, we need to recompute various information if we want to use
+ * it again.
+ */
+
+void Compiler::fgUpdateChangedFlowGraph()
+{
+ // We need to clear this so we don't hit an assert calling fgRenumberBlocks().
+ fgDomsComputed = false;
+
+ JITDUMP("\nRenumbering the basic blocks for fgUpdateChangeFlowGraph\n");
+ fgRenumberBlocks();
+
+ fgComputePreds();
+ fgComputeEnterBlocksSet();
+ fgComputeReachabilitySets();
+ fgComputeDoms();
+}
+
+/*****************************************************************************
+ * Compute the bbReach sets.
+ *
+ * This can be called to recompute the bbReach sets after the flow graph changes, such as when the
+ * number of BasicBlocks change (and thus, the BlockSet epoch changes).
+ *
+ * Finally, this also sets the BBF_GC_SAFE_POINT flag on blocks.
+ *
+ * Assumes the predecessor lists are correct.
+ *
+ * TODO-Throughput: This algorithm consumes O(n^2) because we're using dense bitsets to
+ * represent reachability. While this yields O(1) time queries, it bloats the memory usage
+ * for large code. We can do better if we try to approach reachability by
+ * computing the strongly connected components of the flow graph. That way we only need
+ * linear memory to label every block with its SCC.
+ */
+
+void Compiler::fgComputeReachabilitySets()
+{
+ assert(fgComputePredsDone);
+ assert(!fgCheapPredsValid);
+
+#ifdef DEBUG
+ fgReachabilitySetsValid = false;
+#endif // DEBUG
+
+ BasicBlock* block;
+
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ // Initialize the per-block bbReach sets. (Note that we can't just call BlockSetOps::ClearD()
+ // when re-running this computation, because if the epoch changes, the size and representation of the
+ // sets might change).
+ block->bbReach = BlockSetOps::MakeEmpty(this);
+
+ /* Mark block as reaching itself */
+ BlockSetOps::AddElemD(this, block->bbReach, block->bbNum);
+ }
+
+ /* Find the reachable blocks */
+ // Also, set BBF_GC_SAFE_POINT.
+
+ bool change;
+ BlockSet BLOCKSET_INIT_NOCOPY(newReach, BlockSetOps::MakeEmpty(this));
+ do
+ {
+ change = false;
+
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ BlockSetOps::Assign(this, newReach, block->bbReach);
+
+ bool predGcSafe = (block->bbPreds != nullptr); // Do all of our predecessor blocks have a GC safe bit?
+
+ for (flowList* pred = block->bbPreds; pred != nullptr; pred = pred->flNext)
+ {
+ BasicBlock* predBlock = pred->flBlock;
+
+ /* Union the predecessor's reachability set into newReach */
+ BlockSetOps::UnionD(this, newReach, predBlock->bbReach);
+
+ if (!(predBlock->bbFlags & BBF_GC_SAFE_POINT))
+ {
+ predGcSafe = false;
+ }
+ }
+
+ if (predGcSafe)
+ {
+ block->bbFlags |= BBF_GC_SAFE_POINT;
+ }
+
+ if (!BlockSetOps::Equal(this, newReach, block->bbReach))
+ {
+ BlockSetOps::Assign(this, block->bbReach, newReach);
+ change = true;
+ }
+ }
+ } while (change);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nAfter computing reachability sets:\n");
+ fgDispReach();
+ }
+
+ fgReachabilitySetsValid = true;
+#endif // DEBUG
+}
+
+/*****************************************************************************
+ * Compute the entry blocks set.
+ *
+ * Initialize fgEnterBlks to the set of blocks for which we don't have explicit control
+ * flow edges. These are the entry basic block and each of the EH handler blocks.
+ * For ARM, also include the BBJ_ALWAYS block of a BBJ_CALLFINALLY/BBJ_ALWAYS pair,
+ * to avoid creating "retless" calls, since we need the BBJ_ALWAYS for the purpose
+ * of unwinding, even if the call doesn't return (due to an explicit throw, for example).
+ */
+
+void Compiler::fgComputeEnterBlocksSet()
+{
+#ifdef DEBUG
+ fgEnterBlksSetValid = false;
+#endif // DEBUG
+
+ fgEnterBlks = BlockSetOps::MakeEmpty(this);
+
+ /* Now set the entry basic block */
+ BlockSetOps::AddElemD(this, fgEnterBlks, fgFirstBB->bbNum);
+ assert(fgFirstBB->bbNum == 1);
+
+ if (compHndBBtabCount > 0)
+ {
+ /* Also 'or' in the handler basic blocks */
+ EHblkDsc* HBtab;
+ EHblkDsc* HBtabEnd;
+ for (HBtab = compHndBBtab, HBtabEnd = compHndBBtab + compHndBBtabCount; HBtab < HBtabEnd; HBtab++)
+ {
+ if (HBtab->HasFilter())
+ {
+ BlockSetOps::AddElemD(this, fgEnterBlks, HBtab->ebdFilter->bbNum);
+ }
+ BlockSetOps::AddElemD(this, fgEnterBlks, HBtab->ebdHndBeg->bbNum);
+ }
+ }
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ // TODO-ARM-Cleanup: The ARM code here to prevent creating retless calls by adding the BBJ_ALWAYS
+ // to the enter blocks is a bit of a compromise, because sometimes the blocks are already reachable,
+ // and it messes up DFS ordering to have them marked as enter block. We should prevent the
+ // creation of retless calls some other way.
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ if (block->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ assert(block->isBBCallAlwaysPair());
+
+ // Don't remove the BBJ_ALWAYS block that is only here for the unwinder. It might be dead
+ // if the finally is no-return, so mark it as an entry point.
+ BlockSetOps::AddElemD(this, fgEnterBlks, block->bbNext->bbNum);
+ }
+ }
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Enter blocks: ");
+ BLOCKSET_ITER_INIT(this, iter, fgEnterBlks, bbNum);
+ while (iter.NextElem(this, &bbNum))
+ {
+ printf("BB%02u ", bbNum);
+ }
+ printf("\n");
+ }
+#endif // DEBUG
+
+#ifdef DEBUG
+ fgEnterBlksSetValid = true;
+#endif // DEBUG
+}
+
+/*****************************************************************************
+ * Remove unreachable blocks.
+ *
+ * Return true if any unreachable blocks were removed.
+ */
+
+bool Compiler::fgRemoveUnreachableBlocks()
+{
+ assert(!fgCheapPredsValid);
+ assert(fgReachabilitySetsValid);
+
+ bool hasLoops = false;
+ bool hasUnreachableBlocks = false;
+ BasicBlock* block;
+
+ /* Record unreachable blocks */
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ /* Internal throw blocks are also reachable */
+ if (fgIsThrowHlpBlk(block))
+ {
+ goto SKIP_BLOCK;
+ }
+ else if (block == genReturnBB)
+ {
+ // Don't remove statements for the genReturnBB block, as we might have special hookups there.
+ // For example, <BUGNUM> in VSW 364383, </BUGNUM>
+ // the profiler hookup needs to have the "void GT_RETURN" statement
+ // to properly set the info.compProfilerCallback flag.
+ goto SKIP_BLOCK;
+ }
+ else
+ {
+ // If any of the entry blocks can reach this block, then we skip it.
+ if (!BlockSetOps::IsEmptyIntersection(this, fgEnterBlks, block->bbReach))
+ {
+ goto SKIP_BLOCK;
+ }
+ }
+
+ // Remove all the code for the block
+ fgUnreachableBlock(block);
+
+ // Make sure that the block was marked as removed */
+ noway_assert(block->bbFlags & BBF_REMOVED);
+
+ // Some blocks mark the end of trys and catches
+ // and can't be removed. We convert these into
+ // empty blocks of type BBJ_THROW
+
+ if (block->bbFlags & BBF_DONT_REMOVE)
+ {
+ bool bIsBBCallAlwaysPair = block->isBBCallAlwaysPair();
+
+ /* Unmark the block as removed, */
+ /* clear BBF_INTERNAL as well and set BBJ_IMPORTED */
+
+ block->bbFlags &= ~(BBF_REMOVED | BBF_INTERNAL | BBF_NEEDS_GCPOLL);
+ block->bbFlags |= BBF_IMPORTED;
+ block->bbJumpKind = BBJ_THROW;
+ block->bbSetRunRarely();
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ // If this is a <BBJ_CALLFINALLY, BBJ_ALWAYS> pair, we have to clear BBF_FINALLY_TARGET flag on
+ // the target node (of BBJ_ALWAYS) since BBJ_CALLFINALLY node is getting converted to a BBJ_THROW.
+ if (bIsBBCallAlwaysPair)
+ {
+ noway_assert(block->bbNext->bbJumpKind == BBJ_ALWAYS);
+ fgClearFinallyTargetBit(block->bbNext->bbJumpDest);
+ }
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ }
+ else
+ {
+ /* We have to call fgRemoveBlock next */
+ hasUnreachableBlocks = true;
+ }
+ continue;
+
+ SKIP_BLOCK:;
+
+ // if (block->isRunRarely())
+ // continue;
+ if (block->bbJumpKind == BBJ_RETURN)
+ {
+ continue;
+ }
+
+ /* Set BBF_LOOP_HEAD if we have backwards branches to this block */
+
+ unsigned blockNum = block->bbNum;
+ for (flowList* pred = block->bbPreds; pred != nullptr; pred = pred->flNext)
+ {
+ BasicBlock* predBlock = pred->flBlock;
+ if (blockNum <= predBlock->bbNum)
+ {
+ if (predBlock->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ continue;
+ }
+
+ /* If block can reach predBlock then we have a loop head */
+ if (BlockSetOps::IsMember(this, predBlock->bbReach, blockNum))
+ {
+ hasLoops = true;
+
+ /* Set the BBF_LOOP_HEAD flag */
+ block->bbFlags |= BBF_LOOP_HEAD;
+ break;
+ }
+ }
+ }
+ }
+
+ fgHasLoops = hasLoops;
+
+ if (hasUnreachableBlocks)
+ {
+ // Now remove the unreachable blocks
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ // If we mark the block with BBF_REMOVED then
+ // we need to call fgRemovedBlock() on it
+
+ if (block->bbFlags & BBF_REMOVED)
+ {
+ fgRemoveBlock(block, true);
+
+ // When we have a BBJ_CALLFINALLY, BBJ_ALWAYS pair; fgRemoveBlock will remove
+ // both blocks, so we must advance 1 extra place in the block list
+ //
+ if (block->isBBCallAlwaysPair())
+ {
+ block = block->bbNext;
+ }
+ }
+ }
+ }
+
+ return hasUnreachableBlocks;
+}
+
+/*****************************************************************************
+ *
+ * Function called to compute the dominator and reachable sets.
+ *
+ * Assumes the predecessor lists are computed and correct.
+ */
+
+void Compiler::fgComputeReachability()
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgComputeReachability\n");
+ }
+
+ fgVerifyHandlerTab();
+
+ // Make sure that the predecessor lists are accurate
+ assert(fgComputePredsDone);
+ fgDebugCheckBBlist();
+#endif // DEBUG
+
+ /* Create a list of all BBJ_RETURN blocks. The head of the list is 'fgReturnBlocks'. */
+ fgReturnBlocks = nullptr;
+
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ // If this is a BBJ_RETURN block, add it to our list of all BBJ_RETURN blocks. This list is only
+ // used to find return blocks.
+ if (block->bbJumpKind == BBJ_RETURN)
+ {
+ fgReturnBlocks = new (this, CMK_Reachability) BasicBlockList(block, fgReturnBlocks);
+ }
+ }
+
+ // Compute reachability and then delete blocks determined to be unreachable. If we delete blocks, we
+ // need to loop, as that might have caused more blocks to become unreachable. This can happen in the
+ // case where a call to a finally is unreachable and deleted (maybe the call to the finally is
+ // preceded by a throw or an infinite loop), making the blocks following the finally unreachable.
+ // However, all EH entry blocks are considered global entry blocks, causing the blocks following the
+ // call to the finally to stay rooted, until a second round of reachability is done.
+ // The dominator algorithm expects that all blocks can be reached from the fgEnterBlks set.
+ unsigned passNum = 1;
+ bool changed;
+ do
+ {
+ // Just to be paranoid, avoid infinite loops; fall back to minopts.
+ if (passNum > 10)
+ {
+ noway_assert(!"Too many unreachable block removal loops");
+ }
+
+ /* Walk the flow graph, reassign block numbers to keep them in ascending order */
+ JITDUMP("\nRenumbering the basic blocks for fgComputeReachability pass #%u\n", passNum);
+ passNum++;
+ fgRenumberBlocks();
+
+ //
+ // Compute fgEnterBlks
+ //
+
+ fgComputeEnterBlocksSet();
+
+ //
+ // Compute bbReach
+ //
+
+ fgComputeReachabilitySets();
+
+ //
+ // Use reachability information to delete unreachable blocks.
+ // Also, determine if the flow graph has loops and set 'fgHasLoops' accordingly.
+ // Set the BBF_LOOP_HEAD flag on the block target of backwards branches.
+ //
+
+ changed = fgRemoveUnreachableBlocks();
+
+ } while (changed);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nAfter computing reachability:\n");
+ fgDispBasicBlocks(verboseTrees);
+ printf("\n");
+ }
+
+ fgVerifyHandlerTab();
+ fgDebugCheckBBlist(true);
+#endif // DEBUG
+
+ //
+ // Now, compute the dominators
+ //
+
+ fgComputeDoms();
+}
+
+/** In order to be able to compute dominance, we need to first get a DFS reverse post order sort on the basic flow graph
+ * for the dominance algorithm to operate correctly. The reason why we need the DFS sort is because
+ * we will build the dominance sets using the partial order induced by the DFS sorting. With this
+ * precondition not holding true, the algorithm doesn't work properly.
+ */
+void Compiler::fgDfsInvPostOrder()
+{
+ // NOTE: This algorithm only pays attention to the actual blocks. It ignores the imaginary entry block.
+
+ // visited : Once we run the DFS post order sort recursive algorithm, we mark the nodes we visited to avoid
+ // backtracking.
+ BlockSet BLOCKSET_INIT_NOCOPY(visited, BlockSetOps::MakeEmpty(this));
+
+ // We begin by figuring out which basic blocks don't have incoming edges and mark them as
+ // start nodes. Later on we run the recursive algorithm for each node that we
+ // mark in this step.
+ BlockSet_ValRet_T startNodes = fgDomFindStartNodes();
+
+ // Make sure fgEnterBlks are still there in startNodes, even if they participate in a loop (i.e., there is
+ // an incoming edge into the block).
+ assert(fgEnterBlksSetValid);
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ //
+ // BlockSetOps::UnionD(this, startNodes, fgEnterBlks);
+ //
+ // This causes problems on ARM, because we for BBJ_CALLFINALLY/BBJ_ALWAYS pairs, we add the BBJ_ALWAYS
+ // to the enter blocks set to prevent flow graph optimizations from removing it and creating retless call finallies
+ // (BBF_RETLESS_CALL). This leads to an incorrect DFS ordering in some cases, because we start the recursive walk
+ // from the BBJ_ALWAYS, which is reachable from other blocks. A better solution would be to change ARM to avoid
+ // creating retless calls in a different way, not by adding BBJ_ALWAYS to fgEnterBlks.
+ //
+ // So, let us make sure at least fgFirstBB is still there, even if it participates in a loop.
+ BlockSetOps::AddElemD(this, startNodes, 1);
+ assert(fgFirstBB->bbNum == 1);
+#else
+ BlockSetOps::UnionD(this, startNodes, fgEnterBlks);
+#endif
+
+ assert(BlockSetOps::IsMember(this, startNodes, fgFirstBB->bbNum));
+
+ // Call the flowgraph DFS traversal helper.
+ unsigned postIndex = 1;
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ // If the block has no predecessors, and we haven't already visited it (because it's in fgEnterBlks but also
+ // reachable from the first block), go ahead and traverse starting from this block.
+ if (BlockSetOps::IsMember(this, startNodes, block->bbNum) &&
+ !BlockSetOps::IsMember(this, visited, block->bbNum))
+ {
+ fgDfsInvPostOrderHelper(block, visited, &postIndex);
+ }
+ }
+
+ // After the DFS reverse postorder is completed, we must have visited all the basic blocks.
+ noway_assert(postIndex == fgBBcount + 1);
+ noway_assert(fgBBNumMax == fgBBcount);
+
+#ifdef DEBUG
+ if (0 && verbose)
+ {
+ printf("\nAfter doing a post order traversal of the BB graph, this is the ordering:\n");
+ for (unsigned i = 1; i <= fgBBNumMax; ++i)
+ {
+ printf("%02u -> BB%02u\n", i, fgBBInvPostOrder[i]->bbNum);
+ }
+ printf("\n");
+ }
+#endif // DEBUG
+}
+
+BlockSet_ValRet_T Compiler::fgDomFindStartNodes()
+{
+ unsigned j;
+ BasicBlock* block;
+
+ // startNodes :: A set that represents which basic blocks in the flow graph don't have incoming edges.
+ // We begin assuming everything is a start block and remove any block that is being referenced by another in its
+ // successor list.
+
+ BlockSet BLOCKSET_INIT_NOCOPY(startNodes, BlockSetOps::MakeFull(this));
+
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ unsigned cSucc = block->NumSucc(this);
+ for (j = 0; j < cSucc; ++j)
+ {
+ BasicBlock* succ = block->GetSucc(j, this);
+ BlockSetOps::RemoveElemD(this, startNodes, succ->bbNum);
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nDominator computation start blocks (those blocks with no incoming edges):\n");
+ BLOCKSET_ITER_INIT(this, iter, startNodes, bbNum);
+ while (iter.NextElem(this, &bbNum))
+ {
+ printf("BB%02u ", bbNum);
+ }
+ printf("\n");
+ }
+#endif // DEBUG
+
+ return startNodes;
+}
+
+//------------------------------------------------------------------------
+// fgDfsInvPostOrderHelper: Helper to assign post-order numbers to blocks.
+//
+// Arguments:
+// block - The starting entry block
+// visited - The set of visited blocks
+// count - Pointer to the Dfs counter
+//
+// Notes:
+// Compute a non-recursive DFS traversal of the flow graph using an
+// evaluation stack to assign post-order numbers.
+
+void Compiler::fgDfsInvPostOrderHelper(BasicBlock* block, BlockSet& visited, unsigned* count)
+{
+ // Assume we haven't visited this node yet (callers ensure this).
+ assert(!BlockSetOps::IsMember(this, visited, block->bbNum));
+
+ // Allocate a local stack to hold the DFS traversal actions necessary
+ // to compute pre/post-ordering of the control flowgraph.
+ ArrayStack<DfsBlockEntry> stack(this);
+
+ // Push the first block on the stack to seed the traversal.
+ stack.Push(DfsBlockEntry(DSS_Pre, block));
+ // Flag the node we just visited to avoid backtracking.
+ BlockSetOps::AddElemD(this, visited, block->bbNum);
+
+ // The search is terminated once all the actions have been processed.
+ while (stack.Height() != 0)
+ {
+ DfsBlockEntry current = stack.Pop();
+ BasicBlock* currentBlock = current.dfsBlock;
+
+ if (current.dfsStackState == DSS_Pre)
+ {
+ // This is a pre-visit that corresponds to the first time the
+ // node is encountered in the spanning tree and receives pre-order
+ // numberings. By pushing the post-action on the stack here we
+ // are guaranteed to only process it after all of its successors
+ // pre and post actions are processed.
+ stack.Push(DfsBlockEntry(DSS_Post, currentBlock));
+
+ unsigned cSucc = currentBlock->NumSucc(this);
+ for (unsigned j = 0; j < cSucc; ++j)
+ {
+ BasicBlock* succ = currentBlock->GetSucc(j, this);
+
+ // If this is a node we haven't seen before, go ahead and process
+ if (!BlockSetOps::IsMember(this, visited, succ->bbNum))
+ {
+ // Push a pre-visit action for this successor onto the stack and
+ // mark it as visited in case this block has multiple successors
+ // to the same node (multi-graph).
+ stack.Push(DfsBlockEntry(DSS_Pre, succ));
+ BlockSetOps::AddElemD(this, visited, succ->bbNum);
+ }
+ }
+ }
+ else
+ {
+ // This is a post-visit that corresponds to the last time the
+ // node is visited in the spanning tree and only happens after
+ // all descendents in the spanning tree have had pre and post
+ // actions applied.
+
+ assert(current.dfsStackState == DSS_Post);
+
+ unsigned invCount = fgBBcount - *count + 1;
+ assert(1 <= invCount && invCount <= fgBBNumMax);
+ fgBBInvPostOrder[invCount] = currentBlock;
+ currentBlock->bbDfsNum = invCount;
+ ++(*count);
+ }
+ }
+}
+
+void Compiler::fgComputeDoms()
+{
+ assert(!fgCheapPredsValid);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgComputeDoms\n");
+ }
+
+ fgVerifyHandlerTab();
+
+ // Make sure that the predecessor lists are accurate.
+ // Also check that the blocks are properly, densely numbered (so calling fgRenumberBlocks is not necessary).
+ fgDebugCheckBBlist(true);
+
+ // Assert things related to the BlockSet epoch.
+ assert(fgBBcount == fgBBNumMax);
+ assert(BasicBlockBitSetTraits::GetSize(this) == fgBBNumMax + 1);
+#endif // DEBUG
+
+ BlockSet BLOCKSET_INIT_NOCOPY(processedBlks, BlockSetOps::MakeEmpty(this));
+
+ fgBBInvPostOrder = new (this, CMK_DominatorMemory) BasicBlock*[fgBBNumMax + 1];
+ memset(fgBBInvPostOrder, 0, sizeof(BasicBlock*) * (fgBBNumMax + 1));
+
+ fgDfsInvPostOrder();
+ noway_assert(fgBBInvPostOrder[0] == nullptr);
+
+ // flRoot and bbRoot represent an imaginary unique entry point in the flow graph.
+ // All the orphaned EH blocks and fgFirstBB will temporarily have its predecessors list
+ // (with bbRoot as the only basic block in it) set as flRoot.
+ // Later on, we clear their predecessors and let them to be nullptr again.
+ // Since we number basic blocks starting at one, the imaginary entry block is conveniently numbered as zero.
+ flowList flRoot;
+ BasicBlock bbRoot;
+
+ bbRoot.bbPreds = nullptr;
+ bbRoot.bbNum = 0;
+ bbRoot.bbIDom = &bbRoot;
+ bbRoot.bbDfsNum = 0;
+ flRoot.flNext = nullptr;
+ flRoot.flBlock = &bbRoot;
+
+ fgBBInvPostOrder[0] = &bbRoot;
+
+ // Mark both bbRoot and fgFirstBB processed
+ BlockSetOps::AddElemD(this, processedBlks, 0); // bbRoot == block #0
+ BlockSetOps::AddElemD(this, processedBlks, 1); // fgFirstBB == block #1
+ assert(fgFirstBB->bbNum == 1);
+
+ // Special case fgFirstBB to say its IDom is bbRoot.
+ fgFirstBB->bbIDom = &bbRoot;
+
+ BasicBlock* block = nullptr;
+
+ for (block = fgFirstBB->bbNext; block != nullptr; block = block->bbNext)
+ {
+ // If any basic block has no predecessors then we flag it as processed and temporarily
+ // mark its precedessor list to be flRoot. This makes the flowgraph connected,
+ // a precondition that is needed by the dominance algorithm to operate properly.
+ if (block->bbPreds == nullptr)
+ {
+ block->bbPreds = &flRoot;
+ block->bbIDom = &bbRoot;
+ BlockSetOps::AddElemD(this, processedBlks, block->bbNum);
+ }
+ else
+ {
+ block->bbIDom = nullptr;
+ }
+ }
+
+ // Mark the EH blocks as entry blocks and also flag them as processed.
+ if (compHndBBtabCount > 0)
+ {
+ EHblkDsc* HBtab;
+ EHblkDsc* HBtabEnd;
+ for (HBtab = compHndBBtab, HBtabEnd = compHndBBtab + compHndBBtabCount; HBtab < HBtabEnd; HBtab++)
+ {
+ if (HBtab->HasFilter())
+ {
+ HBtab->ebdFilter->bbIDom = &bbRoot;
+ BlockSetOps::AddElemD(this, processedBlks, HBtab->ebdFilter->bbNum);
+ }
+ HBtab->ebdHndBeg->bbIDom = &bbRoot;
+ BlockSetOps::AddElemD(this, processedBlks, HBtab->ebdHndBeg->bbNum);
+ }
+ }
+
+ // Now proceed to compute the immediate dominators for each basic block.
+ bool changed = true;
+ while (changed)
+ {
+ changed = false;
+ for (unsigned i = 1; i <= fgBBNumMax;
+ ++i) // Process each actual block; don't process the imaginary predecessor block.
+ {
+ flowList* first = nullptr;
+ BasicBlock* newidom = nullptr;
+ block = fgBBInvPostOrder[i];
+
+ // If we have a block that has bbRoot as its bbIDom
+ // it means we flag it as processed and as an entry block so
+ // in this case we're all set.
+ if (block->bbIDom == &bbRoot)
+ {
+ continue;
+ }
+
+ // Pick up the first processed predecesor of the current block.
+ for (first = block->bbPreds; first != nullptr; first = first->flNext)
+ {
+ if (BlockSetOps::IsMember(this, processedBlks, first->flBlock->bbNum))
+ {
+ break;
+ }
+ }
+ noway_assert(first != nullptr);
+
+ // We assume the first processed predecessor will be the
+ // immediate dominator and then compute the forward flow analysis.
+ newidom = first->flBlock;
+ for (flowList* p = block->bbPreds; p != nullptr; p = p->flNext)
+ {
+ if (p->flBlock == first->flBlock)
+ {
+ continue;
+ }
+ if (p->flBlock->bbIDom != nullptr)
+ {
+ // fgIntersectDom is basically the set intersection between
+ // the dominance sets of the new IDom and the current predecessor
+ // Since the nodes are ordered in DFS inverse post order and
+ // IDom induces a tree, fgIntersectDom actually computes
+ // the lowest common ancestor in the dominator tree.
+ newidom = fgIntersectDom(p->flBlock, newidom);
+ }
+ }
+
+ // If the Immediate dominator changed, assign the new one
+ // to the current working basic block.
+ if (block->bbIDom != newidom)
+ {
+ noway_assert(newidom != nullptr);
+ block->bbIDom = newidom;
+ changed = true;
+ }
+ BlockSetOps::AddElemD(this, processedBlks, block->bbNum);
+ }
+ }
+
+ // As stated before, once we have computed immediate dominance we need to clear
+ // all the basic blocks whose predecessor list was set to flRoot. This
+ // reverts that and leaves the blocks the same as before.
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ if (block->bbPreds == &flRoot)
+ {
+ block->bbPreds = nullptr;
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ fgDispDoms();
+ }
+#endif
+
+ fgBuildDomTree();
+
+ fgModified = false;
+ fgDomBBcount = fgBBcount;
+ assert(fgBBcount == fgBBNumMax);
+ assert(BasicBlockBitSetTraits::GetSize(this) == fgDomBBcount + 1);
+
+ fgDomsComputed = true;
+}
+
+void Compiler::fgBuildDomTree()
+{
+ unsigned i;
+ BasicBlock* block;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nInside fgBuildDomTree\n");
+ }
+#endif // DEBUG
+
+ // domTree :: The dominance tree represented using adjacency lists. We use BasicBlockList to represent edges.
+ // Indexed by basic block number.
+ unsigned bbArraySize = fgBBNumMax + 1;
+ BasicBlockList** domTree = new (this, CMK_DominatorMemory) BasicBlockList*[bbArraySize];
+
+ fgDomTreePreOrder = new (this, CMK_DominatorMemory) unsigned[bbArraySize];
+ fgDomTreePostOrder = new (this, CMK_DominatorMemory) unsigned[bbArraySize];
+
+ // Initialize all the data structures.
+ for (i = 0; i < bbArraySize; ++i)
+ {
+ domTree[i] = nullptr;
+ fgDomTreePreOrder[i] = fgDomTreePostOrder[i] = 0;
+ }
+
+ // Build the dominance tree.
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ // If the immediate dominator is not the imaginary root (bbRoot)
+ // we proceed to append this block to the children of the dominator node.
+ if (block->bbIDom->bbNum != 0)
+ {
+ int bbNum = block->bbIDom->bbNum;
+ domTree[bbNum] = new (this, CMK_DominatorMemory) BasicBlockList(block, domTree[bbNum]);
+ }
+ else
+ {
+ // This means this block had bbRoot set as its IDom. We clear it out
+ // and convert the tree back to a forest.
+ block->bbIDom = nullptr;
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nAfter computing the Dominance Tree:\n");
+ fgDispDomTree(domTree);
+ }
+#endif // DEBUG
+
+ // Get the bitset that represents the roots of the dominance tree.
+ // Something to note here is that the dominance tree has been converted from a forest to a tree
+ // by using the bbRoot trick on fgComputeDoms. The reason we have a forest instead of a real tree
+ // is because we treat the EH blocks as entry nodes so the real dominance tree is not necessarily connected.
+ BlockSet_ValRet_T domTreeEntryNodes = fgDomTreeEntryNodes(domTree);
+
+ // The preorder and postorder numbers.
+ // We start from 1 to match the bbNum ordering.
+ unsigned preNum = 1;
+ unsigned postNum = 1;
+
+ // There will be nodes in the dominance tree that will not be reachable:
+ // the catch blocks that return since they don't have any predecessor.
+ // For that matter we'll keep track of how many nodes we can
+ // reach and assert at the end that we visited all of them.
+ unsigned domTreeReachable = fgBBcount;
+
+ // Once we have the dominance tree computed, we need to traverse it
+ // to get the preorder and postorder numbers for each node. The purpose of
+ // this is to achieve O(1) queries for of the form A dominates B.
+ for (i = 1; i <= fgBBNumMax; ++i)
+ {
+ if (BlockSetOps::IsMember(this, domTreeEntryNodes, i))
+ {
+ if (domTree[i] == nullptr)
+ {
+ // If this is an entry node but there's no children on this
+ // node, it means it's unreachable so we decrement the reachable
+ // counter.
+ --domTreeReachable;
+ }
+ else
+ {
+ // Otherwise, we do a DFS traversal of the dominator tree.
+ fgTraverseDomTree(i, domTree, &preNum, &postNum);
+ }
+ }
+ }
+
+ noway_assert(preNum == domTreeReachable + 1);
+ noway_assert(postNum == domTreeReachable + 1);
+
+ // Once we have all the reachable nodes numbered, we proceed to
+ // assign numbers to the non-reachable ones, just assign incrementing
+ // values. We must reach fgBBcount at the end.
+
+ for (i = 1; i <= fgBBNumMax; ++i)
+ {
+ if (BlockSetOps::IsMember(this, domTreeEntryNodes, i))
+ {
+ if (domTree[i] == nullptr)
+ {
+ fgDomTreePreOrder[i] = preNum++;
+ fgDomTreePostOrder[i] = postNum++;
+ }
+ }
+ }
+
+ noway_assert(preNum == fgBBNumMax + 1);
+ noway_assert(postNum == fgBBNumMax + 1);
+ noway_assert(fgDomTreePreOrder[0] == 0); // Unused first element
+ noway_assert(fgDomTreePostOrder[0] == 0); // Unused first element
+
+#ifdef DEBUG
+ if (0 && verbose)
+ {
+ printf("\nAfter traversing the dominance tree:\n");
+ printf("PreOrder:\n");
+ for (i = 1; i <= fgBBNumMax; ++i)
+ {
+ printf("BB%02u : %02u\n", i, fgDomTreePreOrder[i]);
+ }
+ printf("PostOrder:\n");
+ for (i = 1; i <= fgBBNumMax; ++i)
+ {
+ printf("BB%02u : %02u\n", i, fgDomTreePostOrder[i]);
+ }
+ }
+#endif // DEBUG
+}
+
+BlockSet_ValRet_T Compiler::fgDomTreeEntryNodes(BasicBlockList** domTree)
+{
+ // domTreeEntryNodes :: Set that represents which basic blocks are roots of the dominator forest.
+
+ BlockSet BLOCKSET_INIT_NOCOPY(domTreeEntryNodes, BlockSetOps::MakeFull(this));
+
+ // First of all we need to find all the roots of the dominance forest.
+
+ for (unsigned i = 1; i <= fgBBNumMax; ++i)
+ {
+ for (BasicBlockList* current = domTree[i]; current != nullptr; current = current->next)
+ {
+ BlockSetOps::RemoveElemD(this, domTreeEntryNodes, current->block->bbNum);
+ }
+ }
+
+ return domTreeEntryNodes;
+}
+
+#ifdef DEBUG
+void Compiler::fgDispDomTree(BasicBlockList** domTree)
+{
+ for (unsigned i = 1; i <= fgBBNumMax; ++i)
+ {
+ if (domTree[i] != nullptr)
+ {
+ printf("BB%02u : ", i);
+ for (BasicBlockList* current = domTree[i]; current != nullptr; current = current->next)
+ {
+ assert(current->block);
+ printf("BB%02u ", current->block->bbNum);
+ }
+ printf("\n");
+ }
+ }
+ printf("\n");
+}
+#endif // DEBUG
+
+//------------------------------------------------------------------------
+// fgTraverseDomTree: Assign pre/post-order numbers to the dominator tree.
+//
+// Arguments:
+// bbNum - The basic block number of the starting block
+// domTree - The dominator tree (as child block lists)
+// preNum - Pointer to the pre-number counter
+// postNum - Pointer to the post-number counter
+//
+// Notes:
+// Runs a non-recursive DFS traversal of the dominator tree using an
+// evaluation stack to assign pre-order and post-order numbers.
+// These numberings are used to provide constant time lookup for
+// ancestor/descendent tests between pairs of nodes in the tree.
+
+void Compiler::fgTraverseDomTree(unsigned bbNum, BasicBlockList** domTree, unsigned* preNum, unsigned* postNum)
+{
+ noway_assert(bbNum <= fgBBNumMax);
+
+ // If the block preorder number is not zero it means we already visited
+ // that node, so we skip it.
+ if (fgDomTreePreOrder[bbNum] == 0)
+ {
+ // If this is the first time we visit this node, both preorder and postnumber
+ // values must be zero.
+ noway_assert(fgDomTreePostOrder[bbNum] == 0);
+
+ // Allocate a local stack to hold the Dfs traversal actions necessary
+ // to compute pre/post-ordering of the dominator tree.
+ ArrayStack<DfsNumEntry> stack(this);
+
+ // Push the first entry number on the stack to seed the traversal.
+ stack.Push(DfsNumEntry(DSS_Pre, bbNum));
+
+ // The search is terminated once all the actions have been processed.
+ while (stack.Height() != 0)
+ {
+ DfsNumEntry current = stack.Pop();
+ unsigned currentNum = current.dfsNum;
+
+ if (current.dfsStackState == DSS_Pre)
+ {
+ // This pre-visit action corresponds to the first time the
+ // node is encountered during the spanning traversal.
+ noway_assert(fgDomTreePreOrder[currentNum] == 0);
+ noway_assert(fgDomTreePostOrder[currentNum] == 0);
+
+ // Assign the preorder number on the first visit.
+ fgDomTreePreOrder[currentNum] = (*preNum)++;
+
+ // Push this nodes post-action on the stack such that all successors
+ // pre-order visits occur before this nodes post-action. We will assign
+ // its post-order numbers when we pop off the stack.
+ stack.Push(DfsNumEntry(DSS_Post, currentNum));
+
+ // For each child in the dominator tree process its pre-actions.
+ for (BasicBlockList* child = domTree[currentNum]; child != nullptr; child = child->next)
+ {
+ unsigned childNum = child->block->bbNum;
+
+ // This is a tree so never could have been visited
+ assert(fgDomTreePreOrder[childNum] == 0);
+
+ // Push the successor in the dominator tree for pre-actions.
+ stack.Push(DfsNumEntry(DSS_Pre, childNum));
+ }
+ }
+ else
+ {
+ // This post-visit action corresponds to the last time the node
+ // is encountered and only after all descendents in the spanning
+ // tree have had pre and post-order numbers assigned.
+
+ assert(current.dfsStackState == DSS_Post);
+ assert(fgDomTreePreOrder[currentNum] != 0);
+ assert(fgDomTreePostOrder[currentNum] == 0);
+
+ // Now assign this nodes post-order number.
+ fgDomTreePostOrder[currentNum] = (*postNum)++;
+ }
+ }
+ }
+}
+
+// This code finds the lowest common ancestor in the
+// dominator tree between two basic blocks. The LCA in the Dominance tree
+// represents the closest dominator between the two basic blocks. Used to
+// adjust the IDom value in fgComputDoms.
+BasicBlock* Compiler::fgIntersectDom(BasicBlock* a, BasicBlock* b)
+{
+ BasicBlock* finger1 = a;
+ BasicBlock* finger2 = b;
+ while (finger1 != finger2)
+ {
+ while (finger1->bbDfsNum > finger2->bbDfsNum)
+ {
+ finger1 = finger1->bbIDom;
+ }
+ while (finger2->bbDfsNum > finger1->bbDfsNum)
+ {
+ finger2 = finger2->bbIDom;
+ }
+ }
+ return finger1;
+}
+
+// Return a BlockSet containing all the blocks that dominate 'block'.
+BlockSet_ValRet_T Compiler::fgGetDominatorSet(BasicBlock* block)
+{
+ assert(block != nullptr);
+
+ BlockSet BLOCKSET_INIT_NOCOPY(domSet, BlockSetOps::MakeEmpty(this));
+
+ do
+ {
+ BlockSetOps::AddElemD(this, domSet, block->bbNum);
+ if (block == block->bbIDom)
+ {
+ break; // We found a cycle in the IDom list, so we're done.
+ }
+ block = block->bbIDom;
+ } while (block != nullptr);
+
+ return domSet;
+}
+
+/*****************************************************************************
+ *
+ * fgComputeCheapPreds: Function called to compute the BasicBlock::bbCheapPreds lists.
+ *
+ * No other block data is changed (e.g., bbRefs, bbFlags).
+ *
+ * The cheap preds lists are similar to the normal (bbPreds) predecessor lists, but are cheaper to
+ * compute and store, as follows:
+ * 1. A flow edge is typed BasicBlockList, which only has a block pointer and 'next' pointer. It doesn't
+ * have weights or a dup count.
+ * 2. The preds list for a block is not sorted by block number.
+ * 3. The predecessors of the block following a BBJ_CALLFINALLY (the corresponding BBJ_ALWAYS,
+ * for normal, non-retless calls to the finally) are not computed.
+ * 4. The cheap preds lists will contain duplicates if a single switch table has multiple branches
+ * to the same block. Thus, we don't spend the time looking for duplicates for every edge we insert.
+ */
+void Compiler::fgComputeCheapPreds()
+{
+ noway_assert(!fgComputePredsDone); // We can't do this if we've got the full preds.
+ noway_assert(fgFirstBB != nullptr);
+
+ BasicBlock* block;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** In fgComputeCheapPreds()\n");
+ fgDispBasicBlocks();
+ printf("\n");
+ }
+#endif // DEBUG
+
+ // Clear out the cheap preds lists.
+ fgRemovePreds();
+
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ switch (block->bbJumpKind)
+ {
+ case BBJ_COND:
+ fgAddCheapPred(block->bbJumpDest, block);
+ fgAddCheapPred(block->bbNext, block);
+ break;
+
+ case BBJ_CALLFINALLY:
+ case BBJ_LEAVE: // If fgComputeCheapPreds is called before all blocks are imported, BBJ_LEAVE blocks are
+ // still in the BB list.
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ fgAddCheapPred(block->bbJumpDest, block);
+ break;
+
+ case BBJ_NONE:
+ fgAddCheapPred(block->bbNext, block);
+ break;
+
+ case BBJ_EHFILTERRET:
+ // Connect end of filter to catch handler.
+ // In a well-formed program, this cannot be null. Tolerate here, so that we can call
+ // fgComputeCheapPreds before fgImport on an ill-formed program; the problem will be detected in
+ // fgImport.
+ if (block->bbJumpDest != nullptr)
+ {
+ fgAddCheapPred(block->bbJumpDest, block);
+ }
+ break;
+
+ case BBJ_SWITCH:
+ unsigned jumpCnt;
+ jumpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab;
+ jumpTab = block->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ fgAddCheapPred(*jumpTab, block);
+ } while (++jumpTab, --jumpCnt);
+
+ break;
+
+ case BBJ_EHFINALLYRET: // It's expensive to compute the preds for this case, so we don't for the cheap
+ // preds.
+ case BBJ_THROW:
+ case BBJ_RETURN:
+ break;
+
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+ }
+
+ fgCheapPredsValid = true;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** After fgComputeCheapPreds()\n");
+ fgDispBasicBlocks();
+ printf("\n");
+ }
+#endif
+}
+
+/*****************************************************************************
+ * Add 'blockPred' to the cheap predecessor list of 'block'.
+ */
+
+void Compiler::fgAddCheapPred(BasicBlock* block, BasicBlock* blockPred)
+{
+ assert(!fgComputePredsDone);
+ assert(block != nullptr);
+ assert(blockPred != nullptr);
+
+ block->bbCheapPreds = new (this, CMK_FlowList) BasicBlockList(blockPred, block->bbCheapPreds);
+
+#if MEASURE_BLOCK_SIZE
+ genFlowNodeCnt += 1;
+ genFlowNodeSize += sizeof(BasicBlockList);
+#endif // MEASURE_BLOCK_SIZE
+}
+
+/*****************************************************************************
+ * Remove 'blockPred' from the cheap predecessor list of 'block'.
+ * If there are duplicate edges, only remove one of them.
+ */
+void Compiler::fgRemoveCheapPred(BasicBlock* block, BasicBlock* blockPred)
+{
+ assert(!fgComputePredsDone);
+ assert(fgCheapPredsValid);
+
+ flowList* oldEdge = nullptr;
+
+ assert(block != nullptr);
+ assert(blockPred != nullptr);
+ assert(block->bbCheapPreds != nullptr);
+
+ /* Is this the first block in the pred list? */
+ if (blockPred == block->bbCheapPreds->block)
+ {
+ block->bbCheapPreds = block->bbCheapPreds->next;
+ }
+ else
+ {
+ BasicBlockList* pred;
+ for (pred = block->bbCheapPreds; pred->next != nullptr; pred = pred->next)
+ {
+ if (blockPred == pred->next->block)
+ {
+ break;
+ }
+ }
+ noway_assert(pred->next != nullptr); // we better have found it!
+ pred->next = pred->next->next; // splice it out
+ }
+}
+
+void Compiler::fgRemovePreds()
+{
+ C_ASSERT(offsetof(BasicBlock, bbPreds) ==
+ offsetof(BasicBlock, bbCheapPreds)); // bbPreds and bbCheapPreds are at the same place in a union,
+ C_ASSERT(sizeof(((BasicBlock*)0)->bbPreds) ==
+ sizeof(((BasicBlock*)0)->bbCheapPreds)); // and are the same size. So, this function removes both.
+
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ block->bbPreds = nullptr;
+ }
+ fgComputePredsDone = false;
+ fgCheapPredsValid = false;
+}
+
+/*****************************************************************************
+ *
+ * Function called to compute the bbPreds lists.
+ */
+void Compiler::fgComputePreds()
+{
+ noway_assert(fgFirstBB);
+
+ BasicBlock* block;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** In fgComputePreds()\n");
+ fgDispBasicBlocks();
+ printf("\n");
+ }
+#endif // DEBUG
+
+ // reset the refs count for each basic block
+
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ block->bbRefs = 0;
+ }
+
+ /* the first block is always reachable! */
+ fgFirstBB->bbRefs = 1;
+
+ /* Treat the initial block as a jump target */
+ fgFirstBB->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL;
+
+ fgRemovePreds();
+
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ switch (block->bbJumpKind)
+ {
+ case BBJ_CALLFINALLY:
+ if (!(block->bbFlags & BBF_RETLESS_CALL))
+ {
+ assert(block->isBBCallAlwaysPair());
+
+ /* Mark the next block as being a jump target,
+ since the call target will return there */
+ PREFIX_ASSUME(block->bbNext != nullptr);
+ block->bbNext->bbFlags |= (BBF_JMP_TARGET | BBF_HAS_LABEL);
+ }
+
+ __fallthrough;
+
+ case BBJ_LEAVE: // Sometimes fgComputePreds is called before all blocks are imported, so BBJ_LEAVE
+ // blocks are still in the BB list.
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+
+ /* Mark the jump dest block as being a jump target */
+ block->bbJumpDest->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL;
+
+ fgAddRefPred(block->bbJumpDest, block, nullptr, true);
+
+ /* Is the next block reachable? */
+
+ if (block->bbJumpKind != BBJ_COND)
+ {
+ break;
+ }
+
+ noway_assert(block->bbNext);
+
+ /* Fall through, the next block is also reachable */
+ __fallthrough;
+
+ case BBJ_NONE:
+
+ fgAddRefPred(block->bbNext, block, nullptr, true);
+ break;
+
+ case BBJ_EHFILTERRET:
+
+ // Connect end of filter to catch handler.
+ // In a well-formed program, this cannot be null. Tolerate here, so that we can call
+ // fgComputePreds before fgImport on an ill-formed program; the problem will be detected in fgImport.
+ if (block->bbJumpDest != nullptr)
+ {
+ fgAddRefPred(block->bbJumpDest, block, nullptr, true);
+ }
+ break;
+
+ case BBJ_EHFINALLYRET:
+ {
+ /* Connect the end of the finally to the successor of
+ the call to this finally */
+
+ if (!block->hasHndIndex())
+ {
+ NO_WAY("endfinally outside a finally/fault block.");
+ }
+
+ unsigned hndIndex = block->getHndIndex();
+ EHblkDsc* ehDsc = ehGetDsc(hndIndex);
+
+ if (!ehDsc->HasFinallyOrFaultHandler())
+ {
+ NO_WAY("endfinally outside a finally/fault block.");
+ }
+
+ if (ehDsc->HasFinallyHandler())
+ {
+ // Find all BBJ_CALLFINALLY that branched to this finally handler.
+ BasicBlock* begBlk;
+ BasicBlock* endBlk;
+ ehGetCallFinallyBlockRange(hndIndex, &begBlk, &endBlk);
+
+ BasicBlock* finBeg = ehDsc->ebdHndBeg;
+ for (BasicBlock* bcall = begBlk; bcall != endBlk; bcall = bcall->bbNext)
+ {
+ if (bcall->bbJumpKind != BBJ_CALLFINALLY || bcall->bbJumpDest != finBeg)
+ {
+ continue;
+ }
+
+ noway_assert(bcall->isBBCallAlwaysPair());
+ fgAddRefPred(bcall->bbNext, block, nullptr, true);
+ }
+ }
+ }
+ break;
+
+ case BBJ_THROW:
+ case BBJ_RETURN:
+ break;
+
+ case BBJ_SWITCH:
+ unsigned jumpCnt;
+ jumpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab;
+ jumpTab = block->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ /* Mark the target block as being a jump target */
+ (*jumpTab)->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL;
+
+ fgAddRefPred(*jumpTab, block, nullptr, true);
+ } while (++jumpTab, --jumpCnt);
+
+ break;
+
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+ }
+
+ for (unsigned EHnum = 0; EHnum < compHndBBtabCount; EHnum++)
+ {
+ EHblkDsc* ehDsc = ehGetDsc(EHnum);
+
+ if (ehDsc->HasFilter())
+ {
+ ehDsc->ebdFilter->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL;
+ }
+
+ ehDsc->ebdHndBeg->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL;
+ }
+
+ fgModified = false;
+ fgComputePredsDone = true;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** After fgComputePreds()\n");
+ fgDispBasicBlocks();
+ printf("\n");
+ }
+#endif
+}
+
+unsigned Compiler::fgNSuccsOfFinallyRet(BasicBlock* block)
+{
+ BasicBlock* bb;
+ unsigned res;
+ fgSuccOfFinallyRetWork(block, ~0, &bb, &res);
+ return res;
+}
+
+BasicBlock* Compiler::fgSuccOfFinallyRet(BasicBlock* block, unsigned i)
+{
+ BasicBlock* bb;
+ unsigned res;
+ fgSuccOfFinallyRetWork(block, i, &bb, &res);
+ return bb;
+}
+
+void Compiler::fgSuccOfFinallyRetWork(BasicBlock* block, unsigned i, BasicBlock** bres, unsigned* nres)
+{
+ assert(block->hasHndIndex()); // Otherwise, endfinally outside a finally/fault block?
+
+ unsigned hndIndex = block->getHndIndex();
+ EHblkDsc* ehDsc = ehGetDsc(hndIndex);
+
+ assert(ehDsc->HasFinallyOrFaultHandler()); // Otherwise, endfinally outside a finally/fault block.
+
+ *bres = nullptr;
+ unsigned succNum = 0;
+
+ if (ehDsc->HasFinallyHandler())
+ {
+ BasicBlock* begBlk;
+ BasicBlock* endBlk;
+ ehGetCallFinallyBlockRange(hndIndex, &begBlk, &endBlk);
+
+ BasicBlock* finBeg = ehDsc->ebdHndBeg;
+
+ for (BasicBlock* bcall = begBlk; bcall != endBlk; bcall = bcall->bbNext)
+ {
+ if (bcall->bbJumpKind != BBJ_CALLFINALLY || bcall->bbJumpDest != finBeg)
+ {
+ continue;
+ }
+
+ assert(bcall->isBBCallAlwaysPair());
+
+ if (succNum == i)
+ {
+ *bres = bcall->bbNext;
+ return;
+ }
+ succNum++;
+ }
+ }
+ assert(i == ~0u || ehDsc->HasFaultHandler()); // Should reach here only for fault blocks.
+ if (i == ~0u)
+ {
+ *nres = succNum;
+ }
+}
+
+Compiler::SwitchUniqueSuccSet Compiler::GetDescriptorForSwitch(BasicBlock* switchBlk)
+{
+ assert(switchBlk->bbJumpKind == BBJ_SWITCH);
+ BlockToSwitchDescMap* switchMap = GetSwitchDescMap();
+ SwitchUniqueSuccSet res;
+ if (switchMap->Lookup(switchBlk, &res))
+ {
+ return res;
+ }
+ else
+ {
+ // We must compute the descriptor. Find which are dups, by creating a bit set with the unique successors.
+ // We create a temporary bitset of blocks to compute the unique set of successor blocks,
+ // since adding a block's number twice leaves just one "copy" in the bitset. Note that
+ // we specifically don't use the BlockSet type, because doing so would require making a
+ // call to EnsureBasicBlockEpoch() to make sure the epoch is up-to-date. However, that
+ // can create a new epoch, thus invalidating all existing BlockSet objects, such as
+ // reachability information stored in the blocks. To avoid that, we just use a local BitVec.
+
+ BitVecTraits blockVecTraits(fgBBNumMax + 1, this);
+ BitVec BITVEC_INIT_NOCOPY(uniqueSuccBlocks, BitVecOps::MakeEmpty(&blockVecTraits));
+ BasicBlock** jumpTable = switchBlk->bbJumpSwt->bbsDstTab;
+ unsigned jumpCount = switchBlk->bbJumpSwt->bbsCount;
+ for (unsigned i = 0; i < jumpCount; i++)
+ {
+ BasicBlock* targ = jumpTable[i];
+ BitVecOps::AddElemD(&blockVecTraits, uniqueSuccBlocks, targ->bbNum);
+ }
+ // Now we have a set of unique successors.
+ unsigned numNonDups = BitVecOps::Count(&blockVecTraits, uniqueSuccBlocks);
+
+ typedef BasicBlock* BasicBlockPtr;
+ BasicBlockPtr* nonDups = new (getAllocator()) BasicBlockPtr[numNonDups];
+
+ unsigned nonDupInd = 0;
+ // At this point, all unique targets are in "uniqueSuccBlocks". As we encounter each,
+ // add to nonDups, remove from "uniqueSuccBlocks".
+ for (unsigned i = 0; i < jumpCount; i++)
+ {
+ BasicBlock* targ = jumpTable[i];
+ if (BitVecOps::IsMember(&blockVecTraits, uniqueSuccBlocks, targ->bbNum))
+ {
+ nonDups[nonDupInd] = targ;
+ nonDupInd++;
+ BitVecOps::RemoveElemD(&blockVecTraits, uniqueSuccBlocks, targ->bbNum);
+ }
+ }
+
+ assert(nonDupInd == numNonDups);
+ assert(BitVecOps::Count(&blockVecTraits, uniqueSuccBlocks) == 0);
+ res.numDistinctSuccs = numNonDups;
+ res.nonDuplicates = nonDups;
+ switchMap->Set(switchBlk, res);
+ return res;
+ }
+}
+
+void Compiler::SwitchUniqueSuccSet::UpdateTarget(IAllocator* alloc,
+ BasicBlock* switchBlk,
+ BasicBlock* from,
+ BasicBlock* to)
+{
+ assert(switchBlk->bbJumpKind == BBJ_SWITCH); // Precondition.
+ unsigned jmpTabCnt = switchBlk->bbJumpSwt->bbsCount;
+ BasicBlock** jmpTab = switchBlk->bbJumpSwt->bbsDstTab;
+
+ // Is "from" still in the switch table (because it had more than one entry before?)
+ bool fromStillPresent = false;
+ for (unsigned i = 0; i < jmpTabCnt; i++)
+ {
+ if (jmpTab[i] == from)
+ {
+ fromStillPresent = true;
+ break;
+ }
+ }
+
+ // Is "to" already in "this"?
+ bool toAlreadyPresent = false;
+ for (unsigned i = 0; i < numDistinctSuccs; i++)
+ {
+ if (nonDuplicates[i] == to)
+ {
+ toAlreadyPresent = true;
+ break;
+ }
+ }
+
+ // Four cases:
+ // If "from" is still present, and "to" is already present, do nothing
+ // If "from" is still present, and "to" is not, must reallocate to add an entry.
+ // If "from" is not still present, and "to" is not present, write "to" where "from" was.
+ // If "from" is not still present, but "to" is present, remove "from".
+ if (fromStillPresent && toAlreadyPresent)
+ {
+ return;
+ }
+ else if (fromStillPresent && !toAlreadyPresent)
+ {
+ // reallocate to add an entry
+ typedef BasicBlock* BasicBlockPtr;
+ BasicBlockPtr* newNonDups = new (alloc) BasicBlockPtr[numDistinctSuccs + 1];
+ memcpy(newNonDups, nonDuplicates, numDistinctSuccs * sizeof(BasicBlock*));
+ newNonDups[numDistinctSuccs] = to;
+ numDistinctSuccs++;
+ nonDuplicates = newNonDups;
+ }
+ else if (!fromStillPresent && !toAlreadyPresent)
+ {
+#ifdef DEBUG
+ // write "to" where "from" was
+ bool foundFrom = false;
+#endif // DEBUG
+ for (unsigned i = 0; i < numDistinctSuccs; i++)
+ {
+ if (nonDuplicates[i] == from)
+ {
+ nonDuplicates[i] = to;
+#ifdef DEBUG
+ foundFrom = true;
+#endif // DEBUG
+ break;
+ }
+ }
+ assert(foundFrom);
+ }
+ else
+ {
+ assert(!fromStillPresent && toAlreadyPresent);
+#ifdef DEBUG
+ // remove "from".
+ bool foundFrom = false;
+#endif // DEBUG
+ for (unsigned i = 0; i < numDistinctSuccs; i++)
+ {
+ if (nonDuplicates[i] == from)
+ {
+ nonDuplicates[i] = nonDuplicates[numDistinctSuccs - 1];
+ numDistinctSuccs--;
+#ifdef DEBUG
+ foundFrom = true;
+#endif // DEBUG
+ break;
+ }
+ }
+ assert(foundFrom);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Simple utility function to remove an entry for a block in the switch desc
+ * map. So it can be called from other phases.
+ *
+ */
+void Compiler::fgInvalidateSwitchDescMapEntry(BasicBlock* block)
+{
+ // Check if map has no entries yet.
+ if (m_switchDescMap != nullptr)
+ {
+ m_switchDescMap->Remove(block);
+ }
+}
+
+void Compiler::UpdateSwitchTableTarget(BasicBlock* switchBlk, BasicBlock* from, BasicBlock* to)
+{
+ if (m_switchDescMap == nullptr)
+ {
+ return; // No mappings, nothing to do.
+ }
+
+ // Otherwise...
+ BlockToSwitchDescMap* switchMap = GetSwitchDescMap();
+ SwitchUniqueSuccSet* res = switchMap->LookupPointer(switchBlk);
+ if (res != nullptr)
+ {
+ // If no result, nothing to do. Otherwise, update it.
+ res->UpdateTarget(getAllocator(), switchBlk, from, to);
+ }
+}
+
+/*****************************************************************************
+ * For a block that is in a handler region, find the first block of the most-nested
+ * handler containing the block.
+ */
+BasicBlock* Compiler::fgFirstBlockOfHandler(BasicBlock* block)
+{
+ assert(block->hasHndIndex());
+ return ehGetDsc(block->getHndIndex())->ebdHndBeg;
+}
+
+/*****************************************************************************
+ *
+ * Function called to find back edges and return blocks and mark them as needing GC Polls. This marks all
+ * blocks.
+ */
+void Compiler::fgMarkGCPollBlocks()
+{
+ if (GCPOLL_NONE == opts.compGCPollType)
+ {
+ return;
+ }
+
+#ifdef DEBUG
+ /* Check that the flowgraph data (bbNum, bbRefs, bbPreds) is up-to-date */
+ fgDebugCheckBBlist();
+#endif
+
+ BasicBlock* block;
+
+ // Return blocks always need GC polls. In addition, all back edges (including those from switch
+ // statements) need GC polls. The poll is on the block with the outgoing back edge (or ret), rather than
+ // on the destination or on the edge itself.
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ bool blockNeedsPoll = false;
+ switch (block->bbJumpKind)
+ {
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ blockNeedsPoll = (block->bbJumpDest->bbNum <= block->bbNum);
+ break;
+
+ case BBJ_RETURN:
+ blockNeedsPoll = true;
+ break;
+
+ case BBJ_SWITCH:
+ unsigned jumpCnt;
+ jumpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab;
+ jumpTab = block->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ if ((*jumpTab)->bbNum <= block->bbNum)
+ {
+ blockNeedsPoll = true;
+ break;
+ }
+ } while (++jumpTab, --jumpCnt);
+ break;
+
+ default:
+ break;
+ }
+
+ if (blockNeedsPoll)
+ {
+ block->bbFlags |= BBF_NEEDS_GCPOLL;
+ }
+ }
+}
+
+void Compiler::fgInitBlockVarSets()
+{
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ block->InitVarSets(this);
+ }
+
+ // QMarks are much like blocks, and need their VarSets initialized.
+ assert(!compIsForInlining());
+ for (unsigned i = 0; i < compQMarks->Size(); i++)
+ {
+ GenTreePtr qmark = compQMarks->Get(i);
+ // Perhaps the gtOper of a QMark node was changed to something else since it was created and put on this list.
+ // So can't hurt to check.
+ if (qmark->OperGet() == GT_QMARK)
+ {
+ VarSetOps::AssignAllowUninitRhs(this, qmark->gtQmark.gtThenLiveSet, VarSetOps::UninitVal());
+ VarSetOps::AssignAllowUninitRhs(this, qmark->gtQmark.gtElseLiveSet, VarSetOps::UninitVal());
+ }
+ }
+ fgBBVarSetsInited = true;
+}
+
+/*****************************************************************************
+ *
+ * The following does the final pass on BBF_NEEDS_GCPOLL and then actually creates the GC Polls.
+ */
+void Compiler::fgCreateGCPolls()
+{
+ if (GCPOLL_NONE == opts.compGCPollType)
+ {
+ return;
+ }
+
+ bool createdPollBlocks = false;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgCreateGCPolls() for %s\n", info.compFullName);
+ }
+#endif // DEBUG
+
+ if (!(opts.MinOpts() || opts.compDbgCode))
+ {
+ // Remove polls from well formed loops with a constant upper bound.
+ for (unsigned lnum = 0; lnum < optLoopCount; ++lnum)
+ {
+ // Look for constant counted loops that run for a short duration. This logic is very similar to
+ // what's in code:Compiler::optUnrollLoops, since they have similar constraints. However, this
+ // logic is much more permissive since we're not doing a complex transformation.
+
+ /* TODO-Cleanup:
+ * I feel bad cloning so much logic from optUnrollLoops
+ */
+
+ // Filter out loops not meeting the obvious preconditions.
+ //
+ if (optLoopTable[lnum].lpFlags & LPFLG_REMOVED)
+ {
+ continue;
+ }
+
+ if (!(optLoopTable[lnum].lpFlags & LPFLG_CONST))
+ {
+ continue;
+ }
+
+ BasicBlock* head = optLoopTable[lnum].lpHead;
+ BasicBlock* bottom = optLoopTable[lnum].lpBottom;
+
+ // Loops dominated by GC_SAFE_POINT won't have this set.
+ if (!(bottom->bbFlags & BBF_NEEDS_GCPOLL))
+ {
+ continue;
+ }
+
+ /* Get the loop data:
+ - initial constant
+ - limit constant
+ - iterator
+ - iterator increment
+ - increment operation type (i.e. ASG_ADD, ASG_SUB, etc...)
+ - loop test type (i.e. GT_GE, GT_LT, etc...)
+ */
+
+ int lbeg = optLoopTable[lnum].lpConstInit;
+ int llim = optLoopTable[lnum].lpConstLimit();
+ genTreeOps testOper = optLoopTable[lnum].lpTestOper();
+
+ int lvar = optLoopTable[lnum].lpIterVar();
+ int iterInc = optLoopTable[lnum].lpIterConst();
+ genTreeOps iterOper = optLoopTable[lnum].lpIterOper();
+
+ var_types iterOperType = optLoopTable[lnum].lpIterOperType();
+ bool unsTest = (optLoopTable[lnum].lpTestTree->gtFlags & GTF_UNSIGNED) != 0;
+ if (lvaTable[lvar].lvAddrExposed)
+ { // Can't reason about the value of the iteration variable.
+ continue;
+ }
+
+ unsigned totalIter;
+
+ /* Find the number of iterations - the function returns false if not a constant number */
+
+ if (!optComputeLoopRep(lbeg, llim, iterInc, iterOper, iterOperType, testOper, unsTest,
+ // The value here doesn't matter for this variation of the optimization
+ true, &totalIter))
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Could not compute loop iterations for loop from BB%02u to BB%02u", head->bbNum,
+ bottom->bbNum);
+ }
+#endif // DEBUG
+ (void)head; // suppress gcc error.
+
+ continue;
+ }
+
+ /* Forget it if there are too many repetitions or not a constant loop */
+
+ static const unsigned ITER_LIMIT = 256;
+ if (totalIter > ITER_LIMIT)
+ {
+ continue;
+ }
+
+ // It is safe to elminate the poll from this loop.
+ bottom->bbFlags &= ~BBF_NEEDS_GCPOLL;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Removing poll in block BB%02u because it forms a bounded counted loop\n", bottom->bbNum);
+ }
+#endif // DEBUG
+ }
+ }
+
+ // Final chance to optimize the polls. Move all polls in loops from the bottom of the loop up to the
+ // loop head. Also eliminate all epilog polls in non-leaf methods. This only works if we have dominator
+ // information.
+ if (fgDomsComputed)
+ {
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ if (!(block->bbFlags & BBF_NEEDS_GCPOLL))
+ {
+ continue;
+ }
+
+ if (block->bbJumpKind == BBJ_COND || block->bbJumpKind == BBJ_ALWAYS)
+ {
+ // make sure that this is loop-like
+ if (!fgReachable(block->bbJumpDest, block))
+ {
+ block->bbFlags &= ~BBF_NEEDS_GCPOLL;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Removing poll in block BB%02u because it is not loop\n", block->bbNum);
+ }
+#endif // DEBUG
+ continue;
+ }
+ }
+ else if (!(block->bbJumpKind == BBJ_RETURN || block->bbJumpKind == BBJ_SWITCH))
+ {
+ noway_assert(!"GC Poll on a block that has no control transfer.");
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Removing poll in block BB%02u because it is not a jump\n", block->bbNum);
+ }
+#endif // DEBUG
+ block->bbFlags &= ~BBF_NEEDS_GCPOLL;
+ continue;
+ }
+
+ // Because of block compaction, it's possible to end up with a block that is both poll and safe.
+ // Clean those up now.
+
+ if (block->bbFlags & BBF_GC_SAFE_POINT)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Removing poll in return block BB%02u because it is GC Safe\n", block->bbNum);
+ }
+#endif // DEBUG
+ block->bbFlags &= ~BBF_NEEDS_GCPOLL;
+ continue;
+ }
+
+ if (block->bbJumpKind == BBJ_RETURN)
+ {
+ if (!optReachWithoutCall(fgFirstBB, block))
+ {
+ // check to see if there is a call along the path between the first block and the return
+ // block.
+ block->bbFlags &= ~BBF_NEEDS_GCPOLL;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Removing poll in return block BB%02u because it dominated by a call\n", block->bbNum);
+ }
+#endif // DEBUG
+ continue;
+ }
+ }
+ }
+ }
+
+ noway_assert(!fgGCPollsCreated);
+ BasicBlock* block;
+ fgGCPollsCreated = true;
+
+ // Walk through the blocks and hunt for a block that has BBF_NEEDS_GCPOLL
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ // Because of block compaction, it's possible to end up with a block that is both poll and safe.
+ // And if !fgDomsComputed, we won't have cleared them, so skip them now
+ if (!(block->bbFlags & BBF_NEEDS_GCPOLL) || (block->bbFlags & BBF_GC_SAFE_POINT))
+ {
+ continue;
+ }
+
+ // This block needs a poll. We either just insert a callout or we split the block and inline part of
+ // the test. This depends on the value of opts.compGCPollType.
+
+ // If we're doing GCPOLL_CALL, just insert a GT_CALL node before the last node in the block.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef DEBUG
+ switch (block->bbJumpKind)
+ {
+ case BBJ_RETURN:
+ case BBJ_ALWAYS:
+ case BBJ_COND:
+ case BBJ_SWITCH:
+ break;
+ default:
+ noway_assert(!"Unknown block type for BBF_NEEDS_GCPOLL");
+ }
+#endif // DEBUG
+
+ noway_assert(opts.compGCPollType);
+
+ GCPollType pollType = opts.compGCPollType;
+ // pollType is set to either CALL or INLINE at this point. Below is the list of places where we
+ // can't or don't want to emit an inline check. Check all of those. If after all of that we still
+ // have INLINE, then emit an inline check.
+
+ if (opts.MinOpts() || opts.compDbgCode)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Selecting CALL poll in block BB%02u because of debug/minopts\n", block->bbNum);
+ }
+#endif // DEBUG
+
+ // Don't split blocks and create inlined polls unless we're optimizing.
+ pollType = GCPOLL_CALL;
+ }
+ else if (genReturnBB == block)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Selecting CALL poll in block BB%02u because it is the single return block\n", block->bbNum);
+ }
+#endif // DEBUG
+
+ // we don't want to split the single return block
+ pollType = GCPOLL_CALL;
+ }
+ else if (BBJ_SWITCH == block->bbJumpKind)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Selecting CALL poll in block BB%02u because it is a loop formed by a SWITCH\n", block->bbNum);
+ }
+#endif // DEBUG
+
+ // I don't want to deal with all the outgoing edges of a switch block.
+ pollType = GCPOLL_CALL;
+ }
+
+ // TODO-Cleanup: potentially don't split if we're in an EH region.
+
+ createdPollBlocks |= fgCreateGCPoll(pollType, block);
+ }
+
+ // If we split a block to create a GC Poll, then rerun fgReorderBlocks to push the rarely run blocks out
+ // past the epilog. We should never split blocks unless we're optimizing.
+ if (createdPollBlocks)
+ {
+ noway_assert(!opts.MinOpts() && !opts.compDbgCode);
+ fgReorderBlocks();
+ }
+}
+
+/*****************************************************************************
+ *
+ * Actually create a GCPoll in the given block. Returns true if it created
+ * a basic block.
+ */
+
+bool Compiler::fgCreateGCPoll(GCPollType pollType, BasicBlock* block)
+{
+ assert(!(block->bbFlags & BBF_GC_SAFE_POINT));
+ bool createdPollBlocks;
+
+ void* addrTrap;
+ void* pAddrOfCaptureThreadGlobal;
+
+ addrTrap = info.compCompHnd->getAddrOfCaptureThreadGlobal(&pAddrOfCaptureThreadGlobal);
+
+#ifdef ENABLE_FAST_GCPOLL_HELPER
+ // I never want to split blocks if we've got two indirections here.
+ // This is a size trade-off assuming the VM has ENABLE_FAST_GCPOLL_HELPER.
+ // So don't do it when that is off
+ if (pAddrOfCaptureThreadGlobal != NULL)
+ {
+ pollType = GCPOLL_CALL;
+ }
+#endif // ENABLE_FAST_GCPOLL_HELPER
+
+ if (GCPOLL_CALL == pollType)
+ {
+ createdPollBlocks = false;
+ GenTreePtr tree = gtNewHelperCallNode(CORINFO_HELP_POLL_GC, TYP_VOID);
+#if GTF_CALL_REG_SAVE
+ tree->gtCall.gtCallMoreFlags |= GTF_CALL_REG_SAVE;
+#endif // GTF_CALL_REG_SAVE
+
+ // for BBJ_ALWAYS I don't need to insert it before the condition. Just append it.
+ if (block->bbJumpKind == BBJ_ALWAYS)
+ {
+ fgInsertStmtAtEnd(block, tree);
+ }
+ else
+ {
+ GenTreeStmt* newStmt = fgInsertStmtNearEnd(block, tree);
+ // For DDB156656, we need to associate the GC Poll with the IL offset (and therefore sequence
+ // point) of the tree before which we inserted the poll. One example of when this is a
+ // problem:
+ // if (...) { //1
+ // ...
+ // } //2
+ // else { //3
+ // ...
+ // }
+ // (gcpoll) //4
+ // return. //5
+ //
+ // If we take the if statement at 1, we encounter a jump at 2. This jumps over the else
+ // and lands at 4. 4 is where we inserted the gcpoll. However, that is associated with
+ // the sequence point a 3. Therefore, the debugger displays the wrong source line at the
+ // gc poll location.
+ //
+ // More formally, if control flow targets an instruction, that instruction must be the
+ // start of a new sequence point.
+ if (newStmt->gtNext)
+ {
+ // Is it possible for gtNext to be NULL?
+ noway_assert(newStmt->gtNext->gtOper == GT_STMT);
+ newStmt->gtStmtILoffsx = newStmt->gtNextStmt->gtStmtILoffsx;
+ }
+ }
+
+ block->bbFlags |= BBF_GC_SAFE_POINT;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*** creating GC Poll in block BB%02u\n", block->bbNum);
+ gtDispTreeList(block->bbTreeList);
+ }
+#endif // DEBUG
+ }
+ else
+ {
+ createdPollBlocks = true;
+ // if we're doing GCPOLL_INLINE, then:
+ // 1) Create two new blocks: Poll and Bottom. The original block is called Top.
+
+ // I want to create:
+ // top -> poll -> bottom (lexically)
+ // so that we jump over poll to get to bottom.
+ BasicBlock* top = block;
+ BasicBlock* poll = fgNewBBafter(BBJ_NONE, top, true);
+ BasicBlock* bottom = fgNewBBafter(top->bbJumpKind, poll, true);
+ BBjumpKinds oldJumpKind = top->bbJumpKind;
+
+ // Update block flags
+ unsigned originalFlags;
+ originalFlags = top->bbFlags | BBF_GC_SAFE_POINT;
+
+ // Unlike Fei's inliner from puclr, I'm allowed to split loops.
+ // And we keep a few other flags...
+ noway_assert((originalFlags & (BBF_SPLIT_NONEXIST & ~(BBF_LOOP_HEAD | BBF_LOOP_CALL0 | BBF_LOOP_CALL1))) == 0);
+ top->bbFlags = originalFlags & (~BBF_SPLIT_LOST | BBF_GC_SAFE_POINT);
+ bottom->bbFlags |= originalFlags & (BBF_SPLIT_GAINED | BBF_IMPORTED | BBF_GC_SAFE_POINT);
+ bottom->inheritWeight(top);
+ poll->bbFlags |= originalFlags & (BBF_SPLIT_GAINED | BBF_IMPORTED | BBF_GC_SAFE_POINT);
+
+ // 9) Mark Poll as rarely run.
+ poll->bbSetRunRarely();
+
+ // 5) Bottom gets all the outgoing edges and inherited flags of Original.
+ bottom->bbJumpDest = top->bbJumpDest;
+
+ // 2) Add a GC_CALL node to Poll.
+ GenTreePtr tree = gtNewHelperCallNode(CORINFO_HELP_POLL_GC, TYP_VOID);
+#if GTF_CALL_REG_SAVE
+ tree->gtCall.gtCallMoreFlags |= GTF_CALL_REG_SAVE;
+#endif // GTF_CALL_REG_SAVE
+ fgInsertStmtAtEnd(poll, tree);
+
+ // 3) Remove the last statement from Top and add it to Bottom.
+ if (oldJumpKind != BBJ_ALWAYS)
+ {
+ // if I'm always jumping to the target, then this is not a condition that needs moving.
+ GenTreeStmt* stmt = top->firstStmt();
+ while (stmt->gtNext)
+ {
+ stmt = stmt->gtNextStmt;
+ }
+ fgRemoveStmt(top, stmt);
+ fgInsertStmtAtEnd(bottom, stmt);
+ }
+
+ // for BBJ_ALWAYS blocks, bottom is an empty block.
+
+ // 4) Create a GT_EQ node that checks against g_TrapReturningThreads. True jumps to Bottom,
+ // false falls through to poll. Add this to the end of Top. Top is now BBJ_COND. Bottom is
+ // now a jump target
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef ENABLE_FAST_GCPOLL_HELPER
+ // Prefer the fast gc poll helepr over the double indirection
+ noway_assert(pAddrOfCaptureThreadGlobal == nullptr);
+#endif
+
+ GenTreePtr trap;
+ if (pAddrOfCaptureThreadGlobal != nullptr)
+ {
+ trap = gtNewOperNode(GT_IND, TYP_I_IMPL,
+ gtNewIconHandleNode((size_t)pAddrOfCaptureThreadGlobal, GTF_ICON_PTR_HDL));
+ }
+ else
+ {
+ trap = gtNewIconHandleNode((size_t)addrTrap, GTF_ICON_PTR_HDL);
+ }
+
+ GenTreePtr trapRelop = gtNewOperNode(GT_EQ, TYP_INT,
+ // lhs [g_TrapReturningThreads]
+ gtNewOperNode(GT_IND, TYP_INT, trap),
+ // rhs 0
+ gtNewIconNode(0, TYP_INT));
+ trapRelop->gtFlags |= GTF_RELOP_JMP_USED | GTF_DONT_CSE; // Treat reading g_TrapReturningThreads as volatile.
+ GenTreePtr trapCheck = gtNewOperNode(GT_JTRUE, TYP_VOID, trapRelop);
+ fgInsertStmtAtEnd(top, trapCheck);
+ top->bbJumpDest = bottom;
+ top->bbJumpKind = BBJ_COND;
+ bottom->bbFlags |= BBF_JMP_TARGET;
+
+ // 7) Bottom has Top and Poll as its predecessors. Poll has just Top as a predecessor.
+ fgAddRefPred(bottom, poll);
+ fgAddRefPred(bottom, top);
+ fgAddRefPred(poll, top);
+
+ // 8) Replace Top with Bottom in the predecessor list of all outgoing edges from Bottom (1 for
+ // jumps, 2 for conditional branches, N for switches).
+ switch (oldJumpKind)
+ {
+ case BBJ_RETURN:
+ // no successors
+ break;
+ case BBJ_COND:
+ // replace predecessor in the fall through block.
+ noway_assert(bottom->bbNext);
+ fgReplacePred(bottom->bbNext, top, bottom);
+
+ // fall through for the jump target
+ __fallthrough;
+
+ case BBJ_ALWAYS:
+ fgReplacePred(bottom->bbJumpDest, top, bottom);
+ break;
+ case BBJ_SWITCH:
+ NO_WAY("SWITCH should be a call rather than an inlined poll.");
+ break;
+ default:
+ NO_WAY("Unknown block type for updating predecessor lists.");
+ }
+
+ top->bbFlags &= ~BBF_NEEDS_GCPOLL;
+ noway_assert(!(poll->bbFlags & BBF_NEEDS_GCPOLL));
+ noway_assert(!(bottom->bbFlags & BBF_NEEDS_GCPOLL));
+
+ if (compCurBB == top)
+ {
+ compCurBB = bottom;
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*** creating inlined GC Poll in top block BB%02u\n", top->bbNum);
+ gtDispTreeList(top->bbTreeList);
+ printf(" poll block is BB%02u\n", poll->bbNum);
+ gtDispTreeList(poll->bbTreeList);
+ printf(" bottom block is BB%02u\n", bottom->bbNum);
+ gtDispTreeList(bottom->bbTreeList);
+ }
+#endif // DEBUG
+ }
+
+ return createdPollBlocks;
+}
+
+/*****************************************************************************
+ *
+ * The following helps find a basic block given its PC offset.
+ */
+
+void Compiler::fgInitBBLookup()
+{
+ BasicBlock** dscBBptr;
+ BasicBlock* tmpBBdesc;
+
+ /* Allocate the basic block table */
+
+ dscBBptr = fgBBs = new (this, CMK_BasicBlock) BasicBlock*[fgBBcount];
+
+ /* Walk all the basic blocks, filling in the table */
+
+ for (tmpBBdesc = fgFirstBB; tmpBBdesc; tmpBBdesc = tmpBBdesc->bbNext)
+ {
+ *dscBBptr++ = tmpBBdesc;
+ }
+
+ noway_assert(dscBBptr == fgBBs + fgBBcount);
+}
+
+BasicBlock* Compiler::fgLookupBB(unsigned addr)
+{
+ unsigned lo;
+ unsigned hi;
+
+ /* Do a binary search */
+
+ for (lo = 0, hi = fgBBcount - 1;;)
+ {
+
+ AGAIN:;
+
+ if (lo > hi)
+ {
+ break;
+ }
+
+ unsigned mid = (lo + hi) / 2;
+ BasicBlock* dsc = fgBBs[mid];
+
+ // We introduce internal blocks for BBJ_CALLFINALLY. Skip over these.
+
+ while (dsc->bbFlags & BBF_INTERNAL)
+ {
+ dsc = dsc->bbNext;
+ mid++;
+
+ // We skipped over too many, Set hi back to the original mid - 1
+
+ if (mid > hi)
+ {
+ mid = (lo + hi) / 2;
+ hi = mid - 1;
+ goto AGAIN;
+ }
+ }
+
+ unsigned pos = dsc->bbCodeOffs;
+
+ if (pos < addr)
+ {
+ if ((lo == hi) && (lo == (fgBBcount - 1)))
+ {
+ noway_assert(addr == dsc->bbCodeOffsEnd);
+ return nullptr; // NULL means the end of method
+ }
+ lo = mid + 1;
+ continue;
+ }
+
+ if (pos > addr)
+ {
+ hi = mid - 1;
+ continue;
+ }
+
+ return dsc;
+ }
+#ifdef DEBUG
+ printf("ERROR: Couldn't find basic block at offset %04X\n", addr);
+#endif // DEBUG
+ NO_WAY("fgLookupBB failed.");
+}
+
+/*****************************************************************************
+ *
+ * The 'jump target' array uses the following flags to indicate what kind
+ * of label is present.
+ */
+
+#define JT_NONE 0x00 // This IL offset is never used
+#define JT_ADDR 0x01 // merely make sure this is an OK address
+#define JT_JUMP 0x02 // 'normal' jump target
+#define JT_MULTI 0x04 // target of multiple jumps
+
+inline void Compiler::fgMarkJumpTarget(BYTE* jumpTarget, unsigned offs)
+{
+ /* Make sure we set JT_MULTI if target of multiple jumps */
+
+ noway_assert(JT_MULTI == JT_JUMP << 1);
+
+ jumpTarget[offs] |= (jumpTarget[offs] & JT_JUMP) << 1 | JT_JUMP;
+}
+
+//------------------------------------------------------------------------
+// FgStack: simple stack model for the inlinee's evaluation stack.
+//
+// Model the inputs available to various operations in the inline body.
+// Tracks constants, arguments, array lengths.
+
+class FgStack
+{
+public:
+ FgStack() : slot0(SLOT_INVALID), slot1(SLOT_INVALID), depth(0)
+ {
+ // Empty
+ }
+
+ void Clear()
+ {
+ depth = 0;
+ }
+ void PushUnknown()
+ {
+ Push(SLOT_UNKNOWN);
+ }
+ void PushConstant()
+ {
+ Push(SLOT_CONSTANT);
+ }
+ void PushArrayLen()
+ {
+ Push(SLOT_ARRAYLEN);
+ }
+ void PushArgument(unsigned arg)
+ {
+ Push(SLOT_ARGUMENT + arg);
+ }
+ unsigned GetSlot0() const
+ {
+ assert(depth >= 1);
+ return slot0;
+ }
+ unsigned GetSlot1() const
+ {
+ assert(depth >= 2);
+ return slot1;
+ }
+ static bool IsConstant(unsigned value)
+ {
+ return value == SLOT_CONSTANT;
+ }
+ static bool IsArrayLen(unsigned value)
+ {
+ return value == SLOT_ARRAYLEN;
+ }
+ static bool IsArgument(unsigned value)
+ {
+ return value >= SLOT_ARGUMENT;
+ }
+ static unsigned SlotTypeToArgNum(unsigned value)
+ {
+ assert(IsArgument(value));
+ return value - SLOT_ARGUMENT;
+ }
+ bool IsStackTwoDeep() const
+ {
+ return depth == 2;
+ }
+ bool IsStackOneDeep() const
+ {
+ return depth == 1;
+ }
+ bool IsStackAtLeastOneDeep() const
+ {
+ return depth >= 1;
+ }
+
+private:
+ enum
+ {
+ SLOT_INVALID = UINT_MAX,
+ SLOT_UNKNOWN = 0,
+ SLOT_CONSTANT = 1,
+ SLOT_ARRAYLEN = 2,
+ SLOT_ARGUMENT = 3
+ };
+
+ void Push(int type)
+ {
+ switch (depth)
+ {
+ case 0:
+ ++depth;
+ slot0 = type;
+ break;
+ case 1:
+ ++depth;
+ __fallthrough;
+ case 2:
+ slot1 = slot0;
+ slot0 = type;
+ }
+ }
+
+ unsigned slot0;
+ unsigned slot1;
+ unsigned depth;
+};
+
+//------------------------------------------------------------------------
+// fgFindJumpTargets: walk the IL stream, determining jump target offsets
+//
+// Arguments:
+// codeAddr - base address of the IL code buffer
+// codeSize - number of bytes in the IL code buffer
+// jumpTarget - [OUT] byte array for flagging jump targets
+//
+// Notes:
+// If inlining or prejitting the root, this method also makes
+// various observations about the method that factor into inline
+// decisions.
+//
+// May throw an exception if the IL is malformed.
+//
+// jumpTarget[N] is set to a JT_* value if IL offset N is a
+// jump target in the method.
+//
+// Also sets lvAddrExposed and lvArgWrite in lvaTable[].
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable : 21000) // Suppress PREFast warning about overly large function
+#endif
+
+void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, BYTE* jumpTarget)
+{
+ const BYTE* codeBegp = codeAddr;
+ const BYTE* codeEndp = codeAddr + codeSize;
+ unsigned varNum;
+ bool seenJump = false;
+ var_types varType = DUMMY_INIT(TYP_UNDEF); // TYP_ type
+ typeInfo ti; // Verifier type.
+ bool typeIsNormed = false;
+ FgStack pushedStack;
+ const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
+ const bool makeInlineObservations = (compInlineResult != nullptr);
+ const bool isInlining = compIsForInlining();
+
+ if (makeInlineObservations)
+ {
+ // Observe force inline state and code size.
+ compInlineResult->NoteBool(InlineObservation::CALLEE_IS_FORCE_INLINE, isForceInline);
+ compInlineResult->NoteInt(InlineObservation::CALLEE_IL_CODE_SIZE, codeSize);
+
+#ifdef DEBUG
+
+ // If inlining, this method should still be a candidate.
+ if (isInlining)
+ {
+ assert(compInlineResult->IsCandidate());
+ }
+
+#endif // DEBUG
+
+ // note that we're starting to look at the opcodes.
+ compInlineResult->Note(InlineObservation::CALLEE_BEGIN_OPCODE_SCAN);
+ }
+
+ while (codeAddr < codeEndp)
+ {
+ OPCODE opcode = (OPCODE)getU1LittleEndian(codeAddr);
+ codeAddr += sizeof(__int8);
+ opts.instrCount++;
+ typeIsNormed = false;
+
+ DECODE_OPCODE:
+
+ if (opcode >= CEE_COUNT)
+ {
+ BADCODE3("Illegal opcode", ": %02X", (int)opcode);
+ }
+
+ if ((opcode >= CEE_LDARG_0 && opcode <= CEE_STLOC_S) || (opcode >= CEE_LDARG && opcode <= CEE_STLOC))
+ {
+ opts.lvRefCount++;
+ }
+
+ if (makeInlineObservations && (opcode >= CEE_LDNULL) && (opcode <= CEE_LDC_R8))
+ {
+ pushedStack.PushConstant();
+ }
+
+ unsigned sz = opcodeSizes[opcode];
+
+ switch (opcode)
+ {
+ case CEE_PREFIX1:
+ {
+ if (codeAddr >= codeEndp)
+ {
+ goto TOO_FAR;
+ }
+ opcode = (OPCODE)(256 + getU1LittleEndian(codeAddr));
+ codeAddr += sizeof(__int8);
+ goto DECODE_OPCODE;
+ }
+
+ case CEE_PREFIX2:
+ case CEE_PREFIX3:
+ case CEE_PREFIX4:
+ case CEE_PREFIX5:
+ case CEE_PREFIX6:
+ case CEE_PREFIX7:
+ case CEE_PREFIXREF:
+ {
+ BADCODE3("Illegal opcode", ": %02X", (int)opcode);
+ }
+
+ case CEE_CALL:
+ case CEE_CALLVIRT:
+ {
+ // There has to be code after the call, otherwise the inlinee is unverifiable.
+ if (isInlining)
+ {
+
+ noway_assert(codeAddr < codeEndp - sz);
+ }
+
+ // If the method has a call followed by a ret, assume that
+ // it is a wrapper method.
+ if (makeInlineObservations)
+ {
+ if ((OPCODE)getU1LittleEndian(codeAddr + sz) == CEE_RET)
+ {
+ compInlineResult->Note(InlineObservation::CALLEE_LOOKS_LIKE_WRAPPER);
+ }
+ }
+ }
+ break;
+
+ case CEE_LEAVE:
+ case CEE_LEAVE_S:
+ case CEE_BR:
+ case CEE_BR_S:
+ case CEE_BRFALSE:
+ case CEE_BRFALSE_S:
+ case CEE_BRTRUE:
+ case CEE_BRTRUE_S:
+ case CEE_BEQ:
+ case CEE_BEQ_S:
+ case CEE_BGE:
+ case CEE_BGE_S:
+ case CEE_BGE_UN:
+ case CEE_BGE_UN_S:
+ case CEE_BGT:
+ case CEE_BGT_S:
+ case CEE_BGT_UN:
+ case CEE_BGT_UN_S:
+ case CEE_BLE:
+ case CEE_BLE_S:
+ case CEE_BLE_UN:
+ case CEE_BLE_UN_S:
+ case CEE_BLT:
+ case CEE_BLT_S:
+ case CEE_BLT_UN:
+ case CEE_BLT_UN_S:
+ case CEE_BNE_UN:
+ case CEE_BNE_UN_S:
+ {
+ seenJump = true;
+
+ if (codeAddr > codeEndp - sz)
+ {
+ goto TOO_FAR;
+ }
+
+ // Compute jump target address
+ signed jmpDist = (sz == 1) ? getI1LittleEndian(codeAddr) : getI4LittleEndian(codeAddr);
+
+ if (compIsForInlining() && jmpDist == 0 &&
+ (opcode == CEE_LEAVE || opcode == CEE_LEAVE_S || opcode == CEE_BR || opcode == CEE_BR_S))
+ {
+ break; /* NOP */
+ }
+
+ unsigned jmpAddr = (IL_OFFSET)(codeAddr - codeBegp) + sz + jmpDist;
+
+ // Make sure target is reasonable
+ if (jmpAddr >= codeSize)
+ {
+ BADCODE3("code jumps to outer space", " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
+ }
+
+ // Mark the jump target
+ fgMarkJumpTarget(jumpTarget, jmpAddr);
+
+ // See if jump might be sensitive to inlining
+ if (makeInlineObservations && (opcode != CEE_BR_S) && (opcode != CEE_BR))
+ {
+ fgObserveInlineConstants(opcode, pushedStack, isInlining);
+ }
+ }
+ break;
+
+ case CEE_SWITCH:
+ {
+ seenJump = true;
+
+ if (makeInlineObservations)
+ {
+ compInlineResult->Note(InlineObservation::CALLEE_HAS_SWITCH);
+
+ // Fail fast, if we're inlining and can't handle this.
+ if (isInlining && compInlineResult->IsFailure())
+ {
+ return;
+ }
+ }
+
+ // Make sure we don't go past the end reading the number of cases
+ if (codeAddr > codeEndp - sizeof(DWORD))
+ {
+ goto TOO_FAR;
+ }
+
+ // Read the number of cases
+ unsigned jmpCnt = getU4LittleEndian(codeAddr);
+ codeAddr += sizeof(DWORD);
+
+ if (jmpCnt > codeSize / sizeof(DWORD))
+ {
+ goto TOO_FAR;
+ }
+
+ // Find the end of the switch table
+ unsigned jmpBase = (unsigned)((codeAddr - codeBegp) + jmpCnt * sizeof(DWORD));
+
+ // Make sure there is more code after the switch
+ if (jmpBase >= codeSize)
+ {
+ goto TOO_FAR;
+ }
+
+ // jmpBase is also the target of the default case, so mark it
+ fgMarkJumpTarget(jumpTarget, jmpBase);
+
+ // Process table entries
+ while (jmpCnt > 0)
+ {
+ unsigned jmpAddr = jmpBase + getI4LittleEndian(codeAddr);
+ codeAddr += 4;
+
+ if (jmpAddr >= codeSize)
+ {
+ BADCODE3("jump target out of range", " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
+ }
+
+ fgMarkJumpTarget(jumpTarget, jmpAddr);
+ jmpCnt--;
+ }
+
+ // We've advanced past all the bytes in this instruction
+ sz = 0;
+ }
+ break;
+
+ case CEE_UNALIGNED:
+ case CEE_CONSTRAINED:
+ case CEE_READONLY:
+ case CEE_VOLATILE:
+ case CEE_TAILCALL:
+ {
+ if (codeAddr >= codeEndp)
+ {
+ goto TOO_FAR;
+ }
+ }
+ break;
+
+ case CEE_STARG:
+ case CEE_STARG_S:
+ {
+ noway_assert(sz == sizeof(BYTE) || sz == sizeof(WORD));
+
+ if (codeAddr > codeEndp - sz)
+ {
+ goto TOO_FAR;
+ }
+
+ varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr) : getU2LittleEndian(codeAddr);
+ varNum = compMapILargNum(varNum); // account for possible hidden param
+
+ // This check is only intended to prevent an AV. Bad varNum values will later
+ // be handled properly by the verifier.
+ if (varNum < lvaTableCnt)
+ {
+ if (isInlining)
+ {
+ impInlineInfo->inlArgInfo[varNum].argHasStargOp = true;
+ }
+ else
+ {
+ // In non-inline cases, note written-to locals.
+ lvaTable[varNum].lvArgWrite = 1;
+ }
+ }
+ }
+ break;
+
+ case CEE_LDARGA:
+ case CEE_LDARGA_S:
+ case CEE_LDLOCA:
+ case CEE_LDLOCA_S:
+ {
+ // Handle address-taken args or locals
+ noway_assert(sz == sizeof(BYTE) || sz == sizeof(WORD));
+
+ if (codeAddr > codeEndp - sz)
+ {
+ goto TOO_FAR;
+ }
+
+ varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr) : getU2LittleEndian(codeAddr);
+
+ if (isInlining)
+ {
+ if (opcode == CEE_LDLOCA || opcode == CEE_LDLOCA_S)
+ {
+ varType = impInlineInfo->lclVarInfo[varNum + impInlineInfo->argCnt].lclTypeInfo;
+ ti = impInlineInfo->lclVarInfo[varNum + impInlineInfo->argCnt].lclVerTypeInfo;
+
+ impInlineInfo->lclVarInfo[varNum + impInlineInfo->argCnt].lclHasLdlocaOp = true;
+ }
+ else
+ {
+ noway_assert(opcode == CEE_LDARGA || opcode == CEE_LDARGA_S);
+
+ varType = impInlineInfo->lclVarInfo[varNum].lclTypeInfo;
+ ti = impInlineInfo->lclVarInfo[varNum].lclVerTypeInfo;
+
+ impInlineInfo->inlArgInfo[varNum].argHasLdargaOp = true;
+
+ pushedStack.PushArgument(varNum);
+ }
+ }
+ else
+ {
+ if (opcode == CEE_LDLOCA || opcode == CEE_LDLOCA_S)
+ {
+ if (varNum >= info.compMethodInfo->locals.numArgs)
+ {
+ BADCODE("bad local number");
+ }
+
+ varNum += info.compArgsCount;
+ }
+ else
+ {
+ noway_assert(opcode == CEE_LDARGA || opcode == CEE_LDARGA_S);
+
+ if (varNum >= info.compILargsCount)
+ {
+ BADCODE("bad argument number");
+ }
+
+ varNum = compMapILargNum(varNum); // account for possible hidden param
+ }
+
+ varType = (var_types)lvaTable[varNum].lvType;
+ ti = lvaTable[varNum].lvVerTypeInfo;
+
+ // Determine if the next instruction will consume
+ // the address. If so we won't mark this var as
+ // address taken.
+ //
+ // We will put structs on the stack and changing
+ // the addrTaken of a local requires an extra pass
+ // in the morpher so we won't apply this
+ // optimization to structs.
+ //
+ // Debug code spills for every IL instruction, and
+ // therefore it will split statements, so we will
+ // need the address. Note that this optimization
+ // is based in that we know what trees we will
+ // generate for this ldfld, and we require that we
+ // won't need the address of this local at all
+ noway_assert(varNum < lvaTableCnt);
+
+ const bool notStruct = !varTypeIsStruct(&lvaTable[varNum]);
+ const bool notLastInstr = (codeAddr < codeEndp - sz);
+ const bool notDebugCode = !opts.compDbgCode;
+
+ if (notStruct && notLastInstr && notDebugCode &&
+ impILConsumesAddr(codeAddr + sz, impTokenLookupContextHandle, info.compScopeHnd))
+ {
+ // We can skip the addrtaken, as next IL instruction consumes
+ // the address.
+ }
+ else
+ {
+ lvaTable[varNum].lvHasLdAddrOp = 1;
+ if (!info.compIsStatic && (varNum == 0))
+ {
+ // Addr taken on "this" pointer is significant,
+ // go ahead to mark it as permanently addr-exposed here.
+ lvaSetVarAddrExposed(0);
+ // This may be conservative, but probably not very.
+ }
+ }
+ } // isInlining
+
+ typeIsNormed = ti.IsValueClass() && !varTypeIsStruct(varType);
+ }
+ break;
+
+ case CEE_JMP:
+
+#if !defined(_TARGET_X86_) && !defined(_TARGET_ARM_)
+ if (!isInlining)
+ {
+ // We transform this into a set of ldarg's + tail call and
+ // thus may push more onto the stack than originally thought.
+ // This doesn't interfere with verification because CEE_JMP
+ // is never verifiable, and there's nothing unsafe you can
+ // do with a an IL stack overflow if the JIT is expecting it.
+ info.compMaxStack = max(info.compMaxStack, info.compILargsCount);
+ break;
+ }
+#endif // !_TARGET_X86_ && !_TARGET_ARM_
+
+ // If we are inlining, we need to fail for a CEE_JMP opcode, just like
+ // the list of other opcodes (for all platforms).
+
+ __fallthrough;
+
+ case CEE_CALLI:
+ case CEE_LOCALLOC:
+ case CEE_MKREFANY:
+ case CEE_RETHROW:
+ // CEE_CALLI should not be inlined because the JIT cannot generate an inlined call frame. If the call
+ // target
+ // is a no-marshal CALLI P/Invoke we end up calling the IL stub. We don't NGEN these stubs, so we'll
+ // have to
+ // JIT an IL stub for a trivial func. It's almost certainly a better choice to leave out the inline
+ // candidate so we can generate an inlined call frame. It might be nice to call getCallInfo to figure
+ // out
+ // what kind of call we have here.
+
+ // Consider making this only for not force inline.
+ if (makeInlineObservations)
+ {
+ // Arguably this should be NoteFatal, but the legacy behavior is
+ // to ignore this for the prejit root.
+ compInlineResult->Note(InlineObservation::CALLEE_UNSUPPORTED_OPCODE);
+
+ // Fail fast if we're inlining...
+ if (isInlining)
+ {
+ assert(compInlineResult->IsFailure());
+ return;
+ }
+ }
+ break;
+
+ case CEE_LDARG_0:
+ case CEE_LDARG_1:
+ case CEE_LDARG_2:
+ case CEE_LDARG_3:
+ if (makeInlineObservations)
+ {
+ pushedStack.PushArgument(opcode - CEE_LDARG_0);
+ }
+ break;
+
+ case CEE_LDARG_S:
+ case CEE_LDARG:
+ {
+ if (codeAddr > codeEndp - sz)
+ {
+ goto TOO_FAR;
+ }
+
+ varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr) : getU2LittleEndian(codeAddr);
+
+ if (makeInlineObservations)
+ {
+ pushedStack.PushArgument(varNum);
+ }
+ }
+ break;
+
+ case CEE_LDLEN:
+ if (makeInlineObservations)
+ {
+ pushedStack.PushArrayLen();
+ }
+ break;
+
+ case CEE_CEQ:
+ case CEE_CGT:
+ case CEE_CGT_UN:
+ case CEE_CLT:
+ case CEE_CLT_UN:
+ if (makeInlineObservations)
+ {
+ fgObserveInlineConstants(opcode, pushedStack, isInlining);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Skip any remaining operands this opcode may have
+ codeAddr += sz;
+
+ // Note the opcode we just saw
+ if (makeInlineObservations)
+ {
+ InlineObservation obs =
+ typeIsNormed ? InlineObservation::CALLEE_OPCODE_NORMED : InlineObservation::CALLEE_OPCODE;
+ compInlineResult->NoteInt(obs, opcode);
+ }
+ }
+
+ if (codeAddr != codeEndp)
+ {
+ TOO_FAR:
+ BADCODE3("Code ends in the middle of an opcode, or there is a branch past the end of the method",
+ " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
+ }
+
+ if (makeInlineObservations)
+ {
+ compInlineResult->Note(InlineObservation::CALLEE_END_OPCODE_SCAN);
+
+ // If the inline is viable and discretionary, do the
+ // profitability screening.
+ if (compInlineResult->IsDiscretionaryCandidate())
+ {
+ // Make some callsite specific observations that will feed
+ // into the profitability model.
+ impMakeDiscretionaryInlineObservations(impInlineInfo, compInlineResult);
+
+ // None of those observations should have changed the
+ // inline's viability.
+ assert(compInlineResult->IsCandidate());
+
+ if (isInlining)
+ {
+ // Assess profitability...
+ CORINFO_METHOD_INFO* methodInfo = &impInlineInfo->inlineCandidateInfo->methInfo;
+ compInlineResult->DetermineProfitability(methodInfo);
+
+ if (compInlineResult->IsFailure())
+ {
+ impInlineRoot()->m_inlineStrategy->NoteUnprofitable();
+ JITDUMP("\n\nInline expansion aborted, inline not profitable\n");
+ return;
+ }
+ else
+ {
+ // The inline is still viable.
+ assert(compInlineResult->IsCandidate());
+ }
+ }
+ else
+ {
+ // Prejit root case. Profitability assessment for this
+ // is done over in compCompileHelper.
+ }
+ }
+ }
+
+ // None of the local vars in the inlinee should have address taken or been written to.
+ // Therefore we should NOT need to enter this "if" statement.
+ if (!isInlining && !info.compIsStatic)
+ {
+ fgAdjustForAddressExposedOrWrittenThis();
+ }
+}
+
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+//------------------------------------------------------------------------
+// fgAdjustForAddressExposedOrWrittenThis: update var table for cases
+// where the this pointer value can change.
+//
+// Notes:
+// Modifies lvaArg0Var to refer to a temp if the value of 'this' can
+// change. The original this (info.compThisArg) then remains
+// unmodified in the method. fgAddInternal is reponsible for
+// adding the code to copy the initial this into the temp.
+
+void Compiler::fgAdjustForAddressExposedOrWrittenThis()
+{
+ // Optionally enable adjustment during stress.
+ if (!tiVerificationNeeded && compStressCompile(STRESS_GENERIC_VARN, 15))
+ {
+ lvaTable[info.compThisArg].lvArgWrite = true;
+ }
+
+ // If this is exposed or written to, create a temp for the modifiable this
+ if (lvaTable[info.compThisArg].lvAddrExposed || lvaTable[info.compThisArg].lvArgWrite)
+ {
+ // If there is a "ldarga 0" or "starg 0", grab and use the temp.
+ lvaArg0Var = lvaGrabTemp(false DEBUGARG("Address-exposed, or written this pointer"));
+ noway_assert(lvaArg0Var > (unsigned)info.compThisArg);
+ lvaTable[lvaArg0Var].lvType = lvaTable[info.compThisArg].TypeGet();
+ lvaTable[lvaArg0Var].lvAddrExposed = lvaTable[info.compThisArg].lvAddrExposed;
+ lvaTable[lvaArg0Var].lvDoNotEnregister = lvaTable[info.compThisArg].lvDoNotEnregister;
+#ifdef DEBUG
+ lvaTable[lvaArg0Var].lvVMNeedsStackAddr = lvaTable[info.compThisArg].lvVMNeedsStackAddr;
+ lvaTable[lvaArg0Var].lvLiveInOutOfHndlr = lvaTable[info.compThisArg].lvLiveInOutOfHndlr;
+ lvaTable[lvaArg0Var].lvLclFieldExpr = lvaTable[info.compThisArg].lvLclFieldExpr;
+ lvaTable[lvaArg0Var].lvLiveAcrossUCall = lvaTable[info.compThisArg].lvLiveAcrossUCall;
+#endif
+ lvaTable[lvaArg0Var].lvArgWrite = lvaTable[info.compThisArg].lvArgWrite;
+ lvaTable[lvaArg0Var].lvVerTypeInfo = lvaTable[info.compThisArg].lvVerTypeInfo;
+
+ // Clear the TI_FLAG_THIS_PTR in the original 'this' pointer.
+ noway_assert(lvaTable[lvaArg0Var].lvVerTypeInfo.IsThisPtr());
+ lvaTable[info.compThisArg].lvVerTypeInfo.ClearThisPtr();
+ lvaTable[info.compThisArg].lvAddrExposed = false;
+ lvaTable[info.compThisArg].lvArgWrite = false;
+ }
+}
+
+//------------------------------------------------------------------------
+// fgObserveInlineConstants: look for operations that might get optimized
+// if this method were to be inlined, and report these to the inliner.
+//
+// Arguments:
+// opcode -- MSIL opcode under consideration
+// stack -- abstract stack model at this point in the IL
+// isInlining -- true if we're inlining (vs compiling a prejit root)
+//
+// Notes:
+// Currently only invoked on compare and branch opcodes.
+//
+// If we're inlining we also look at the argument values supplied by
+// the caller at this call site.
+//
+// The crude stack model may overestimate stack depth.
+
+void Compiler::fgObserveInlineConstants(OPCODE opcode, const FgStack& stack, bool isInlining)
+{
+ // We should be able to record inline observations.
+ assert(compInlineResult != nullptr);
+
+ // The stack only has to be 1 deep for BRTRUE/FALSE
+ bool lookForBranchCases = stack.IsStackAtLeastOneDeep();
+
+ if (compInlineResult->UsesLegacyPolicy())
+ {
+ // LegacyPolicy misses cases where the stack is really one
+ // deep but the model says it's two deep. We need to do
+ // likewise to preseve old behavior.
+ lookForBranchCases &= !stack.IsStackTwoDeep();
+ }
+
+ if (lookForBranchCases)
+ {
+ if (opcode == CEE_BRFALSE || opcode == CEE_BRFALSE_S || opcode == CEE_BRTRUE || opcode == CEE_BRTRUE_S)
+ {
+ unsigned slot0 = stack.GetSlot0();
+ if (FgStack::IsArgument(slot0))
+ {
+ compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_CONSTANT_TEST);
+
+ if (isInlining)
+ {
+ // Check for the double whammy of an incoming constant argument
+ // feeding a constant test.
+ unsigned varNum = FgStack::SlotTypeToArgNum(slot0);
+ if (impInlineInfo->inlArgInfo[varNum].argNode->OperIsConst())
+ {
+ compInlineResult->Note(InlineObservation::CALLSITE_CONSTANT_ARG_FEEDS_TEST);
+ }
+ }
+ }
+
+ return;
+ }
+ }
+
+ // Remaining cases require at least two things on the stack.
+ if (!stack.IsStackTwoDeep())
+ {
+ return;
+ }
+
+ unsigned slot0 = stack.GetSlot0();
+ unsigned slot1 = stack.GetSlot1();
+
+ // Arg feeds constant test
+ if ((FgStack::IsConstant(slot0) && FgStack::IsArgument(slot1)) ||
+ (FgStack::IsConstant(slot1) && FgStack::IsArgument(slot0)))
+ {
+ compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_CONSTANT_TEST);
+ }
+
+ // Arg feeds range check
+ if ((FgStack::IsArrayLen(slot0) && FgStack::IsArgument(slot1)) ||
+ (FgStack::IsArrayLen(slot1) && FgStack::IsArgument(slot0)))
+ {
+ compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_RANGE_CHECK);
+ }
+
+ // Check for an incoming arg that's a constant
+ if (isInlining)
+ {
+ if (FgStack::IsArgument(slot0))
+ {
+ unsigned varNum = FgStack::SlotTypeToArgNum(slot0);
+ if (impInlineInfo->inlArgInfo[varNum].argNode->OperIsConst())
+ {
+ compInlineResult->Note(InlineObservation::CALLSITE_CONSTANT_ARG_FEEDS_TEST);
+ }
+ }
+
+ if (FgStack::IsArgument(slot1))
+ {
+ unsigned varNum = FgStack::SlotTypeToArgNum(slot1);
+ if (impInlineInfo->inlArgInfo[varNum].argNode->OperIsConst())
+ {
+ compInlineResult->Note(InlineObservation::CALLSITE_CONSTANT_ARG_FEEDS_TEST);
+ }
+ }
+ }
+}
+
+/*****************************************************************************
+ *
+ * Finally link up the bbJumpDest of the blocks together
+ */
+
+void Compiler::fgMarkBackwardJump(BasicBlock* startBlock, BasicBlock* endBlock)
+{
+ noway_assert(startBlock->bbNum <= endBlock->bbNum);
+
+ for (BasicBlock* block = startBlock; block != endBlock->bbNext; block = block->bbNext)
+ {
+ if ((block->bbFlags & BBF_BACKWARD_JUMP) == 0)
+ {
+ block->bbFlags |= BBF_BACKWARD_JUMP;
+ }
+ }
+}
+
+/*****************************************************************************
+ *
+ * Finally link up the bbJumpDest of the blocks together
+ */
+
+void Compiler::fgLinkBasicBlocks()
+{
+ /* Create the basic block lookup tables */
+
+ fgInitBBLookup();
+
+ /* First block is always reachable */
+
+ fgFirstBB->bbRefs = 1;
+
+ /* Walk all the basic blocks, filling in the target addresses */
+
+ for (BasicBlock* curBBdesc = fgFirstBB; curBBdesc; curBBdesc = curBBdesc->bbNext)
+ {
+ switch (curBBdesc->bbJumpKind)
+ {
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ case BBJ_LEAVE:
+ curBBdesc->bbJumpDest = fgLookupBB(curBBdesc->bbJumpOffs);
+ curBBdesc->bbJumpDest->bbRefs++;
+ if (curBBdesc->bbJumpDest->bbNum <= curBBdesc->bbNum)
+ {
+ fgMarkBackwardJump(curBBdesc->bbJumpDest, curBBdesc);
+ }
+
+ /* Is the next block reachable? */
+
+ if (curBBdesc->bbJumpKind == BBJ_ALWAYS || curBBdesc->bbJumpKind == BBJ_LEAVE)
+ {
+ break;
+ }
+
+ if (!curBBdesc->bbNext)
+ {
+ BADCODE("Fall thru the end of a method");
+ }
+
+ // Fall through, the next block is also reachable
+
+ case BBJ_NONE:
+ curBBdesc->bbNext->bbRefs++;
+ break;
+
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+ case BBJ_THROW:
+ case BBJ_RETURN:
+ break;
+
+ case BBJ_SWITCH:
+
+ unsigned jumpCnt;
+ jumpCnt = curBBdesc->bbJumpSwt->bbsCount;
+ BasicBlock** jumpPtr;
+ jumpPtr = curBBdesc->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ *jumpPtr = fgLookupBB((unsigned)*(size_t*)jumpPtr);
+ (*jumpPtr)->bbRefs++;
+ if ((*jumpPtr)->bbNum <= curBBdesc->bbNum)
+ {
+ fgMarkBackwardJump(*jumpPtr, curBBdesc);
+ }
+ } while (++jumpPtr, --jumpCnt);
+
+ /* Default case of CEE_SWITCH (next block), is at end of jumpTab[] */
+
+ noway_assert(*(jumpPtr - 1) == curBBdesc->bbNext);
+ break;
+
+ case BBJ_CALLFINALLY: // BBJ_CALLFINALLY and BBJ_EHCATCHRET don't appear until later
+ case BBJ_EHCATCHRET:
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+ }
+}
+
+/*****************************************************************************
+ *
+ * Walk the instrs to create the basic blocks.
+ */
+
+void Compiler::fgMakeBasicBlocks(const BYTE* codeAddr, IL_OFFSET codeSize, BYTE* jumpTarget)
+{
+ const BYTE* codeBegp = codeAddr;
+ const BYTE* codeEndp = codeAddr + codeSize;
+ bool tailCall = false;
+ unsigned curBBoffs;
+ BasicBlock* curBBdesc;
+
+ /* Clear the beginning offset for the first BB */
+
+ curBBoffs = 0;
+
+#ifdef DEBUGGING_SUPPORT
+ if (opts.compDbgCode && (info.compVarScopesCount > 0))
+ {
+ compResetScopeLists();
+
+ // Ignore scopes beginning at offset 0
+ while (compGetNextEnterScope(0))
+ { /* do nothing */
+ }
+ while (compGetNextExitScope(0))
+ { /* do nothing */
+ }
+ }
+#endif
+
+ BBjumpKinds jmpKind;
+
+ do
+ {
+ OPCODE opcode;
+ unsigned sz;
+ unsigned jmpAddr = DUMMY_INIT(BAD_IL_OFFSET);
+ unsigned bbFlags = 0;
+ BBswtDesc* swtDsc = nullptr;
+ unsigned nxtBBoffs;
+
+ opcode = (OPCODE)getU1LittleEndian(codeAddr);
+ codeAddr += sizeof(__int8);
+ jmpKind = BBJ_NONE;
+
+ DECODE_OPCODE:
+
+ /* Get the size of additional parameters */
+
+ noway_assert(opcode < CEE_COUNT);
+
+ sz = opcodeSizes[opcode];
+
+ switch (opcode)
+ {
+ signed jmpDist;
+
+ case CEE_PREFIX1:
+ if (jumpTarget[codeAddr - codeBegp] != JT_NONE)
+ {
+ BADCODE3("jump target between prefix 0xFE and opcode", " at offset %04X",
+ (IL_OFFSET)(codeAddr - codeBegp));
+ }
+
+ opcode = (OPCODE)(256 + getU1LittleEndian(codeAddr));
+ codeAddr += sizeof(__int8);
+ goto DECODE_OPCODE;
+
+ /* Check to see if we have a jump/return opcode */
+
+ case CEE_BRFALSE:
+ case CEE_BRFALSE_S:
+ case CEE_BRTRUE:
+ case CEE_BRTRUE_S:
+
+ case CEE_BEQ:
+ case CEE_BEQ_S:
+ case CEE_BGE:
+ case CEE_BGE_S:
+ case CEE_BGE_UN:
+ case CEE_BGE_UN_S:
+ case CEE_BGT:
+ case CEE_BGT_S:
+ case CEE_BGT_UN:
+ case CEE_BGT_UN_S:
+ case CEE_BLE:
+ case CEE_BLE_S:
+ case CEE_BLE_UN:
+ case CEE_BLE_UN_S:
+ case CEE_BLT:
+ case CEE_BLT_S:
+ case CEE_BLT_UN:
+ case CEE_BLT_UN_S:
+ case CEE_BNE_UN:
+ case CEE_BNE_UN_S:
+
+ jmpKind = BBJ_COND;
+ goto JMP;
+
+ case CEE_LEAVE:
+ case CEE_LEAVE_S:
+
+ // We need to check if we are jumping out of a finally-protected try.
+ jmpKind = BBJ_LEAVE;
+ goto JMP;
+
+ case CEE_BR:
+ case CEE_BR_S:
+ jmpKind = BBJ_ALWAYS;
+ goto JMP;
+
+ JMP:
+
+ /* Compute the target address of the jump */
+
+ jmpDist = (sz == 1) ? getI1LittleEndian(codeAddr) : getI4LittleEndian(codeAddr);
+
+ if (compIsForInlining() && jmpDist == 0 && (opcode == CEE_BR || opcode == CEE_BR_S))
+ {
+ continue; /* NOP */
+ }
+
+ jmpAddr = (IL_OFFSET)(codeAddr - codeBegp) + sz + jmpDist;
+ break;
+
+ case CEE_SWITCH:
+ {
+ unsigned jmpBase;
+ unsigned jmpCnt; // # of switch cases (excluding defualt)
+
+ BasicBlock** jmpTab;
+ BasicBlock** jmpPtr;
+
+ /* Allocate the switch descriptor */
+
+ swtDsc = new (this, CMK_BasicBlock) BBswtDesc;
+
+ /* Read the number of entries in the table */
+
+ jmpCnt = getU4LittleEndian(codeAddr);
+ codeAddr += 4;
+
+ /* Compute the base offset for the opcode */
+
+ jmpBase = (IL_OFFSET)((codeAddr - codeBegp) + jmpCnt * sizeof(DWORD));
+
+ /* Allocate the jump table */
+
+ jmpPtr = jmpTab = new (this, CMK_BasicBlock) BasicBlock*[jmpCnt + 1];
+
+ /* Fill in the jump table */
+
+ for (unsigned count = jmpCnt; count; count--)
+ {
+ jmpDist = getI4LittleEndian(codeAddr);
+ codeAddr += 4;
+
+ // store the offset in the pointer. We change these in fgLinkBasicBlocks().
+ *jmpPtr++ = (BasicBlock*)(size_t)(jmpBase + jmpDist);
+ }
+
+ /* Append the default label to the target table */
+
+ *jmpPtr++ = (BasicBlock*)(size_t)jmpBase;
+
+ /* Make sure we found the right number of labels */
+
+ noway_assert(jmpPtr == jmpTab + jmpCnt + 1);
+
+ /* Compute the size of the switch opcode operands */
+
+ sz = sizeof(DWORD) + jmpCnt * sizeof(DWORD);
+
+ /* Fill in the remaining fields of the switch descriptor */
+
+ swtDsc->bbsCount = jmpCnt + 1;
+ swtDsc->bbsDstTab = jmpTab;
+
+ /* This is definitely a jump */
+
+ jmpKind = BBJ_SWITCH;
+ fgHasSwitch = true;
+
+#ifndef LEGACY_BACKEND
+ if (opts.compProcedureSplitting)
+ {
+ // TODO-CQ: We might need to create a switch table; we won't know for sure until much later.
+ // However, switch tables don't work with hot/cold splitting, currently. The switch table data needs
+ // a relocation such that if the base (the first block after the prolog) and target of the switch
+ // branch are put in different sections, the difference stored in the table is updated. However, our
+ // relocation implementation doesn't support three different pointers (relocation address, base, and
+ // target). So, we need to change our switch table implementation to be more like
+ // JIT64: put the table in the code section, in the same hot/cold section as the switch jump itself
+ // (maybe immediately after the switch jump), and make the "base" address be also in that section,
+ // probably the address after the switch jump.
+ opts.compProcedureSplitting = false;
+ JITDUMP("Turning off procedure splitting for this method, as it might need switch tables; "
+ "implementation limitation.\n");
+ }
+#endif // !LEGACY_BACKEND
+ }
+ goto GOT_ENDP;
+
+ case CEE_ENDFILTER:
+ bbFlags |= BBF_DONT_REMOVE;
+ jmpKind = BBJ_EHFILTERRET;
+ break;
+
+ case CEE_ENDFINALLY:
+ jmpKind = BBJ_EHFINALLYRET;
+ break;
+
+ case CEE_TAILCALL:
+ if (compIsForInlining())
+ {
+ // TODO-CQ: We can inline some callees with explicit tail calls if we can guarantee that the calls
+ // can be dispatched as tail calls from the caller.
+ compInlineResult->NoteFatal(InlineObservation::CALLEE_EXPLICIT_TAIL_PREFIX);
+ return;
+ }
+
+ __fallthrough;
+
+ case CEE_READONLY:
+ case CEE_CONSTRAINED:
+ case CEE_VOLATILE:
+ case CEE_UNALIGNED:
+ // fgFindJumpTargets should have ruled out this possibility
+ // (i.e. a prefix opcodes as last intruction in a block)
+ noway_assert(codeAddr < codeEndp);
+
+ if (jumpTarget[codeAddr - codeBegp] != JT_NONE)
+ {
+ BADCODE3("jump target between prefix and an opcode", " at offset %04X",
+ (IL_OFFSET)(codeAddr - codeBegp));
+ }
+ break;
+
+ case CEE_CALL:
+ case CEE_CALLVIRT:
+ case CEE_CALLI:
+ {
+ if (compIsForInlining() || // Ignore tail call in the inlinee. Period.
+ (!tailCall && !compTailCallStress()) // A new BB with BBJ_RETURN would have been created
+
+ // after a tailcall statement.
+ // We need to keep this invariant if we want to stress the tailcall.
+ // That way, the potential (tail)call statement is always the last
+ // statement in the block.
+ // Otherwise, we will assert at the following line in fgMorphCall()
+ // noway_assert(fgMorphStmt->gtNext == NULL);
+ )
+ {
+ // Neither .tailcall prefix, no tailcall stress. So move on.
+ break;
+ }
+
+ // Make sure the code sequence is legal for the tail call.
+ // If so, mark this BB as having a BBJ_RETURN.
+
+ if (codeAddr >= codeEndp - sz)
+ {
+ BADCODE3("No code found after the call instruction", " at offset %04X",
+ (IL_OFFSET)(codeAddr - codeBegp));
+ }
+
+ if (tailCall)
+ {
+ bool isCallPopAndRet = false;
+
+ // impIsTailCallILPattern uses isRecursive flag to determine whether ret in a fallthrough block is
+ // allowed. We don't know at this point whether the call is recursive so we conservatively pass
+ // false. This will only affect explicit tail calls when IL verification is not needed for the
+ // method.
+ bool isRecursive = false;
+ if (!impIsTailCallILPattern(tailCall, opcode, codeAddr + sz, codeEndp, isRecursive,
+ &isCallPopAndRet))
+ {
+#ifdef _TARGET_AMD64_
+ BADCODE3("tail call not followed by ret or pop+ret", " at offset %04X",
+ (IL_OFFSET)(codeAddr - codeBegp));
+#else
+ BADCODE3("tail call not followed by ret", " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
+#endif //_TARGET_AMD64_
+ }
+
+#ifdef _TARGET_AMD64_
+ if (isCallPopAndRet)
+ {
+ // By breaking here, we let pop and ret opcodes to be
+ // imported after tail call. If tail prefix is honored,
+ // stmts corresponding to pop and ret will be removed
+ // in fgMorphCall().
+ break;
+ }
+#endif //_TARGET_AMD64_
+ }
+ else
+ {
+ OPCODE nextOpcode = (OPCODE)getU1LittleEndian(codeAddr + sz);
+
+ if (nextOpcode != CEE_RET)
+ {
+ noway_assert(compTailCallStress());
+ // Next OPCODE is not a CEE_RET, bail the attempt to stress the tailcall.
+ // (I.e. We will not make a new BB after the "call" statement.)
+ break;
+ }
+ }
+ }
+
+ /* For tail call, we just call CORINFO_HELP_TAILCALL, and it jumps to the
+ target. So we don't need an epilog - just like CORINFO_HELP_THROW.
+ Make the block BBJ_RETURN, but we will change it to BBJ_THROW
+ if the tailness of the call is satisfied.
+ NOTE : The next instruction is guaranteed to be a CEE_RET
+ and it will create another BasicBlock. But there may be an
+ jump directly to that CEE_RET. If we want to avoid creating
+ an unnecessary block, we need to check if the CEE_RETURN is
+ the target of a jump.
+ */
+
+ // fall-through
+
+ case CEE_JMP:
+ /* These are equivalent to a return from the current method
+ But instead of directly returning to the caller we jump and
+ execute something else in between */
+ case CEE_RET:
+ jmpKind = BBJ_RETURN;
+ break;
+
+ case CEE_THROW:
+ case CEE_RETHROW:
+ jmpKind = BBJ_THROW;
+ break;
+
+#ifdef DEBUG
+// make certain we did not forget any flow of control instructions
+// by checking the 'ctrl' field in opcode.def. First filter out all
+// non-ctrl instructions
+#define BREAK(name) \
+ case name: \
+ break;
+#define NEXT(name) \
+ case name: \
+ break;
+#define CALL(name)
+#define THROW(name)
+#undef RETURN // undef contract RETURN macro
+#define RETURN(name)
+#define META(name)
+#define BRANCH(name)
+#define COND_BRANCH(name)
+#define PHI(name)
+
+#define OPDEF(name, string, pop, push, oprType, opcType, l, s1, s2, ctrl) ctrl(name)
+#include "opcode.def"
+#undef OPDEF
+
+#undef PHI
+#undef BREAK
+#undef CALL
+#undef NEXT
+#undef THROW
+#undef RETURN
+#undef META
+#undef BRANCH
+#undef COND_BRANCH
+
+ // These ctrl-flow opcodes don't need any special handling
+ case CEE_NEWOBJ: // CTRL_CALL
+ break;
+
+ // what's left are forgotten instructions
+ default:
+ BADCODE("Unrecognized control Opcode");
+ break;
+#else // !DEBUG
+ default:
+ break;
+#endif // !DEBUG
+ }
+
+ /* Jump over the operand */
+
+ codeAddr += sz;
+
+ GOT_ENDP:
+
+ tailCall = (opcode == CEE_TAILCALL);
+
+ /* Make sure a jump target isn't in the middle of our opcode */
+
+ if (sz)
+ {
+ IL_OFFSET offs = (IL_OFFSET)(codeAddr - codeBegp) - sz; // offset of the operand
+
+ for (unsigned i = 0; i < sz; i++, offs++)
+ {
+ if (jumpTarget[offs] != JT_NONE)
+ {
+ BADCODE3("jump into the middle of an opcode", " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
+ }
+ }
+ }
+
+ /* Compute the offset of the next opcode */
+
+ nxtBBoffs = (IL_OFFSET)(codeAddr - codeBegp);
+
+#ifdef DEBUGGING_SUPPORT
+
+ bool foundScope = false;
+
+ if (opts.compDbgCode && (info.compVarScopesCount > 0))
+ {
+ while (compGetNextEnterScope(nxtBBoffs))
+ {
+ foundScope = true;
+ }
+ while (compGetNextExitScope(nxtBBoffs))
+ {
+ foundScope = true;
+ }
+ }
+#endif
+
+ /* Do we have a jump? */
+
+ if (jmpKind == BBJ_NONE)
+ {
+ /* No jump; make sure we don't fall off the end of the function */
+
+ if (codeAddr == codeEndp)
+ {
+ BADCODE3("missing return opcode", " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
+ }
+
+ /* If a label follows this opcode, we'll have to make a new BB */
+
+ bool makeBlock = (jumpTarget[nxtBBoffs] != JT_NONE);
+
+#ifdef DEBUGGING_SUPPORT
+ if (!makeBlock && foundScope)
+ {
+ makeBlock = true;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Splitting at BBoffs = %04u\n", nxtBBoffs);
+ }
+#endif // DEBUG
+ }
+#endif // DEBUGGING_SUPPORT
+
+ if (!makeBlock)
+ {
+ continue;
+ }
+ }
+
+ /* We need to create a new basic block */
+
+ curBBdesc = fgNewBasicBlock(jmpKind);
+
+ curBBdesc->bbFlags |= bbFlags;
+ curBBdesc->bbRefs = 0;
+
+ curBBdesc->bbCodeOffs = curBBoffs;
+ curBBdesc->bbCodeOffsEnd = nxtBBoffs;
+
+ unsigned profileWeight;
+ if (fgGetProfileWeightForBasicBlock(curBBoffs, &profileWeight))
+ {
+ curBBdesc->setBBProfileWeight(profileWeight);
+ if (profileWeight == 0)
+ {
+ curBBdesc->bbSetRunRarely();
+ }
+ else
+ {
+ // Note that bbNewBasicBlock (called from fgNewBasicBlock) may have
+ // already marked the block as rarely run. In that case (and when we know
+ // that the block profile weight is non-zero) we want to unmark that.
+
+ curBBdesc->bbFlags &= ~BBF_RUN_RARELY;
+ }
+ }
+
+ switch (jmpKind)
+ {
+ case BBJ_SWITCH:
+ curBBdesc->bbJumpSwt = swtDsc;
+ break;
+
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ case BBJ_LEAVE:
+ noway_assert(jmpAddr != DUMMY_INIT(BAD_IL_OFFSET));
+ curBBdesc->bbJumpOffs = jmpAddr;
+ break;
+
+ default:
+ break;
+ }
+
+ DBEXEC(verbose, curBBdesc->dspBlockHeader(this, false, false, false));
+
+ /* Remember where the next BB will start */
+
+ curBBoffs = nxtBBoffs;
+ } while (codeAddr < codeEndp);
+
+ noway_assert(codeAddr == codeEndp);
+
+ /* Finally link up the bbJumpDest of the blocks together */
+
+ fgLinkBasicBlocks();
+}
+
+/*****************************************************************************
+ *
+ * Main entry point to discover the basic blocks for the current function.
+ */
+
+void Compiler::fgFindBasicBlocks()
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgFindBasicBlocks() for %s\n", info.compFullName);
+ }
+#endif
+
+ /* Allocate the 'jump target' vector
+ *
+ * We need one extra byte as we mark
+ * jumpTarget[info.compILCodeSize] with JT_ADDR
+ * when we need to add a dummy block
+ * to record the end of a try or handler region.
+ */
+ BYTE* jumpTarget = new (this, CMK_Unknown) BYTE[info.compILCodeSize + 1];
+ memset(jumpTarget, JT_NONE, info.compILCodeSize + 1);
+ noway_assert(JT_NONE == 0);
+
+ /* Walk the instrs to find all jump targets */
+
+ fgFindJumpTargets(info.compCode, info.compILCodeSize, jumpTarget);
+ if (compDonotInline())
+ {
+ return;
+ }
+
+ unsigned XTnum;
+
+ /* Are there any exception handlers? */
+
+ if (info.compXcptnsCount > 0)
+ {
+ noway_assert(!compIsForInlining());
+
+ /* Check and mark all the exception handlers */
+
+ for (XTnum = 0; XTnum < info.compXcptnsCount; XTnum++)
+ {
+ DWORD tmpOffset;
+ CORINFO_EH_CLAUSE clause;
+ info.compCompHnd->getEHinfo(info.compMethodHnd, XTnum, &clause);
+ noway_assert(clause.HandlerLength != (unsigned)-1);
+
+ if (clause.TryLength <= 0)
+ {
+ BADCODE("try block length <=0");
+ }
+
+ /* Mark the 'try' block extent and the handler itself */
+
+ if (clause.TryOffset > info.compILCodeSize)
+ {
+ BADCODE("try offset is > codesize");
+ }
+ if (jumpTarget[clause.TryOffset] == JT_NONE)
+ {
+ jumpTarget[clause.TryOffset] = JT_ADDR;
+ }
+
+ tmpOffset = clause.TryOffset + clause.TryLength;
+ if (tmpOffset > info.compILCodeSize)
+ {
+ BADCODE("try end is > codesize");
+ }
+ if (jumpTarget[tmpOffset] == JT_NONE)
+ {
+ jumpTarget[tmpOffset] = JT_ADDR;
+ }
+
+ if (clause.HandlerOffset > info.compILCodeSize)
+ {
+ BADCODE("handler offset > codesize");
+ }
+ if (jumpTarget[clause.HandlerOffset] == JT_NONE)
+ {
+ jumpTarget[clause.HandlerOffset] = JT_ADDR;
+ }
+
+ tmpOffset = clause.HandlerOffset + clause.HandlerLength;
+ if (tmpOffset > info.compILCodeSize)
+ {
+ BADCODE("handler end > codesize");
+ }
+ if (jumpTarget[tmpOffset] == JT_NONE)
+ {
+ jumpTarget[tmpOffset] = JT_ADDR;
+ }
+
+ if (clause.Flags & CORINFO_EH_CLAUSE_FILTER)
+ {
+ if (clause.FilterOffset > info.compILCodeSize)
+ {
+ BADCODE("filter offset > codesize");
+ }
+ if (jumpTarget[clause.FilterOffset] == JT_NONE)
+ {
+ jumpTarget[clause.FilterOffset] = JT_ADDR;
+ }
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ bool anyJumpTargets = false;
+ printf("Jump targets:\n");
+ for (unsigned i = 0; i < info.compILCodeSize + 1; i++)
+ {
+ if (jumpTarget[i] == JT_NONE)
+ {
+ continue;
+ }
+
+ anyJumpTargets = true;
+ printf(" IL_%04x", i);
+
+ if (jumpTarget[i] & JT_ADDR)
+ {
+ printf(" addr");
+ }
+ if (jumpTarget[i] & JT_MULTI)
+ {
+ printf(" multi");
+ }
+ printf("\n");
+ }
+ if (!anyJumpTargets)
+ {
+ printf(" none\n");
+ }
+ }
+#endif // DEBUG
+
+ /* Now create the basic blocks */
+
+ fgMakeBasicBlocks(info.compCode, info.compILCodeSize, jumpTarget);
+
+ if (compIsForInlining())
+ {
+ if (compInlineResult->IsFailure())
+ {
+ return;
+ }
+
+ bool hasReturnBlocks = false;
+ bool hasMoreThanOneReturnBlock = false;
+
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ if (block->bbJumpKind == BBJ_RETURN)
+ {
+ if (hasReturnBlocks)
+ {
+ hasMoreThanOneReturnBlock = true;
+ break;
+ }
+
+ hasReturnBlocks = true;
+ }
+ }
+
+ if (!hasReturnBlocks && !compInlineResult->UsesLegacyPolicy())
+ {
+ //
+ // Mark the call node as "no return". The inliner might ignore CALLEE_DOES_NOT_RETURN and
+ // fail inline for a different reasons. In that case we still want to make the "no return"
+ // information available to the caller as it can impact caller's code quality.
+ //
+
+ impInlineInfo->iciCall->gtCallMoreFlags |= GTF_CALL_M_DOES_NOT_RETURN;
+ }
+
+ compInlineResult->NoteBool(InlineObservation::CALLEE_DOES_NOT_RETURN, !hasReturnBlocks);
+
+ if (compInlineResult->IsFailure())
+ {
+ return;
+ }
+
+ noway_assert(info.compXcptnsCount == 0);
+ compHndBBtab = impInlineInfo->InlinerCompiler->compHndBBtab;
+ compHndBBtabAllocCount =
+ impInlineInfo->InlinerCompiler->compHndBBtabAllocCount; // we probably only use the table, not add to it.
+ compHndBBtabCount = impInlineInfo->InlinerCompiler->compHndBBtabCount;
+ info.compXcptnsCount = impInlineInfo->InlinerCompiler->info.compXcptnsCount;
+
+ if (info.compRetNativeType != TYP_VOID && hasMoreThanOneReturnBlock)
+ {
+ // The lifetime of this var might expand multiple BBs. So it is a long lifetime compiler temp.
+ lvaInlineeReturnSpillTemp = lvaGrabTemp(false DEBUGARG("Inline candidate multiple BBJ_RETURN spill temp"));
+ lvaTable[lvaInlineeReturnSpillTemp].lvType = info.compRetNativeType;
+ }
+ return;
+ }
+
+ /* Mark all blocks within 'try' blocks as such */
+
+ if (info.compXcptnsCount == 0)
+ {
+ return;
+ }
+
+ if (info.compXcptnsCount > MAX_XCPTN_INDEX)
+ {
+ IMPL_LIMITATION("too many exception clauses");
+ }
+
+ /* Allocate the exception handler table */
+
+ fgAllocEHTable();
+
+ /* Assume we don't need to sort the EH table (such that nested try/catch
+ * appear before their try or handler parent). The EH verifier will notice
+ * when we do need to sort it.
+ */
+
+ fgNeedToSortEHTable = false;
+
+ verInitEHTree(info.compXcptnsCount);
+ EHNodeDsc* initRoot = ehnNext; // remember the original root since
+ // it may get modified during insertion
+
+ // Annotate BBs with exception handling information required for generating correct eh code
+ // as well as checking for correct IL
+
+ EHblkDsc* HBtab;
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ CORINFO_EH_CLAUSE clause;
+ info.compCompHnd->getEHinfo(info.compMethodHnd, XTnum, &clause);
+ noway_assert(clause.HandlerLength != (unsigned)-1); // @DEPRECATED
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ dispIncomingEHClause(XTnum, clause);
+ }
+#endif // DEBUG
+
+ IL_OFFSET tryBegOff = clause.TryOffset;
+ IL_OFFSET tryEndOff = tryBegOff + clause.TryLength;
+ IL_OFFSET filterBegOff = 0;
+ IL_OFFSET hndBegOff = clause.HandlerOffset;
+ IL_OFFSET hndEndOff = hndBegOff + clause.HandlerLength;
+
+ if (clause.Flags & CORINFO_EH_CLAUSE_FILTER)
+ {
+ filterBegOff = clause.FilterOffset;
+ }
+
+ if (tryEndOff > info.compILCodeSize)
+ {
+ BADCODE3("end of try block beyond end of method for try", " at offset %04X", tryBegOff);
+ }
+ if (hndEndOff > info.compILCodeSize)
+ {
+ BADCODE3("end of hnd block beyond end of method for try", " at offset %04X", tryBegOff);
+ }
+
+ HBtab->ebdTryBegOffset = tryBegOff;
+ HBtab->ebdTryEndOffset = tryEndOff;
+ HBtab->ebdFilterBegOffset = filterBegOff;
+ HBtab->ebdHndBegOffset = hndBegOff;
+ HBtab->ebdHndEndOffset = hndEndOff;
+
+ /* Convert the various addresses to basic blocks */
+
+ BasicBlock* tryBegBB = fgLookupBB(tryBegOff);
+ BasicBlock* tryEndBB =
+ fgLookupBB(tryEndOff); // note: this can be NULL if the try region is at the end of the function
+ BasicBlock* hndBegBB = fgLookupBB(hndBegOff);
+ BasicBlock* hndEndBB = nullptr;
+ BasicBlock* filtBB = nullptr;
+ BasicBlock* block;
+
+ //
+ // Assert that the try/hnd beginning blocks are set up correctly
+ //
+ if (tryBegBB == nullptr)
+ {
+ BADCODE("Try Clause is invalid");
+ }
+
+ if (hndBegBB == nullptr)
+ {
+ BADCODE("Handler Clause is invalid");
+ }
+
+ tryBegBB->bbFlags |= BBF_HAS_LABEL;
+ hndBegBB->bbFlags |= BBF_HAS_LABEL | BBF_JMP_TARGET;
+
+#if HANDLER_ENTRY_MUST_BE_IN_HOT_SECTION
+ // This will change the block weight from 0 to 1
+ // and clear the rarely run flag
+ hndBegBB->makeBlockHot();
+#else
+ hndBegBB->bbSetRunRarely(); // handler entry points are rarely executed
+#endif
+
+ if (hndEndOff < info.compILCodeSize)
+ {
+ hndEndBB = fgLookupBB(hndEndOff);
+ }
+
+ if (clause.Flags & CORINFO_EH_CLAUSE_FILTER)
+ {
+ filtBB = HBtab->ebdFilter = fgLookupBB(clause.FilterOffset);
+
+ filtBB->bbCatchTyp = BBCT_FILTER;
+ filtBB->bbFlags |= BBF_HAS_LABEL | BBF_JMP_TARGET;
+
+ hndBegBB->bbCatchTyp = BBCT_FILTER_HANDLER;
+
+#if HANDLER_ENTRY_MUST_BE_IN_HOT_SECTION
+ // This will change the block weight from 0 to 1
+ // and clear the rarely run flag
+ filtBB->makeBlockHot();
+#else
+ filtBB->bbSetRunRarely(); // filter entry points are rarely executed
+#endif
+
+ // Mark all BBs that belong to the filter with the XTnum of the corresponding handler
+ for (block = filtBB; /**/; block = block->bbNext)
+ {
+ if (block == nullptr)
+ {
+ BADCODE3("Missing endfilter for filter", " at offset %04X", filtBB->bbCodeOffs);
+ return;
+ }
+
+ // Still inside the filter
+ block->setHndIndex(XTnum);
+
+ if (block->bbJumpKind == BBJ_EHFILTERRET)
+ {
+ // Mark catch handler as successor.
+ block->bbJumpDest = hndBegBB;
+ assert(block->bbJumpDest->bbCatchTyp == BBCT_FILTER_HANDLER);
+ break;
+ }
+ }
+
+ if (!block->bbNext || block->bbNext != hndBegBB)
+ {
+ BADCODE3("Filter does not immediately precede handler for filter", " at offset %04X",
+ filtBB->bbCodeOffs);
+ }
+ }
+ else
+ {
+ HBtab->ebdTyp = clause.ClassToken;
+
+ /* Set bbCatchTyp as appropriate */
+
+ if (clause.Flags & CORINFO_EH_CLAUSE_FINALLY)
+ {
+ hndBegBB->bbCatchTyp = BBCT_FINALLY;
+ }
+ else
+ {
+ if (clause.Flags & CORINFO_EH_CLAUSE_FAULT)
+ {
+ hndBegBB->bbCatchTyp = BBCT_FAULT;
+ }
+ else
+ {
+ hndBegBB->bbCatchTyp = clause.ClassToken;
+
+ // These values should be non-zero value that will
+ // not collide with real tokens for bbCatchTyp
+ if (clause.ClassToken == 0)
+ {
+ BADCODE("Exception catch type is Null");
+ }
+
+ noway_assert(clause.ClassToken != BBCT_FAULT);
+ noway_assert(clause.ClassToken != BBCT_FINALLY);
+ noway_assert(clause.ClassToken != BBCT_FILTER);
+ noway_assert(clause.ClassToken != BBCT_FILTER_HANDLER);
+ }
+ }
+ }
+
+ /* Mark the initial block and last blocks in the 'try' region */
+
+ tryBegBB->bbFlags |= BBF_TRY_BEG | BBF_HAS_LABEL;
+
+ /* Prevent future optimizations of removing the first block */
+ /* of a TRY block and the first block of an exception handler */
+
+ tryBegBB->bbFlags |= BBF_DONT_REMOVE;
+ hndBegBB->bbFlags |= BBF_DONT_REMOVE;
+ hndBegBB->bbRefs++; // The first block of a handler gets an extra, "artificial" reference count.
+
+ if (clause.Flags & CORINFO_EH_CLAUSE_FILTER)
+ {
+ filtBB->bbFlags |= BBF_DONT_REMOVE;
+ filtBB->bbRefs++; // The first block of a filter gets an extra, "artificial" reference count.
+ }
+
+ tryBegBB->bbFlags |= BBF_DONT_REMOVE;
+ hndBegBB->bbFlags |= BBF_DONT_REMOVE;
+
+ //
+ // Store the info to the table of EH block handlers
+ //
+
+ HBtab->ebdHandlerType = ToEHHandlerType(clause.Flags);
+
+ HBtab->ebdTryBeg = tryBegBB;
+ HBtab->ebdTryLast = (tryEndBB == nullptr) ? fgLastBB : tryEndBB->bbPrev;
+
+ HBtab->ebdHndBeg = hndBegBB;
+ HBtab->ebdHndLast = (hndEndBB == nullptr) ? fgLastBB : hndEndBB->bbPrev;
+
+ //
+ // Assert that all of our try/hnd blocks are setup correctly.
+ //
+ if (HBtab->ebdTryLast == nullptr)
+ {
+ BADCODE("Try Clause is invalid");
+ }
+
+ if (HBtab->ebdHndLast == nullptr)
+ {
+ BADCODE("Handler Clause is invalid");
+ }
+
+ //
+ // Verify that it's legal
+ //
+
+ verInsertEhNode(&clause, HBtab);
+
+ } // end foreach handler table entry
+
+ fgSortEHTable();
+
+ // Next, set things related to nesting that depend on the sorting being complete.
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ /* Mark all blocks in the finally/fault or catch clause */
+
+ BasicBlock* tryBegBB = HBtab->ebdTryBeg;
+ BasicBlock* hndBegBB = HBtab->ebdHndBeg;
+
+ IL_OFFSET tryBegOff = HBtab->ebdTryBegOffset;
+ IL_OFFSET tryEndOff = HBtab->ebdTryEndOffset;
+
+ IL_OFFSET hndBegOff = HBtab->ebdHndBegOffset;
+ IL_OFFSET hndEndOff = HBtab->ebdHndEndOffset;
+
+ BasicBlock* block;
+
+ for (block = hndBegBB; block && (block->bbCodeOffs < hndEndOff); block = block->bbNext)
+ {
+ if (!block->hasHndIndex())
+ {
+ block->setHndIndex(XTnum);
+ }
+
+ // All blocks in a catch handler or filter are rarely run, except the entry
+ if ((block != hndBegBB) && (hndBegBB->bbCatchTyp != BBCT_FINALLY))
+ {
+ block->bbSetRunRarely();
+ }
+ }
+
+ /* Mark all blocks within the covered range of the try */
+
+ for (block = tryBegBB; block && (block->bbCodeOffs < tryEndOff); block = block->bbNext)
+ {
+ /* Mark this BB as belonging to a 'try' block */
+
+ if (!block->hasTryIndex())
+ {
+ block->setTryIndex(XTnum);
+ }
+
+#ifdef DEBUG
+ /* Note: the BB can't span the 'try' block */
+
+ if (!(block->bbFlags & BBF_INTERNAL))
+ {
+ noway_assert(tryBegOff <= block->bbCodeOffs);
+ noway_assert(tryEndOff >= block->bbCodeOffsEnd || tryEndOff == tryBegOff);
+ }
+#endif
+ }
+
+/* Init ebdHandlerNestingLevel of current clause, and bump up value for all
+ * enclosed clauses (which have to be before it in the table).
+ * Innermost try-finally blocks must precede outermost
+ * try-finally blocks.
+ */
+
+#if !FEATURE_EH_FUNCLETS
+ HBtab->ebdHandlerNestingLevel = 0;
+#endif // !FEATURE_EH_FUNCLETS
+
+ HBtab->ebdEnclosingTryIndex = EHblkDsc::NO_ENCLOSING_INDEX;
+ HBtab->ebdEnclosingHndIndex = EHblkDsc::NO_ENCLOSING_INDEX;
+
+ noway_assert(XTnum < compHndBBtabCount);
+ noway_assert(XTnum == ehGetIndex(HBtab));
+
+ for (EHblkDsc* xtab = compHndBBtab; xtab < HBtab; xtab++)
+ {
+#if !FEATURE_EH_FUNCLETS
+ if (jitIsBetween(xtab->ebdHndBegOffs(), hndBegOff, hndEndOff))
+ {
+ xtab->ebdHandlerNestingLevel++;
+ }
+#endif // !FEATURE_EH_FUNCLETS
+
+ /* If we haven't recorded an enclosing try index for xtab then see
+ * if this EH region should be recorded. We check if the
+ * first offset in the xtab lies within our region. If so,
+ * the last offset also must lie within the region, due to
+ * nesting rules. verInsertEhNode(), below, will check for proper nesting.
+ */
+ if (xtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
+ {
+ bool begBetween = jitIsBetween(xtab->ebdTryBegOffs(), tryBegOff, tryEndOff);
+ if (begBetween)
+ {
+ // Record the enclosing scope link
+ xtab->ebdEnclosingTryIndex = (unsigned short)XTnum;
+ }
+ }
+
+ /* Do the same for the enclosing handler index.
+ */
+ if (xtab->ebdEnclosingHndIndex == EHblkDsc::NO_ENCLOSING_INDEX)
+ {
+ bool begBetween = jitIsBetween(xtab->ebdTryBegOffs(), hndBegOff, hndEndOff);
+ if (begBetween)
+ {
+ // Record the enclosing scope link
+ xtab->ebdEnclosingHndIndex = (unsigned short)XTnum;
+ }
+ }
+ }
+
+ } // end foreach handler table entry
+
+#if !FEATURE_EH_FUNCLETS
+
+ EHblkDsc* HBtabEnd;
+ for (HBtab = compHndBBtab, HBtabEnd = compHndBBtab + compHndBBtabCount; HBtab < HBtabEnd; HBtab++)
+ {
+ if (ehMaxHndNestingCount <= HBtab->ebdHandlerNestingLevel)
+ ehMaxHndNestingCount = HBtab->ebdHandlerNestingLevel + 1;
+ }
+
+#endif // !FEATURE_EH_FUNCLETS
+
+#ifndef DEBUG
+ if (tiVerificationNeeded)
+#endif
+ {
+ // always run these checks for a debug build
+ verCheckNestingLevel(initRoot);
+ }
+
+#ifndef DEBUG
+ // fgNormalizeEH assumes that this test has been passed. And Ssa assumes that fgNormalizeEHTable
+ // has been run. So do this unless we're in minOpts mode (and always in debug).
+ if (tiVerificationNeeded || !opts.MinOpts())
+#endif
+ {
+ fgCheckBasicBlockControlFlow();
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ JITDUMP("*************** After fgFindBasicBlocks() has created the EH table\n");
+ fgDispHandlerTab();
+ }
+
+ // We can't verify the handler table until all the IL legality checks have been done (above), since bad IL
+ // (such as illegal nesting of regions) will trigger asserts here.
+ fgVerifyHandlerTab();
+#endif
+
+ fgNormalizeEH();
+}
+
+/*****************************************************************************
+ * Check control flow constraints for well formed IL. Bail if any of the constraints
+ * are violated.
+ */
+
+void Compiler::fgCheckBasicBlockControlFlow()
+{
+ assert(!fgNormalizeEHDone); // These rules aren't quite correct after EH normalization has introduced new blocks
+
+ EHblkDsc* HBtab;
+
+ for (BasicBlock* blk = fgFirstBB; blk; blk = blk->bbNext)
+ {
+ if (blk->bbFlags & BBF_INTERNAL)
+ {
+ continue;
+ }
+
+ switch (blk->bbJumpKind)
+ {
+ case BBJ_NONE: // block flows into the next one (no jump)
+
+ fgControlFlowPermitted(blk, blk->bbNext);
+
+ break;
+
+ case BBJ_ALWAYS: // block does unconditional jump to target
+
+ fgControlFlowPermitted(blk, blk->bbJumpDest);
+
+ break;
+
+ case BBJ_COND: // block conditionally jumps to the target
+
+ fgControlFlowPermitted(blk, blk->bbNext);
+
+ fgControlFlowPermitted(blk, blk->bbJumpDest);
+
+ break;
+
+ case BBJ_RETURN: // block ends with 'ret'
+
+ if (blk->hasTryIndex() || blk->hasHndIndex())
+ {
+ BADCODE3("Return from a protected block", ". Before offset %04X", blk->bbCodeOffsEnd);
+ }
+ break;
+
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+
+ if (!blk->hasHndIndex()) // must be part of a handler
+ {
+ BADCODE3("Missing handler", ". Before offset %04X", blk->bbCodeOffsEnd);
+ }
+
+ HBtab = ehGetDsc(blk->getHndIndex());
+
+ // Endfilter allowed only in a filter block
+ if (blk->bbJumpKind == BBJ_EHFILTERRET)
+ {
+ if (!HBtab->HasFilter())
+ {
+ BADCODE("Unexpected endfilter");
+ }
+ }
+ // endfinally allowed only in a finally/fault block
+ else if (!HBtab->HasFinallyOrFaultHandler())
+ {
+ BADCODE("Unexpected endfinally");
+ }
+
+ // The handler block should be the innermost block
+ // Exception blocks are listed, innermost first.
+ if (blk->hasTryIndex() && (blk->getTryIndex() < blk->getHndIndex()))
+ {
+ BADCODE("endfinally / endfilter in nested try block");
+ }
+
+ break;
+
+ case BBJ_THROW: // block ends with 'throw'
+ /* throw is permitted from every BB, so nothing to check */
+ /* importer makes sure that rethrow is done from a catch */
+ break;
+
+ case BBJ_LEAVE: // block always jumps to the target, maybe out of guarded
+ // region. Used temporarily until importing
+ fgControlFlowPermitted(blk, blk->bbJumpDest, TRUE);
+
+ break;
+
+ case BBJ_SWITCH: // block ends with a switch statement
+
+ BBswtDesc* swtDesc;
+ swtDesc = blk->bbJumpSwt;
+
+ assert(swtDesc);
+
+ unsigned i;
+ for (i = 0; i < swtDesc->bbsCount; i++)
+ {
+ fgControlFlowPermitted(blk, swtDesc->bbsDstTab[i]);
+ }
+
+ break;
+
+ case BBJ_EHCATCHRET: // block ends with a leave out of a catch (only #if FEATURE_EH_FUNCLETS)
+ case BBJ_CALLFINALLY: // block always calls the target finally
+ default:
+ noway_assert(!"Unexpected bbJumpKind"); // these blocks don't get created until importing
+ break;
+ }
+ }
+}
+
+/****************************************************************************
+ * Check that the leave from the block is legal.
+ * Consider removing this check here if we can do it cheaply during importing
+ */
+
+void Compiler::fgControlFlowPermitted(BasicBlock* blkSrc, BasicBlock* blkDest, BOOL isLeave)
+{
+ assert(!fgNormalizeEHDone); // These rules aren't quite correct after EH normalization has introduced new blocks
+
+ unsigned srcHndBeg, destHndBeg;
+ unsigned srcHndEnd, destHndEnd;
+ bool srcInFilter, destInFilter;
+ bool srcInCatch = false;
+
+ EHblkDsc* srcHndTab;
+
+ srcHndTab = ehInitHndRange(blkSrc, &srcHndBeg, &srcHndEnd, &srcInFilter);
+ ehInitHndRange(blkDest, &destHndBeg, &destHndEnd, &destInFilter);
+
+ /* Impose the rules for leaving or jumping from handler blocks */
+
+ if (blkSrc->hasHndIndex())
+ {
+ srcInCatch = srcHndTab->HasCatchHandler() && srcHndTab->InHndRegionILRange(blkSrc);
+
+ /* Are we jumping within the same handler index? */
+ if (BasicBlock::sameHndRegion(blkSrc, blkDest))
+ {
+ /* Do we have a filter clause? */
+ if (srcHndTab->HasFilter())
+ {
+ /* filters and catch handlers share same eh index */
+ /* we need to check for control flow between them. */
+ if (srcInFilter != destInFilter)
+ {
+ if (!jitIsBetween(blkDest->bbCodeOffs, srcHndBeg, srcHndEnd))
+ {
+ BADCODE3("Illegal control flow between filter and handler", ". Before offset %04X",
+ blkSrc->bbCodeOffsEnd);
+ }
+ }
+ }
+ }
+ else
+ {
+ /* The handler indexes of blkSrc and blkDest are different */
+ if (isLeave)
+ {
+ /* Any leave instructions must not enter the dest handler from outside*/
+ if (!jitIsBetween(srcHndBeg, destHndBeg, destHndEnd))
+ {
+ BADCODE3("Illegal use of leave to enter handler", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+ }
+ else
+ {
+ /* We must use a leave to exit a handler */
+ BADCODE3("Illegal control flow out of a handler", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+
+ /* Do we have a filter clause? */
+ if (srcHndTab->HasFilter())
+ {
+ /* It is ok to leave from the handler block of a filter, */
+ /* but not from the filter block of a filter */
+ if (srcInFilter != destInFilter)
+ {
+ BADCODE3("Illegal to leave a filter handler", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+ }
+
+ /* We should never leave a finally handler */
+ if (srcHndTab->HasFinallyHandler())
+ {
+ BADCODE3("Illegal to leave a finally handler", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+
+ /* We should never leave a fault handler */
+ if (srcHndTab->HasFaultHandler())
+ {
+ BADCODE3("Illegal to leave a fault handler", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+ }
+ }
+ else if (blkDest->hasHndIndex())
+ {
+ /* blkSrc was not inside a handler, but blkDst is inside a handler */
+ BADCODE3("Illegal control flow into a handler", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+
+ /* Are we jumping from a catch handler into the corresponding try? */
+ /* VB uses this for "on error goto " */
+
+ if (isLeave && srcInCatch)
+ {
+ // inspect all handlers containing the jump source
+
+ bool bValidJumpToTry = false; // are we jumping in a valid way from a catch to the corresponding try?
+ bool bCatchHandlerOnly = true; // false if we are jumping out of a non-catch handler
+ EHblkDsc* ehTableEnd;
+ EHblkDsc* ehDsc;
+
+ for (ehDsc = compHndBBtab, ehTableEnd = compHndBBtab + compHndBBtabCount;
+ bCatchHandlerOnly && ehDsc < ehTableEnd; ehDsc++)
+ {
+ if (ehDsc->InHndRegionILRange(blkSrc))
+ {
+ if (ehDsc->HasCatchHandler())
+ {
+ if (ehDsc->InTryRegionILRange(blkDest))
+ {
+ // If we already considered the jump for a different try/catch,
+ // we would have two overlapping try regions with two overlapping catch
+ // regions, which is illegal.
+ noway_assert(!bValidJumpToTry);
+
+ // Allowed if it is the first instruction of an inner try
+ // (and all trys in between)
+ //
+ // try {
+ // ..
+ // _tryAgain:
+ // ..
+ // try {
+ // _tryNestedInner:
+ // ..
+ // try {
+ // _tryNestedIllegal:
+ // ..
+ // } catch {
+ // ..
+ // }
+ // ..
+ // } catch {
+ // ..
+ // }
+ // ..
+ // } catch {
+ // ..
+ // leave _tryAgain // Allowed
+ // ..
+ // leave _tryNestedInner // Allowed
+ // ..
+ // leave _tryNestedIllegal // Not Allowed
+ // ..
+ // }
+ //
+ // Note: The leave is allowed also from catches nested inside the catch shown above.
+
+ /* The common case where leave is to the corresponding try */
+ if (ehDsc->ebdIsSameTry(this, blkDest->getTryIndex()) ||
+ /* Also allowed is a leave to the start of a try which starts in the handler's try */
+ fgFlowToFirstBlockOfInnerTry(ehDsc->ebdTryBeg, blkDest, false))
+ {
+ bValidJumpToTry = true;
+ }
+ }
+ }
+ else
+ {
+ // We are jumping from a handler which is not a catch handler.
+
+ // If it's a handler, but not a catch handler, it must be either a finally or fault
+ if (!ehDsc->HasFinallyOrFaultHandler())
+ {
+ BADCODE3("Handlers must be catch, finally, or fault", ". Before offset %04X",
+ blkSrc->bbCodeOffsEnd);
+ }
+
+ // Are we jumping out of this handler?
+ if (!ehDsc->InHndRegionILRange(blkDest))
+ {
+ bCatchHandlerOnly = false;
+ }
+ }
+ }
+ else if (ehDsc->InFilterRegionILRange(blkSrc))
+ {
+ // Are we jumping out of a filter?
+ if (!ehDsc->InFilterRegionILRange(blkDest))
+ {
+ bCatchHandlerOnly = false;
+ }
+ }
+ }
+
+ if (bCatchHandlerOnly)
+ {
+ if (bValidJumpToTry)
+ {
+ return;
+ }
+ else
+ {
+ // FALL THROUGH
+ // This is either the case of a leave to outside the try/catch,
+ // or a leave to a try not nested in this try/catch.
+ // The first case is allowed, the second one will be checked
+ // later when we check the try block rules (it is illegal if we
+ // jump to the middle of the destination try).
+ }
+ }
+ else
+ {
+ BADCODE3("illegal leave to exit a finally, fault or filter", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+ }
+
+ /* Check all the try block rules */
+
+ IL_OFFSET srcTryBeg;
+ IL_OFFSET srcTryEnd;
+ IL_OFFSET destTryBeg;
+ IL_OFFSET destTryEnd;
+
+ ehInitTryRange(blkSrc, &srcTryBeg, &srcTryEnd);
+ ehInitTryRange(blkDest, &destTryBeg, &destTryEnd);
+
+ /* Are we jumping between try indexes? */
+ if (!BasicBlock::sameTryRegion(blkSrc, blkDest))
+ {
+ // Are we exiting from an inner to outer try?
+ if (jitIsBetween(srcTryBeg, destTryBeg, destTryEnd) && jitIsBetween(srcTryEnd - 1, destTryBeg, destTryEnd))
+ {
+ if (!isLeave)
+ {
+ BADCODE3("exit from try block without a leave", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+ }
+ else if (jitIsBetween(destTryBeg, srcTryBeg, srcTryEnd))
+ {
+ // check that the dest Try is first instruction of an inner try
+ if (!fgFlowToFirstBlockOfInnerTry(blkSrc, blkDest, false))
+ {
+ BADCODE3("control flow into middle of try", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+ }
+ else // there is no nesting relationship between src and dest
+ {
+ if (isLeave)
+ {
+ // check that the dest Try is first instruction of an inner try sibling
+ if (!fgFlowToFirstBlockOfInnerTry(blkSrc, blkDest, true))
+ {
+ BADCODE3("illegal leave into middle of try", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+ }
+ else
+ {
+ BADCODE3("illegal control flow in to/out of try block", ". Before offset %04X", blkSrc->bbCodeOffsEnd);
+ }
+ }
+ }
+}
+
+/*****************************************************************************
+ * Check that blkDest is the first block of an inner try or a sibling
+ * with no intervening trys in between
+ */
+
+bool Compiler::fgFlowToFirstBlockOfInnerTry(BasicBlock* blkSrc, BasicBlock* blkDest, bool sibling)
+{
+ assert(!fgNormalizeEHDone); // These rules aren't quite correct after EH normalization has introduced new blocks
+
+ noway_assert(blkDest->hasTryIndex());
+
+ unsigned XTnum = blkDest->getTryIndex();
+ unsigned lastXTnum = blkSrc->hasTryIndex() ? blkSrc->getTryIndex() : compHndBBtabCount;
+ noway_assert(XTnum < compHndBBtabCount);
+ noway_assert(lastXTnum <= compHndBBtabCount);
+
+ EHblkDsc* HBtab = ehGetDsc(XTnum);
+
+ // check that we are not jumping into middle of try
+ if (HBtab->ebdTryBeg != blkDest)
+ {
+ return false;
+ }
+
+ if (sibling)
+ {
+ noway_assert(!BasicBlock::sameTryRegion(blkSrc, blkDest));
+
+ // find the l.u.b of the two try ranges
+ // Set lastXTnum to the l.u.b.
+
+ HBtab = ehGetDsc(lastXTnum);
+
+ for (lastXTnum++, HBtab++; lastXTnum < compHndBBtabCount; lastXTnum++, HBtab++)
+ {
+ if (jitIsBetweenInclusive(blkDest->bbNum, HBtab->ebdTryBeg->bbNum, HBtab->ebdTryLast->bbNum))
+ {
+ break;
+ }
+ }
+ }
+
+ // now check there are no intervening trys between dest and l.u.b
+ // (it is ok to have intervening trys as long as they all start at
+ // the same code offset)
+
+ HBtab = ehGetDsc(XTnum);
+
+ for (XTnum++, HBtab++; XTnum < lastXTnum; XTnum++, HBtab++)
+ {
+ if (HBtab->ebdTryBeg->bbNum < blkDest->bbNum && blkDest->bbNum <= HBtab->ebdTryLast->bbNum)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*****************************************************************************
+ * Returns the handler nesting level of the block.
+ * *pFinallyNesting is set to the nesting level of the inner-most
+ * finally-protected try the block is in.
+ */
+
+unsigned Compiler::fgGetNestingLevel(BasicBlock* block, unsigned* pFinallyNesting)
+{
+ unsigned curNesting = 0; // How many handlers is the block in
+ unsigned tryFin = (unsigned)-1; // curNesting when we see innermost finally-protected try
+ unsigned XTnum;
+ EHblkDsc* HBtab;
+
+ /* We find the block's handler nesting level by walking over the
+ complete exception table and find enclosing clauses. */
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ noway_assert(HBtab->ebdTryBeg && HBtab->ebdHndBeg);
+
+ if (HBtab->HasFinallyHandler() && (tryFin == (unsigned)-1) && bbInTryRegions(XTnum, block))
+ {
+ tryFin = curNesting;
+ }
+ else if (bbInHandlerRegions(XTnum, block))
+ {
+ curNesting++;
+ }
+ }
+
+ if (tryFin == (unsigned)-1)
+ {
+ tryFin = curNesting;
+ }
+
+ if (pFinallyNesting)
+ {
+ *pFinallyNesting = curNesting - tryFin;
+ }
+
+ return curNesting;
+}
+
+/*****************************************************************************
+ *
+ * Import the basic blocks of the procedure.
+ */
+
+void Compiler::fgImport()
+{
+ fgHasPostfix = false;
+
+ impImport(fgFirstBB);
+
+ if (!(opts.eeFlags & CORJIT_FLG_SKIP_VERIFICATION))
+ {
+ CorInfoMethodRuntimeFlags verFlag;
+ verFlag = tiIsVerifiableCode ? CORINFO_FLG_VERIFIABLE : CORINFO_FLG_UNVERIFIABLE;
+ info.compCompHnd->setMethodAttribs(info.compMethodHnd, verFlag);
+ }
+}
+
+/*****************************************************************************
+ * This function returns true if tree is a node with a call
+ * that unconditionally throws an exception
+ */
+
+bool Compiler::fgIsThrow(GenTreePtr tree)
+{
+ if ((tree->gtOper != GT_CALL) || (tree->gtCall.gtCallType != CT_HELPER))
+ {
+ return false;
+ }
+
+ // TODO-Throughput: Replace all these calls to eeFindHelper() with a table based lookup
+
+ if ((tree->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_OVERFLOW)) ||
+ (tree->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_VERIFICATION)) ||
+ (tree->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_RNGCHKFAIL)) ||
+ (tree->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_THROWDIVZERO)) ||
+#if COR_JIT_EE_VERSION > 460
+ (tree->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_THROWNULLREF)) ||
+#endif // COR_JIT_EE_VERSION
+ (tree->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_THROW)) ||
+ (tree->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_RETHROW)))
+ {
+ noway_assert(tree->gtFlags & GTF_CALL);
+ noway_assert(tree->gtFlags & GTF_EXCEPT);
+ return true;
+ }
+
+ // TODO-CQ: there are a bunch of managed methods in [mscorlib]System.ThrowHelper
+ // that would be nice to recognize.
+
+ return false;
+}
+
+/*****************************************************************************
+ * This function returns true for blocks that are in different hot-cold regions.
+ * It returns false when the blocks are both in the same regions
+ */
+
+bool Compiler::fgInDifferentRegions(BasicBlock* blk1, BasicBlock* blk2)
+{
+ noway_assert(blk1 != nullptr);
+ noway_assert(blk2 != nullptr);
+
+ if (fgFirstColdBlock == nullptr)
+ {
+ return false;
+ }
+
+ // If one block is Hot and the other is Cold then we are in different regions
+ return ((blk1->bbFlags & BBF_COLD) != (blk2->bbFlags & BBF_COLD));
+}
+
+bool Compiler::fgIsBlockCold(BasicBlock* blk)
+{
+ noway_assert(blk != nullptr);
+
+ if (fgFirstColdBlock == nullptr)
+ {
+ return false;
+ }
+
+ return ((blk->bbFlags & BBF_COLD) != 0);
+}
+
+/*****************************************************************************
+ * This function returns true if tree is a GT_COMMA node with a call
+ * that unconditionally throws an exception
+ */
+
+bool Compiler::fgIsCommaThrow(GenTreePtr tree, bool forFolding /* = false */)
+{
+ // Instead of always folding comma throws,
+ // with stress enabled we only fold half the time
+
+ if (forFolding && compStressCompile(STRESS_FOLD, 50))
+ {
+ return false; /* Don't fold */
+ }
+
+ /* Check for cast of a GT_COMMA with a throw overflow */
+ if ((tree->gtOper == GT_COMMA) && (tree->gtFlags & GTF_CALL) && (tree->gtFlags & GTF_EXCEPT))
+ {
+ return (fgIsThrow(tree->gtOp.gtOp1));
+ }
+ return false;
+}
+
+//------------------------------------------------------------------------
+// fgIsIndirOfAddrOfLocal: Determine whether "tree" is an indirection of a local.
+//
+// Arguments:
+// tree - The tree node under consideration
+//
+// Return Value:
+// If "tree" is a indirection (GT_IND, GT_BLK, or GT_OBJ) whose arg is an ADDR,
+// whose arg in turn is a LCL_VAR, return that LCL_VAR node, else nullptr.
+//
+// static
+GenTreePtr Compiler::fgIsIndirOfAddrOfLocal(GenTreePtr tree)
+{
+ GenTreePtr res = nullptr;
+ if (tree->OperIsIndir())
+ {
+ GenTreePtr addr = tree->AsIndir()->Addr();
+
+ // Post rationalization, we can have Indir(Lea(..) trees. Therefore to recognize
+ // Indir of addr of a local, skip over Lea in Indir(Lea(base, index, scale, offset))
+ // to get to base variable.
+ if (addr->OperGet() == GT_LEA)
+ {
+ // We use this method in backward dataflow after liveness computation - fgInterBlockLocalVarLiveness().
+ // Therefore it is critical that we don't miss 'uses' of any local. It may seem this method overlooks
+ // if the index part of the LEA has indir( someAddrOperator ( lclVar ) ) to search for a use but it's
+ // covered by the fact we're traversing the expression in execution order and we also visit the index.
+ GenTreeAddrMode* lea = addr->AsAddrMode();
+ GenTreePtr base = lea->Base();
+
+ if (base != nullptr)
+ {
+ if (base->OperGet() == GT_IND)
+ {
+ return fgIsIndirOfAddrOfLocal(base);
+ }
+ // else use base as addr
+ addr = base;
+ }
+ }
+
+ if (addr->OperGet() == GT_ADDR)
+ {
+ GenTreePtr lclvar = addr->gtOp.gtOp1;
+ if (lclvar->OperGet() == GT_LCL_VAR)
+ {
+ res = lclvar;
+ }
+ }
+ else if (addr->OperGet() == GT_LCL_VAR_ADDR)
+ {
+ res = addr;
+ }
+ }
+ return res;
+}
+
+GenTreePtr Compiler::fgGetStaticsCCtorHelper(CORINFO_CLASS_HANDLE cls, CorInfoHelpFunc helper)
+{
+ bool bNeedClassID = true;
+ unsigned callFlags = 0;
+
+ var_types type = TYP_BYREF;
+
+ // This is sort of ugly, as we have knowledge of what the helper is returning.
+ // We need the return type.
+ switch (helper)
+ {
+ case CORINFO_HELP_GETSHARED_GCSTATIC_BASE_NOCTOR:
+ bNeedClassID = false;
+ __fallthrough;
+
+ case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_NOCTOR:
+ callFlags |= GTF_CALL_HOISTABLE;
+ __fallthrough;
+
+ case CORINFO_HELP_GETSHARED_GCSTATIC_BASE:
+ case CORINFO_HELP_GETSHARED_GCSTATIC_BASE_DYNAMICCLASS:
+ case CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE_DYNAMICCLASS:
+ case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE:
+ case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_DYNAMICCLASS:
+ // type = TYP_BYREF;
+ break;
+
+ case CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE_NOCTOR:
+ bNeedClassID = false;
+ __fallthrough;
+
+ case CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_NOCTOR:
+ callFlags |= GTF_CALL_HOISTABLE;
+ __fallthrough;
+
+ case CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE:
+ case CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE:
+ case CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_DYNAMICCLASS:
+ case CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS:
+ type = TYP_I_IMPL;
+ break;
+
+ default:
+ assert(!"unknown shared statics helper");
+ break;
+ }
+
+ GenTreeArgList* argList = nullptr;
+
+ GenTreePtr opModuleIDArg;
+ GenTreePtr opClassIDArg;
+
+ // Get the class ID
+ unsigned clsID;
+ size_t moduleID;
+ void* pclsID;
+ void* pmoduleID;
+
+ clsID = info.compCompHnd->getClassDomainID(cls, &pclsID);
+
+ moduleID = info.compCompHnd->getClassModuleIdForStatics(cls, nullptr, &pmoduleID);
+
+ if (!(callFlags & GTF_CALL_HOISTABLE))
+ {
+ if (info.compCompHnd->getClassAttribs(cls) & CORINFO_FLG_BEFOREFIELDINIT)
+ {
+ callFlags |= GTF_CALL_HOISTABLE;
+ }
+ }
+
+ if (pmoduleID)
+ {
+ opModuleIDArg = gtNewIconHandleNode((size_t)pmoduleID, GTF_ICON_CIDMID_HDL);
+ opModuleIDArg = gtNewOperNode(GT_IND, TYP_I_IMPL, opModuleIDArg);
+ opModuleIDArg->gtFlags |= GTF_IND_INVARIANT;
+ }
+ else
+ {
+ opModuleIDArg = gtNewIconNode((size_t)moduleID, TYP_I_IMPL);
+ }
+
+ if (bNeedClassID)
+ {
+ if (pclsID)
+ {
+ opClassIDArg = gtNewIconHandleNode((size_t)pclsID, GTF_ICON_CIDMID_HDL);
+ opClassIDArg = gtNewOperNode(GT_IND, TYP_INT, opClassIDArg);
+ opClassIDArg->gtFlags |= GTF_IND_INVARIANT;
+ }
+ else
+ {
+ opClassIDArg = gtNewIconNode(clsID, TYP_INT);
+ }
+
+ // call the helper to get the base
+ argList = gtNewArgList(opModuleIDArg, opClassIDArg);
+ }
+ else
+ {
+ argList = gtNewArgList(opModuleIDArg);
+ }
+
+ if (!s_helperCallProperties.NoThrow(helper))
+ {
+ callFlags |= GTF_EXCEPT;
+ }
+
+ return gtNewHelperCallNode(helper, type, callFlags, argList);
+}
+
+GenTreePtr Compiler::fgGetSharedCCtor(CORINFO_CLASS_HANDLE cls)
+{
+#ifdef FEATURE_READYTORUN_COMPILER
+ if (opts.IsReadyToRun())
+ {
+ CORINFO_RESOLVED_TOKEN resolvedToken;
+ ZeroMemory(&resolvedToken, sizeof(resolvedToken));
+ resolvedToken.hClass = cls;
+
+ return impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_STATIC_BASE, TYP_BYREF);
+ }
+#endif
+
+ // Call the shared non gc static helper, as its the fastest
+ return fgGetStaticsCCtorHelper(cls, info.compCompHnd->getSharedCCtorHelper(cls));
+}
+
+//
+// Returns true unless the address expression could
+// never represent a NULL
+//
+bool Compiler::fgAddrCouldBeNull(GenTreePtr addr)
+{
+ if (addr->gtOper == GT_ADDR)
+ {
+ if (addr->gtOp.gtOp1->gtOper == GT_CNS_INT)
+ {
+ GenTreePtr cns1Tree = addr->gtOp.gtOp1;
+ if (!cns1Tree->IsIconHandle())
+ {
+ // Indirection of some random constant...
+ // It is safest just to return true
+ return true;
+ }
+ }
+ else if (addr->gtOp.gtOp1->OperIsLocalAddr())
+ {
+ return false;
+ }
+ return false; // we can't have a null address
+ }
+ else if (addr->gtOper == GT_ADD)
+ {
+ if (addr->gtOp.gtOp1->gtOper == GT_CNS_INT)
+ {
+ GenTreePtr cns1Tree = addr->gtOp.gtOp1;
+ if (!cns1Tree->IsIconHandle())
+ {
+ if (!fgIsBigOffset(cns1Tree->gtIntCon.gtIconVal))
+ {
+ // Op1 was an ordinary small constant
+ return fgAddrCouldBeNull(addr->gtOp.gtOp2);
+ }
+ }
+ else // Op1 was a handle represented as a constant
+ {
+ // Is Op2 also a constant?
+ if (addr->gtOp.gtOp2->gtOper == GT_CNS_INT)
+ {
+ GenTreePtr cns2Tree = addr->gtOp.gtOp2;
+ // Is this an addition of a handle and constant
+ if (!cns2Tree->IsIconHandle())
+ {
+ if (!fgIsBigOffset(cns2Tree->gtIntCon.gtIconVal))
+ {
+ // Op2 was an ordinary small constant
+ return false; // we can't have a null address
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // Op1 is not a constant
+ // What about Op2?
+ if (addr->gtOp.gtOp2->gtOper == GT_CNS_INT)
+ {
+ GenTreePtr cns2Tree = addr->gtOp.gtOp2;
+ // Is this an addition of a small constant
+ if (!cns2Tree->IsIconHandle())
+ {
+ if (!fgIsBigOffset(cns2Tree->gtIntCon.gtIconVal))
+ {
+ // Op2 was an ordinary small constant
+ return fgAddrCouldBeNull(addr->gtOp.gtOp1);
+ }
+ }
+ }
+ }
+ }
+ return true; // default result: addr could be null
+}
+
+/*****************************************************************************
+ * Optimize the call to the delegate constructor.
+ */
+
+GenTreePtr Compiler::fgOptimizeDelegateConstructor(GenTreePtr call, CORINFO_CONTEXT_HANDLE* ExactContextHnd)
+{
+ noway_assert(call->gtOper == GT_CALL);
+
+ noway_assert(call->gtCall.gtCallType == CT_USER_FUNC);
+ CORINFO_METHOD_HANDLE methHnd = call->gtCall.gtCallMethHnd;
+ CORINFO_CLASS_HANDLE clsHnd = info.compCompHnd->getMethodClass(methHnd);
+
+ GenTreePtr targetMethod = call->gtCall.gtCallArgs->gtOp.gtOp2->gtOp.gtOp1;
+ noway_assert(targetMethod->TypeGet() == TYP_I_IMPL);
+ genTreeOps oper = targetMethod->OperGet();
+ if (oper == GT_FTN_ADDR || oper == GT_CALL || oper == GT_QMARK)
+ {
+ CORINFO_METHOD_HANDLE targetMethodHnd = nullptr;
+ GenTreePtr qmarkNode = nullptr;
+ if (oper == GT_FTN_ADDR)
+ {
+ targetMethodHnd = targetMethod->gtFptrVal.gtFptrMethod;
+ }
+ else if (oper == GT_CALL && targetMethod->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_VIRTUAL_FUNC_PTR))
+ {
+ GenTreePtr handleNode = targetMethod->gtCall.gtCallArgs->gtOp.gtOp2->gtOp.gtOp2->gtOp.gtOp1;
+
+ if (handleNode->OperGet() == GT_CNS_INT)
+ {
+ // it's a ldvirtftn case, fetch the methodhandle off the helper for ldvirtftn. It's the 3rd arg
+ targetMethodHnd = CORINFO_METHOD_HANDLE(handleNode->gtIntCon.gtCompileTimeHandle);
+ }
+ // Sometimes the argument to this is the result of a generic dictionary lookup, which shows
+ // up as a GT_QMARK.
+ else if (handleNode->OperGet() == GT_QMARK)
+ {
+ qmarkNode = handleNode;
+ }
+ }
+ // Sometimes we don't call CORINFO_HELP_VIRTUAL_FUNC_PTR but instead just call
+ // CORINFO_HELP_RUNTIMEHANDLE_METHOD directly.
+ else if (oper == GT_QMARK)
+ {
+ qmarkNode = targetMethod;
+ }
+ if (qmarkNode)
+ {
+ noway_assert(qmarkNode->OperGet() == GT_QMARK);
+ // The argument is actually a generic dictionary lookup. For delegate creation it looks
+ // like:
+ // GT_QMARK
+ // GT_COLON
+ // op1 -> call
+ // Arg 1 -> token (has compile time handle)
+ // op2 -> lclvar
+ //
+ //
+ // In this case I can find the token (which is a method handle) and that is the compile time
+ // handle.
+ noway_assert(qmarkNode->gtOp.gtOp2->OperGet() == GT_COLON);
+ noway_assert(qmarkNode->gtOp.gtOp2->gtOp.gtOp1->OperGet() == GT_CALL);
+ GenTreePtr runtimeLookupCall = qmarkNode->gtOp.gtOp2->gtOp.gtOp1;
+
+ // This could be any of CORINFO_HELP_RUNTIMEHANDLE_(METHOD|CLASS)(_LOG?)
+ GenTreePtr tokenNode = runtimeLookupCall->gtCall.gtCallArgs->gtOp.gtOp2->gtOp.gtOp1;
+ noway_assert(tokenNode->OperGet() == GT_CNS_INT);
+ targetMethodHnd = CORINFO_METHOD_HANDLE(tokenNode->gtIntCon.gtCompileTimeHandle);
+ }
+
+#ifdef FEATURE_READYTORUN_COMPILER
+ if (opts.IsReadyToRun())
+ {
+ // ReadyToRun has this optimization for a non-virtual function pointers only for now.
+ if (oper == GT_FTN_ADDR)
+ {
+ // The first argument of the helper is delegate this pointer
+ GenTreeArgList* helperArgs = gtNewArgList(call->gtCall.gtCallObjp);
+ CORINFO_CONST_LOOKUP entryPoint;
+
+ // The second argument of the helper is the target object pointers
+ helperArgs->gtOp.gtOp2 = gtNewArgList(call->gtCall.gtCallArgs->gtOp.gtOp1);
+
+ call = gtNewHelperCallNode(CORINFO_HELP_READYTORUN_DELEGATE_CTOR, TYP_VOID, GTF_EXCEPT, helperArgs);
+#if COR_JIT_EE_VERSION > 460
+ info.compCompHnd->getReadyToRunDelegateCtorHelper(targetMethod->gtFptrVal.gtLdftnResolvedToken, clsHnd,
+ &entryPoint);
+#else
+ info.compCompHnd->getReadyToRunHelper(targetMethod->gtFptrVal.gtLdftnResolvedToken,
+ CORINFO_HELP_READYTORUN_DELEGATE_CTOR, &entryPoint);
+#endif
+ call->gtCall.setEntryPoint(entryPoint);
+ }
+ }
+ else
+#endif
+ if (targetMethodHnd != nullptr)
+ {
+ CORINFO_METHOD_HANDLE alternateCtor = nullptr;
+ DelegateCtorArgs ctorData;
+ ctorData.pMethod = info.compMethodHnd;
+ ctorData.pArg3 = nullptr;
+ ctorData.pArg4 = nullptr;
+ ctorData.pArg5 = nullptr;
+
+ alternateCtor = info.compCompHnd->GetDelegateCtor(methHnd, clsHnd, targetMethodHnd, &ctorData);
+ if (alternateCtor != methHnd)
+ {
+ // we erase any inline info that may have been set for generics has it is not needed here,
+ // and in fact it will pass the wrong info to the inliner code
+ *ExactContextHnd = nullptr;
+
+ call->gtCall.gtCallMethHnd = alternateCtor;
+
+ noway_assert(call->gtCall.gtCallArgs->gtOp.gtOp2->gtOp.gtOp2 == nullptr);
+ if (ctorData.pArg3)
+ {
+ call->gtCall.gtCallArgs->gtOp.gtOp2->gtOp.gtOp2 =
+ gtNewArgList(gtNewIconHandleNode(size_t(ctorData.pArg3), GTF_ICON_FTN_ADDR));
+
+ if (ctorData.pArg4)
+ {
+ call->gtCall.gtCallArgs->gtOp.gtOp2->gtOp.gtOp2->gtOp.gtOp2 =
+ gtNewArgList(gtNewIconHandleNode(size_t(ctorData.pArg4), GTF_ICON_FTN_ADDR));
+
+ if (ctorData.pArg5)
+ {
+ call->gtCall.gtCallArgs->gtOp.gtOp2->gtOp.gtOp2->gtOp.gtOp2->gtOp.gtOp2 =
+ gtNewArgList(gtNewIconHandleNode(size_t(ctorData.pArg5), GTF_ICON_FTN_ADDR));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return call;
+}
+
+bool Compiler::fgCastNeeded(GenTreePtr tree, var_types toType)
+{
+ //
+ // If tree is a relop and we need an 4-byte integer
+ // then we never need to insert a cast
+ //
+ if ((tree->OperKind() & GTK_RELOP) && (genActualType(toType) == TYP_INT))
+ {
+ return false;
+ }
+
+ var_types fromType;
+
+ //
+ // Is the tree as GT_CAST or a GT_CALL ?
+ //
+ if (tree->OperGet() == GT_CAST)
+ {
+ fromType = tree->CastToType();
+ }
+ else if (tree->OperGet() == GT_CALL)
+ {
+ fromType = (var_types)tree->gtCall.gtReturnType;
+ }
+ else
+ {
+ fromType = tree->TypeGet();
+ }
+
+ //
+ // If both types are the same then an additional cast is not necessary
+ //
+ if (toType == fromType)
+ {
+ return false;
+ }
+ //
+ // If the sign-ness of the two types are different then a cast is necessary
+ //
+ if (varTypeIsUnsigned(toType) != varTypeIsUnsigned(fromType))
+ {
+ return true;
+ }
+ //
+ // If the from type is the same size or smaller then an additional cast is not necessary
+ //
+ if (genTypeSize(toType) >= genTypeSize(fromType))
+ {
+ return false;
+ }
+
+ //
+ // Looks like we will need the cast
+ //
+ return true;
+}
+
+// If assigning to a local var, add a cast if the target is
+// marked as NormalizedOnStore. Returns true if any change was made
+GenTreePtr Compiler::fgDoNormalizeOnStore(GenTreePtr tree)
+{
+ //
+ // Only normalize the stores in the global morph phase
+ //
+ if (fgGlobalMorph)
+ {
+ noway_assert(tree->OperGet() == GT_ASG);
+
+ GenTreePtr op1 = tree->gtOp.gtOp1;
+ GenTreePtr op2 = tree->gtOp.gtOp2;
+
+ if (op1->gtOper == GT_LCL_VAR && genActualType(op1->TypeGet()) == TYP_INT)
+ {
+ // Small-typed arguments and aliased locals are normalized on load.
+ // Other small-typed locals are normalized on store.
+ // If it is an assignment to one of the latter, insert the cast on RHS
+ unsigned varNum = op1->gtLclVarCommon.gtLclNum;
+ LclVarDsc* varDsc = &lvaTable[varNum];
+
+ if (varDsc->lvNormalizeOnStore())
+ {
+ noway_assert(op1->gtType <= TYP_INT);
+ op1->gtType = TYP_INT;
+
+ if (fgCastNeeded(op2, varDsc->TypeGet()))
+ {
+ op2 = gtNewCastNode(TYP_INT, op2, varDsc->TypeGet());
+ tree->gtOp.gtOp2 = op2;
+
+ // Propagate GTF_COLON_COND
+ op2->gtFlags |= (tree->gtFlags & GTF_COLON_COND);
+ }
+ }
+ }
+ }
+
+ return tree;
+}
+
+/*****************************************************************************
+ *
+ * Mark whether the edge "srcBB -> dstBB" forms a loop that will always
+ * execute a call or not.
+ */
+
+inline void Compiler::fgLoopCallTest(BasicBlock* srcBB, BasicBlock* dstBB)
+{
+ /* Bail if this is not a backward edge */
+
+ if (srcBB->bbNum < dstBB->bbNum)
+ {
+ return;
+ }
+
+ /* Unless we already know that there is a loop without a call here ... */
+
+ if (!(dstBB->bbFlags & BBF_LOOP_CALL0))
+ {
+ /* Check whether there is a loop path that doesn't call */
+
+ if (optReachWithoutCall(dstBB, srcBB))
+ {
+ dstBB->bbFlags |= BBF_LOOP_CALL0;
+ dstBB->bbFlags &= ~BBF_LOOP_CALL1;
+ }
+ else
+ {
+ dstBB->bbFlags |= BBF_LOOP_CALL1;
+ }
+ }
+ // if this loop will always call, then we can omit the GC Poll
+ if ((GCPOLL_NONE != opts.compGCPollType) && (dstBB->bbFlags & BBF_LOOP_CALL1))
+ {
+ srcBB->bbFlags &= ~BBF_NEEDS_GCPOLL;
+ }
+}
+
+/*****************************************************************************
+ *
+ * Mark which loops are guaranteed to execute a call.
+ */
+
+void Compiler::fgLoopCallMark()
+{
+ BasicBlock* block;
+
+ /* If we've already marked all the block, bail */
+
+ if (fgLoopCallMarked)
+ {
+ return;
+ }
+
+ fgLoopCallMarked = true;
+
+ /* Walk the blocks, looking for backward edges */
+
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ switch (block->bbJumpKind)
+ {
+ case BBJ_COND:
+ case BBJ_CALLFINALLY:
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ fgLoopCallTest(block, block->bbJumpDest);
+ break;
+
+ case BBJ_SWITCH:
+
+ unsigned jumpCnt;
+ jumpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jumpPtr;
+ jumpPtr = block->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ fgLoopCallTest(block, *jumpPtr);
+ } while (++jumpPtr, --jumpCnt);
+
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/*****************************************************************************
+ *
+ * Note the fact that the given block is a loop header.
+ */
+
+inline void Compiler::fgMarkLoopHead(BasicBlock* block)
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("fgMarkLoopHead: Checking loop head block BB%02u: ", block->bbNum);
+ }
+#endif
+
+ /* Have we decided to generate fully interruptible code already? */
+
+ if (genInterruptible)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("method is already fully interruptible\n");
+ }
+#endif
+ return;
+ }
+
+ /* Is the loop head block known to execute a method call? */
+
+ if (block->bbFlags & BBF_GC_SAFE_POINT)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("this block will execute a call\n");
+ }
+#endif
+ // single block loops that contain GC safe points don't need polls.
+ block->bbFlags &= ~BBF_NEEDS_GCPOLL;
+ return;
+ }
+
+ /* Are dominator sets available? */
+
+ if (fgDomsComputed)
+ {
+ /* Make sure that we know which loops will always execute calls */
+
+ if (!fgLoopCallMarked)
+ {
+ fgLoopCallMark();
+ }
+
+ /* Will every trip through our loop execute a call? */
+
+ if (block->bbFlags & BBF_LOOP_CALL1)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("this block dominates a block that will execute a call\n");
+ }
+#endif
+ return;
+ }
+ }
+
+ /*
+ * We have to make this method fully interruptible since we can not
+ * ensure that this loop will execute a call every time it loops.
+ *
+ * We'll also need to generate a full register map for this method.
+ */
+
+ assert(!codeGen->isGCTypeFixed());
+
+ if (!compCanEncodePtrArgCntMax())
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("a callsite with more than 1023 pushed args exists\n");
+ }
+#endif
+ return;
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("no guaranteed callsite exits, marking method as fully interruptible\n");
+ }
+#endif
+
+ // only enable fully interruptible code for if we're hijacking.
+ if (GCPOLL_NONE == opts.compGCPollType)
+ {
+ genInterruptible = true;
+ }
+}
+
+GenTreePtr Compiler::fgGetCritSectOfStaticMethod()
+{
+ noway_assert(!compIsForInlining());
+
+ noway_assert(info.compIsStatic); // This method should only be called for static methods.
+
+ GenTreePtr tree = nullptr;
+
+ CORINFO_LOOKUP_KIND kind = info.compCompHnd->getLocationOfThisType(info.compMethodHnd);
+
+ if (!kind.needsRuntimeLookup)
+ {
+ void *critSect = nullptr, **pCrit = nullptr;
+ critSect = info.compCompHnd->getMethodSync(info.compMethodHnd, (void**)&pCrit);
+ noway_assert((!critSect) != (!pCrit));
+
+ tree = gtNewIconEmbHndNode(critSect, pCrit, GTF_ICON_METHOD_HDL);
+ }
+ else
+ {
+ // Collectible types requires that for shared generic code, if we use the generic context paramter
+ // that we report it. (This is a conservative approach, we could detect some cases particularly when the
+ // context parameter is this that we don't need the eager reporting logic.)
+ lvaGenericsContextUsed = true;
+
+ switch (kind.runtimeLookupKind)
+ {
+ case CORINFO_LOOKUP_THISOBJ:
+ {
+ noway_assert(!"Should never get this for static method.");
+ break;
+ }
+
+ case CORINFO_LOOKUP_CLASSPARAM:
+ {
+ // In this case, the hidden param is the class handle.
+ tree = gtNewLclvNode(info.compTypeCtxtArg, TYP_I_IMPL);
+ break;
+ }
+
+ case CORINFO_LOOKUP_METHODPARAM:
+ {
+ // In this case, the hidden param is the method handle.
+ tree = gtNewLclvNode(info.compTypeCtxtArg, TYP_I_IMPL);
+ // Call helper CORINFO_HELP_GETCLASSFROMMETHODPARAM to get the class handle
+ // from the method handle.
+ tree = gtNewHelperCallNode(CORINFO_HELP_GETCLASSFROMMETHODPARAM, TYP_I_IMPL, 0, gtNewArgList(tree));
+ break;
+ }
+
+ default:
+ {
+ noway_assert(!"Unknown LOOKUP_KIND");
+ break;
+ }
+ }
+
+ noway_assert(tree); // tree should now contain the CORINFO_CLASS_HANDLE for the exact class.
+
+ // Given the class handle, get the pointer to the Monitor.
+ tree = gtNewHelperCallNode(CORINFO_HELP_GETSYNCFROMCLASSHANDLE, TYP_I_IMPL, 0, gtNewArgList(tree));
+ }
+
+ noway_assert(tree);
+ return tree;
+}
+
+#if !defined(_TARGET_X86_)
+
+/*****************************************************************************
+ *
+ * Add monitor enter/exit calls for synchronized methods, and a try/fault
+ * to ensure the 'exit' is called if the 'enter' was successful. On x86, we
+ * generate monitor enter/exit calls and tell the VM the code location of
+ * these calls. When an exception occurs between those locations, the VM
+ * automatically releases the lock. For non-x86 platforms, the JIT is
+ * responsible for creating a try/finally to protect the monitor enter/exit,
+ * and the VM doesn't need to know anything special about the method during
+ * exception processing -- it's just a normal try/finally.
+ *
+ * We generate the following code:
+ *
+ * void Foo()
+ * {
+ * unsigned byte acquired = 0;
+ * try {
+ * JIT_MonEnterWorker(<lock object>, &acquired);
+ *
+ * *** all the preexisting user code goes here ***
+ *
+ * JIT_MonExitWorker(<lock object>, &acquired);
+ * } fault {
+ * JIT_MonExitWorker(<lock object>, &acquired);
+ * }
+ * L_return:
+ * ret
+ * }
+ *
+ * If the lock is actually acquired, then the 'acquired' variable is set to 1
+ * by the helper call. During normal exit, the finally is called, 'acquired'
+ * is 1, and the lock is released. If an exception occurs before the lock is
+ * acquired, but within the 'try' (extremely unlikely, but possible), 'acquired'
+ * will be 0, and the monitor exit call will quickly return without attempting
+ * to release the lock. Otherwise, 'acquired' will be 1, and the lock will be
+ * released during exception processing.
+ *
+ * For synchronized methods, we generate a single return block.
+ * We can do this without creating additional "step" blocks because "ret" blocks
+ * must occur at the top-level (of the original code), not nested within any EH
+ * constructs. From the CLI spec, 12.4.2.8.2.3 "ret": "Shall not be enclosed in any
+ * protected block, filter, or handler." Also, 3.57: "The ret instruction cannot be
+ * used to transfer control out of a try, filter, catch, or finally block. From within
+ * a try or catch, use the leave instruction with a destination of a ret instruction
+ * that is outside all enclosing exception blocks."
+ *
+ * In addition, we can add a "fault" at the end of a method and be guaranteed that no
+ * control falls through. From the CLI spec, section 12.4 "Control flow": "Control is not
+ * permitted to simply fall through the end of a method. All paths shall terminate with one
+ * of these instructions: ret, throw, jmp, or (tail. followed by call, calli, or callvirt)."
+ *
+ * We only need to worry about "ret" and "throw", as the CLI spec prevents any other
+ * alternatives. Section 15.4.3.3 "Implementation information" states about exiting
+ * synchronized methods: "Exiting a synchronized method using a tail. call shall be
+ * implemented as though the tail. had not been specified." Section 3.37 "jmp" states:
+ * "The jmp instruction cannot be used to transferred control out of a try, filter,
+ * catch, fault or finally block; or out of a synchronized region." And, "throw" will
+ * be handled naturally; no additional work is required.
+ */
+
+void Compiler::fgAddSyncMethodEnterExit()
+{
+ assert((info.compFlags & CORINFO_FLG_SYNCH) != 0);
+
+ // We need to do this transformation before funclets are created.
+ assert(!fgFuncletsCreated);
+
+ // Assume we don't need to update the bbPreds lists.
+ assert(!fgComputePredsDone);
+
+#if !FEATURE_EH
+ // If we don't support EH, we can't add the EH needed by synchronized methods.
+ // Of course, we could simply ignore adding the EH constructs, since we don't
+ // support exceptions being thrown in this mode, but we would still need to add
+ // the monitor enter/exit, and that doesn't seem worth it for this minor case.
+ // By the time EH is working, we can just enable the whole thing.
+ NYI("No support for synchronized methods");
+#endif // !FEATURE_EH
+
+ // Create a scratch first BB where we can put the new variable initialization.
+ // Don't put the scratch BB in the protected region.
+
+ fgEnsureFirstBBisScratch();
+
+ // Create a block for the start of the try region, where the monitor enter call
+ // will go.
+
+ assert(fgFirstBB->bbFallsThrough());
+
+ BasicBlock* tryBegBB = fgNewBBafter(BBJ_NONE, fgFirstBB, false);
+ BasicBlock* tryLastBB = fgLastBB;
+
+ // Create a block for the fault.
+
+ assert(!tryLastBB->bbFallsThrough());
+ BasicBlock* faultBB = fgNewBBafter(BBJ_EHFINALLYRET, tryLastBB, false);
+
+ assert(tryLastBB->bbNext == faultBB);
+ assert(faultBB->bbNext == nullptr);
+ assert(faultBB == fgLastBB);
+
+ { // Scope the EH region creation
+
+ // Add the new EH region at the end, since it is the least nested,
+ // and thus should be last.
+
+ EHblkDsc* newEntry;
+ unsigned XTnew = compHndBBtabCount;
+
+ newEntry = fgAddEHTableEntry(XTnew);
+
+ // Initialize the new entry
+
+ newEntry->ebdHandlerType = EH_HANDLER_FAULT;
+
+ newEntry->ebdTryBeg = tryBegBB;
+ newEntry->ebdTryLast = tryLastBB;
+
+ newEntry->ebdHndBeg = faultBB;
+ newEntry->ebdHndLast = faultBB;
+
+ newEntry->ebdTyp = 0; // unused for fault
+
+ newEntry->ebdEnclosingTryIndex = EHblkDsc::NO_ENCLOSING_INDEX;
+ newEntry->ebdEnclosingHndIndex = EHblkDsc::NO_ENCLOSING_INDEX;
+
+ newEntry->ebdTryBegOffset = tryBegBB->bbCodeOffs;
+ newEntry->ebdTryEndOffset = tryLastBB->bbCodeOffsEnd;
+ newEntry->ebdFilterBegOffset = 0;
+ newEntry->ebdHndBegOffset = 0; // handler doesn't correspond to any IL
+ newEntry->ebdHndEndOffset = 0; // handler doesn't correspond to any IL
+
+ // Set some flags on the new region. This is the same as when we set up
+ // EH regions in fgFindBasicBlocks(). Note that the try has no enclosing
+ // handler, and the fault has no enclosing try.
+
+ tryBegBB->bbFlags |= BBF_HAS_LABEL | BBF_DONT_REMOVE | BBF_TRY_BEG | BBF_IMPORTED;
+
+ faultBB->bbFlags |= BBF_HAS_LABEL | BBF_DONT_REMOVE | BBF_IMPORTED;
+ faultBB->bbCatchTyp = BBCT_FAULT;
+
+ tryBegBB->setTryIndex(XTnew);
+ tryBegBB->clearHndIndex();
+
+ faultBB->clearTryIndex();
+ faultBB->setHndIndex(XTnew);
+
+ // Walk the user code blocks and set all blocks that don't already have a try handler
+ // to point to the new try handler.
+
+ BasicBlock* tmpBB;
+ for (tmpBB = tryBegBB->bbNext; tmpBB != faultBB; tmpBB = tmpBB->bbNext)
+ {
+ if (!tmpBB->hasTryIndex())
+ {
+ tmpBB->setTryIndex(XTnew);
+ }
+ }
+
+ // Walk the EH table. Make every EH entry that doesn't already have an enclosing
+ // try index mark this new entry as their enclosing try index.
+
+ unsigned XTnum;
+ EHblkDsc* HBtab;
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < XTnew; XTnum++, HBtab++)
+ {
+ if (HBtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
+ {
+ HBtab->ebdEnclosingTryIndex =
+ (unsigned short)XTnew; // This EH region wasn't previously nested, but now it is.
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ JITDUMP("Synchronized method - created additional EH descriptor EH#%u for try/fault wrapping monitor "
+ "enter/exit\n",
+ XTnew);
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ }
+
+ fgVerifyHandlerTab();
+#endif // DEBUG
+ }
+
+ // Create a 'monitor acquired' boolean (actually, an unsigned byte: 1 = acquired, 0 = not acquired).
+
+ var_types typeMonAcquired = TYP_UBYTE;
+ this->lvaMonAcquired = lvaGrabTemp(true DEBUGARG("Synchronized method monitor acquired boolean"));
+
+ lvaTable[lvaMonAcquired].lvType = typeMonAcquired;
+
+ { // Scope the variables of the variable initialization
+
+ // Initialize the 'acquired' boolean.
+
+ GenTreePtr zero = gtNewZeroConNode(genActualType(typeMonAcquired));
+ GenTreePtr varNode = gtNewLclvNode(lvaMonAcquired, typeMonAcquired);
+ GenTreePtr initNode = gtNewAssignNode(varNode, zero);
+
+ fgInsertStmtAtEnd(fgFirstBB, initNode);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nSynchronized method - Add 'acquired' initialization in first block BB%02u [%08p]\n", fgFirstBB,
+ dspPtr(fgFirstBB));
+ gtDispTree(initNode);
+ printf("\n");
+ }
+#endif
+ }
+
+ // Make a copy of the 'this' pointer to be used in the handler so it does not inhibit enregistration
+ // of all uses of the variable.
+ unsigned lvaCopyThis = 0;
+ if (!info.compIsStatic)
+ {
+ lvaCopyThis = lvaGrabTemp(true DEBUGARG("Synchronized method monitor acquired boolean"));
+ lvaTable[lvaCopyThis].lvType = TYP_REF;
+
+ GenTreePtr thisNode = gtNewLclvNode(info.compThisArg, TYP_REF);
+ GenTreePtr copyNode = gtNewLclvNode(lvaCopyThis, TYP_REF);
+ GenTreePtr initNode = gtNewAssignNode(copyNode, thisNode);
+
+ fgInsertStmtAtEnd(tryBegBB, initNode);
+ }
+
+ fgCreateMonitorTree(lvaMonAcquired, info.compThisArg, tryBegBB, true /*enter*/);
+
+ // exceptional case
+ fgCreateMonitorTree(lvaMonAcquired, lvaCopyThis, faultBB, false /*exit*/);
+
+ // non-exceptional cases
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ if (block->bbJumpKind == BBJ_RETURN)
+ {
+ fgCreateMonitorTree(lvaMonAcquired, info.compThisArg, block, false /*exit*/);
+ }
+ }
+}
+
+// fgCreateMonitorTree: Create tree to execute a monitor enter or exit operation for synchronized methods
+// lvaMonAcquired: lvaNum of boolean variable that tracks if monitor has been acquired.
+// lvaThisVar: lvaNum of variable being used as 'this' pointer, may not be the original one. Is only used for
+// nonstatic methods
+// block: block to insert the tree in. It is inserted at the end or in the case of a return, immediately before the
+// GT_RETURN
+// enter: whether to create a monitor enter or exit
+
+GenTree* Compiler::fgCreateMonitorTree(unsigned lvaMonAcquired, unsigned lvaThisVar, BasicBlock* block, bool enter)
+{
+ // Insert the expression "enter/exitCrit(this, &acquired)" or "enter/exitCrit(handle, &acquired)"
+
+ var_types typeMonAcquired = TYP_UBYTE;
+ GenTreePtr varNode = gtNewLclvNode(lvaMonAcquired, typeMonAcquired);
+ GenTreePtr varAddrNode = gtNewOperNode(GT_ADDR, TYP_BYREF, varNode);
+ GenTreePtr tree;
+
+ if (info.compIsStatic)
+ {
+ tree = fgGetCritSectOfStaticMethod();
+ tree = gtNewHelperCallNode(enter ? CORINFO_HELP_MON_ENTER_STATIC : CORINFO_HELP_MON_EXIT_STATIC, TYP_VOID, 0,
+ gtNewArgList(tree, varAddrNode));
+ }
+ else
+ {
+ tree = gtNewLclvNode(lvaThisVar, TYP_REF);
+ tree = gtNewHelperCallNode(enter ? CORINFO_HELP_MON_ENTER : CORINFO_HELP_MON_EXIT, TYP_VOID, 0,
+ gtNewArgList(tree, varAddrNode));
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nSynchronized method - Add monitor %s call to block BB%02u [%08p]\n", enter ? "enter" : "exit", block,
+ dspPtr(block));
+ gtDispTree(tree);
+ printf("\n");
+ }
+#endif
+
+ if (block->bbJumpKind == BBJ_RETURN && block->lastStmt()->gtStmtExpr->gtOper == GT_RETURN)
+ {
+ GenTree* retNode = block->lastStmt()->gtStmtExpr;
+ GenTree* retExpr = retNode->gtOp.gtOp1;
+
+ if (retExpr != nullptr)
+ {
+ // have to insert this immediately before the GT_RETURN so we transform:
+ // ret(...) ->
+ // ret(comma(comma(tmp=...,call mon_exit), tmp)
+ //
+ //
+ // Before morph stage, it is possible to have a case of GT_RETURN(TYP_LONG, op1) where op1's type is
+ // TYP_STRUCT (of 8-bytes) and op1 is call node. See the big comment block in impReturnInstruction()
+ // for details for the case where info.compRetType is not the same as info.compRetNativeType. For
+ // this reason pass compMethodInfo->args.retTypeClass which is guaranteed to be a valid class handle
+ // if the return type is a value class. Note that fgInsertCommFormTemp() in turn uses this class handle
+ // if the type of op1 is TYP_STRUCT to perform lvaSetStruct() on the new temp that is created, which
+ // in turn passes it to VM to know the size of value type.
+ GenTree* temp = fgInsertCommaFormTemp(&retNode->gtOp.gtOp1, info.compMethodInfo->args.retTypeClass);
+
+ GenTree* lclVar = retNode->gtOp.gtOp1->gtOp.gtOp2;
+ retNode->gtOp.gtOp1->gtOp.gtOp2 = gtNewOperNode(GT_COMMA, retExpr->TypeGet(), tree, lclVar);
+ }
+ else
+ {
+ // Insert this immediately before the GT_RETURN
+ fgInsertStmtNearEnd(block, tree);
+ }
+ }
+ else
+ {
+ fgInsertStmtAtEnd(block, tree);
+ }
+
+ return tree;
+}
+
+// Convert a BBJ_RETURN block in a synchronized method to a BBJ_ALWAYS.
+// We've previously added a 'try' block around the original program code using fgAddSyncMethodEnterExit().
+// Thus, we put BBJ_RETURN blocks inside a 'try'. In IL this is illegal. Instead, we would
+// see a 'leave' inside a 'try' that would get transformed into BBJ_CALLFINALLY/BBJ_ALWAYS blocks
+// during importing, and the BBJ_ALWAYS would point at an outer block with the BBJ_RETURN.
+// Here, we mimic some of the logic of importing a LEAVE to get the same effect for synchronized methods.
+void Compiler::fgConvertSyncReturnToLeave(BasicBlock* block)
+{
+ assert(!fgFuncletsCreated);
+ assert(info.compFlags & CORINFO_FLG_SYNCH);
+ assert(genReturnBB != nullptr);
+ assert(genReturnBB != block);
+ assert(fgReturnCount <= 1); // We have a single return for synchronized methods
+ assert(block->bbJumpKind == BBJ_RETURN);
+ assert((block->bbFlags & BBF_HAS_JMP) == 0);
+ assert(block->hasTryIndex());
+ assert(!block->hasHndIndex());
+ assert(compHndBBtabCount >= 1);
+
+ unsigned tryIndex = block->getTryIndex();
+ assert(tryIndex == compHndBBtabCount - 1); // The BBJ_RETURN must be at the top-level before we inserted the
+ // try/finally, which must be the last EH region.
+
+ EHblkDsc* ehDsc = ehGetDsc(tryIndex);
+ assert(ehDsc->ebdEnclosingTryIndex ==
+ EHblkDsc::NO_ENCLOSING_INDEX); // There are no enclosing regions of the BBJ_RETURN block
+ assert(ehDsc->ebdEnclosingHndIndex == EHblkDsc::NO_ENCLOSING_INDEX);
+
+ // Convert the BBJ_RETURN to BBJ_ALWAYS, jumping to genReturnBB.
+ block->bbJumpKind = BBJ_ALWAYS;
+ block->bbJumpDest = genReturnBB;
+ block->bbJumpDest->bbRefs++;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Synchronized method - convert block BB%02u to BBJ_ALWAYS [targets BB%02u]\n", block->bbNum,
+ block->bbJumpDest->bbNum);
+ }
+#endif
+}
+
+#endif // !_TARGET_X86_
+
+//------------------------------------------------------------------------
+// fgAddReversePInvokeEnterExit: Add enter/exit calls for reverse PInvoke methods
+//
+// Arguments:
+// None.
+//
+// Return Value:
+// None.
+
+void Compiler::fgAddReversePInvokeEnterExit()
+{
+ assert(opts.IsReversePInvoke());
+
+#if COR_JIT_EE_VERSION > 460
+ lvaReversePInvokeFrameVar = lvaGrabTempWithImplicitUse(false DEBUGARG("Reverse Pinvoke FrameVar"));
+
+ LclVarDsc* varDsc = &lvaTable[lvaReversePInvokeFrameVar];
+ varDsc->lvType = TYP_BLK;
+ varDsc->lvExactSize = eeGetEEInfo()->sizeOfReversePInvokeFrame;
+
+ GenTreePtr tree;
+
+ // Add enter pinvoke exit callout at the start of prolog
+
+ tree = gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(lvaReversePInvokeFrameVar, TYP_BLK));
+
+ tree = gtNewHelperCallNode(CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, TYP_VOID, 0, gtNewArgList(tree));
+
+ fgEnsureFirstBBisScratch();
+
+ fgInsertStmtAtBeg(fgFirstBB, tree);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nReverse PInvoke method - Add reverse pinvoke enter in first basic block [%08p]\n", dspPtr(fgFirstBB));
+ gtDispTree(tree);
+ printf("\n");
+ }
+#endif
+
+ // Add reverse pinvoke exit callout at the end of epilog
+
+ tree = gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(lvaReversePInvokeFrameVar, TYP_BLK));
+
+ tree = gtNewHelperCallNode(CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, TYP_VOID, 0, gtNewArgList(tree));
+
+ assert(genReturnBB != nullptr);
+
+ fgInsertStmtAtEnd(genReturnBB, tree);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nReverse PInvoke method - Add reverse pinvoke exit in return basic block [%08p]\n",
+ dspPtr(genReturnBB));
+ gtDispTree(tree);
+ printf("\n");
+ }
+#endif
+
+#endif // COR_JIT_EE_VERSION > 460
+}
+
+/*****************************************************************************
+ *
+ * Return 'true' if there is more than one BBJ_RETURN block.
+ */
+
+bool Compiler::fgMoreThanOneReturnBlock()
+{
+ unsigned retCnt = 0;
+
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ if (block->bbJumpKind == BBJ_RETURN)
+ {
+ retCnt++;
+ if (retCnt > 1)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*****************************************************************************
+ *
+ * Add any internal blocks/trees we may need
+ */
+
+void Compiler::fgAddInternal()
+{
+ noway_assert(!compIsForInlining());
+
+ /*
+ <BUGNUM> VSW441487 </BUGNUM>
+
+ The "this" pointer is implicitly used in the following cases:
+ 1. Locking of synchronized methods
+ 2. Dictionary access of shared generics code
+ 3. If a method has "catch(FooException<T>)", the EH code accesses "this" to determine T.
+ 4. Initializing the type from generic methods which require precise cctor semantics
+ 5. Verifier does special handling of "this" in the .ctor
+
+ However, we might overwrite it with a "starg 0".
+ In this case, we will redirect all "ldarg(a)/starg(a) 0" to a temp lvaTable[lvaArg0Var]
+ */
+
+ if (!info.compIsStatic)
+ {
+ if (lvaArg0Var != info.compThisArg)
+ {
+ // When we're using the general encoder, we mark compThisArg address-taken to ensure that it is not
+ // enregistered (since the decoder always reports a stack location for "this" for generics
+ // context vars).
+ bool lva0CopiedForGenericsCtxt;
+#ifndef JIT32_GCENCODER
+ lva0CopiedForGenericsCtxt = ((info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_THIS) != 0);
+#else // JIT32_GCENCODER
+ lva0CopiedForGenericsCtxt = false;
+#endif // JIT32_GCENCODER
+ noway_assert(lva0CopiedForGenericsCtxt || !lvaTable[info.compThisArg].lvAddrExposed);
+ noway_assert(!lvaTable[info.compThisArg].lvArgWrite);
+ noway_assert(lvaTable[lvaArg0Var].lvAddrExposed || lvaTable[lvaArg0Var].lvArgWrite ||
+ lva0CopiedForGenericsCtxt);
+
+ var_types thisType = lvaTable[info.compThisArg].TypeGet();
+
+ // Now assign the original input "this" to the temp
+
+ GenTreePtr tree;
+
+ tree = gtNewLclvNode(lvaArg0Var, thisType);
+
+ tree = gtNewAssignNode(tree, // dst
+ gtNewLclvNode(info.compThisArg, thisType) // src
+ );
+
+ /* Create a new basic block and stick the assignment in it */
+
+ fgEnsureFirstBBisScratch();
+
+ fgInsertStmtAtEnd(fgFirstBB, tree);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nCopy \"this\" to lvaArg0Var in first basic block [%08p]\n", dspPtr(fgFirstBB));
+ gtDispTree(tree);
+ printf("\n");
+ }
+#endif
+ }
+ }
+
+ // Grab a temp for the security object.
+ // (Note: opts.compDbgEnC currently also causes the security object to be generated. See Compiler::compCompile)
+ if (opts.compNeedSecurityCheck)
+ {
+ noway_assert(lvaSecurityObject == BAD_VAR_NUM);
+ lvaSecurityObject = lvaGrabTempWithImplicitUse(false DEBUGARG("security check"));
+ lvaTable[lvaSecurityObject].lvType = TYP_REF;
+ }
+
+ /* Assume we will generate a single shared return sequence */
+
+ ULONG returnWeight = 0;
+ bool oneReturn;
+ bool allProfWeight;
+
+ //
+ // We will generate just one epilog (return block)
+ // when we are asked to generate enter/leave callbacks
+ // or for methods with PInvoke
+ // or for methods calling into unmanaged code
+ // or for synchronized methods.
+ //
+ if (compIsProfilerHookNeeded() || (info.compCallUnmanaged != 0) || opts.IsReversePInvoke() ||
+ ((info.compFlags & CORINFO_FLG_SYNCH) != 0))
+ {
+ // We will generate only one return block
+ // We will transform the BBJ_RETURN blocks
+ // into jumps to the one return block
+ //
+ oneReturn = true;
+ allProfWeight = false;
+ }
+ else
+ {
+ //
+ // We are allowed to have multiple individual exits
+ // However we can still decide to have a single return
+ //
+ oneReturn = false;
+ allProfWeight = true;
+
+ // Count the BBJ_RETURN blocks and set the returnWeight to the
+ // sum of all these blocks.
+ //
+ fgReturnCount = 0;
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ if (block->bbJumpKind == BBJ_RETURN)
+ {
+ //
+ // returnCount is the count of BBJ_RETURN blocks in this method
+ //
+ fgReturnCount++;
+ //
+ // If all BBJ_RETURN blocks have a valid profiled weights
+ // then allProfWeight will be true, else it is false
+ //
+ if ((block->bbFlags & BBF_PROF_WEIGHT) == 0)
+ {
+ allProfWeight = false;
+ }
+ //
+ // returnWeight is the sum of the weights of all BBJ_RETURN blocks
+ returnWeight += block->bbWeight;
+ }
+ }
+
+ //
+ // If we only have one (or zero) return blocks then
+ // we do not need a special one return block
+ //
+ if (fgReturnCount > 1)
+ {
+ //
+ // should we generate a single return block?
+ //
+ if (fgReturnCount > 4)
+ {
+ // Our epilog encoding only supports up to 4 epilogs
+ // TODO-CQ: support >4 return points for ARM/AMD64, which presumably support any number of epilogs?
+ //
+ oneReturn = true;
+ }
+ else if (compCodeOpt() == SMALL_CODE)
+ {
+ // For the Small_Code case we always generate a
+ // single return block when we have multiple
+ // return points
+ //
+ oneReturn = true;
+ }
+ }
+ }
+
+#if !defined(_TARGET_X86_)
+ // Add the synchronized method enter/exit calls and try/finally protection. Note
+ // that this must happen before the one BBJ_RETURN block is created below, so the
+ // BBJ_RETURN block gets placed at the top-level, not within an EH region. (Otherwise,
+ // we'd have to be really careful when creating the synchronized method try/finally
+ // not to include the BBJ_RETURN block.)
+ if ((info.compFlags & CORINFO_FLG_SYNCH) != 0)
+ {
+ fgAddSyncMethodEnterExit();
+ }
+#endif // !_TARGET_X86_
+
+ if (oneReturn)
+ {
+ genReturnBB = fgNewBBinRegion(BBJ_RETURN);
+ genReturnBB->bbRefs = 1; // bbRefs gets update later, for now it should be 1
+ fgReturnCount++;
+
+ if (allProfWeight)
+ {
+ //
+ // if we have profile data for all BBJ_RETURN blocks
+ // then we can set BBF_PROF_WEIGHT for genReturnBB
+ //
+ genReturnBB->bbFlags |= BBF_PROF_WEIGHT;
+ }
+ else
+ {
+ //
+ // We can't rely upon the calculated returnWeight unless
+ // all of the BBJ_RETURN blocks had valid profile weights
+ // So we will use the weight of the first block instead
+ //
+ returnWeight = fgFirstBB->bbWeight;
+ }
+
+ //
+ // Set the weight of the oneReturn block
+ //
+ genReturnBB->bbWeight = min(returnWeight, BB_MAX_WEIGHT);
+
+ if (returnWeight == 0)
+ {
+ //
+ // If necessary set the Run Rarely flag
+ //
+ genReturnBB->bbFlags |= BBF_RUN_RARELY;
+ }
+ else
+ {
+ // Make sure that the RunRarely flag is clear
+ // because fgNewBBinRegion will set it to true
+ //
+ genReturnBB->bbFlags &= ~BBF_RUN_RARELY;
+ }
+
+ genReturnBB->bbFlags |= (BBF_INTERNAL | BBF_DONT_REMOVE);
+
+ noway_assert(genReturnBB->bbNext == nullptr);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n genReturnBB [BB%02u] created\n", genReturnBB->bbNum);
+ }
+#endif
+ }
+ else
+ {
+ //
+ // We don't have a oneReturn block for this method
+ //
+ genReturnBB = nullptr;
+ }
+
+ // If there is a return value, then create a temp for it. Real returns will store the value in there and
+ // it'll be reloaded by the single return.
+ if (genReturnBB && compMethodHasRetVal())
+ {
+ genReturnLocal = lvaGrabTemp(true DEBUGARG("Single return block return value"));
+
+ if (compMethodReturnsNativeScalarType())
+ {
+ lvaTable[genReturnLocal].lvType = genActualType(info.compRetNativeType);
+ }
+ else if (compMethodReturnsRetBufAddr())
+ {
+ lvaTable[genReturnLocal].lvType = TYP_BYREF;
+ }
+ else if (compMethodReturnsMultiRegRetType())
+ {
+ lvaTable[genReturnLocal].lvType = TYP_STRUCT;
+ lvaSetStruct(genReturnLocal, info.compMethodInfo->args.retTypeClass, true);
+ lvaTable[genReturnLocal].lvIsMultiRegRet = true;
+ }
+ else
+ {
+ assert(!"unreached");
+ }
+
+ if (varTypeIsFloating(lvaTable[genReturnLocal].lvType))
+ {
+ this->compFloatingPointUsed = true;
+ }
+
+ if (!varTypeIsFloating(info.compRetType))
+ {
+ lvaTable[genReturnLocal].setPrefReg(REG_INTRET, this);
+#ifdef REG_FLOATRET
+ }
+ else
+ {
+ lvaTable[genReturnLocal].setPrefReg(REG_FLOATRET, this);
+ }
+#endif
+
+#ifdef DEBUG
+ // This temporary should not be converted to a double in stress mode,
+ // because we introduce assigns to it after the stress conversion
+ lvaTable[genReturnLocal].lvKeepType = 1;
+#endif
+ }
+ else
+ {
+ genReturnLocal = BAD_VAR_NUM;
+ }
+
+ if (info.compCallUnmanaged != 0)
+ {
+ // The P/Invoke helpers only require a frame variable, so only allocate the
+ // TCB variable if we're not using them.
+ if (!opts.ShouldUsePInvokeHelpers())
+ {
+ info.compLvFrameListRoot = lvaGrabTemp(false DEBUGARG("Pinvoke FrameListRoot"));
+ }
+
+ lvaInlinedPInvokeFrameVar = lvaGrabTempWithImplicitUse(false DEBUGARG("Pinvoke FrameVar"));
+
+ LclVarDsc* varDsc = &lvaTable[lvaInlinedPInvokeFrameVar];
+ varDsc->addPrefReg(RBM_PINVOKE_TCB, this);
+ varDsc->lvType = TYP_BLK;
+ // Make room for the inlined frame.
+ varDsc->lvExactSize = eeGetEEInfo()->inlinedCallFrameInfo.size;
+#if FEATURE_FIXED_OUT_ARGS
+ // Grab and reserve space for TCB, Frame regs used in PInvoke epilog to pop the inlined frame.
+ // See genPInvokeMethodEpilog() for use of the grabbed var. This is only necessary if we are
+ // not using the P/Invoke helpers.
+ if (!opts.ShouldUsePInvokeHelpers() && compJmpOpUsed)
+ {
+ lvaPInvokeFrameRegSaveVar = lvaGrabTempWithImplicitUse(false DEBUGARG("PInvokeFrameRegSave Var"));
+ varDsc = &lvaTable[lvaPInvokeFrameRegSaveVar];
+ varDsc->lvType = TYP_BLK;
+ varDsc->lvExactSize = 2 * REGSIZE_BYTES;
+ }
+#endif
+ }
+
+ // Do we need to insert a "JustMyCode" callback?
+
+ CORINFO_JUST_MY_CODE_HANDLE* pDbgHandle = nullptr;
+ CORINFO_JUST_MY_CODE_HANDLE dbgHandle = nullptr;
+ if (opts.compDbgCode && !(opts.eeFlags & CORJIT_FLG_IL_STUB))
+ {
+ dbgHandle = info.compCompHnd->getJustMyCodeHandle(info.compMethodHnd, &pDbgHandle);
+ }
+
+#ifdef _TARGET_ARM64_
+ // TODO-ARM64-NYI: don't do just-my-code
+ dbgHandle = nullptr;
+ pDbgHandle = nullptr;
+#endif // _TARGET_ARM64_
+
+ noway_assert(!dbgHandle || !pDbgHandle);
+
+ if (dbgHandle || pDbgHandle)
+ {
+ GenTreePtr guardCheckVal =
+ gtNewOperNode(GT_IND, TYP_INT, gtNewIconEmbHndNode(dbgHandle, pDbgHandle, GTF_ICON_TOKEN_HDL));
+ GenTreePtr guardCheckCond = gtNewOperNode(GT_EQ, TYP_INT, guardCheckVal, gtNewZeroConNode(TYP_INT));
+ guardCheckCond->gtFlags |= GTF_RELOP_QMARK;
+
+ // Create the callback which will yield the final answer
+
+ GenTreePtr callback = gtNewHelperCallNode(CORINFO_HELP_DBG_IS_JUST_MY_CODE, TYP_VOID);
+ callback = new (this, GT_COLON) GenTreeColon(TYP_VOID, gtNewNothingNode(), callback);
+
+ // Stick the conditional call at the start of the method
+
+ fgEnsureFirstBBisScratch();
+ fgInsertStmtAtEnd(fgFirstBB, gtNewQmarkNode(TYP_VOID, guardCheckCond, callback));
+ }
+
+ /* Do we need to call out for security ? */
+
+ if (tiSecurityCalloutNeeded)
+ {
+ // We must have grabbed this local.
+ noway_assert(opts.compNeedSecurityCheck);
+ noway_assert(lvaSecurityObject != BAD_VAR_NUM);
+
+ GenTreePtr tree;
+
+ /* Insert the expression "call JIT_Security_Prolog(MethodHnd, &SecurityObject)" */
+
+ tree = gtNewIconEmbMethHndNode(info.compMethodHnd);
+
+ tree = gtNewHelperCallNode(info.compCompHnd->getSecurityPrologHelper(info.compMethodHnd), TYP_VOID, 0,
+ gtNewArgList(tree, gtNewOperNode(GT_ADDR, TYP_BYREF,
+ gtNewLclvNode(lvaSecurityObject, TYP_REF))));
+
+ /* Create a new basic block and stick the call in it */
+
+ fgEnsureFirstBBisScratch();
+
+ fgInsertStmtAtEnd(fgFirstBB, tree);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\ntiSecurityCalloutNeeded - Add call JIT_Security_Prolog(%08p) statement ",
+ dspPtr(info.compMethodHnd));
+ printTreeID(tree);
+ printf(" in first basic block [%08p]\n", dspPtr(fgFirstBB));
+ gtDispTree(tree);
+ printf("\n");
+ }
+#endif
+ }
+
+#if defined(_TARGET_X86_)
+
+ /* Is this a 'synchronized' method? */
+
+ if (info.compFlags & CORINFO_FLG_SYNCH)
+ {
+ GenTreePtr tree = NULL;
+
+ /* Insert the expression "enterCrit(this)" or "enterCrit(handle)" */
+
+ if (info.compIsStatic)
+ {
+ tree = fgGetCritSectOfStaticMethod();
+
+ tree = gtNewHelperCallNode(CORINFO_HELP_MON_ENTER_STATIC, TYP_VOID, 0, gtNewArgList(tree));
+ }
+ else
+ {
+ noway_assert(lvaTable[info.compThisArg].lvType == TYP_REF);
+
+ tree = gtNewLclvNode(info.compThisArg, TYP_REF);
+
+ tree = gtNewHelperCallNode(CORINFO_HELP_MON_ENTER, TYP_VOID, 0, gtNewArgList(tree));
+ }
+
+ /* Create a new basic block and stick the call in it */
+
+ fgEnsureFirstBBisScratch();
+
+ fgInsertStmtAtEnd(fgFirstBB, tree);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nSynchronized method - Add enterCrit statement in first basic block [%08p]\n", dspPtr(fgFirstBB));
+ gtDispTree(tree);
+ printf("\n");
+ }
+#endif
+
+ /* We must be generating a single exit point for this to work */
+
+ noway_assert(oneReturn);
+ noway_assert(genReturnBB);
+
+ /* Create the expression "exitCrit(this)" or "exitCrit(handle)" */
+
+ if (info.compIsStatic)
+ {
+ tree = fgGetCritSectOfStaticMethod();
+
+ tree = gtNewHelperCallNode(CORINFO_HELP_MON_EXIT_STATIC, TYP_VOID, 0, gtNewArgList(tree));
+ }
+ else
+ {
+ tree = gtNewLclvNode(info.compThisArg, TYP_REF);
+
+ tree = gtNewHelperCallNode(CORINFO_HELP_MON_EXIT, TYP_VOID, 0, gtNewArgList(tree));
+ }
+
+ fgInsertStmtAtEnd(genReturnBB, tree);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nSynchronized method - Add exit expression ");
+ printTreeID(tree);
+ printf("\n");
+ }
+#endif
+
+ // Reset cookies used to track start and end of the protected region in synchronized methods
+ syncStartEmitCookie = NULL;
+ syncEndEmitCookie = NULL;
+ }
+
+#endif // _TARGET_X86_
+
+ /* Do we need to do runtime call out to check the security? */
+
+ if (tiRuntimeCalloutNeeded)
+ {
+ GenTreePtr tree;
+
+ /* Insert the expression "call verificationRuntimeCheck(MethodHnd)" */
+
+ tree = gtNewIconEmbMethHndNode(info.compMethodHnd);
+
+ tree = gtNewHelperCallNode(CORINFO_HELP_VERIFICATION_RUNTIME_CHECK, TYP_VOID, 0, gtNewArgList(tree));
+
+ /* Create a new basic block and stick the call in it */
+
+ fgEnsureFirstBBisScratch();
+
+ fgInsertStmtAtEnd(fgFirstBB, tree);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\ntiRuntimeCalloutNeeded - Call verificationRuntimeCheck(%08p) statement in first basic block "
+ "[%08p]\n",
+ dspPtr(info.compMethodHnd), dspPtr(fgFirstBB));
+ gtDispTree(tree);
+ printf("\n");
+ }
+#endif
+ }
+
+ if (opts.IsReversePInvoke())
+ {
+ fgAddReversePInvokeEnterExit();
+ }
+
+ //
+ // Add 'return' expression to the return block if we made it as "oneReturn" before.
+ //
+ if (oneReturn)
+ {
+ GenTreePtr tree;
+
+ //
+ // Make the 'return' expression.
+ //
+
+ // make sure to reload the return value as part of the return (it is saved by the "real return").
+ if (genReturnLocal != BAD_VAR_NUM)
+ {
+ noway_assert(compMethodHasRetVal());
+
+ GenTreePtr retTemp = gtNewLclvNode(genReturnLocal, lvaTable[genReturnLocal].TypeGet());
+
+ // make sure copy prop ignores this node (make sure it always does a reload from the temp).
+ retTemp->gtFlags |= GTF_DONT_CSE;
+ tree = gtNewOperNode(GT_RETURN, retTemp->gtType, retTemp);
+ }
+ else
+ {
+ noway_assert(info.compRetType == TYP_VOID || varTypeIsStruct(info.compRetType));
+ // return void
+ tree = new (this, GT_RETURN) GenTreeOp(GT_RETURN, TYP_VOID);
+ }
+
+ /* Add 'return' expression to the return block */
+
+ noway_assert(genReturnBB);
+
+ fgInsertStmtAtEnd(genReturnBB, tree);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\noneReturn statement tree ");
+ printTreeID(tree);
+ printf(" added to genReturnBB [%08p]\n", dspPtr(genReturnBB));
+ gtDispTree(tree);
+ printf("\n");
+ }
+#endif
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** After fgAddInternal()\n");
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ }
+#endif
+}
+
+/*****************************************************************************
+ *
+ * Create a new statement from tree and wire the links up.
+ */
+GenTreeStmt* Compiler::fgNewStmtFromTree(GenTreePtr tree, BasicBlock* block, IL_OFFSETX offs)
+{
+ GenTreeStmt* stmt = gtNewStmt(tree, offs);
+ gtSetStmtInfo(stmt);
+ fgSetStmtSeq(stmt);
+
+#if DEBUG
+ if (block != nullptr)
+ {
+ fgDebugCheckNodeLinks(block, stmt);
+ }
+#endif
+
+ return stmt;
+}
+
+GenTreeStmt* Compiler::fgNewStmtFromTree(GenTreePtr tree)
+{
+ return fgNewStmtFromTree(tree, nullptr, BAD_IL_OFFSET);
+}
+
+GenTreeStmt* Compiler::fgNewStmtFromTree(GenTreePtr tree, BasicBlock* block)
+{
+ return fgNewStmtFromTree(tree, block, BAD_IL_OFFSET);
+}
+
+GenTreeStmt* Compiler::fgNewStmtFromTree(GenTreePtr tree, IL_OFFSETX offs)
+{
+ return fgNewStmtFromTree(tree, nullptr, offs);
+}
+
+//------------------------------------------------------------------------
+// fgFindBlockILOffset: Given a block, find the IL offset corresponding to the first statement
+// in the block with a legal IL offset. Skip any leading statements that have BAD_IL_OFFSET.
+// If no statement has an initialized statement offset (including the case where there are
+// no statements in the block), then return BAD_IL_OFFSET. This function is used when
+// blocks are split or modified, and we want to maintain the IL offset as much as possible
+// to preserve good debugging behavior.
+//
+// Arguments:
+// block - The block to check.
+//
+// Return Value:
+// The first good IL offset of a statement in the block, or BAD_IL_OFFSET if such an IL offset
+// cannot be found.
+//
+// If we are not built with DEBUGGING_SUPPORT or DEBUG, then always report BAD_IL_OFFSET,
+// since in that case statements don't contain an IL offset. The effect will be that split
+// blocks will lose their IL offset information.
+
+IL_OFFSET Compiler::fgFindBlockILOffset(BasicBlock* block)
+{
+ // This function searches for IL offsets in statement nodes, so it can't be used in LIR. We
+ // could have a similar function for LIR that searches for GT_IL_OFFSET nodes.
+ assert(!block->IsLIR());
+
+#if defined(DEBUGGING_SUPPORT) || defined(DEBUG)
+ for (GenTree* stmt = block->bbTreeList; stmt != nullptr; stmt = stmt->gtNext)
+ {
+ assert(stmt->IsStatement());
+ if (stmt->gtStmt.gtStmtILoffsx != BAD_IL_OFFSET)
+ {
+ return jitGetILoffs(stmt->gtStmt.gtStmtILoffsx);
+ }
+ }
+#endif // defined(DEBUGGING_SUPPORT) || defined(DEBUG)
+
+ return BAD_IL_OFFSET;
+}
+
+//------------------------------------------------------------------------------
+// fgSplitBlockAtEnd - split the given block into two blocks.
+// All code in the block stays in the original block.
+// Control falls through from original to new block, and
+// the new block is returned.
+//------------------------------------------------------------------------------
+BasicBlock* Compiler::fgSplitBlockAtEnd(BasicBlock* curr)
+{
+ // We'd like to use fgNewBBafter(), but we need to update the preds list before linking in the new block.
+ // (We need the successors of 'curr' to be correct when we do this.)
+ BasicBlock* newBlock = bbNewBasicBlock(curr->bbJumpKind);
+
+ // Start the new block with no refs. When we set the preds below, this will get updated correctly.
+ newBlock->bbRefs = 0;
+
+ // For each successor of the original block, set the new block as their predecessor.
+ // Note we are using the "rational" version of the successor iterator that does not hide the finallyret arcs.
+ // Without these arcs, a block 'b' may not be a member of succs(preds(b))
+ if (curr->bbJumpKind != BBJ_SWITCH)
+ {
+ unsigned numSuccs = curr->NumSucc(this);
+ for (unsigned i = 0; i < numSuccs; i++)
+ {
+ BasicBlock* succ = curr->GetSucc(i, this);
+ if (succ != newBlock)
+ {
+ JITDUMP("BB%02u previous predecessor was BB%02u, now is BB%02u\n", succ->bbNum, curr->bbNum,
+ newBlock->bbNum);
+ fgReplacePred(succ, curr, newBlock);
+ }
+ }
+
+ newBlock->bbJumpDest = curr->bbJumpDest;
+ curr->bbJumpDest = nullptr;
+ }
+ else
+ {
+ // In the case of a switch statement there's more complicated logic in order to wire up the predecessor lists
+ // but fortunately there's an existing method that implements this functionality.
+ newBlock->bbJumpSwt = curr->bbJumpSwt;
+
+ fgChangeSwitchBlock(curr, newBlock);
+
+ curr->bbJumpSwt = nullptr;
+ }
+
+ newBlock->inheritWeight(curr);
+
+ // Set the new block's flags. Note that the new block isn't BBF_INTERNAL unless the old block is.
+ newBlock->bbFlags = curr->bbFlags;
+
+ // Remove flags that the new block can't have.
+ newBlock->bbFlags &= ~(BBF_TRY_BEG | BBF_LOOP_HEAD | BBF_LOOP_CALL0 | BBF_LOOP_CALL1 | BBF_HAS_LABEL |
+ BBF_JMP_TARGET | BBF_FUNCLET_BEG | BBF_LOOP_PREHEADER | BBF_KEEP_BBJ_ALWAYS);
+
+ // Remove the GC safe bit on the new block. It seems clear that if we split 'curr' at the end,
+ // such that all the code is left in 'curr', and 'newBlock' just gets the control flow, then
+ // both 'curr' and 'newBlock' could accurately retain an existing GC safe bit. However, callers
+ // use this function to split blocks in the middle, or at the beginning, and they don't seem to
+ // be careful about updating this flag appropriately. So, removing the GC safe bit is simply
+ // conservative: some functions might end up being fully interruptible that could be partially
+ // interruptible if we exercised more care here.
+ newBlock->bbFlags &= ~BBF_GC_SAFE_POINT;
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ newBlock->bbFlags &= ~(BBF_FINALLY_TARGET);
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+ // The new block has no code, so we leave bbCodeOffs/bbCodeOffsEnd set to BAD_IL_OFFSET. If a caller
+ // puts code in the block, then it needs to update these.
+
+ // Insert the new block in the block list after the 'curr' block.
+ fgInsertBBafter(curr, newBlock);
+ fgExtendEHRegionAfter(curr); // The new block is in the same EH region as the old block.
+
+ // Remove flags from the old block that are no longer possible.
+ curr->bbFlags &= ~(BBF_HAS_JMP | BBF_RETLESS_CALL);
+
+ // Default to fallthru, and add the arc for that.
+ curr->bbJumpKind = BBJ_NONE;
+ fgAddRefPred(newBlock, curr);
+
+ return newBlock;
+}
+
+//------------------------------------------------------------------------------
+// fgSplitBlockAfterStatement - Split the given block, with all code after
+// the given statement going into the second block.
+//------------------------------------------------------------------------------
+BasicBlock* Compiler::fgSplitBlockAfterStatement(BasicBlock* curr, GenTree* stmt)
+{
+ assert(!curr->IsLIR()); // No statements in LIR, so you can't use this function.
+
+ BasicBlock* newBlock = fgSplitBlockAtEnd(curr);
+
+ if (stmt)
+ {
+ newBlock->bbTreeList = stmt->gtNext;
+ if (newBlock->bbTreeList)
+ {
+ newBlock->bbTreeList->gtPrev = curr->bbTreeList->gtPrev;
+ }
+ curr->bbTreeList->gtPrev = stmt;
+ stmt->gtNext = nullptr;
+
+ // Update the IL offsets of the blocks to match the split.
+
+ assert(newBlock->bbCodeOffs == BAD_IL_OFFSET);
+ assert(newBlock->bbCodeOffsEnd == BAD_IL_OFFSET);
+
+ // curr->bbCodeOffs remains the same
+ newBlock->bbCodeOffsEnd = curr->bbCodeOffsEnd;
+
+ IL_OFFSET splitPointILOffset = fgFindBlockILOffset(newBlock);
+
+ curr->bbCodeOffsEnd = splitPointILOffset;
+ newBlock->bbCodeOffs = splitPointILOffset;
+ }
+ else
+ {
+ assert(curr->bbTreeList == nullptr); // if no tree was given then it better be an empty block
+ }
+
+ return newBlock;
+}
+
+//------------------------------------------------------------------------------
+// fgSplitBlockAfterNode - Split the given block, with all code after
+// the given node going into the second block.
+// This function is only used in LIR.
+//------------------------------------------------------------------------------
+BasicBlock* Compiler::fgSplitBlockAfterNode(BasicBlock* curr, GenTree* node)
+{
+ assert(curr->IsLIR());
+
+ BasicBlock* newBlock = fgSplitBlockAtEnd(curr);
+
+ if (node != nullptr)
+ {
+ LIR::Range& currBBRange = LIR::AsRange(curr);
+
+ if (node != currBBRange.LastNode())
+ {
+ LIR::Range nodesToMove = currBBRange.Remove(node->gtNext, currBBRange.LastNode());
+ LIR::AsRange(newBlock).InsertAtBeginning(std::move(nodesToMove));
+ }
+
+ // Update the IL offsets of the blocks to match the split.
+
+ assert(newBlock->bbCodeOffs == BAD_IL_OFFSET);
+ assert(newBlock->bbCodeOffsEnd == BAD_IL_OFFSET);
+
+ // curr->bbCodeOffs remains the same
+ newBlock->bbCodeOffsEnd = curr->bbCodeOffsEnd;
+
+ // Search backwards from the end of the current block looking for the IL offset to use
+ // for the end IL offset for the original block.
+ IL_OFFSET splitPointILOffset = BAD_IL_OFFSET;
+ LIR::Range::ReverseIterator riter;
+ LIR::Range::ReverseIterator riterEnd;
+ for (riter = currBBRange.rbegin(), riterEnd = currBBRange.rend(); riter != riterEnd; ++riter)
+ {
+ if ((*riter)->gtOper == GT_IL_OFFSET)
+ {
+ GenTreeStmt* stmt = (*riter)->AsStmt();
+ if (stmt->gtStmtILoffsx != BAD_IL_OFFSET)
+ {
+ splitPointILOffset = jitGetILoffs(stmt->gtStmtILoffsx);
+ break;
+ }
+ }
+ }
+
+ curr->bbCodeOffsEnd = splitPointILOffset;
+
+ // Also use this as the beginning offset of the next block. Presumably we could/should
+ // look to see if the first node is a GT_IL_OFFSET node, and use that instead.
+ newBlock->bbCodeOffs = splitPointILOffset;
+ }
+ else
+ {
+ assert(curr->bbTreeList == nullptr); // if no node was given then it better be an empty block
+ }
+
+ return newBlock;
+}
+
+//------------------------------------------------------------------------------
+// fgSplitBlockAtBeginning - Split the given block into two blocks.
+// Control falls through from original to new block,
+// and the new block is returned.
+// All code in the original block goes into the new block
+//------------------------------------------------------------------------------
+BasicBlock* Compiler::fgSplitBlockAtBeginning(BasicBlock* curr)
+{
+ BasicBlock* newBlock = fgSplitBlockAtEnd(curr);
+
+ newBlock->bbTreeList = curr->bbTreeList;
+ curr->bbTreeList = nullptr;
+
+ // The new block now has all the code, and the old block has none. Update the
+ // IL offsets for the block to reflect this.
+
+ newBlock->bbCodeOffs = curr->bbCodeOffs;
+ newBlock->bbCodeOffsEnd = curr->bbCodeOffsEnd;
+
+ curr->bbCodeOffs = BAD_IL_OFFSET;
+ curr->bbCodeOffsEnd = BAD_IL_OFFSET;
+
+ return newBlock;
+}
+
+//------------------------------------------------------------------------
+// fgSplitEdge: Splits the edge between a block 'curr' and its successor 'succ' by creating a new block
+// that replaces 'succ' as a successor of 'curr', and which branches unconditionally
+// to (or falls through to) 'succ'. Note that for a BBJ_COND block 'curr',
+// 'succ' might be the fall-through path or the branch path from 'curr'.
+//
+// Arguments:
+// curr - A block which branches conditionally to 'succ'
+// succ - The target block
+//
+// Return Value:
+// Returns a new block, that is a successor of 'curr' and which branches unconditionally to 'succ'
+//
+// Assumptions:
+// 'curr' must have a bbJumpKind of BBJ_COND or BBJ_SWITCH
+//
+// Notes:
+// The returned block is empty.
+
+BasicBlock* Compiler::fgSplitEdge(BasicBlock* curr, BasicBlock* succ)
+{
+ assert(curr->bbJumpKind == BBJ_COND || curr->bbJumpKind == BBJ_SWITCH);
+ assert(fgGetPredForBlock(succ, curr) != nullptr);
+
+ BasicBlock* newBlock;
+ if (succ == curr->bbNext)
+ {
+ // The successor is the fall-through path of a BBJ_COND, or
+ // an immediately following block of a BBJ_SWITCH (which has
+ // no fall-through path). For this case, simply insert a new
+ // fall-through block after 'curr'.
+ newBlock = fgNewBBafter(BBJ_NONE, curr, true /*extendRegion*/);
+ }
+ else
+ {
+ newBlock = fgNewBBinRegion(BBJ_ALWAYS, curr, curr->isRunRarely());
+ // The new block always jumps to 'succ'
+ newBlock->bbJumpDest = succ;
+ }
+ newBlock->bbFlags |= (curr->bbFlags & succ->bbFlags & (BBF_BACKWARD_JUMP));
+
+ JITDUMP("Splitting edge from BB%02u to BB%02u; adding BB%02u\n", curr->bbNum, succ->bbNum, newBlock->bbNum);
+
+ if (curr->bbJumpKind == BBJ_COND)
+ {
+ fgReplacePred(succ, curr, newBlock);
+ if (curr->bbJumpDest == succ)
+ {
+ // Now 'curr' jumps to newBlock
+ curr->bbJumpDest = newBlock;
+ newBlock->bbFlags |= BBF_JMP_TARGET;
+ }
+ fgAddRefPred(newBlock, curr);
+ }
+ else
+ {
+ assert(curr->bbJumpKind == BBJ_SWITCH);
+
+ // newBlock replaces 'succ' in the switch.
+ fgReplaceSwitchJumpTarget(curr, newBlock, succ);
+
+ // And 'succ' has 'newBlock' as a new predecessor.
+ fgAddRefPred(succ, newBlock);
+ }
+
+ // This isn't accurate, but it is complex to compute a reasonable number so just assume that we take the
+ // branch 50% of the time.
+ newBlock->inheritWeightPercentage(curr, 50);
+
+ // The bbLiveIn and bbLiveOut are both equal to the bbLiveIn of 'succ'
+ if (fgLocalVarLivenessDone)
+ {
+ VarSetOps::Assign(this, newBlock->bbLiveIn, succ->bbLiveIn);
+ VarSetOps::Assign(this, newBlock->bbLiveOut, succ->bbLiveIn);
+ }
+
+ return newBlock;
+}
+
+/*****************************************************************************/
+/*****************************************************************************/
+
+void Compiler::fgFindOperOrder()
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgFindOperOrder()\n");
+ }
+#endif
+
+ BasicBlock* block;
+ GenTreeStmt* stmt;
+
+ /* Walk the basic blocks and for each statement determine
+ * the evaluation order, cost, FP levels, etc... */
+
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ compCurBB = block;
+ for (stmt = block->firstStmt(); stmt; stmt = stmt->gtNextStmt)
+ {
+ /* Recursively process the statement */
+
+ compCurStmt = stmt;
+ gtSetStmtInfo(stmt);
+ }
+ }
+}
+
+/*****************************************************************************/
+void Compiler::fgSimpleLowering()
+{
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ // Walk the statement trees in this basic block, converting ArrLength nodes.
+ compCurBB = block; // Used in fgRngChkTarget.
+
+#ifdef LEGACY_BACKEND
+ for (GenTreeStmt* stmt = block->FirstNonPhiDef(); stmt; stmt = stmt->gtNextStmt)
+ {
+ for (GenTreePtr tree = stmt->gtStmtList; tree; tree = tree->gtNext)
+ {
+#else
+ LIR::Range& range = LIR::AsRange(block);
+ for (GenTree* tree : range)
+ {
+ {
+#endif
+ if (tree->gtOper == GT_ARR_LENGTH)
+ {
+ GenTreeArrLen* arrLen = tree->AsArrLen();
+ GenTreePtr arr = arrLen->gtArrLen.ArrRef();
+ GenTreePtr add;
+ GenTreePtr con;
+
+ /* Create the expression "*(array_addr + ArrLenOffs)" */
+
+ noway_assert(arr->gtNext == tree);
+
+ noway_assert(arrLen->ArrLenOffset() == offsetof(CORINFO_Array, length) ||
+ arrLen->ArrLenOffset() == offsetof(CORINFO_String, stringLen));
+
+ if ((arr->gtOper == GT_CNS_INT) && (arr->gtIntCon.gtIconVal == 0))
+ {
+ // If the array is NULL, then we should get a NULL reference
+ // exception when computing its length. We need to maintain
+ // an invariant where there is no sum of two constants node, so
+ // let's simply return an indirection of NULL.
+
+ add = arr;
+ }
+ else
+ {
+ con = gtNewIconNode(arrLen->ArrLenOffset(), TYP_I_IMPL);
+ con->gtRsvdRegs = 0;
+
+ add = gtNewOperNode(GT_ADD, TYP_REF, arr, con);
+ add->gtRsvdRegs = arr->gtRsvdRegs;
+
+#ifdef LEGACY_BACKEND
+ con->gtCopyFPlvl(arr);
+
+ add->gtCopyFPlvl(arr);
+ add->CopyCosts(arr);
+
+ arr->gtNext = con;
+ con->gtPrev = arr;
+
+ con->gtNext = add;
+ add->gtPrev = con;
+
+ add->gtNext = tree;
+ tree->gtPrev = add;
+#else
+ range.InsertAfter(arr, con, add);
+#endif
+ }
+
+ // Change to a GT_IND.
+ tree->ChangeOperUnchecked(GT_IND);
+
+ tree->gtOp.gtOp1 = add;
+ }
+ else if (tree->OperGet() == GT_ARR_BOUNDS_CHECK
+#ifdef FEATURE_SIMD
+ || tree->OperGet() == GT_SIMD_CHK
+#endif // FEATURE_SIMD
+ )
+ {
+ // Add in a call to an error routine.
+ fgSetRngChkTarget(tree, false);
+ }
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose && fgRngChkThrowAdded)
+ {
+ printf("\nAfter fgSimpleLowering() added some RngChk throw blocks");
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ printf("\n");
+ }
+#endif
+}
+
+/*****************************************************************************
+ */
+
+void Compiler::fgUpdateRefCntForClone(BasicBlock* addedToBlock, GenTreePtr clonedTree)
+{
+ assert(clonedTree->gtOper != GT_STMT);
+
+ if (lvaLocalVarRefCounted)
+ {
+ compCurBB = addedToBlock;
+ fgWalkTreePre(&clonedTree, Compiler::lvaIncRefCntsCB, (void*)this, true);
+ }
+}
+
+/*****************************************************************************
+ */
+
+void Compiler::fgUpdateRefCntForExtract(GenTreePtr wholeTree, GenTreePtr keptTree)
+{
+ if (lvaLocalVarRefCounted)
+ {
+ /* Update the refCnts of removed lcl vars - The problem is that
+ * we have to consider back the side effects trees so we first
+ * increment all refCnts for side effects then decrement everything
+ * in the statement
+ */
+ if (keptTree)
+ {
+ fgWalkTreePre(&keptTree, Compiler::lvaIncRefCntsCB, (void*)this, true);
+ }
+
+ fgWalkTreePre(&wholeTree, Compiler::lvaDecRefCntsCB, (void*)this, true);
+ }
+}
+
+VARSET_VALRET_TP Compiler::fgGetVarBits(GenTreePtr tree)
+{
+ VARSET_TP VARSET_INIT_NOCOPY(varBits, VarSetOps::MakeEmpty(this));
+
+ assert(tree->gtOper == GT_LCL_VAR || tree->gtOper == GT_LCL_FLD || tree->gtOper == GT_REG_VAR);
+
+ unsigned int lclNum = tree->gtLclVarCommon.gtLclNum;
+ LclVarDsc* varDsc = lvaTable + lclNum;
+ if (varDsc->lvTracked)
+ {
+ VarSetOps::AddElemD(this, varBits, varDsc->lvVarIndex);
+ }
+ else if (varDsc->lvType == TYP_STRUCT && varDsc->lvPromoted)
+ {
+ for (unsigned i = varDsc->lvFieldLclStart; i < varDsc->lvFieldLclStart + varDsc->lvFieldCnt; ++i)
+ {
+ noway_assert(lvaTable[i].lvIsStructField);
+ if (lvaTable[i].lvTracked)
+ {
+ unsigned varIndex = lvaTable[i].lvVarIndex;
+ noway_assert(varIndex < lvaTrackedCount);
+ VarSetOps::AddElemD(this, varBits, varIndex);
+ }
+ }
+ }
+ return varBits;
+}
+
+/*****************************************************************************
+ *
+ * Find and remove any basic blocks that are useless (e.g. they have not been
+ * imported because they are not reachable, or they have been optimized away).
+ */
+
+void Compiler::fgRemoveEmptyBlocks()
+{
+ BasicBlock* cur;
+ BasicBlock* nxt;
+
+ /* If we remove any blocks, we'll have to do additional work */
+
+ unsigned removedBlks = 0;
+
+ for (cur = fgFirstBB; cur != nullptr; cur = nxt)
+ {
+ /* Get hold of the next block (in case we delete 'cur') */
+
+ nxt = cur->bbNext;
+
+ /* Should this block be removed? */
+
+ if (!(cur->bbFlags & BBF_IMPORTED))
+ {
+ noway_assert(cur->isEmpty());
+
+ if (ehCanDeleteEmptyBlock(cur))
+ {
+ /* Mark the block as removed */
+
+ cur->bbFlags |= BBF_REMOVED;
+
+ /* Remember that we've removed a block from the list */
+
+ removedBlks++;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("BB%02u was not imported, marked as removed (%d)\n", cur->bbNum, removedBlks);
+ }
+#endif // DEBUG
+
+ /* Drop the block from the list */
+
+ fgUnlinkBlock(cur);
+ }
+ else
+ {
+ // We were prevented from deleting this block by EH normalization. Mark the block as imported.
+ cur->bbFlags |= BBF_IMPORTED;
+ }
+ }
+ }
+
+ /* If no blocks were removed, we're done */
+
+ if (removedBlks == 0)
+ {
+ return;
+ }
+
+ /* Update all references in the exception handler table.
+ * Mark the new blocks as non-removable.
+ *
+ * We may have made the entire try block unreachable.
+ * Check for this case and remove the entry from the EH table.
+ */
+
+ unsigned XTnum;
+ EHblkDsc* HBtab;
+ INDEBUG(unsigned delCnt = 0;)
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ AGAIN:
+ /* If the beginning of the try block was not imported, we
+ * need to remove the entry from the EH table. */
+
+ if (HBtab->ebdTryBeg->bbFlags & BBF_REMOVED)
+ {
+ noway_assert(!(HBtab->ebdTryBeg->bbFlags & BBF_IMPORTED));
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Beginning of try block (BB%02u) not imported "
+ "- remove index #%u from the EH table\n",
+ HBtab->ebdTryBeg->bbNum, XTnum + delCnt);
+ }
+ delCnt++;
+#endif // DEBUG
+
+ fgRemoveEHTableEntry(XTnum);
+
+ if (XTnum < compHndBBtabCount)
+ {
+ // There are more entries left to process, so do more. Note that
+ // HBtab now points to the next entry, that we copied down to the
+ // current slot. XTnum also stays the same.
+ goto AGAIN;
+ }
+
+ break; // no more entries (we deleted the last one), so exit the loop
+ }
+
+/* At this point we know we have a valid try block */
+
+#ifdef DEBUG
+ assert(HBtab->ebdTryBeg->bbFlags & BBF_IMPORTED);
+ assert(HBtab->ebdTryBeg->bbFlags & BBF_DONT_REMOVE);
+
+ assert(HBtab->ebdHndBeg->bbFlags & BBF_IMPORTED);
+ assert(HBtab->ebdHndBeg->bbFlags & BBF_DONT_REMOVE);
+
+ if (HBtab->HasFilter())
+ {
+ assert(HBtab->ebdFilter->bbFlags & BBF_IMPORTED);
+ assert(HBtab->ebdFilter->bbFlags & BBF_DONT_REMOVE);
+ }
+#endif // DEBUG
+
+ fgSkipRmvdBlocks(HBtab);
+ } /* end of the for loop over XTnum */
+
+ // Renumber the basic blocks
+ JITDUMP("\nRenumbering the basic blocks for fgRemoveEmptyBlocks\n");
+ fgRenumberBlocks();
+
+#ifdef DEBUG
+ fgVerifyHandlerTab();
+#endif // DEBUG
+}
+
+/*****************************************************************************
+ *
+ * Remove a useless statement from a basic block.
+ * The default is to decrement ref counts of included vars
+ *
+ */
+
+void Compiler::fgRemoveStmt(BasicBlock* block,
+ GenTreePtr node,
+ // whether to decrement ref counts for tracked vars in statement
+ bool updateRefCount)
+{
+ noway_assert(node);
+ assert(fgOrder == FGOrderTree);
+
+ GenTreeStmt* tree = block->firstStmt();
+ GenTreeStmt* stmt = node->AsStmt();
+
+#ifdef DEBUG
+ if (verbose &&
+ stmt->gtStmtExpr->gtOper != GT_NOP) // Don't print if it is a GT_NOP. Too much noise from the inliner.
+ {
+ printf("\nRemoving statement ");
+ printTreeID(stmt);
+ printf(" in BB%02u as useless:\n", block->bbNum);
+ gtDispTree(stmt);
+ }
+#endif // DEBUG
+
+ if (opts.compDbgCode && stmt->gtPrev != stmt && stmt->gtStmtILoffsx != BAD_IL_OFFSET)
+ {
+ /* TODO: For debuggable code, should we remove significant
+ statement boundaries. Or should we leave a GT_NO_OP in its place? */
+ }
+
+ /* Is it the first statement in the list? */
+
+ GenTreeStmt* firstStmt = block->firstStmt();
+ if (firstStmt == stmt)
+ {
+ if (firstStmt->gtNext == nullptr)
+ {
+ assert(firstStmt == block->lastStmt());
+
+ /* this is the only statement - basic block becomes empty */
+ block->bbTreeList = nullptr;
+ }
+ else
+ {
+ block->bbTreeList = tree->gtNext;
+ block->bbTreeList->gtPrev = tree->gtPrev;
+ }
+ goto DONE;
+ }
+
+ /* Is it the last statement in the list? */
+
+ if (stmt == block->lastStmt())
+ {
+ stmt->gtPrev->gtNext = nullptr;
+ block->bbTreeList->gtPrev = stmt->gtPrev;
+ goto DONE;
+ }
+
+ tree = stmt->gtPrevStmt;
+ noway_assert(tree);
+
+ tree->gtNext = stmt->gtNext;
+ stmt->gtNext->gtPrev = tree;
+
+DONE:
+ fgStmtRemoved = true;
+
+ if (optValnumCSE_phase)
+ {
+ optValnumCSE_UnmarkCSEs(stmt->gtStmtExpr, nullptr);
+ }
+ else
+ {
+ if (updateRefCount)
+ {
+ if (fgStmtListThreaded)
+ {
+ fgWalkTreePre(&stmt->gtStmtExpr, Compiler::lvaDecRefCntsCB, (void*)this, true);
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ if (block->bbTreeList == nullptr)
+ {
+ printf("\nBB%02u becomes empty", block->bbNum);
+ }
+ printf("\n");
+ }
+#endif // DEBUG
+}
+
+/******************************************************************************/
+// Returns true if the operator is involved in control-flow
+// TODO-Cleanup: Move this into genTreeKinds in genTree.h
+
+inline bool OperIsControlFlow(genTreeOps oper)
+{
+ switch (oper)
+ {
+ case GT_JTRUE:
+ case GT_SWITCH:
+ case GT_LABEL:
+
+ case GT_CALL:
+ case GT_JMP:
+
+ case GT_RETURN:
+ case GT_RETFILT:
+#if !FEATURE_EH_FUNCLETS
+ case GT_END_LFIN:
+#endif // !FEATURE_EH_FUNCLETS
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/******************************************************************************
+ * Tries to throw away a stmt. The statement can be anywhere in block->bbTreeList.
+ * Returns true if it did remove the statement.
+ */
+
+bool Compiler::fgCheckRemoveStmt(BasicBlock* block, GenTreePtr node)
+{
+ if (opts.compDbgCode)
+ {
+ return false;
+ }
+
+ GenTreeStmt* stmt = node->AsStmt();
+
+ GenTreePtr tree = stmt->gtStmtExpr;
+ genTreeOps oper = tree->OperGet();
+
+ if (OperIsControlFlow(oper) || oper == GT_NO_OP)
+ {
+ return false;
+ }
+
+ // TODO: Use a recursive version of gtNodeHasSideEffects()
+ if (tree->gtFlags & GTF_SIDE_EFFECT)
+ {
+ return false;
+ }
+
+ fgRemoveStmt(block, stmt);
+ return true;
+}
+
+/****************************************************************************************************
+ *
+ *
+ */
+bool Compiler::fgCanCompactBlocks(BasicBlock* block, BasicBlock* bNext)
+{
+ if ((block == nullptr) || (bNext == nullptr))
+ {
+ return false;
+ }
+
+ noway_assert(block->bbNext == bNext);
+
+ if (block->bbJumpKind != BBJ_NONE)
+ {
+ return false;
+ }
+
+ // If the next block has multiple incoming edges, we can still compact if the first block is empty.
+ // However, not if it is the beginning of a handler.
+ if (bNext->countOfInEdges() != 1 &&
+ (!block->isEmpty() || (block->bbFlags & BBF_FUNCLET_BEG) || (block->bbCatchTyp != BBCT_NONE)))
+ {
+ return false;
+ }
+
+ if (bNext->bbFlags & BBF_DONT_REMOVE)
+ {
+ return false;
+ }
+
+ // Don't compact the first block if it was specially created as a scratch block.
+ if (fgBBisScratch(block))
+ {
+ return false;
+ }
+
+#if defined(_TARGET_ARM_)
+ // We can't compact a finally target block, as we need to generate special code for such blocks during code
+ // generation
+ if ((bNext->bbFlags & BBF_FINALLY_TARGET) != 0)
+ return false;
+#endif
+
+ // We don't want to compact blocks that are in different Hot/Cold regions
+ //
+ if (fgInDifferentRegions(block, bNext))
+ {
+ return false;
+ }
+
+ // We cannot compact two blocks in different EH regions.
+ //
+ if (fgCanRelocateEHRegions)
+ {
+ if (!BasicBlock::sameEHRegion(block, bNext))
+ {
+ return false;
+ }
+ }
+ // if there is a switch predecessor don't bother because we'd have to update the uniquesuccs as well
+ // (if they are valid)
+ for (flowList* pred = bNext->bbPreds; pred; pred = pred->flNext)
+ {
+ if (pred->flBlock->bbJumpKind == BBJ_SWITCH)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*****************************************************************************************************
+ *
+ * Function called to compact two given blocks in the flowgraph
+ * Assumes that all necessary checks have been performed,
+ * i.e. fgCanCompactBlocks returns true.
+ *
+ * Uses for this function - whenever we change links, insert blocks,...
+ * It will keep the flowgraph data in synch - bbNum, bbRefs, bbPreds
+ */
+
+void Compiler::fgCompactBlocks(BasicBlock* block, BasicBlock* bNext)
+{
+ noway_assert(block != nullptr);
+ noway_assert((block->bbFlags & BBF_REMOVED) == 0);
+ noway_assert(block->bbJumpKind == BBJ_NONE);
+
+ noway_assert(bNext == block->bbNext);
+ noway_assert(bNext != nullptr);
+ noway_assert((bNext->bbFlags & BBF_REMOVED) == 0);
+ noway_assert(bNext->countOfInEdges() == 1 || block->isEmpty());
+ noway_assert(bNext->bbPreds);
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ noway_assert((bNext->bbFlags & BBF_FINALLY_TARGET) == 0);
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+ // Make sure the second block is not the start of a TRY block or an exception handler
+
+ noway_assert(bNext->bbCatchTyp == BBCT_NONE);
+ noway_assert((bNext->bbFlags & BBF_TRY_BEG) == 0);
+ noway_assert((bNext->bbFlags & BBF_DONT_REMOVE) == 0);
+
+ /* both or none must have an exception handler */
+ noway_assert(block->hasTryIndex() == bNext->hasTryIndex());
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nCompacting blocks BB%02u and BB%02u:\n", block->bbNum, bNext->bbNum);
+ }
+#endif
+
+ if (bNext->countOfInEdges() > 1)
+ {
+ JITDUMP("Second block has multiple incoming edges\n");
+
+ assert(block->isEmpty());
+ block->bbFlags |= BBF_JMP_TARGET;
+ for (flowList* pred = bNext->bbPreds; pred; pred = pred->flNext)
+ {
+ fgReplaceJumpTarget(pred->flBlock, block, bNext);
+
+ if (pred->flBlock != block)
+ {
+ fgAddRefPred(block, pred->flBlock);
+ }
+ }
+ bNext->bbPreds = nullptr;
+ }
+ else
+ {
+ noway_assert(bNext->bbPreds->flNext == nullptr);
+ noway_assert(bNext->bbPreds->flBlock == block);
+ }
+
+ /* Start compacting - move all the statements in the second block to the first block */
+
+ // First move any phi definitions of the second block after the phi defs of the first.
+ // TODO-CQ: This may be the wrong thing to do. If we're compacting blocks, it's because a
+ // control-flow choice was constant-folded away. So probably phi's need to go away,
+ // as well, in favor of one of the incoming branches. Or at least be modified.
+
+ assert(block->IsLIR() == bNext->IsLIR());
+ if (block->IsLIR())
+ {
+ LIR::Range& blockRange = LIR::AsRange(block);
+ LIR::Range& nextRange = LIR::AsRange(bNext);
+
+ // Does the next block have any phis?
+ GenTree* nextFirstNonPhi = nullptr;
+ LIR::ReadOnlyRange nextPhis = nextRange.PhiNodes();
+ if (!nextPhis.IsEmpty())
+ {
+ GenTree* blockLastPhi = blockRange.LastPhiNode();
+ nextFirstNonPhi = nextPhis.LastNode()->gtNext;
+
+ LIR::Range phisToMove = nextRange.Remove(std::move(nextPhis));
+ blockRange.InsertAfter(blockLastPhi, std::move(phisToMove));
+ }
+ else
+ {
+ nextFirstNonPhi = nextRange.FirstNode();
+ }
+
+ // Does the block have any other code?
+ if (nextFirstNonPhi != nullptr)
+ {
+ LIR::Range nextNodes = nextRange.Remove(nextFirstNonPhi, nextRange.LastNode());
+ blockRange.InsertAtEnd(std::move(nextNodes));
+ }
+ }
+ else
+ {
+ GenTreePtr blkNonPhi1 = block->FirstNonPhiDef();
+ GenTreePtr bNextNonPhi1 = bNext->FirstNonPhiDef();
+ GenTreePtr blkFirst = block->firstStmt();
+ GenTreePtr bNextFirst = bNext->firstStmt();
+
+ // Does the second have any phis?
+ if (bNextFirst != nullptr && bNextFirst != bNextNonPhi1)
+ {
+ GenTreePtr bNextLast = bNextFirst->gtPrev;
+ assert(bNextLast->gtNext == nullptr);
+
+ // Does "blk" have phis?
+ if (blkNonPhi1 != blkFirst)
+ {
+ // Yes, has phis.
+ // Insert after the last phi of "block."
+ // First, bNextPhis after last phi of block.
+ GenTreePtr blkLastPhi;
+ if (blkNonPhi1 != nullptr)
+ {
+ blkLastPhi = blkNonPhi1->gtPrev;
+ }
+ else
+ {
+ blkLastPhi = blkFirst->gtPrev;
+ }
+
+ blkLastPhi->gtNext = bNextFirst;
+ bNextFirst->gtPrev = blkLastPhi;
+
+ // Now, rest of "block" after last phi of "bNext".
+ GenTreePtr bNextLastPhi = nullptr;
+ if (bNextNonPhi1 != nullptr)
+ {
+ bNextLastPhi = bNextNonPhi1->gtPrev;
+ }
+ else
+ {
+ bNextLastPhi = bNextFirst->gtPrev;
+ }
+
+ bNextLastPhi->gtNext = blkNonPhi1;
+ if (blkNonPhi1 != nullptr)
+ {
+ blkNonPhi1->gtPrev = bNextLastPhi;
+ }
+ else
+ {
+ // block has no non phis, so make the last statement be the last added phi.
+ blkFirst->gtPrev = bNextLastPhi;
+ }
+
+ // Now update the bbTreeList of "bNext".
+ bNext->bbTreeList = bNextNonPhi1;
+ if (bNextNonPhi1 != nullptr)
+ {
+ bNextNonPhi1->gtPrev = bNextLast;
+ }
+ }
+ else
+ {
+ if (blkFirst != nullptr) // If "block" has no statements, fusion will work fine...
+ {
+ // First, bNextPhis at start of block.
+ GenTreePtr blkLast = blkFirst->gtPrev;
+ block->bbTreeList = bNextFirst;
+ // Now, rest of "block" (if it exists) after last phi of "bNext".
+ GenTreePtr bNextLastPhi = nullptr;
+ if (bNextNonPhi1 != nullptr)
+ {
+ // There is a first non phi, so the last phi is before it.
+ bNextLastPhi = bNextNonPhi1->gtPrev;
+ }
+ else
+ {
+ // All the statements are phi defns, so the last one is the prev of the first.
+ bNextLastPhi = bNextFirst->gtPrev;
+ }
+ bNextFirst->gtPrev = blkLast;
+ bNextLastPhi->gtNext = blkFirst;
+ blkFirst->gtPrev = bNextLastPhi;
+ // Now update the bbTreeList of "bNext"
+ bNext->bbTreeList = bNextNonPhi1;
+ if (bNextNonPhi1 != nullptr)
+ {
+ bNextNonPhi1->gtPrev = bNextLast;
+ }
+ }
+ }
+ }
+
+ // Now proceed with the updated bbTreeLists.
+ GenTreePtr stmtList1 = block->firstStmt();
+ GenTreePtr stmtList2 = bNext->firstStmt();
+
+ /* the block may have an empty list */
+
+ if (stmtList1)
+ {
+ GenTreePtr stmtLast1 = block->lastStmt();
+
+ /* The second block may be a GOTO statement or something with an empty bbTreeList */
+ if (stmtList2)
+ {
+ GenTreePtr stmtLast2 = bNext->lastStmt();
+
+ /* append list2 to list 1 */
+
+ stmtLast1->gtNext = stmtList2;
+ stmtList2->gtPrev = stmtLast1;
+ stmtList1->gtPrev = stmtLast2;
+ }
+ }
+ else
+ {
+ /* block was formerly empty and now has bNext's statements */
+ block->bbTreeList = stmtList2;
+ }
+ }
+
+ // Note we could update the local variable weights here by
+ // calling lvaMarkLocalVars, with the block and weight adjustment.
+
+ // If either block or bNext has a profile weight
+ // or if both block and bNext have non-zero weights
+ // then we select the highest weight block.
+
+ if ((block->bbFlags & BBF_PROF_WEIGHT) || (bNext->bbFlags & BBF_PROF_WEIGHT) ||
+ (block->bbWeight && bNext->bbWeight))
+ {
+ // We are keeping block so update its fields
+ // when bNext has a greater weight
+
+ if (block->bbWeight < bNext->bbWeight)
+ {
+ block->bbWeight = bNext->bbWeight;
+
+ block->bbFlags |= (bNext->bbFlags & BBF_PROF_WEIGHT); // Set the profile weight flag (if necessary)
+ if (block->bbWeight != 0)
+ {
+ block->bbFlags &= ~BBF_RUN_RARELY; // Clear any RarelyRun flag
+ }
+ }
+ }
+ // otherwise if either block has a zero weight we select the zero weight
+ else
+ {
+ noway_assert((block->bbWeight == BB_ZERO_WEIGHT) || (bNext->bbWeight == BB_ZERO_WEIGHT));
+ block->bbWeight = BB_ZERO_WEIGHT;
+ block->bbFlags |= BBF_RUN_RARELY; // Set the RarelyRun flag
+ }
+
+ /* set the right links */
+
+ block->bbJumpKind = bNext->bbJumpKind;
+ VarSetOps::AssignAllowUninitRhs(this, block->bbLiveOut, bNext->bbLiveOut);
+
+ // Update the beginning and ending IL offsets (bbCodeOffs and bbCodeOffsEnd).
+ // Set the beginning IL offset to the minimum, and the ending offset to the maximum, of the respective blocks.
+ // If one block has an unknown offset, we take the other block.
+ // We are merging into 'block', so if its values are correct, just leave them alone.
+ // TODO: we should probably base this on the statements within.
+
+ if (block->bbCodeOffs == BAD_IL_OFFSET)
+ {
+ block->bbCodeOffs = bNext->bbCodeOffs; // If they are both BAD_IL_OFFSET, this doesn't change anything.
+ }
+ else if (bNext->bbCodeOffs != BAD_IL_OFFSET)
+ {
+ // The are both valid offsets; compare them.
+ if (block->bbCodeOffs > bNext->bbCodeOffs)
+ {
+ block->bbCodeOffs = bNext->bbCodeOffs;
+ }
+ }
+
+ if (block->bbCodeOffsEnd == BAD_IL_OFFSET)
+ {
+ block->bbCodeOffsEnd = bNext->bbCodeOffsEnd; // If they are both BAD_IL_OFFSET, this doesn't change anything.
+ }
+ else if (bNext->bbCodeOffsEnd != BAD_IL_OFFSET)
+ {
+ // The are both valid offsets; compare them.
+ if (block->bbCodeOffsEnd < bNext->bbCodeOffsEnd)
+ {
+ block->bbCodeOffsEnd = bNext->bbCodeOffsEnd;
+ }
+ }
+
+ if (((block->bbFlags & BBF_INTERNAL) != 0) && ((bNext->bbFlags & BBF_INTERNAL) == 0))
+ {
+ // If 'block' is an internal block and 'bNext' isn't, then adjust the flags set on 'block'.
+ block->bbFlags &= ~BBF_INTERNAL; // Clear the BBF_INTERNAL flag
+ block->bbFlags |= BBF_IMPORTED; // Set the BBF_IMPORTED flag
+ }
+
+ /* Update the flags for block with those found in bNext */
+
+ block->bbFlags |= (bNext->bbFlags & BBF_COMPACT_UPD);
+
+ /* mark bNext as removed */
+
+ bNext->bbFlags |= BBF_REMOVED;
+
+ /* Unlink bNext and update all the marker pointers if necessary */
+
+ fgUnlinkRange(block->bbNext, bNext);
+
+ // If bNext was the last block of a try or handler, update the EH table.
+
+ ehUpdateForDeletedBlock(bNext);
+
+ /* If we're collapsing a block created after the dominators are
+ computed, rename the block and reuse dominator information from
+ the other block */
+ if (fgDomsComputed && block->bbNum > fgDomBBcount)
+ {
+ BlockSetOps::Assign(this, block->bbReach, bNext->bbReach);
+ BlockSetOps::ClearD(this, bNext->bbReach);
+
+ block->bbIDom = bNext->bbIDom;
+ bNext->bbIDom = nullptr;
+
+ // In this case, there's no need to update the preorder and postorder numbering
+ // since we're changing the bbNum, this makes the basic block all set.
+ block->bbNum = bNext->bbNum;
+ }
+
+ /* Set the jump targets */
+
+ switch (bNext->bbJumpKind)
+ {
+ case BBJ_CALLFINALLY:
+ // Propagate RETLESS property
+ block->bbFlags |= (bNext->bbFlags & BBF_RETLESS_CALL);
+
+ __fallthrough;
+
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ block->bbJumpDest = bNext->bbJumpDest;
+
+ /* Update the predecessor list for 'bNext->bbJumpDest' */
+ fgReplacePred(bNext->bbJumpDest, bNext, block);
+
+ /* Update the predecessor list for 'bNext->bbNext' if it is different than 'bNext->bbJumpDest' */
+ if (bNext->bbJumpKind == BBJ_COND && bNext->bbJumpDest != bNext->bbNext)
+ {
+ fgReplacePred(bNext->bbNext, bNext, block);
+ }
+ break;
+
+ case BBJ_NONE:
+ /* Update the predecessor list for 'bNext->bbNext' */
+ fgReplacePred(bNext->bbNext, bNext, block);
+ break;
+
+ case BBJ_EHFILTERRET:
+ fgReplacePred(bNext->bbJumpDest, bNext, block);
+ break;
+
+ case BBJ_EHFINALLYRET:
+ {
+ unsigned hndIndex = block->getHndIndex();
+ EHblkDsc* ehDsc = ehGetDsc(hndIndex);
+
+ if (ehDsc->HasFinallyHandler()) // No need to do this for fault handlers
+ {
+ BasicBlock* begBlk;
+ BasicBlock* endBlk;
+ ehGetCallFinallyBlockRange(hndIndex, &begBlk, &endBlk);
+
+ BasicBlock* finBeg = ehDsc->ebdHndBeg;
+
+ for (BasicBlock* bcall = begBlk; bcall != endBlk; bcall = bcall->bbNext)
+ {
+ if (bcall->bbJumpKind != BBJ_CALLFINALLY || bcall->bbJumpDest != finBeg)
+ {
+ continue;
+ }
+
+ noway_assert(bcall->isBBCallAlwaysPair());
+ fgReplacePred(bcall->bbNext, bNext, block);
+ }
+ }
+ }
+ break;
+
+ case BBJ_THROW:
+ case BBJ_RETURN:
+ /* no jumps or fall through blocks to set here */
+ break;
+
+ case BBJ_SWITCH:
+ block->bbJumpSwt = bNext->bbJumpSwt;
+ // We are moving the switch jump from bNext to block. Examine the jump targets
+ // of the BBJ_SWITCH at bNext and replace the predecessor to 'bNext' with ones to 'block'
+ fgChangeSwitchBlock(bNext, block);
+ break;
+
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+
+ fgUpdateLoopsAfterCompacting(block, bNext);
+
+#if DEBUG
+ if (verbose && 0)
+ {
+ printf("\nAfter compacting:\n");
+ fgDispBasicBlocks(false);
+ }
+#endif
+
+#if DEBUG
+ if (JitConfig.JitSlowDebugChecksEnabled() != 0)
+ {
+ // Make sure that the predecessor lists are accurate
+ fgDebugCheckBBlist();
+ }
+#endif // DEBUG
+}
+
+void Compiler::fgUpdateLoopsAfterCompacting(BasicBlock* block, BasicBlock* bNext)
+{
+ /* Check if the removed block is not part the loop table */
+ noway_assert(bNext);
+
+ for (unsigned loopNum = 0; loopNum < optLoopCount; loopNum++)
+ {
+ /* Some loops may have been already removed by
+ * loop unrolling or conditional folding */
+
+ if (optLoopTable[loopNum].lpFlags & LPFLG_REMOVED)
+ {
+ continue;
+ }
+
+ /* Check the loop head (i.e. the block preceding the loop) */
+
+ if (optLoopTable[loopNum].lpHead == bNext)
+ {
+ optLoopTable[loopNum].lpHead = block;
+ }
+
+ /* Check the loop bottom */
+
+ if (optLoopTable[loopNum].lpBottom == bNext)
+ {
+ optLoopTable[loopNum].lpBottom = block;
+ }
+
+ /* Check the loop exit */
+
+ if (optLoopTable[loopNum].lpExit == bNext)
+ {
+ noway_assert(optLoopTable[loopNum].lpExitCnt == 1);
+ optLoopTable[loopNum].lpExit = block;
+ }
+
+ /* Check the loop entry */
+
+ if (optLoopTable[loopNum].lpEntry == bNext)
+ {
+ optLoopTable[loopNum].lpEntry = block;
+ }
+ }
+}
+
+/*****************************************************************************************************
+ *
+ * Function called to remove a block when it is unreachable.
+ *
+ * This function cannot remove the first block.
+ */
+
+void Compiler::fgUnreachableBlock(BasicBlock* block)
+{
+ // genReturnBB should never be removed, as we might have special hookups there.
+ // Therefore, we should never come here to remove the statements in the genReturnBB block.
+ // For example, <BUGNUM> in VSW 364383, </BUGNUM>
+ // the profiler hookup needs to have the "void GT_RETURN" statement
+ // to properly set the info.compProfilerCallback flag.
+ noway_assert(block != genReturnBB);
+
+ if (block->bbFlags & BBF_REMOVED)
+ {
+ return;
+ }
+
+/* Removing an unreachable block */
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nRemoving unreachable BB%02u\n", block->bbNum);
+ }
+#endif // DEBUG
+
+ noway_assert(block->bbPrev != nullptr); // Can use this function to remove the first block
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ assert(!block->bbPrev->isBBCallAlwaysPair()); // can't remove the BBJ_ALWAYS of a BBJ_CALLFINALLY / BBJ_ALWAYS pair
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+ /* First walk the statement trees in this basic block and delete each stmt */
+
+ /* Make the block publicly available */
+ compCurBB = block;
+
+ if (block->IsLIR())
+ {
+ LIR::Range& blockRange = LIR::AsRange(block);
+ if (!blockRange.IsEmpty())
+ {
+ blockRange.Delete(this, block, blockRange.FirstNode(), blockRange.LastNode());
+ }
+ }
+ else
+ {
+ // TODO-Cleanup: I'm not sure why this happens -- if the block is unreachable, why does it have phis?
+ // Anyway, remove any phis.
+
+ GenTreePtr firstNonPhi = block->FirstNonPhiDef();
+ if (block->bbTreeList != firstNonPhi)
+ {
+ if (firstNonPhi != nullptr)
+ {
+ firstNonPhi->gtPrev = block->lastStmt();
+ }
+ block->bbTreeList = firstNonPhi;
+ }
+
+ for (GenTreeStmt* stmt = block->firstStmt(); stmt; stmt = stmt->gtNextStmt)
+ {
+ fgRemoveStmt(block, stmt);
+ }
+ noway_assert(block->bbTreeList == nullptr);
+ }
+
+ /* Next update the loop table and bbWeights */
+ optUpdateLoopsBeforeRemoveBlock(block);
+
+ /* Mark the block as removed */
+ block->bbFlags |= BBF_REMOVED;
+
+ /* update bbRefs and bbPreds for the blocks reached by this block */
+ fgRemoveBlockAsPred(block);
+}
+
+/*****************************************************************************************************
+ *
+ * Function called to remove or morph a GT_JTRUE statement when we jump to the same
+ * block when both the condition is true or false.
+ */
+void Compiler::fgRemoveJTrue(BasicBlock* block)
+{
+ noway_assert(block->bbJumpKind == BBJ_COND && block->bbJumpDest == block->bbNext);
+ assert(compRationalIRForm == block->IsLIR());
+
+ flowList* flow = fgGetPredForBlock(block->bbNext, block);
+ noway_assert(flow->flDupCount == 2);
+
+ // Change the BBJ_COND to BBJ_NONE, and adjust the refCount and dupCount.
+ block->bbJumpKind = BBJ_NONE;
+ block->bbFlags &= ~BBF_NEEDS_GCPOLL;
+ --block->bbNext->bbRefs;
+ --flow->flDupCount;
+
+#ifdef DEBUG
+ block->bbJumpDest = nullptr;
+ if (verbose)
+ {
+ printf("Block BB%02u becoming a BBJ_NONE to BB%02u (jump target is the same whether the condition is true or "
+ "false)\n",
+ block->bbNum, block->bbNext->bbNum);
+ }
+#endif
+
+ /* Remove the block jump condition */
+
+ if (block->IsLIR())
+ {
+ LIR::Range& blockRange = LIR::AsRange(block);
+
+ GenTree* test = blockRange.LastNode();
+ assert(test->OperGet() == GT_JTRUE);
+
+ bool isClosed;
+ unsigned sideEffects;
+ LIR::ReadOnlyRange testRange = blockRange.GetTreeRange(test, &isClosed, &sideEffects);
+
+ // TODO-LIR: this should really be checking GTF_ALL_EFFECT, but that produces unacceptable
+ // diffs compared to the existing backend.
+ if (isClosed && ((sideEffects & GTF_SIDE_EFFECT) == 0))
+ {
+ // If the jump and its operands form a contiguous, side-effect-free range,
+ // remove them.
+ blockRange.Delete(this, block, std::move(testRange));
+ }
+ else
+ {
+ // Otherwise, just remove the jump node itself.
+ blockRange.Remove(test);
+ }
+ }
+ else
+ {
+ GenTreeStmt* test = block->lastStmt();
+ GenTree* tree = test->gtStmtExpr;
+
+ noway_assert(tree->gtOper == GT_JTRUE);
+
+ GenTree* sideEffList = nullptr;
+
+ if (tree->gtFlags & GTF_SIDE_EFFECT)
+ {
+ gtExtractSideEffList(tree, &sideEffList);
+
+ if (sideEffList)
+ {
+ noway_assert(sideEffList->gtFlags & GTF_SIDE_EFFECT);
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Extracted side effects list from condition...\n");
+ gtDispTree(sideEffList);
+ printf("\n");
+ }
+#endif
+ }
+ }
+
+ // Delete the cond test or replace it with the side effect tree
+ if (sideEffList == nullptr)
+ {
+ fgRemoveStmt(block, test);
+ }
+ else
+ {
+ test->gtStmtExpr = sideEffList;
+
+ fgMorphBlockStmt(block, test DEBUGARG("fgRemoveJTrue"));
+ }
+ }
+}
+
+/*****************************************************************************************************
+ *
+ * Function to return the last basic block in the main part of the function. With funclets, it is
+ * the block immediately before the first funclet.
+ * An inclusive end of the main method.
+ */
+
+BasicBlock* Compiler::fgLastBBInMainFunction()
+{
+#if FEATURE_EH_FUNCLETS
+
+ if (fgFirstFuncletBB != nullptr)
+ {
+ return fgFirstFuncletBB->bbPrev;
+ }
+
+#endif // FEATURE_EH_FUNCLETS
+
+ assert(fgLastBB->bbNext == nullptr);
+
+ return fgLastBB;
+}
+
+/*****************************************************************************************************
+ *
+ * Function to return the first basic block after the main part of the function. With funclets, it is
+ * the block of the first funclet. Otherwise it is NULL if there are no funclets (fgLastBB->bbNext).
+ * This is equivalent to fgLastBBInMainFunction()->bbNext
+ * An exclusive end of the main method.
+ */
+
+BasicBlock* Compiler::fgEndBBAfterMainFunction()
+{
+#if FEATURE_EH_FUNCLETS
+
+ if (fgFirstFuncletBB != nullptr)
+ {
+ return fgFirstFuncletBB;
+ }
+
+#endif // FEATURE_EH_FUNCLETS
+
+ assert(fgLastBB->bbNext == nullptr);
+
+ return nullptr;
+}
+
+// Removes the block from the bbPrev/bbNext chain
+// Updates fgFirstBB and fgLastBB if necessary
+// Does not update fgFirstFuncletBB or fgFirstColdBlock (fgUnlinkRange does)
+
+void Compiler::fgUnlinkBlock(BasicBlock* block)
+{
+ if (block->bbPrev)
+ {
+ block->bbPrev->bbNext = block->bbNext;
+ if (block->bbNext)
+ {
+ block->bbNext->bbPrev = block->bbPrev;
+ }
+ else
+ {
+ fgLastBB = block->bbPrev;
+ }
+ }
+ else
+ {
+ assert(block == fgFirstBB);
+ assert(block != fgLastBB);
+ assert((fgFirstBBScratch == nullptr) || (fgFirstBBScratch == fgFirstBB));
+
+ fgFirstBB = block->bbNext;
+ fgFirstBB->bbPrev = nullptr;
+
+ if (fgFirstBBScratch != nullptr)
+ {
+#ifdef DEBUG
+ // We had created an initial scratch BB, but now we're deleting it.
+ if (verbose)
+ {
+ printf("Unlinking scratch BB%02u\n", block->bbNum);
+ }
+#endif // DEBUG
+ fgFirstBBScratch = nullptr;
+ }
+ }
+}
+
+/*****************************************************************************************************
+ *
+ * Function called to unlink basic block range [bBeg .. bEnd] from the basic block list.
+ *
+ * 'bBeg' can't be the first block.
+ */
+
+void Compiler::fgUnlinkRange(BasicBlock* bBeg, BasicBlock* bEnd)
+{
+ assert(bBeg != nullptr);
+ assert(bEnd != nullptr);
+
+ BasicBlock* bPrev = bBeg->bbPrev;
+ assert(bPrev != nullptr); // Can't unlink a range starting with the first block
+
+ bPrev->setNext(bEnd->bbNext);
+
+ /* If we removed the last block in the method then update fgLastBB */
+ if (fgLastBB == bEnd)
+ {
+ fgLastBB = bPrev;
+ noway_assert(fgLastBB->bbNext == nullptr);
+ }
+
+ // If bEnd was the first Cold basic block update fgFirstColdBlock
+ if (fgFirstColdBlock == bEnd)
+ {
+ fgFirstColdBlock = bPrev->bbNext;
+ }
+
+#if FEATURE_EH_FUNCLETS
+#ifdef DEBUG
+ // You can't unlink a range that includes the first funclet block. A range certainly
+ // can't cross the non-funclet/funclet region. And you can't unlink the first block
+ // of the first funclet with this, either. (If that's necessary, it could be allowed
+ // by updating fgFirstFuncletBB to bEnd->bbNext.)
+ for (BasicBlock* tempBB = bBeg; tempBB != bEnd->bbNext; tempBB = tempBB->bbNext)
+ {
+ assert(tempBB != fgFirstFuncletBB);
+ }
+#endif // DEBUG
+#endif // FEATURE_EH_FUNCLETS
+}
+
+/*****************************************************************************************************
+ *
+ * Function called to remove a basic block
+ */
+
+void Compiler::fgRemoveBlock(BasicBlock* block, bool unreachable)
+{
+ BasicBlock* bPrev = block->bbPrev;
+
+ /* The block has to be either unreachable or empty */
+
+ PREFIX_ASSUME(block != nullptr);
+
+ JITDUMP("fgRemoveBlock BB%02u\n", block->bbNum);
+
+ // If we've cached any mappings from switch blocks to SwitchDesc's (which contain only the
+ // *unique* successors of the switch block), invalidate that cache, since an entry in one of
+ // the SwitchDescs might be removed.
+ InvalidateUniqueSwitchSuccMap();
+
+ noway_assert((block == fgFirstBB) || (bPrev && (bPrev->bbNext == block)));
+ noway_assert(!(block->bbFlags & BBF_DONT_REMOVE));
+
+ // Should never remove a genReturnBB, as we might have special hookups there.
+ noway_assert(block != genReturnBB);
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ // Don't remove a finally target
+ assert(!(block->bbFlags & BBF_FINALLY_TARGET));
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+ if (unreachable)
+ {
+ PREFIX_ASSUME(bPrev != nullptr);
+
+ fgUnreachableBlock(block);
+
+ /* If this is the last basic block update fgLastBB */
+ if (block == fgLastBB)
+ {
+ fgLastBB = bPrev;
+ }
+
+#if FEATURE_EH_FUNCLETS
+ // If block was the fgFirstFuncletBB then set fgFirstFuncletBB to block->bbNext
+ if (block == fgFirstFuncletBB)
+ {
+ fgFirstFuncletBB = block->bbNext;
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ if (bPrev->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ // bPrev CALL becomes RETLESS as the BBJ_ALWAYS block is unreachable
+ bPrev->bbFlags |= BBF_RETLESS_CALL;
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ NO_WAY("No retless call finally blocks; need unwind target instead");
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ }
+ else if (bPrev->bbJumpKind == BBJ_ALWAYS && bPrev->bbJumpDest == block->bbNext &&
+ !(bPrev->bbFlags & BBF_KEEP_BBJ_ALWAYS) && (block != fgFirstColdBlock) &&
+ (block->bbNext != fgFirstColdBlock))
+ {
+ // previous block is a BBJ_ALWAYS to the next block: change to BBJ_NONE.
+ // Note that we don't do it if bPrev follows a BBJ_CALLFINALLY block (BBF_KEEP_BBJ_ALWAYS),
+ // because that would violate our invariant that BBJ_CALLFINALLY blocks are followed by
+ // BBJ_ALWAYS blocks.
+ bPrev->bbJumpKind = BBJ_NONE;
+ bPrev->bbFlags &= ~BBF_NEEDS_GCPOLL;
+ }
+
+ // If this is the first Cold basic block update fgFirstColdBlock
+ if (block == fgFirstColdBlock)
+ {
+ fgFirstColdBlock = block->bbNext;
+ }
+
+ /* Unlink this block from the bbNext chain */
+ fgUnlinkBlock(block);
+
+ /* At this point the bbPreds and bbRefs had better be zero */
+ noway_assert((block->bbRefs == 0) && (block->bbPreds == nullptr));
+
+ /* A BBJ_CALLFINALLY is usually paired with a BBJ_ALWAYS.
+ * If we delete such a BBJ_CALLFINALLY we also delete the BBJ_ALWAYS
+ */
+ if (block->isBBCallAlwaysPair())
+ {
+ BasicBlock* leaveBlk = block->bbNext;
+ noway_assert(leaveBlk->bbJumpKind == BBJ_ALWAYS);
+
+ leaveBlk->bbFlags &= ~BBF_DONT_REMOVE;
+ leaveBlk->bbRefs = 0;
+ leaveBlk->bbPreds = nullptr;
+
+ fgRemoveBlock(leaveBlk, true);
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ fgClearFinallyTargetBit(leaveBlk->bbJumpDest);
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ }
+ else if (block->bbJumpKind == BBJ_RETURN)
+ {
+ fgRemoveReturnBlock(block);
+ }
+ }
+ else // block is empty
+ {
+ noway_assert(block->isEmpty());
+
+ /* The block cannot follow a non-retless BBJ_CALLFINALLY (because we don't know who may jump to it) */
+ noway_assert((bPrev == nullptr) || !bPrev->isBBCallAlwaysPair());
+
+ /* This cannot be the last basic block */
+ noway_assert(block != fgLastBB);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Removing empty BB%02u\n", block->bbNum);
+ }
+#endif // DEBUG
+
+#ifdef DEBUG
+ /* Some extra checks for the empty case */
+
+ switch (block->bbJumpKind)
+ {
+ case BBJ_NONE:
+ break;
+
+ case BBJ_ALWAYS:
+ /* Do not remove a block that jumps to itself - used for while (true){} */
+ noway_assert(block->bbJumpDest != block);
+
+ /* Empty GOTO can be removed iff bPrev is BBJ_NONE */
+ noway_assert(bPrev && bPrev->bbJumpKind == BBJ_NONE);
+ break;
+
+ default:
+ noway_assert(!"Empty block of this type cannot be removed!");
+ break;
+ }
+#endif // DEBUG
+
+ noway_assert(block->bbJumpKind == BBJ_NONE || block->bbJumpKind == BBJ_ALWAYS);
+
+ /* Who is the "real" successor of this block? */
+
+ BasicBlock* succBlock;
+
+ if (block->bbJumpKind == BBJ_ALWAYS)
+ {
+ succBlock = block->bbJumpDest;
+ }
+ else
+ {
+ succBlock = block->bbNext;
+ }
+
+ bool skipUnmarkLoop = false;
+
+ // If block is the backedge for a loop and succBlock precedes block
+ // then the succBlock becomes the new LOOP HEAD
+ // NOTE: there's an assumption here that the blocks are numbered in increasing bbNext order.
+ // NOTE 2: if fgDomsComputed is false, then we can't check reachability. However, if this is
+ // the case, then the loop structures probably are also invalid, and shouldn't be used. This
+ // can be the case late in compilation (such as Lower), where remnants of earlier created
+ // structures exist, but haven't been maintained.
+ if (block->isLoopHead() && (succBlock->bbNum <= block->bbNum))
+ {
+ succBlock->bbFlags |= BBF_LOOP_HEAD;
+ if (fgDomsComputed && fgReachable(succBlock, block))
+ {
+ /* Mark all the reachable blocks between 'succBlock' and 'block', excluding 'block' */
+ optMarkLoopBlocks(succBlock, block, true);
+ }
+ }
+ else if (succBlock->isLoopHead() && bPrev && (succBlock->bbNum <= bPrev->bbNum))
+ {
+ skipUnmarkLoop = true;
+ }
+
+ noway_assert(succBlock);
+
+ // If this is the first Cold basic block update fgFirstColdBlock
+ if (block == fgFirstColdBlock)
+ {
+ fgFirstColdBlock = block->bbNext;
+ }
+
+#if FEATURE_EH_FUNCLETS
+ // Update fgFirstFuncletBB if necessary
+ if (block == fgFirstFuncletBB)
+ {
+ fgFirstFuncletBB = block->bbNext;
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ /* First update the loop table and bbWeights */
+ optUpdateLoopsBeforeRemoveBlock(block, skipUnmarkLoop);
+
+ /* Remove the block */
+
+ if (bPrev == nullptr)
+ {
+ /* special case if this is the first BB */
+
+ noway_assert(block == fgFirstBB);
+
+ /* Must be a fall through to next block */
+
+ noway_assert(block->bbJumpKind == BBJ_NONE);
+
+ /* old block no longer gets the extra ref count for being the first block */
+ block->bbRefs--;
+ succBlock->bbRefs++;
+
+ /* Set the new firstBB */
+ fgUnlinkBlock(block);
+
+ /* Always treat the initial block as a jump target */
+ fgFirstBB->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL;
+ }
+ else
+ {
+ fgUnlinkBlock(block);
+ }
+
+ /* mark the block as removed and set the change flag */
+
+ block->bbFlags |= BBF_REMOVED;
+
+ /* Update bbRefs and bbPreds.
+ * All blocks jumping to 'block' now jump to 'succBlock'.
+ * First, remove 'block' from the predecessor list of succBlock.
+ */
+
+ fgRemoveRefPred(succBlock, block);
+
+ for (flowList* pred = block->bbPreds; pred; pred = pred->flNext)
+ {
+ BasicBlock* predBlock = pred->flBlock;
+
+ /* Are we changing a loop backedge into a forward jump? */
+
+ if (block->isLoopHead() && (predBlock->bbNum >= block->bbNum) && (predBlock->bbNum <= succBlock->bbNum))
+ {
+ /* First update the loop table and bbWeights */
+ optUpdateLoopsBeforeRemoveBlock(predBlock);
+ }
+
+ /* If predBlock is a new predecessor, then add it to succBlock's
+ predecessor's list. */
+ if (predBlock->bbJumpKind != BBJ_SWITCH)
+ {
+ // Even if the pred is not a switch, we could have a conditional branch
+ // to the fallthrough, so duplicate there could be preds
+ for (unsigned i = 0; i < pred->flDupCount; i++)
+ {
+ fgAddRefPred(succBlock, predBlock);
+ }
+ }
+
+ /* change all jumps to the removed block */
+ switch (predBlock->bbJumpKind)
+ {
+ default:
+ noway_assert(!"Unexpected bbJumpKind in fgRemoveBlock()");
+ break;
+
+ case BBJ_NONE:
+ noway_assert(predBlock == bPrev);
+ PREFIX_ASSUME(bPrev != nullptr);
+
+ /* In the case of BBJ_ALWAYS we have to change the type of its predecessor */
+ if (block->bbJumpKind == BBJ_ALWAYS)
+ {
+ /* bPrev now becomes a BBJ_ALWAYS */
+ bPrev->bbJumpKind = BBJ_ALWAYS;
+ bPrev->bbJumpDest = succBlock;
+ }
+ break;
+
+ case BBJ_COND:
+ /* The links for the direct predecessor case have already been updated above */
+ if (predBlock->bbJumpDest != block)
+ {
+ succBlock->bbFlags |= BBF_HAS_LABEL | BBF_JMP_TARGET;
+ break;
+ }
+
+ /* Check if both side of the BBJ_COND now jump to the same block */
+ if (predBlock->bbNext == succBlock)
+ {
+ // Make sure we are replacing "block" with "succBlock" in predBlock->bbJumpDest.
+ noway_assert(predBlock->bbJumpDest == block);
+ predBlock->bbJumpDest = succBlock;
+ fgRemoveJTrue(predBlock);
+ break;
+ }
+
+ /* Fall through for the jump case */
+ __fallthrough;
+
+ case BBJ_CALLFINALLY:
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ noway_assert(predBlock->bbJumpDest == block);
+ predBlock->bbJumpDest = succBlock;
+ succBlock->bbFlags |= BBF_HAS_LABEL | BBF_JMP_TARGET;
+ break;
+
+ case BBJ_SWITCH:
+ // Change any jumps from 'predBlock' (a BBJ_SWITCH) to 'block' to jump to 'succBlock'
+ //
+ // For the jump targets of 'predBlock' (a BBJ_SWITCH) that jump to 'block'
+ // remove the old predecessor at 'block' from 'predBlock' and
+ // add the new predecessor at 'succBlock' from 'predBlock'
+ //
+ fgReplaceSwitchJumpTarget(predBlock, succBlock, block);
+ break;
+ }
+ }
+ }
+
+ if (bPrev != nullptr)
+ {
+ switch (bPrev->bbJumpKind)
+ {
+ case BBJ_CALLFINALLY:
+ // If prev is a BBJ_CALLFINALLY it better be marked as RETLESS
+ noway_assert(bPrev->bbFlags & BBF_RETLESS_CALL);
+ break;
+
+ case BBJ_ALWAYS:
+ // Check for branch to next block. Just make sure the BBJ_ALWAYS block is not
+ // part of a BBJ_CALLFINALLY/BBJ_ALWAYS pair. We do this here and don't rely on fgUpdateFlowGraph
+ // because we can be called by ComputeDominators and it expects it to remove this jump to
+ // the next block. This is the safest fix. We should remove all this BBJ_CALLFINALLY/BBJ_ALWAYS
+ // pairing.
+
+ if ((bPrev->bbJumpDest == bPrev->bbNext) &&
+ !fgInDifferentRegions(bPrev, bPrev->bbJumpDest)) // We don't remove a branch from Hot -> Cold
+ {
+ if ((bPrev == fgFirstBB) || !bPrev->bbPrev->isBBCallAlwaysPair())
+ {
+ // It's safe to change the jump type
+ bPrev->bbJumpKind = BBJ_NONE;
+ bPrev->bbFlags &= ~BBF_NEEDS_GCPOLL;
+ }
+ }
+ break;
+
+ case BBJ_COND:
+ /* Check for branch to next block */
+ if (bPrev->bbJumpDest == bPrev->bbNext)
+ {
+ fgRemoveJTrue(bPrev);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ ehUpdateForDeletedBlock(block);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Function called to connect to block that previously had a fall through
+ */
+
+BasicBlock* Compiler::fgConnectFallThrough(BasicBlock* bSrc, BasicBlock* bDst)
+{
+ BasicBlock* jmpBlk = nullptr;
+
+ /* If bSrc is non-NULL */
+
+ if (bSrc != nullptr)
+ {
+ /* If bSrc falls through to a block that is not bDst, we will insert a jump to bDst */
+
+ if (bSrc->bbFallsThrough() && (bSrc->bbNext != bDst))
+ {
+ switch (bSrc->bbJumpKind)
+ {
+
+ case BBJ_NONE:
+ bSrc->bbJumpKind = BBJ_ALWAYS;
+ bSrc->bbJumpDest = bDst;
+ bSrc->bbJumpDest->bbFlags |= (BBF_JMP_TARGET | BBF_HAS_LABEL);
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Block BB%02u ended with a BBJ_NONE, Changed to an unconditional jump to BB%02u\n",
+ bSrc->bbNum, bSrc->bbJumpDest->bbNum);
+ }
+#endif
+ break;
+
+ case BBJ_CALLFINALLY:
+ case BBJ_COND:
+
+ // Add a new block after bSrc which jumps to 'bDst'
+ jmpBlk = fgNewBBafter(BBJ_ALWAYS, bSrc, true);
+
+ if (fgComputePredsDone)
+ {
+ fgAddRefPred(jmpBlk, bSrc, fgGetPredForBlock(bDst, bSrc));
+ }
+
+ // When adding a new jmpBlk we will set the bbWeight and bbFlags
+ //
+ if (fgHaveValidEdgeWeights)
+ {
+ noway_assert(fgComputePredsDone);
+
+ flowList* newEdge = fgGetPredForBlock(jmpBlk, bSrc);
+
+ jmpBlk->bbWeight = (newEdge->flEdgeWeightMin + newEdge->flEdgeWeightMax) / 2;
+ if (bSrc->bbWeight == 0)
+ {
+ jmpBlk->bbWeight = 0;
+ }
+
+ if (jmpBlk->bbWeight == 0)
+ {
+ jmpBlk->bbFlags |= BBF_RUN_RARELY;
+ }
+
+ BasicBlock::weight_t weightDiff = (newEdge->flEdgeWeightMax - newEdge->flEdgeWeightMin);
+ BasicBlock::weight_t slop = BasicBlock::GetSlopFraction(bSrc, bDst);
+
+ //
+ // If the [min/max] values for our edge weight is within the slop factor
+ // then we will set the BBF_PROF_WEIGHT flag for the block
+ //
+ if (weightDiff <= slop)
+ {
+ jmpBlk->bbFlags |= BBF_PROF_WEIGHT;
+ }
+ }
+ else
+ {
+ // We set the bbWeight to the smaller of bSrc->bbWeight or bDst->bbWeight
+ if (bSrc->bbWeight < bDst->bbWeight)
+ {
+ jmpBlk->bbWeight = bSrc->bbWeight;
+ jmpBlk->bbFlags |= (bSrc->bbFlags & BBF_RUN_RARELY);
+ }
+ else
+ {
+ jmpBlk->bbWeight = bDst->bbWeight;
+ jmpBlk->bbFlags |= (bDst->bbFlags & BBF_RUN_RARELY);
+ }
+ }
+
+ jmpBlk->bbJumpDest = bDst;
+ jmpBlk->bbJumpDest->bbFlags |= (BBF_JMP_TARGET | BBF_HAS_LABEL);
+
+ if (fgComputePredsDone)
+ {
+ fgReplacePred(bDst, bSrc, jmpBlk);
+ }
+ else
+ {
+ jmpBlk->bbFlags |= BBF_IMPORTED;
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Added an unconditional jump to BB%02u after block BB%02u\n", jmpBlk->bbJumpDest->bbNum,
+ bSrc->bbNum);
+ }
+#endif // DEBUG
+ break;
+
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+ }
+ else
+ {
+ // If bSrc is an unconditional branch to the next block
+ // then change it to a BBJ_NONE block
+ //
+ if ((bSrc->bbJumpKind == BBJ_ALWAYS) && !(bSrc->bbFlags & BBF_KEEP_BBJ_ALWAYS) &&
+ (bSrc->bbJumpDest == bSrc->bbNext))
+ {
+ bSrc->bbJumpKind = BBJ_NONE;
+ bSrc->bbFlags &= ~BBF_NEEDS_GCPOLL;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Changed an unconditional jump from BB%02u to the next block BB%02u into a BBJ_NONE block\n",
+ bSrc->bbNum, bSrc->bbNext->bbNum);
+ }
+#endif // DEBUG
+ }
+ }
+ }
+
+ return jmpBlk;
+}
+
+/*****************************************************************************
+ Walk the flow graph, reassign block numbers to keep them in ascending order.
+ Returns 'true' if any renumbering was actually done, OR if we change the
+ maximum number of assigned basic blocks (this can happen if we do inlining,
+ create a new, high-numbered block, then that block goes away. We go to
+ renumber the blocks, none of them actually change number, but we shrink the
+ maximum assigned block number. This affects the block set epoch).
+*/
+
+bool Compiler::fgRenumberBlocks()
+{
+ // If we renumber the blocks the dominator information will be out-of-date
+ if (fgDomsComputed)
+ {
+ noway_assert(!"Can't call Compiler::fgRenumberBlocks() when fgDomsComputed==true");
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** Before renumbering the basic blocks\n");
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ }
+#endif // DEBUG
+
+ bool renumbered = false;
+ bool newMaxBBNum = false;
+ BasicBlock* block;
+
+ unsigned numStart = 1 + (compIsForInlining() ? impInlineInfo->InlinerCompiler->fgBBNumMax : 0);
+ unsigned num;
+
+ for (block = fgFirstBB, num = numStart; block != nullptr; block = block->bbNext, num++)
+ {
+ noway_assert((block->bbFlags & BBF_REMOVED) == 0);
+
+ if (block->bbNum != num)
+ {
+ renumbered = true;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Renumber BB%02u to BB%02u\n", block->bbNum, num);
+ }
+#endif // DEBUG
+ block->bbNum = num;
+ }
+
+ if (block->bbNext == nullptr)
+ {
+ fgLastBB = block;
+ fgBBcount = num - numStart + 1;
+ if (compIsForInlining())
+ {
+ if (impInlineInfo->InlinerCompiler->fgBBNumMax != num)
+ {
+ impInlineInfo->InlinerCompiler->fgBBNumMax = num;
+ newMaxBBNum = true;
+ }
+ }
+ else
+ {
+ if (fgBBNumMax != num)
+ {
+ fgBBNumMax = num;
+ newMaxBBNum = true;
+ }
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** After renumbering the basic blocks\n");
+ if (renumbered)
+ {
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ }
+ else
+ {
+ printf("=============== No blocks renumbered!\n");
+ }
+ }
+#endif // DEBUG
+
+ // Now update the BlockSet epoch, which depends on the block numbers.
+ // If any blocks have been renumbered then create a new BlockSet epoch.
+ // Even if we have not renumbered any blocks, we might still need to force
+ // a new BlockSet epoch, for one of several reasons. If there are any new
+ // blocks with higher numbers than the former maximum numbered block, then we
+ // need a new epoch with a new size matching the new largest numbered block.
+ // Also, if the number of blocks is different from the last time we set the
+ // BlockSet epoch, then we need a new epoch. This wouldn't happen if we
+ // renumbered blocks after every block addition/deletion, but it might be
+ // the case that we can change the number of blocks, then set the BlockSet
+ // epoch without renumbering, then change the number of blocks again, then
+ // renumber.
+ if (renumbered || newMaxBBNum)
+ {
+ NewBasicBlockEpoch();
+
+ // The key in the unique switch successor map is dependent on the block number, so invalidate that cache.
+ InvalidateUniqueSwitchSuccMap();
+ }
+ else
+ {
+ EnsureBasicBlockEpoch();
+ }
+
+ // Tell our caller if any blocks actually were renumbered.
+ return renumbered || newMaxBBNum;
+}
+
+/*****************************************************************************
+ *
+ * Is the BasicBlock bJump a forward branch?
+ * Optionally bSrc can be supplied to indicate that
+ * bJump must be forward with respect to bSrc
+ */
+bool Compiler::fgIsForwardBranch(BasicBlock* bJump, BasicBlock* bSrc /* = NULL */)
+{
+ bool result = false;
+
+ if ((bJump->bbJumpKind == BBJ_COND) || (bJump->bbJumpKind == BBJ_ALWAYS))
+ {
+ BasicBlock* bDest = bJump->bbJumpDest;
+ BasicBlock* bTemp = (bSrc == nullptr) ? bJump : bSrc;
+
+ while (true)
+ {
+ bTemp = bTemp->bbNext;
+
+ if (bTemp == nullptr)
+ {
+ break;
+ }
+
+ if (bTemp == bDest)
+ {
+ result = true;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*****************************************************************************
+ *
+ * Function called to expand the set of rarely run blocks
+ */
+
+bool Compiler::fgExpandRarelyRunBlocks()
+{
+ bool result = false;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** In fgExpandRarelyRunBlocks()\n");
+ }
+
+ const char* reason = nullptr;
+#endif
+
+ // We expand the number of rarely run blocks by observing
+ // that a block that falls into or jumps to a rarely run block,
+ // must itself be rarely run and when we have a conditional
+ // jump in which both branches go to rarely run blocks then
+ // the block must itself be rarely run
+
+ BasicBlock* block;
+ BasicBlock* bPrev;
+
+ for (bPrev = fgFirstBB, block = bPrev->bbNext; block != nullptr; bPrev = block, block = block->bbNext)
+ {
+ if (bPrev->isRunRarely())
+ {
+ continue;
+ }
+
+ /* bPrev is known to be a normal block here */
+ switch (bPrev->bbJumpKind)
+ {
+ case BBJ_ALWAYS:
+
+ /* Is the jump target rarely run? */
+ if (bPrev->bbJumpDest->isRunRarely())
+ {
+ INDEBUG(reason = "Unconditional jump to a rarely run block";)
+ goto NEW_RARELY_RUN;
+ }
+ break;
+
+ case BBJ_CALLFINALLY:
+
+ // Check for a BBJ_CALLFINALLY followed by a rarely run paired BBJ_ALWAYS
+ //
+ if (bPrev->isBBCallAlwaysPair())
+ {
+ /* Is the next block rarely run? */
+ if (block->isRunRarely())
+ {
+ INDEBUG(reason = "Call of finally followed by a rarely run block";)
+ goto NEW_RARELY_RUN;
+ }
+ }
+ break;
+
+ case BBJ_NONE:
+
+ /* is fall through target rarely run? */
+ if (block->isRunRarely())
+ {
+ INDEBUG(reason = "Falling into a rarely run block";)
+ goto NEW_RARELY_RUN;
+ }
+ break;
+
+ case BBJ_COND:
+
+ if (!block->isRunRarely())
+ {
+ continue;
+ }
+
+ /* If both targets of the BBJ_COND are run rarely then don't reorder */
+ if (bPrev->bbJumpDest->isRunRarely())
+ {
+ /* bPrev should also be marked as run rarely */
+ if (!bPrev->isRunRarely())
+ {
+ INDEBUG(reason = "Both sides of a conditional jump are rarely run";)
+
+ NEW_RARELY_RUN:
+ /* If the weight of the block was obtained from a profile run,
+ than it's more accurate than our static analysis */
+ if (bPrev->bbFlags & BBF_PROF_WEIGHT)
+ {
+ continue;
+ }
+ result = true;
+
+#ifdef DEBUG
+ assert(reason != nullptr);
+ if (verbose)
+ {
+ printf("%s, marking BB%02u as rarely run\n", reason, bPrev->bbNum);
+ }
+#endif // DEBUG
+
+ /* Must not have previously been marked */
+ noway_assert(!bPrev->isRunRarely());
+
+ /* Mark bPrev as a new rarely run block */
+ bPrev->bbSetRunRarely();
+
+ BasicBlock* bPrevPrev = nullptr;
+ BasicBlock* tmpbb;
+
+ if ((bPrev->bbFlags & BBF_KEEP_BBJ_ALWAYS) != 0)
+ {
+ // If we've got a BBJ_CALLFINALLY/BBJ_ALWAYS pair, treat the BBJ_CALLFINALLY as an
+ // additional predecessor for the BBJ_ALWAYS block
+ tmpbb = bPrev->bbPrev;
+ noway_assert(tmpbb != nullptr);
+#if FEATURE_EH_FUNCLETS
+ noway_assert(tmpbb->isBBCallAlwaysPair());
+ bPrevPrev = tmpbb;
+#else
+ if (tmpbb->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ bPrevPrev = tmpbb;
+ }
+#endif
+ }
+
+ /* Now go back to it's earliest predecessor to see */
+ /* if it too should now be marked as rarely run */
+ flowList* pred = bPrev->bbPreds;
+
+ if ((pred != nullptr) || (bPrevPrev != nullptr))
+ {
+ // bPrevPrev will be set to the lexically
+ // earliest predecessor of bPrev.
+
+ while (pred != nullptr)
+ {
+ if (bPrevPrev == nullptr)
+ {
+ // Initially we select the first block in the bbPreds list
+ bPrevPrev = pred->flBlock;
+ continue;
+ }
+
+ // Walk the flow graph lexically forward from pred->flBlock
+ // if we find (block == bPrevPrev) then
+ // pred->flBlock is an earlier predecessor.
+ for (tmpbb = pred->flBlock; tmpbb != nullptr; tmpbb = tmpbb->bbNext)
+ {
+ if (tmpbb == bPrevPrev)
+ {
+ /* We found an ealier predecessor */
+ bPrevPrev = pred->flBlock;
+ break;
+ }
+ else if (tmpbb == bPrev)
+ {
+ // We have reached bPrev so stop walking
+ // as this cannot be an earlier predecessor
+ break;
+ }
+ }
+
+ // Onto the next predecessor
+ pred = pred->flNext;
+ }
+
+ // Walk the flow graph forward from bPrevPrev
+ // if we don't find (tmpbb == bPrev) then our candidate
+ // bPrevPrev is lexically after bPrev and we do not
+ // want to select it as our new block
+
+ for (tmpbb = bPrevPrev; tmpbb != nullptr; tmpbb = tmpbb->bbNext)
+ {
+ if (tmpbb == bPrev)
+ {
+ // Set up block back to the lexically
+ // earliest predecessor of pPrev
+
+ block = bPrevPrev;
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ // Now iterate over every block to see if we can prove that a block is rarely run
+ // (i.e. when all predecessors to the block are rarely run)
+ //
+ for (bPrev = fgFirstBB, block = bPrev->bbNext; block != nullptr; bPrev = block, block = block->bbNext)
+ {
+ // If block is not run rarely, then check to make sure that it has
+ // at least one non-rarely run block.
+
+ if (!block->isRunRarely())
+ {
+ bool rare = true;
+
+ /* Make sure that block has at least one normal predecessor */
+ for (flowList* pred = block->bbPreds; pred != nullptr; pred = pred->flNext)
+ {
+ /* Find the fall through predecessor, if any */
+ if (!pred->flBlock->isRunRarely())
+ {
+ rare = false;
+ break;
+ }
+ }
+
+ if (rare)
+ {
+ // If 'block' is the start of a handler or filter then we cannot make it
+ // rarely run because we may have an exceptional edge that
+ // branches here.
+ //
+ if (bbIsHandlerBeg(block))
+ {
+ rare = false;
+ }
+ }
+
+ if (rare)
+ {
+ block->bbSetRunRarely();
+ result = true;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("All branches to BB%02u are from rarely run blocks, marking as rarely run\n", block->bbNum);
+ }
+#endif // DEBUG
+
+ // When marking a BBJ_CALLFINALLY as rarely run we also mark
+ // the BBJ_ALWAYS that comes after it as rarely run
+ //
+ if (block->isBBCallAlwaysPair())
+ {
+ BasicBlock* bNext = block->bbNext;
+ PREFIX_ASSUME(bNext != nullptr);
+ bNext->bbSetRunRarely();
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Also marking the BBJ_ALWAYS at BB%02u as rarely run\n", bNext->bbNum);
+ }
+#endif // DEBUG
+ }
+ }
+ }
+
+ /* COMPACT blocks if possible */
+ if (bPrev->bbJumpKind == BBJ_NONE)
+ {
+ if (fgCanCompactBlocks(bPrev, block))
+ {
+ fgCompactBlocks(bPrev, block);
+
+ block = bPrev;
+ continue;
+ }
+ }
+ //
+ // if bPrev->bbWeight is not based upon profile data we can adjust
+ // the weights of bPrev and block
+ //
+ else if (bPrev->isBBCallAlwaysPair() && // we must have a BBJ_CALLFINALLY and BBK_ALWAYS pair
+ (bPrev->bbWeight != block->bbWeight) && // the weights are currently different
+ ((bPrev->bbFlags & BBF_PROF_WEIGHT) == 0)) // and the BBJ_CALLFINALLY block is not using profiled
+ // weights
+ {
+ if (block->isRunRarely())
+ {
+ bPrev->bbWeight =
+ block->bbWeight; // the BBJ_CALLFINALLY block now has the same weight as the BBJ_ALWAYS block
+ bPrev->bbFlags |= BBF_RUN_RARELY; // and is now rarely run
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Marking the BBJ_CALLFINALLY block at BB%02u as rarely run because BB%02u is rarely run\n",
+ bPrev->bbNum, block->bbNum);
+ }
+#endif // DEBUG
+ }
+ else if (bPrev->isRunRarely())
+ {
+ block->bbWeight =
+ bPrev->bbWeight; // the BBJ_ALWAYS block now has the same weight as the BBJ_CALLFINALLY block
+ block->bbFlags |= BBF_RUN_RARELY; // and is now rarely run
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Marking the BBJ_ALWAYS block at BB%02u as rarely run because BB%02u is rarely run\n",
+ block->bbNum, bPrev->bbNum);
+ }
+#endif // DEBUG
+ }
+ else // Both blocks are hot, bPrev is known not to be using profiled weight
+ {
+ bPrev->bbWeight =
+ block->bbWeight; // the BBJ_CALLFINALLY block now has the same weight as the BBJ_ALWAYS block
+ }
+ noway_assert(block->bbWeight == bPrev->bbWeight);
+ }
+ }
+
+ return result;
+}
+
+/*****************************************************************************
+ *
+ * Returns true if it is allowable (based upon the EH regions)
+ * to place block bAfter immediately after bBefore. It is allowable
+ * if the 'bBefore' and 'bAfter' blocks are in the exact same EH region.
+ */
+
+bool Compiler::fgEhAllowsMoveBlock(BasicBlock* bBefore, BasicBlock* bAfter)
+{
+ return BasicBlock::sameEHRegion(bBefore, bAfter);
+}
+
+/*****************************************************************************
+ *
+ * Function called to move the range of blocks [bStart .. bEnd].
+ * The blocks are placed immediately after the insertAfterBlk.
+ * fgFirstFuncletBB is not updated; that is the responsibility of the caller, if necessary.
+ */
+
+void Compiler::fgMoveBlocksAfter(BasicBlock* bStart, BasicBlock* bEnd, BasicBlock* insertAfterBlk)
+{
+ /* We have decided to insert the block(s) after 'insertAfterBlk' */
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Relocated block%s [BB%02u..BB%02u] inserted after BB%02u%s\n", (bStart == bEnd) ? "" : "s",
+ bStart->bbNum, bEnd->bbNum, insertAfterBlk->bbNum,
+ (insertAfterBlk->bbNext == nullptr) ? " at the end of method" : "");
+ }
+#endif // DEBUG
+
+ /* relink [bStart .. bEnd] into the flow graph */
+
+ bEnd->bbNext = insertAfterBlk->bbNext;
+ if (insertAfterBlk->bbNext)
+ {
+ insertAfterBlk->bbNext->bbPrev = bEnd;
+ }
+ insertAfterBlk->setNext(bStart);
+
+ /* If insertAfterBlk was fgLastBB then update fgLastBB */
+ if (insertAfterBlk == fgLastBB)
+ {
+ fgLastBB = bEnd;
+ noway_assert(fgLastBB->bbNext == nullptr);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Function called to relocate a single range to the end of the method.
+ * Only an entire consecutive region can be moved and it will be kept together.
+ * Except for the first block, the range cannot have any blocks that jump into or out of the region.
+ * When successful we return the bLast block which is the last block that we relocated.
+ * When unsuccessful we return NULL.
+
+ =============================================================
+ NOTE: This function can invalidate all pointers into the EH table, as well as change the size of the EH table!
+ =============================================================
+ */
+
+BasicBlock* Compiler::fgRelocateEHRange(unsigned regionIndex, FG_RELOCATE_TYPE relocateType)
+{
+ INDEBUG(const char* reason = "None";)
+
+ // Figure out the range of blocks we're going to move
+
+ unsigned XTnum;
+ EHblkDsc* HBtab;
+ BasicBlock* bStart = nullptr;
+ BasicBlock* bMiddle = nullptr;
+ BasicBlock* bLast = nullptr;
+ BasicBlock* bPrev = nullptr;
+
+#if FEATURE_EH_FUNCLETS
+ // We don't support moving try regions... yet?
+ noway_assert(relocateType == FG_RELOCATE_HANDLER);
+#endif // FEATURE_EH_FUNCLETS
+
+ HBtab = ehGetDsc(regionIndex);
+
+ if (relocateType == FG_RELOCATE_TRY)
+ {
+ bStart = HBtab->ebdTryBeg;
+ bLast = HBtab->ebdTryLast;
+ }
+ else if (relocateType == FG_RELOCATE_HANDLER)
+ {
+ if (HBtab->HasFilter())
+ {
+ // The filter and handler funclets must be moved together, and remain contiguous.
+ bStart = HBtab->ebdFilter;
+ bMiddle = HBtab->ebdHndBeg;
+ bLast = HBtab->ebdHndLast;
+ }
+ else
+ {
+ bStart = HBtab->ebdHndBeg;
+ bLast = HBtab->ebdHndLast;
+ }
+ }
+
+ // Our range must contain either all rarely run blocks or all non-rarely run blocks
+ bool inTheRange = false;
+ bool validRange = false;
+
+ BasicBlock* block;
+
+ noway_assert(bStart != nullptr && bLast != nullptr);
+ if (bStart == fgFirstBB)
+ {
+ INDEBUG(reason = "can not relocate first block";)
+ goto FAILURE;
+ }
+
+#if !FEATURE_EH_FUNCLETS
+ // In the funclets case, we still need to set some information on the handler blocks
+ if (bLast->bbNext == NULL)
+ {
+ INDEBUG(reason = "region is already at the end of the method";)
+ goto FAILURE;
+ }
+#endif // !FEATURE_EH_FUNCLETS
+
+ // Walk the block list for this purpose:
+ // 1. Verify that all the blocks in the range are either all rarely run or not rarely run.
+ // When creating funclets, we ignore the run rarely flag, as we need to be able to move any blocks
+ // in the range.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#if !FEATURE_EH_FUNCLETS
+ bool isRare;
+ isRare = bStart->isRunRarely();
+#endif // !FEATURE_EH_FUNCLETS
+ block = fgFirstBB;
+ while (true)
+ {
+ if (block == bStart)
+ {
+ noway_assert(inTheRange == false);
+ inTheRange = true;
+ }
+ else if (block == bLast->bbNext)
+ {
+ noway_assert(inTheRange == true);
+ inTheRange = false;
+ break; // we found the end, so we're done
+ }
+
+ if (inTheRange)
+ {
+#if !FEATURE_EH_FUNCLETS
+ // Unless all blocks are (not) run rarely we must return false.
+ if (isRare != block->isRunRarely())
+ {
+ INDEBUG(reason = "this region contains both rarely run and non-rarely run blocks";)
+ goto FAILURE;
+ }
+#endif // !FEATURE_EH_FUNCLETS
+
+ validRange = true;
+ }
+
+ if (block == nullptr)
+ {
+ break;
+ }
+
+ block = block->bbNext;
+ }
+ // Ensure that bStart .. bLast defined a valid range
+ noway_assert((validRange == true) && (inTheRange == false));
+
+ bPrev = bStart->bbPrev;
+ noway_assert(bPrev != nullptr); // Can't move a range that includes the first block of the function.
+
+ JITDUMP("Relocating %s range BB%02u..BB%02u (EH#%u) to end of BBlist\n",
+ (relocateType == FG_RELOCATE_TRY) ? "try" : "handler", bStart->bbNum, bLast->bbNum, regionIndex);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ }
+
+ if (!FEATURE_EH_FUNCLETS)
+ {
+ // This is really expensive, and quickly becomes O(n^n) with funclets
+ // so only do it once after we've created them (see fgCreateFunclets)
+ if (expensiveDebugCheckLevel >= 2)
+ {
+ fgDebugCheckBBlist();
+ }
+ }
+#endif // DEBUG
+
+#if FEATURE_EH_FUNCLETS
+
+ bStart->bbFlags |= BBF_FUNCLET_BEG; // Mark the start block of the funclet
+
+ if (bMiddle != nullptr)
+ {
+ bMiddle->bbFlags |= BBF_FUNCLET_BEG; // Also mark the start block of a filter handler as a funclet
+ }
+
+#endif // FEATURE_EH_FUNCLETS
+
+ BasicBlock* bNext;
+ bNext = bLast->bbNext;
+
+ /* Temporarily unlink [bStart .. bLast] from the flow graph */
+ fgUnlinkRange(bStart, bLast);
+
+ BasicBlock* insertAfterBlk;
+ insertAfterBlk = fgLastBB;
+
+#if FEATURE_EH_FUNCLETS
+
+ // There are several cases we need to consider when moving an EH range.
+ // If moving a range X, we must consider its relationship to every other EH
+ // range A in the table. Note that each entry in the table represents both
+ // a protected region and a handler region (possibly including a filter region
+ // that must live before and adjacent to the handler region), so we must
+ // consider try and handler regions independently. These are the cases:
+ // 1. A is completely contained within X (where "completely contained" means
+ // that the 'begin' and 'last' parts of A are strictly between the 'begin'
+ // and 'end' parts of X, and aren't equal to either, for example, they don't
+ // share 'last' blocks). In this case, when we move X, A moves with it, and
+ // the EH table doesn't need to change.
+ // 2. X is completely contained within A. In this case, X gets extracted from A,
+ // and the range of A shrinks, but because A is strictly within X, the EH
+ // table doesn't need to change.
+ // 3. A and X have exactly the same range. In this case, A is moving with X and
+ // the EH table doesn't need to change.
+ // 4. A and X share the 'last' block. There are two sub-cases:
+ // (a) A is a larger range than X (such that the beginning of A precedes the
+ // beginning of X): in this case, we are moving the tail of A. We set the
+ // 'last' block of A to the the block preceding the beginning block of X.
+ // (b) A is a smaller range than X. Thus, we are moving the entirety of A along
+ // with X. In this case, nothing in the EH record for A needs to change.
+ // 5. A and X share the 'beginning' block (but aren't the same range, as in #3).
+ // This can never happen here, because we are only moving handler ranges (we don't
+ // move try ranges), and handler regions cannot start at the beginning of a try
+ // range or handler range and be a subset.
+ //
+ // Note that A and X must properly nest for the table to be well-formed. For example,
+ // the beginning of A can't be strictly within the range of X (that is, the beginning
+ // of A isn't shared with the beginning of X) and the end of A outside the range.
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ if (XTnum != regionIndex) // we don't need to update our 'last' pointer
+ {
+ if (HBtab->ebdTryLast == bLast)
+ {
+ // If we moved a set of blocks that were at the end of
+ // a different try region then we may need to update ebdTryLast
+ for (block = HBtab->ebdTryBeg; block != nullptr; block = block->bbNext)
+ {
+ if (block == bPrev)
+ {
+ // We were contained within it, so shrink its region by
+ // setting its 'last'
+ fgSetTryEnd(HBtab, bPrev);
+ break;
+ }
+ else if (block == HBtab->ebdTryLast->bbNext)
+ {
+ // bPrev does not come after the TryBeg, thus we are larger, and
+ // it is moving with us.
+ break;
+ }
+ }
+ }
+ if (HBtab->ebdHndLast == bLast)
+ {
+ // If we moved a set of blocks that were at the end of
+ // a different handler region then we must update ebdHndLast
+ for (block = HBtab->ebdHndBeg; block != nullptr; block = block->bbNext)
+ {
+ if (block == bPrev)
+ {
+ fgSetHndEnd(HBtab, bPrev);
+ break;
+ }
+ else if (block == HBtab->ebdHndLast->bbNext)
+ {
+ // bPrev does not come after the HndBeg
+ break;
+ }
+ }
+ }
+ }
+ } // end exception table iteration
+
+ // Insert the block(s) we are moving after fgLastBlock
+ fgMoveBlocksAfter(bStart, bLast, insertAfterBlk);
+
+ if (fgFirstFuncletBB == nullptr) // The funclet region isn't set yet
+ {
+ fgFirstFuncletBB = bStart;
+ }
+ else
+ {
+ assert(fgFirstFuncletBB !=
+ insertAfterBlk->bbNext); // We insert at the end, not at the beginning, of the funclet region.
+ }
+
+ // These asserts assume we aren't moving try regions (which we might need to do). Only
+ // try regions can have fall through into or out of the region.
+
+ noway_assert(!bPrev->bbFallsThrough()); // There can be no fall through into a filter or handler region
+ noway_assert(!bLast->bbFallsThrough()); // There can be no fall through out of a handler region
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Create funclets: moved region\n");
+ fgDispHandlerTab();
+ }
+
+ // We have to wait to do this until we've created all the additional regions
+ // Because this relies on ebdEnclosingTryIndex and ebdEnclosingHndIndex
+ if (!FEATURE_EH_FUNCLETS)
+ {
+ // This is really expensive, and quickly becomes O(n^n) with funclets
+ // so only do it once after we've created them (see fgCreateFunclets)
+ if (expensiveDebugCheckLevel >= 2)
+ {
+ fgDebugCheckBBlist();
+ }
+ }
+#endif // DEBUG
+
+#else // FEATURE_EH_FUNCLETS
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ if (XTnum == regionIndex)
+ {
+ // Don't update our handler's Last info
+ continue;
+ }
+
+ if (HBtab->ebdTryLast == bLast)
+ {
+ // If we moved a set of blocks that were at the end of
+ // a different try region then we may need to update ebdTryLast
+ for (block = HBtab->ebdTryBeg; block != NULL; block = block->bbNext)
+ {
+ if (block == bPrev)
+ {
+ fgSetTryEnd(HBtab, bPrev);
+ break;
+ }
+ else if (block == HBtab->ebdTryLast->bbNext)
+ {
+ // bPrev does not come after the TryBeg
+ break;
+ }
+ }
+ }
+ if (HBtab->ebdHndLast == bLast)
+ {
+ // If we moved a set of blocks that were at the end of
+ // a different handler region then we must update ebdHndLast
+ for (block = HBtab->ebdHndBeg; block != NULL; block = block->bbNext)
+ {
+ if (block == bPrev)
+ {
+ fgSetHndEnd(HBtab, bPrev);
+ break;
+ }
+ else if (block == HBtab->ebdHndLast->bbNext)
+ {
+ // bPrev does not come after the HndBeg
+ break;
+ }
+ }
+ }
+ } // end exception table iteration
+
+ // We have decided to insert the block(s) after fgLastBlock
+ fgMoveBlocksAfter(bStart, bLast, insertAfterBlk);
+
+ // If bPrev falls through, we will insert a jump to block
+ fgConnectFallThrough(bPrev, bStart);
+
+ // If bLast falls through, we will insert a jump to bNext
+ fgConnectFallThrough(bLast, bNext);
+
+#endif // FEATURE_EH_FUNCLETS
+
+ goto DONE;
+
+FAILURE:
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** Failed fgRelocateEHRange(BB%02u..BB%02u) because %s\n", bStart->bbNum, bLast->bbNum,
+ reason);
+ }
+#endif // DEBUG
+
+ bLast = nullptr;
+
+DONE:
+
+ return bLast;
+}
+
+#if FEATURE_EH_FUNCLETS
+
+#if defined(_TARGET_ARM_)
+
+/*****************************************************************************
+ * We just removed a BBJ_CALLFINALLY/BBJ_ALWAYS pair. If this was the only such pair
+ * targeting the BBJ_ALWAYS target, then we need to clear the BBF_FINALLY_TARGET bit
+ * so that target can also be removed. 'block' is the finally target. Since we just
+ * removed the BBJ_ALWAYS, it better have the BBF_FINALLY_TARGET bit set.
+ */
+
+void Compiler::fgClearFinallyTargetBit(BasicBlock* block)
+{
+ assert((block->bbFlags & BBF_FINALLY_TARGET) != 0);
+
+ for (flowList* pred = block->bbPreds; pred; pred = pred->flNext)
+ {
+ if (pred->flBlock->bbJumpKind == BBJ_ALWAYS && pred->flBlock->bbJumpDest == block)
+ {
+ BasicBlock* pPrev = pred->flBlock->bbPrev;
+ if (pPrev != NULL)
+ {
+ if (pPrev->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ // We found a BBJ_CALLFINALLY / BBJ_ALWAYS that still points to this finally target
+ return;
+ }
+ }
+ }
+ }
+
+ // Didn't find any BBJ_CALLFINALLY / BBJ_ALWAYS that still points here, so clear the bit
+
+ block->bbFlags &= ~BBF_FINALLY_TARGET;
+}
+
+#endif // defined(_TARGET_ARM_)
+
+/*****************************************************************************
+ * Is this an intra-handler control flow edge?
+ *
+ * 'block' is the head block of a funclet/handler region, or .
+ * 'predBlock' is a predecessor block of 'block' in the predecessor list.
+ *
+ * 'predBlock' can legally only be one of three things:
+ * 1. in the same handler region (e.g., the source of a back-edge of a loop from
+ * 'predBlock' to 'block'), including in nested regions within the handler,
+ * 2. if 'block' begins a handler that is a filter-handler, 'predBlock' must be in the 'filter' region,
+ * 3. for other handlers, 'predBlock' must be in the 'try' region corresponding to handler (or any
+ * region nested in the 'try' region).
+ *
+ * Note that on AMD64/ARM64, the BBJ_CALLFINALLY block that calls a finally handler is not
+ * within the corresponding 'try' region: it is placed in the corresponding 'try' region's
+ * parent (which might be the main function body). This is how it is represented to the VM
+ * (with a special "cloned finally" EH table entry).
+ *
+ * Return 'true' for case #1, and 'false' otherwise.
+ */
+bool Compiler::fgIsIntraHandlerPred(BasicBlock* predBlock, BasicBlock* block)
+{
+ // Some simple preconditions (as stated above)
+ assert(!fgFuncletsCreated);
+ assert(fgGetPredForBlock(block, predBlock) != nullptr);
+ assert(block->hasHndIndex());
+
+ EHblkDsc* xtab = ehGetDsc(block->getHndIndex());
+
+#if FEATURE_EH_CALLFINALLY_THUNKS
+ if (xtab->HasFinallyHandler())
+ {
+ assert((xtab->ebdHndBeg == block) || // The normal case
+ ((xtab->ebdHndBeg->bbNext == block) &&
+ (xtab->ebdHndBeg->bbFlags & BBF_INTERNAL))); // After we've already inserted a header block, and we're
+ // trying to decide how to split up the predecessor edges.
+ if (predBlock->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ assert(predBlock->bbJumpDest == block);
+
+ // A BBJ_CALLFINALLY predecessor of the handler can only come from the corresponding try,
+ // not from any EH clauses nested in this handler. However, we represent the BBJ_CALLFINALLY
+ // as being in the 'try' region's parent EH region, which might be the main function body.
+
+ unsigned tryIndex = xtab->ebdEnclosingTryIndex;
+ if (tryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
+ {
+ assert(!predBlock->hasTryIndex());
+ }
+ else
+ {
+ assert(predBlock->hasTryIndex());
+ assert(tryIndex == predBlock->getTryIndex());
+ assert(ehGetDsc(tryIndex)->InTryRegionBBRange(predBlock));
+ }
+ return false;
+ }
+ }
+#endif // FEATURE_EH_CALLFINALLY_THUNKS
+
+ assert(predBlock->hasHndIndex() || predBlock->hasTryIndex());
+
+ // We could search the try region looking for predBlock by using bbInTryRegions
+ // but that does a lexical search for the block, and then assumes funclets
+ // have been created and does a lexical search of all funclets that were pulled
+ // out of the parent try region.
+ // First, funclets haven't been created yet, and even if they had been, we shouldn't
+ // have any funclet directly branching to another funclet (they have to return first).
+ // So we can safely use CheckIsTryRegion instead of bbInTryRegions.
+ // Second, I believe the depth of any EH graph will on average be smaller than the
+ // breadth of the blocks within a try body. Thus it is faster to get our answer by
+ // looping outward over the region graph. However, I have added asserts, as a
+ // precaution, to ensure both algorithms agree. The asserts also check that the only
+ // way to reach the head of a funclet is from the corresponding try body or from
+ // within the funclet (and *not* any nested funclets).
+
+ if (predBlock->hasTryIndex())
+ {
+ // Because the EH clauses are listed inside-out, any nested trys will be at a
+ // lower index than the current try and if there's no enclosing try, tryIndex
+ // will terminate at NO_ENCLOSING_INDEX
+
+ unsigned tryIndex = predBlock->getTryIndex();
+ while (tryIndex < block->getHndIndex())
+ {
+ tryIndex = ehGetEnclosingTryIndex(tryIndex);
+ }
+ // tryIndex should enclose predBlock
+ assert((tryIndex == EHblkDsc::NO_ENCLOSING_INDEX) || ehGetDsc(tryIndex)->InTryRegionBBRange(predBlock));
+
+ // At this point tryIndex is either block's handler's corresponding try body
+ // or some outer try region that contains both predBlock & block or
+ // NO_ENCLOSING_REGION (because there was no try body that encloses both).
+ if (tryIndex == block->getHndIndex())
+ {
+ assert(xtab->InTryRegionBBRange(predBlock));
+ assert(!xtab->InHndRegionBBRange(predBlock));
+ return false;
+ }
+ // tryIndex should enclose block (and predBlock as previously asserted)
+ assert((tryIndex == EHblkDsc::NO_ENCLOSING_INDEX) || ehGetDsc(tryIndex)->InTryRegionBBRange(block));
+ }
+ if (xtab->HasFilter())
+ {
+ // The block is a handler. Check if the pred block is from its filter. We only need to
+ // check the end filter flag, as there is only a single filter for any handler, and we
+ // already know predBlock is a predecessor of block.
+ if (predBlock->bbJumpKind == BBJ_EHFILTERRET)
+ {
+ assert(!xtab->InHndRegionBBRange(predBlock));
+ return false;
+ }
+ }
+ // It is not in our try region (or filter), so it must be within this handler (or try bodies
+ // within this handler)
+ assert(!xtab->InTryRegionBBRange(predBlock));
+ assert(xtab->InHndRegionBBRange(predBlock));
+ return true;
+}
+
+/*****************************************************************************
+ * Does this block, first block of a handler region, have any predecessor edges
+ * that are not from its corresponding try region?
+ */
+
+bool Compiler::fgAnyIntraHandlerPreds(BasicBlock* block)
+{
+ assert(block->hasHndIndex());
+ assert(fgFirstBlockOfHandler(block) == block); // this block is the first block of a handler
+
+ flowList* pred;
+
+ for (pred = block->bbPreds; pred; pred = pred->flNext)
+ {
+ BasicBlock* predBlock = pred->flBlock;
+
+ if (fgIsIntraHandlerPred(predBlock, block))
+ {
+ // We have a predecessor that is not from our try region
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*****************************************************************************
+ * Introduce a new head block of the handler for the prolog to be put in, ahead
+ * of the current handler head 'block'.
+ * Note that this code has some similarities to fgCreateLoopPreHeader().
+ */
+
+void Compiler::fgInsertFuncletPrologBlock(BasicBlock* block)
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nCreating funclet prolog header for BB%02u\n", block->bbNum);
+ }
+#endif
+
+ assert(block->hasHndIndex());
+ assert(fgFirstBlockOfHandler(block) == block); // this block is the first block of a handler
+
+ /* Allocate a new basic block */
+
+ BasicBlock* newHead = bbNewBasicBlock(BBJ_NONE);
+
+ // In fgComputePreds() we set the BBF_JMP_TARGET and BBF_HAS_LABEL for all of the handler entry points
+ //
+ newHead->bbFlags |= (BBF_INTERNAL | BBF_JMP_TARGET | BBF_HAS_LABEL);
+ newHead->inheritWeight(block);
+ newHead->bbRefs = 0;
+
+ fgInsertBBbefore(block, newHead); // insert the new block in the block list
+ fgExtendEHRegionBefore(block); // Update the EH table to make the prolog block the first block in the block's EH
+ // block.
+
+ // fgExtendEHRegionBefore mucks with the bbRefs without updating the pred list, which we will
+ // do below for this block. So, undo that change.
+ assert(newHead->bbRefs > 0);
+ newHead->bbRefs--;
+ block->bbRefs++;
+
+ // Distribute the pred list between newHead and block. Incoming edges coming from outside
+ // the handler go to the prolog. Edges coming from with the handler are back-edges, and
+ // go to the existing 'block'.
+
+ for (flowList* pred = block->bbPreds; pred; pred = pred->flNext)
+ {
+ BasicBlock* predBlock = pred->flBlock;
+ if (!fgIsIntraHandlerPred(predBlock, block))
+ {
+ // It's a jump from outside the handler; add it to the newHead preds list and remove
+ // it from the block preds list.
+
+ switch (predBlock->bbJumpKind)
+ {
+ case BBJ_CALLFINALLY:
+ noway_assert(predBlock->bbJumpDest == block);
+ predBlock->bbJumpDest = newHead;
+ fgRemoveRefPred(block, predBlock);
+ fgAddRefPred(newHead, predBlock);
+ break;
+
+ default:
+ // The only way into the handler is via a BBJ_CALLFINALLY (to a finally handler), or
+ // via exception handling.
+ noway_assert(false);
+ break;
+ }
+ }
+ }
+
+ assert(nullptr == fgGetPredForBlock(block, newHead));
+ fgAddRefPred(block, newHead);
+
+ assert((newHead->bbFlags & (BBF_INTERNAL | BBF_JMP_TARGET | BBF_HAS_LABEL)) ==
+ (BBF_INTERNAL | BBF_JMP_TARGET | BBF_HAS_LABEL));
+}
+
+/*****************************************************************************
+ *
+ * Every funclet will have a prolog. That prolog will be inserted as the first instructions
+ * in the first block of the funclet. If the prolog is also the head block of a loop, we
+ * would end up with the prolog instructions being executed more than once.
+ * Check for this by searching the predecessor list for loops, and create a new prolog header
+ * block when needed. We detect a loop by looking for any predecessor that isn't in the
+ * handler's try region, since the only way to get into a handler is via that try region.
+ */
+
+void Compiler::fgCreateFuncletPrologBlocks()
+{
+ noway_assert(fgComputePredsDone);
+ noway_assert(!fgDomsComputed); // this function doesn't maintain the dom sets
+ assert(!fgFuncletsCreated);
+
+ bool prologBlocksCreated = false;
+ EHblkDsc* HBtabEnd;
+ EHblkDsc* HBtab;
+
+ for (HBtab = compHndBBtab, HBtabEnd = compHndBBtab + compHndBBtabCount; HBtab < HBtabEnd; HBtab++)
+ {
+ BasicBlock* head = HBtab->ebdHndBeg;
+
+ if (fgAnyIntraHandlerPreds(head))
+ {
+ // We need to create a new block in which to place the prolog, and split the existing
+ // head block predecessor edges into those that should point to the prolog, and those
+ // that shouldn't.
+ //
+ // It's arguable that we should just always do this, and not only when we "need to",
+ // so there aren't two different code paths. However, it's unlikely to be necessary
+ // for catch handlers because they have an incoming argument (the exception object)
+ // that needs to get stored or saved, so back-arcs won't normally go to the head. It's
+ // possible when writing in IL to generate a legal loop (e.g., push an Exception object
+ // on the stack before jumping back to the catch head), but C# probably won't. This will
+ // most commonly only be needed for finallys with a do/while loop at the top of the
+ // finally.
+ //
+ // Note that we don't check filters. This might be a bug, but filters always have a filter
+ // object live on entry, so it's at least unlikely (illegal?) that a loop edge targets the
+ // filter head.
+
+ fgInsertFuncletPrologBlock(head);
+ prologBlocksCreated = true;
+ }
+ }
+
+ if (prologBlocksCreated)
+ {
+ // If we've modified the graph, reset the 'modified' flag, since the dominators haven't
+ // been computed.
+ fgModified = false;
+
+#if DEBUG
+ if (verbose)
+ {
+ JITDUMP("\nAfter fgCreateFuncletPrologBlocks()");
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ }
+
+ fgVerifyHandlerTab();
+ fgDebugCheckBBlist();
+#endif // DEBUG
+ }
+}
+
+/*****************************************************************************
+ *
+ * Function to create funclets out of all EH catch/finally/fault blocks.
+ * We only move filter and handler blocks, not try blocks.
+ */
+
+void Compiler::fgCreateFunclets()
+{
+ assert(!fgFuncletsCreated);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgCreateFunclets()\n");
+ }
+#endif
+
+ fgCreateFuncletPrologBlocks();
+
+ unsigned XTnum;
+ EHblkDsc* HBtab;
+ const unsigned int funcCnt = ehFuncletCount() + 1;
+
+ if (!FitsIn<unsigned short>(funcCnt))
+ {
+ IMPL_LIMITATION("Too many funclets");
+ }
+
+ FuncInfoDsc* funcInfo = new (this, CMK_BasicBlock) FuncInfoDsc[funcCnt];
+
+ unsigned short funcIdx;
+
+ // Setup the root FuncInfoDsc and prepare to start associating
+ // FuncInfoDsc's with their corresponding EH region
+ memset((void*)funcInfo, 0, funcCnt * sizeof(FuncInfoDsc));
+ assert(funcInfo[0].funKind == FUNC_ROOT);
+ funcIdx = 1;
+
+ // Because we iterate from the top to the bottom of the compHndBBtab array, we are iterating
+ // from most nested (innermost) to least nested (outermost) EH region. It would be reasonable
+ // to iterate in the opposite order, but the order of funclets shouldn't matter.
+ //
+ // We move every handler region to the end of the function: each handler will become a funclet.
+ //
+ // Note that fgRelocateEHRange() can add new entries to the EH table. However, they will always
+ // be added *after* the current index, so our iteration here is not invalidated.
+ // It *can* invalidate the compHndBBtab pointer itself, though, if it gets reallocated!
+
+ for (XTnum = 0; XTnum < compHndBBtabCount; XTnum++)
+ {
+ HBtab = ehGetDsc(XTnum); // must re-compute this every loop, since fgRelocateEHRange changes the table
+ if (HBtab->HasFilter())
+ {
+ assert(funcIdx < funcCnt);
+ funcInfo[funcIdx].funKind = FUNC_FILTER;
+ funcInfo[funcIdx].funEHIndex = (unsigned short)XTnum;
+ funcIdx++;
+ }
+ assert(funcIdx < funcCnt);
+ funcInfo[funcIdx].funKind = FUNC_HANDLER;
+ funcInfo[funcIdx].funEHIndex = (unsigned short)XTnum;
+ HBtab->ebdFuncIndex = funcIdx;
+ funcIdx++;
+ fgRelocateEHRange(XTnum, FG_RELOCATE_HANDLER);
+ }
+
+ // We better have populated all of them by now
+ assert(funcIdx == funcCnt);
+
+ // Publish
+ compCurrFuncIdx = 0;
+ compFuncInfos = funcInfo;
+ compFuncInfoCount = (unsigned short)funcCnt;
+
+ fgFuncletsCreated = true;
+
+#if DEBUG
+ if (verbose)
+ {
+ JITDUMP("\nAfter fgCreateFunclets()");
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ }
+
+ fgVerifyHandlerTab();
+ fgDebugCheckBBlist();
+#endif // DEBUG
+}
+
+#else // !FEATURE_EH_FUNCLETS
+
+ /*****************************************************************************
+ *
+ * Function called to relocate any and all EH regions.
+ * Only entire consecutive EH regions will be moved and they will be kept together.
+ * Except for the first block, the range can not have any blocks that jump into or out of the region.
+ */
+
+ bool Compiler::fgRelocateEHRegions()
+ {
+ bool result = false; // Our return value
+
+#ifdef DEBUG
+ if (verbose)
+ printf("*************** In fgRelocateEHRegions()\n");
+#endif
+
+ if (fgCanRelocateEHRegions)
+ {
+ unsigned XTnum;
+ EHblkDsc* HBtab;
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ // Nested EH regions cannot be moved.
+ // Also we don't want to relocate an EH region that has a filter
+ if ((HBtab->ebdHandlerNestingLevel == 0) && !HBtab->HasFilter())
+ {
+ bool movedTry = false;
+#if DEBUG
+ bool movedHnd = false;
+#endif // DEBUG
+
+ // Only try to move the outermost try region
+ if (HBtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX)
+ {
+ // Move the entire try region if it can be moved
+ if (HBtab->ebdTryBeg->isRunRarely())
+ {
+ BasicBlock* bTryLastBB = fgRelocateEHRange(XTnum, FG_RELOCATE_TRY);
+ if (bTryLastBB != NULL)
+ {
+ result = true;
+ movedTry = true;
+ }
+ }
+#if DEBUG
+ if (verbose && movedTry)
+ {
+ printf("\nAfter relocating an EH try region");
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+
+ // Make sure that the predecessor lists are accurate
+ if (expensiveDebugCheckLevel >= 2)
+ {
+ fgDebugCheckBBlist();
+ }
+ }
+#endif // DEBUG
+ }
+
+ // Currently it is not good to move the rarely run handler regions to the end of the method
+ // because fgDetermineFirstColdBlock() must put the start of any handler region in the hot section.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#if 0
+ // Now try to move the entire handler region if it can be moved.
+ // Don't try to move a finally handler unless we already moved the try region.
+ if (HBtab->ebdHndBeg->isRunRarely() &&
+ !HBtab->ebdHndBeg->hasTryIndex() &&
+ (movedTry || !HBtab->HasFinallyHandler()))
+ {
+ BasicBlock* bHndLastBB = fgRelocateEHRange(XTnum, FG_RELOCATE_HANDLER);
+ if (bHndLastBB != NULL)
+ {
+ result = true;
+ movedHnd = true;
+ }
+ }
+#endif // 0
+
+#if DEBUG
+ if (verbose && movedHnd)
+ {
+ printf("\nAfter relocating an EH handler region");
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+
+ // Make sure that the predecessor lists are accurate
+ if (expensiveDebugCheckLevel >= 2)
+ {
+ fgDebugCheckBBlist();
+ }
+ }
+#endif // DEBUG
+ }
+ }
+ }
+
+#if DEBUG
+ fgVerifyHandlerTab();
+
+ if (verbose && result)
+ {
+ printf("\nAfter fgRelocateEHRegions()");
+ fgDispBasicBlocks();
+ fgDispHandlerTab();
+ // Make sure that the predecessor lists are accurate
+ fgDebugCheckBBlist();
+ }
+#endif // DEBUG
+
+ return result;
+ }
+
+#endif // !FEATURE_EH_FUNCLETS
+
+bool flowList::setEdgeWeightMinChecked(BasicBlock::weight_t newWeight, BasicBlock::weight_t slop, bool* wbUsedSlop)
+{
+ bool result = false;
+ if ((newWeight <= flEdgeWeightMax) && (newWeight >= flEdgeWeightMin))
+ {
+ flEdgeWeightMin = newWeight;
+ result = true;
+ }
+ else if (slop > 0)
+ {
+ // We allow for a small amount of inaccuracy in block weight counts.
+ if (flEdgeWeightMax < newWeight)
+ {
+ // We have already determined that this edge's weight
+ // is less than newWeight, so we just allow for the slop
+ if (newWeight <= (flEdgeWeightMax + slop))
+ {
+ result = true;
+
+ if (flEdgeWeightMax != 0)
+ {
+ // We will raise flEdgeWeightMin and Max towards newWeight
+ flEdgeWeightMin = flEdgeWeightMax;
+ flEdgeWeightMax = newWeight;
+ }
+
+ if (wbUsedSlop != nullptr)
+ {
+ *wbUsedSlop = true;
+ }
+ }
+ }
+ else
+ {
+ assert(flEdgeWeightMin > newWeight);
+
+ // We have already determined that this edge's weight
+ // is more than newWeight, so we just allow for the slop
+ if ((newWeight + slop) >= flEdgeWeightMin)
+ {
+ result = true;
+
+ assert(flEdgeWeightMax != 0);
+
+ // We will lower flEdgeWeightMin towards newWeight
+ flEdgeWeightMin = newWeight;
+
+ if (wbUsedSlop != nullptr)
+ {
+ *wbUsedSlop = true;
+ }
+ }
+ }
+
+ // If we are returning true then we should have adjusted the range so that
+ // the newWeight is in new range [Min..Max] or fgEdjeWeightMax is zero.
+ // Also we should have set wbUsedSlop to true.
+ if (result == true)
+ {
+ assert((flEdgeWeightMax == 0) || ((newWeight <= flEdgeWeightMax) && (newWeight >= flEdgeWeightMin)));
+
+ if (wbUsedSlop != nullptr)
+ {
+ assert(*wbUsedSlop == true);
+ }
+ }
+ }
+
+#if DEBUG
+ if (result == false)
+ {
+ result = false; // break here
+ }
+#endif // DEBUG
+
+ return result;
+}
+
+bool flowList::setEdgeWeightMaxChecked(BasicBlock::weight_t newWeight, BasicBlock::weight_t slop, bool* wbUsedSlop)
+{
+ bool result = false;
+ if ((newWeight >= flEdgeWeightMin) && (newWeight <= flEdgeWeightMax))
+ {
+ flEdgeWeightMax = newWeight;
+ result = true;
+ }
+ else if (slop > 0)
+ {
+ // We allow for a small amount of inaccuracy in block weight counts.
+ if (flEdgeWeightMax < newWeight)
+ {
+ // We have already determined that this edge's weight
+ // is less than newWeight, so we just allow for the slop
+ if (newWeight <= (flEdgeWeightMax + slop))
+ {
+ result = true;
+
+ if (flEdgeWeightMax != 0)
+ {
+ // We will allow this to raise flEdgeWeightMax towards newWeight
+ flEdgeWeightMax = newWeight;
+ }
+
+ if (wbUsedSlop != nullptr)
+ {
+ *wbUsedSlop = true;
+ }
+ }
+ }
+ else
+ {
+ assert(flEdgeWeightMin > newWeight);
+
+ // We have already determined that this edge's weight
+ // is more than newWeight, so we just allow for the slop
+ if ((newWeight + slop) >= flEdgeWeightMin)
+ {
+ result = true;
+
+ assert(flEdgeWeightMax != 0);
+
+ // We will allow this to lower flEdgeWeightMin and Max towards newWeight
+ flEdgeWeightMax = flEdgeWeightMin;
+ flEdgeWeightMin = newWeight;
+
+ if (wbUsedSlop != nullptr)
+ {
+ *wbUsedSlop = true;
+ }
+ }
+ }
+
+ // If we are returning true then we should have adjusted the range so that
+ // the newWeight is in new range [Min..Max] or fgEdjeWeightMax is zero
+ // Also we should have set wbUsedSlop to true, unless it is NULL
+ if (result == true)
+ {
+ assert((flEdgeWeightMax == 0) || ((newWeight <= flEdgeWeightMax) && (newWeight >= flEdgeWeightMin)));
+
+ assert((wbUsedSlop == nullptr) || (*wbUsedSlop == true));
+ }
+ }
+
+#if DEBUG
+ if (result == false)
+ {
+ result = false; // break here
+ }
+#endif // DEBUG
+
+ return result;
+}
+
+#ifdef DEBUG
+void Compiler::fgPrintEdgeWeights()
+{
+ BasicBlock* bSrc;
+ BasicBlock* bDst;
+ flowList* edge;
+
+ // Print out all of the edge weights
+ for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->bbNext)
+ {
+ if (bDst->bbPreds != nullptr)
+ {
+ printf(" Edge weights into BB%02u :", bDst->bbNum);
+ for (edge = bDst->bbPreds; edge != nullptr; edge = edge->flNext)
+ {
+ bSrc = edge->flBlock;
+ // This is the control flow edge (bSrc -> bDst)
+
+ printf("BB%02u ", bSrc->bbNum);
+
+ if (edge->flEdgeWeightMin < BB_MAX_WEIGHT)
+ {
+ printf("(%s", refCntWtd2str(edge->flEdgeWeightMin));
+ }
+ else
+ {
+ printf("(MAX");
+ }
+ if (edge->flEdgeWeightMin != edge->flEdgeWeightMax)
+ {
+ if (edge->flEdgeWeightMax < BB_MAX_WEIGHT)
+ {
+ printf("..%s", refCntWtd2str(edge->flEdgeWeightMax));
+ }
+ else
+ {
+ printf("..MAX");
+ }
+ }
+ printf(")");
+ if (edge->flNext != nullptr)
+ {
+ printf(", ");
+ }
+ }
+ printf("\n");
+ }
+ }
+}
+#endif // DEBUG
+
+// return true if there is a possibility that the method has a loop (a backedge is present)
+bool Compiler::fgMightHaveLoop()
+{
+ // Don't use a BlockSet for this temporary bitset of blocks: we don't want to have to call EnsureBasicBlockEpoch()
+ // and potentially change the block epoch.
+
+ BitVecTraits blockVecTraits(fgBBNumMax + 1, this);
+ BitVec BLOCKSET_INIT_NOCOPY(blocksSeen, BitVecOps::MakeEmpty(&blockVecTraits));
+
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ BitVecOps::AddElemD(&blockVecTraits, blocksSeen, block->bbNum);
+
+ AllSuccessorIter succsEnd = block->GetAllSuccs(this).end();
+ for (AllSuccessorIter succs = block->GetAllSuccs(this).begin(); succs != succsEnd; ++succs)
+ {
+ BasicBlock* succ = (*succs);
+ if (BitVecOps::IsMember(&blockVecTraits, blocksSeen, succ->bbNum))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void Compiler::fgComputeEdgeWeights()
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgComputeEdgeWeights()\n");
+ }
+#endif // DEBUG
+
+ if (fgIsUsingProfileWeights() == false)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("fgComputeEdgeWeights() we do not have any profile data so we are not using the edge weights\n");
+ }
+#endif // DEBUG
+ fgHaveValidEdgeWeights = false;
+ fgCalledWeight = BB_UNITY_WEIGHT;
+ }
+
+#if DEBUG
+ if (verbose)
+ {
+ fgDispBasicBlocks();
+ printf("\n");
+ }
+#endif // DEBUG
+
+ BasicBlock* bSrc;
+ BasicBlock* bDst;
+ flowList* edge;
+ unsigned iterations = 0;
+ unsigned goodEdgeCountCurrent = 0;
+ unsigned goodEdgeCountPrevious = 0;
+ bool inconsistentProfileData = false;
+ bool hasIncompleteEdgeWeights = false;
+ unsigned numEdges = 0;
+ bool usedSlop = false;
+ bool changed;
+ bool modified;
+
+ BasicBlock::weight_t returnWeight;
+ BasicBlock::weight_t slop;
+
+ // If we have any blocks that did not have profile derived weight
+ // we will try to fix their weight up here
+ //
+ modified = false;
+ do // while (changed)
+ {
+ changed = false;
+ returnWeight = 0;
+ iterations++;
+
+ for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->bbNext)
+ {
+ if (((bDst->bbFlags & BBF_PROF_WEIGHT) == 0) && (bDst->bbPreds != nullptr))
+ {
+ BasicBlock* bOnlyNext;
+
+ // This block does not have a profile derived weight
+ //
+ BasicBlock::weight_t newWeight = BB_MAX_WEIGHT;
+
+ if (bDst->countOfInEdges() == 1)
+ {
+ // Only one block flows into bDst
+ bSrc = bDst->bbPreds->flBlock;
+
+ // Does this block flow into only one other block
+ if (bSrc->bbJumpKind == BBJ_NONE)
+ {
+ bOnlyNext = bSrc->bbNext;
+ }
+ else if (bSrc->bbJumpKind == BBJ_ALWAYS)
+ {
+ bOnlyNext = bSrc->bbJumpDest;
+ }
+ else
+ {
+ bOnlyNext = nullptr;
+ }
+
+ if ((bOnlyNext == bDst) && ((bSrc->bbFlags & BBF_PROF_WEIGHT) != 0))
+ {
+ // We know the exact weight of bDst
+ newWeight = bSrc->bbWeight;
+ }
+ }
+
+ // Does this block flow into only one other block
+ if (bDst->bbJumpKind == BBJ_NONE)
+ {
+ bOnlyNext = bDst->bbNext;
+ }
+ else if (bDst->bbJumpKind == BBJ_ALWAYS)
+ {
+ bOnlyNext = bDst->bbJumpDest;
+ }
+ else
+ {
+ bOnlyNext = nullptr;
+ }
+
+ if ((bOnlyNext != nullptr) && (bOnlyNext->bbPreds != nullptr))
+ {
+ // Does only one block flow into bOnlyNext
+ if (bOnlyNext->countOfInEdges() == 1)
+ {
+ noway_assert(bOnlyNext->bbPreds->flBlock == bDst);
+
+ // We know the exact weight of bDst
+ newWeight = bOnlyNext->bbWeight;
+ }
+ }
+
+ if ((newWeight != BB_MAX_WEIGHT) && (bDst->bbWeight != newWeight))
+ {
+ changed = true;
+ modified = true;
+ bDst->bbWeight = newWeight;
+ if (newWeight == 0)
+ {
+ bDst->bbFlags |= BBF_RUN_RARELY;
+ }
+ else
+ {
+ bDst->bbFlags &= ~BBF_RUN_RARELY;
+ }
+ }
+ }
+
+ // Sum up the weights of all of the return blocks and throw blocks
+ // This is used when we have a back-edge into block 1
+ //
+ if (((bDst->bbFlags & BBF_PROF_WEIGHT) != 0) &&
+ ((bDst->bbJumpKind == BBJ_RETURN) || (bDst->bbJumpKind == BBJ_THROW)))
+ {
+ returnWeight += bDst->bbWeight;
+ }
+ }
+ }
+ // Generally when we synthesize profile estimates we do it in a way where this algorithm will converge
+ // but downstream opts that remove conditional branches may create a situation where this is not the case.
+ // For instance a loop that becomes unreachable creates a sort of 'ring oscillator' (See test b539509)
+ while (changed && iterations < 10);
+
+#if DEBUG
+ if (verbose && modified)
+ {
+ printf("fgComputeEdgeWeights() adjusted the weight of some blocks\n");
+ fgDispBasicBlocks();
+ printf("\n");
+ }
+#endif
+
+ // When we are not using profile data we have already setup fgCalledWeight
+ // only set it here if we are using profile data
+ //
+ if (fgIsUsingProfileWeights())
+ {
+ // If the first block has one ref then it's weight is the fgCalledWeight
+ // otherwise we have backedge's into the first block so instead
+ // we use the sum of the return block weights.
+ // If the profile data has a 0 for the returnWeoght
+ // then just use the first block weight rather than the 0
+ //
+ if ((fgFirstBB->countOfInEdges() == 1) || (returnWeight == 0))
+ {
+ fgCalledWeight = fgFirstBB->bbWeight;
+ }
+ else
+ {
+ fgCalledWeight = returnWeight;
+ }
+ }
+
+ // Now we will compute the initial flEdgeWeightMin and flEdgeWeightMax values
+ for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->bbNext)
+ {
+ BasicBlock::weight_t bDstWeight = bDst->bbWeight;
+
+ // We subtract out the called count so that bDstWeight is
+ // the sum of all edges that go into this block from this method.
+ //
+ if (bDst == fgFirstBB)
+ {
+ bDstWeight -= fgCalledWeight;
+ }
+
+ for (edge = bDst->bbPreds; edge != nullptr; edge = edge->flNext)
+ {
+ bool assignOK = true;
+
+ bSrc = edge->flBlock;
+ // We are processing the control flow edge (bSrc -> bDst)
+
+ numEdges++;
+
+ //
+ // If the bSrc or bDst blocks do not have exact profile weights
+ // then we must reset any values that they currently have
+ //
+
+ if (((bSrc->bbFlags & BBF_PROF_WEIGHT) == 0) || ((bDst->bbFlags & BBF_PROF_WEIGHT) == 0))
+ {
+ edge->flEdgeWeightMin = BB_ZERO_WEIGHT;
+ edge->flEdgeWeightMax = BB_MAX_WEIGHT;
+ }
+
+ slop = BasicBlock::GetSlopFraction(bSrc, bDst) + 1;
+ switch (bSrc->bbJumpKind)
+ {
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ case BBJ_NONE:
+ case BBJ_CALLFINALLY:
+ // We know the exact edge weight
+ assignOK &= edge->setEdgeWeightMinChecked(bSrc->bbWeight, slop, &usedSlop);
+ assignOK &= edge->setEdgeWeightMaxChecked(bSrc->bbWeight, slop, &usedSlop);
+ break;
+
+ case BBJ_COND:
+ case BBJ_SWITCH:
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+ if (edge->flEdgeWeightMax > bSrc->bbWeight)
+ {
+ // The maximum edge weight to block can't be greater than the weight of bSrc
+ assignOK &= edge->setEdgeWeightMaxChecked(bSrc->bbWeight, slop, &usedSlop);
+ }
+ break;
+
+ default:
+ // We should never have an edge that starts from one of these jump kinds
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+
+ // The maximum edge weight to block can't be greater than the weight of bDst
+ if (edge->flEdgeWeightMax > bDstWeight)
+ {
+ assignOK &= edge->setEdgeWeightMaxChecked(bDstWeight, slop, &usedSlop);
+ }
+
+ if (!assignOK)
+ {
+ // Here we have inconsistent profile data
+ inconsistentProfileData = true;
+ // No point in continuing
+ goto EARLY_EXIT;
+ }
+ }
+ }
+
+ fgEdgeCount = numEdges;
+
+ iterations = 0;
+
+ do
+ {
+ iterations++;
+ goodEdgeCountPrevious = goodEdgeCountCurrent;
+ goodEdgeCountCurrent = 0;
+ hasIncompleteEdgeWeights = false;
+
+ for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->bbNext)
+ {
+ for (edge = bDst->bbPreds; edge != nullptr; edge = edge->flNext)
+ {
+ bool assignOK = true;
+
+ // We are processing the control flow edge (bSrc -> bDst)
+ bSrc = edge->flBlock;
+
+ slop = BasicBlock::GetSlopFraction(bSrc, bDst) + 1;
+ if (bSrc->bbJumpKind == BBJ_COND)
+ {
+ int diff;
+ flowList* otherEdge;
+ if (bSrc->bbNext == bDst)
+ {
+ otherEdge = fgGetPredForBlock(bSrc->bbJumpDest, bSrc);
+ }
+ else
+ {
+ otherEdge = fgGetPredForBlock(bSrc->bbNext, bSrc);
+ }
+ noway_assert(edge->flEdgeWeightMin <= edge->flEdgeWeightMax);
+ noway_assert(otherEdge->flEdgeWeightMin <= otherEdge->flEdgeWeightMax);
+
+ // Adjust edge->flEdgeWeightMin up or adjust otherEdge->flEdgeWeightMax down
+ diff = ((int)bSrc->bbWeight) - ((int)edge->flEdgeWeightMin + (int)otherEdge->flEdgeWeightMax);
+ if (diff > 0)
+ {
+ assignOK &= edge->setEdgeWeightMinChecked(edge->flEdgeWeightMin + diff, slop, &usedSlop);
+ }
+ else if (diff < 0)
+ {
+ assignOK &=
+ otherEdge->setEdgeWeightMaxChecked(otherEdge->flEdgeWeightMax + diff, slop, &usedSlop);
+ }
+
+ // Adjust otherEdge->flEdgeWeightMin up or adjust edge->flEdgeWeightMax down
+ diff = ((int)bSrc->bbWeight) - ((int)otherEdge->flEdgeWeightMin + (int)edge->flEdgeWeightMax);
+ if (diff > 0)
+ {
+ assignOK &=
+ otherEdge->setEdgeWeightMinChecked(otherEdge->flEdgeWeightMin + diff, slop, &usedSlop);
+ }
+ else if (diff < 0)
+ {
+ assignOK &= edge->setEdgeWeightMaxChecked(edge->flEdgeWeightMax + diff, slop, &usedSlop);
+ }
+
+ if (!assignOK)
+ {
+ // Here we have inconsistent profile data
+ inconsistentProfileData = true;
+ // No point in continuing
+ goto EARLY_EXIT;
+ }
+#ifdef DEBUG
+ // Now edge->flEdgeWeightMin and otherEdge->flEdgeWeightMax) should add up to bSrc->bbWeight
+ diff = ((int)bSrc->bbWeight) - ((int)edge->flEdgeWeightMin + (int)otherEdge->flEdgeWeightMax);
+ noway_assert((-((int)slop) <= diff) && (diff <= ((int)slop)));
+
+ // Now otherEdge->flEdgeWeightMin and edge->flEdgeWeightMax) should add up to bSrc->bbWeight
+ diff = ((int)bSrc->bbWeight) - ((int)otherEdge->flEdgeWeightMin + (int)edge->flEdgeWeightMax);
+ noway_assert((-((int)slop) <= diff) && (diff <= ((int)slop)));
+#endif // DEBUG
+ }
+ }
+ }
+
+ for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->bbNext)
+ {
+ BasicBlock::weight_t bDstWeight = bDst->bbWeight;
+
+ if (bDstWeight == BB_MAX_WEIGHT)
+ {
+ inconsistentProfileData = true;
+ // No point in continuing
+ goto EARLY_EXIT;
+ }
+ else
+ {
+ // We subtract out the called count so that bDstWeight is
+ // the sum of all edges that go into this block from this method.
+ //
+ if (bDst == fgFirstBB)
+ {
+ bDstWeight -= fgCalledWeight;
+ }
+
+ UINT64 minEdgeWeightSum = 0;
+ UINT64 maxEdgeWeightSum = 0;
+
+ // Calculate the sums of the minimum and maximum edge weights
+ for (edge = bDst->bbPreds; edge != nullptr; edge = edge->flNext)
+ {
+ // We are processing the control flow edge (bSrc -> bDst)
+ bSrc = edge->flBlock;
+
+ maxEdgeWeightSum += edge->flEdgeWeightMax;
+ minEdgeWeightSum += edge->flEdgeWeightMin;
+ }
+
+ // maxEdgeWeightSum is the sum of all flEdgeWeightMax values into bDst
+ // minEdgeWeightSum is the sum of all flEdgeWeightMin values into bDst
+
+ for (edge = bDst->bbPreds; edge != nullptr; edge = edge->flNext)
+ {
+ bool assignOK = true;
+
+ // We are processing the control flow edge (bSrc -> bDst)
+ bSrc = edge->flBlock;
+ slop = BasicBlock::GetSlopFraction(bSrc, bDst) + 1;
+
+ // otherMaxEdgesWeightSum is the sum of all of the other edges flEdgeWeightMax values
+ // This can be used to compute a lower bound for our minimum edge weight
+ noway_assert(maxEdgeWeightSum >= edge->flEdgeWeightMax);
+ UINT64 otherMaxEdgesWeightSum = maxEdgeWeightSum - edge->flEdgeWeightMax;
+
+ // otherMinEdgesWeightSum is the sum of all of the other edges flEdgeWeightMin values
+ // This can be used to compute an upper bound for our maximum edge weight
+ noway_assert(minEdgeWeightSum >= edge->flEdgeWeightMin);
+ UINT64 otherMinEdgesWeightSum = minEdgeWeightSum - edge->flEdgeWeightMin;
+
+ if (bDstWeight >= otherMaxEdgesWeightSum)
+ {
+ // minWeightCalc is our minWeight when every other path to bDst takes it's flEdgeWeightMax value
+ BasicBlock::weight_t minWeightCalc =
+ (BasicBlock::weight_t)(bDstWeight - otherMaxEdgesWeightSum);
+ if (minWeightCalc > edge->flEdgeWeightMin)
+ {
+ assignOK &= edge->setEdgeWeightMinChecked(minWeightCalc, slop, &usedSlop);
+ }
+ }
+
+ if (bDstWeight >= otherMinEdgesWeightSum)
+ {
+ // maxWeightCalc is our maxWeight when every other path to bDst takes it's flEdgeWeightMin value
+ BasicBlock::weight_t maxWeightCalc =
+ (BasicBlock::weight_t)(bDstWeight - otherMinEdgesWeightSum);
+ if (maxWeightCalc < edge->flEdgeWeightMax)
+ {
+ assignOK &= edge->setEdgeWeightMaxChecked(maxWeightCalc, slop, &usedSlop);
+ }
+ }
+
+ if (!assignOK)
+ {
+ // Here we have inconsistent profile data
+ inconsistentProfileData = true;
+ // No point in continuing
+ goto EARLY_EXIT;
+ }
+
+ // When flEdgeWeightMin equals flEdgeWeightMax we have a "good" edge weight
+ if (edge->flEdgeWeightMin == edge->flEdgeWeightMax)
+ {
+ // Count how many "good" edge weights we have
+ // Each time through we should have more "good" weights
+ // We exit the while loop when no longer find any new "good" edges
+ goodEdgeCountCurrent++;
+ }
+ else
+ {
+ // Remember that we have seen at least one "Bad" edge weight
+ // so that we will repeat the while loop again
+ hasIncompleteEdgeWeights = true;
+ }
+ }
+ }
+ }
+
+ if (inconsistentProfileData)
+ {
+ hasIncompleteEdgeWeights = true;
+ break;
+ }
+
+ if (numEdges == goodEdgeCountCurrent)
+ {
+ noway_assert(hasIncompleteEdgeWeights == false);
+ break;
+ }
+
+ } while (hasIncompleteEdgeWeights && (goodEdgeCountCurrent > goodEdgeCountPrevious) && (iterations < 8));
+
+EARLY_EXIT:;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ if (inconsistentProfileData)
+ {
+ printf("fgComputeEdgeWeights() found inconsistent profile data, not using the edge weights\n");
+ }
+ else
+ {
+ if (hasIncompleteEdgeWeights)
+ {
+ printf("fgComputeEdgeWeights() was able to compute exact edge weights for %3d of the %3d edges, using "
+ "%d passes.\n",
+ goodEdgeCountCurrent, numEdges, iterations);
+ }
+ else
+ {
+ printf("fgComputeEdgeWeights() was able to compute exact edge weights for all of the %3d edges, using "
+ "%d passes.\n",
+ numEdges, iterations);
+ }
+
+ fgPrintEdgeWeights();
+ }
+ }
+#endif // DEBUG
+
+ fgSlopUsedInEdgeWeights = usedSlop;
+ fgRangeUsedInEdgeWeights = false;
+
+ // See if any edge weight are expressed in [min..max] form
+
+ for (bDst = fgFirstBB; bDst != nullptr; bDst = bDst->bbNext)
+ {
+ if (bDst->bbPreds != nullptr)
+ {
+ for (edge = bDst->bbPreds; edge != nullptr; edge = edge->flNext)
+ {
+ bSrc = edge->flBlock;
+ // This is the control flow edge (bSrc -> bDst)
+
+ if (edge->flEdgeWeightMin != edge->flEdgeWeightMax)
+ {
+ fgRangeUsedInEdgeWeights = true;
+ break;
+ }
+ }
+ if (fgRangeUsedInEdgeWeights)
+ {
+ break;
+ }
+ }
+ }
+
+ fgHaveValidEdgeWeights = !inconsistentProfileData;
+ fgEdgeWeightsComputed = true;
+}
+
+// fgOptimizeBranchToEmptyUnconditional:
+// optimize a jump to an empty block which ends in an unconditional branch.
+// Args:
+// block: source block
+// bDest: destination
+// Returns: true if we changed the code
+//
+bool Compiler::fgOptimizeBranchToEmptyUnconditional(BasicBlock* block, BasicBlock* bDest)
+{
+ bool optimizeJump = true;
+
+ assert(bDest->isEmpty());
+ assert(bDest->bbJumpKind == BBJ_ALWAYS);
+
+ // We do not optimize jumps between two different try regions.
+ // However jumping to a block that is not in any try region is OK
+ //
+ if (bDest->hasTryIndex() && !BasicBlock::sameTryRegion(block, bDest))
+ {
+ optimizeJump = false;
+ }
+
+ // Don't optimize a jump to a removed block
+ if (bDest->bbJumpDest->bbFlags & BBF_REMOVED)
+ {
+ 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,
+ // it would skip the finally target. BB1 might be a BBJ_ALWAYS block part
+ // of a BBJ_CALLFINALLY/BBJ_ALWAYS pair, so changing the finally target
+ // would change the unwind behavior.
+ if (bDest->bbFlags & BBF_FINALLY_TARGET)
+ {
+ optimizeJump = false;
+ }
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+ // Must optimize jump if bDest has been removed
+ //
+ if (bDest->bbFlags & BBF_REMOVED)
+ {
+ optimizeJump = true;
+ }
+
+ // If we are optimizing using real profile weights
+ // then don't optimize a conditional jump to an unconditional jump
+ // until after we have computed the edge weights
+ //
+ if (fgIsUsingProfileWeights() && !fgEdgeWeightsComputed)
+ {
+ fgNeedsUpdateFlowGraph = true;
+ optimizeJump = false;
+ }
+
+ if (optimizeJump)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nOptimizing a jump to an unconditional jump (BB%02u -> BB%02u -> BB%02u)\n", block->bbNum,
+ bDest->bbNum, bDest->bbJumpDest->bbNum);
+ }
+#endif // DEBUG
+
+ //
+ // When we optimize a branch to branch we need to update the profile weight
+ // of bDest by subtracting out the block/edge weight of the path that is being optimized.
+ //
+ if (fgHaveValidEdgeWeights && ((bDest->bbFlags & BBF_PROF_WEIGHT) != 0))
+ {
+ flowList* edge1 = fgGetPredForBlock(bDest, block);
+ noway_assert(edge1 != nullptr);
+
+ BasicBlock::weight_t edgeWeight;
+
+ if (edge1->flEdgeWeightMin != edge1->flEdgeWeightMax)
+ {
+ //
+ // We only have an estimate for the edge weight
+ //
+ edgeWeight = (edge1->flEdgeWeightMin + edge1->flEdgeWeightMax) / 2;
+ //
+ // Clear the profile weight flag
+ //
+ bDest->bbFlags &= ~BBF_PROF_WEIGHT;
+ }
+ else
+ {
+ //
+ // We only have the exact edge weight
+ //
+ edgeWeight = edge1->flEdgeWeightMin;
+ }
+
+ //
+ // Update the bDest->bbWeight
+ //
+ if (bDest->bbWeight > edgeWeight)
+ {
+ bDest->bbWeight -= edgeWeight;
+ }
+ else
+ {
+ bDest->bbWeight = BB_ZERO_WEIGHT;
+ bDest->bbFlags |= BBF_RUN_RARELY; // Set the RarelyRun flag
+ }
+
+ flowList* edge2 = fgGetPredForBlock(bDest->bbJumpDest, bDest);
+
+ if (edge2 != nullptr)
+ {
+ //
+ // Update the edge2 min/max weights
+ //
+ if (edge2->flEdgeWeightMin > edge1->flEdgeWeightMin)
+ {
+ edge2->flEdgeWeightMin -= edge1->flEdgeWeightMin;
+ }
+ else
+ {
+ edge2->flEdgeWeightMin = BB_ZERO_WEIGHT;
+ }
+
+ if (edge2->flEdgeWeightMax > edge1->flEdgeWeightMin)
+ {
+ edge2->flEdgeWeightMax -= edge1->flEdgeWeightMin;
+ }
+ else
+ {
+ edge2->flEdgeWeightMax = BB_ZERO_WEIGHT;
+ }
+ }
+ }
+
+ // Optimize the JUMP to empty unconditional JUMP to go to the new target
+ block->bbJumpDest = bDest->bbJumpDest;
+
+ fgAddRefPred(bDest->bbJumpDest, block, fgRemoveRefPred(bDest, block));
+
+ return true;
+ }
+ return false;
+}
+
+// fgOptimizeEmptyBlock:
+// Does flow optimization of an empty block (can remove it in some cases)
+//
+// Args:
+// block: an empty block
+// Returns: true if we changed the code
+
+bool Compiler::fgOptimizeEmptyBlock(BasicBlock* block)
+{
+ assert(block->isEmpty());
+
+ BasicBlock* bPrev = block->bbPrev;
+
+ switch (block->bbJumpKind)
+ {
+ case BBJ_COND:
+ case BBJ_SWITCH:
+ case BBJ_THROW:
+
+ /* can never happen */
+ noway_assert(!"Conditional, switch, or throw block with empty body!");
+ break;
+
+ case BBJ_CALLFINALLY:
+ case BBJ_RETURN:
+ case BBJ_EHCATCHRET:
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+
+ /* leave them as is */
+ /* some compilers generate multiple returns and put all of them at the end -
+ * to solve that we need the predecessor list */
+
+ break;
+
+ case BBJ_ALWAYS:
+
+ // A GOTO cannot be to the next block since that
+ // should have been fixed by the optimization above
+ // An exception is made for a jump from Hot to Cold
+ noway_assert(block->bbJumpDest != block->bbNext || ((bPrev != nullptr) && bPrev->isBBCallAlwaysPair()) ||
+ fgInDifferentRegions(block, block->bbNext));
+
+ /* Cannot remove the first BB */
+ if (!bPrev)
+ {
+ break;
+ }
+
+ /* Do not remove a block that jumps to itself - used for while (true){} */
+ if (block->bbJumpDest == block)
+ {
+ break;
+ }
+
+ /* Empty GOTO can be removed iff bPrev is BBJ_NONE */
+ if (bPrev->bbJumpKind != BBJ_NONE)
+ {
+ break;
+ }
+
+ // can't allow fall through into cold code
+ if (block->bbNext == fgFirstColdBlock)
+ {
+ break;
+ }
+
+ /* Can fall through since this is similar with removing
+ * a BBJ_NONE block, only the successor is different */
+
+ __fallthrough;
+
+ case BBJ_NONE:
+
+ /* special case if this is the first BB */
+ if (!bPrev)
+ {
+ assert(block == fgFirstBB);
+ }
+ else
+ {
+ /* If this block follows a BBJ_CALLFINALLY do not remove it
+ * (because we don't know who may jump to it) */
+ if (bPrev->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ break;
+ }
+ }
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ /* Don't remove finally targets */
+ if (block->bbFlags & BBF_FINALLY_TARGET)
+ break;
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+#if FEATURE_EH_FUNCLETS
+ /* Don't remove an empty block that is in a different EH region
+ * from its successor block, if the block is the target of a
+ * catch return. It is required that the return address of a
+ * catch be in the correct EH region, for re-raise of thread
+ * abort exceptions to work. Insert a NOP in the empty block
+ * to ensure we generate code for the block, if we keep it.
+ */
+ {
+ BasicBlock* succBlock;
+
+ if (block->bbJumpKind == BBJ_ALWAYS)
+ {
+ succBlock = block->bbJumpDest;
+ }
+ else
+ {
+ succBlock = block->bbNext;
+ }
+
+ if ((succBlock != nullptr) && !BasicBlock::sameEHRegion(block, succBlock))
+ {
+ // The empty block and the block that follows it are in different
+ // EH regions. Is this a case where they can't be merged?
+
+ bool okToMerge = true; // assume it's ok
+ for (flowList* pred = block->bbPreds; pred; pred = pred->flNext)
+ {
+ if (pred->flBlock->bbJumpKind == BBJ_EHCATCHRET)
+ {
+ assert(pred->flBlock->bbJumpDest == block);
+ okToMerge = false; // we can't get rid of the empty block
+ break;
+ }
+ }
+
+ if (!okToMerge)
+ {
+ // Insert a NOP in the empty block to ensure we generate code
+ // for the catchret target in the right EH region.
+ GenTree* nop = new (this, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID);
+
+ if (block->IsLIR())
+ {
+ LIR::AsRange(block).InsertAtEnd(nop);
+ }
+ else
+ {
+ GenTreePtr nopStmt = fgInsertStmtAtEnd(block, nop);
+ fgSetStmtSeq(nopStmt);
+ gtSetStmtInfo(nopStmt);
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nKeeping empty block BB%02u - it is the target of a catch return\n", block->bbNum);
+ }
+#endif // DEBUG
+
+ break; // go to the next block
+ }
+ }
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ if (!ehCanDeleteEmptyBlock(block))
+ {
+ // We're not allowed to remove this block due to reasons related to the EH table.
+ break;
+ }
+
+ /* special case if this is the last BB */
+ if (block == fgLastBB)
+ {
+ if (!bPrev)
+ {
+ break;
+ }
+ fgLastBB = bPrev;
+ }
+
+ /* Remove the block */
+ compCurBB = block;
+ fgRemoveBlock(block, false);
+ return true;
+
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+ return false;
+}
+
+// fgOptimizeSwitchBranches:
+// Does flow optimization for a switch - bypasses jumps to empty unconditional branches,
+// and transforms degenerate switch cases like those with 1 or 2 targets
+//
+// Args:
+// block: BasicBlock that contains the switch
+// Returns: true if we changed the code
+//
+bool Compiler::fgOptimizeSwitchBranches(BasicBlock* block)
+{
+ assert(block->bbJumpKind == BBJ_SWITCH);
+
+ unsigned jmpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jmpTab = block->bbJumpSwt->bbsDstTab;
+ BasicBlock* bNewDest; // the new jump target for the current switch case
+ BasicBlock* bDest;
+ bool returnvalue = false;
+
+ do
+ {
+ REPEAT_SWITCH:;
+ bDest = *jmpTab;
+ bNewDest = bDest;
+
+ // Do we have a JUMP to an empty unconditional JUMP block?
+ if (bDest->isEmpty() && (bDest->bbJumpKind == BBJ_ALWAYS) &&
+ (bDest != bDest->bbJumpDest)) // special case for self jumps
+ {
+ bool optimizeJump = true;
+
+ // We do not optimize jumps between two different try regions.
+ // However jumping to a block that is not in any try region is OK
+ //
+ if (bDest->hasTryIndex() && !BasicBlock::sameTryRegion(block, bDest))
+ {
+ optimizeJump = false;
+ }
+
+ // If we are optimize using real profile weights
+ // then don't optimize a switch jump to an unconditional jump
+ // until after we have computed the edge weights
+ //
+ if (fgIsUsingProfileWeights() && !fgEdgeWeightsComputed)
+ {
+ fgNeedsUpdateFlowGraph = true;
+ optimizeJump = false;
+ }
+
+ if (optimizeJump)
+ {
+ bNewDest = bDest->bbJumpDest;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nOptimizing a switch jump to an empty block with an unconditional jump (BB%02u -> BB%02u "
+ "-> BB%02u)\n",
+ block->bbNum, bDest->bbNum, bNewDest->bbNum);
+ }
+#endif // DEBUG
+ }
+ }
+
+ if (bNewDest != bDest)
+ {
+ //
+ // When we optimize a branch to branch we need to update the profile weight
+ // of bDest by subtracting out the block/edge weight of the path that is being optimized.
+ //
+ if (fgIsUsingProfileWeights() && ((bDest->bbFlags & BBF_PROF_WEIGHT) != 0))
+ {
+ if (fgHaveValidEdgeWeights)
+ {
+ flowList* edge = fgGetPredForBlock(bDest, block);
+ BasicBlock::weight_t branchThroughWeight = edge->flEdgeWeightMin;
+
+ if (bDest->bbWeight > branchThroughWeight)
+ {
+ bDest->bbWeight -= branchThroughWeight;
+ }
+ else
+ {
+ bDest->bbWeight = BB_ZERO_WEIGHT;
+ bDest->bbFlags |= BBF_RUN_RARELY;
+ }
+ }
+ }
+
+ // Update the switch jump table
+ *jmpTab = bNewDest;
+
+ // Maintain, if necessary, the set of unique targets of "block."
+ UpdateSwitchTableTarget(block, bDest, bNewDest);
+
+ fgAddRefPred(bNewDest, block, fgRemoveRefPred(bDest, block));
+
+ // we optimized a Switch label - goto REPEAT_SWITCH to follow this new jump
+ returnvalue = true;
+
+ goto REPEAT_SWITCH;
+ }
+ } while (++jmpTab, --jmpCnt);
+
+ GenTreeStmt* switchStmt = nullptr;
+ LIR::Range* blockRange = nullptr;
+
+ GenTree* switchTree;
+ if (block->IsLIR())
+ {
+ blockRange = &LIR::AsRange(block);
+ switchTree = blockRange->LastNode();
+
+ assert(switchTree->OperGet() == GT_SWITCH_TABLE);
+ }
+ else
+ {
+ switchStmt = block->lastStmt();
+ switchTree = switchStmt->gtStmtExpr;
+
+ assert(switchTree->OperGet() == GT_SWITCH);
+ }
+
+ noway_assert(switchTree->gtType == TYP_VOID);
+
+ // At this point all of the case jump targets have been updated such
+ // that none of them go to block that is an empty unconditional block
+ //
+ jmpTab = block->bbJumpSwt->bbsDstTab;
+ jmpCnt = block->bbJumpSwt->bbsCount;
+ // Now check for two trivial switch jumps.
+ //
+ if (block->NumSucc(this) == 1)
+ {
+ // Use BBJ_ALWAYS for a switch with only a default clause, or with only one unique successor.
+ BasicBlock* uniqueSucc = jmpTab[0];
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nRemoving a switch jump with a single target (BB%02u)\n", block->bbNum);
+ printf("BEFORE:\n");
+ }
+#endif // DEBUG
+
+ if (block->IsLIR())
+ {
+ bool isClosed;
+ unsigned sideEffects;
+ LIR::ReadOnlyRange switchTreeRange = blockRange->GetTreeRange(switchTree, &isClosed, &sideEffects);
+
+ // The switch tree should form a contiguous, side-effect free range by construction. See
+ // Lowering::LowerSwitch for details.
+ assert(isClosed);
+ assert((sideEffects & GTF_ALL_EFFECT) == 0);
+
+ blockRange->Delete(this, block, std::move(switchTreeRange));
+ }
+ else
+ {
+ /* check for SIDE_EFFECTS */
+ if (switchTree->gtFlags & GTF_SIDE_EFFECT)
+ {
+ /* Extract the side effects from the conditional */
+ GenTreePtr sideEffList = nullptr;
+
+ gtExtractSideEffList(switchTree, &sideEffList);
+
+ if (sideEffList == nullptr)
+ {
+ goto NO_SWITCH_SIDE_EFFECT;
+ }
+
+ noway_assert(sideEffList->gtFlags & GTF_SIDE_EFFECT);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nSwitch expression has side effects! Extracting side effects...\n");
+ gtDispTree(switchTree);
+ printf("\n");
+ gtDispTree(sideEffList);
+ printf("\n");
+ }
+#endif // DEBUG
+
+ /* Replace the conditional statement with the list of side effects */
+ noway_assert(sideEffList->gtOper != GT_STMT);
+ noway_assert(sideEffList->gtOper != GT_SWITCH);
+
+ switchStmt->gtStmtExpr = sideEffList;
+
+ if (fgStmtListThreaded)
+ {
+ /* Update the lclvar ref counts */
+ compCurBB = block;
+ fgUpdateRefCntForExtract(switchTree, sideEffList);
+
+ /* Update ordering, costs, FP levels, etc. */
+ gtSetStmtInfo(switchStmt);
+
+ /* Re-link the nodes for this statement */
+ fgSetStmtSeq(switchStmt);
+ }
+ }
+ else
+ {
+
+ NO_SWITCH_SIDE_EFFECT:
+
+ /* conditional has NO side effect - remove it */
+ fgRemoveStmt(block, switchStmt);
+ }
+ }
+
+ // Change the switch jump into a BBJ_ALWAYS
+ block->bbJumpDest = block->bbJumpSwt->bbsDstTab[0];
+ block->bbJumpKind = BBJ_ALWAYS;
+ if (jmpCnt > 1)
+ {
+ for (unsigned i = 1; i < jmpCnt; ++i)
+ {
+ (void)fgRemoveRefPred(jmpTab[i], block);
+ }
+ }
+
+ return true;
+ }
+ else if (block->bbJumpSwt->bbsCount == 2 && block->bbJumpSwt->bbsDstTab[1] == block->bbNext)
+ {
+ /* Use a BBJ_COND(switchVal==0) for a switch with only one
+ significant clause besides the default clause, if the
+ default clause is bbNext */
+ GenTree* switchVal = switchTree->gtOp.gtOp1;
+ noway_assert(genActualTypeIsIntOrI(switchVal->TypeGet()));
+
+ // If we are in LIR, remove the jump table from the block.
+ if (block->IsLIR())
+ {
+ GenTree* jumpTable = switchTree->gtOp.gtOp2;
+ assert(jumpTable->OperGet() == GT_JMPTABLE);
+ blockRange->Remove(jumpTable);
+ }
+
+ // Change the GT_SWITCH(switchVal) into GT_JTRUE(GT_EQ(switchVal==0)).
+ // Also mark the node as GTF_DONT_CSE as further down JIT is not capable of handling it.
+ // For example CSE could determine that the expression rooted at GT_EQ is a candidate cse and
+ // replace it with a COMMA node. In such a case we will end up with GT_JTRUE node pointing to
+ // a COMMA node which results in noway asserts in fgMorphSmpOp(), optAssertionGen() and rpPredictTreeRegUse().
+ // For the same reason fgMorphSmpOp() marks GT_JTRUE nodes with RELOP children as GTF_DONT_CSE.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nConverting a switch (BB%02u) with only one significant clause besides a default target to a "
+ "conditional branch\n",
+ block->bbNum);
+ }
+#endif // DEBUG
+
+ switchTree->ChangeOper(GT_JTRUE);
+ GenTree* zeroConstNode = gtNewZeroConNode(genActualType(switchVal->TypeGet()));
+ GenTree* condNode = gtNewOperNode(GT_EQ, TYP_INT, switchVal, zeroConstNode);
+ switchTree->gtOp.gtOp1 = condNode;
+ switchTree->gtOp.gtOp1->gtFlags |= (GTF_RELOP_JMP_USED | GTF_DONT_CSE);
+
+ if (block->IsLIR())
+ {
+ blockRange->InsertAfter(switchVal, zeroConstNode, condNode);
+ }
+ else
+ {
+ // Re-link the nodes for this statement.
+ fgSetStmtSeq(switchStmt);
+ }
+
+ block->bbJumpDest = block->bbJumpSwt->bbsDstTab[0];
+ block->bbJumpKind = BBJ_COND;
+
+ return true;
+ }
+ return returnvalue;
+}
+
+// fgBlockEndFavorsTailDuplication:
+// Heuristic function that returns true if this block ends in a statement that looks favorable
+// for tail-duplicating its successor (such as assigning a constant to a local).
+// Args:
+// block: BasicBlock we are considering duplicating the successor of
+// Returns:
+// true if it seems like a good idea
+//
+bool Compiler::fgBlockEndFavorsTailDuplication(BasicBlock* block)
+{
+ if (block->isRunRarely())
+ {
+ return false;
+ }
+
+ if (!block->lastStmt())
+ {
+ return false;
+ }
+ else
+ {
+ // Tail duplication tends to pay off when the last statement
+ // is an assignment of a constant, arraylength, or a relop.
+ // This is because these statements produce information about values
+ // that would otherwise be lost at the upcoming merge point.
+
+ GenTreeStmt* lastStmt = block->lastStmt();
+ GenTree* tree = lastStmt->gtStmtExpr;
+ if (tree->gtOper != GT_ASG)
+ {
+ return false;
+ }
+
+ if (tree->OperIsBlkOp())
+ {
+ return false;
+ }
+
+ GenTree* op2 = tree->gtOp.gtOp2;
+ if (op2->gtOper != GT_ARR_LENGTH && !op2->OperIsConst() && ((op2->OperKind() & GTK_RELOP) == 0))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+// fgBlockIsGoodTailDuplicationCandidate:
+// Heuristic function that examines a block (presumably one that is a merge point) to determine
+// if it should be duplicated.
+// args:
+// target - the tail block (candidate for duplication)
+// returns:
+// true if this block seems like a good candidate for duplication
+//
+bool Compiler::fgBlockIsGoodTailDuplicationCandidate(BasicBlock* target)
+{
+ GenTreeStmt* stmt = target->FirstNonPhiDef();
+
+ // Here we are looking for blocks with a single statement feeding a conditional branch.
+ // These blocks are small, and when duplicated onto the tail of blocks that end in
+ // assignments, there is a high probability of the branch completely going away.
+
+ // This is by no means the only kind of tail that it is beneficial to duplicate,
+ // just the only one we recognize for now.
+
+ if (stmt != target->lastStmt())
+ {
+ return false;
+ }
+
+ if (target->bbJumpKind != BBJ_COND)
+ {
+ return false;
+ }
+
+ GenTree* tree = stmt->gtStmtExpr;
+
+ if (tree->gtOper != GT_JTRUE)
+ {
+ return false;
+ }
+
+ // must be some kind of relational operator
+ GenTree* cond = tree->gtOp.gtOp1;
+ if (!(cond->OperKind() & GTK_RELOP))
+ {
+ return false;
+ }
+
+ // op1 must be some combinations of casts of local or constant
+ GenTree* op1 = cond->gtOp.gtOp1;
+ while (op1->gtOper == GT_CAST)
+ {
+ op1 = op1->gtOp.gtOp1;
+ }
+ if (!op1->IsLocal() && !op1->OperIsConst())
+ {
+ return false;
+ }
+
+ // op2 must be some combinations of casts of local or constant
+ GenTree* op2 = cond->gtOp.gtOp2;
+ while (op2->gtOper == GT_CAST)
+ {
+ op2 = op2->gtOp.gtOp1;
+ }
+ if (!op2->IsLocal() && !op2->OperIsConst())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+// fgOptimizeUncondBranchToSimpleCond:
+// For a block which has an unconditional branch, look to see if its target block
+// is a good candidate for tail duplication, and if so do that duplication.
+//
+// Args:
+// block - block with uncond branch
+// target - block which is target of first block
+//
+// returns: true if changes were made
+
+bool Compiler::fgOptimizeUncondBranchToSimpleCond(BasicBlock* block, BasicBlock* target)
+{
+ assert(block->bbJumpKind == BBJ_ALWAYS);
+ assert(block->bbJumpDest == target);
+
+ // TODO-Review: OK if they are in the same region?
+ if (compHndBBtabCount > 0)
+ {
+ return false;
+ }
+
+ if (!fgBlockIsGoodTailDuplicationCandidate(target))
+ {
+ return false;
+ }
+
+ if (!fgBlockEndFavorsTailDuplication(block))
+ {
+ return false;
+ }
+
+ // NOTE: we do not currently hit this assert because this function is only called when
+ // `fgUpdateFlowGraph` has been called with `doTailDuplication` set to true, and the
+ // backend always calls `fgUpdateFlowGraph` with `doTailDuplication` set to false.
+ assert(!block->IsLIR());
+
+ GenTreeStmt* stmt = target->FirstNonPhiDef();
+ assert(stmt == target->lastStmt());
+
+ // Duplicate the target block at the end of this block
+
+ GenTree* cloned = gtCloneExpr(stmt->gtStmtExpr);
+ noway_assert(cloned);
+ GenTree* jmpStmt = gtNewStmt(cloned);
+
+ block->bbJumpKind = BBJ_COND;
+ block->bbJumpDest = target->bbJumpDest;
+ fgAddRefPred(block->bbJumpDest, block);
+ fgRemoveRefPred(target, block);
+
+ // add an unconditional block after this block to jump to the target block's fallthrough block
+
+ BasicBlock* next = fgNewBBafter(BBJ_ALWAYS, block, true);
+ next->bbFlags = block->bbFlags | BBF_INTERNAL;
+ next->bbFlags &= ~(BBF_TRY_BEG | BBF_LOOP_HEAD | BBF_LOOP_CALL0 | BBF_LOOP_CALL1 | BBF_HAS_LABEL | BBF_JMP_TARGET |
+ BBF_FUNCLET_BEG | BBF_LOOP_PREHEADER | BBF_KEEP_BBJ_ALWAYS);
+
+ next->bbJumpDest = target->bbNext;
+ target->bbNext->bbFlags |= BBF_JMP_TARGET;
+ fgAddRefPred(next, block);
+ fgAddRefPred(next->bbJumpDest, next);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("fgOptimizeUncondBranchToSimpleCond(from BB%02u to cond BB%02u), created new uncond BB%02u\n",
+ block->bbNum, target->bbNum, next->bbNum);
+ }
+#endif // DEBUG
+
+ if (fgStmtListThreaded)
+ {
+ gtSetStmtInfo(jmpStmt);
+ }
+
+ fgInsertStmtAtEnd(block, jmpStmt);
+
+ return true;
+}
+
+// fgOptimizeBranchToNext:
+// Optimize a block which has a branch to the following block
+// Args:
+// block - block with a branch
+// bNext - block which is both next and the target of the first block
+// bPrev - block which is prior to the first block
+//
+// returns: true if changes were made
+//
+bool Compiler::fgOptimizeBranchToNext(BasicBlock* block, BasicBlock* bNext, BasicBlock* bPrev)
+{
+ assert(block->bbJumpKind == BBJ_COND || block->bbJumpKind == BBJ_ALWAYS);
+ assert(block->bbJumpDest == bNext);
+ assert(block->bbNext = bNext);
+ assert(block->bbPrev == bPrev);
+
+ if (block->bbJumpKind == BBJ_ALWAYS)
+ {
+ // We can't remove it if it is a branch from hot => cold
+ if (!fgInDifferentRegions(block, bNext))
+ {
+ // We can't remove if it is marked as BBF_KEEP_BBJ_ALWAYS
+ if (!(block->bbFlags & BBF_KEEP_BBJ_ALWAYS))
+ {
+ // We can't remove if the BBJ_ALWAYS is part of a BBJ_CALLFINALLY pair
+ if ((bPrev == nullptr) || !bPrev->isBBCallAlwaysPair())
+ {
+ /* the unconditional jump is to the next BB */
+ block->bbJumpKind = BBJ_NONE;
+ block->bbFlags &= ~BBF_NEEDS_GCPOLL;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nRemoving unconditional jump to next block (BB%02u -> BB%02u) (converted BB%02u to "
+ "fall-through)\n",
+ block->bbNum, bNext->bbNum, block->bbNum);
+ }
+#endif // DEBUG
+ return true;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* remove the conditional statement at the end of block */
+ noway_assert(block->bbJumpKind == BBJ_COND);
+ noway_assert(block->bbTreeList);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nRemoving conditional jump to next block (BB%02u -> BB%02u)\n", block->bbNum, bNext->bbNum);
+ }
+#endif // DEBUG
+
+ if (block->IsLIR())
+ {
+ LIR::Range& blockRange = LIR::AsRange(block);
+ GenTree* jmp = blockRange.LastNode();
+ assert(jmp->OperGet() == GT_JTRUE);
+
+ bool isClosed;
+ unsigned sideEffects;
+ LIR::ReadOnlyRange jmpRange = blockRange.GetTreeRange(jmp, &isClosed, &sideEffects);
+
+ // TODO-LIR: this should really be checking GTF_ALL_EFFECT, but that produces unacceptable
+ // diffs compared to the existing backend.
+ if (isClosed && ((sideEffects & GTF_SIDE_EFFECT) == 0))
+ {
+ // If the jump and its operands form a contiguous, side-effect-free range,
+ // remove them.
+ blockRange.Delete(this, block, std::move(jmpRange));
+ }
+ else
+ {
+ // Otherwise, just remove the jump node itself.
+ blockRange.Remove(jmp);
+ }
+ }
+ else
+ {
+ GenTreeStmt* cond = block->lastStmt();
+ noway_assert(cond->gtStmtExpr->gtOper == GT_JTRUE);
+
+ /* check for SIDE_EFFECTS */
+ if (cond->gtStmtExpr->gtFlags & GTF_SIDE_EFFECT)
+ {
+ /* Extract the side effects from the conditional */
+ GenTreePtr sideEffList = nullptr;
+
+ gtExtractSideEffList(cond->gtStmtExpr, &sideEffList);
+
+ if (sideEffList == nullptr)
+ {
+ compCurBB = block;
+ fgRemoveStmt(block, cond);
+ }
+ else
+ {
+ noway_assert(sideEffList->gtFlags & GTF_SIDE_EFFECT);
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nConditional has side effects! Extracting side effects...\n");
+ gtDispTree(cond);
+ printf("\n");
+ gtDispTree(sideEffList);
+ printf("\n");
+ }
+#endif // DEBUG
+
+ /* Replace the conditional statement with the list of side effects */
+ noway_assert(sideEffList->gtOper != GT_STMT);
+ noway_assert(sideEffList->gtOper != GT_JTRUE);
+
+ cond->gtStmtExpr = sideEffList;
+
+ if (fgStmtListThreaded)
+ {
+ /* Update the lclvar ref counts */
+ compCurBB = block;
+ fgUpdateRefCntForExtract(cond->gtStmtExpr, sideEffList);
+
+ /* Update ordering, costs, FP levels, etc. */
+ gtSetStmtInfo(cond);
+
+ /* Re-link the nodes for this statement */
+ fgSetStmtSeq(cond);
+ }
+ }
+ }
+ else
+ {
+ compCurBB = block;
+ /* conditional has NO side effect - remove it */
+ fgRemoveStmt(block, cond);
+ }
+ }
+
+ /* Conditional is gone - simply fall into the next block */
+
+ block->bbJumpKind = BBJ_NONE;
+ block->bbFlags &= ~BBF_NEEDS_GCPOLL;
+
+ /* Update bbRefs and bbNum - Conditional predecessors to the same
+ * block are counted twice so we have to remove one of them */
+
+ noway_assert(bNext->countOfInEdges() > 1);
+ fgRemoveRefPred(bNext, block);
+
+ return true;
+ }
+ return false;
+}
+
+/*****************************************************************************
+ *
+ * Function called to optimize an unconditional branch that branches
+ * to a conditional branch.
+ * Currently we require that the conditional branch jump back to the
+ * block that follows the unconditional branch.
+ *
+ * We can improve the code execution and layout by concatenating a copy
+ * of the conditional branch block at the end of the conditional branch
+ * and reversing the sense of the branch.
+ *
+ * This is only done when the amount of code to be copied is smaller than
+ * our calculated threshold in maxDupCostSz.
+ *
+ */
+
+bool Compiler::fgOptimizeBranch(BasicBlock* bJump)
+{
+ if (opts.MinOpts())
+ {
+ return false;
+ }
+
+ if (bJump->bbJumpKind != BBJ_ALWAYS)
+ {
+ return false;
+ }
+
+ if (bJump->bbFlags & BBF_KEEP_BBJ_ALWAYS)
+ {
+ return false;
+ }
+
+ // Don't hoist a conditional branch into the scratch block; we'd prefer it stay
+ // either BBJ_NONE or BBJ_ALWAYS.
+ if (fgBBisScratch(bJump))
+ {
+ return false;
+ }
+
+ BasicBlock* bDest = bJump->bbJumpDest;
+
+ if (bDest->bbJumpKind != BBJ_COND)
+ {
+ return false;
+ }
+
+ if (bDest->bbJumpDest != bJump->bbNext)
+ {
+ return false;
+ }
+
+ // 'bJump' must be in the same try region as the condition, since we're going to insert
+ // a duplicated condition in 'bJump', and the condition might include exception throwing code.
+ if (!BasicBlock::sameTryRegion(bJump, bDest))
+ {
+ return false;
+ }
+
+ // do not jump into another try region
+ BasicBlock* bDestNext = bDest->bbNext;
+ if (bDestNext->hasTryIndex() && !BasicBlock::sameTryRegion(bJump, bDestNext))
+ {
+ return false;
+ }
+
+ // This function is only called by fgReorderBlocks, which we do not run in the backend.
+ // If we wanted to run block reordering in the backend, we would need to be able to
+ // calculate cost information for LIR on a per-node basis in order for this function
+ // to work.
+ assert(!bJump->IsLIR());
+ assert(!bDest->IsLIR());
+
+ GenTreeStmt* stmt;
+ unsigned estDupCostSz = 0;
+ for (stmt = bDest->firstStmt(); stmt; stmt = stmt->gtNextStmt)
+ {
+ GenTreePtr expr = stmt->gtStmtExpr;
+
+ /* We call gtPrepareCost to measure the cost of duplicating this tree */
+ gtPrepareCost(expr);
+
+ estDupCostSz += expr->gtCostSz;
+ }
+
+ bool allProfileWeightsAreValid = false;
+ BasicBlock::weight_t weightJump = bJump->bbWeight;
+ BasicBlock::weight_t weightDest = bDest->bbWeight;
+ BasicBlock::weight_t weightNext = bJump->bbNext->bbWeight;
+ bool rareJump = bJump->isRunRarely();
+ bool rareDest = bDest->isRunRarely();
+ bool rareNext = bJump->bbNext->isRunRarely();
+
+ // If we have profile data then we calculate the number of time
+ // the loop will iterate into loopIterations
+ if (fgIsUsingProfileWeights())
+ {
+ // Only rely upon the profile weight when all three of these blocks
+ // have either good profile weights or are rarelyRun
+ //
+ if ((bJump->bbFlags & (BBF_PROF_WEIGHT | BBF_RUN_RARELY)) &&
+ (bDest->bbFlags & (BBF_PROF_WEIGHT | BBF_RUN_RARELY)) &&
+ (bJump->bbNext->bbFlags & (BBF_PROF_WEIGHT | BBF_RUN_RARELY)))
+ {
+ allProfileWeightsAreValid = true;
+
+ if ((weightJump * 100) < weightDest)
+ {
+ rareJump = true;
+ }
+
+ if ((weightNext * 100) < weightDest)
+ {
+ rareNext = true;
+ }
+
+ if (((weightDest * 100) < weightJump) && ((weightDest * 100) < weightNext))
+ {
+ rareDest = true;
+ }
+ }
+ }
+
+ unsigned maxDupCostSz = 6;
+
+ //
+ // Branches between the hot and rarely run regions
+ // should be minimized. So we allow a larger size
+ //
+ if (rareDest != rareJump)
+ {
+ maxDupCostSz += 6;
+ }
+
+ if (rareDest != rareNext)
+ {
+ maxDupCostSz += 6;
+ }
+
+ //
+ // We we are ngen-ing:
+ // If the uncondional branch is a rarely run block then
+ // we are willing to have more code expansion since we
+ // won't be running code from this page
+ //
+ if (opts.eeFlags & CORJIT_FLG_PREJIT)
+ {
+ if (rareJump)
+ {
+ maxDupCostSz *= 2;
+ }
+ }
+
+ // If the compare has too high cost then we don't want to dup
+
+ bool costIsTooHigh = (estDupCostSz > maxDupCostSz);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nDuplication of the conditional block BB%02u (always branch from BB%02u) %s, because the cost of "
+ "duplication (%i) is %s than %i,"
+ " validProfileWeights = %s\n",
+ bDest->bbNum, bJump->bbNum, costIsTooHigh ? "not done" : "performed", estDupCostSz,
+ costIsTooHigh ? "greater" : "less or equal", maxDupCostSz, allProfileWeightsAreValid ? "true" : "false");
+ }
+#endif // DEBUG
+
+ if (costIsTooHigh)
+ {
+ return false;
+ }
+
+ /* Looks good - duplicate the conditional block */
+
+ GenTree* newStmtList = nullptr; // new stmt list to be added to bJump
+ GenTree* newStmtLast = nullptr;
+ bool cloneExprFailed = false;
+
+ /* Visit all the statements in bDest */
+
+ for (GenTree* curStmt = bDest->bbTreeList; curStmt; curStmt = curStmt->gtNext)
+ {
+ /* Clone/substitute the expression */
+
+ stmt = gtCloneExpr(curStmt)->AsStmt();
+
+ // cloneExpr doesn't handle everything
+
+ if (stmt == nullptr)
+ {
+ cloneExprFailed = true;
+ break;
+ }
+
+ /* Append the expression to our list */
+
+ if (newStmtList != nullptr)
+ {
+ newStmtLast->gtNext = stmt;
+ }
+ else
+ {
+ newStmtList = stmt;
+ }
+
+ stmt->gtPrev = newStmtLast;
+ newStmtLast = stmt;
+ }
+
+ if (cloneExprFailed)
+ {
+ return false;
+ }
+
+ noway_assert(newStmtLast != nullptr);
+ noway_assert(stmt != nullptr);
+ noway_assert(stmt->gtOper == GT_STMT);
+
+ if ((newStmtLast == nullptr) || (stmt == nullptr) || (stmt->gtOper != GT_STMT))
+ {
+ return false;
+ }
+
+ /* Get to the condition node from the statement tree */
+
+ GenTreePtr condTree = stmt->gtStmtExpr;
+ noway_assert(condTree->gtOper == GT_JTRUE);
+
+ if (condTree->gtOper != GT_JTRUE)
+ {
+ return false;
+ }
+
+ //
+ // Set condTree to the operand to the GT_JTRUE
+ //
+ condTree = condTree->gtOp.gtOp1;
+
+ //
+ // This condTree has to be a RelOp comparison
+ //
+ if (condTree->OperIsCompare() == false)
+ {
+ return false;
+ }
+
+ // Bump up the ref-counts of any variables in 'stmt'
+ fgUpdateRefCntForClone(bJump, stmt->gtStmtExpr);
+
+ //
+ // Find the last statement in the bJump block
+ //
+ GenTreeStmt* lastStmt = nullptr;
+ for (stmt = bJump->firstStmt(); stmt; stmt = stmt->gtNextStmt)
+ {
+ lastStmt = stmt;
+ }
+ stmt = bJump->firstStmt();
+
+ /* Join the two linked lists */
+ newStmtLast->gtNext = nullptr;
+
+ if (lastStmt != nullptr)
+ {
+ stmt->gtPrev = newStmtLast;
+ lastStmt->gtNext = newStmtList;
+ newStmtList->gtPrev = lastStmt;
+ }
+ else
+ {
+ bJump->bbTreeList = newStmtList;
+ newStmtList->gtPrev = newStmtLast;
+ }
+
+ //
+ // Reverse the sense of the compare
+ //
+ gtReverseCond(condTree);
+
+ bJump->bbJumpKind = BBJ_COND;
+ bJump->bbJumpDest = bDest->bbNext;
+
+ /* Mark the jump dest block as being a jump target */
+ bJump->bbJumpDest->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL;
+
+ // We need to update the following flags of the bJump block if they were set in the bbJumpDest block
+ bJump->bbFlags |= (bJump->bbJumpDest->bbFlags &
+ (BBF_HAS_NEWOBJ | BBF_HAS_NEWARRAY | BBF_HAS_NULLCHECK | BBF_HAS_IDX_LEN | BBF_HAS_VTABREF));
+
+ /* Update bbRefs and bbPreds */
+
+ // bJump now falls through into the next block
+ //
+ fgAddRefPred(bJump->bbNext, bJump);
+
+ // bJump no longer jumps to bDest
+ //
+ fgRemoveRefPred(bDest, bJump);
+
+ // bJump now jumps to bDest->bbNext
+ //
+ fgAddRefPred(bDest->bbNext, bJump);
+
+ if (weightJump > 0)
+ {
+ if (allProfileWeightsAreValid)
+ {
+ if (weightDest > weightJump)
+ {
+ bDest->bbWeight = (weightDest - weightJump);
+ }
+ else if (!bDest->isRunRarely())
+ {
+ bDest->bbWeight = BB_UNITY_WEIGHT;
+ }
+ }
+ else
+ {
+ BasicBlock::weight_t newWeightDest = 0;
+ BasicBlock::weight_t unloopWeightDest = 0;
+
+ if (weightDest > weightJump)
+ {
+ newWeightDest = (weightDest - weightJump);
+ }
+ if (weightDest >= (BB_LOOP_WEIGHT * BB_UNITY_WEIGHT) / 2)
+ {
+ newWeightDest = (weightDest * 2) / (BB_LOOP_WEIGHT * BB_UNITY_WEIGHT);
+ }
+ if ((newWeightDest > 0) || (unloopWeightDest > 0))
+ {
+ bDest->bbWeight = Max(newWeightDest, unloopWeightDest);
+ }
+ }
+ }
+
+#if DEBUG
+ if (verbose)
+ {
+ printf("\nAfter this change in fgOptimizeBranch");
+ fgDispBasicBlocks(verboseTrees);
+ printf("\n");
+ }
+#endif // DEBUG
+
+ return true;
+}
+
+/*****************************************************************************
+ *
+ * Function called to optimize switch statements
+ */
+
+bool Compiler::fgOptimizeSwitchJumps()
+{
+ bool result = false; // Our return value
+
+#if 0
+ // TODO-CQ: Add switch jump optimizations?
+ if (!fgHasSwitch)
+ return false;
+
+ if (!fgHaveValidEdgeWeights)
+ return false;
+
+ for (BasicBlock* bSrc = fgFirstBB; bSrc != NULL; bSrc = bSrc->bbNext)
+ {
+ if (bSrc->bbJumpKind == BBJ_SWITCH)
+ {
+ unsigned jumpCnt; jumpCnt = bSrc->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab; jumpTab = bSrc->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ BasicBlock* bDst = *jumpTab;
+ flowList* edgeToDst = fgGetPredForBlock(bDst, bSrc);
+ double outRatio = (double) edgeToDst->flEdgeWeightMin / (double) bSrc->bbWeight;
+
+ if (outRatio >= 0.60)
+ {
+ // straighten switch here...
+ }
+ }
+ while (++jumpTab, --jumpCnt);
+ }
+ }
+#endif
+
+ return result;
+}
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable : 21000) // Suppress PREFast warning about overly large function
+#endif
+/*****************************************************************************
+ *
+ * Function called to reorder the flowgraph of BasicBlocks such that any
+ * rarely run blocks are placed at the end of the block list.
+ * If we have profile information we also use that information to reverse
+ * all conditional jumps that would benefit.
+ */
+
+void Compiler::fgReorderBlocks()
+{
+ noway_assert(opts.compDbgCode == false);
+
+#if FEATURE_EH_FUNCLETS
+ assert(fgFuncletsCreated);
+#endif // FEATURE_EH_FUNCLETS
+
+ // We can't relocate anything if we only have one block
+ if (fgFirstBB->bbNext == nullptr)
+ {
+ return;
+ }
+
+ bool newRarelyRun = false;
+ bool movedBlocks = false;
+ bool optimizedSwitches = false;
+
+ // First let us expand the set of run rarely blocks
+ newRarelyRun |= fgExpandRarelyRunBlocks();
+
+#if !FEATURE_EH_FUNCLETS
+ movedBlocks |= fgRelocateEHRegions();
+#endif // !FEATURE_EH_FUNCLETS
+
+ //
+ // If we are using profile weights we can change some
+ // switch jumps into conditional test and jump
+ //
+ if (fgIsUsingProfileWeights())
+ {
+ //
+ // Note that this is currently not yet implemented
+ //
+ optimizedSwitches = fgOptimizeSwitchJumps();
+ if (optimizedSwitches)
+ {
+ fgUpdateFlowGraph();
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgReorderBlocks()\n");
+
+ printf("\nInitial BasicBlocks");
+ fgDispBasicBlocks(verboseTrees);
+ printf("\n");
+ }
+#endif // DEBUG
+
+ BasicBlock* bNext;
+ BasicBlock* bPrev;
+ BasicBlock* block;
+ unsigned XTnum;
+ EHblkDsc* HBtab;
+
+ // Iterate over every block, remembering our previous block in bPrev
+ for (bPrev = fgFirstBB, block = bPrev->bbNext; block != nullptr; bPrev = block, block = block->bbNext)
+ {
+ //
+ // Consider relocating the rarely run blocks such that they are at the end of the method.
+ // We also consider reversing conditional branches so that they become a not taken forwards branch.
+ //
+
+ // If block is marked with a BBF_KEEP_BBJ_ALWAYS flag then we don't move the block
+ if ((block->bbFlags & BBF_KEEP_BBJ_ALWAYS) != 0)
+ {
+ continue;
+ }
+
+ // Finally and handlers blocks are to be kept contiguous.
+ // TODO-CQ: Allow reordering within the handler region
+ if (block->hasHndIndex() == true)
+ {
+ continue;
+ }
+
+ bool reorderBlock = true; // This is set to false if we decide not to reorder 'block'
+ bool isRare = block->isRunRarely();
+ BasicBlock* bDest = nullptr;
+ bool forwardBranch = false;
+ bool backwardBranch = false;
+
+ // Setup bDest
+ if ((bPrev->bbJumpKind == BBJ_COND) || (bPrev->bbJumpKind == BBJ_ALWAYS))
+ {
+ bDest = bPrev->bbJumpDest;
+ forwardBranch = fgIsForwardBranch(bPrev);
+ backwardBranch = !forwardBranch;
+ }
+
+ // We will look for bPrev as a non rarely run block followed by block as a rarely run block
+ //
+ if (bPrev->isRunRarely())
+ {
+ reorderBlock = false;
+ }
+
+ // If the weights of the bPrev, block and bDest were all obtained from a profile run
+ // then we can use them to decide if it is useful to reverse this conditional branch
+
+ BasicBlock::weight_t profHotWeight = -1;
+
+ if ((bPrev->bbFlags & BBF_PROF_WEIGHT) && (block->bbFlags & BBF_PROF_WEIGHT) &&
+ ((bDest == nullptr) || (bDest->bbFlags & BBF_PROF_WEIGHT)))
+ {
+ //
+ // All blocks have profile information
+ //
+ if (forwardBranch)
+ {
+ if (bPrev->bbJumpKind == BBJ_ALWAYS)
+ {
+ // We can pull up the blocks that the unconditional jump branches to
+ // if the weight of bDest is greater or equal to the weight of block
+ // also the weight of bDest can't be zero.
+ //
+ if ((bDest->bbWeight < block->bbWeight) || (bDest->bbWeight == 0))
+ {
+ reorderBlock = false;
+ }
+ else
+ {
+ //
+ // If this remains true then we will try to pull up bDest to succeed bPrev
+ //
+ bool moveDestUp = true;
+
+ if (fgHaveValidEdgeWeights)
+ {
+ //
+ // The edge bPrev -> bDest must have a higher minimum weight
+ // than every other edge into bDest
+ //
+ flowList* edgeFromPrev = fgGetPredForBlock(bDest, bPrev);
+ noway_assert(edgeFromPrev != nullptr);
+
+ // Examine all of the other edges into bDest
+ for (flowList* edge = bDest->bbPreds; edge != nullptr; edge = edge->flNext)
+ {
+ if (edge != edgeFromPrev)
+ {
+ if (edge->flEdgeWeightMax >= edgeFromPrev->flEdgeWeightMin)
+ {
+ moveDestUp = false;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ //
+ // The block bPrev must have a higher weight
+ // than every other block that goes into bDest
+ //
+
+ // Examine all of the other edges into bDest
+ for (flowList* edge = bDest->bbPreds; edge != nullptr; edge = edge->flNext)
+ {
+ BasicBlock* bTemp = edge->flBlock;
+
+ if ((bTemp != bPrev) && (bTemp->bbWeight >= bPrev->bbWeight))
+ {
+ moveDestUp = false;
+ break;
+ }
+ }
+ }
+
+ // Are we still good to move bDest up to bPrev?
+ if (moveDestUp)
+ {
+ //
+ // We will consider all blocks that have less weight than profHotWeight to be
+ // uncommonly run blocks as compared with the hot path of bPrev taken-jump to bDest
+ //
+ profHotWeight = bDest->bbWeight - 1;
+ }
+ else
+ {
+ if (block->isRunRarely())
+ {
+ // We will move any rarely run blocks blocks
+ profHotWeight = 0;
+ }
+ else
+ {
+ // We will move all blocks that have a weight less or equal to our fall through block
+ profHotWeight = block->bbWeight + 1;
+ }
+ // But we won't try to connect with bDest
+ bDest = nullptr;
+ }
+ }
+ }
+ else // (bPrev->bbJumpKind == BBJ_COND)
+ {
+ noway_assert(bPrev->bbJumpKind == BBJ_COND);
+ //
+ // We will reverse branch if the taken-jump to bDest ratio (i.e. 'takenRatio')
+ // is more than 51%
+ //
+ // We will setup profHotWeight to be maximum bbWeight that a block
+ // could have for us not to want to reverse the conditional branch
+ //
+ // We will consider all blocks that have less weight than profHotWeight to be
+ // uncommonly run blocks as compared with the hot path of bPrev taken-jump to bDest
+ //
+ if (fgHaveValidEdgeWeights)
+ {
+ // We have valid edge weights, however even with valid edge weights
+ // we may have a minimum and maximum range for each edges value
+ //
+ // We will check that the min weight of the bPrev to bDest edge
+ // is more than twice the max weight of the bPrev to block edge.
+ //
+ // bPrev --> [BB04, weight 31]
+ // | \
+ // edgeToBlock -------------> O \
+ // [min=8,max=10] V \
+ // block --> [BB05, weight 10] \
+ // \
+ // edgeToDest ----------------------------> O
+ // [min=21,max=23] |
+ // V
+ // bDest ---------------> [BB08, weight 21]
+ //
+ flowList* edgeToDest = fgGetPredForBlock(bDest, bPrev);
+ flowList* edgeToBlock = fgGetPredForBlock(block, bPrev);
+ noway_assert(edgeToDest != nullptr);
+ noway_assert(edgeToBlock != nullptr);
+ //
+ // Calculate the taken ratio
+ // A takenRation of 0.10 means taken 10% of the time, not taken 90% of the time
+ // A takenRation of 0.50 means taken 50% of the time, not taken 50% of the time
+ // A takenRation of 0.90 means taken 90% of the time, not taken 10% of the time
+ //
+ double takenCount =
+ ((double)edgeToDest->flEdgeWeightMin + (double)edgeToDest->flEdgeWeightMax) / 2.0;
+ double notTakenCount =
+ ((double)edgeToBlock->flEdgeWeightMin + (double)edgeToBlock->flEdgeWeightMax) / 2.0;
+ double totalCount = takenCount + notTakenCount;
+ double takenRatio = takenCount / totalCount;
+
+ // If the takenRatio is greater or equal to 51% then we will reverse the branch
+ if (takenRatio < 0.51)
+ {
+ reorderBlock = false;
+ }
+ else
+ {
+ // set profHotWeight
+ profHotWeight = (edgeToBlock->flEdgeWeightMin + edgeToBlock->flEdgeWeightMax) / 2 - 1;
+ }
+ }
+ else
+ {
+ // We don't have valid edge weight so we will be more conservative
+ // We could have bPrev, block or bDest as part of a loop and thus have extra weight
+ //
+ // We will do two checks:
+ // 1. Check that the weight of bDest is at least two times more than block
+ // 2. Check that the weight of bPrev is at least three times more than block
+ //
+ // bPrev --> [BB04, weight 31]
+ // | \
+ // V \
+ // block --> [BB05, weight 10] \
+ // \
+ // |
+ // V
+ // bDest ---------------> [BB08, weight 21]
+ //
+ // For this case weightDest is calculated as (21+1)/2 or 11
+ // and weightPrev is calculated as (31+2)/3 also 11
+ //
+ // Generally both weightDest and weightPrev should calculate
+ // the same value unless bPrev or bDest are part of a loop
+ //
+ BasicBlock::weight_t weightDest =
+ bDest->isMaxBBWeight() ? bDest->bbWeight : (bDest->bbWeight + 1) / 2;
+ BasicBlock::weight_t weightPrev =
+ bPrev->isMaxBBWeight() ? bPrev->bbWeight : (bPrev->bbWeight + 2) / 3;
+
+ // select the lower of weightDest and weightPrev
+ profHotWeight = (weightDest < weightPrev) ? weightDest : weightPrev;
+
+ // if the weight of block is greater (or equal) to profHotWeight then we don't reverse the cond
+ if (block->bbWeight >= profHotWeight)
+ {
+ reorderBlock = false;
+ }
+ }
+ }
+ }
+ else // not a forwardBranch
+ {
+ if (bPrev->bbFallsThrough())
+ {
+ goto CHECK_FOR_RARE;
+ }
+
+ // Here we should pull up the highest weight block remaining
+ // and place it here since bPrev does not fall through.
+
+ BasicBlock::weight_t highestWeight = 0;
+ BasicBlock* candidateBlock = nullptr;
+ BasicBlock* lastNonFallThroughBlock = bPrev;
+ BasicBlock* bTmp = bPrev->bbNext;
+
+ while (bTmp != nullptr)
+ {
+ // Don't try to split a Call/Always pair
+ //
+ if (bTmp->isBBCallAlwaysPair())
+ {
+ // Move bTmp forward
+ bTmp = bTmp->bbNext;
+ }
+
+ //
+ // Check for loop exit condition
+ //
+ if (bTmp == nullptr)
+ {
+ break;
+ }
+
+ //
+ // if its weight is the highest one we've seen and
+ // the EH regions allow for us to place bTmp after bPrev
+ //
+ if ((bTmp->bbWeight > highestWeight) && fgEhAllowsMoveBlock(bPrev, bTmp))
+ {
+ // When we have a current candidateBlock that is a conditional (or unconditional) jump
+ // to bTmp (which is a higher weighted block) then it is better to keep out current
+ // candidateBlock and have it fall into bTmp
+ //
+ if ((candidateBlock == nullptr) ||
+ ((candidateBlock->bbJumpKind != BBJ_COND) && (candidateBlock->bbJumpKind != BBJ_ALWAYS)) ||
+ (candidateBlock->bbJumpDest != bTmp))
+ {
+ // otherwise we have a new candidateBlock
+ //
+ highestWeight = bTmp->bbWeight;
+ candidateBlock = lastNonFallThroughBlock->bbNext;
+ }
+ }
+
+ if ((bTmp->bbFallsThrough() == false) || (bTmp->bbWeight == 0))
+ {
+ lastNonFallThroughBlock = bTmp;
+ }
+
+ bTmp = bTmp->bbNext;
+ }
+
+ // If we didn't find a suitable block then skip this
+ if (highestWeight == 0)
+ {
+ reorderBlock = false;
+ }
+ else
+ {
+ noway_assert(candidateBlock != nullptr);
+
+ // If the candidateBlock is the same a block then skip this
+ if (candidateBlock == block)
+ {
+ reorderBlock = false;
+ }
+ else
+ {
+ // Set bDest to the block that we want to come after bPrev
+ bDest = candidateBlock;
+
+ // set profHotWeight
+ profHotWeight = highestWeight - 1;
+ }
+ }
+ }
+ }
+ else // we don't have good profile info (or we are falling through)
+ {
+
+ CHECK_FOR_RARE:;
+
+ /* We only want to reorder when we have a rarely run */
+ /* block right after a normal block, */
+ /* (bPrev is known to be a normal block at this point) */
+ if (!isRare)
+ {
+ reorderBlock = false;
+ }
+ else
+ {
+ /* If the jump target bDest is also a rarely run block then we don't want to do the reversal */
+ if (bDest && bDest->isRunRarely())
+ {
+ reorderBlock = false; /* Both block and bDest are rarely run */
+ }
+ else
+ {
+ // We will move any rarely run blocks blocks
+ profHotWeight = 0;
+ }
+ }
+ }
+
+ if (reorderBlock == false)
+ {
+ //
+ // Check for an unconditional branch to a conditional branch
+ // which also branches back to our next block
+ //
+ if (fgOptimizeBranch(bPrev))
+ {
+ noway_assert(bPrev->bbJumpKind == BBJ_COND);
+ }
+ continue;
+ }
+
+ // Now we need to determine which blocks should be moved
+ //
+ // We consider one of two choices:
+ //
+ // 1. Moving the fall-through blocks (or rarely run blocks) down to
+ // later in the method and hopefully connecting the jump dest block
+ // so that it becomes the fall through block
+ //
+ // And when bDest in not NULL, we also consider:
+ //
+ // 2. Moving the bDest block (or blocks) up to bPrev
+ // so that it could be used as a fall through block
+ //
+ // We will prefer option #1 if we are able to connect the jump dest
+ // block as the fall though block otherwise will we try to use option #2
+ //
+
+ //
+ // Consider option #1: relocating blocks starting at 'block'
+ // to later in flowgraph
+ //
+ // We set bStart to the first block that will be relocated
+ // and bEnd to the last block that will be relocated
+
+ BasicBlock* bStart = block;
+ BasicBlock* bEnd = bStart;
+ bNext = bEnd->bbNext;
+ bool connected_bDest = false;
+
+ if ((backwardBranch && !isRare) ||
+ ((block->bbFlags & BBF_DONT_REMOVE) != 0)) // Don't choose option #1 when block is the start of a try region
+ {
+ bStart = nullptr;
+ bEnd = nullptr;
+ }
+ else
+ {
+ while (true)
+ {
+ // Don't try to split a Call/Always pair
+ //
+ if (bEnd->isBBCallAlwaysPair())
+ {
+ // Move bEnd and bNext forward
+ bEnd = bNext;
+ bNext = bNext->bbNext;
+ }
+
+ //
+ // Check for loop exit condition
+ //
+ if (bNext == nullptr)
+ {
+ break;
+ }
+
+#if FEATURE_EH_FUNCLETS
+ // Check if we've reached the funclets region, at the end of the function
+ if (fgFirstFuncletBB == bEnd->bbNext)
+ {
+ break;
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ if (bNext == bDest)
+ {
+ connected_bDest = true;
+ break;
+ }
+
+ // All the blocks must have the same try index
+ // and must not have the BBF_DONT_REMOVE flag set
+
+ if (!BasicBlock::sameTryRegion(bStart, bNext) || ((bNext->bbFlags & BBF_DONT_REMOVE) != 0))
+ {
+ // exit the loop, bEnd is now set to the
+ // last block that we want to relocate
+ break;
+ }
+
+ // If we are relocating rarely run blocks..
+ if (isRare)
+ {
+ // ... then all blocks must be rarely run
+ if (!bNext->isRunRarely())
+ {
+ // exit the loop, bEnd is now set to the
+ // last block that we want to relocate
+ break;
+ }
+ }
+ else
+ {
+ // If we are moving blocks that are hot then all
+ // of the blocks moved must be less than profHotWeight */
+ if (bNext->bbWeight >= profHotWeight)
+ {
+ // exit the loop, bEnd is now set to the
+ // last block that we would relocate
+ break;
+ }
+ }
+
+ // Move bEnd and bNext forward
+ bEnd = bNext;
+ bNext = bNext->bbNext;
+ }
+
+ // Set connected_bDest to true if moving blocks [bStart .. bEnd]
+ // connects with the the jump dest of bPrev (i.e bDest) and
+ // thus allows bPrev fall through instead of jump.
+ if (bNext == bDest)
+ {
+ connected_bDest = true;
+ }
+ }
+
+ // Now consider option #2: Moving the jump dest block (or blocks)
+ // up to bPrev
+ //
+ // The variables bStart2, bEnd2 and bPrev2 are used for option #2
+ //
+ // We will setup bStart2 to the first block that will be relocated
+ // and bEnd2 to the last block that will be relocated
+ // and bPrev2 to be the lexical pred of bDest
+ //
+ // If after this calculation bStart2 is NULL we cannot use option #2,
+ // otherwise bStart2, bEnd2 and bPrev2 are all non-NULL and we will use option #2
+
+ BasicBlock* bStart2 = nullptr;
+ BasicBlock* bEnd2 = nullptr;
+ BasicBlock* bPrev2 = nullptr;
+
+ // If option #1 didn't connect bDest and bDest isn't NULL
+ if ((connected_bDest == false) && (bDest != nullptr) &&
+ // The jump target cannot be moved if it has the BBF_DONT_REMOVE flag set
+ ((bDest->bbFlags & BBF_DONT_REMOVE) == 0))
+ {
+ // We will consider option #2: relocating blocks starting at 'bDest' to succeed bPrev
+ //
+ // setup bPrev2 to be the lexical pred of bDest
+
+ bPrev2 = block;
+ while (bPrev2 != nullptr)
+ {
+ if (bPrev2->bbNext == bDest)
+ {
+ break;
+ }
+
+ bPrev2 = bPrev2->bbNext;
+ }
+
+ if ((bPrev2 != nullptr) && fgEhAllowsMoveBlock(bPrev, bDest))
+ {
+ // We have decided that relocating bDest to be after bPrev is best
+ // Set bStart2 to the first block that will be relocated
+ // and bEnd2 to the last block that will be relocated
+ //
+ // Assigning to bStart2 selects option #2
+ //
+ bStart2 = bDest;
+ bEnd2 = bStart2;
+ bNext = bEnd2->bbNext;
+
+ while (true)
+ {
+ // Don't try to split a Call/Always pair
+ //
+ if (bEnd2->isBBCallAlwaysPair())
+ {
+ noway_assert(bNext->bbJumpKind == BBJ_ALWAYS);
+ // Move bEnd2 and bNext forward
+ bEnd2 = bNext;
+ bNext = bNext->bbNext;
+ }
+
+ // Check for the Loop exit conditions
+
+ if (bNext == nullptr)
+ {
+ break;
+ }
+
+ if (bEnd2->bbFallsThrough() == false)
+ {
+ break;
+ }
+
+ // If we are relocating rarely run blocks..
+ // All the blocks must have the same try index,
+ // and must not have the BBF_DONT_REMOVE flag set
+
+ if (!BasicBlock::sameTryRegion(bStart2, bNext) || ((bNext->bbFlags & BBF_DONT_REMOVE) != 0))
+ {
+ // exit the loop, bEnd2 is now set to the
+ // last block that we want to relocate
+ break;
+ }
+
+ if (isRare)
+ {
+ /* ... then all blocks must not be rarely run */
+ if (bNext->isRunRarely())
+ {
+ // exit the loop, bEnd2 is now set to the
+ // last block that we want to relocate
+ break;
+ }
+ }
+ else
+ {
+ // If we are relocating hot blocks
+ // all blocks moved must be greater than profHotWeight
+ if (bNext->bbWeight <= profHotWeight)
+ {
+ // exit the loop, bEnd2 is now set to the
+ // last block that we want to relocate
+ break;
+ }
+ }
+
+ // Move bEnd2 and bNext forward
+ bEnd2 = bNext;
+ bNext = bNext->bbNext;
+ }
+ }
+ }
+
+ // If we are using option #1 then ...
+ if (bStart2 == nullptr)
+ {
+ // Don't use option #1 for a backwards branch
+ if (bStart == nullptr)
+ {
+ continue;
+ }
+
+ // .... Don't move a set of blocks that are already at the end of the main method
+ if (bEnd == fgLastBBInMainFunction())
+ {
+ continue;
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ if (bDest != nullptr)
+ {
+ if (bPrev->bbJumpKind == BBJ_COND)
+ {
+ printf("Decided to reverse conditional branch at block BB%02u branch to BB%02u ", bPrev->bbNum,
+ bDest->bbNum);
+ }
+ else if (bPrev->bbJumpKind == BBJ_ALWAYS)
+ {
+ printf("Decided to straighten unconditional branch at block BB%02u branch to BB%02u ", bPrev->bbNum,
+ bDest->bbNum);
+ }
+ else
+ {
+ printf("Decided to place hot code after BB%02u, placed BB%02u after this block ", bPrev->bbNum,
+ bDest->bbNum);
+ }
+
+ if (profHotWeight > 0)
+ {
+ printf("because of IBC profile data\n");
+ }
+ else
+ {
+ if (bPrev->bbFallsThrough())
+ {
+ printf("since it falls into a rarely run block\n");
+ }
+ else
+ {
+ printf("since it is succeeded by a rarely run block\n");
+ }
+ }
+ }
+ else
+ {
+ printf("Decided to relocate block(s) after block BB%02u since they are %s block(s)\n", bPrev->bbNum,
+ block->isRunRarely() ? "rarely run" : "uncommonly run");
+ }
+ }
+#endif // DEBUG
+
+ // We will set insertAfterBlk to the block the precedes our insertion range
+ // We will set bStartPrev to be the block that precedes the set of blocks that we are moving
+ BasicBlock* insertAfterBlk;
+ BasicBlock* bStartPrev;
+
+ if (bStart2 != nullptr)
+ {
+ // Option #2: relocating blocks starting at 'bDest' to follow bPrev
+
+ // Update bStart and bEnd so that we can use these two for all later operations
+ bStart = bStart2;
+ bEnd = bEnd2;
+
+ // Set bStartPrev to be the block that comes before bStart
+ bStartPrev = bPrev2;
+
+ // We will move [bStart..bEnd] to immediately after bPrev
+ insertAfterBlk = bPrev;
+ }
+ else
+ {
+ // option #1: Moving the fall-through blocks (or rarely run blocks) down to later in the method
+
+ // Set bStartPrev to be the block that come before bStart
+ bStartPrev = bPrev;
+
+ // We will move [bStart..bEnd] but we will pick the insert location later
+ insertAfterBlk = nullptr;
+ }
+
+ // We are going to move [bStart..bEnd] so they can't be NULL
+ noway_assert(bStart != nullptr);
+ noway_assert(bEnd != nullptr);
+
+ // bEnd can't be a BBJ_CALLFINALLY unless it is a RETLESS call
+ noway_assert((bEnd->bbJumpKind != BBJ_CALLFINALLY) || (bEnd->bbFlags & BBF_RETLESS_CALL));
+
+ // bStartPrev must be set to the block that precedes bStart
+ noway_assert(bStartPrev->bbNext == bStart);
+
+ // Since we will be unlinking [bStart..bEnd],
+ // we need to compute and remember if bStart is in each of
+ // the try and handler regions
+ //
+ bool* fStartIsInTry = nullptr;
+ bool* fStartIsInHnd = nullptr;
+
+ if (compHndBBtabCount > 0)
+ {
+ fStartIsInTry = new (this, CMK_Unknown) bool[compHndBBtabCount];
+ fStartIsInHnd = new (this, CMK_Unknown) bool[compHndBBtabCount];
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ fStartIsInTry[XTnum] = HBtab->InTryRegionBBRange(bStart);
+ fStartIsInHnd[XTnum] = HBtab->InHndRegionBBRange(bStart);
+ }
+ }
+
+ /* Temporarily unlink [bStart..bEnd] from the flow graph */
+ fgUnlinkRange(bStart, bEnd);
+
+ if (insertAfterBlk == nullptr)
+ {
+ // Find new location for the unlinked block(s)
+ // Set insertAfterBlk to the block which will precede the insertion point
+
+ if (!bStart->hasTryIndex() && isRare)
+ {
+ // We'll just insert the blocks at the end of the method. If the method
+ // has funclets, we will insert at the end of the main method but before
+ // any of the funclets. Note that we create funclets before we call
+ // fgReorderBlocks().
+
+ insertAfterBlk = fgLastBBInMainFunction();
+ noway_assert(insertAfterBlk != bPrev);
+ }
+ else
+ {
+ BasicBlock* startBlk;
+ BasicBlock* lastBlk;
+ EHblkDsc* ehDsc = ehInitTryBlockRange(bStart, &startBlk, &lastBlk);
+
+ BasicBlock* endBlk;
+
+ /* Setup startBlk and endBlk as the range to search */
+
+ if (ehDsc != nullptr)
+ {
+ endBlk = lastBlk->bbNext;
+
+ /*
+ Multiple (nested) try regions might start from the same BB.
+ For example,
+
+ try3 try2 try1
+ |--- |--- |--- BB01
+ | | | BB02
+ | | |--- BB03
+ | | BB04
+ | |------------ BB05
+ | BB06
+ |------------------- BB07
+
+ Now if we want to insert in try2 region, we will start with startBlk=BB01.
+ The following loop will allow us to start from startBlk==BB04.
+ */
+ while (!BasicBlock::sameTryRegion(startBlk, bStart) && (startBlk != endBlk))
+ {
+ startBlk = startBlk->bbNext;
+ }
+
+ // startBlk cannot equal endBlk as it must come before endBlk
+ if (startBlk == endBlk)
+ {
+ goto CANNOT_MOVE;
+ }
+
+ // we also can't start searching the try region at bStart
+ if (startBlk == bStart)
+ {
+ // if bEnd is the last block in the method or
+ // or if bEnd->bbNext is in a different try region
+ // then we cannot move the blocks
+ //
+ if ((bEnd->bbNext == nullptr) || !BasicBlock::sameTryRegion(startBlk, bEnd->bbNext))
+ {
+ goto CANNOT_MOVE;
+ }
+
+ startBlk = bEnd->bbNext;
+
+ // Check that the new startBlk still comes before endBlk
+
+ // startBlk cannot equal endBlk as it must come before endBlk
+ if (startBlk == endBlk)
+ {
+ goto CANNOT_MOVE;
+ }
+
+ BasicBlock* tmpBlk = startBlk;
+ while ((tmpBlk != endBlk) && (tmpBlk != nullptr))
+ {
+ tmpBlk = tmpBlk->bbNext;
+ }
+
+ // when tmpBlk is NULL that means startBlk is after endBlk
+ // so there is no way to move bStart..bEnd within the try region
+ if (tmpBlk == nullptr)
+ {
+ goto CANNOT_MOVE;
+ }
+ }
+ }
+ else
+ {
+ noway_assert(isRare == false);
+
+ /* We'll search through the entire main method */
+ startBlk = fgFirstBB;
+ endBlk = fgEndBBAfterMainFunction();
+ }
+
+ // Calculate nearBlk and jumpBlk and then call fgFindInsertPoint()
+ // to find our insertion block
+ //
+ {
+ // If the set of blocks that we are moving ends with a BBJ_ALWAYS to
+ // another [rarely run] block that comes after bPrev (forward branch)
+ // then we can set up nearBlk to eliminate this jump sometimes
+ //
+ BasicBlock* nearBlk = nullptr;
+ BasicBlock* jumpBlk = nullptr;
+
+ if ((bEnd->bbJumpKind == BBJ_ALWAYS) && (!isRare || bEnd->bbJumpDest->isRunRarely()) &&
+ fgIsForwardBranch(bEnd, bPrev))
+ {
+ // Set nearBlk to be the block in [startBlk..endBlk]
+ // such that nearBlk->bbNext == bEnd->JumpDest
+ // if no such block exists then set nearBlk to NULL
+ nearBlk = startBlk;
+ jumpBlk = bEnd;
+ do
+ {
+ // We do not want to set nearBlk to bPrev
+ // since then we will not move [bStart..bEnd]
+ //
+ if (nearBlk != bPrev)
+ {
+ // Check if nearBlk satisfies our requirement
+ if (nearBlk->bbNext == bEnd->bbJumpDest)
+ {
+ break;
+ }
+ }
+
+ // Did we reach the endBlk?
+ if (nearBlk == endBlk)
+ {
+ nearBlk = nullptr;
+ break;
+ }
+
+ // advance nearBlk to the next block
+ nearBlk = nearBlk->bbNext;
+
+ } while (nearBlk != nullptr);
+ }
+
+ // if nearBlk is NULL then we set nearBlk to be the
+ // first block that we want to insert after.
+ if (nearBlk == nullptr)
+ {
+ if (bDest != nullptr)
+ {
+ // we want to insert after bDest
+ nearBlk = bDest;
+ }
+ else
+ {
+ // we want to insert after bPrev
+ nearBlk = bPrev;
+ }
+ }
+
+ /* Set insertAfterBlk to the block which we will insert after. */
+
+ insertAfterBlk =
+ fgFindInsertPoint(bStart->bbTryIndex,
+ true, // Insert in the try region.
+ startBlk, endBlk, nearBlk, jumpBlk, bStart->bbWeight == BB_ZERO_WEIGHT);
+ }
+
+ /* See if insertAfterBlk is the same as where we started, */
+ /* or if we could not find any insertion point */
+
+ if ((insertAfterBlk == bPrev) || (insertAfterBlk == nullptr))
+ {
+ CANNOT_MOVE:;
+ /* We couldn't move the blocks, so put everything back */
+ /* relink [bStart .. bEnd] into the flow graph */
+
+ bPrev->setNext(bStart);
+ if (bEnd->bbNext)
+ {
+ bEnd->bbNext->bbPrev = bEnd;
+ }
+#ifdef DEBUG
+ if (verbose)
+ {
+ if (bStart != bEnd)
+ {
+ printf("Could not relocate blocks (BB%02u .. BB%02u)\n", bStart->bbNum, bEnd->bbNum);
+ }
+ else
+ {
+ printf("Could not relocate block BB%02u\n", bStart->bbNum);
+ }
+ }
+#endif // DEBUG
+ continue;
+ }
+ }
+ }
+
+ noway_assert(insertAfterBlk != nullptr);
+ noway_assert(bStartPrev != nullptr);
+ noway_assert(bStartPrev != insertAfterBlk);
+
+#ifdef DEBUG
+ movedBlocks = true;
+
+ if (verbose)
+ {
+ const char* msg;
+ if (bStart2 != nullptr)
+ {
+ msg = "hot";
+ }
+ else
+ {
+ if (isRare)
+ {
+ msg = "rarely run";
+ }
+ else
+ {
+ msg = "uncommon";
+ }
+ }
+
+ printf("Relocated %s ", msg);
+ if (bStart != bEnd)
+ {
+ printf("blocks (BB%02u .. BB%02u)", bStart->bbNum, bEnd->bbNum);
+ }
+ else
+ {
+ printf("block BB%02u", bStart->bbNum);
+ }
+
+ if (bPrev->bbJumpKind == BBJ_COND)
+ {
+ printf(" by reversing conditional jump at BB%02u\n", bPrev->bbNum);
+ }
+ else
+ {
+ printf("\n", bPrev->bbNum);
+ }
+ }
+#endif // DEBUG
+
+ if (bPrev->bbJumpKind == BBJ_COND)
+ {
+ /* Reverse the bPrev jump condition */
+ GenTree* condTest = bPrev->lastStmt();
+
+ condTest = condTest->gtStmt.gtStmtExpr;
+ noway_assert(condTest->gtOper == GT_JTRUE);
+
+ condTest->gtOp.gtOp1 = gtReverseCond(condTest->gtOp.gtOp1);
+
+ if (bStart2 == nullptr)
+ {
+ /* Set the new jump dest for bPrev to the rarely run or uncommon block(s) */
+ bPrev->bbJumpDest = bStart;
+ bStart->bbFlags |= (BBF_JMP_TARGET | BBF_HAS_LABEL);
+ }
+ else
+ {
+ noway_assert(insertAfterBlk == bPrev);
+ noway_assert(insertAfterBlk->bbNext == block);
+
+ /* Set the new jump dest for bPrev to the rarely run or uncommon block(s) */
+ bPrev->bbJumpDest = block;
+ block->bbFlags |= (BBF_JMP_TARGET | BBF_HAS_LABEL);
+ }
+ }
+
+ // If we are moving blocks that are at the end of a try or handler
+ // we will need to shorten ebdTryLast or ebdHndLast
+ //
+ ehUpdateLastBlocks(bEnd, bStartPrev);
+
+ // If we are moving blocks into the end of a try region or handler region
+ // we will need to extend ebdTryLast or ebdHndLast so the blocks that we
+ // are moving are part of this try or handler region.
+ //
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ // Are we moving blocks to the end of a try region?
+ if (HBtab->ebdTryLast == insertAfterBlk)
+ {
+ if (fStartIsInTry[XTnum])
+ {
+ // bStart..bEnd is in the try, so extend the try region
+ fgSetTryEnd(HBtab, bEnd);
+ }
+ }
+
+ // Are we moving blocks to the end of a handler region?
+ if (HBtab->ebdHndLast == insertAfterBlk)
+ {
+ if (fStartIsInHnd[XTnum])
+ {
+ // bStart..bEnd is in the handler, so extend the handler region
+ fgSetHndEnd(HBtab, bEnd);
+ }
+ }
+ }
+
+ /* We have decided to insert the block(s) after 'insertAfterBlk' */
+ fgMoveBlocksAfter(bStart, bEnd, insertAfterBlk);
+
+ if (bDest)
+ {
+ /* We may need to insert an unconditional branch after bPrev to bDest */
+ fgConnectFallThrough(bPrev, bDest);
+ }
+ else
+ {
+ /* If bPrev falls through, we must insert a jump to block */
+ fgConnectFallThrough(bPrev, block);
+ }
+
+ BasicBlock* bSkip = bEnd->bbNext;
+
+ /* If bEnd falls through, we must insert a jump to bNext */
+ fgConnectFallThrough(bEnd, bNext);
+
+ if (bStart2 == nullptr)
+ {
+ /* If insertAfterBlk falls through, we are forced to */
+ /* add a jump around the block(s) we just inserted */
+ fgConnectFallThrough(insertAfterBlk, bSkip);
+ }
+ else
+ {
+ /* We may need to insert an unconditional branch after bPrev2 to bStart */
+ fgConnectFallThrough(bPrev2, bStart);
+ }
+
+#if DEBUG
+ if (verbose)
+ {
+ printf("\nAfter this change in fgReorderBlocks");
+ fgDispBasicBlocks(verboseTrees);
+ printf("\n");
+ }
+ fgVerifyHandlerTab();
+
+ // Make sure that the predecessor lists are accurate
+ if (expensiveDebugCheckLevel >= 2)
+ {
+ fgDebugCheckBBlist();
+ }
+#endif // DEBUG
+
+ // Set our iteration point 'block' to be the new bPrev->bbNext
+ // It will be used as the next bPrev
+ block = bPrev->bbNext;
+
+ } // end of for loop(bPrev,block)
+
+ bool changed = movedBlocks || newRarelyRun || optimizedSwitches;
+
+ if (changed)
+ {
+ fgNeedsUpdateFlowGraph = true;
+#if DEBUG
+ // Make sure that the predecessor lists are accurate
+ if (expensiveDebugCheckLevel >= 2)
+ {
+ fgDebugCheckBBlist();
+ }
+#endif // DEBUG
+ }
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+/*-------------------------------------------------------------------------
+ *
+ * Walk the basic blocks list to determine the first block to place in the
+ * cold section. This would be the first of a series of rarely executed blocks
+ * such that no succeeding blocks are in a try region or an exception handler
+ * or are rarely executed.
+ */
+
+void Compiler::fgDetermineFirstColdBlock()
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** In fgDetermineFirstColdBlock()\n");
+ }
+#endif // DEBUG
+
+ // Since we may need to create a new transistion block
+ // we assert that it is OK to create new blocks.
+ //
+ assert(fgSafeBasicBlockCreation);
+
+ fgFirstColdBlock = nullptr;
+
+#if FEATURE_STACK_FP_X87
+ if (compMayHaveTransitionBlocks)
+ {
+ opts.compProcedureSplitting = false;
+
+ // See comment above declaration of compMayHaveTransitionBlocks for comments on this
+ JITDUMP("Turning off procedure splitting for this method, as it may end up having FP transition blocks\n");
+ }
+#endif // FEATURE_STACK_FP_X87
+
+ if (!opts.compProcedureSplitting)
+ {
+ JITDUMP("No procedure splitting will be done for this method\n");
+ return;
+ }
+
+#ifdef DEBUG
+ if ((compHndBBtabCount > 0) && !opts.compProcedureSplittingEH)
+ {
+ JITDUMP("No procedure splitting will be done for this method with EH (by request)\n");
+ return;
+ }
+#endif // DEBUG
+
+#if FEATURE_EH_FUNCLETS
+ // TODO-CQ: handle hot/cold splitting in functions with EH (including synchronized methods
+ // that create EH in methods without explicit EH clauses).
+
+ if (compHndBBtabCount > 0)
+ {
+ JITDUMP("No procedure splitting will be done for this method with EH (implementation limitation)\n");
+ return;
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ BasicBlock* firstColdBlock = nullptr;
+ BasicBlock* prevToFirstColdBlock = nullptr;
+ BasicBlock* block;
+ BasicBlock* lblk;
+
+ for (lblk = nullptr, block = fgFirstBB; block != nullptr; lblk = block, block = block->bbNext)
+ {
+ bool blockMustBeInHotSection = false;
+
+#if HANDLER_ENTRY_MUST_BE_IN_HOT_SECTION
+ if (bbIsHandlerBeg(block))
+ {
+ blockMustBeInHotSection = true;
+ }
+#endif // HANDLER_ENTRY_MUST_BE_IN_HOT_SECTION
+
+ // Do we have a candidate for the first cold block?
+ if (firstColdBlock != nullptr)
+ {
+ // We have a candidate for first cold block
+
+ // Is this a hot block?
+ if (blockMustBeInHotSection || (block->isRunRarely() == false))
+ {
+ // We have to restart the search for the first cold block
+ firstColdBlock = nullptr;
+ prevToFirstColdBlock = nullptr;
+ }
+ }
+ else // (firstColdBlock == NULL)
+ {
+ // We don't have a candidate for first cold block
+
+ // Is this a cold block?
+ if (!blockMustBeInHotSection && (block->isRunRarely() == true))
+ {
+ //
+ // If the last block that was hot was a BBJ_COND
+ // then we will have to add an unconditional jump
+ // so the code size for block needs be large
+ // enough to make it worth our while
+ //
+ if ((lblk == nullptr) || (lblk->bbJumpKind != BBJ_COND) || (fgGetCodeEstimate(block) >= 8))
+ {
+ // This block is now a candidate for first cold block
+ // Also remember the predecessor to this block
+ firstColdBlock = block;
+ prevToFirstColdBlock = lblk;
+ }
+ }
+ }
+ }
+
+ if (firstColdBlock == fgFirstBB)
+ {
+ // If the first block is Cold then we can't move any blocks
+ // into the cold section
+
+ firstColdBlock = nullptr;
+ }
+
+ if (firstColdBlock != nullptr)
+ {
+ noway_assert(prevToFirstColdBlock != nullptr);
+
+ if (prevToFirstColdBlock == nullptr)
+ {
+ return; // To keep Prefast happy
+ }
+
+ // If we only have one cold block
+ // then it may not be worth it to move it
+ // into the Cold section as a jump to the
+ // Cold section is 5 bytes in size.
+ //
+ if (firstColdBlock->bbNext == nullptr)
+ {
+ // If the size of the cold block is 7 or less
+ // then we will keep it in the Hot section.
+ //
+ if (fgGetCodeEstimate(firstColdBlock) < 8)
+ {
+ firstColdBlock = nullptr;
+ goto EXIT;
+ }
+ }
+
+ // When the last Hot block fall through into the Cold section
+ // we may need to add a jump
+ //
+ if (prevToFirstColdBlock->bbFallsThrough())
+ {
+ switch (prevToFirstColdBlock->bbJumpKind)
+ {
+ default:
+ noway_assert(!"Unhandled jumpkind in fgDetermineFirstColdBlock()");
+
+ case BBJ_CALLFINALLY:
+ // A BBJ_CALLFINALLY that falls through is always followed
+ // by an empty BBJ_ALWAYS.
+ //
+ assert(prevToFirstColdBlock->isBBCallAlwaysPair());
+ firstColdBlock =
+ firstColdBlock->bbNext; // Note that this assignment could make firstColdBlock == nullptr
+ break;
+
+ case BBJ_COND:
+ //
+ // This is a slightly more complicated case, because we will
+ // probably need to insert a block to jump to the cold section.
+ //
+ if (firstColdBlock->isEmpty() && (firstColdBlock->bbJumpKind == BBJ_ALWAYS))
+ {
+ // We can just use this block as the transitionBlock
+ firstColdBlock = firstColdBlock->bbNext;
+ // Note that this assignment could make firstColdBlock == NULL
+ }
+ else
+ {
+ BasicBlock* transitionBlock = fgNewBBafter(BBJ_ALWAYS, prevToFirstColdBlock, true);
+ transitionBlock->bbJumpDest = firstColdBlock;
+ transitionBlock->inheritWeight(firstColdBlock);
+
+ noway_assert(fgComputePredsDone);
+
+ // Update the predecessor list for firstColdBlock
+ fgReplacePred(firstColdBlock, prevToFirstColdBlock, transitionBlock);
+
+ // Add prevToFirstColdBlock as a predecessor for transitionBlock
+ fgAddRefPred(transitionBlock, prevToFirstColdBlock);
+ }
+ break;
+
+ case BBJ_NONE:
+ // If the block preceding the first cold block is BBJ_NONE,
+ // convert it to BBJ_ALWAYS to force an explicit jump.
+
+ prevToFirstColdBlock->bbJumpDest = firstColdBlock;
+ prevToFirstColdBlock->bbJumpKind = BBJ_ALWAYS;
+ break;
+ }
+ }
+ }
+
+ if (firstColdBlock != nullptr)
+ {
+ firstColdBlock->bbFlags |= BBF_JMP_TARGET;
+
+ for (block = firstColdBlock; block; block = block->bbNext)
+ {
+ block->bbFlags |= BBF_COLD;
+ }
+ }
+
+EXIT:;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ if (firstColdBlock)
+ {
+ printf("fgFirstColdBlock is BB%02u.\n", firstColdBlock->bbNum);
+ }
+ else
+ {
+ printf("fgFirstColdBlock is NULL.\n");
+ }
+
+ fgDispBasicBlocks();
+ }
+
+ fgVerifyHandlerTab();
+#endif // DEBUG
+
+ fgFirstColdBlock = firstColdBlock;
+}
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable : 21000) // Suppress PREFast warning about overly large function
+#endif
+/*****************************************************************************
+ *
+ * Function called to "comb" the basic block list.
+ * Removes any empty blocks, unreachable blocks and redundant jumps.
+ * Most of those appear after dead store removal and folding of conditionals.
+ *
+ * Returns: true if the flowgraph has been modified
+ *
+ * It also compacts basic blocks
+ * (consecutive basic blocks that should in fact be one).
+ *
+ * NOTE:
+ * Debuggable code and Min Optimization JIT also introduces basic blocks
+ * but we do not optimize those!
+ */
+
+bool Compiler::fgUpdateFlowGraph(bool doTailDuplication)
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** In fgUpdateFlowGraph()");
+ }
+#endif // DEBUG
+
+ /* This should never be called for debuggable code */
+
+ noway_assert(!opts.MinOpts() && !opts.compDbgCode);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nBefore updating the flow graph:\n");
+ fgDispBasicBlocks(verboseTrees);
+ printf("\n");
+ }
+#endif // DEBUG
+
+ /* Walk all the basic blocks - look for unconditional jumps, empty blocks, blocks to compact, etc...
+ *
+ * OBSERVATION:
+ * Once a block is removed the predecessors are not accurate (assuming they were at the beginning)
+ * For now we will only use the information in bbRefs because it is easier to be updated
+ */
+
+ bool modified = false;
+ bool change;
+ do
+ {
+ change = false;
+
+ BasicBlock* block; // the current block
+ BasicBlock* bPrev = nullptr; // the previous non-worthless block
+ BasicBlock* bNext; // the successor of the current block
+ BasicBlock* bDest; // the jump target of the current block
+
+ for (block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ /* Some blocks may be already marked removed by other optimizations
+ * (e.g worthless loop removal), without being explicitly removed
+ * from the list.
+ */
+
+ if (block->bbFlags & BBF_REMOVED)
+ {
+ if (bPrev)
+ {
+ bPrev->setNext(block->bbNext);
+ }
+ else
+ {
+ /* WEIRD first basic block is removed - should have an assert here */
+ noway_assert(!"First basic block marked as BBF_REMOVED???");
+
+ fgFirstBB = block->bbNext;
+ }
+ continue;
+ }
+
+ /* We jump to the REPEAT label if we performed a change involving the current block
+ * This is in case there are other optimizations that can show up
+ * (e.g. - compact 3 blocks in a row)
+ * If nothing happens, we then finish the iteration and move to the next block
+ */
+
+ REPEAT:;
+
+ bNext = block->bbNext;
+ bDest = nullptr;
+
+ if (block->bbJumpKind == BBJ_ALWAYS)
+ {
+ bDest = block->bbJumpDest;
+ if (doTailDuplication && fgOptimizeUncondBranchToSimpleCond(block, bDest))
+ {
+ change = true;
+ modified = true;
+ bDest = block->bbJumpDest;
+ bNext = block->bbNext;
+ }
+ }
+
+ // Remove JUMPS to the following block
+ // and optimize any JUMPS to JUMPS
+
+ if (block->bbJumpKind == BBJ_COND || block->bbJumpKind == BBJ_ALWAYS)
+ {
+ bDest = block->bbJumpDest;
+ if (bDest == bNext)
+ {
+ if (fgOptimizeBranchToNext(block, bNext, bPrev))
+ {
+ change = true;
+ modified = true;
+ bDest = nullptr;
+ }
+ }
+ }
+
+ if (bDest != nullptr)
+ {
+ // Do we have a JUMP to an empty unconditional JUMP block?
+ if (bDest->isEmpty() && (bDest->bbJumpKind == BBJ_ALWAYS) &&
+ (bDest != bDest->bbJumpDest)) // special case for self jumps
+ {
+ if (fgOptimizeBranchToEmptyUnconditional(block, bDest))
+ {
+ change = true;
+ modified = true;
+ goto REPEAT;
+ }
+ }
+
+ // Check for a conditional branch that just skips over an empty BBJ_ALWAYS block
+
+ if ((block->bbJumpKind == BBJ_COND) && // block is a BBJ_COND block
+ (bNext != nullptr) && // block is not the last block
+ (bNext->bbRefs == 1) && // No other block jumps to bNext
+ (bNext->bbNext == bDest) && // The block after bNext is the BBJ_COND jump dest
+ (bNext->bbJumpKind == BBJ_ALWAYS) && // The next block is a BBJ_ALWAYS block
+ bNext->isEmpty() && // and it is an an empty block
+ (bNext != bNext->bbJumpDest) && // special case for self jumps
+ (bDest != fgFirstColdBlock))
+ {
+ bool optimizeJump = true;
+
+ // We do not optimize jumps between two different try regions.
+ // However jumping to a block that is not in any try region is OK
+ //
+ if (bDest->hasTryIndex() && !BasicBlock::sameTryRegion(block, bDest))
+ {
+ optimizeJump = false;
+ }
+
+ // Also consider bNext's try region
+ //
+ if (bNext->hasTryIndex() && !BasicBlock::sameTryRegion(block, bNext))
+ {
+ optimizeJump = false;
+ }
+
+ // If we are optimizing using real profile weights
+ // then don't optimize a conditional jump to an unconditional jump
+ // until after we have computed the edge weights
+ //
+ if (fgIsUsingProfileWeights())
+ {
+ // if block and bdest are in different hot/cold regions we can't do this this optimization
+ // because we can't allow fall-through into the cold region.
+ if (!fgEdgeWeightsComputed || fgInDifferentRegions(block, bDest))
+ {
+ fgNeedsUpdateFlowGraph = true;
+ optimizeJump = false;
+ }
+ }
+
+ if (optimizeJump)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nReversing a conditional jump around an unconditional jump (BB%02u -> BB%02u -> "
+ "BB%02u)\n",
+ block->bbNum, bDest->bbNum, bNext->bbJumpDest->bbNum);
+ }
+#endif // DEBUG
+ /* Reverse the jump condition */
+
+ GenTree* test = block->lastNode();
+ noway_assert(test->gtOper == GT_JTRUE);
+
+ GenTree* cond = gtReverseCond(test->gtOp.gtOp1);
+ assert(cond == test->gtOp.gtOp1); // Ensure `gtReverseCond` did not create a new node.
+ test->gtOp.gtOp1 = cond;
+
+ // Optimize the Conditional JUMP to go to the new target
+ block->bbJumpDest = bNext->bbJumpDest;
+
+ fgAddRefPred(bNext->bbJumpDest, block, fgRemoveRefPred(bNext->bbJumpDest, bNext));
+
+ /*
+ Unlink bNext from the BasicBlock list; note that we can
+ do this even though other blocks could jump to it - the
+ reason is that elsewhere in this function we always
+ redirect jumps to jumps to jump to the final label,
+ so even if another block jumps to bNext it won't matter
+ once we're done since any such jump will be redirected
+ to the final target by the time we're done here.
+ */
+
+ fgRemoveRefPred(bNext, block);
+ fgUnlinkBlock(bNext);
+
+ /* Mark the block as removed */
+ bNext->bbFlags |= BBF_REMOVED;
+
+ // If this is the first Cold basic block update fgFirstColdBlock
+ if (bNext == fgFirstColdBlock)
+ {
+ fgFirstColdBlock = bNext->bbNext;
+ }
+
+ //
+ // If we removed the end of a try region or handler region
+ // we will need to update ebdTryLast or ebdHndLast.
+ //
+
+ EHblkDsc* HBtab;
+ EHblkDsc* HBtabEnd;
+
+ for (HBtab = compHndBBtab, HBtabEnd = compHndBBtab + compHndBBtabCount; HBtab < HBtabEnd;
+ HBtab++)
+ {
+ if ((HBtab->ebdTryLast == bNext) || (HBtab->ebdHndLast == bNext))
+ {
+ fgSkipRmvdBlocks(HBtab);
+ }
+ }
+
+ // we optimized this JUMP - goto REPEAT to catch similar cases
+ change = true;
+ modified = true;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nAfter reversing the jump:\n");
+ fgDispBasicBlocks(verboseTrees);
+ }
+#endif // DEBUG
+
+ /*
+ For a rare special case we cannot jump to REPEAT
+ as jumping to REPEAT will cause us to delete 'block'
+ because it currently appears to be unreachable. As
+ it is a self loop that only has a single bbRef (itself)
+ However since the unlinked bNext has additional bbRefs
+ (that we will later connect to 'block'), it is not really
+ unreachable.
+ */
+ if ((bNext->bbRefs > 0) && (bNext->bbJumpDest == block) && (block->bbRefs == 1))
+ {
+ continue;
+ }
+
+ goto REPEAT;
+ }
+ }
+ }
+
+ //
+ // Update the switch jump table such that it follows jumps to jumps:
+ //
+ if (block->bbJumpKind == BBJ_SWITCH)
+ {
+ if (fgOptimizeSwitchBranches(block))
+ {
+ change = true;
+ modified = true;
+ goto REPEAT;
+ }
+ }
+
+ noway_assert(!(block->bbFlags & BBF_REMOVED));
+
+ /* COMPACT blocks if possible */
+
+ if (fgCanCompactBlocks(block, bNext))
+ {
+ fgCompactBlocks(block, bNext);
+
+ /* we compacted two blocks - goto REPEAT to catch similar cases */
+ change = true;
+ modified = true;
+ goto REPEAT;
+ }
+
+ /* Remove unreachable or empty blocks - do not consider blocks marked BBF_DONT_REMOVE or genReturnBB block
+ * These include first and last block of a TRY, exception handlers and RANGE_CHECK_FAIL THROW blocks */
+
+ if ((block->bbFlags & BBF_DONT_REMOVE) == BBF_DONT_REMOVE || block == genReturnBB)
+ {
+ bPrev = block;
+ continue;
+ }
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ // Don't remove the BBJ_ALWAYS block of a BBJ_CALLFINALLY/BBJ_ALWAYS pair.
+ if (block->countOfInEdges() == 0 && bPrev->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ assert(bPrev->isBBCallAlwaysPair());
+ noway_assert(!(bPrev->bbFlags & BBF_RETLESS_CALL));
+ noway_assert(block->bbJumpKind == BBJ_ALWAYS);
+ bPrev = block;
+ continue;
+ }
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+
+ noway_assert(!block->bbCatchTyp);
+ noway_assert(!(block->bbFlags & BBF_TRY_BEG));
+
+ /* Remove unreachable blocks
+ *
+ * We'll look for blocks that have countOfInEdges() = 0 (blocks may become
+ * unreachable due to a BBJ_ALWAYS introduced by conditional folding for example)
+ */
+
+ if (block->countOfInEdges() == 0)
+ {
+ /* no references -> unreachable - remove it */
+ /* For now do not update the bbNum, do it at the end */
+
+ fgRemoveBlock(block, true);
+
+ change = true;
+ modified = true;
+
+ /* we removed the current block - the rest of the optimizations won't have a target
+ * continue with the next one */
+
+ continue;
+ }
+ else if (block->countOfInEdges() == 1)
+ {
+ switch (block->bbJumpKind)
+ {
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ if (block->bbJumpDest == block)
+ {
+ fgRemoveBlock(block, true);
+
+ change = true;
+ modified = true;
+
+ /* we removed the current block - the rest of the optimizations
+ * won't have a target so continue with the next block */
+
+ continue;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ noway_assert(!(block->bbFlags & BBF_REMOVED));
+
+ /* Remove EMPTY blocks */
+
+ if (block->isEmpty())
+ {
+ assert(bPrev == block->bbPrev);
+ if (fgOptimizeEmptyBlock(block))
+ {
+ change = true;
+ modified = true;
+ }
+
+ /* Have we removed the block? */
+
+ if (block->bbFlags & BBF_REMOVED)
+ {
+ /* block was removed - no change to bPrev */
+ continue;
+ }
+ }
+
+ /* Set the predecessor of the last reachable block
+ * If we removed the current block, the predecessor remains unchanged
+ * otherwise, since the current block is ok, it becomes the predecessor */
+
+ noway_assert(!(block->bbFlags & BBF_REMOVED));
+
+ bPrev = block;
+ }
+ } while (change);
+
+ fgNeedsUpdateFlowGraph = false;
+
+#ifdef DEBUG
+ if (verbose && modified)
+ {
+ printf("\nAfter updating the flow graph:\n");
+ fgDispBasicBlocks(verboseTrees);
+ fgDispHandlerTab();
+ }
+
+ if (compRationalIRForm)
+ {
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ LIR::AsRange(block).CheckLIR(this);
+ }
+ }
+
+ fgVerifyHandlerTab();
+ // Make sure that the predecessor lists are accurate
+ fgDebugCheckBBlist();
+ fgDebugCheckUpdate();
+#endif // DEBUG
+
+ return modified;
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+/*****************************************************************************
+ * Check that the flow graph is really updated
+ */
+
+#ifdef DEBUG
+
+void Compiler::fgDebugCheckUpdate()
+{
+ if (!compStressCompile(STRESS_CHK_FLOW_UPDATE, 30))
+ {
+ return;
+ }
+
+ /* We check for these conditions:
+ * no unreachable blocks -> no blocks have countOfInEdges() = 0
+ * no empty blocks -> no blocks have bbTreeList = 0
+ * no un-imported blocks -> no blocks have BBF_IMPORTED not set (this is
+ * kind of redundand with the above, but to make sure)
+ * no un-compacted blocks -> BBJ_NONE followed by block with no jumps to it (countOfInEdges() = 1)
+ */
+
+ BasicBlock* prev;
+ BasicBlock* block;
+ for (prev = nullptr, block = fgFirstBB; block != nullptr; prev = block, block = block->bbNext)
+ {
+ /* no unreachable blocks */
+
+ if ((block->countOfInEdges() == 0) && !(block->bbFlags & BBF_DONT_REMOVE)
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_)
+ // With funclets, we never get rid of the BBJ_ALWAYS part of a BBJ_CALLFINALLY/BBJ_ALWAYS pair,
+ // even if we can prove that the finally block never returns.
+ && (prev == NULL || block->bbJumpKind != BBJ_ALWAYS || !prev->isBBCallAlwaysPair())
+#endif // FEATURE_EH_FUNCLETS
+ )
+ {
+ noway_assert(!"Unreachable block not removed!");
+ }
+
+ /* no empty blocks */
+
+ if (block->isEmpty() && !(block->bbFlags & BBF_DONT_REMOVE))
+ {
+ switch (block->bbJumpKind)
+ {
+ case BBJ_CALLFINALLY:
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+ case BBJ_RETURN:
+ /* for BBJ_ALWAYS is probably just a GOTO, but will have to be treated */
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ /* These jump kinds are allowed to have empty tree lists */
+ break;
+
+ default:
+ /* it may be the case that the block had more than one reference to it
+ * so we couldn't remove it */
+
+ if (block->countOfInEdges() == 0)
+ {
+ noway_assert(!"Empty block not removed!");
+ }
+ break;
+ }
+ }
+
+ /* no un-imported blocks */
+
+ if (!(block->bbFlags & BBF_IMPORTED))
+ {
+ /* internal blocks do not count */
+
+ if (!(block->bbFlags & BBF_INTERNAL))
+ {
+ noway_assert(!"Non IMPORTED block not removed!");
+ }
+ }
+
+ bool prevIsCallAlwaysPair = ((prev != nullptr) && prev->isBBCallAlwaysPair());
+
+ // Check for an unnecessary jumps to the next block
+ bool doAssertOnJumpToNextBlock = false; // unless we have a BBJ_COND or BBJ_ALWAYS we can not assert
+
+ if (block->bbJumpKind == BBJ_COND)
+ {
+ // A conditional branch should never jump to the next block
+ // as it can be folded into a BBJ_NONE;
+ doAssertOnJumpToNextBlock = true;
+ }
+ else if (block->bbJumpKind == BBJ_ALWAYS)
+ {
+ // Generally we will want to assert if a BBJ_ALWAYS branches to the next block
+ doAssertOnJumpToNextBlock = true;
+
+ // If the BBF_KEEP_BBJ_ALWAYS flag is set we allow it to jump to the next block
+ if (block->bbFlags & BBF_KEEP_BBJ_ALWAYS)
+ {
+ doAssertOnJumpToNextBlock = false;
+ }
+
+ // A call/always pair is also allowed to jump to the next block
+ if (prevIsCallAlwaysPair)
+ {
+ doAssertOnJumpToNextBlock = false;
+ }
+
+ // We are allowed to have a branch from a hot 'block' to a cold 'bbNext'
+ //
+ if ((block->bbNext != nullptr) && fgInDifferentRegions(block, block->bbNext))
+ {
+ doAssertOnJumpToNextBlock = false;
+ }
+ }
+
+ if (doAssertOnJumpToNextBlock)
+ {
+ if (block->bbJumpDest == block->bbNext)
+ {
+ noway_assert(!"Unnecessary jump to the next block!");
+ }
+ }
+
+ /* Make sure BBF_KEEP_BBJ_ALWAYS is set correctly */
+
+ if ((block->bbJumpKind == BBJ_ALWAYS) && prevIsCallAlwaysPair)
+ {
+ noway_assert(block->bbFlags & BBF_KEEP_BBJ_ALWAYS);
+ }
+
+ /* For a BBJ_CALLFINALLY block we make sure that we are followed by */
+ /* an BBJ_ALWAYS block with BBF_INTERNAL set */
+ /* or that it's a BBF_RETLESS_CALL */
+ if (block->bbJumpKind == BBJ_CALLFINALLY)
+ {
+ assert((block->bbFlags & BBF_RETLESS_CALL) || block->isBBCallAlwaysPair());
+ }
+
+ /* no un-compacted blocks */
+
+ if (fgCanCompactBlocks(block, block->bbNext))
+ {
+ noway_assert(!"Found un-compacted blocks!");
+ }
+ }
+}
+
+#endif // DEBUG
+
+/*****************************************************************************
+ * We've inserted a new block before 'block' that should be part of the same EH region as 'block'.
+ * Update the EH table to make this so. Also, set the new block to have the right EH region data
+ * (copy the bbTryIndex, bbHndIndex, and bbCatchTyp from 'block' to the new predecessor, and clear
+ * 'bbCatchTyp' from 'block').
+ */
+void Compiler::fgExtendEHRegionBefore(BasicBlock* block)
+{
+ assert(block->bbPrev != nullptr);
+
+ BasicBlock* bPrev = block->bbPrev;
+
+ bPrev->copyEHRegion(block);
+
+ // The first block (and only the first block) of a handler has bbCatchTyp set
+ bPrev->bbCatchTyp = block->bbCatchTyp;
+ block->bbCatchTyp = BBCT_NONE;
+
+ EHblkDsc* HBtab;
+ EHblkDsc* HBtabEnd;
+
+ for (HBtab = compHndBBtab, HBtabEnd = compHndBBtab + compHndBBtabCount; HBtab < HBtabEnd; HBtab++)
+ {
+ /* Multiple pointers in EHblkDsc can point to same block. We can not early out after the first match. */
+ if (HBtab->ebdTryBeg == block)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("EH#%u: New first block of try: BB%02u\n", ehGetIndex(HBtab), bPrev->bbNum);
+ }
+#endif // DEBUG
+ HBtab->ebdTryBeg = bPrev;
+ bPrev->bbFlags |= BBF_TRY_BEG | BBF_DONT_REMOVE | BBF_HAS_LABEL;
+ // clear the TryBeg flag unless it begins another try region
+ if (!bbIsTryBeg(block))
+ {
+ block->bbFlags &= ~BBF_TRY_BEG;
+ }
+ }
+
+ if (HBtab->ebdHndBeg == block)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("EH#%u: New first block of handler: BB%02u\n", ehGetIndex(HBtab), bPrev->bbNum);
+ }
+#endif // DEBUG
+
+ // The first block of a handler has an artificial extra refcount. Transfer that to the new block.
+ assert(block->bbRefs > 0);
+ block->bbRefs--;
+
+ HBtab->ebdHndBeg = bPrev;
+ bPrev->bbFlags |= BBF_DONT_REMOVE | BBF_HAS_LABEL;
+ bPrev->bbRefs++;
+
+ // If this is a handler for a filter, the last block of the filter will end with
+ // a BBJ_EJFILTERRET block that has a bbJumpDest that jumps to the first block of
+ // it's handler. So we need to update it to keep things in sync.
+ //
+ if (HBtab->HasFilter())
+ {
+ BasicBlock* bFilterLast = HBtab->BBFilterLast();
+ assert(bFilterLast != nullptr);
+ assert(bFilterLast->bbJumpKind == BBJ_EHFILTERRET);
+ assert(bFilterLast->bbJumpDest == block);
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("EH#%u: Updating bbJumpDest for filter ret block: BB%02u => BB%02u\n", ehGetIndex(HBtab),
+ bFilterLast->bbNum, bPrev->bbNum);
+ }
+#endif // DEBUG
+ // Change the bbJumpDest for bFilterLast from the old first 'block' to the new first 'bPrev'
+ bFilterLast->bbJumpDest = bPrev;
+ }
+ }
+
+ if (HBtab->HasFilter() && (HBtab->ebdFilter == block))
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("EH#%u: New first block of filter: BB%02u\n", ehGetIndex(HBtab), bPrev->bbNum);
+ }
+#endif // DEBUG
+
+ // The first block of a filter has an artificial extra refcount. Transfer that to the new block.
+ assert(block->bbRefs > 0);
+ block->bbRefs--;
+
+ HBtab->ebdFilter = bPrev;
+ bPrev->bbFlags |= BBF_DONT_REMOVE | BBF_HAS_LABEL;
+ bPrev->bbRefs++;
+ }
+ }
+}
+
+/*****************************************************************************
+ * We've inserted a new block after 'block' that should be part of the same EH region as 'block'.
+ * Update the EH table to make this so. Also, set the new block to have the right EH region data.
+ */
+
+void Compiler::fgExtendEHRegionAfter(BasicBlock* block)
+{
+ BasicBlock* newBlk = block->bbNext;
+ assert(newBlk != nullptr);
+
+ newBlk->copyEHRegion(block);
+ newBlk->bbCatchTyp =
+ BBCT_NONE; // Only the first block of a catch has this set, and 'newBlk' can't be the first block of a catch.
+
+ // TODO-Throughput: if the block is not in an EH region, then we don't need to walk the EH table looking for 'last'
+ // block pointers to update.
+ ehUpdateLastBlocks(block, newBlk);
+}
+
+/*****************************************************************************
+ *
+ * Insert a BasicBlock before the given block.
+ */
+
+BasicBlock* Compiler::fgNewBBbefore(BBjumpKinds jumpKind, BasicBlock* block, bool extendRegion)
+{
+ // Create a new BasicBlock and chain it in
+
+ BasicBlock* newBlk = bbNewBasicBlock(jumpKind);
+ newBlk->bbFlags |= BBF_INTERNAL;
+
+ fgInsertBBbefore(block, newBlk);
+
+ newBlk->bbRefs = 0;
+
+ if (newBlk->bbFallsThrough() && block->isRunRarely())
+ {
+ newBlk->bbSetRunRarely();
+ }
+
+ if (extendRegion)
+ {
+ fgExtendEHRegionBefore(block);
+ }
+ else
+ {
+ // When extendRegion is false the caller is responsible for setting these two values
+ newBlk->setTryIndex(MAX_XCPTN_INDEX); // Note: this is still a legal index, just unlikely
+ newBlk->setHndIndex(MAX_XCPTN_INDEX); // Note: this is still a legal index, just unlikely
+ }
+
+ // We assume that if the block we are inserting before is in the cold region, then this new
+ // block will also be in the cold region.
+ newBlk->bbFlags |= (block->bbFlags & BBF_COLD);
+
+ return newBlk;
+}
+
+/*****************************************************************************
+ *
+ * Insert a BasicBlock after the given block.
+ */
+
+BasicBlock* Compiler::fgNewBBafter(BBjumpKinds jumpKind, BasicBlock* block, bool extendRegion)
+{
+ // Create a new BasicBlock and chain it in
+
+ BasicBlock* newBlk = bbNewBasicBlock(jumpKind);
+ newBlk->bbFlags |= BBF_INTERNAL;
+
+ fgInsertBBafter(block, newBlk);
+
+ newBlk->bbRefs = 0;
+
+ if (block->bbFallsThrough() && block->isRunRarely())
+ {
+ newBlk->bbSetRunRarely();
+ }
+
+ if (extendRegion)
+ {
+ fgExtendEHRegionAfter(block);
+ }
+ else
+ {
+ // When extendRegion is false the caller is responsible for setting these two values
+ newBlk->setTryIndex(MAX_XCPTN_INDEX); // Note: this is still a legal index, just unlikely
+ newBlk->setHndIndex(MAX_XCPTN_INDEX); // Note: this is still a legal index, just unlikely
+ }
+
+ // If the new block is in the cold region (because the block we are inserting after
+ // is in the cold region), mark it as such.
+ newBlk->bbFlags |= (block->bbFlags & BBF_COLD);
+
+ return newBlk;
+}
+
+/*****************************************************************************
+ * Inserts basic block before existing basic block.
+ *
+ * If insertBeforeBlk is in the funclet region, then newBlk will be in the funclet region.
+ * (If insertBeforeBlk is the first block of the funclet region, then 'newBlk' will be the
+ * new first block of the funclet region.)
+ */
+void Compiler::fgInsertBBbefore(BasicBlock* insertBeforeBlk, BasicBlock* newBlk)
+{
+ if (insertBeforeBlk->bbPrev)
+ {
+ fgInsertBBafter(insertBeforeBlk->bbPrev, newBlk);
+ }
+ else
+ {
+ newBlk->setNext(fgFirstBB);
+
+ fgFirstBB = newBlk;
+ newBlk->bbPrev = nullptr;
+ }
+
+#if FEATURE_EH_FUNCLETS
+
+ /* Update fgFirstFuncletBB if insertBeforeBlk is the first block of the funclet region. */
+
+ if (fgFirstFuncletBB == insertBeforeBlk)
+ {
+ fgFirstFuncletBB = newBlk;
+ }
+
+#endif // FEATURE_EH_FUNCLETS
+}
+
+/*****************************************************************************
+ * Inserts basic block after existing basic block.
+ *
+ * If insertBeforeBlk is in the funclet region, then newBlk will be in the funclet region.
+ * (It can't be used to insert a block as the first block of the funclet region).
+ */
+void Compiler::fgInsertBBafter(BasicBlock* insertAfterBlk, BasicBlock* newBlk)
+{
+ newBlk->bbNext = insertAfterBlk->bbNext;
+
+ if (insertAfterBlk->bbNext)
+ {
+ insertAfterBlk->bbNext->bbPrev = newBlk;
+ }
+
+ insertAfterBlk->bbNext = newBlk;
+ newBlk->bbPrev = insertAfterBlk;
+
+ if (fgLastBB == insertAfterBlk)
+ {
+ fgLastBB = newBlk;
+ assert(fgLastBB->bbNext == nullptr);
+ }
+}
+
+// We have two edges (bAlt => bCur) and (bCur => bNext).
+//
+// Returns true if the weight of (bAlt => bCur)
+// is greater than the weight of (bCur => bNext).
+// We compare the edge weights if we have valid edge weights
+// otherwise we compare blocks weights.
+//
+bool Compiler::fgIsBetterFallThrough(BasicBlock* bCur, BasicBlock* bAlt)
+{
+ // bCur can't be NULL and must be a fall through bbJumpKind
+ noway_assert(bCur != nullptr);
+ noway_assert(bCur->bbFallsThrough());
+ noway_assert(bAlt != nullptr);
+
+ // We only handle the cases when bAlt is a BBJ_ALWAYS or a BBJ_COND
+ if ((bAlt->bbJumpKind != BBJ_ALWAYS) && (bAlt->bbJumpKind != BBJ_COND))
+ {
+ return false;
+ }
+
+ // if bAlt doesn't jump to bCur it can't be a better fall through than bCur
+ if (bAlt->bbJumpDest != bCur)
+ {
+ return false;
+ }
+
+ // Currently bNext is the fall through for bCur
+ BasicBlock* bNext = bCur->bbNext;
+ noway_assert(bNext != nullptr);
+
+ // We will set result to true if bAlt is a better fall through than bCur
+ bool result;
+ if (fgHaveValidEdgeWeights)
+ {
+ // We will compare the edge weight for our two choices
+ flowList* edgeFromAlt = fgGetPredForBlock(bCur, bAlt);
+ flowList* edgeFromCur = fgGetPredForBlock(bNext, bCur);
+ noway_assert(edgeFromCur != nullptr);
+ noway_assert(edgeFromAlt != nullptr);
+
+ result = (edgeFromAlt->flEdgeWeightMin > edgeFromCur->flEdgeWeightMax);
+ }
+ else
+ {
+ if (bAlt->bbJumpKind == BBJ_ALWAYS)
+ {
+ // Our result is true if bAlt's weight is more than bCur's weight
+ result = (bAlt->bbWeight > bCur->bbWeight);
+ }
+ else
+ {
+ noway_assert(bAlt->bbJumpKind == BBJ_COND);
+ // Our result is true if bAlt's weight is more than twice bCur's weight
+ result = (bAlt->bbWeight > (2 * bCur->bbWeight));
+ }
+ }
+ return result;
+}
+
+//------------------------------------------------------------------------
+// fgCheckEHCanInsertAfterBlock: Determine if a block can be inserted after
+// 'blk' and legally be put in the EH region specified by 'regionIndex'. This
+// can be true if the most nested region the block is in is already 'regionIndex',
+// as we'll just extend the most nested region (and any region ending at the same block).
+// It can also be true if it is the end of (a set of) EH regions, such that
+// inserting the block and properly extending some EH regions (if necessary)
+// puts the block in the correct region. We only consider the case of extending
+// an EH region after 'blk' (that is, to include 'blk' and the newly insert block);
+// we don't consider inserting a block as the the first block of an EH region following 'blk'.
+//
+// Consider this example:
+//
+// try3 try2 try1
+// |--- | | BB01
+// | |--- | BB02
+// | | |--- BB03
+// | | | BB04
+// | |--- |--- BB05
+// | BB06
+// |----------------- BB07
+//
+// Passing BB05 and try1/try2/try3 as the region to insert into (as well as putInTryRegion==true)
+// will all return 'true'. Here are the cases:
+// 1. Insert into try1: the most nested EH region BB05 is in is already try1, so we can insert after
+// it and extend try1 (and try2).
+// 2. Insert into try2: we can extend try2, but leave try1 alone.
+// 3. Insert into try3: we can leave try1 and try2 alone, and put the new block just in try3. Note that
+// in this case, after we "loop outwards" in the EH nesting, we get to a place where we're in the middle
+// of the try3 region, not at the end of it.
+// In all cases, it is possible to put a block after BB05 and put it in any of these three 'try' regions legally.
+//
+// Filters are ignored; if 'blk' is in a filter, the answer will be false.
+//
+// Arguments:
+// blk - the BasicBlock we are checking to see if we can insert after.
+// regionIndex - the EH region we want to insert a block into. regionIndex is
+// in the range [0..compHndBBtabCount]; 0 means "main method".
+// putInTryRegion - 'true' if the new block should be inserted in the 'try' region of 'regionIndex'.
+// For regionIndex 0 (the "main method"), this should be 'true'.
+//
+// Return Value:
+// 'true' if a block can be inserted after 'blk' and put in EH region 'regionIndex', else 'false'.
+//
+bool Compiler::fgCheckEHCanInsertAfterBlock(BasicBlock* blk, unsigned regionIndex, bool putInTryRegion)
+{
+ assert(blk != nullptr);
+ assert(regionIndex <= compHndBBtabCount);
+
+ if (regionIndex == 0)
+ {
+ assert(putInTryRegion);
+ }
+
+ bool inTryRegion;
+ unsigned nestedRegionIndex = ehGetMostNestedRegionIndex(blk, &inTryRegion);
+
+ bool insertOK = true;
+ for (;;)
+ {
+ if (nestedRegionIndex == regionIndex)
+ {
+ // This block is in the region we want to be in. We can insert here if it's the right type of region.
+ // (If we want to be in the 'try' region, but the block is in the handler region, then inserting a
+ // new block after 'blk' can't put it in the 'try' region, and vice-versa, since we only consider
+ // extending regions after, not prepending to regions.)
+ // This check will be 'true' if we are trying to put something in the main function (as putInTryRegion
+ // must be 'true' if regionIndex is zero, and inTryRegion will also be 'true' if nestedRegionIndex is zero).
+ insertOK = (putInTryRegion == inTryRegion);
+ break;
+ }
+ else if (nestedRegionIndex == 0)
+ {
+ // The block is in the main function, but we want to put something in a nested region. We can't do that.
+ insertOK = false;
+ break;
+ }
+
+ assert(nestedRegionIndex > 0);
+ EHblkDsc* ehDsc = ehGetDsc(nestedRegionIndex - 1); // ehGetDsc uses [0..compHndBBtabCount) form.
+
+ if (inTryRegion)
+ {
+ if (blk != ehDsc->ebdTryLast)
+ {
+ // Not the last block? Then it must be somewhere else within the try region, so we can't insert here.
+ insertOK = false;
+ break; // exit the 'for' loop
+ }
+ }
+ else
+ {
+ // We ignore filters.
+ if (blk != ehDsc->ebdHndLast)
+ {
+ // Not the last block? Then it must be somewhere else within the handler region, so we can't insert
+ // here.
+ insertOK = false;
+ break; // exit the 'for' loop
+ }
+ }
+
+ // Things look good for this region; check the enclosing regions, if any.
+
+ nestedRegionIndex =
+ ehGetEnclosingRegionIndex(nestedRegionIndex - 1,
+ &inTryRegion); // ehGetEnclosingRegionIndex uses [0..compHndBBtabCount) form.
+
+ // Convert to [0..compHndBBtabCount] form.
+ nestedRegionIndex = (nestedRegionIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : nestedRegionIndex + 1;
+ } // end of for(;;)
+
+ return insertOK;
+}
+
+//------------------------------------------------------------------------
+// Finds the block closest to endBlk in the range [startBlk..endBlk) after which a block can be
+// inserted easily. Note that endBlk cannot be returned; its predecessor is the last block that can
+// be returned. The new block will be put in an EH region described by the arguments regionIndex,
+// putInTryRegion, startBlk, and endBlk (explained below), so it must be legal to place to put the
+// new block after the insertion location block, give it the specified EH region index, and not break
+// EH nesting rules. This function is careful to choose a block in the correct EH region. However,
+// it assumes that the new block can ALWAYS be placed at the end (just before endBlk). That means
+// that the caller must ensure that is true.
+//
+// Below are the possible cases for the arguments to this method:
+// 1. putInTryRegion == true and regionIndex > 0:
+// Search in the try region indicated by regionIndex.
+// 2. putInTryRegion == false and regionIndex > 0:
+// a. If startBlk is the first block of a filter and endBlk is the block after the end of the
+// filter (that is, the startBlk and endBlk match a filter bounds exactly), then choose a
+// location within this filter region. (Note that, due to IL rules, filters do not have any
+// EH nested within them.) Otherwise, filters are skipped.
+// b. Else, search in the handler region indicated by regionIndex.
+// 3. regionIndex = 0:
+// Search in the entire main method, excluding all EH regions. In this case, putInTryRegion must be true.
+//
+// This method makes sure to find an insertion point which would not cause the inserted block to
+// be put inside any inner try/filter/handler regions.
+//
+// The actual insertion occurs after the returned block. Note that the returned insertion point might
+// be the last block of a more nested EH region, because the new block will be inserted after the insertion
+// point, and will not extend the more nested EH region. For example:
+//
+// try3 try2 try1
+// |--- | | BB01
+// | |--- | BB02
+// | | |--- BB03
+// | | | BB04
+// | |--- |--- BB05
+// | BB06
+// |----------------- BB07
+//
+// for regionIndex==try3, putInTryRegion==true, we might return BB05, even though BB05 will have a try index
+// for try1 (the most nested 'try' region the block is in). That's because when we insert after BB05, the new
+// block will be in the correct, desired EH region, since try1 and try2 regions will not be extended to include
+// the inserted block. Furthermore, for regionIndex==try2, putInTryRegion==true, we can also return BB05. In this
+// case, when the new block is inserted, the try1 region remains the same, but we need extend region 'try2' to
+// include the inserted block. (We also need to check all parent regions as well, just in case any parent regions
+// also end on the same block, in which case we would also need to extend the parent regions. This is standard
+// procedure when inserting a block at the end of an EH region.)
+//
+// If nearBlk is non-nullptr then we return the closest block after nearBlk that will work best.
+//
+// We try to find a block in the appropriate region that is not a fallthrough block, so we can insert after it
+// without the need to insert a jump around the inserted block.
+//
+// Note that regionIndex is numbered the same as BasicBlock::bbTryIndex and BasicBlock::bbHndIndex, that is, "0" is
+// "main method" and otherwise is +1 from normal, so we can call, e.g., ehGetDsc(tryIndex - 1).
+//
+// Arguments:
+// regionIndex - the region index where the new block will be inserted. Zero means entire method;
+// non-zero means either a "try" or a "handler" region, depending on what putInTryRegion says.
+// putInTryRegion - 'true' to put the block in the 'try' region corresponding to 'regionIndex', 'false'
+// to put the block in the handler region. Should be 'true' if regionIndex==0.
+// startBlk - start block of range to search.
+// endBlk - end block of range to search (don't include this block in the range). Can be nullptr to indicate
+// the end of the function.
+// nearBlk - If non-nullptr, try to find an insertion location closely after this block. If nullptr, we insert
+// at the best location found towards the end of the acceptable block range.
+// jumpBlk - When nearBlk is set, this can be set to the block which jumps to bNext->bbNext (TODO: need to review
+// this?)
+// runRarely - true if the block being inserted is expected to be rarely run. This helps determine
+// the best place to put the new block, by putting in a place that has the same 'rarely run' characteristic.
+//
+// Return Value:
+// A block with the desired characteristics, so the new block will be inserted after this one.
+// If there is no suitable location, return nullptr. This should basically never happen.
+
+BasicBlock* Compiler::fgFindInsertPoint(unsigned regionIndex,
+ bool putInTryRegion,
+ BasicBlock* startBlk,
+ BasicBlock* endBlk,
+ BasicBlock* nearBlk,
+ BasicBlock* jumpBlk,
+ bool runRarely)
+{
+ noway_assert(startBlk != nullptr);
+ noway_assert(startBlk != endBlk);
+ noway_assert((regionIndex == 0 && putInTryRegion) || // Search in the main method
+ (putInTryRegion && regionIndex > 0 &&
+ startBlk->bbTryIndex == regionIndex) || // Search in the specified try region
+ (!putInTryRegion && regionIndex > 0 &&
+ startBlk->bbHndIndex == regionIndex)); // Search in the specified handler region
+
+#ifdef DEBUG
+ // Assert that startBlk precedes endBlk in the block list.
+ // We don't want to use bbNum to assert this condition, as we cannot depend on the block numbers being
+ // sequential at all times.
+ for (BasicBlock* b = startBlk; b != endBlk; b = b->bbNext)
+ {
+ assert(b != nullptr); // We reached the end of the block list, but never found endBlk.
+ }
+#endif // DEBUG
+
+ JITDUMP("fgFindInsertPoint(regionIndex=%u, putInTryRegion=%s, startBlk=BB%02u, endBlk=BB%02u, nearBlk=BB%02u, "
+ "jumpBlk=BB%02u, runRarely=%s)\n",
+ regionIndex, dspBool(putInTryRegion), startBlk->bbNum, (endBlk == nullptr) ? 0 : endBlk->bbNum,
+ (nearBlk == nullptr) ? 0 : nearBlk->bbNum, (jumpBlk == nullptr) ? 0 : jumpBlk->bbNum, dspBool(runRarely));
+
+ bool reachedNear = false; // Have we reached 'nearBlk' in our search? If not, we'll keep searching.
+ bool inFilter = false; // Are we in a filter region that we need to skip?
+ BasicBlock* bestBlk =
+ nullptr; // Set to the best insertion point we've found so far that meets all the EH requirements.
+ BasicBlock* goodBlk =
+ nullptr; // Set to an acceptable insertion point that we'll use if we don't find a 'best' option.
+ BasicBlock* blk;
+
+ if (nearBlk != nullptr)
+ {
+ // Does the nearBlk precede the startBlk?
+ for (blk = nearBlk; blk != nullptr; blk = blk->bbNext)
+ {
+ if (blk == startBlk)
+ {
+ reachedNear = true;
+ break;
+ }
+ else if (blk == endBlk)
+ {
+ break;
+ }
+ }
+ }
+
+ for (blk = startBlk; blk != endBlk; blk = blk->bbNext)
+ {
+ // The only way (blk == nullptr) could be true is if the caller passed an endBlk that preceded startBlk in the
+ // block list, or if endBlk isn't in the block list at all. In DEBUG, we'll instead hit the similar
+ // well-formedness assert earlier in this function.
+ noway_assert(blk != nullptr);
+
+ if (blk == nearBlk)
+ {
+ reachedNear = true;
+ }
+
+ if (blk->bbCatchTyp == BBCT_FILTER)
+ {
+ // Record the fact that we entered a filter region, so we don't insert into filters...
+ // Unless the caller actually wanted the block inserted in this exact filter region.
+ // Detect this by the fact that startBlk and endBlk point to the filter begin and end.
+ if (putInTryRegion || (blk != startBlk) || (startBlk != ehGetDsc(regionIndex - 1)->ebdFilter) ||
+ (endBlk != ehGetDsc(regionIndex - 1)->ebdHndBeg))
+ {
+ inFilter = true;
+ }
+ }
+ else if (blk->bbCatchTyp == BBCT_FILTER_HANDLER)
+ {
+ // Record the fact that we exited a filter region.
+ inFilter = false;
+ }
+
+ // Don't insert a block inside this filter region.
+ if (inFilter)
+ {
+ continue;
+ }
+
+ // Note that the new block will be inserted AFTER "blk". We check to make sure that doing so
+ // would put the block in the correct EH region. We make an assumption here that you can
+ // ALWAYS insert the new block before "endBlk" (that is, at the end of the search range)
+ // and be in the correct EH region. This is must be guaranteed by the caller (as it is by
+ // fgNewBBinRegion(), which passes the search range as an exact EH region block range).
+ // Because of this assumption, we only check the EH information for blocks before the last block.
+ if (blk->bbNext != endBlk)
+ {
+ // We are in the middle of the search range. We can't insert the new block in
+ // an inner try or handler region. We can, however, set the insertion
+ // point to the last block of an EH try/handler region, if the enclosing
+ // region is the region we wish to insert in. (Since multiple regions can
+ // end at the same block, we need to search outwards, checking that the
+ // block is the last block of every EH region out to the region we want
+ // to insert in.) This is especially useful for putting a call-to-finally
+ // block on AMD64 immediately after its corresponding 'try' block, so in the
+ // common case, we'll just fall through to it. For example:
+ //
+ // BB01
+ // BB02 -- first block of try
+ // BB03
+ // BB04 -- last block of try
+ // BB05 -- first block of finally
+ // BB06
+ // BB07 -- last block of handler
+ // BB08
+ //
+ // Assume there is only one try/finally, so BB01 and BB08 are in the "main function".
+ // For AMD64 call-to-finally, we'll want to insert the BBJ_CALLFINALLY in
+ // the main function, immediately after BB04. This allows us to do that.
+
+ if (!fgCheckEHCanInsertAfterBlock(blk, regionIndex, putInTryRegion))
+ {
+ // Can't insert here.
+ continue;
+ }
+ }
+
+ // Look for an insert location:
+ // 1. We want blocks that don't end with a fall through,
+ // 2. Also, when blk equals nearBlk we may want to insert here.
+ if (!blk->bbFallsThrough() || (blk == nearBlk))
+ {
+ bool updateBestBlk = true; // We will probably update the bestBlk
+
+ // If blk falls through then we must decide whether to use the nearBlk
+ // hint
+ if (blk->bbFallsThrough())
+ {
+ noway_assert(blk == nearBlk);
+ if (jumpBlk != nullptr)
+ {
+ updateBestBlk = fgIsBetterFallThrough(blk, jumpBlk);
+ }
+ else
+ {
+ updateBestBlk = false;
+ }
+ }
+
+ // If we already have a best block, see if the 'runRarely' flags influences
+ // our choice. If we want a runRarely insertion point, and the existing best
+ // block is run rarely but the current block isn't run rarely, then don't
+ // update the best block.
+ // TODO-CQ: We should also handle the reverse case, where runRarely is false (we
+ // want a non-rarely-run block), but bestBlock->isRunRarely() is true. In that
+ // case, we should update the block, also. Probably what we want is:
+ // (bestBlk->isRunRarely() != runRarely) && (blk->isRunRarely() == runRarely)
+ if (updateBestBlk && (bestBlk != nullptr) && runRarely && bestBlk->isRunRarely() && !blk->isRunRarely())
+ {
+ updateBestBlk = false;
+ }
+
+ if (updateBestBlk)
+ {
+ // We found a 'best' insertion location, so save it away.
+ bestBlk = blk;
+
+ // If we've reached nearBlk, we've satisfied all the criteria,
+ // so we're done.
+ if (reachedNear)
+ {
+ goto DONE;
+ }
+
+ // If we haven't reached nearBlk, keep looking for a 'best' location, just
+ // in case we'll find one at or after nearBlk. If no nearBlk was specified,
+ // we prefer inserting towards the end of the given range, so keep looking
+ // for more acceptable insertion locations.
+ }
+ }
+
+ // No need to update goodBlk after we have set bestBlk, but we could still find a better
+ // bestBlk, so keep looking.
+ if (bestBlk != nullptr)
+ {
+ continue;
+ }
+
+ // Set the current block as a "good enough" insertion point, if it meets certain criteria.
+ // We'll return this block if we don't find a "best" block in the search range. The block
+ // can't be a BBJ_CALLFINALLY of a BBJ_CALLFINALLY/BBJ_ALWAYS pair (since we don't want
+ // to insert anything between these two blocks). Otherwise, we can use it. However,
+ // if we'd previously chosen a BBJ_COND block, then we'd prefer the "good" block to be
+ // something else. We keep updating it until we've reached the 'nearBlk', to push it as
+ // close to endBlk as possible.
+ if (!blk->isBBCallAlwaysPair())
+ {
+ if (goodBlk == nullptr)
+ {
+ goodBlk = blk;
+ }
+ else if ((goodBlk->bbJumpKind == BBJ_COND) || (blk->bbJumpKind != BBJ_COND))
+ {
+ if ((blk == nearBlk) || !reachedNear)
+ {
+ goodBlk = blk;
+ }
+ }
+ }
+ }
+
+ // If we didn't find a non-fall_through block, then insert at the last good block.
+
+ if (bestBlk == nullptr)
+ {
+ bestBlk = goodBlk;
+ }
+
+DONE:;
+
+ return bestBlk;
+}
+
+//------------------------------------------------------------------------
+// Creates a new BasicBlock and inserts it in a specific EH region, given by 'tryIndex', 'hndIndex', and 'putInFilter'.
+//
+// If 'putInFilter' it true, then the block is inserted in the filter region given by 'hndIndex'. In this case, tryIndex
+// must be a less nested EH region (that is, tryIndex > hndIndex).
+//
+// Otherwise, the block is inserted in either the try region or the handler region, depending on which one is the inner
+// region. In other words, if the try region indicated by tryIndex is nested in the handler region indicated by
+// hndIndex,
+// then the new BB will be created in the try region. Vice versa.
+//
+// Note that tryIndex and hndIndex are numbered the same as BasicBlock::bbTryIndex and BasicBlock::bbHndIndex, that is,
+// "0" is "main method" and otherwise is +1 from normal, so we can call, e.g., ehGetDsc(tryIndex - 1).
+//
+// To be more specific, this function will create a new BB in one of the following 5 regions (if putInFilter is false):
+// 1. When tryIndex = 0 and hndIndex = 0:
+// The new BB will be created in the method region.
+// 2. When tryIndex != 0 and hndIndex = 0:
+// The new BB will be created in the try region indicated by tryIndex.
+// 3. When tryIndex == 0 and hndIndex != 0:
+// The new BB will be created in the handler region indicated by hndIndex.
+// 4. When tryIndex != 0 and hndIndex != 0 and tryIndex < hndIndex:
+// In this case, the try region is nested inside the handler region. Therefore, the new BB will be created
+// in the try region indicated by tryIndex.
+// 5. When tryIndex != 0 and hndIndex != 0 and tryIndex > hndIndex:
+// In this case, the handler region is nested inside the try region. Therefore, the new BB will be created
+// in the handler region indicated by hndIndex.
+//
+// Note that if tryIndex != 0 and hndIndex != 0 then tryIndex must not be equal to hndIndex (this makes sense because
+// if they are equal, you are asking to put the new block in both the try and handler, which is impossible).
+//
+// The BasicBlock will not be inserted inside an EH region that is more nested than the requested tryIndex/hndIndex
+// region (so the function is careful to skip more nested EH regions when searching for a place to put the new block).
+//
+// This function cannot be used to insert a block as the first block of any region. It always inserts a block after
+// an existing block in the given region.
+//
+// If nearBlk is nullptr, or the block is run rarely, then the new block is assumed to be run rarely.
+//
+// Arguments:
+// jumpKind - the jump kind of the new block to create.
+// tryIndex - the try region to insert the new block in, described above. This must be a number in the range
+// [0..compHndBBtabCount].
+// hndIndex - the handler region to insert the new block in, described above. This must be a number in the range
+// [0..compHndBBtabCount].
+// nearBlk - insert the new block closely after this block, if possible. If nullptr, put the new block anywhere
+// in the requested region.
+// putInFilter - put the new block in the filter region given by hndIndex, as described above.
+// runRarely - 'true' if the new block is run rarely.
+// insertAtEnd - 'true' if the block should be inserted at the end of the region. Note: this is currently only
+// implemented when inserting into the main function (not into any EH region).
+//
+// Return Value:
+// The new block.
+
+BasicBlock* Compiler::fgNewBBinRegion(BBjumpKinds jumpKind,
+ unsigned tryIndex,
+ unsigned hndIndex,
+ BasicBlock* nearBlk,
+ bool putInFilter /* = false */,
+ bool runRarely /* = false */,
+ bool insertAtEnd /* = false */)
+{
+ assert(tryIndex <= compHndBBtabCount);
+ assert(hndIndex <= compHndBBtabCount);
+
+ /* afterBlk is the block which will precede the newBB */
+ BasicBlock* afterBlk;
+
+ // start and end limit for inserting the block
+ BasicBlock* startBlk = nullptr;
+ BasicBlock* endBlk = nullptr;
+
+ bool putInTryRegion = true;
+ unsigned regionIndex = 0;
+
+ // First, figure out which region (the "try" region or the "handler" region) to put the newBB in.
+ if ((tryIndex == 0) && (hndIndex == 0))
+ {
+ assert(!putInFilter);
+
+ endBlk = fgEndBBAfterMainFunction(); // don't put new BB in funclet region
+
+ if (insertAtEnd || (nearBlk == nullptr))
+ {
+ /* We'll just insert the block at the end of the method, before the funclets */
+
+ afterBlk = fgLastBBInMainFunction();
+ goto _FoundAfterBlk;
+ }
+ else
+ {
+ // We'll search through the entire method
+ startBlk = fgFirstBB;
+ }
+
+ noway_assert(regionIndex == 0);
+ }
+ else
+ {
+ noway_assert(tryIndex > 0 || hndIndex > 0);
+ PREFIX_ASSUME(tryIndex <= compHndBBtabCount);
+ PREFIX_ASSUME(hndIndex <= compHndBBtabCount);
+
+ // Decide which region to put in, the "try" region or the "handler" region.
+ if (tryIndex == 0)
+ {
+ noway_assert(hndIndex > 0);
+ putInTryRegion = false;
+ }
+ else if (hndIndex == 0)
+ {
+ noway_assert(tryIndex > 0);
+ noway_assert(putInTryRegion);
+ assert(!putInFilter);
+ }
+ else
+ {
+ noway_assert(tryIndex > 0 && hndIndex > 0 && tryIndex != hndIndex);
+ putInTryRegion = (tryIndex < hndIndex);
+ }
+
+ if (putInTryRegion)
+ {
+ // Try region is the inner region.
+ // In other words, try region must be nested inside the handler region.
+ noway_assert(hndIndex == 0 || bbInHandlerRegions(hndIndex - 1, ehGetDsc(tryIndex - 1)->ebdTryBeg));
+ assert(!putInFilter);
+ }
+ else
+ {
+ // Handler region is the inner region.
+ // In other words, handler region must be nested inside the try region.
+ noway_assert(tryIndex == 0 || bbInTryRegions(tryIndex - 1, ehGetDsc(hndIndex - 1)->ebdHndBeg));
+ }
+
+ // Figure out the start and end block range to search for an insertion location. Pick the beginning and
+ // ending blocks of the target EH region (the 'endBlk' is one past the last block of the EH region, to make
+ // loop iteration easier). Note that, after funclets have been created (for FEATURE_EH_FUNCLETS),
+ // this linear block range will not include blocks of handlers for try/handler clauses nested within
+ // this EH region, as those blocks have been extracted as funclets. That is ok, though, because we don't
+ // want to insert a block in any nested EH region.
+
+ if (putInTryRegion)
+ {
+ // We will put the newBB in the try region.
+ EHblkDsc* ehDsc = ehGetDsc(tryIndex - 1);
+ startBlk = ehDsc->ebdTryBeg;
+ endBlk = ehDsc->ebdTryLast->bbNext;
+ regionIndex = tryIndex;
+ }
+ else if (putInFilter)
+ {
+ // We will put the newBB in the filter region.
+ EHblkDsc* ehDsc = ehGetDsc(hndIndex - 1);
+ startBlk = ehDsc->ebdFilter;
+ endBlk = ehDsc->ebdHndBeg;
+ regionIndex = hndIndex;
+ }
+ else
+ {
+ // We will put the newBB in the handler region.
+ EHblkDsc* ehDsc = ehGetDsc(hndIndex - 1);
+ startBlk = ehDsc->ebdHndBeg;
+ endBlk = ehDsc->ebdHndLast->bbNext;
+ regionIndex = hndIndex;
+ }
+
+ noway_assert(regionIndex > 0);
+ }
+
+ // Now find the insertion point.
+ afterBlk = fgFindInsertPoint(regionIndex, putInTryRegion, startBlk, endBlk, nearBlk, nullptr, runRarely);
+
+_FoundAfterBlk:;
+
+ /* We have decided to insert the block after 'afterBlk'. */
+ noway_assert(afterBlk != nullptr);
+
+ JITDUMP("fgNewBBinRegion(jumpKind=%u, tryIndex=%u, hndIndex=%u, putInFilter=%s, runRarely=%s, insertAtEnd=%s): "
+ "inserting after BB%02u\n",
+ jumpKind, tryIndex, hndIndex, dspBool(putInFilter), dspBool(runRarely), dspBool(insertAtEnd),
+ afterBlk->bbNum);
+
+ return fgNewBBinRegionWorker(jumpKind, afterBlk, regionIndex, putInTryRegion);
+}
+
+//------------------------------------------------------------------------
+// Creates a new BasicBlock and inserts it in the same EH region as 'srcBlk'.
+//
+// See the implementation of fgNewBBinRegion() used by this one for more notes.
+//
+// Arguments:
+// jumpKind - the jump kind of the new block to create.
+// srcBlk - insert the new block in the same EH region as this block, and closely after it if possible.
+//
+// Return Value:
+// The new block.
+
+BasicBlock* Compiler::fgNewBBinRegion(BBjumpKinds jumpKind,
+ BasicBlock* srcBlk,
+ bool runRarely /* = false */,
+ bool insertAtEnd /* = false */)
+{
+ assert(srcBlk != nullptr);
+
+ const unsigned tryIndex = srcBlk->bbTryIndex;
+ const unsigned hndIndex = srcBlk->bbHndIndex;
+ bool putInFilter = false;
+
+ // Check to see if we need to put the new block in a filter. We do if srcBlk is in a filter.
+ // This can only be true if there is a handler index, and the handler region is more nested than the
+ // try region (if any). This is because no EH regions can be nested within a filter.
+ if (BasicBlock::ehIndexMaybeMoreNested(hndIndex, tryIndex))
+ {
+ assert(hndIndex != 0); // If hndIndex is more nested, we must be in some handler!
+ putInFilter = ehGetDsc(hndIndex - 1)->InFilterRegionBBRange(srcBlk);
+ }
+
+ return fgNewBBinRegion(jumpKind, tryIndex, hndIndex, srcBlk, putInFilter, runRarely, insertAtEnd);
+}
+
+//------------------------------------------------------------------------
+// Creates a new BasicBlock and inserts it at the end of the function.
+//
+// See the implementation of fgNewBBinRegion() used by this one for more notes.
+//
+// Arguments:
+// jumpKind - the jump kind of the new block to create.
+//
+// Return Value:
+// The new block.
+
+BasicBlock* Compiler::fgNewBBinRegion(BBjumpKinds jumpKind)
+{
+ return fgNewBBinRegion(jumpKind, 0, 0, nullptr, /* putInFilter */ false, /* runRarely */ false,
+ /* insertAtEnd */ true);
+}
+
+//------------------------------------------------------------------------
+// Creates a new BasicBlock, and inserts it after 'afterBlk'.
+//
+// The block cannot be inserted into a more nested try/handler region than that specified by 'regionIndex'.
+// (It is given exactly 'regionIndex'.) Thus, the parameters must be passed to ensure proper EH nesting
+// rules are followed.
+//
+// Arguments:
+// jumpKind - the jump kind of the new block to create.
+// afterBlk - insert the new block after this one.
+// regionIndex - the block will be put in this EH region.
+// putInTryRegion - If true, put the new block in the 'try' region corresponding to 'regionIndex', and
+// set its handler index to the most nested handler region enclosing that 'try' region.
+// Otherwise, put the block in the handler region specified by 'regionIndex', and set its 'try'
+// index to the most nested 'try' region enclosing that handler region.
+//
+// Return Value:
+// The new block.
+
+BasicBlock* Compiler::fgNewBBinRegionWorker(BBjumpKinds jumpKind,
+ BasicBlock* afterBlk,
+ unsigned regionIndex,
+ bool putInTryRegion)
+{
+ /* Insert the new block */
+ BasicBlock* afterBlkNext = afterBlk->bbNext;
+ (void)afterBlkNext; // prevent "unused variable" error from GCC
+ BasicBlock* newBlk = fgNewBBafter(jumpKind, afterBlk, false);
+
+ if (putInTryRegion)
+ {
+ noway_assert(regionIndex <= MAX_XCPTN_INDEX);
+ newBlk->bbTryIndex = (unsigned short)regionIndex;
+ newBlk->bbHndIndex = bbFindInnermostHandlerRegionContainingTryRegion(regionIndex);
+ }
+ else
+ {
+ newBlk->bbTryIndex = bbFindInnermostTryRegionContainingHandlerRegion(regionIndex);
+ noway_assert(regionIndex <= MAX_XCPTN_INDEX);
+ newBlk->bbHndIndex = (unsigned short)regionIndex;
+ }
+
+ // We're going to compare for equal try regions (to handle the case of 'mutually protect'
+ // regions). We need to save off the current try region, otherwise we might change it
+ // before it gets compared later, thereby making future comparisons fail.
+
+ BasicBlock* newTryBeg;
+ BasicBlock* newTryLast;
+ (void)ehInitTryBlockRange(newBlk, &newTryBeg, &newTryLast);
+
+ unsigned XTnum;
+ EHblkDsc* HBtab;
+
+ for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++)
+ {
+ // Is afterBlk at the end of a try region?
+ if (HBtab->ebdTryLast == afterBlk)
+ {
+ noway_assert(afterBlkNext == newBlk->bbNext);
+
+ bool extendTryRegion = false;
+ if (newBlk->hasTryIndex())
+ {
+ // We're adding a block after the last block of some try region. Do
+ // we extend the try region to include the block, or not?
+ // If the try region is exactly the same as the try region
+ // associated with the new block (based on the block's try index,
+ // which represents the innermost try the block is a part of), then
+ // we extend it.
+ // If the try region is a "parent" try region -- an enclosing try region
+ // that has the same last block as the new block's try region -- then
+ // we also extend. For example:
+ // try { // 1
+ // ...
+ // try { // 2
+ // ...
+ // } /* 2 */ } /* 1 */
+ // This example is meant to indicate that both try regions 1 and 2 end at
+ // the same block, and we're extending 2. Thus, we must also extend 1. If we
+ // only extended 2, we would break proper nesting. (Dev11 bug 137967)
+
+ extendTryRegion = HBtab->ebdIsSameTry(newTryBeg, newTryLast) || bbInTryRegions(XTnum, newBlk);
+ }
+
+ // Does newBlk extend this try region?
+ if (extendTryRegion)
+ {
+ // Yes, newBlk extends this try region
+
+ // newBlk is the now the new try last block
+ fgSetTryEnd(HBtab, newBlk);
+ }
+ }
+
+ // Is afterBlk at the end of a handler region?
+ if (HBtab->ebdHndLast == afterBlk)
+ {
+ noway_assert(afterBlkNext == newBlk->bbNext);
+
+ // Does newBlk extend this handler region?
+ bool extendHndRegion = false;
+ if (newBlk->hasHndIndex())
+ {
+ // We're adding a block after the last block of some handler region. Do
+ // we extend the handler region to include the block, or not?
+ // If the handler region is exactly the same as the handler region
+ // associated with the new block (based on the block's handler index,
+ // which represents the innermost handler the block is a part of), then
+ // we extend it.
+ // If the handler region is a "parent" handler region -- an enclosing
+ // handler region that has the same last block as the new block's handler
+ // region -- then we also extend. For example:
+ // catch { // 1
+ // ...
+ // catch { // 2
+ // ...
+ // } /* 2 */ } /* 1 */
+ // This example is meant to indicate that both handler regions 1 and 2 end at
+ // the same block, and we're extending 2. Thus, we must also extend 1. If we
+ // only extended 2, we would break proper nesting. (Dev11 bug 372051)
+
+ extendHndRegion = bbInHandlerRegions(XTnum, newBlk);
+ }
+
+ if (extendHndRegion)
+ {
+ // Yes, newBlk extends this handler region
+
+ // newBlk is now the last block of the handler.
+ fgSetHndEnd(HBtab, newBlk);
+ }
+ }
+ }
+
+ /* If afterBlk falls through, we insert a jump around newBlk */
+ fgConnectFallThrough(afterBlk, newBlk->bbNext);
+
+#ifdef DEBUG
+ fgVerifyHandlerTab();
+#endif
+
+ return newBlk;
+}
+
+/*****************************************************************************
+ */
+
+/* static */
+unsigned Compiler::acdHelper(SpecialCodeKind codeKind)
+{
+ switch (codeKind)
+ {
+ case SCK_RNGCHK_FAIL:
+ return CORINFO_HELP_RNGCHKFAIL;
+#if COR_JIT_EE_VERSION > 460
+ case SCK_ARG_EXCPN:
+ return CORINFO_HELP_THROW_ARGUMENTEXCEPTION;
+ case SCK_ARG_RNG_EXCPN:
+ return CORINFO_HELP_THROW_ARGUMENTOUTOFRANGEEXCEPTION;
+#endif // COR_JIT_EE_VERSION
+ case SCK_DIV_BY_ZERO:
+ return CORINFO_HELP_THROWDIVZERO;
+ case SCK_ARITH_EXCPN:
+ return CORINFO_HELP_OVERFLOW;
+ default:
+ assert(!"Bad codeKind");
+ return 0;
+ }
+}
+
+/*****************************************************************************
+ *
+ * Find/create an added code entry associated with the given block and with
+ * the given kind.
+ */
+
+BasicBlock* Compiler::fgAddCodeRef(BasicBlock* srcBlk, unsigned refData, SpecialCodeKind kind, unsigned stkDepth)
+{
+ // Record that the code will call a THROW_HELPER
+ // so on Windows Amd64 we can allocate the 4 outgoing
+ // arg slots on the stack frame if there are no other calls.
+ compUsesThrowHelper = true;
+
+ // For debuggable code, genJumpToThrowHlpBlk() will generate the 'throw'
+ // code inline. It has to be kept consistent with fgAddCodeRef()
+ if (opts.compDbgCode)
+ {
+ return nullptr;
+ }
+
+ const static BBjumpKinds jumpKinds[] = {
+ BBJ_NONE, // SCK_NONE
+ BBJ_THROW, // SCK_RNGCHK_FAIL
+ BBJ_ALWAYS, // SCK_PAUSE_EXEC
+ BBJ_THROW, // SCK_DIV_BY_ZERO
+ BBJ_THROW, // SCK_ARITH_EXCP, SCK_OVERFLOW
+ BBJ_THROW, // SCK_ARG_EXCPN
+ BBJ_THROW, // SCK_ARG_RNG_EXCPN
+ };
+
+ noway_assert(sizeof(jumpKinds) == SCK_COUNT); // sanity check
+
+ /* First look for an existing entry that matches what we're looking for */
+
+ AddCodeDsc* add = fgFindExcptnTarget(kind, refData);
+
+ if (add) // found it
+ {
+#ifdef _TARGET_X86_
+ // If different range checks happen at different stack levels,
+ // they can't all jump to the same "call @rngChkFailed" AND have
+ // frameless methods, as the rngChkFailed may need to unwind the
+ // stack, and we have to be able to report the stack level.
+ //
+ // The following check forces most methods that reference an
+ // array element in a parameter list to have an EBP frame,
+ // this restriction could be removed with more careful code
+ // generation for BBJ_THROW (i.e. range check failed).
+ //
+ if (add->acdStkLvl != stkDepth)
+ {
+ codeGen->setFrameRequired(true);
+ }
+#endif // _TARGET_X86_
+
+ return add->acdDstBlk;
+ }
+
+ /* We have to allocate a new entry and prepend it to the list */
+
+ add = new (this, CMK_Unknown) AddCodeDsc;
+ add->acdData = refData;
+ add->acdKind = kind;
+ add->acdStkLvl = (unsigned short)stkDepth;
+ noway_assert(add->acdStkLvl == stkDepth);
+ add->acdNext = fgAddCodeList;
+ fgAddCodeList = add;
+
+ /* Create the target basic block */
+
+ BasicBlock* newBlk;
+
+ newBlk = add->acdDstBlk = fgNewBBinRegion(jumpKinds[kind], srcBlk, /* runRarely */ true, /* insertAtEnd */ true);
+
+ add->acdDstBlk->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ const char* msgWhere = "";
+ if (!srcBlk->hasTryIndex() && !srcBlk->hasHndIndex())
+ {
+ msgWhere = "non-EH region";
+ }
+ else if (!srcBlk->hasTryIndex())
+ {
+ msgWhere = "handler";
+ }
+ else if (!srcBlk->hasHndIndex())
+ {
+ msgWhere = "try";
+ }
+ else if (srcBlk->getTryIndex() < srcBlk->getHndIndex())
+ {
+ msgWhere = "try";
+ }
+ else
+ {
+ msgWhere = "handler";
+ }
+
+ const char* msg;
+ switch (kind)
+ {
+ case SCK_RNGCHK_FAIL:
+ msg = " for RNGCHK_FAIL";
+ break;
+ case SCK_PAUSE_EXEC:
+ msg = " for PAUSE_EXEC";
+ break;
+ case SCK_DIV_BY_ZERO:
+ msg = " for DIV_BY_ZERO";
+ break;
+ case SCK_OVERFLOW:
+ msg = " for OVERFLOW";
+ break;
+#if COR_JIT_EE_VERSION > 460
+ case SCK_ARG_EXCPN:
+ msg = " for ARG_EXCPN";
+ break;
+ case SCK_ARG_RNG_EXCPN:
+ msg = " for ARG_RNG_EXCPN";
+ break;
+#endif // COR_JIT_EE_VERSION
+ default:
+ msg = " for ??";
+ break;
+ }
+
+ printf("\nfgAddCodeRef -"
+ " Add BB in %s%s, new block BB%02u [%08p], stkDepth is %d\n",
+ msgWhere, msg, add->acdDstBlk->bbNum, dspPtr(add->acdDstBlk), stkDepth);
+ }
+#endif // DEBUG
+
+#ifdef DEBUG
+ newBlk->bbTgtStkDepth = stkDepth;
+#endif // DEBUG
+
+ /* Mark the block as added by the compiler and not removable by future flow
+ graph optimizations. Note that no bbJumpDest points to these blocks. */
+
+ newBlk->bbFlags |= BBF_IMPORTED;
+ newBlk->bbFlags |= BBF_DONT_REMOVE;
+
+ /* Remember that we're adding a new basic block */
+
+ fgAddCodeModf = true;
+ fgRngChkThrowAdded = true;
+
+ /* Now figure out what code to insert */
+
+ GenTreeCall* tree;
+ int helper = CORINFO_HELP_UNDEF;
+
+ switch (kind)
+ {
+ case SCK_RNGCHK_FAIL:
+ helper = CORINFO_HELP_RNGCHKFAIL;
+ break;
+
+ case SCK_DIV_BY_ZERO:
+ helper = CORINFO_HELP_THROWDIVZERO;
+ break;
+
+ case SCK_ARITH_EXCPN:
+ helper = CORINFO_HELP_OVERFLOW;
+ noway_assert(SCK_OVERFLOW == SCK_ARITH_EXCPN);
+ break;
+
+#if COR_JIT_EE_VERSION > 460
+ case SCK_ARG_EXCPN:
+ helper = CORINFO_HELP_THROW_ARGUMENTEXCEPTION;
+ break;
+
+ case SCK_ARG_RNG_EXCPN:
+ helper = CORINFO_HELP_THROW_ARGUMENTOUTOFRANGEEXCEPTION;
+ break;
+#endif // COR_JIT_EE_VERSION
+
+ // case SCK_PAUSE_EXEC:
+ // noway_assert(!"add code to pause exec");
+
+ default:
+ noway_assert(!"unexpected code addition kind");
+ return nullptr;
+ }
+
+ noway_assert(helper != CORINFO_HELP_UNDEF);
+
+ // Add the appropriate helper call.
+ tree = gtNewHelperCallNode(helper, TYP_VOID, GTF_EXCEPT);
+
+ // There are no args here but fgMorphArgs has side effects
+ // such as setting the outgoing arg area (which is necessary
+ // on AMD if there are any calls).
+ tree = fgMorphArgs(tree);
+
+ // Store the tree in the new basic block.
+ assert(!srcBlk->isEmpty());
+ if (!srcBlk->IsLIR())
+ {
+ fgInsertStmtAtEnd(newBlk, fgNewStmtFromTree(tree));
+ }
+ else
+ {
+ LIR::AsRange(newBlk).InsertAtEnd(LIR::SeqTree(this, tree));
+ }
+
+ return add->acdDstBlk;
+}
+
+/*****************************************************************************
+ * Finds the block to jump to, to throw a given kind of exception
+ * We maintain a cache of one AddCodeDsc for each kind, to make searching fast.
+ * Note : Each block uses the same (maybe shared) block as the jump target for
+ * a given type of exception
+ */
+
+Compiler::AddCodeDsc* Compiler::fgFindExcptnTarget(SpecialCodeKind kind, unsigned refData)
+{
+ if (!(fgExcptnTargetCache[kind] && // Try the cached value first
+ fgExcptnTargetCache[kind]->acdData == refData))
+ {
+ // Too bad, have to search for the jump target for the exception
+
+ AddCodeDsc* add = nullptr;
+
+ for (add = fgAddCodeList; add != nullptr; add = add->acdNext)
+ {
+ if (add->acdData == refData && add->acdKind == kind)
+ {
+ break;
+ }
+ }
+
+ fgExcptnTargetCache[kind] = add; // Cache it
+ }
+
+ return fgExcptnTargetCache[kind];
+}
+
+/*****************************************************************************
+ *
+ * The given basic block contains an array range check; return the label this
+ * range check is to jump to upon failure.
+ */
+
+BasicBlock* Compiler::fgRngChkTarget(BasicBlock* block, unsigned stkDepth, SpecialCodeKind kind)
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*** Computing fgRngChkTarget for block BB%02u to stkDepth %d\n", block->bbNum, stkDepth);
+ if (!block->IsLIR())
+ {
+ gtDispTree(compCurStmt);
+ }
+ }
+#endif // DEBUG
+
+ /* We attach the target label to the containing try block (if any) */
+ noway_assert(!compIsForInlining());
+ return fgAddCodeRef(block, bbThrowIndex(block), kind, stkDepth);
+}
+
+// Sequences the tree.
+// prevTree is what gtPrev of the first node in execution order gets set to.
+// Returns the first node (execution order) in the sequenced tree.
+GenTree* Compiler::fgSetTreeSeq(GenTree* tree, GenTree* prevTree, bool isLIR)
+{
+ GenTree list;
+
+ if (prevTree == nullptr)
+ {
+ prevTree = &list;
+ }
+ fgTreeSeqLst = prevTree;
+ fgTreeSeqNum = 0;
+ fgTreeSeqBeg = nullptr;
+ fgSetTreeSeqHelper(tree, isLIR);
+
+ GenTree* result = prevTree->gtNext;
+ if (prevTree == &list)
+ {
+ list.gtNext->gtPrev = nullptr;
+ }
+
+ return result;
+}
+
+/*****************************************************************************
+ *
+ * Assigns sequence numbers to the given tree and its sub-operands, and
+ * threads all the nodes together via the 'gtNext' and 'gtPrev' fields.
+ * Uses 'global' - fgTreeSeqLst
+ */
+
+void Compiler::fgSetTreeSeqHelper(GenTreePtr tree, bool isLIR)
+{
+ genTreeOps oper;
+ unsigned kind;
+
+ noway_assert(tree);
+ assert(!IsUninitialized(tree));
+ noway_assert(tree->gtOper != GT_STMT);
+
+ /* Figure out what kind of a node we have */
+
+ oper = tree->OperGet();
+ kind = tree->OperKind();
+
+ /* Is this a leaf/constant node? */
+
+ if (kind & (GTK_CONST | GTK_LEAF))
+ {
+ fgSetTreeSeqFinish(tree, isLIR);
+ return;
+ }
+
+ // Special handling for dynamic block ops.
+ if (tree->OperIsDynBlkOp())
+ {
+ GenTreeDynBlk* dynBlk;
+ GenTree* src;
+ GenTree* asg = tree;
+ if (tree->OperGet() == GT_ASG)
+ {
+ dynBlk = tree->gtGetOp1()->AsDynBlk();
+ src = tree->gtGetOp2();
+ }
+ else
+ {
+ dynBlk = tree->AsDynBlk();
+ src = dynBlk->Data();
+ asg = nullptr;
+ }
+ GenTree* sizeNode = dynBlk->gtDynamicSize;
+ GenTree* dstAddr = dynBlk->Addr();
+ if (dynBlk->gtEvalSizeFirst)
+ {
+ fgSetTreeSeqHelper(sizeNode, isLIR);
+ }
+ if (tree->gtFlags & GTF_REVERSE_OPS)
+ {
+ fgSetTreeSeqHelper(src, isLIR);
+ fgSetTreeSeqHelper(dstAddr, isLIR);
+ }
+ else
+ {
+ fgSetTreeSeqHelper(dstAddr, isLIR);
+ fgSetTreeSeqHelper(src, isLIR);
+ }
+ if (!dynBlk->gtEvalSizeFirst)
+ {
+ fgSetTreeSeqHelper(sizeNode, isLIR);
+ }
+ fgSetTreeSeqFinish(dynBlk, isLIR);
+ if (asg != nullptr)
+ {
+ fgSetTreeSeqFinish(asg, isLIR);
+ }
+ return;
+ }
+
+ /* Is it a 'simple' unary/binary operator? */
+
+ if (kind & GTK_SMPOP)
+ {
+ GenTreePtr op1 = tree->gtOp.gtOp1;
+ GenTreePtr op2 = tree->gtGetOp2();
+
+ // Special handling for GT_LIST
+ if (tree->OperGet() == GT_LIST)
+ {
+ // First, handle the list items, which will be linked in forward order.
+ // As we go, we will link the GT_LIST nodes in reverse order - we will number
+ // them and update fgTreeSeqList in a subsequent traversal.
+ GenTreePtr nextList = tree;
+ GenTreePtr list = nullptr;
+ while (nextList != nullptr && nextList->OperGet() == GT_LIST)
+ {
+ list = nextList;
+ GenTreePtr listItem = list->gtOp.gtOp1;
+ fgSetTreeSeqHelper(listItem, isLIR);
+ nextList = list->gtOp.gtOp2;
+ if (nextList != nullptr)
+ {
+ nextList->gtNext = list;
+ }
+ list->gtPrev = nextList;
+ }
+ // Next, handle the GT_LIST nodes.
+ // Note that fgSetTreeSeqFinish() sets the gtNext to null, so we need to capture the nextList
+ // before we call that method.
+ nextList = list;
+ do
+ {
+ assert(list != nullptr);
+ list = nextList;
+ nextList = list->gtNext;
+ fgSetTreeSeqFinish(list, isLIR);
+ } while (list != tree);
+ return;
+ }
+
+ /* Special handling for AddrMode */
+ if (tree->OperIsAddrMode())
+ {
+ bool reverse = ((tree->gtFlags & GTF_REVERSE_OPS) != 0);
+ if (reverse)
+ {
+ assert(op1 != nullptr && op2 != nullptr);
+ fgSetTreeSeqHelper(op2, isLIR);
+ }
+ if (op1 != nullptr)
+ {
+ fgSetTreeSeqHelper(op1, isLIR);
+ }
+ if (!reverse && op2 != nullptr)
+ {
+ fgSetTreeSeqHelper(op2, isLIR);
+ }
+
+ fgSetTreeSeqFinish(tree, isLIR);
+ return;
+ }
+
+ /* Check for a nilary operator */
+
+ if (op1 == nullptr)
+ {
+ noway_assert(op2 == nullptr);
+ fgSetTreeSeqFinish(tree, isLIR);
+ return;
+ }
+
+ /* Is this a unary operator?
+ * Although UNARY GT_IND has a special structure */
+
+ if (oper == GT_IND)
+ {
+ /* Visit the indirection first - op2 may point to the
+ * jump Label for array-index-out-of-range */
+
+ fgSetTreeSeqHelper(op1, isLIR);
+ fgSetTreeSeqFinish(tree, isLIR);
+ return;
+ }
+
+ /* Now this is REALLY a unary operator */
+
+ if (!op2)
+ {
+ /* Visit the (only) operand and we're done */
+
+ fgSetTreeSeqHelper(op1, isLIR);
+ fgSetTreeSeqFinish(tree, isLIR);
+ return;
+ }
+
+ /*
+ For "real" ?: operators, we make sure the order is
+ as follows:
+
+ condition
+ 1st operand
+ GT_COLON
+ 2nd operand
+ GT_QMARK
+ */
+
+ if (oper == GT_QMARK)
+ {
+ noway_assert((tree->gtFlags & GTF_REVERSE_OPS) == 0);
+
+ fgSetTreeSeqHelper(op1, isLIR);
+ // Here, for the colon, the sequence does not actually represent "order of evaluation":
+ // one or the other of the branches is executed, not both. Still, to make debugging checks
+ // work, we want the sequence to match the order in which we'll generate code, which means
+ // "else" clause then "then" clause.
+ fgSetTreeSeqHelper(op2->AsColon()->ElseNode(), isLIR);
+ fgSetTreeSeqHelper(op2, isLIR);
+ fgSetTreeSeqHelper(op2->AsColon()->ThenNode(), isLIR);
+
+ fgSetTreeSeqFinish(tree, isLIR);
+ return;
+ }
+
+ if (oper == GT_COLON)
+ {
+ fgSetTreeSeqFinish(tree, isLIR);
+ return;
+ }
+
+ /* This is a binary operator */
+
+ if (tree->gtFlags & GTF_REVERSE_OPS)
+ {
+ fgSetTreeSeqHelper(op2, isLIR);
+ fgSetTreeSeqHelper(op1, isLIR);
+ }
+ else
+ {
+ fgSetTreeSeqHelper(op1, isLIR);
+ fgSetTreeSeqHelper(op2, isLIR);
+ }
+
+ fgSetTreeSeqFinish(tree, isLIR);
+ return;
+ }
+
+ /* See what kind of a special operator we have here */
+
+ switch (oper)
+ {
+ case GT_FIELD:
+ noway_assert(tree->gtField.gtFldObj == nullptr);
+ break;
+
+ case GT_CALL:
+
+ /* We'll evaluate the 'this' argument value first */
+ if (tree->gtCall.gtCallObjp)
+ {
+ fgSetTreeSeqHelper(tree->gtCall.gtCallObjp, isLIR);
+ }
+
+ /* We'll evaluate the arguments next, left to right
+ * NOTE: setListOrder needs cleanup - eliminate the #ifdef afterwards */
+
+ if (tree->gtCall.gtCallArgs)
+ {
+ fgSetTreeSeqHelper(tree->gtCall.gtCallArgs, isLIR);
+ }
+
+ /* Evaluate the temp register arguments list
+ * This is a "hidden" list and its only purpose is to
+ * extend the life of temps until we make the call */
+
+ if (tree->gtCall.gtCallLateArgs)
+ {
+ fgSetTreeSeqHelper(tree->gtCall.gtCallLateArgs, isLIR);
+ }
+
+ if ((tree->gtCall.gtCallType == CT_INDIRECT) && (tree->gtCall.gtCallCookie != nullptr))
+ {
+ fgSetTreeSeqHelper(tree->gtCall.gtCallCookie, isLIR);
+ }
+
+ if (tree->gtCall.gtCallType == CT_INDIRECT)
+ {
+ fgSetTreeSeqHelper(tree->gtCall.gtCallAddr, isLIR);
+ }
+
+ if (tree->gtCall.gtControlExpr)
+ {
+ fgSetTreeSeqHelper(tree->gtCall.gtControlExpr, isLIR);
+ }
+
+ break;
+
+ case GT_ARR_ELEM:
+
+ fgSetTreeSeqHelper(tree->gtArrElem.gtArrObj, isLIR);
+
+ unsigned dim;
+ for (dim = 0; dim < tree->gtArrElem.gtArrRank; dim++)
+ {
+ fgSetTreeSeqHelper(tree->gtArrElem.gtArrInds[dim], isLIR);
+ }
+
+ break;
+
+ case GT_ARR_OFFSET:
+ fgSetTreeSeqHelper(tree->gtArrOffs.gtOffset, isLIR);
+ fgSetTreeSeqHelper(tree->gtArrOffs.gtIndex, isLIR);
+ fgSetTreeSeqHelper(tree->gtArrOffs.gtArrObj, isLIR);
+ break;
+
+ case GT_CMPXCHG:
+ // Evaluate the trees left to right
+ fgSetTreeSeqHelper(tree->gtCmpXchg.gtOpLocation, isLIR);
+ fgSetTreeSeqHelper(tree->gtCmpXchg.gtOpValue, isLIR);
+ fgSetTreeSeqHelper(tree->gtCmpXchg.gtOpComparand, isLIR);
+ break;
+
+ case GT_ARR_BOUNDS_CHECK:
+#ifdef FEATURE_SIMD
+ case GT_SIMD_CHK:
+#endif // FEATURE_SIMD
+ // Evaluate the trees left to right
+ fgSetTreeSeqHelper(tree->gtBoundsChk.gtArrLen, isLIR);
+ fgSetTreeSeqHelper(tree->gtBoundsChk.gtIndex, isLIR);
+ break;
+
+ case GT_STORE_DYN_BLK:
+ case GT_DYN_BLK:
+ noway_assert(!"DYN_BLK nodes should be sequenced as a special case");
+ break;
+
+ default:
+#ifdef DEBUG
+ gtDispTree(tree);
+ noway_assert(!"unexpected operator");
+#endif // DEBUG
+ break;
+ }
+
+ fgSetTreeSeqFinish(tree, isLIR);
+}
+
+void Compiler::fgSetTreeSeqFinish(GenTreePtr tree, bool isLIR)
+{
+ // If we are sequencing a node that does not appear in LIR,
+ // do not add it to the list.
+ if (isLIR && (((tree->OperGet() == GT_LIST) && !tree->AsArgList()->IsAggregate()) || tree->OperGet() == GT_ARGPLACE))
+ {
+ return;
+ }
+
+ /* Append to the node list */
+ ++fgTreeSeqNum;
+
+#ifdef DEBUG
+ tree->gtSeqNum = fgTreeSeqNum;
+
+ if (verbose & 0)
+ {
+ printf("SetTreeOrder: ");
+ printTreeID(fgTreeSeqLst);
+ printf(" followed by ");
+ printTreeID(tree);
+ printf("\n");
+ }
+#endif // DEBUG
+
+ fgTreeSeqLst->gtNext = tree;
+ tree->gtNext = nullptr;
+ tree->gtPrev = fgTreeSeqLst;
+ fgTreeSeqLst = tree;
+
+ /* Remember the very first node */
+
+ if (!fgTreeSeqBeg)
+ {
+ fgTreeSeqBeg = tree;
+ assert(tree->gtSeqNum == 1);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Figure out the order in which operators should be evaluated, along with
+ * other information (such as the register sets trashed by each subtree).
+ * Also finds blocks that need GC polls and inserts them as needed.
+ */
+
+void Compiler::fgSetBlockOrder()
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgSetBlockOrder()\n");
+ }
+#endif // DEBUG
+
+#ifdef DEBUG
+ BasicBlock::s_nMaxTrees = 0;
+#endif
+
+ /* Walk the basic blocks to assign sequence numbers */
+
+ /* If we don't compute the doms, then we never mark blocks as loops. */
+ if (fgDomsComputed)
+ {
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ /* If this block is a loop header, mark it appropriately */
+
+ if (block->isLoopHead())
+ {
+ fgMarkLoopHead(block);
+ }
+ }
+ }
+ // only enable fully interruptible code for if we're hijacking.
+ else if (GCPOLL_NONE == opts.compGCPollType)
+ {
+ /* If we don't have the dominators, use an abbreviated test for fully interruptible. If there are
+ * any back edges, check the source and destination blocks to see if they're GC Safe. If not, then
+ * go fully interruptible. */
+
+ /* XXX Mon 1/21/2008
+ * Wouldn't it be nice to have a block iterator that can do this loop?
+ */
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+// true if the edge is forward, or if it is a back edge and either the source and dest are GC safe.
+#define EDGE_IS_GC_SAFE(src, dst) \
+ (((src)->bbNum < (dst)->bbNum) || (((src)->bbFlags | (dst)->bbFlags) & BBF_GC_SAFE_POINT))
+
+ bool partiallyInterruptible = true;
+ switch (block->bbJumpKind)
+ {
+ case BBJ_COND:
+ case BBJ_ALWAYS:
+ partiallyInterruptible = EDGE_IS_GC_SAFE(block, block->bbJumpDest);
+ break;
+
+ case BBJ_SWITCH:
+
+ unsigned jumpCnt;
+ jumpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jumpPtr;
+ jumpPtr = block->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ partiallyInterruptible &= EDGE_IS_GC_SAFE(block, *jumpPtr);
+ } while (++jumpPtr, --jumpCnt);
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (!partiallyInterruptible)
+ {
+ // DDB 204533:
+ // The GC encoding for fully interruptible methods does not
+ // support more than 1023 pushed arguments, so we can't set
+ // genInterruptible here when we have 1024 or more pushed args
+ //
+ if (compCanEncodePtrArgCntMax())
+ {
+ genInterruptible = true;
+ }
+ break;
+ }
+#undef EDGE_IS_GC_SAFE
+ }
+ }
+
+ if (!fgGCPollsCreated)
+ {
+ fgCreateGCPolls();
+ }
+
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+
+#if FEATURE_FASTTAILCALL
+#ifndef JIT32_GCENCODER
+ if (block->endsWithTailCallOrJmp(this, true) && !(block->bbFlags & BBF_GC_SAFE_POINT) &&
+ optReachWithoutCall(fgFirstBB, block))
+ {
+ // We have a tail call that is reachable without making any other
+ // 'normal' call that would have counted as a GC Poll. If we were
+ // using polls, all return blocks meeting this criteria would have
+ // already added polls and then marked as being GC safe
+ // (BBF_GC_SAFE_POINT). Thus we can only reach here when *NOT*
+ // using GC polls, but instead relying on the JIT to generate
+ // fully-interruptible code.
+ noway_assert(GCPOLL_NONE == opts.compGCPollType);
+
+ // This tail call might combine with other tail calls to form a
+ // loop. Thus we need to either add a poll, or make the method
+ // fully interruptible. I chose the later because that's what
+ // JIT64 does.
+ genInterruptible = true;
+ }
+#endif // !JIT32_GCENCODER
+#endif // FEATURE_FASTTAILCALL
+
+ fgSetBlockOrder(block);
+ }
+
+ /* Remember that now the tree list is threaded */
+
+ fgStmtListThreaded = true;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("The biggest BB has %4u tree nodes\n", BasicBlock::s_nMaxTrees);
+ }
+ fgDebugCheckLinks();
+#endif // DEBUG
+}
+
+/*****************************************************************************/
+
+void Compiler::fgSetStmtSeq(GenTreePtr tree)
+{
+ GenTree list; // helper node that we use to start the StmtList
+ // It's located in front of the first node in the list
+
+ noway_assert(tree->gtOper == GT_STMT);
+
+ /* Assign numbers and next/prev links for this tree */
+
+ fgTreeSeqNum = 0;
+ fgTreeSeqLst = &list;
+ fgTreeSeqBeg = nullptr;
+
+ fgSetTreeSeqHelper(tree->gtStmt.gtStmtExpr, false);
+
+ /* Record the address of the first node */
+
+ tree->gtStmt.gtStmtList = fgTreeSeqBeg;
+
+#ifdef DEBUG
+
+ if (list.gtNext->gtPrev != &list)
+ {
+ printf("&list ");
+ printTreeID(&list);
+ printf(" != list.next->prev ");
+ printTreeID(list.gtNext->gtPrev);
+ printf("\n");
+ goto BAD_LIST;
+ }
+
+ GenTreePtr temp;
+ GenTreePtr last;
+ for (temp = list.gtNext, last = &list; temp; last = temp, temp = temp->gtNext)
+ {
+ if (temp->gtPrev != last)
+ {
+ printTreeID(temp);
+ printf("->gtPrev = ");
+ printTreeID(temp->gtPrev);
+ printf(", but last = ");
+ printTreeID(last);
+ printf("\n");
+
+ BAD_LIST:;
+
+ printf("\n");
+ gtDispTree(tree->gtStmt.gtStmtExpr);
+ printf("\n");
+
+ for (GenTreePtr bad = &list; bad; bad = bad->gtNext)
+ {
+ printf(" entry at ");
+ printTreeID(bad);
+ printf(" (prev=");
+ printTreeID(bad->gtPrev);
+ printf(",next=)");
+ printTreeID(bad->gtNext);
+ printf("\n");
+ }
+
+ printf("\n");
+ noway_assert(!"Badly linked tree");
+ break;
+ }
+ }
+#endif // DEBUG
+
+ /* Fix the first node's 'prev' link */
+
+ noway_assert(list.gtNext->gtPrev == &list);
+ list.gtNext->gtPrev = nullptr;
+
+#ifdef DEBUG
+ /* Keep track of the highest # of tree nodes */
+
+ if (BasicBlock::s_nMaxTrees < fgTreeSeqNum)
+ {
+ BasicBlock::s_nMaxTrees = fgTreeSeqNum;
+ }
+#endif // DEBUG
+}
+
+/*****************************************************************************/
+
+void Compiler::fgSetBlockOrder(BasicBlock* block)
+{
+ GenTreePtr tree;
+
+ tree = block->bbTreeList;
+ if (!tree)
+ {
+ return;
+ }
+
+ for (;;)
+ {
+ fgSetStmtSeq(tree);
+
+ /* Are there any more trees in this basic block? */
+
+ if (tree->gtNext == nullptr)
+ {
+ /* last statement in the tree list */
+ noway_assert(block->lastStmt() == tree);
+ break;
+ }
+
+#ifdef DEBUG
+ if (block->bbTreeList == tree)
+ {
+ /* first statement in the list */
+ noway_assert(tree->gtPrev->gtNext == nullptr);
+ }
+ else
+ {
+ noway_assert(tree->gtPrev->gtNext == tree);
+ }
+
+ noway_assert(tree->gtNext->gtPrev == tree);
+#endif // DEBUG
+
+ tree = tree->gtNext;
+ }
+}
+
+#ifdef LEGACY_BACKEND
+//------------------------------------------------------------------------
+// fgOrderBlockOps: Get the execution order for a block assignment
+//
+// Arguments:
+// tree - The block assignment
+// reg0 - The register for the destination
+// reg1 - The register for the source
+// reg2 - The register for the size
+// opsPtr - An array of 3 GenTreePtr's, an out argument for the operands, in order
+// regsPtr - An array of three regMaskTP - an out argument for the registers, in order
+//
+// Return Value:
+// The return values go into the arrays that are passed in, and provide the
+// operands and associated registers, in execution order.
+//
+// Notes:
+// This method is somewhat convoluted in order to preserve old behavior from when
+// block assignments had their dst and src in a GT_LIST as op1, and their size as op2.
+// The old tree was like this:
+// tree->gtOp
+// / \
+// GT_LIST [size/clsHnd]
+// / \
+// [dest] [val/src]
+//
+// The new tree looks like this:
+// GT_ASG
+// / \
+// blk/obj [val/src]
+// / \
+// [destAddr] [*size/clsHnd] *only for GT_DYN_BLK
+//
+// For the (usual) case of GT_BLK or GT_OBJ, the size is always "evaluated" (i.e.
+// instantiated into a register) last. In those cases, the GTF_REVERSE_OPS flag
+// on the assignment works as usual.
+// In order to preserve previous possible orderings, the order for evaluating
+// the size of a GT_DYN_BLK node is controlled by its gtEvalSizeFirst flag. If
+// that is set, the size is evaluated first, and then the src and dst are evaluated
+// according to the GTF_REVERSE_OPS flag on the assignment.
+
+void Compiler::fgOrderBlockOps(GenTreePtr tree,
+ regMaskTP reg0,
+ regMaskTP reg1,
+ regMaskTP reg2,
+ GenTreePtr* opsPtr, // OUT
+ regMaskTP* regsPtr) // OUT
+{
+ assert(tree->OperIsBlkOp());
+
+ GenTreeBlk* destBlk = tree->gtOp.gtOp1->AsBlk();
+ GenTreePtr destAddr = destBlk->Addr();
+ GenTreePtr srcPtrOrVal = tree->gtOp.gtOp2;
+ if (tree->OperIsCopyBlkOp())
+ {
+ assert(srcPtrOrVal->OperIsIndir());
+ srcPtrOrVal = srcPtrOrVal->AsIndir()->Addr();
+ }
+ GenTreePtr sizeNode = (destBlk->gtOper == GT_DYN_BLK) ? destBlk->AsDynBlk()->gtDynamicSize : nullptr;
+ noway_assert((sizeNode != nullptr) || ((destBlk->gtFlags & GTF_REVERSE_OPS) == 0));
+ assert(destAddr != nullptr);
+ assert(srcPtrOrVal != nullptr);
+
+ GenTreePtr ops[3] = {
+ destAddr, // Dest address
+ srcPtrOrVal, // Val / Src address
+ sizeNode // Size of block
+ };
+
+ regMaskTP regs[3] = {reg0, reg1, reg2};
+
+ static int blockOpsOrder[4][3] =
+ // destBlk->gtEvalSizeFirst | tree->gtFlags
+ {
+ // -------------------------+----------------------------
+ {0, 1, 2}, // false | -
+ {2, 0, 1}, // true | -
+ {1, 0, 2}, // false | GTF_REVERSE_OPS
+ {2, 1, 0} // true | GTF_REVERSE_OPS
+ };
+
+ int orderNum = ((destBlk->gtFlags & GTF_REVERSE_OPS) != 0) * 1 + ((tree->gtFlags & GTF_REVERSE_OPS) != 0) * 2;
+
+ assert(orderNum < 4);
+
+ int* order = blockOpsOrder[orderNum];
+
+ PREFIX_ASSUME(order != NULL);
+
+ // Fill in the OUT arrays according to the order we have selected
+
+ opsPtr[0] = ops[order[0]];
+ opsPtr[1] = ops[order[1]];
+ opsPtr[2] = ops[order[2]];
+
+ regsPtr[0] = regs[order[0]];
+ regsPtr[1] = regs[order[1]];
+ regsPtr[2] = regs[order[2]];
+}
+#endif // LEGACY_BACKEND
+
+//------------------------------------------------------------------------
+// fgGetFirstNode: Get the first node in the tree, in execution order
+//
+// Arguments:
+// tree - The top node of the tree of interest
+//
+// Return Value:
+// The first node in execution order, that belongs to tree.
+//
+// Assumptions:
+// 'tree' must either be a leaf, or all of its constituent nodes must be contiguous
+// in execution order.
+// TODO-Cleanup: Add a debug-only method that verifies this.
+
+/* static */
+GenTreePtr Compiler::fgGetFirstNode(GenTreePtr tree)
+{
+ GenTreePtr child = tree;
+ while (child->NumChildren() > 0)
+ {
+ if (child->OperIsBinary() && child->IsReverseOp())
+ {
+ child = child->GetChild(1);
+ }
+ else
+ {
+ child = child->GetChild(0);
+ }
+ }
+ return child;
+}
+
+// Examine the bbTreeList and return the estimated code size for this block
+unsigned Compiler::fgGetCodeEstimate(BasicBlock* block)
+{
+ unsigned costSz = 0; // estimate of blocks code size cost
+
+ switch (block->bbJumpKind)
+ {
+ case BBJ_NONE:
+ costSz = 0;
+ break;
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ case BBJ_LEAVE:
+ case BBJ_COND:
+ costSz = 2;
+ break;
+ case BBJ_CALLFINALLY:
+ costSz = 5;
+ break;
+ case BBJ_SWITCH:
+ costSz = 10;
+ break;
+ case BBJ_THROW:
+ costSz = 1; // We place a int3 after the code for a throw block
+ break;
+ case BBJ_EHFINALLYRET:
+ case BBJ_EHFILTERRET:
+ costSz = 1;
+ break;
+ case BBJ_RETURN: // return from method
+ costSz = 3;
+ break;
+ default:
+ noway_assert(!"Bad bbJumpKind");
+ break;
+ }
+
+ GenTreePtr tree = block->FirstNonPhiDef();
+ if (tree)
+ {
+ do
+ {
+ noway_assert(tree->gtOper == GT_STMT);
+
+ if (tree->gtCostSz < MAX_COST)
+ {
+ costSz += tree->gtCostSz;
+ }
+ else
+ {
+ // We could walk the tree to find out the real gtCostSz,
+ // but just using MAX_COST for this trees code size works OK
+ costSz += tree->gtCostSz;
+ }
+
+ tree = tree->gtNext;
+ } while (tree);
+ }
+
+ return costSz;
+}
+
+#if DUMP_FLOWGRAPHS
+
+struct escapeMapping_t
+{
+ char ch;
+ const char* sub;
+};
+
+// clang-format off
+static escapeMapping_t s_EscapeFileMapping[] =
+{
+ {':', "="},
+ {'<', "["},
+ {'>', "]"},
+ {';', "~semi~"},
+ {'|', "~bar~"},
+ {'&', "~amp~"},
+ {'"', "~quot~"},
+ {'*', "~star~"},
+ {0, nullptr}
+};
+
+static escapeMapping_t s_EscapeMapping[] =
+{
+ {'<', "&lt;"},
+ {'>', "&gt;"},
+ {'&', "&amp;"},
+ {'"', "&quot;"},
+ {0, nullptr}
+};
+// clang-formt on
+
+const char* Compiler::fgProcessEscapes(const char* nameIn, escapeMapping_t* map)
+{
+ const char* nameOut = nameIn;
+ unsigned lengthOut;
+ unsigned index;
+ bool match;
+ bool subsitutionRequired;
+ const char* pChar;
+
+ lengthOut = 1;
+ subsitutionRequired = false;
+ pChar = nameIn;
+ while (*pChar != '\0')
+ {
+ match = false;
+ index = 0;
+ while (map[index].ch != 0)
+ {
+ if (*pChar == map[index].ch)
+ {
+ match = true;
+ break;
+ }
+ index++;
+ }
+ if (match)
+ {
+ subsitutionRequired = true;
+ lengthOut += (unsigned)strlen(map[index].sub);
+ }
+ else
+ {
+ lengthOut += 1;
+ }
+ pChar++;
+ }
+
+ if (subsitutionRequired)
+ {
+ char* newName = (char*) compGetMemA(lengthOut, CMK_DebugOnly);
+ char* pDest;
+ pDest = newName;
+ pChar = nameIn;
+ while (*pChar != '\0')
+ {
+ match = false;
+ index = 0;
+ while (map[index].ch != 0)
+ {
+ if (*pChar == map[index].ch)
+ {
+ match = true;
+ break;
+ }
+ index++;
+ }
+ if (match)
+ {
+ strcpy(pDest, map[index].sub);
+ pDest += strlen(map[index].sub);
+ }
+ else
+ {
+ *pDest++ = *pChar;
+ }
+ pChar++;
+ }
+ *pDest++ = '\0';
+ nameOut = (const char*) newName;
+ }
+
+ return nameOut;
+}
+
+static void fprintfDouble(FILE* fgxFile, double value)
+{
+ assert(value >= 0.0);
+
+ if ((value >= 0.010) || (value == 0.0))
+ {
+ fprintf(fgxFile, "\"%7.3f\"", value);
+ }
+ else if (value >= 0.00010)
+ {
+ fprintf(fgxFile, "\"%7.5f\"", value);
+ }
+ else
+ {
+ fprintf(fgxFile, "\"%7E\"", value);
+ }
+}
+
+//------------------------------------------------------------------------
+// fgOpenFlowGraphFile: Open a file to dump either the xml or dot format flow graph
+//
+// Arguments:
+// wbDontClose - A boolean out argument that indicates whether the caller should close the file
+// phase - A phase identifier to indicate which phase is associated with the dump
+// type - A (wide) string indicating the type of dump, "dot" or "xml"
+//
+// Return Value:
+// Opens a file to which a flowgraph can be dumped, whose name is based on the current
+// config vales.
+
+FILE* Compiler::fgOpenFlowGraphFile(bool* wbDontClose, Phases phase, LPCWSTR type)
+{
+ FILE* fgxFile;
+ LPCWSTR pattern = nullptr;
+ LPCWSTR filename = nullptr;
+ LPCWSTR pathname = nullptr;
+ const char* escapedString;
+ bool createDuplicateFgxFiles = true;
+
+#ifdef DEBUG
+ if (opts.eeFlags & CORJIT_FLG_PREJIT)
+ {
+ pattern = JitConfig.NgenDumpFg();
+ filename = JitConfig.NgenDumpFgFile();
+ pathname = JitConfig.NgenDumpFgDir();
+ }
+ else
+ {
+ pattern = JitConfig.JitDumpFg();
+ filename = JitConfig.JitDumpFgFile();
+ pathname = JitConfig.JitDumpFgDir();
+ }
+#endif // DEBUG
+
+ if (fgBBcount <= 1) {
+ return nullptr;
+}
+
+ if (pattern == nullptr) {
+ return nullptr;
+}
+
+ if (wcslen(pattern) == 0) {
+ return nullptr;
+}
+
+ LPCWSTR phasePattern = JitConfig.JitDumpFgPhase();
+ LPCWSTR phaseName = PhaseShortNames[phase];
+ if (phasePattern == nullptr)
+ {
+ if (phase != PHASE_DETERMINE_FIRST_COLD_BLOCK)
+ {
+ return nullptr;
+ }
+ }
+ else if (*phasePattern != W('*'))
+ {
+ if (wcsstr(phasePattern, phaseName) == nullptr)
+ {
+ return nullptr;
+ }
+ }
+
+ if (*pattern != W('*'))
+ {
+ bool hasColon = (wcschr(pattern, W(':')) != nullptr);
+
+ if (hasColon)
+ {
+ const char* className = info.compClassName;
+ if (*pattern == W('*'))
+ {
+ pattern++;
+ }
+ else
+ {
+ while ((*pattern != W(':')) && (*pattern != W('*')))
+ {
+ if (*pattern != *className) {
+ return nullptr;
+}
+
+ pattern++;
+ className++;
+ }
+ if (*pattern == W('*'))
+ {
+ pattern++;
+ }
+ else
+ {
+ if (*className != 0) {
+ return nullptr;
+}
+ }
+ }
+ if (*pattern != W(':')) {
+ return nullptr;
+ }
+
+ pattern++;
+ }
+
+ const char* methodName = info.compMethodName;
+ if (*pattern == W('*'))
+ {
+ pattern++;
+ }
+ else
+ {
+ while ((*pattern != 0) && (*pattern != W('*')))
+ {
+ if (*pattern != *methodName) {
+ return nullptr;
+}
+
+ pattern++;
+ methodName++;
+ }
+ if (*pattern == W('*'))
+ {
+ pattern++;
+ }
+ else
+ {
+ if (*methodName != 0) {
+ return nullptr;
+}
+ }
+ }
+ if (*pattern != 0) {
+ return nullptr;
+ }
+ }
+
+ if (filename == nullptr)
+ {
+ filename = W("default");
+ }
+
+ if (wcscmp(filename, W("profiled")) == 0)
+ {
+ if ((fgFirstBB->bbFlags & BBF_PROF_WEIGHT) != 0)
+ {
+ createDuplicateFgxFiles = true;
+ goto ONE_FILE_PER_METHOD;
+ }
+ else
+ {
+ return nullptr;
+ }
+ }
+ if (wcscmp(filename, W("hot")) == 0)
+ {
+ if (info.compMethodInfo->regionKind == CORINFO_REGION_HOT)
+
+ {
+ createDuplicateFgxFiles = true;
+ goto ONE_FILE_PER_METHOD;
+ }
+ else
+ {
+ return nullptr;
+ }
+ }
+ else if (wcscmp(filename, W("cold")) == 0)
+ {
+ if (info.compMethodInfo->regionKind == CORINFO_REGION_COLD)
+ {
+ createDuplicateFgxFiles = true;
+ goto ONE_FILE_PER_METHOD;
+ }
+ else
+ {
+ return nullptr;
+ }
+ }
+ else if (wcscmp(filename, W("jit")) == 0)
+ {
+ if (info.compMethodInfo->regionKind == CORINFO_REGION_JIT)
+ {
+ createDuplicateFgxFiles = true;
+ goto ONE_FILE_PER_METHOD;
+ }
+ else
+ {
+ return nullptr;
+ }
+ }
+ else if (wcscmp(filename, W("all")) == 0)
+ {
+ createDuplicateFgxFiles = true;
+
+ONE_FILE_PER_METHOD:;
+
+ escapedString = fgProcessEscapes(info.compFullName, s_EscapeFileMapping);
+ size_t wCharCount = strlen(escapedString) + wcslen(phaseName) + 1 + strlen("~999") + wcslen(type) + 1;
+ if (pathname != nullptr)
+ {
+ wCharCount += wcslen(pathname) + 1;
+ }
+ filename = (LPCWSTR) alloca(wCharCount * sizeof(WCHAR));
+ if (pathname != nullptr)
+ {
+ swprintf_s((LPWSTR)filename, wCharCount, W("%s\\%S-%s.%s"), pathname, escapedString, phaseName, type);
+ }
+ else
+ {
+ swprintf_s((LPWSTR)filename, wCharCount, W("%S.%s"), escapedString, type);
+ }
+ fgxFile = _wfopen(filename, W("r")); // Check if this file already exists
+ if (fgxFile != nullptr)
+ {
+ // For Generic methods we will have both hot and cold versions
+ if (createDuplicateFgxFiles == false)
+ {
+ fclose(fgxFile);
+ return nullptr;
+ }
+ // Yes, this filename already exists, so create a different one by appending ~2, ~3, etc...
+ for (int i = 2; i < 1000; i++)
+ {
+ fclose(fgxFile);
+ if (pathname != nullptr)
+ {
+ swprintf_s((LPWSTR)filename, wCharCount, W("%s\\%S~%d.%s"), pathname, escapedString, i, type);
+ }
+ else
+ {
+ swprintf_s((LPWSTR)filename, wCharCount, W("%S~%d.%s"), escapedString, i, type);
+ }
+ fgxFile = _wfopen(filename, W("r")); // Check if this file exists
+ if (fgxFile == nullptr) {
+ break;
+ }
+ }
+ // If we have already created 1000 files with this name then just fail
+ if (fgxFile != nullptr)
+ {
+ fclose(fgxFile);
+ return nullptr;
+ }
+ }
+ fgxFile = _wfopen(filename, W("a+"));
+ *wbDontClose = false;
+ }
+ else if (wcscmp(filename, W("stdout")) == 0)
+ {
+ fgxFile = jitstdout;
+ *wbDontClose = true;
+ }
+ else if (wcscmp(filename, W("stderr")) == 0)
+ {
+ fgxFile = stderr;
+ *wbDontClose = true;
+ }
+ else
+ {
+ LPCWSTR origFilename = filename;
+ size_t wCharCount = wcslen(origFilename) + wcslen(type) + 2;
+ if (pathname != nullptr)
+ {
+ wCharCount += wcslen(pathname) + 1;
+ }
+ filename = (LPCWSTR) alloca(wCharCount * sizeof(WCHAR));
+ if (pathname != nullptr)
+ {
+ swprintf_s((LPWSTR)filename, wCharCount, W("%s\\%s.%s"), pathname, origFilename, type);
+ }
+ else
+ {
+ swprintf_s((LPWSTR)filename, wCharCount, W("%s.%s"), origFilename, type);
+ }
+ fgxFile = _wfopen(filename, W("a+"));
+ *wbDontClose = false;
+ }
+
+ return fgxFile;
+}
+
+//------------------------------------------------------------------------
+// fgDumpFlowGraph: Dump the xml or dot format flow graph, if enabled for this phase.
+//
+// Arguments:
+// phase - A phase identifier to indicate which phase is associated with the dump,
+// i.e. which phase has just completed.
+//
+// Return Value:
+// True iff a flowgraph has been dumped.
+//
+// Notes:
+// The xml dumps are the historical mechanism for dumping the flowgraph.
+// The dot format can be viewed by:
+// - Graphviz (http://www.graphviz.org/)
+// - The command "C:\Program Files (x86)\Graphviz2.38\bin\dot.exe" -Tsvg -oFoo.svg -Kdot Foo.dot
+// will produce a Foo.svg file that can be opened with any svg-capable browser (e.g. IE).
+// - http://rise4fun.com/Agl/
+// - Cut and paste the graph from your .dot file, replacing the digraph on the page, and then click the play
+// button.
+// - It will show a rotating '/' and then render the graph in the browser.
+// MSAGL has also been open-sourced to https://github.com/Microsoft/automatic-graph-layout.git.
+//
+// Here are the config values that control it:
+// COMPlus_JitDumpFg A string (ala the COMPlus_JitDump string) indicating what methods to dump flowgraphs
+// for.
+// COMPlus_JitDumpFgDir A path to a directory into which the flowgraphs will be dumped.
+// COMPlus_JitDumpFgFile The filename to use. The default is "default.[xml|dot]".
+// Note that the new graphs will be appended to this file if it already exists.
+// COMPlus_JitDumpFgPhase Phase(s) after which to dump the flowgraph.
+// Set to the short name of a phase to see the flowgraph after that phase.
+// Leave unset to dump after COLD-BLK (determine first cold block) or set to * for all
+// phases.
+// COMPlus_JitDumpFgDot Set to non-zero to emit Dot instead of Xml Flowgraph dump. (Default is xml format.)
+
+bool Compiler::fgDumpFlowGraph(Phases phase)
+{
+ bool result = false;
+ bool dontClose = false;
+ bool createDotFile = false;
+ if (JitConfig.JitDumpFgDot())
+ {
+ createDotFile = true;
+ }
+
+ FILE* fgxFile = fgOpenFlowGraphFile(&dontClose, phase, createDotFile ? W("dot") : W("fgx"));
+
+ if (fgxFile == nullptr)
+ {
+ return false;
+ }
+ bool validWeights = fgHaveValidEdgeWeights;
+ unsigned calledCount = max(fgCalledWeight, BB_UNITY_WEIGHT) / BB_UNITY_WEIGHT;
+ double weightDivisor = (double) (calledCount * BB_UNITY_WEIGHT);
+ const char* escapedString;
+ const char* regionString = "NONE";
+
+ if (info.compMethodInfo->regionKind == CORINFO_REGION_HOT)
+ {
+ regionString="HOT";
+ }
+ else if (info.compMethodInfo->regionKind == CORINFO_REGION_COLD)
+ {
+ regionString="COLD";
+ }
+ else if (info.compMethodInfo->regionKind == CORINFO_REGION_JIT)
+ {
+ regionString="JIT";
+ }
+
+ if (createDotFile)
+ {
+ fprintf(fgxFile, "digraph %s\n{\n", info.compMethodName);
+ fprintf(fgxFile, "/* Method %d, after phase %s */", Compiler::jitTotalMethodCompiled, PhaseNames[phase]);
+ }
+ else
+ {
+ fprintf(fgxFile, "<method");
+
+ escapedString = fgProcessEscapes(info.compFullName, s_EscapeMapping);
+ fprintf(fgxFile, "\n name=\"%s\"", escapedString);
+
+ escapedString = fgProcessEscapes(info.compClassName, s_EscapeMapping);
+ fprintf(fgxFile, "\n className=\"%s\"", escapedString);
+
+ escapedString = fgProcessEscapes(info.compMethodName, s_EscapeMapping);
+ fprintf(fgxFile, "\n methodName=\"%s\"", escapedString);
+ fprintf(fgxFile, "\n ngenRegion=\"%s\"", regionString);
+
+ fprintf(fgxFile, "\n bytesOfIL=\"%d\"", info.compILCodeSize);
+ fprintf(fgxFile, "\n localVarCount=\"%d\"", lvaCount);
+
+ if (fgHaveProfileData())
+ {
+ fprintf(fgxFile, "\n calledCount=\"%d\"", calledCount);
+ fprintf(fgxFile, "\n profileData=\"true\"");
+ }
+ if (compHndBBtabCount > 0)
+ {
+ fprintf(fgxFile, "\n hasEHRegions=\"true\"");
+ }
+ if (fgHasLoops)
+ {
+ fprintf(fgxFile, "\n hasLoops=\"true\"");
+ }
+ if (validWeights)
+ {
+ fprintf(fgxFile, "\n validEdgeWeights=\"true\"");
+ if (!fgSlopUsedInEdgeWeights && !fgRangeUsedInEdgeWeights)
+ {
+ fprintf(fgxFile, "\n exactEdgeWeights=\"true\"");
+ }
+ }
+ if (fgFirstColdBlock != nullptr)
+ {
+ fprintf(fgxFile, "\n firstColdBlock=\"%d\"", fgFirstColdBlock->bbNum);
+ }
+
+ fprintf(fgxFile, ">");
+
+ fprintf(fgxFile, "\n <blocks");
+ fprintf(fgxFile, "\n blockCount=\"%d\"", fgBBcount);
+ fprintf(fgxFile, ">");
+ }
+
+ static const char* kindImage[] = { "EHFINALLYRET", "EHFILTERRET", "EHCATCHRET",
+ "THROW", "RETURN", "NONE", "ALWAYS", "LEAVE",
+ "CALLFINALLY", "COND", "SWITCH" };
+
+ BasicBlock* block;
+ unsigned blockOrdinal;
+ for (block = fgFirstBB , blockOrdinal = 1;
+ block != nullptr;
+ block = block->bbNext, blockOrdinal++)
+ {
+ if (createDotFile)
+ {
+ // Add constraint edges to try to keep nodes ordered.
+ // It seems to work best if these edges are all created first.
+ switch(block->bbJumpKind)
+ {
+ case BBJ_COND:
+ case BBJ_NONE:
+ assert(block->bbNext != nullptr);
+ fprintf(fgxFile, " BB%02u -> BB%02u\n", block->bbNum, block->bbNext->bbNum);
+ break;
+ default:
+ // These may or may not have an edge to the next block.
+ // Add a transparent edge to keep nodes ordered.
+ if (block->bbNext != nullptr)
+ {
+ fprintf(fgxFile, " BB%02u -> BB%02u [arrowtail=none,color=transparent]\n", block->bbNum, block->bbNext->bbNum);
+ }
+ }
+ }
+ else
+ {
+ fprintf(fgxFile,"\n <block");
+ fprintf(fgxFile,"\n id=\"%d\"", block->bbNum);
+ fprintf(fgxFile,"\n ordinal=\"%d\"", blockOrdinal);
+ fprintf(fgxFile,"\n jumpKind=\"%s\"", kindImage[block->bbJumpKind]);
+ if (block->hasTryIndex())
+ {
+ fprintf(fgxFile,"\n inTry=\"%s\"", "true");
+ }
+ if (block->hasHndIndex())
+ {
+ fprintf(fgxFile,"\n inHandler=\"%s\"", "true");
+ }
+ if (((fgFirstBB->bbFlags & BBF_PROF_WEIGHT) != 0) &&
+ ((block->bbFlags & BBF_COLD) == 0) )
+ {
+ fprintf(fgxFile,"\n hot=\"true\"");
+ }
+ if (block->bbFlags & (BBF_HAS_NEWOBJ | BBF_HAS_NEWARRAY))
+ {
+ fprintf(fgxFile,"\n callsNew=\"true\"");
+ }
+ if (block->bbFlags & BBF_LOOP_HEAD)
+ {
+ fprintf(fgxFile,"\n loopHead=\"true\"");
+ }
+ fprintf(fgxFile,"\n weight=");
+ fprintfDouble(fgxFile, ((double) block->bbWeight) / weightDivisor);
+ fprintf(fgxFile,"\n codeEstimate=\"%d\"", fgGetCodeEstimate(block));
+ fprintf(fgxFile,"\n startOffset=\"%d\"", block->bbCodeOffs);
+ fprintf(fgxFile,"\n endOffset=\"%d\"", block->bbCodeOffsEnd);
+ fprintf(fgxFile, ">");
+ fprintf(fgxFile,"\n </block>");
+ }
+ }
+
+ if (!createDotFile)
+ {
+ fprintf(fgxFile, "\n </blocks>");
+
+ fprintf(fgxFile, "\n <edges");
+ fprintf(fgxFile, "\n edgeCount=\"%d\"", fgEdgeCount);
+ fprintf(fgxFile, ">");
+ }
+
+ unsigned edgeNum = 1;
+ BasicBlock* bTarget;
+ for (bTarget = fgFirstBB; bTarget != nullptr; bTarget = bTarget->bbNext)
+ {
+ double targetWeightDivisor;
+ if (bTarget->bbWeight == BB_ZERO_WEIGHT)
+ {
+ targetWeightDivisor = 1.0;
+ }
+ else
+ {
+ targetWeightDivisor = (double) bTarget->bbWeight;
+ }
+
+ flowList* edge;
+ for (edge = bTarget->bbPreds; edge != nullptr; edge = edge->flNext, edgeNum++)
+ {
+ BasicBlock* bSource = edge->flBlock;
+ double sourceWeightDivisor;
+ if (bSource->bbWeight == BB_ZERO_WEIGHT)
+ {
+ sourceWeightDivisor = 1.0;
+ }
+ else
+ {
+ sourceWeightDivisor = (double) bSource->bbWeight;
+ }
+ if (createDotFile)
+ {
+ // Don't duplicate the edges we added above.
+ if ((bSource->bbNum == (bTarget->bbNum - 1)) &&
+ ((bSource->bbJumpKind == BBJ_NONE) || (bSource->bbJumpKind == BBJ_COND)))
+ {
+ continue;
+ }
+ fprintf(fgxFile, " BB%02u -> BB%02u", bSource->bbNum, bTarget->bbNum);
+ if ((bSource->bbNum > bTarget->bbNum))
+ {
+ fprintf(fgxFile, "[arrowhead=normal,arrowtail=none,color=green]\n");
+ }
+ else
+ {
+ fprintf(fgxFile, "\n");
+ }
+ }
+ else
+ {
+ fprintf(fgxFile,"\n <edge");
+ fprintf(fgxFile,"\n id=\"%d\"", edgeNum);
+ fprintf(fgxFile,"\n source=\"%d\"", bSource->bbNum);
+ fprintf(fgxFile,"\n target=\"%d\"", bTarget->bbNum);
+ if (bSource->bbJumpKind == BBJ_SWITCH)
+ {
+ if (edge->flDupCount >= 2)
+ {
+ fprintf(fgxFile,"\n switchCases=\"%d\"", edge->flDupCount);
+ }
+ if (bSource->bbJumpSwt->getDefault() == bTarget)
+ {
+ fprintf(fgxFile,"\n switchDefault=\"true\"");
+ }
+ }
+ if (validWeights)
+ {
+ unsigned edgeWeight = (edge->flEdgeWeightMin + edge->flEdgeWeightMax) / 2;
+ fprintf(fgxFile,"\n weight=");
+ fprintfDouble(fgxFile, ((double) edgeWeight) / weightDivisor);
+
+ if (edge->flEdgeWeightMin != edge->flEdgeWeightMax)
+ {
+ fprintf(fgxFile,"\n minWeight=");
+ fprintfDouble(fgxFile, ((double) edge->flEdgeWeightMin) / weightDivisor);
+ fprintf(fgxFile,"\n maxWeight=");
+ fprintfDouble(fgxFile, ((double) edge->flEdgeWeightMax) / weightDivisor);
+ }
+
+ if (edgeWeight > 0)
+ {
+ if (edgeWeight < bSource->bbWeight)
+ {
+ fprintf(fgxFile,"\n out=");
+ fprintfDouble(fgxFile, ((double) edgeWeight) / sourceWeightDivisor );
+ }
+ if (edgeWeight < bTarget->bbWeight)
+ {
+ fprintf(fgxFile,"\n in=");
+ fprintfDouble(fgxFile, ((double) edgeWeight) / targetWeightDivisor);
+ }
+ }
+ }
+ }
+ if (!createDotFile)
+ {
+ fprintf(fgxFile, ">");
+ fprintf(fgxFile,"\n </edge>");
+ }
+ }
+ }
+ if (createDotFile)
+ {
+ fprintf(fgxFile, "}\n");
+ }
+ else
+ {
+ fprintf(fgxFile, "\n </edges>");
+ fprintf(fgxFile, "\n</method>\n");
+ }
+
+ if (dontClose)
+ {
+ // fgxFile is jitstdout or stderr
+ fprintf(fgxFile, "\n");
+ }
+ else
+ {
+ fclose(fgxFile);
+ }
+
+ return result;
+}
+
+#endif // DUMP_FLOWGRAPHS
+
+/*****************************************************************************/
+#ifdef DEBUG
+
+void Compiler::fgDispReach()
+{
+ printf("------------------------------------------------\n");
+ printf("BBnum Reachable by \n");
+ printf("------------------------------------------------\n");
+
+ for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+ {
+ printf("BB%02u : ", block->bbNum);
+ BLOCKSET_ITER_INIT(this, iter, block->bbReach, bbNum);
+ while (iter.NextElem(this, &bbNum))
+ {
+ printf("BB%02u ", bbNum);
+ }
+ printf("\n");
+ }
+}
+
+void Compiler::fgDispDoms()
+{
+ // Don't bother printing this when we have a large number of BasicBlocks in the method
+ if (fgBBcount > 256)
+ {
+ return;
+ }
+
+ printf("------------------------------------------------\n");
+ printf("BBnum Dominated by\n");
+ printf("------------------------------------------------\n");
+
+ for (unsigned i = 1; i <= fgBBNumMax; ++i)
+ {
+ BasicBlock* current = fgBBInvPostOrder[i];
+ printf("BB%02u: ", current->bbNum);
+ while (current != current->bbIDom)
+ {
+ printf("BB%02u ", current->bbNum);
+ current = current->bbIDom;
+ }
+ printf("\n");
+ }
+}
+
+/*****************************************************************************/
+
+void Compiler::fgTableDispBasicBlock(BasicBlock* block,
+ int ibcColWidth /* = 0 */)
+{
+ unsigned flags = block->bbFlags;
+
+ unsigned bbNumMax = compIsForInlining() ? impInlineInfo->InlinerCompiler->fgBBNumMax : fgBBNumMax;
+ int maxBlockNumWidth = CountDigits(bbNumMax);
+ maxBlockNumWidth = max(maxBlockNumWidth, 2);
+ int blockNumWidth = CountDigits(block->bbNum);
+ blockNumWidth = max(blockNumWidth, 2);
+ int blockNumPadding = maxBlockNumWidth - blockNumWidth;
+
+ printf("BB%02u%*s [%08p] %2u",
+ block->bbNum,
+ blockNumPadding, "",
+ dspPtr(block),
+ block->bbRefs);
+
+ //
+ // Display EH 'try' region index
+ //
+
+ if (block->hasTryIndex())
+ {
+ printf(" %2u", block->getTryIndex());
+ }
+ else
+ {
+ printf(" ");
+ }
+
+ //
+ // Display EH handler region index
+ //
+
+ if (block->hasHndIndex())
+ {
+ printf(" %2u", block->getHndIndex());
+ }
+ else
+ {
+ printf(" ");
+ }
+
+ printf(" ");
+
+ //
+ // Display block predecessor list
+ //
+
+ unsigned charCnt;
+ if (fgCheapPredsValid)
+ {
+ charCnt = block->dspCheapPreds();
+ }
+ else
+ {
+ charCnt = block->dspPreds();
+ }
+
+ if (charCnt < 19)
+ {
+ printf("%*s", 19 - charCnt, "");
+ }
+
+ printf(" ");
+
+ //
+ // Display block weight
+ //
+
+ if (block->isMaxBBWeight())
+ {
+ printf(" MAX ");
+ }
+ else
+ {
+ printf("%6s", refCntWtd2str(block->getBBWeight(this)));
+ }
+
+ //
+ // Display optional IBC weight column.
+ // Note that iColWidth includes one character for a leading space, if there is an IBC column.
+ //
+
+ if (ibcColWidth > 0)
+ {
+ if (block->bbFlags & BBF_PROF_WEIGHT)
+ {
+ printf("%*u", ibcColWidth, block->bbWeight);
+ }
+ else
+ {
+ // No IBC data. Just print spaces to align the column.
+ printf("%*s", ibcColWidth, "");
+ }
+ }
+
+ printf(" ");
+
+ //
+ // Display block IL range
+ //
+
+ block->dspBlockILRange();
+
+ //
+ // Display block branch target
+ //
+
+ if (flags & BBF_REMOVED)
+ {
+ printf( "[removed] ");
+ }
+ else
+ {
+ switch (block->bbJumpKind)
+ {
+ case BBJ_COND:
+ printf("-> BB%02u%*s ( cond )", block->bbJumpDest->bbNum, maxBlockNumWidth - max(CountDigits(block->bbJumpDest->bbNum), 2), "");
+ break;
+
+ case BBJ_CALLFINALLY:
+ printf("-> BB%02u%*s (callf )", block->bbJumpDest->bbNum, maxBlockNumWidth - max(CountDigits(block->bbJumpDest->bbNum), 2), "");
+ break;
+
+ case BBJ_ALWAYS:
+ if (flags & BBF_KEEP_BBJ_ALWAYS)
+ {
+ printf("-> BB%02u%*s (ALWAYS)", block->bbJumpDest->bbNum, maxBlockNumWidth - max(CountDigits(block->bbJumpDest->bbNum), 2), "");
+ }
+ else
+ {
+ printf("-> BB%02u%*s (always)", block->bbJumpDest->bbNum, maxBlockNumWidth - max(CountDigits(block->bbJumpDest->bbNum), 2), "");
+ }
+ break;
+
+ case BBJ_LEAVE:
+ printf("-> BB%02u%*s (leave )", block->bbJumpDest->bbNum, maxBlockNumWidth - max(CountDigits(block->bbJumpDest->bbNum), 2), "");
+ break;
+
+ case BBJ_EHFINALLYRET:
+ printf( "%*s (finret)", maxBlockNumWidth - 2, "");
+ break;
+
+ case BBJ_EHFILTERRET:
+ printf( "%*s (fltret)", maxBlockNumWidth - 2, "");
+ break;
+
+ case BBJ_EHCATCHRET:
+ printf("-> BB%02u%*s ( cret )", block->bbJumpDest->bbNum, maxBlockNumWidth - max(CountDigits(block->bbJumpDest->bbNum), 2), "");
+ break;
+
+ case BBJ_THROW:
+ printf( "%*s (throw )", maxBlockNumWidth - 2, "");
+ break;
+
+ case BBJ_RETURN:
+ printf( "%*s (return)", maxBlockNumWidth - 2, "");
+ break;
+
+ default:
+ printf( "%*s ", maxBlockNumWidth - 2, "");
+ break;
+
+ case BBJ_SWITCH:
+ printf("->");
+
+ unsigned jumpCnt;
+ jumpCnt = block->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab;
+ jumpTab = block->bbJumpSwt->bbsDstTab;
+ int switchWidth;
+ switchWidth = 0;
+ do
+ {
+ printf("%cBB%02u",
+ (jumpTab == block->bbJumpSwt->bbsDstTab) ? ' ' : ',',
+ (*jumpTab)->bbNum);
+ switchWidth += 1 /* space/comma */ + 2 /* BB */ + max(CountDigits((*jumpTab)->bbNum), 2);
+ }
+ while (++jumpTab, --jumpCnt);
+
+ if (switchWidth < 7)
+ {
+ printf("%*s", 8 - switchWidth, "");
+ }
+
+ printf(" (switch)");
+ break;
+ }
+ }
+
+ printf(" ");
+
+ //
+ // Display block EH region and type, including nesting indicator
+ //
+
+ if (block->hasTryIndex())
+ {
+ printf("T%d ", block->getTryIndex());
+ }
+ else
+ {
+ printf(" ");
+ }
+
+ if (block->hasHndIndex())
+ {
+ printf("H%d ", block->getHndIndex());
+ }
+ else
+ {
+ printf(" ");
+ }
+
+ if (flags & BBF_FUNCLET_BEG)
+ {
+ printf("F ");
+ }
+ else
+ {
+ printf(" ");
+ }
+
+ int cnt = 0;
+
+ switch (block->bbCatchTyp)
+ {
+ case BBCT_NONE: break;
+ case BBCT_FAULT: printf("fault "); cnt += 6; break;
+ case BBCT_FINALLY: printf("finally "); cnt += 8; break;
+ case BBCT_FILTER: printf("filter "); cnt += 7; break;
+ case BBCT_FILTER_HANDLER: printf("filtHnd "); cnt += 8; break;
+ default: printf("catch "); cnt += 6; break;
+ }
+
+ if (block->bbCatchTyp != BBCT_NONE)
+ {
+ cnt += 2;
+ printf("{ ");
+ /* brace matching editor workaround to compensate for the preceding line: } */
+ }
+
+ if (flags & BBF_TRY_BEG)
+ {
+ // Output a brace for every try region that this block opens
+
+ EHblkDsc* HBtab;
+ EHblkDsc* HBtabEnd;
+
+ for (HBtab = compHndBBtab, HBtabEnd = compHndBBtab + compHndBBtabCount;
+ HBtab < HBtabEnd;
+ HBtab++)
+ {
+ if (HBtab->ebdTryBeg == block)
+ {
+ cnt += 6;
+ printf("try { ");
+ /* brace matching editor workaround to compensate for the preceding line: } */
+ }
+ }
+ }
+
+ EHblkDsc* HBtab;
+ EHblkDsc* HBtabEnd;
+
+ for (HBtab = compHndBBtab, HBtabEnd = compHndBBtab + compHndBBtabCount;
+ HBtab < HBtabEnd;
+ HBtab++)
+ {
+ if (HBtab->ebdTryLast == block)
+ {
+ cnt += 2;
+ /* brace matching editor workaround to compensate for the following line: { */
+ printf("} ");
+ }
+ if (HBtab->ebdHndLast == block)
+ {
+ cnt += 2;
+ /* brace matching editor workaround to compensate for the following line: { */
+ printf("} ");
+ }
+ if (HBtab->HasFilter() && block->bbNext == HBtab->ebdHndBeg)
+ {
+ cnt += 2;
+ /* brace matching editor workaround to compensate for the following line: { */
+ printf("} ");
+ }
+ }
+
+ while (cnt < 12)
+ {
+ cnt++;
+ printf(" ");
+ }
+
+ //
+ // Display block flags
+ //
+
+ block->dspFlags();
+
+ printf("\n");
+}
+
+/****************************************************************************
+ Dump blocks from firstBlock to lastBlock.
+*/
+
+void Compiler::fgDispBasicBlocks(BasicBlock* firstBlock,
+ BasicBlock* lastBlock,
+ bool dumpTrees)
+{
+ BasicBlock* block;
+
+ int padWidth = 0;
+#ifdef _TARGET_AMD64_
+ padWidth = 8;
+#endif // _TARGET_AMD64_
+
+ // If any block has IBC data, we add an "IBC weight" column just before the 'IL range' column. This column is as
+ // wide as necessary to accommodate all the various IBC weights. It's at least 4 characters wide, to accommodate
+ // the "IBC" title and leading space.
+ int ibcColWidth = 0;
+ for (block = firstBlock; block != nullptr; block = block->bbNext)
+ {
+ if (block->bbFlags & BBF_PROF_WEIGHT)
+ {
+ int thisIbcWidth = CountDigits(block->bbWeight);
+ ibcColWidth = max(ibcColWidth, thisIbcWidth);
+ }
+
+ if (block == lastBlock) {
+ break;
+ }
+ }
+ if (ibcColWidth > 0)
+ {
+ ibcColWidth = max(ibcColWidth, 3) + 1; // + 1 for the leading space
+ }
+
+ unsigned bbNumMax = compIsForInlining() ? impInlineInfo->InlinerCompiler->fgBBNumMax : fgBBNumMax;
+ int maxBlockNumWidth = CountDigits(bbNumMax);
+ maxBlockNumWidth = max(maxBlockNumWidth, 2);
+
+ padWidth += maxBlockNumWidth - 2; // Account for functions with a large number of blocks.
+
+ printf("\n");
+ printf("------%*s------------------------------------%*s-----------------------%*s----------------------------------------\n",
+ padWidth, "------------",
+ ibcColWidth, "------------",
+ maxBlockNumWidth, "----");
+ printf("BBnum %*sdescAddr ref try hnd %s weight %*s%s [IL range] [jump]%*s [EH region] [flags]\n",
+ padWidth, "",
+ fgCheapPredsValid ? "cheap preds" :
+ (fgComputePredsDone ? "preds "
+ : " "),
+ ((ibcColWidth > 0) ? ibcColWidth - 3 : 0), "", // Subtract 3 for the width of "IBC", printed next.
+ ((ibcColWidth > 0) ? "IBC"
+ : ""),
+ maxBlockNumWidth, ""
+ );
+ printf("------%*s------------------------------------%*s-----------------------%*s----------------------------------------\n",
+ padWidth, "------------",
+ ibcColWidth, "------------",
+ maxBlockNumWidth, "----");
+
+ for (block = firstBlock;
+ block;
+ block = block->bbNext)
+ {
+ // First, do some checking on the bbPrev links
+ if (block->bbPrev)
+ {
+ if (block->bbPrev->bbNext != block)
+ {
+ printf("bad prev link\n");
+ }
+ }
+ else if (block != fgFirstBB)
+ {
+ printf("bad prev link!\n");
+ }
+
+ if (block == fgFirstColdBlock)
+ {
+ printf("~~~~~~%*s~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~%*s~~~~~~~~~~~~~~~~~~~~~~~%*s~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n",
+ padWidth, "~~~~~~~~~~~~",
+ ibcColWidth, "~~~~~~~~~~~~",
+ maxBlockNumWidth, "~~~~");
+ }
+
+#if FEATURE_EH_FUNCLETS
+ if (block == fgFirstFuncletBB)
+ {
+ printf("++++++%*s++++++++++++++++++++++++++++++++++++%*s+++++++++++++++++++++++%*s++++++++++++++++++++++++++++++++++++++++ funclets follow\n",
+ padWidth, "++++++++++++",
+ ibcColWidth, "++++++++++++",
+ maxBlockNumWidth, "++++");
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ fgTableDispBasicBlock(block, ibcColWidth);
+
+ if (block == lastBlock) {
+ break;
+ }
+ }
+
+ printf("------%*s------------------------------------%*s-----------------------%*s----------------------------------------\n",
+ padWidth, "------------",
+ ibcColWidth, "------------",
+ maxBlockNumWidth, "----");
+
+ if (dumpTrees)
+ {
+ fgDumpTrees(firstBlock, lastBlock);
+ }
+}
+
+/*****************************************************************************/
+
+void Compiler::fgDispBasicBlocks(bool dumpTrees)
+{
+ fgDispBasicBlocks(fgFirstBB, nullptr, dumpTrees);
+}
+
+/*****************************************************************************/
+// Increment the stmtNum and dump the tree using gtDispTree
+//
+void Compiler::fgDumpStmtTree(GenTreePtr stmt, unsigned blkNum)
+{
+ compCurStmtNum++; // Increment the current stmtNum
+
+ printf("\n***** BB%02u, stmt %d\n", blkNum, compCurStmtNum);
+
+ if (fgOrder == FGOrderLinear || opts.compDbgInfo)
+ {
+ gtDispTree(stmt);
+ }
+ else
+ {
+ gtDispTree(stmt->gtStmt.gtStmtExpr);
+ }
+}
+
+//------------------------------------------------------------------------
+// Compiler::fgDumpBlock: dumps the contents of the given block to stdout.
+//
+// Arguments:
+// block - The block to dump.
+//
+void Compiler::fgDumpBlock(BasicBlock* block)
+{
+ printf("\n------------ ");
+ block->dspBlockHeader(this);
+
+ if (!block->IsLIR())
+ {
+ for (GenTreeStmt* stmt = block->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt)
+ {
+ fgDumpStmtTree(stmt, block->bbNum);
+ if (stmt == block->bbTreeList)
+ {
+ block->bbStmtNum = compCurStmtNum; // Set the block->bbStmtNum
+ }
+ }
+ }
+ else
+ {
+ gtDispRange(LIR::AsRange(block));
+ }
+}
+
+/*****************************************************************************/
+// Walk the BasicBlock list calling fgDumpTree once per Stmt
+//
+void Compiler::fgDumpTrees(BasicBlock* firstBlock,
+ BasicBlock* lastBlock)
+{
+ compCurStmtNum = 0; // Reset the current stmtNum
+
+ /* Walk the basic blocks */
+
+ // Note that typically we have already called fgDispBasicBlocks()
+ // so we don't need to print the preds and succs again here
+ //
+ for (BasicBlock* block = firstBlock; block; block = block->bbNext)
+ {
+ fgDumpBlock(block);
+
+ if (block == lastBlock) {
+ break;
+ }
+ }
+ printf("\n-------------------------------------------------------------------------------------------------------------------\n");
+}
+
+
+/*****************************************************************************
+ * Try to create as many candidates for GTF_MUL_64RSLT as possible.
+ * We convert 'intOp1*intOp2' into 'int(long(nop(intOp1))*long(intOp2))'.
+ */
+
+/* static */
+Compiler::fgWalkResult Compiler::fgStress64RsltMulCB(GenTreePtr* pTree, fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+ Compiler* pComp = data->compiler;
+
+ if (tree->gtOper != GT_MUL || tree->gtType != TYP_INT || (tree->gtOverflow())) {
+ return WALK_CONTINUE;
+}
+
+ // To ensure optNarrowTree() doesn't fold back to the original tree.
+ tree->gtOp.gtOp1 = pComp->gtNewOperNode(GT_NOP, TYP_LONG, tree->gtOp.gtOp1);
+ tree->gtOp.gtOp1 = pComp->gtNewCastNode(TYP_LONG, tree->gtOp.gtOp1, TYP_LONG);
+ tree->gtOp.gtOp2 = pComp->gtNewCastNode(TYP_LONG, tree->gtOp.gtOp2, TYP_LONG);
+ tree->gtType = TYP_LONG;
+ *pTree = pComp->gtNewCastNode(TYP_INT, tree, TYP_INT);
+
+ return WALK_SKIP_SUBTREES;
+}
+
+void Compiler::fgStress64RsltMul()
+{
+ if (!compStressCompile(STRESS_64RSLT_MUL, 20)) {
+ return;
+}
+
+ fgWalkAllTreesPre(fgStress64RsltMulCB, (void*)this);
+}
+
+
+// This variable is used to generate "traversal labels": one-time constants with which
+// we label basic blocks that are members of the basic block list, in order to have a
+// fast, high-probability test for membership in that list. Type is "volatile" because
+// it's incremented with an atomic operation, which wants a volatile type; "long" so that
+// wrap-around to 0 (which I think has the highest probability of accidental collision) is
+// postponed a *long* time.
+static volatile int bbTraverseLabel = 1;
+
+/*****************************************************************************
+ *
+ * A DEBUG routine to check the consistency of the flowgraph,
+ * i.e. bbNum, bbRefs, bbPreds have to be up to date.
+ *
+ *****************************************************************************/
+
+void Compiler::fgDebugCheckBBlist(bool checkBBNum /* = false */,
+ bool checkBBRefs /* = true */)
+{
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("*************** In fgDebugCheckBBlist\n");
+ }
+#endif // DEBUG
+
+ fgDebugCheckBlockLinks();
+
+ if (fgBBcount > 10000 && expensiveDebugCheckLevel < 1)
+ {
+ // The basic block checks are too expensive if there are too many blocks,
+ // so give up unless we've been told to try hard.
+ return;
+ }
+
+ DWORD startTickCount = GetTickCount();
+
+ BasicBlock* block;
+ BasicBlock* prevBlock;
+ BasicBlock* blockPred;
+ flowList* pred;
+ unsigned blockRefs;
+
+#if FEATURE_EH_FUNCLETS
+ bool reachedFirstFunclet = false;
+ if (fgFuncletsCreated)
+ {
+ //
+ // Make sure that fgFirstFuncletBB is accurate.
+ // It should be the first basic block in a handler region.
+ //
+ if (fgFirstFuncletBB != nullptr)
+ {
+ assert(fgFirstFuncletBB->hasHndIndex() == true);
+ assert(fgFirstFuncletBB->bbFlags & BBF_FUNCLET_BEG);
+ }
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ /* Check bbNum, bbRefs and bbPreds */
+ // First, pick a traversal stamp, and label all the blocks with it.
+ unsigned curTraversalStamp = unsigned(InterlockedIncrement((LONG*)&bbTraverseLabel));
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ block->bbTraversalStamp = curTraversalStamp;
+ }
+
+ for (prevBlock = nullptr, block = fgFirstBB;
+ block;
+ prevBlock = block, block = block->bbNext)
+ {
+ blockRefs = 0;
+
+ /* First basic block has countOfInEdges() >= 1 */
+
+ if (block == fgFirstBB)
+ {
+ noway_assert(block->countOfInEdges() >= 1);
+ blockRefs = 1;
+ }
+
+ if (checkBBNum)
+ {
+ // Check that bbNum is sequential
+ noway_assert(block->bbNext == nullptr || (block->bbNum + 1 == block->bbNext->bbNum));
+ }
+
+ // If the block is a BBJ_COND, a BBJ_SWITCH or a
+ // lowered GT_SWITCH_TABLE node then make sure it
+ // ends with a GT_JTRUE or a GT_SWITCH
+
+ if (block->bbJumpKind == BBJ_COND)
+ {
+ noway_assert(block->lastNode()->gtNext == nullptr && block->lastNode()->gtOper == GT_JTRUE);
+ }
+ else if (block->bbJumpKind == BBJ_SWITCH)
+ {
+#ifndef LEGACY_BACKEND
+ noway_assert(block->lastNode()->gtNext == nullptr &&
+ (block->lastNode()->gtOper == GT_SWITCH ||
+ block->lastNode()->gtOper == GT_SWITCH_TABLE));
+#else // LEGACY_BACKEND
+ noway_assert(block->lastStmt()->gtNext == NULL &&
+ block->lastStmt()->gtStmtExpr->gtOper == GT_SWITCH);
+#endif // LEGACY_BACKEND
+ }
+ else if (!( block->bbJumpKind == BBJ_ALWAYS
+ || block->bbJumpKind == BBJ_RETURN))
+ {
+ //this block cannot have a poll
+ noway_assert(!(block->bbFlags & BBF_NEEDS_GCPOLL));
+ }
+
+ if (block->bbCatchTyp == BBCT_FILTER)
+ {
+ if (!fgCheapPredsValid) // Don't check cheap preds
+ {
+ // A filter has no predecessors
+ noway_assert(block->bbPreds == nullptr);
+ }
+ }
+
+#if FEATURE_EH_FUNCLETS
+ if (fgFuncletsCreated)
+ {
+ //
+ // There should be no handler blocks until
+ // we get to the fgFirstFuncletBB block,
+ // then every block should be a handler block
+ //
+ if (!reachedFirstFunclet)
+ {
+ if (block == fgFirstFuncletBB)
+ {
+ assert(block->hasHndIndex() == true);
+ reachedFirstFunclet = true;
+ }
+ else
+ {
+ assert(block->hasHndIndex() == false);
+ }
+ }
+ else // reachedFirstFunclet
+ {
+ assert(block->hasHndIndex() == true);
+ }
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ // Don't check cheap preds.
+ for (pred = (fgCheapPredsValid ? nullptr : block->bbPreds); pred != nullptr; blockRefs += pred->flDupCount, pred = pred->flNext)
+ {
+ assert(fgComputePredsDone); // If this isn't set, why do we have a preds list?
+
+ /* make sure this pred is part of the BB list */
+
+ blockPred = pred->flBlock;
+ noway_assert(blockPred->bbTraversalStamp == curTraversalStamp);
+
+ EHblkDsc* ehTryDsc = ehGetBlockTryDsc(block);
+ if (ehTryDsc != nullptr)
+ {
+ // You can jump to the start of a try
+ if (ehTryDsc->ebdTryBeg == block) {
+ goto CHECK_HND;
+}
+
+ // You can jump within the same try region
+ if (bbInTryRegions(block->getTryIndex(), blockPred)) {
+ goto CHECK_HND;
+}
+
+ // The catch block can jump back into the middle of the try
+ if (bbInCatchHandlerRegions(block, blockPred)) {
+ goto CHECK_HND;
+}
+
+ // The end of a finally region is a BBJ_EHFINALLYRET block (during importing, BBJ_LEAVE) which
+ // is marked as "returning" to the BBJ_ALWAYS block following the BBJ_CALLFINALLY
+ // block that does a local call to the finally. This BBJ_ALWAYS is within
+ // the try region protected by the finally (for x86, ARM), but that's ok.
+ if (prevBlock->bbJumpKind == BBJ_CALLFINALLY &&
+ block->bbJumpKind == BBJ_ALWAYS &&
+ blockPred->bbJumpKind == BBJ_EHFINALLYRET) {
+ goto CHECK_HND;
+}
+
+ printf("Jump into the middle of try region: BB%02u branches to BB%02u\n", blockPred->bbNum, block->bbNum);
+ noway_assert(!"Jump into middle of try region");
+ }
+
+CHECK_HND:;
+
+ EHblkDsc* ehHndDsc = ehGetBlockHndDsc(block);
+ if (ehHndDsc != nullptr)
+ {
+ // You can do a BBJ_EHFINALLYRET or BBJ_EHFILTERRET into a handler region
+ if ( (blockPred->bbJumpKind == BBJ_EHFINALLYRET)
+ || (blockPred->bbJumpKind == BBJ_EHFILTERRET)) {
+ goto CHECK_JUMP;
+}
+
+ // Our try block can call our finally block
+ if ((block->bbCatchTyp == BBCT_FINALLY) &&
+ (blockPred->bbJumpKind == BBJ_CALLFINALLY) &&
+ ehCallFinallyInCorrectRegion(blockPred, block->getHndIndex()))
+ {
+ goto CHECK_JUMP;
+ }
+
+ // You can jump within the same handler region
+ if (bbInHandlerRegions(block->getHndIndex(), blockPred)) {
+ goto CHECK_JUMP;
+}
+
+ // A filter can jump to the start of the filter handler
+ if (ehHndDsc->HasFilter()) {
+ goto CHECK_JUMP;
+}
+
+ printf("Jump into the middle of handler region: BB%02u branches to BB%02u\n", blockPred->bbNum, block->bbNum);
+ noway_assert(!"Jump into the middle of handler region");
+ }
+
+CHECK_JUMP:;
+
+ switch (blockPred->bbJumpKind)
+ {
+ case BBJ_COND:
+ noway_assert(blockPred->bbNext == block || blockPred->bbJumpDest == block);
+ break;
+
+ case BBJ_NONE:
+ noway_assert(blockPred->bbNext == block);
+ break;
+
+ case BBJ_CALLFINALLY:
+ case BBJ_ALWAYS:
+ case BBJ_EHCATCHRET:
+ case BBJ_EHFILTERRET:
+ noway_assert(blockPred->bbJumpDest == block);
+ break;
+
+ case BBJ_EHFINALLYRET:
+ {
+ // If the current block is a successor to a BBJ_EHFINALLYRET (return from finally),
+ // then the lexically previous block should be a call to the same finally.
+ // Verify all of that.
+
+ unsigned hndIndex = blockPred->getHndIndex();
+ EHblkDsc* ehDsc = ehGetDsc(hndIndex);
+ BasicBlock* finBeg = ehDsc->ebdHndBeg;
+
+ // Because there is no bbPrev, we have to search for the lexically previous
+ // block. We can shorten the search by only looking in places where it is legal
+ // to have a call to the finally.
+
+ BasicBlock* begBlk;
+ BasicBlock* endBlk;
+ ehGetCallFinallyBlockRange(hndIndex, &begBlk, &endBlk);
+
+ for (BasicBlock* bcall = begBlk; bcall != endBlk; bcall = bcall->bbNext)
+ {
+ if (bcall->bbJumpKind != BBJ_CALLFINALLY || bcall->bbJumpDest != finBeg) {
+ continue;
+}
+
+ if (block == bcall->bbNext) {
+ goto PRED_OK;
+ }
+ }
+
+#if FEATURE_EH_FUNCLETS
+
+ if (fgFuncletsCreated)
+ {
+ // There is no easy way to search just the funclets that were pulled out of
+ // the corresponding try body, so instead we search all the funclets, and if
+ // we find a potential 'hit' we check if the funclet we're looking at is
+ // from the correct try region.
+
+ for (BasicBlock* bcall = fgFirstFuncletBB; bcall; bcall = bcall->bbNext)
+ {
+ if (bcall->bbJumpKind != BBJ_CALLFINALLY || bcall->bbJumpDest != finBeg) {
+ continue;
+}
+
+ if (block != bcall->bbNext) {
+ continue;
+}
+
+ if (ehCallFinallyInCorrectRegion(bcall, hndIndex)) {
+ goto PRED_OK;
+ }
+ }
+ }
+
+#endif // FEATURE_EH_FUNCLETS
+
+ noway_assert(!"BBJ_EHFINALLYRET predecessor of block that doesn't follow a BBJ_CALLFINALLY!");
+ }
+ break;
+
+ case BBJ_THROW:
+ case BBJ_RETURN:
+ noway_assert(!"THROW and RETURN block cannot be in the predecessor list!");
+ break;
+
+ case BBJ_SWITCH:
+ unsigned jumpCnt; jumpCnt = blockPred->bbJumpSwt->bbsCount;
+ BasicBlock** jumpTab; jumpTab = blockPred->bbJumpSwt->bbsDstTab;
+
+ do
+ {
+ if (block == *jumpTab)
+ {
+ goto PRED_OK;
+ }
+ }
+ while (++jumpTab, --jumpCnt);
+
+ noway_assert(!"SWITCH in the predecessor list with no jump label to BLOCK!");
+ break;
+
+ default:
+ noway_assert(!"Unexpected bbJumpKind");
+ break;
+ }
+
+PRED_OK:;
+
+ }
+
+ /* Check the bbRefs */
+ noway_assert(!checkBBRefs || block->bbRefs == blockRefs);
+
+ /* Check that BBF_HAS_HANDLER is valid bbTryIndex */
+ if (block->hasTryIndex())
+ {
+ noway_assert(block->getTryIndex() < compHndBBtabCount);
+ }
+
+ /* Check if BBF_RUN_RARELY is set that we have bbWeight of zero */
+ if (block->isRunRarely())
+ {
+ noway_assert(block->bbWeight == BB_ZERO_WEIGHT);
+ }
+ else
+ {
+ noway_assert(block->bbWeight > BB_ZERO_WEIGHT);
+ }
+ }
+
+ // Make sure the one return BB is not changed.
+ if (genReturnBB)
+ {
+ noway_assert(genReturnBB->bbTreeList);
+ noway_assert(genReturnBB->IsLIR() || genReturnBB->bbTreeList->gtOper == GT_STMT);
+ noway_assert(genReturnBB->IsLIR() || genReturnBB->bbTreeList->gtType == TYP_VOID);
+ }
+
+ // The general encoder/decoder (currently) only reports "this" as a generics context as a stack location,
+ // so we mark info.compThisArg as lvAddrTaken to ensure that it is not enregistered. Otherwise, it should
+ // not be address-taken. This variable determines if the address-taken-ness of "thisArg" is "OK".
+ bool copiedForGenericsCtxt;
+#ifndef JIT32_GCENCODER
+ copiedForGenericsCtxt = ((info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_THIS) != 0);
+#else // JIT32_GCENCODER
+ copiedForGenericsCtxt = FALSE;
+#endif // JIT32_GCENCODER
+
+ // This if only in support of the noway_asserts it contains.
+ if (info.compIsStatic)
+ {
+ // For static method, should have never grabbed the temp.
+ noway_assert(lvaArg0Var == BAD_VAR_NUM);
+ }
+ else
+ {
+ // For instance method:
+ assert(info.compThisArg != BAD_VAR_NUM);
+ bool compThisArgAddrExposedOK = !lvaTable[info.compThisArg].lvAddrExposed;
+#ifndef JIT32_GCENCODER
+ compThisArgAddrExposedOK = compThisArgAddrExposedOK || copiedForGenericsCtxt;
+#endif // !JIT32_GCENCODER
+ noway_assert(compThisArgAddrExposedOK && // should never expose the address of arg 0 or
+ !lvaTable[info.compThisArg].lvArgWrite && // write to arg 0.
+ ( // In addition,
+ lvaArg0Var == info.compThisArg || // lvArg0Var should remain 0 if arg0 is not written to or address-exposed.
+ lvaArg0Var != info.compThisArg &&
+ (lvaTable[lvaArg0Var].lvAddrExposed || lvaTable[lvaArg0Var].lvArgWrite || copiedForGenericsCtxt)
+ ));
+ }
+}
+
+/*****************************************************************************
+ *
+ * A DEBUG routine to check the that the exception flags are correctly set.
+ *
+ ****************************************************************************/
+
+void Compiler::fgDebugCheckFlags(GenTreePtr tree)
+{
+ noway_assert(tree->gtOper != GT_STMT);
+
+ genTreeOps oper = tree->OperGet();
+ unsigned kind = tree->OperKind();
+ unsigned treeFlags = tree->gtFlags & GTF_ALL_EFFECT;
+ unsigned chkFlags = 0;
+
+ /* Is this a leaf node? */
+
+ if (kind & GTK_LEAF)
+ {
+ switch (oper)
+ {
+ case GT_CLS_VAR:
+ chkFlags |= GTF_GLOB_REF;
+ break;
+
+ case GT_CATCH_ARG:
+ chkFlags |= GTF_ORDER_SIDEEFF;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Is it a 'simple' unary/binary operator? */
+
+ else if (kind & GTK_SMPOP)
+ {
+ GenTreePtr op1 = tree->gtOp.gtOp1;
+ GenTreePtr op2 = tree->gtGetOp2();
+
+ // During GS work, we make shadow copies for params.
+ // In gsParamsToShadows(), we create a shadow var of TYP_INT for every small type param.
+ // Then in gsReplaceShadowParams(), we change the gtLclNum to the shadow var.
+ // We also change the types of the local var tree and the assignment tree to TYP_INT if necessary.
+ // However, since we don't morph the tree at this late stage. Manually propagating
+ // TYP_INT up to the GT_ASG tree is only correct if we don't need to propagate the TYP_INT back up.
+ // The following checks will ensure this.
+
+ // Is the left child of "tree" a GT_ASG?,
+ if (op1 && op1->gtOper == GT_ASG)
+ {
+ assert(tree->gtType == TYP_VOID || // If parent is a TYP_VOID, we don't no need to propagate TYP_INT up. We are fine.
+ tree->gtOper == GT_COMMA); // (or) If GT_ASG is the left child of a GT_COMMA, the type of the GT_COMMA node will
+ } // be determined by its right child. So we don't need to propagate TYP_INT up either. We are fine.
+
+ // Is the right child of "tree" a GT_ASG?,
+ if (op2 && op2->gtOper == GT_ASG)
+ {
+ assert(tree->gtType == TYP_VOID); // If parent is a TYP_VOID, we don't no need to propagate TYP_INT up. We are fine.
+ }
+
+ switch (oper)
+ {
+ case GT_QMARK:
+ if (op1->OperIsCompare())
+ {
+ noway_assert(op1->gtFlags & GTF_DONT_CSE);
+ }
+ else
+ {
+ noway_assert( (op1->gtOper == GT_CNS_INT) &&
+ ((op1->gtIntCon.gtIconVal == 0) || (op1->gtIntCon.gtIconVal == 1)) );
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Recursively check the subtrees */
+
+ if (op1) { fgDebugCheckFlags(op1);
+}
+ if (op2) { fgDebugCheckFlags(op2);
+}
+
+ if (op1) { chkFlags |= (op1->gtFlags & GTF_ALL_EFFECT);
+}
+ if (op2) { chkFlags |= (op2->gtFlags & GTF_ALL_EFFECT);
+}
+
+ // We reuse the value of GTF_REVERSE_OPS for a GT_IND-specific flag,
+ // so exempt that (unary) operator.
+ if (tree->OperGet() != GT_IND && tree->gtFlags & GTF_REVERSE_OPS)
+ {
+ /* Must have two operands if GTF_REVERSE is set */
+ noway_assert(op1 && op2);
+
+ /* Make sure that the order of side effects has not been swapped. */
+
+ /* However CSE may introduce an assignment after the reverse flag
+ was set and thus GTF_ASG cannot be considered here. */
+
+ /* For a GT_ASG(GT_IND(x), y) we are interested in the side effects of x */
+ GenTreePtr op1p;
+ if ((kind & GTK_ASGOP) && (op1->gtOper == GT_IND))
+ {
+ op1p = op1->gtOp.gtOp1;
+ }
+ else
+ {
+ op1p = op1;
+ }
+
+ /* This isn't true any more with the sticky GTF_REVERSE */
+ /*
+ // if op1p has side effects, then op2 cannot have side effects
+ if (op1p->gtFlags & (GTF_SIDE_EFFECT & ~GTF_ASG))
+ {
+ if (op2->gtFlags & (GTF_SIDE_EFFECT & ~GTF_ASG))
+ gtDispTree(tree);
+ noway_assert(!(op2->gtFlags & (GTF_SIDE_EFFECT & ~GTF_ASG)));
+ }
+ */
+ }
+
+ if (kind & GTK_ASGOP)
+ {
+ chkFlags |= GTF_ASG;
+ }
+
+ /* Note that it is OK for treeFlags not to have a GTF_EXCEPT,
+ AssertionProp's non-Null may have cleared it */
+ if (tree->OperMayThrow())
+ {
+ chkFlags |= (treeFlags & GTF_EXCEPT);
+ }
+
+ if (oper == GT_ADDR &&
+ (op1->OperIsLocal() ||
+ op1->gtOper == GT_CLS_VAR ||
+ (op1->gtOper == GT_IND && op1->gtOp.gtOp1->gtOper == GT_CLS_VAR_ADDR)))
+ {
+ /* &aliasedVar doesn't need GTF_GLOB_REF, though alisasedVar does.
+ Similarly for clsVar */
+ treeFlags |= GTF_GLOB_REF;
+ }
+ }
+
+ /* See what kind of a special operator we have here */
+
+ else { switch (tree->OperGet())
+ {
+ case GT_CALL:
+
+ GenTreePtr args;
+ GenTreePtr argx;
+ GenTreeCall* call;
+
+ call = tree->AsCall();
+
+ chkFlags |= GTF_CALL;
+
+ if ((treeFlags & GTF_EXCEPT) && !(chkFlags & GTF_EXCEPT))
+ {
+ switch (eeGetHelperNum(tree->gtCall.gtCallMethHnd))
+ {
+ // Is this a helper call that can throw an exception ?
+ case CORINFO_HELP_LDIV:
+ case CORINFO_HELP_LMOD:
+ case CORINFO_HELP_METHOD_ACCESS_CHECK:
+ case CORINFO_HELP_FIELD_ACCESS_CHECK:
+ case CORINFO_HELP_CLASS_ACCESS_CHECK:
+ case CORINFO_HELP_DELEGATE_SECURITY_CHECK:
+ chkFlags |= GTF_EXCEPT;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (call->gtCallObjp)
+ {
+ fgDebugCheckFlags(call->gtCallObjp);
+ chkFlags |= (call->gtCallObjp->gtFlags & GTF_SIDE_EFFECT);
+
+ if (call->gtCallObjp->gtFlags & GTF_ASG)
+ {
+ treeFlags |= GTF_ASG;
+ }
+ }
+
+ for (args = call->gtCallArgs; args; args = args->gtOp.gtOp2)
+ {
+ argx = args->gtOp.gtOp1;
+ fgDebugCheckFlags(argx);
+
+ chkFlags |= (argx->gtFlags & GTF_SIDE_EFFECT);
+
+ if (argx->gtFlags & GTF_ASG)
+ {
+ treeFlags |= GTF_ASG;
+ }
+ }
+
+ for (args = call->gtCallLateArgs; args; args = args->gtOp.gtOp2)
+ {
+ argx = args->gtOp.gtOp1;
+ fgDebugCheckFlags(argx);
+
+ chkFlags |= (argx->gtFlags & GTF_SIDE_EFFECT);
+
+ if (argx->gtFlags & GTF_ASG)
+ {
+ treeFlags |= GTF_ASG;
+ }
+ }
+
+ if ((call->gtCallType == CT_INDIRECT) && (call->gtCallCookie != nullptr))
+ {
+ fgDebugCheckFlags(call->gtCallCookie);
+ chkFlags |= (call->gtCallCookie->gtFlags & GTF_SIDE_EFFECT);
+ }
+
+ if (call->gtCallType == CT_INDIRECT)
+ {
+ fgDebugCheckFlags(call->gtCallAddr);
+ chkFlags |= (call->gtCallAddr->gtFlags & GTF_SIDE_EFFECT);
+ }
+
+ if (call->IsUnmanaged() &&
+ (call->gtCallMoreFlags & GTF_CALL_M_UNMGD_THISCALL))
+ {
+ if (call->gtCallArgs->gtOp.gtOp1->OperGet() == GT_NOP)
+ {
+ noway_assert(call->gtCallLateArgs->gtOp.gtOp1->TypeGet() == TYP_I_IMPL ||
+ call->gtCallLateArgs->gtOp.gtOp1->TypeGet() == TYP_BYREF);
+ }
+ else
+ {
+ noway_assert(call->gtCallArgs->gtOp.gtOp1->TypeGet() == TYP_I_IMPL ||
+ call->gtCallArgs->gtOp.gtOp1->TypeGet() == TYP_BYREF);
+ }
+ }
+ break;
+
+ case GT_ARR_ELEM:
+
+ GenTreePtr arrObj;
+ unsigned dim;
+
+ arrObj = tree->gtArrElem.gtArrObj;
+ fgDebugCheckFlags(arrObj);
+ chkFlags |= (arrObj->gtFlags & GTF_ALL_EFFECT);
+
+ for (dim = 0; dim < tree->gtArrElem.gtArrRank; dim++)
+ {
+ fgDebugCheckFlags(tree->gtArrElem.gtArrInds[dim]);
+ chkFlags |= tree->gtArrElem.gtArrInds[dim]->gtFlags & GTF_ALL_EFFECT;
+ }
+ break;
+
+ case GT_ARR_OFFSET:
+ fgDebugCheckFlags(tree->gtArrOffs.gtOffset);
+ chkFlags |= (tree->gtArrOffs.gtOffset->gtFlags & GTF_ALL_EFFECT);
+ fgDebugCheckFlags(tree->gtArrOffs.gtIndex);
+ chkFlags |= (tree->gtArrOffs.gtIndex->gtFlags & GTF_ALL_EFFECT);
+ fgDebugCheckFlags(tree->gtArrOffs.gtArrObj);
+ chkFlags |= (tree->gtArrOffs.gtArrObj->gtFlags & GTF_ALL_EFFECT);
+ break;
+
+ default:
+ break;
+ }
+}
+
+ if (chkFlags & ~treeFlags)
+ {
+ // Print the tree so we can see it in the log.
+ printf("Missing flags on tree [%06d]: ", dspTreeID(tree));
+ GenTree::gtDispFlags(chkFlags & ~treeFlags, GTF_DEBUG_NONE);
+ printf("\n");
+ gtDispTree(tree);
+
+ noway_assert(!"Missing flags on tree");
+
+ // Print the tree again so we can see it right after we hook up the debugger.
+ printf("Missing flags on tree [%06d]: ", dspTreeID(tree));
+ GenTree::gtDispFlags(chkFlags & ~treeFlags, GTF_DEBUG_NONE);
+ printf("\n");
+ gtDispTree(tree);
+ }
+ else if (treeFlags & ~chkFlags)
+ {
+#if 0
+ // TODO-Cleanup:
+ /* The tree has extra flags set. However, this will happen if we
+ replace a subtree with something, but don't clear the flags up
+ the tree. Can't flag this unless we start clearing flags above.
+
+ Note: we need this working for GTF_CALL and CSEs, so I'm enabling
+ it for calls.
+ */
+ if (tree->OperGet() != GT_CALL && (treeFlags & GTF_CALL) && !(chkFlags & GTF_CALL))
+ {
+ // Print the tree so we can see it in the log.
+ printf("Extra GTF_CALL flags on parent tree [%X]: ", tree);
+ GenTree::gtDispFlags(treeFlags & ~chkFlags, GTF_DEBUG_NONE);
+ printf("\n");
+ gtDispTree(tree);
+
+ noway_assert(!"Extra flags on tree");
+
+ // Print the tree again so we can see it right after we hook up the debugger.
+ printf("Extra GTF_CALL flags on parent tree [%X]: ", tree);
+ GenTree::gtDispFlags(treeFlags & ~chkFlags, GTF_DEBUG_NONE);
+ printf("\n");
+ gtDispTree(tree);
+ }
+#endif // 0
+ }
+}
+
+// DEBUG routine to check correctness of the internal gtNext, gtPrev threading of a statement.
+// This threading is only valid when fgStmtListThreaded is true.
+// This calls an alternate method for FGOrderLinear.
+void Compiler::fgDebugCheckNodeLinks(BasicBlock* block, GenTree* node)
+{
+ // LIR blocks are checked using BasicBlock::CheckLIR().
+ if (block->IsLIR())
+ {
+ LIR::AsRange(block).CheckLIR(this);
+ // TODO: return?
+ }
+
+ GenTreeStmt* stmt = node->AsStmt();
+
+ assert(fgStmtListThreaded);
+
+ noway_assert(stmt->gtStmtList);
+
+ // The first node's gtPrev must be nullptr (the gtPrev list is not circular).
+ // The last node's gtNext must be nullptr (the gtNext list is not circular). This is tested if the loop below terminates.
+ assert(stmt->gtStmtList->gtPrev == nullptr);
+
+ for (GenTreePtr tree = stmt->gtStmtList;
+ tree != nullptr;
+ tree = tree->gtNext)
+ {
+ if (tree->gtPrev)
+ {
+ noway_assert(tree->gtPrev->gtNext == tree);
+ }
+ else
+ {
+ noway_assert(tree == stmt->gtStmtList);
+ }
+
+ if (tree->gtNext)
+ {
+ noway_assert(tree->gtNext->gtPrev == tree);
+ }
+ else
+ {
+ noway_assert(tree == stmt->gtStmtExpr);
+ }
+
+ /* Cross-check gtPrev,gtNext with gtOp for simple trees */
+
+ GenTreePtr expectedPrevTree = nullptr;
+
+ if (tree->OperIsLeaf())
+ {
+ if (tree->gtOper == GT_CATCH_ARG)
+ {
+ // The GT_CATCH_ARG should always have GTF_ORDER_SIDEEFF set
+ noway_assert(tree->gtFlags & GTF_ORDER_SIDEEFF);
+ // The GT_CATCH_ARG has to be the first thing evaluated
+ noway_assert(stmt == block->FirstNonPhiDef());
+ noway_assert(stmt->gtStmtList->gtOper == GT_CATCH_ARG);
+ // The root of the tree should have GTF_ORDER_SIDEEFF set
+ noway_assert(stmt->gtStmtExpr->gtFlags & GTF_ORDER_SIDEEFF);
+ }
+ }
+
+ if (tree->OperIsUnary() && tree->gtOp.gtOp1)
+ {
+ GenTreePtr lclVarTree;
+ expectedPrevTree = tree->gtOp.gtOp1;
+ }
+ else if (tree->OperIsBinary() && tree->gtOp.gtOp1)
+ {
+ switch (tree->gtOper)
+ {
+ case GT_QMARK:
+ expectedPrevTree = tree->gtOp.gtOp2->AsColon()->ThenNode(); // "then" operand of the GT_COLON (generated second).
+ break;
+
+ case GT_COLON:
+ expectedPrevTree = tree->AsColon()->ElseNode(); // "else" branch result (generated first).
+ break;
+
+ default:
+ if (tree->gtOp.gtOp2)
+ {
+ if (tree->gtFlags & GTF_REVERSE_OPS)
+ {
+ expectedPrevTree = tree->gtOp.gtOp1;
+ }
+ else
+ {
+ expectedPrevTree = tree->gtOp.gtOp2;
+ }
+ }
+ else
+ {
+ expectedPrevTree = tree->gtOp.gtOp1;
+ }
+ break;
+ }
+ }
+
+ noway_assert(expectedPrevTree == nullptr || // No expectations about the prev node
+ tree->gtPrev == expectedPrevTree); // The "normal" case
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * A DEBUG routine to check the correctness of the links between GT_STMT nodes
+ * and ordinary nodes within a statement.
+ *
+ ****************************************************************************/
+
+void Compiler::fgDebugCheckLinks(bool morphTrees)
+{
+ // This used to be only on for stress, and there was a comment stating that
+ // it was "quite an expensive operation" but I did not find that to be true.
+ // Set DO_SANITY_DEBUG_CHECKS to false to revert to that behavior.
+ const bool DO_SANITY_DEBUG_CHECKS = true;
+
+ if (!DO_SANITY_DEBUG_CHECKS &&
+ !compStressCompile(STRESS_CHK_FLOW_UPDATE, 30))
+ {
+ return;
+ }
+
+ fgDebugCheckBlockLinks();
+
+ /* For each basic block check the bbTreeList links */
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+PROCESS_BLOCK_AGAIN:;
+ if (block->IsLIR())
+ {
+ LIR::AsRange(block).CheckLIR(this);
+ }
+ else
+ {
+ for (GenTreeStmt* stmt = block->firstStmt(); stmt; stmt = stmt->gtNextStmt)
+ {
+ /* Verify that bbTreeList is threaded correctly */
+ /* Note that for the GT_STMT list, the gtPrev list is circular. The gtNext list is not: gtNext of the last GT_STMT in a block is nullptr. */
+
+ noway_assert(stmt->gtPrev);
+
+ if (stmt == block->bbTreeList)
+ {
+ noway_assert(stmt->gtPrev->gtNext == nullptr);
+ }
+ else
+ {
+ noway_assert(stmt->gtPrev->gtNext == stmt);
+ }
+
+ if (stmt->gtNext)
+ {
+ noway_assert(stmt->gtNext->gtPrev == stmt);
+ }
+ else
+ {
+ noway_assert(block->lastStmt() == stmt);
+ }
+
+ /* For each statement check that the exception flags are properly set */
+
+ noway_assert(stmt->gtStmtExpr);
+
+ if (verbose && 0)
+ {
+ gtDispTree(stmt->gtStmtExpr);
+ }
+
+ fgDebugCheckFlags(stmt->gtStmtExpr);
+
+ // Not only will this stress fgMorphBlockStmt(), but we also get all the checks
+ // done by fgMorphTree()
+
+ if (morphTrees)
+ {
+ // If 'stmt' is removed from the block, restart
+ if (fgMorphBlockStmt(block, stmt DEBUGARG("test morphing")))
+ {
+ goto PROCESS_BLOCK_AGAIN;
+ }
+ }
+
+ /* For each GT_STMT node check that the nodes are threaded correcly - gtStmtList */
+
+ if (fgStmtListThreaded)
+ {
+ fgDebugCheckNodeLinks(block, stmt);
+ }
+ }
+ }
+ }
+}
+
+// ensure that bbNext and bbPrev are consistent
+void Compiler::fgDebugCheckBlockLinks()
+{
+ assert(fgFirstBB->bbPrev == nullptr);
+
+ for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
+ {
+ if (block->bbNext)
+ {
+ assert(block->bbNext->bbPrev == block);
+ }
+ else
+ {
+ assert(block == fgLastBB);
+ }
+
+ if (block->bbPrev)
+ {
+ assert(block->bbPrev->bbNext == block);
+ }
+ else
+ {
+ assert(block == fgFirstBB);
+ }
+
+ // If this is a switch, check that the tables are consistent.
+ // Note that we don't call GetSwitchDescMap(), because it has the side-effect
+ // of allocating it if it is not present.
+ if (block->bbJumpKind == BBJ_SWITCH && m_switchDescMap != nullptr)
+ {
+ SwitchUniqueSuccSet uniqueSuccSet;
+ if (m_switchDescMap->Lookup(block, &uniqueSuccSet))
+ {
+ // Create a set with all the successors. Don't use BlockSet, so we don't need to worry
+ // about the BlockSet epoch.
+ BitVecTraits bitVecTraits(fgBBNumMax + 1, this);
+ BitVec BITVEC_INIT_NOCOPY(succBlocks, BitVecOps::MakeEmpty(&bitVecTraits));
+ BasicBlock** jumpTable = block->bbJumpSwt->bbsDstTab;
+ unsigned jumpCount = block->bbJumpSwt->bbsCount;
+ for (unsigned i = 0; i < jumpCount; i++)
+ {
+ BitVecOps::AddElemD(&bitVecTraits, succBlocks, jumpTable[i]->bbNum);
+ }
+ // Now we should have a set of unique successors that matches what's in the switchMap.
+ // First, check the number of entries, then make sure all the blocks in uniqueSuccSet
+ // are in the BlockSet.
+ unsigned count = BitVecOps::Count(&bitVecTraits, succBlocks);
+ assert(uniqueSuccSet.numDistinctSuccs == count);
+ for (unsigned i = 0; i < uniqueSuccSet.numDistinctSuccs; i++)
+ {
+ assert(BitVecOps::IsMember(&bitVecTraits, succBlocks, uniqueSuccSet.nonDuplicates[i]->bbNum));
+ }
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+#endif // DEBUG
+/*****************************************************************************/
+
+//------------------------------------------------------------------------
+// fgCheckForInlineDepthAndRecursion: compute depth of the candidate, and
+// check for recursion.
+//
+// Return Value:
+// The depth of the inline candidate. The root method is a depth 0, top-level
+// candidates at depth 1, etc.
+//
+// Notes:
+// We generally disallow recursive inlines by policy. However, they are
+// supported by the underlying machinery.
+//
+// Likewise the depth limit is a policy consideration, and serves mostly
+// as a safeguard to prevent runaway inlining of small methods.
+
+unsigned Compiler::fgCheckInlineDepthAndRecursion(InlineInfo* inlineInfo)
+{
+ BYTE* candidateCode = inlineInfo->inlineCandidateInfo->methInfo.ILCode;
+ InlineContext* inlineContext = inlineInfo->iciStmt->gtStmt.gtInlineContext;
+ InlineResult* inlineResult = inlineInfo->inlineResult;
+
+ // There should be a context for all candidates.
+ assert(inlineContext != nullptr);
+ int depth = 0;
+
+ for (; inlineContext != nullptr; inlineContext = inlineContext->GetParent())
+ {
+
+ depth++;
+
+ if (inlineContext->GetCode() == candidateCode)
+ {
+ // This inline candidate has the same IL code buffer as an already
+ // inlined method does.
+ inlineResult->NoteFatal(InlineObservation::CALLSITE_IS_RECURSIVE);
+ break;
+ }
+
+ if (depth > InlineStrategy::IMPLEMENTATION_MAX_INLINE_DEPTH)
+ {
+ break;
+ }
+ }
+
+ inlineResult->NoteInt(InlineObservation::CALLSITE_DEPTH, depth);
+ return depth;
+}
+
+/*****************************************************************************
+ *
+ * Inlining phase
+ */
+
+
+void Compiler::fgInline()
+{
+ if (!opts.OptEnabled(CLFLG_INLINING)) {
+ return;
+}
+
+#ifdef DEBUG
+ if (verbose) {
+ printf("*************** In fgInline()\n");
+}
+#endif // DEBUG
+
+ BasicBlock* block = fgFirstBB;
+ noway_assert(block != nullptr);
+
+ // Set the root inline context on all statements
+ InlineContext* rootContext = m_inlineStrategy->GetRootContext();
+
+ for (; block != nullptr; block = block->bbNext)
+ {
+ for (GenTreeStmt* stmt = block->firstStmt();
+ stmt;
+ stmt = stmt->gtNextStmt)
+ {
+ stmt->gtInlineContext = rootContext;
+ }
+ }
+
+ // Reset block back to start for inlining
+ block = fgFirstBB;
+
+ do
+ {
+ /* Make the current basic block address available globally */
+
+ compCurBB = block;
+
+ GenTreeStmt* stmt;
+ GenTreePtr expr;
+
+ for (stmt = block->firstStmt();
+ stmt != nullptr;
+ stmt = stmt->gtNextStmt)
+ {
+ expr = stmt->gtStmtExpr;
+
+ // See if we can expand the inline candidate
+ if ((expr->gtOper == GT_CALL) && ((expr->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0))
+ {
+ GenTreeCall* call = expr->AsCall();
+ InlineResult inlineResult(this, call, stmt, "fgInline");
+
+ fgMorphStmt = stmt;
+
+ fgMorphCallInline(call, &inlineResult);
+
+ if (stmt->gtStmtExpr->IsNothingNode())
+ {
+ fgRemoveStmt(block, stmt);
+ continue;
+ }
+ }
+ else
+ {
+#ifdef DEBUG
+ // Look for non-candidates.
+ fgWalkTreePre(&stmt->gtStmtExpr, fgFindNonInlineCandidate, stmt);
+#endif
+ }
+
+ // See if we need to replace the return value place holder.
+ fgWalkTreePre(&stmt->gtStmtExpr,
+ fgUpdateInlineReturnExpressionPlaceHolder,
+ (void *) this);
+
+ // See if stmt is of the form GT_COMMA(call, nop)
+ // If yes, we can get rid of GT_COMMA.
+ if (expr->OperGet() == GT_COMMA &&
+ expr->gtOp.gtOp1->OperGet() == GT_CALL &&
+ expr->gtOp.gtOp2->OperGet() == GT_NOP)
+ {
+ stmt->gtStmtExpr = expr->gtOp.gtOp1;
+ }
+ }
+
+ block = block->bbNext;
+
+ } while (block);
+
+#ifdef DEBUG
+
+ // Check that we should not have any inline candidate or return value place holder left.
+
+ block = fgFirstBB;
+ noway_assert(block);
+
+ do
+ {
+ GenTreeStmt* stmt;
+
+ for (stmt = block->firstStmt();
+ stmt;
+ stmt = stmt->gtNextStmt)
+ {
+ // Call Compiler::fgDebugCheckInlineCandidates on each node
+ fgWalkTreePre(&stmt->gtStmtExpr, fgDebugCheckInlineCandidates);
+ }
+
+ block = block->bbNext;
+
+ } while (block);
+
+ fgVerifyHandlerTab();
+
+ if (verbose)
+ {
+ printf("*************** After fgInline()\n");
+ fgDispBasicBlocks(true);
+ fgDispHandlerTab();
+ }
+
+ if (verbose || fgPrintInlinedMethods)
+ {
+ printf("**************** Inline Tree\n");
+ m_inlineStrategy->Dump();
+ }
+
+#endif // DEBUG
+}
+
+#ifdef DEBUG
+
+//------------------------------------------------------------------------
+// fgFindNonInlineCandidate: tree walk helper to ensure that a tree node
+// that is not an inline candidate is noted as a failed inline.
+//
+// Arguments:
+// pTree - pointer to pointer tree node being walked
+// data - contextual data for the walk
+//
+// Return Value:
+// walk result
+//
+// Note:
+// Invokes fgNoteNonInlineCandidate on the nodes it finds.
+
+Compiler::fgWalkResult Compiler::fgFindNonInlineCandidate(GenTreePtr* pTree,
+ fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+ if (tree->gtOper == GT_CALL)
+ {
+ Compiler* compiler = data->compiler;
+ GenTreePtr stmt = (GenTreePtr) data->pCallbackData;
+ GenTreeCall* call = tree->AsCall();
+
+ compiler->fgNoteNonInlineCandidate(stmt, call);
+ }
+ return WALK_CONTINUE;
+}
+
+//------------------------------------------------------------------------
+// fgNoteNonInlineCandidate: account for inlining failures in calls
+// not marked as inline candidates.
+//
+// Arguments:
+// tree - statement containing the call
+// call - the call itself
+//
+// Notes:
+// Used in debug only to try and place descriptions of inline failures
+// into the proper context in the inline tree.
+
+void Compiler::fgNoteNonInlineCandidate(GenTreePtr tree,
+ GenTreeCall* call)
+{
+ InlineResult inlineResult(this, call, nullptr, "fgNotInlineCandidate");
+ InlineObservation currentObservation = InlineObservation::CALLSITE_NOT_CANDIDATE;
+
+ // Try and recover the reason left behind when the jit decided
+ // this call was not a candidate.
+ InlineObservation priorObservation = call->gtInlineObservation;
+
+ if (InlIsValidObservation(priorObservation))
+ {
+ currentObservation = priorObservation;
+ }
+
+ // Would like to just call noteFatal here, since this
+ // observation blocked candidacy, but policy comes into play
+ // here too. Also note there's no need to re-report these
+ // failures, since we reported them during the initial
+ // candidate scan.
+ InlineImpact impact = InlGetImpact(currentObservation);
+
+ if (impact == InlineImpact::FATAL)
+ {
+ inlineResult.NoteFatal(currentObservation);
+ }
+ else
+ {
+ inlineResult.Note(currentObservation);
+ }
+
+ inlineResult.SetReported();
+
+ if (call->gtCallType == CT_USER_FUNC)
+ {
+ // Create InlineContext for the failure
+ m_inlineStrategy->NewFailure(tree, &inlineResult);
+ }
+}
+
+#endif
+
+#if FEATURE_MULTIREG_RET
+
+/*********************************************************************************
+ *
+ * tree - The node which needs to be converted to a struct pointer.
+ *
+ * Return the pointer by either __replacing__ the tree node with a suitable pointer
+ * type or __without replacing__ and just returning a subtree or by __modifying__
+ * a subtree.
+ */
+GenTreePtr Compiler::fgGetStructAsStructPtr(GenTreePtr tree)
+{
+ noway_assert((tree->gtOper == GT_LCL_VAR) ||
+ (tree->gtOper == GT_FIELD) ||
+ (tree->gtOper == GT_IND) ||
+ (tree->gtOper == GT_BLK) ||
+ (tree->gtOper == GT_OBJ) ||
+ tree->OperIsSIMD() ||
+ // tree->gtOper == GT_CALL || cannot get address of call.
+ // tree->gtOper == GT_MKREFANY || inlining should've been aborted due to mkrefany opcode.
+ // tree->gtOper == GT_RET_EXPR || cannot happen after fgUpdateInlineReturnExpressionPlaceHolder
+ (tree->gtOper == GT_COMMA));
+
+ switch (tree->OperGet())
+ {
+ case GT_BLK:
+ case GT_OBJ:
+ case GT_IND:
+ return tree->gtOp.gtOp1;
+
+ case GT_COMMA:
+ tree->gtOp.gtOp2 = fgGetStructAsStructPtr(tree->gtOp.gtOp2);
+ tree->gtType = TYP_BYREF;
+ return tree;
+
+ default:
+ return gtNewOperNode(GT_ADDR, TYP_BYREF, tree);
+ }
+}
+
+/***************************************************************************************************
+ * child - The inlinee of the retExpr node.
+ * retClsHnd - The struct class handle of the type of the inlinee.
+ *
+ * Assign the inlinee to a tmp, if it is a call, just assign it to a lclVar, else we can
+ * use a copyblock to do the assignment.
+ */
+GenTreePtr Compiler::fgAssignStructInlineeToVar(GenTreePtr child, CORINFO_CLASS_HANDLE retClsHnd)
+{
+ assert(child->gtOper != GT_RET_EXPR && child->gtOper != GT_MKREFANY);
+
+ unsigned tmpNum = lvaGrabTemp(false DEBUGARG("RetBuf for struct inline return candidates."));
+ lvaSetStruct(tmpNum, retClsHnd, false);
+ var_types structType = lvaTable[tmpNum].lvType;
+
+ GenTreePtr dst = gtNewLclvNode(tmpNum, structType);
+
+ // If we have a call, we'd like it to be: V00 = call(), but first check if
+ // we have a ", , , call()" -- this is very defensive as we may never get
+ // an inlinee that is made of commas. If the inlinee is not a call, then
+ // we use a copy block to do the assignment.
+ GenTreePtr src = child;
+ GenTreePtr lastComma = NULL;
+ while (src->gtOper == GT_COMMA)
+ {
+ lastComma = src;
+ src = src->gtOp.gtOp2;
+ }
+
+ GenTreePtr newInlinee = NULL;
+ if (src->gtOper == GT_CALL)
+ {
+ // If inlinee was just a call, new inlinee is v05 = call()
+ newInlinee = gtNewAssignNode(dst, src);
+
+ // When returning a multi-register value in a local var, make sure the variable is
+ // marked as lvIsMultiRegRet, so it does not get promoted.
+ if (src->AsCall()->HasMultiRegRetVal())
+ {
+ lvaTable[tmpNum].lvIsMultiRegRet = true;
+ }
+
+ // If inlinee was comma, but a deeper call, new inlinee is (, , , v05 = call())
+ if (child->gtOper == GT_COMMA)
+ {
+ lastComma->gtOp.gtOp2 = newInlinee;
+ newInlinee = child;
+ }
+ }
+ else
+ {
+ // Inlinee is not a call, so just create a copy block to the tmp.
+ src = child;
+ GenTreePtr dstAddr = fgGetStructAsStructPtr(dst);
+ GenTreePtr srcAddr = fgGetStructAsStructPtr(src);
+ newInlinee = gtNewCpObjNode(dstAddr, srcAddr, retClsHnd, false);
+ }
+
+ GenTreePtr production = gtNewLclvNode(tmpNum, structType);
+ return gtNewOperNode(GT_COMMA, structType, newInlinee, production);
+}
+
+/***************************************************************************************************
+ * tree - The tree pointer that has one of its child nodes as retExpr.
+ * child - The inlinee child.
+ * retClsHnd - The struct class handle of the type of the inlinee.
+ *
+ * V04 = call() assignments are okay as we codegen it. Everything else needs to be a copy block or
+ * would need a temp. For example, a cast(ldobj) will then be, cast(v05 = ldobj, v05); But it is
+ * a very rare (or impossible) scenario that we'd have a retExpr transform into a ldobj other than
+ * a lclVar/call. So it is not worthwhile to do pattern matching optimizations like addr(ldobj(op1))
+ * can just be op1.
+ */
+void Compiler::fgAttachStructInlineeToAsg(GenTreePtr tree, GenTreePtr child, CORINFO_CLASS_HANDLE retClsHnd)
+{
+ // We are okay to have:
+ // 1. V02 = call();
+ // 2. copyBlk(dstAddr, srcAddr);
+ assert(tree->gtOper == GT_ASG);
+
+ // We have an assignment, we codegen only V05 = call().
+ if (child->gtOper == GT_CALL && tree->gtOp.gtOp1->gtOper == GT_LCL_VAR)
+ {
+ return;
+ }
+
+ GenTreePtr dstAddr = fgGetStructAsStructPtr(tree->gtOp.gtOp1);
+ GenTreePtr srcAddr = fgGetStructAsStructPtr((child->gtOper == GT_CALL)
+ ? fgAssignStructInlineeToVar(child, retClsHnd) // Assign to a variable if it is a call.
+ : child); // Just get the address, if not a call.
+
+ tree->CopyFrom(gtNewCpObjNode(dstAddr, srcAddr, retClsHnd, false), this);
+}
+
+#endif // FEATURE_MULTIREG_RET
+
+/*****************************************************************************
+ * Callback to replace the inline return expression place holder (GT_RET_EXPR)
+ */
+
+/* static */
+Compiler::fgWalkResult Compiler::fgUpdateInlineReturnExpressionPlaceHolder(GenTreePtr* pTree,
+ fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+ Compiler* comp = data->compiler;
+ CORINFO_CLASS_HANDLE retClsHnd = NO_CLASS_HANDLE;
+
+ if (tree->gtOper == GT_RET_EXPR)
+ {
+ // We are going to copy the tree from the inlinee,
+ // so record the handle now.
+ //
+ if (varTypeIsStruct(tree))
+ {
+ retClsHnd = tree->gtRetExpr.gtRetClsHnd;
+ }
+
+ do
+ {
+ // Obtained the expanded inline candidate
+ GenTreePtr inlineCandidate = tree->gtRetExpr.gtInlineCandidate;
+
+#ifdef DEBUG
+ if (comp->verbose)
+ {
+ printf("\nReplacing the return expression placeholder ");
+ printTreeID(tree);
+ printf(" with ");
+ printTreeID(inlineCandidate);
+ printf("\n");
+ // Dump out the old return expression placeholder it will be overwritten by the CopyFrom below
+ comp->gtDispTree(tree);
+ }
+#endif // DEBUG
+
+ tree->CopyFrom(inlineCandidate, comp);
+
+#ifdef DEBUG
+ if (comp->verbose)
+ {
+ printf("\nInserting the inline return expression\n");
+ comp->gtDispTree(tree);
+ printf("\n");
+ }
+#endif // DEBUG
+ }
+ while (tree->gtOper == GT_RET_EXPR);
+ }
+
+#if FEATURE_MULTIREG_RET
+
+ // Did we record a struct return class handle above?
+ //
+ if (retClsHnd != NO_CLASS_HANDLE)
+ {
+ // Is this a type that is returned in multiple registers?
+ // if so we need to force into into a form we accept.
+ // i.e. LclVar = call()
+ //
+ if (comp->IsMultiRegReturnedType(retClsHnd))
+ {
+ GenTreePtr parent = data->parent;
+ // See assert below, we only look one level above for an asg parent.
+ if (parent->gtOper == GT_ASG)
+ {
+ // Either lhs is a call V05 = call(); or lhs is addr, and asg becomes a copyBlk.
+ comp->fgAttachStructInlineeToAsg(parent, tree, retClsHnd);
+ }
+ else
+ {
+ // Just assign the inlinee to a variable to keep it simple.
+ tree->CopyFrom(comp->fgAssignStructInlineeToVar(tree, retClsHnd), comp);
+ }
+ }
+ }
+
+#if defined(DEBUG)
+
+ // Make sure we don't have a tree like so: V05 = (, , , retExpr);
+ // Since we only look one level above for the parent for '=' and
+ // do not check if there is a series of COMMAs. See above.
+ // Importer and FlowGraph will not generate such a tree, so just
+ // leaving an assert in here. This can be fixed by looking ahead
+ // when we visit GT_ASG similar to fgAttachStructInlineeToAsg.
+ //
+ if ((tree->gtOper == GT_ASG) && (tree->gtOp.gtOp2->gtOper == GT_COMMA))
+ {
+ GenTreePtr comma;
+ for (comma = tree->gtOp.gtOp2;
+ comma->gtOper == GT_COMMA;
+ comma = comma->gtOp.gtOp2)
+ {
+ // empty
+ }
+
+ noway_assert(!varTypeIsStruct(comma) ||
+ comma->gtOper != GT_RET_EXPR ||
+ !comp->IsMultiRegReturnedType(comma->gtRetExpr.gtRetClsHnd));
+ }
+
+#endif // defined(DEBUG)
+#endif // FEATURE_MULTIREG_RET
+
+ return WALK_CONTINUE;
+}
+
+#ifdef DEBUG
+
+/*****************************************************************************
+ * Callback to make sure there is no more GT_RET_EXPR and GTF_CALL_INLINE_CANDIDATE nodes.
+ */
+
+/* static */
+Compiler::fgWalkResult Compiler::fgDebugCheckInlineCandidates(GenTreePtr* pTree,
+ fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+ if (tree->gtOper == GT_CALL)
+ {
+ assert((tree->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0);
+ }
+ else
+ {
+ assert(tree->gtOper != GT_RET_EXPR);
+ }
+
+ return WALK_CONTINUE;
+}
+
+#endif // DEBUG
+
+
+void Compiler::fgInvokeInlineeCompiler(GenTreeCall* call,
+ InlineResult* inlineResult)
+{
+ noway_assert(call->gtOper == GT_CALL);
+ noway_assert((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0);
+ noway_assert(opts.OptEnabled(CLFLG_INLINING));
+
+ // This is the InlineInfo struct representing a method to be inlined.
+ InlineInfo inlineInfo = {nullptr};
+
+ CORINFO_METHOD_HANDLE fncHandle = call->gtCallMethHnd;
+
+ inlineInfo.fncHandle = fncHandle;
+ inlineInfo.iciCall = call;
+ inlineInfo.iciStmt = fgMorphStmt;
+ inlineInfo.iciBlock = compCurBB;
+ inlineInfo.thisDereferencedFirst = false;
+ inlineInfo.retExpr = nullptr;
+ inlineInfo.inlineResult = inlineResult;
+#ifdef FEATURE_SIMD
+ inlineInfo.hasSIMDTypeArgLocalOrReturn = false;
+#endif // FEATURE_SIMD
+
+ InlineCandidateInfo* inlineCandidateInfo = call->gtInlineCandidateInfo;
+ noway_assert(inlineCandidateInfo);
+ // Store the link to inlineCandidateInfo into inlineInfo
+ inlineInfo.inlineCandidateInfo = inlineCandidateInfo;
+
+ unsigned inlineDepth = fgCheckInlineDepthAndRecursion(&inlineInfo);
+
+ if (inlineResult->IsFailure())
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Recursive or deep inline recursion detected. Will not expand this INLINECANDIDATE \n");
+ }
+#endif // DEBUG
+ return;
+ }
+
+ // Set the trap to catch all errors (including recoverable ones from the EE)
+ struct Param
+ {
+ Compiler* pThis;
+ GenTree* call;
+ CORINFO_METHOD_HANDLE fncHandle;
+ InlineCandidateInfo* inlineCandidateInfo;
+ InlineInfo* inlineInfo;
+ } param = {nullptr};
+
+ param.pThis = this;
+ param.call = call;
+ param.fncHandle = fncHandle;
+ param.inlineCandidateInfo = inlineCandidateInfo;
+ param.inlineInfo = &inlineInfo;
+ bool success = eeRunWithErrorTrap<Param>([](Param* pParam)
+ {
+ // Init the local var info of the inlinee
+ pParam->pThis->impInlineInitVars(pParam->inlineInfo);
+
+ if (pParam->inlineInfo->inlineResult->IsCandidate())
+ {
+ /* Clear the temp table */
+ memset(pParam->inlineInfo->lclTmpNum, -1, sizeof(pParam->inlineInfo->lclTmpNum));
+
+ //
+ // Prepare the call to jitNativeCode
+ //
+
+ pParam->inlineInfo->InlinerCompiler = pParam->pThis;
+ if (pParam->pThis->impInlineInfo == nullptr)
+ {
+ pParam->inlineInfo->InlineRoot = pParam->pThis;
+ }
+ else
+ {
+ pParam->inlineInfo->InlineRoot = pParam->pThis->impInlineInfo->InlineRoot;
+ }
+ pParam->inlineInfo->argCnt = pParam->inlineCandidateInfo->methInfo.args.totalILArgs();
+ pParam->inlineInfo->tokenLookupContextHandle = pParam->inlineCandidateInfo->exactContextHnd;
+
+ JITLOG_THIS(pParam->pThis,
+ (LL_INFO100000,
+ "INLINER: inlineInfo.tokenLookupContextHandle for %s set to 0x%p:\n",
+ pParam->pThis->eeGetMethodFullName(pParam->fncHandle),
+ pParam->pThis->dspPtr(pParam->inlineInfo->tokenLookupContextHandle)));
+
+ CORJIT_FLAGS compileFlagsForInlinee;
+ memcpy(&compileFlagsForInlinee, pParam->pThis->opts.jitFlags, sizeof(compileFlagsForInlinee));
+ compileFlagsForInlinee.corJitFlags &= ~CORJIT_FLG_LOST_WHEN_INLINING;
+ compileFlagsForInlinee.corJitFlags |= CORJIT_FLG_SKIP_VERIFICATION;
+
+#ifdef DEBUG
+ if (pParam->pThis->verbose)
+ {
+ printf("\nInvoking compiler for the inlinee method %s :\n",
+ pParam->pThis->eeGetMethodFullName(pParam->fncHandle));
+ }
+#endif // DEBUG
+
+ int result = jitNativeCode(pParam->fncHandle,
+ pParam->inlineCandidateInfo->methInfo.scope,
+ pParam->pThis->info.compCompHnd,
+ &pParam->inlineCandidateInfo->methInfo,
+ (void**)pParam->inlineInfo,
+ nullptr,
+ &compileFlagsForInlinee,
+ pParam->inlineInfo);
+
+ if (result != CORJIT_OK)
+ {
+ // If we haven't yet determined why this inline fails, use
+ // a catch-all something bad happened observation.
+ InlineResult* innerInlineResult = pParam->inlineInfo->inlineResult;
+
+ if (!innerInlineResult->IsFailure())
+ {
+ innerInlineResult->NoteFatal(InlineObservation::CALLSITE_COMPILATION_FAILURE);
+ }
+ }
+ }
+ }, &param);
+ if (!success)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nInlining failed due to an exception during invoking the compiler for the inlinee method %s.\n",
+ eeGetMethodFullName(fncHandle));
+ }
+#endif // DEBUG
+
+ // If we haven't yet determined why this inline fails, use
+ // a catch-all something bad happened observation.
+ if (!inlineResult->IsFailure())
+ {
+ inlineResult->NoteFatal(InlineObservation::CALLSITE_COMPILATION_ERROR);
+ }
+ }
+
+ if (inlineResult->IsFailure())
+ {
+ return;
+ }
+
+#ifdef DEBUG
+ if (0 && verbose)
+ {
+ printf("\nDone invoking compiler for the inlinee method %s\n",
+ eeGetMethodFullName(fncHandle));
+ }
+#endif // DEBUG
+
+ // If there is non-NULL return, but we haven't set the pInlineInfo->retExpr,
+ // That means we haven't imported any BB that contains CEE_RET opcode.
+ // (This could happen for example for a BBJ_THROW block fall through a BBJ_RETURN block which
+ // causes the BBJ_RETURN block not to be imported at all.)
+ // Fail the inlining attempt
+ if (inlineCandidateInfo->fncRetType != TYP_VOID && inlineInfo.retExpr == nullptr)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nInlining failed because pInlineInfo->retExpr is not set in the inlinee method %s.\n",
+ eeGetMethodFullName(fncHandle));
+ }
+#endif // DEBUG
+ inlineResult->NoteFatal(InlineObservation::CALLEE_LACKS_RETURN);
+ return;
+ }
+
+ if (inlineCandidateInfo->initClassResult & CORINFO_INITCLASS_SPECULATIVE)
+ {
+ // we defer the call to initClass() until inlining is completed in case it fails. If inlining succeeds,
+ // we will call initClass().
+ if (!(info.compCompHnd->initClass(nullptr /* field */, fncHandle /* method */,
+ inlineCandidateInfo->exactContextHnd /* context */) & CORINFO_INITCLASS_INITIALIZED))
+ {
+ inlineResult->NoteFatal(InlineObservation::CALLEE_CLASS_INIT_FAILURE);
+ return;
+ }
+ }
+
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ // The inlining attempt cannot be failed starting from this point.
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ // We've successfully obtain the list of inlinee's basic blocks.
+ // Let's insert it to inliner's basic block list.
+ fgInsertInlineeBlocks(&inlineInfo);
+
+#ifdef DEBUG
+
+ if (verbose || fgPrintInlinedMethods)
+ {
+ printf("Successfully inlined %s (%d IL bytes) (depth %d) [%s]\n",
+ eeGetMethodFullName(fncHandle),
+ inlineCandidateInfo->methInfo.ILCodeSize,
+ inlineDepth,
+ inlineResult->ReasonString());
+ }
+
+ if (verbose)
+ {
+ printf("--------------------------------------------------------------------------------------------\n");
+ }
+#endif // DEBUG
+
+#if defined(DEBUG)
+ impInlinedCodeSize += inlineCandidateInfo->methInfo.ILCodeSize;
+#endif
+
+ // We inlined...
+ inlineResult->NoteSuccess();
+}
+
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// The inlining attempt cannot be failed starting from this point.
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
+{
+ GenTreePtr iciCall = pInlineInfo->iciCall;
+ GenTreePtr iciStmt = pInlineInfo->iciStmt;
+ BasicBlock* iciBlock = pInlineInfo->iciBlock;
+ BasicBlock* block;
+
+ // We can write better assert here. For example, we can check that
+ // iciBlock contains iciStmt, which in turn contains iciCall.
+ noway_assert(iciBlock->bbTreeList != nullptr);
+ noway_assert(iciStmt->gtStmt.gtStmtExpr != nullptr);
+ noway_assert(iciCall->gtOper == GT_CALL);
+
+#ifdef DEBUG
+
+ GenTreePtr currentDumpStmt = nullptr;
+
+ if (verbose)
+ {
+ printf("\n\n----------- Statements (and blocks) added due to the inlining of call ");
+ printTreeID(iciCall);
+ printf(" -----------\n");
+ // gtDispTree(iciStmt);
+ }
+
+#endif // DEBUG
+
+ //
+ // Create a new inline context and mark the inlined statements with it
+ //
+ InlineContext* calleeContext = m_inlineStrategy->NewSuccess(pInlineInfo);
+
+ for (block = InlineeCompiler->fgFirstBB;
+ block != nullptr;
+ block = block->bbNext)
+ {
+ for (GenTreeStmt* stmt = block->firstStmt();
+ stmt;
+ stmt = stmt->gtNextStmt)
+ {
+ stmt->gtInlineContext = calleeContext;
+ }
+ }
+
+ //
+ // Prepend statements.
+ //
+ GenTreePtr stmtAfter;
+ stmtAfter = fgInlinePrependStatements(pInlineInfo);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ currentDumpStmt = stmtAfter;
+ printf("\nInlinee method body:");
+ }
+#endif // DEBUG
+
+ if (InlineeCompiler->fgBBcount == 1)
+ {
+ // When fgBBCount is 1 we will always have a non-NULL fgFirstBB
+ //
+ PREFAST_ASSUME(InlineeCompiler->fgFirstBB != nullptr);
+
+ // DDB 91389: Don't throw away the (only) inlinee block
+ // when its return type is not BBJ_RETURN.
+ // In other words, we need its BBJ_ to perform the right thing.
+ if (InlineeCompiler->fgFirstBB->bbJumpKind == BBJ_RETURN)
+ {
+ // Inlinee contains just one BB. So just insert its statement list to topBlock.
+ if (InlineeCompiler->fgFirstBB->bbTreeList)
+ {
+ stmtAfter = fgInsertStmtListAfter(iciBlock,
+ stmtAfter,
+ InlineeCompiler->fgFirstBB->bbTreeList);
+
+ // Copy inlinee bbFlags to caller bbFlags.
+ const unsigned int inlineeBlockFlags = InlineeCompiler->fgFirstBB->bbFlags;
+ noway_assert((inlineeBlockFlags & BBF_HAS_JMP) == 0);
+ noway_assert((inlineeBlockFlags & BBF_KEEP_BBJ_ALWAYS) == 0);
+ iciBlock->bbFlags |= inlineeBlockFlags;
+ }
+#ifdef DEBUG
+ if (verbose)
+ {
+ noway_assert(currentDumpStmt);
+
+ if (currentDumpStmt != stmtAfter)
+ {
+ do
+ {
+ currentDumpStmt = currentDumpStmt->gtNext;
+
+ printf("\n");
+
+ noway_assert(currentDumpStmt->gtOper == GT_STMT);
+
+ gtDispTree(currentDumpStmt);
+ printf("\n");
+
+ } while (currentDumpStmt != stmtAfter);
+ }
+ }
+#endif // DEBUG
+ goto _Done;
+ }
+ }
+
+ //
+ // ======= Inserting inlinee's basic blocks ===============
+ //
+
+ BasicBlock* topBlock;
+ BasicBlock* bottomBlock;
+
+ topBlock = iciBlock;
+
+ bottomBlock = fgNewBBafter(topBlock->bbJumpKind, topBlock, true);
+ bottomBlock->bbRefs = 1;
+ bottomBlock->bbJumpDest = topBlock->bbJumpDest;
+ bottomBlock->inheritWeight(topBlock);
+
+ topBlock->bbJumpKind = BBJ_NONE;
+
+ // Update block flags
+ unsigned originalFlags;
+ originalFlags = topBlock->bbFlags;
+ noway_assert((originalFlags & BBF_SPLIT_NONEXIST) == 0);
+ topBlock->bbFlags &= ~(BBF_SPLIT_LOST);
+ bottomBlock->bbFlags |= originalFlags & BBF_SPLIT_GAINED;
+
+ //
+ // Split statements between topBlock and bottomBlock
+ //
+ GenTreePtr topBlock_Begin;
+ GenTreePtr topBlock_End;
+ GenTreePtr bottomBlock_Begin;
+ GenTreePtr bottomBlock_End;
+
+ topBlock_Begin = nullptr;
+ topBlock_End = nullptr;
+ bottomBlock_Begin = nullptr;
+ bottomBlock_End = nullptr;
+
+ //
+ // First figure out bottomBlock_Begin
+ //
+
+ bottomBlock_Begin = stmtAfter->gtNext;
+
+ if (topBlock->bbTreeList == nullptr)
+ {
+ // topBlock is empty before the split.
+ // In this case, both topBlock and bottomBlock should be empty
+ noway_assert(bottomBlock_Begin == nullptr);
+ topBlock->bbTreeList = nullptr;
+ bottomBlock->bbTreeList = nullptr;
+ }
+ else if (topBlock->bbTreeList == bottomBlock_Begin)
+ {
+ noway_assert(bottomBlock_Begin);
+
+ // topBlock contains at least one statement before the split.
+ // And the split is before the first statement.
+ // In this case, topBlock should be empty, and everything else should be moved to the bottonBlock.
+ bottomBlock->bbTreeList = topBlock->bbTreeList;
+ topBlock->bbTreeList = nullptr;
+ }
+ else if (bottomBlock_Begin == nullptr)
+ {
+ noway_assert(topBlock->bbTreeList);
+
+ // topBlock contains at least one statement before the split.
+ // And the split is at the end of the topBlock.
+ // In this case, everything should be kept in the topBlock, and the bottomBlock should be empty
+
+ bottomBlock->bbTreeList = nullptr;
+ }
+ else
+ {
+ noway_assert(topBlock->bbTreeList);
+ noway_assert(bottomBlock_Begin);
+
+ // This is the normal case where both blocks should contain at least one statement.
+ topBlock_Begin = topBlock->bbTreeList;
+ noway_assert(topBlock_Begin);
+ topBlock_End = bottomBlock_Begin->gtPrev;
+ noway_assert(topBlock_End);
+ bottomBlock_End = topBlock->lastStmt();
+ noway_assert(bottomBlock_End);
+
+ // Break the linkage between 2 blocks.
+ topBlock_End->gtNext = nullptr;
+
+ // Fix up all the pointers.
+ topBlock->bbTreeList = topBlock_Begin;
+ topBlock->bbTreeList->gtPrev = topBlock_End;
+
+ bottomBlock->bbTreeList = bottomBlock_Begin;
+ bottomBlock->bbTreeList->gtPrev = bottomBlock_End;
+ }
+
+ //
+ // Set the try and handler index and fix the jump types of inlinee's blocks.
+ //
+
+ bool inheritWeight;
+ inheritWeight = true; // The firstBB does inherit the weight from the iciBlock
+
+ for (block = InlineeCompiler->fgFirstBB;
+ block != nullptr;
+ block = block->bbNext)
+ {
+ noway_assert(!block->hasTryIndex());
+ noway_assert(!block->hasHndIndex());
+ block->copyEHRegion(iciBlock);
+ block->bbFlags |= iciBlock->bbFlags & BBF_BACKWARD_JUMP;
+
+ if (iciStmt->gtStmt.gtStmtILoffsx != BAD_IL_OFFSET)
+ {
+ block->bbCodeOffs = jitGetILoffs(iciStmt->gtStmt.gtStmtILoffsx);
+ block->bbCodeOffsEnd = block->bbCodeOffs + 1; // TODO: is code size of 1 some magic number for inlining?
+ }
+ else
+ {
+ block->bbCodeOffs = 0; // TODO: why not BAD_IL_OFFSET?
+ block->bbCodeOffsEnd = 0;
+ block->bbFlags |= BBF_INTERNAL;
+ }
+
+ if (block->bbJumpKind == BBJ_RETURN)
+ {
+ inheritWeight = true; // A return block does inherit the weight from the iciBlock
+ noway_assert((block->bbFlags & BBF_HAS_JMP) == 0);
+ if (block->bbNext)
+ {
+ block->bbJumpKind = BBJ_ALWAYS;
+ block->bbJumpDest = bottomBlock;
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nConvert bbJumpKind of BB%02u to BBJ_ALWAYS to bottomBlock BB%02u\n",
+ block->bbNum, bottomBlock->bbNum);
+ }
+#endif // DEBUG
+ }
+ else
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nConvert bbJumpKind of BB%02u to BBJ_NONE\n", block->bbNum);
+ }
+#endif // DEBUG
+ block->bbJumpKind = BBJ_NONE;
+ }
+ }
+ if (inheritWeight)
+ {
+ block->inheritWeight(iciBlock);
+ inheritWeight = false;
+ }
+ else
+ {
+ block->modifyBBWeight(iciBlock->bbWeight / 2);
+ }
+ }
+
+ // Insert inlinee's blocks into inliner's block list.
+ topBlock->setNext(InlineeCompiler->fgFirstBB);
+ InlineeCompiler->fgLastBB->setNext(bottomBlock);
+
+ //
+ // Add inlinee's block count to inliner's.
+ //
+ fgBBcount += InlineeCompiler->fgBBcount;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ fgDispBasicBlocks(InlineeCompiler->fgFirstBB, InlineeCompiler->fgLastBB, true);
+ }
+#endif // DEBUG
+
+_Done:
+
+ //
+ // At this point, we have successully inserted inlinee's code.
+ //
+
+ //
+ // Copy out some flags
+ //
+ compLongUsed |= InlineeCompiler->compLongUsed;
+ compFloatingPointUsed |= InlineeCompiler->compFloatingPointUsed;
+ compLocallocUsed |= InlineeCompiler->compLocallocUsed;
+ compQmarkUsed |= InlineeCompiler->compQmarkUsed;
+ compUnsafeCastUsed |= InlineeCompiler->compUnsafeCastUsed;
+ compNeedsGSSecurityCookie |= InlineeCompiler->compNeedsGSSecurityCookie;
+ compGSReorderStackLayout |= InlineeCompiler->compGSReorderStackLayout;
+
+ // Update optMethodFlags
+
+#ifdef DEBUG
+ unsigned optMethodFlagsBefore = optMethodFlags;
+#endif
+
+ optMethodFlags |= InlineeCompiler->optMethodFlags;
+
+#ifdef DEBUG
+ if (optMethodFlags != optMethodFlagsBefore)
+ {
+ JITDUMP("INLINER: Updating optMethodFlags -- root:%0x callee:%0x new:%0x\n",
+ optMethodFlagsBefore, InlineeCompiler->optMethodFlags, optMethodFlags);
+ }
+#endif
+
+ // If there is non-NULL return, replace the GT_CALL with its return value expression,
+ // so later it will be picked up by the GT_RET_EXPR node.
+ if ((pInlineInfo->inlineCandidateInfo->fncRetType != TYP_VOID) || (iciCall->gtCall.gtReturnType == TYP_STRUCT))
+ {
+ noway_assert(pInlineInfo->retExpr);
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nReturn expression for call at ");
+ printTreeID(iciCall);
+ printf(" is\n");
+ gtDispTree(pInlineInfo->retExpr);
+ }
+#endif // DEBUG
+ // Replace the call with the return expression
+ iciCall->CopyFrom(pInlineInfo->retExpr, this);
+ }
+
+ //
+ // Detach the GT_CALL node from the original statement by hanging a "nothing" node under it,
+ // so that fgMorphStmts can remove the statement once we return from here.
+ //
+ iciStmt->gtStmt.gtStmtExpr = gtNewNothingNode();
+}
+
+// Prepend the statements that are needed before the inlined call.
+// Return the last statement that is prepended.
+
+GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo)
+{
+ BasicBlock* block = inlineInfo->iciBlock;
+
+ GenTreePtr callStmt = inlineInfo->iciStmt;
+ noway_assert(callStmt->gtOper == GT_STMT);
+ IL_OFFSETX callILOffset = callStmt->gtStmt.gtStmtILoffsx;
+
+ GenTreePtr afterStmt = callStmt; // afterStmt is the place where the new statements should be inserted after.
+ GenTreePtr newStmt;
+
+ GenTreePtr call = inlineInfo->iciCall;
+ noway_assert(call->gtOper == GT_CALL);
+
+#ifdef DEBUG
+ if (0 && verbose)
+ {
+ printf("\nfgInlinePrependStatements for iciCall= ");
+ printTreeID(call);
+ printf(":\n");
+ }
+#endif
+
+ // Prepend statements for any initialization / side effects
+
+ InlArgInfo* inlArgInfo = inlineInfo->inlArgInfo;
+ InlLclVarInfo* lclVarInfo = inlineInfo->lclVarInfo;
+
+ GenTreePtr tree;
+
+ // Create the null check statement (but not appending it to the statement list yet) for the 'this' pointer if necessary.
+ // The NULL check should be done after "argument setup statements".
+ // The only reason we move it here is for calling "impInlineFetchArg(0,..." to reserve a temp
+ // for the "this" pointer.
+ // Note: Here we no longer do the optimization that was done by thisDereferencedFirst in the old inliner.
+ // However the assetionProp logic will remove any unecessary null checks that we may have added
+ //
+ GenTreePtr nullcheck = nullptr;
+
+ if (call->gtFlags & GTF_CALL_NULLCHECK && !inlineInfo->thisDereferencedFirst)
+ {
+ // Call impInlineFetchArg to "reserve" a temp for the "this" pointer.
+ nullcheck = gtNewOperNode(GT_IND, TYP_INT,
+ impInlineFetchArg(0, inlArgInfo, lclVarInfo));
+ nullcheck->gtFlags |= GTF_EXCEPT;
+
+ // The NULL-check statement will be inserted to the statement list after those statements
+ // that assign arguments to temps and before the actual body of the inlinee method.
+ }
+
+ /* Treat arguments that had to be assigned to temps */
+ if (inlineInfo->argCnt)
+ {
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nArguments setup:\n");
+ }
+#endif // DEBUG
+
+ for (unsigned argNum = 0; argNum < inlineInfo->argCnt; argNum++)
+ {
+ if (inlArgInfo[argNum].argHasTmp)
+ {
+ noway_assert(inlArgInfo[argNum].argIsUsed);
+
+ /* argBashTmpNode is non-NULL iff the argument's value was
+ referenced exactly once by the original IL. This offers an
+ oppportunity to avoid an intermediate temp and just insert
+ the original argument tree.
+
+ However, if the temp node has been cloned somewhere while
+ importing (e.g. when handling isinst or dup), or if the IL
+ took the address of the argument, then argBashTmpNode will
+ be set (because the value was only explicitly retrieved
+ once) but the optimization cannot be applied.
+ */
+
+ GenTreePtr argSingleUseNode = inlArgInfo[argNum].argBashTmpNode;
+
+ if (argSingleUseNode &&
+ !(argSingleUseNode->gtFlags & GTF_VAR_CLONED) &&
+ !inlArgInfo[argNum].argHasLdargaOp &&
+ !inlArgInfo[argNum].argHasStargOp)
+ {
+ // Change the temp in-place to the actual argument.
+ // We currently do not support this for struct arguments, so it must not be a GT_OBJ.
+ GenTree* argNode = inlArgInfo[argNum].argNode;
+ assert(argNode->gtOper != GT_OBJ);
+ argSingleUseNode->CopyFrom(argNode, this);
+ continue;
+ }
+ else
+ {
+ /* Create the temp assignment for this argument */
+
+ CORINFO_CLASS_HANDLE structHnd = DUMMY_INIT(0);
+
+ if (varTypeIsStruct(lclVarInfo[argNum].lclTypeInfo))
+ {
+ structHnd = gtGetStructHandleIfPresent(inlArgInfo[argNum].argNode);
+ noway_assert(structHnd != NO_CLASS_HANDLE);
+ }
+
+ // Unsafe value cls check is not needed for argTmpNum here since in-linee compiler instance would have
+ // iterated over these and marked them accordingly.
+ impAssignTempGen(inlArgInfo[argNum].argTmpNum,
+ inlArgInfo[argNum].argNode,
+ structHnd,
+ (unsigned)CHECK_SPILL_NONE,
+ & afterStmt,
+ callILOffset,
+ block);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ gtDispTree(afterStmt);
+ }
+#endif // DEBUG
+
+ }
+ }
+ else if (inlArgInfo[argNum].argIsByRefToStructLocal)
+ {
+ // Do nothing.
+ }
+ else
+ {
+ /* The argument is either not used or a const or lcl var */
+
+ noway_assert(!inlArgInfo[argNum].argIsUsed ||
+ inlArgInfo[argNum].argIsInvariant ||
+ inlArgInfo[argNum].argIsLclVar );
+
+ /* Make sure we didnt change argNode's along the way, or else
+ subsequent uses of the arg would have worked with the bashed value */
+ if (inlArgInfo[argNum].argIsInvariant)
+ {
+ assert(inlArgInfo[argNum].argNode->OperIsConst() ||
+ inlArgInfo[argNum].argNode->gtOper == GT_ADDR);
+ }
+ noway_assert((inlArgInfo[argNum].argIsLclVar == 0) ==
+ (inlArgInfo[argNum].argNode->gtOper != GT_LCL_VAR || (inlArgInfo[argNum].argNode->gtFlags & GTF_GLOB_REF)));
+
+ /* If the argument has side effects, append it */
+
+ if (inlArgInfo[argNum].argHasSideEff)
+ {
+ noway_assert(inlArgInfo[argNum].argIsUsed == false);
+
+ if (inlArgInfo[argNum].argNode->gtOper == GT_OBJ ||
+ inlArgInfo[argNum].argNode->gtOper == GT_MKREFANY)
+ {
+ // Don't put GT_OBJ node under a GT_COMMA.
+ // Codegen can't deal with it.
+ // Just hang the address here in case there are side-effect.
+ newStmt = gtNewStmt(gtUnusedValNode(inlArgInfo[argNum].argNode->gtOp.gtOp1), callILOffset);
+ }
+ else
+ {
+ newStmt = gtNewStmt(gtUnusedValNode(inlArgInfo[argNum].argNode), callILOffset);
+ }
+ afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ gtDispTree(afterStmt);
+ }
+#endif // DEBUG
+
+ }
+ }
+ }
+ }
+
+ // Add the CCTOR check if asked for.
+ // Note: We no longer do the optimization that is done before by staticAccessedFirstUsingHelper in the old inliner.
+ // Therefore we might prepend redundant call to HELPER.CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE
+ // before the inlined method body, even if a static field of this type was accessed in the inlinee
+ // using a helper before any other observable side-effect.
+
+ if (inlineInfo->inlineCandidateInfo->initClassResult & CORINFO_INITCLASS_USE_HELPER)
+ {
+ CORINFO_CONTEXT_HANDLE exactContext = inlineInfo->inlineCandidateInfo->exactContextHnd;
+ CORINFO_CLASS_HANDLE exactClass;
+
+ if (((SIZE_T)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS)
+ {
+ exactClass = CORINFO_CLASS_HANDLE((SIZE_T)exactContext & ~CORINFO_CONTEXTFLAGS_MASK);
+ }
+ else
+ {
+ exactClass = info.compCompHnd->getMethodClass(CORINFO_METHOD_HANDLE((SIZE_T)exactContext & ~CORINFO_CONTEXTFLAGS_MASK));
+ }
+
+ tree = fgGetSharedCCtor(exactClass);
+ newStmt = gtNewStmt(tree, callILOffset);
+ afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt);
+ }
+
+ // Insert the nullcheck statement now.
+ if (nullcheck)
+ {
+ newStmt = gtNewStmt(nullcheck, callILOffset);
+ afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt);
+ }
+
+ //
+ // Now zero-init inlinee locals
+ //
+
+ CORINFO_METHOD_INFO* InlineeMethodInfo = InlineeCompiler->info.compMethodInfo;
+
+ unsigned lclCnt = InlineeMethodInfo->locals.numArgs;
+
+ // Does callee contain any zero-init local?
+ if ((lclCnt != 0) &&
+ (InlineeMethodInfo->options & CORINFO_OPT_INIT_LOCALS) != 0)
+ {
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nZero init inlinee locals:\n");
+ }
+#endif // DEBUG
+
+ for (unsigned lclNum = 0; lclNum < lclCnt; lclNum++)
+ {
+ unsigned tmpNum = inlineInfo->lclTmpNum[lclNum];
+
+ // Is the local used at all?
+ if (tmpNum != BAD_VAR_NUM)
+ {
+ var_types lclTyp = (var_types)lvaTable[tmpNum].lvType;
+ noway_assert(lclTyp == lclVarInfo[lclNum + inlineInfo->argCnt].lclTypeInfo);
+
+ if (!varTypeIsStruct(lclTyp))
+ {
+ // Unsafe value cls check is not needed here since in-linee compiler instance would have
+ // iterated over locals and marked accordingly.
+ impAssignTempGen(tmpNum,
+ gtNewZeroConNode(genActualType(lclTyp)),
+ NO_CLASS_HANDLE,
+ (unsigned)CHECK_SPILL_NONE,
+ & afterStmt,
+ callILOffset,
+ block);
+ }
+ else
+ {
+ CORINFO_CLASS_HANDLE structType = lclVarInfo[lclNum + inlineInfo->argCnt].lclVerTypeInfo.GetClassHandle();
+
+ tree = gtNewBlkOpNode(gtNewLclvNode(tmpNum, lclTyp), // Dest
+ gtNewIconNode(0), // Value
+ info.compCompHnd->getClassSize(structType), // Size
+ false, // isVolatile
+ false); // not copyBlock
+
+ newStmt = gtNewStmt(tree, callILOffset);
+ afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt);
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ gtDispTree(afterStmt);
+ }
+#endif // DEBUG
+ }
+ }
+ }
+
+ return afterStmt;
+}
+
+
+/*****************************************************************************/
+/*static*/
+Compiler::fgWalkResult Compiler::fgChkThrowCB(GenTreePtr* pTree,
+ fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+
+ // If this tree doesn't have the EXCEPT flag set, then there is no
+ // way any of the child nodes could throw, so we can stop recursing.
+ if (!(tree->gtFlags & GTF_EXCEPT))
+ {
+ return Compiler::WALK_SKIP_SUBTREES;
+ }
+
+ switch (tree->gtOper)
+ {
+ case GT_MUL:
+ case GT_ADD:
+ case GT_SUB:
+ case GT_ASG_ADD:
+ case GT_ASG_SUB:
+ case GT_CAST:
+ if (tree->gtOverflow()) {
+ return Compiler::WALK_ABORT;
+}
+ break;
+
+ case GT_INDEX:
+ if (tree->gtFlags & GTF_INX_RNGCHK) {
+ return Compiler::WALK_ABORT;
+}
+ break;
+
+ case GT_ARR_BOUNDS_CHECK:
+ return Compiler::WALK_ABORT;
+
+ default:
+ break;
+ }
+
+ return Compiler::WALK_CONTINUE;
+}
+
+/*****************************************************************************/
+/*static*/
+Compiler::fgWalkResult Compiler::fgChkLocAllocCB(GenTreePtr* pTree,
+ fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+
+ if (tree->gtOper == GT_LCLHEAP) {
+ return Compiler::WALK_ABORT;
+}
+
+ return Compiler::WALK_CONTINUE;
+}
+
+/*****************************************************************************/
+/*static*/
+Compiler::fgWalkResult Compiler::fgChkQmarkCB(GenTreePtr* pTree,
+ fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+
+ if (tree->gtOper == GT_QMARK) {
+ return Compiler::WALK_ABORT;
+}
+
+ return Compiler::WALK_CONTINUE;
+}
+
+
+void Compiler::fgLclFldAssign(unsigned lclNum)
+{
+ assert(varTypeIsStruct(lvaTable[lclNum].lvType));
+ if (lvaTable[lclNum].lvPromoted && lvaTable[lclNum].lvFieldCnt > 1)
+ {
+ lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_LocalField));
+ }
+}