diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 7561d0dc5ecd..b17b336d7bfe 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -207,35 +207,52 @@ struct VarScopeDsc #endif }; -// This is the location of a SSA definition. -struct DefLoc +// This class stores information associated with a LclVar SSA definition. +class LclSsaVarDsc { - BasicBlock* m_blk; - GenTree* m_tree; + // The basic block where the definition occurs. Definitions of uninitialized variables + // are considered to occur at the start of the first basic block (fgFirstBB). + // + // TODO-Cleanup: In the case of uninitialized variables the block is set to nullptr by + // SsaBuilder and changed to fgFirstBB during value numbering. It would be useful to + // investigate and perhaps eliminate this rather unexpected behavior. + BasicBlock* m_block; + // The GT_ASG node that generates the definition, or nullptr for definitions + // of uninitialized variables. + GenTreeOp* m_asg; - DefLoc() : m_blk(nullptr), m_tree(nullptr) +public: + LclSsaVarDsc() : m_block(nullptr), m_asg(nullptr) { } - DefLoc(BasicBlock* block, GenTree* tree) : m_blk(block), m_tree(tree) + LclSsaVarDsc(BasicBlock* block, GenTreeOp* asg) : m_block(block), m_asg(asg) { + assert((asg == nullptr) || asg->OperIs(GT_ASG)); } -}; -// This class stores information associated with a LclVar SSA definition. -class LclSsaVarDsc -{ -public: - LclSsaVarDsc() + BasicBlock* GetBlock() const + { + return m_block; + } + + void SetBlock(BasicBlock* block) + { + m_block = block; + } + + GenTreeOp* GetAssignment() const { + return m_asg; } - LclSsaVarDsc(BasicBlock* block, GenTree* tree) : m_defLoc(block, tree) + void SetAssignment(GenTreeOp* asg) { + assert((asg == nullptr) || asg->OperIs(GT_ASG)); + m_asg = asg; } ValueNumPair m_vnPair; - DefLoc m_defLoc; }; // This class stores information associated with a memory SSA definition. diff --git a/src/jit/earlyprop.cpp b/src/jit/earlyprop.cpp index 802c4a60d939..29c012eb1789 100644 --- a/src/jit/earlyprop.cpp +++ b/src/jit/earlyprop.cpp @@ -424,9 +424,9 @@ GenTree* Compiler::optPropGetValueRec(unsigned lclNum, unsigned ssaNum, optPropK } // Track along the use-def chain to get the array length - GenTree* treelhs = lvaTable[lclNum].GetPerSsaData(ssaNum)->m_defLoc.m_tree; + GenTreeOp* ssaDefAsg = lvaTable[lclNum].GetPerSsaData(ssaNum)->GetAssignment(); - if (treelhs == nullptr) + if (ssaDefAsg == nullptr) { // Incoming parameters or live-in variables don't have actual definition tree node // for their FIRST_SSA_NUM. See SsaBuilder::RenameVariables. @@ -434,46 +434,41 @@ GenTree* Compiler::optPropGetValueRec(unsigned lclNum, unsigned ssaNum, optPropK } else { - GenTree** lhsPtr; - GenTree* treeDefParent = treelhs->gtGetParent(&lhsPtr); + assert(ssaDefAsg->OperIs(GT_ASG)); - if (treeDefParent->OperGet() == GT_ASG) - { - assert(treelhs == treeDefParent->gtGetOp1()); - GenTree* treeRhs = treeDefParent->gtGetOp2(); + GenTree* treeRhs = ssaDefAsg->gtGetOp2(); - if (treeRhs->OperIsScalarLocal() && lvaInSsa(treeRhs->AsLclVarCommon()->GetLclNum())) - { - // Recursively track the Rhs - unsigned rhsLclNum = treeRhs->AsLclVarCommon()->GetLclNum(); - unsigned rhsSsaNum = treeRhs->AsLclVarCommon()->GetSsaNum(); + if (treeRhs->OperIsScalarLocal() && lvaInSsa(treeRhs->AsLclVarCommon()->GetLclNum())) + { + // Recursively track the Rhs + unsigned rhsLclNum = treeRhs->AsLclVarCommon()->GetLclNum(); + unsigned rhsSsaNum = treeRhs->AsLclVarCommon()->GetSsaNum(); - value = optPropGetValueRec(rhsLclNum, rhsSsaNum, valueKind, walkDepth + 1); - } - else + value = optPropGetValueRec(rhsLclNum, rhsSsaNum, valueKind, walkDepth + 1); + } + else + { + if (valueKind == optPropKind::OPK_ARRAYLEN) { - if (valueKind == optPropKind::OPK_ARRAYLEN) + value = getArrayLengthFromAllocation(treeRhs); + if (value != nullptr) { - value = getArrayLengthFromAllocation(treeRhs); - if (value != nullptr) + if (!value->IsCnsIntOrI()) { - if (!value->IsCnsIntOrI()) - { - // Leave out non-constant-sized array - value = nullptr; - } + // Leave out non-constant-sized array + value = nullptr; } } - else if (valueKind == optPropKind::OPK_OBJ_GETTYPE) + } + else if (valueKind == optPropKind::OPK_OBJ_GETTYPE) + { + value = getObjectHandleNodeFromAllocation(treeRhs); + if (value != nullptr) { - value = getObjectHandleNodeFromAllocation(treeRhs); - if (value != nullptr) + if (!value->IsCnsIntOrI()) { - if (!value->IsCnsIntOrI()) - { - // Leave out non-constant-sized array - value = nullptr; - } + // Leave out non-constant-sized array + value = nullptr; } } } @@ -548,15 +543,15 @@ void Compiler::optFoldNullCheck(GenTree* tree) if (ssaNum != SsaConfig::RESERVED_SSA_NUM) { - DefLoc defLoc = lvaTable[lclNum].GetPerSsaData(ssaNum)->m_defLoc; - BasicBlock* defBlock = defLoc.m_blk; + LclSsaVarDsc* defLoc = lvaTable[lclNum].GetPerSsaData(ssaNum); + BasicBlock* defBlock = defLoc->GetBlock(); if (compCurBB == defBlock) { - GenTree* defTree = defLoc.m_tree; - GenTree* defParent = defTree->gtGetParent(nullptr); + GenTree* defParent = defLoc->GetAssignment(); + assert(defParent->OperIs(GT_ASG)); - if ((defParent->OperGet() == GT_ASG) && (defParent->gtNext == nullptr)) + if (defParent->gtNext == nullptr) { GenTree* defRHS = defParent->gtGetOp2(); if (defRHS->OperGet() == GT_COMMA) diff --git a/src/jit/optcse.cpp b/src/jit/optcse.cpp index fb639cd274af..06fcd44ab063 100644 --- a/src/jit/optcse.cpp +++ b/src/jit/optcse.cpp @@ -2375,12 +2375,12 @@ class CSE_Heuristic // These should not have been set yet, since this is the first and // only def for this CSE. - assert(ssaVarDsc->m_defLoc.m_blk == nullptr); - assert(ssaVarDsc->m_defLoc.m_tree == nullptr); + assert(ssaVarDsc->GetBlock() == nullptr); + assert(ssaVarDsc->GetAssignment() == nullptr); - ssaVarDsc->m_vnPair = val->gtVNPair; - ssaVarDsc->m_defLoc.m_blk = blk; - ssaVarDsc->m_defLoc.m_tree = asg->AsOp()->gtOp1; + ssaVarDsc->m_vnPair = val->gtVNPair; + ssaVarDsc->SetBlock(blk); + ssaVarDsc->SetAssignment(asg->AsOp()); } /* Create a reference to the CSE temp */ diff --git a/src/jit/optimizer.cpp b/src/jit/optimizer.cpp index fc29d327e6cf..20c34bd638e8 100644 --- a/src/jit/optimizer.cpp +++ b/src/jit/optimizer.cpp @@ -6871,7 +6871,7 @@ void Compiler::optHoistLoopBlocks(unsigned loopNum, ArrayStack* blo // and the SSA definition must be outside the loop we're hoisting from ... isInvariant = isInvariant && !m_compiler->optLoopTable[m_loopNum].lpContains( - m_compiler->lvaGetDesc(lclNum)->GetPerSsaData(lclVar->GetSsaNum())->m_defLoc.m_blk); + m_compiler->lvaGetDesc(lclNum)->GetPerSsaData(lclVar->GetSsaNum())->GetBlock()); // and the VN of the tree is considered invariant as well. // // TODO-CQ: This VN invariance check should not be necessary and in some cases it is conservative - it @@ -7260,7 +7260,7 @@ bool Compiler::optVNIsLoopInvariant(ValueNum vn, unsigned lnum, VNToBoolMap* loo unsigned lclNum = funcApp.m_args[0]; unsigned ssaNum = funcApp.m_args[1]; LclSsaVarDsc* ssaDef = lvaTable[lclNum].GetPerSsaData(ssaNum); - res = !optLoopContains(lnum, ssaDef->m_defLoc.m_blk->bbNatLoopNum); + res = !optLoopContains(lnum, ssaDef->GetBlock()->bbNatLoopNum); } else if (funcApp.m_func == VNF_PhiMemoryDef) { diff --git a/src/jit/rangecheck.cpp b/src/jit/rangecheck.cpp index 7fa84dc4e909..9220ce96b7cd 100644 --- a/src/jit/rangecheck.cpp +++ b/src/jit/rangecheck.cpp @@ -395,9 +395,9 @@ bool RangeCheck::IsMonotonicallyIncreasing(GenTree* expr, bool rejectNegativeCon // If the rhs expr is local, then try to find the def of the local. else if (expr->IsLocal()) { - BasicBlock* asgBlock; - GenTreeOp* asg = GetSsaDefAsg(expr->AsLclVarCommon(), &asgBlock); - return (asg != nullptr) && IsMonotonicallyIncreasing(asg->gtGetOp2(), rejectNegativeConst); + LclSsaVarDsc* ssaDef = GetSsaDefAsg(expr->AsLclVarCommon()); + return (ssaDef != nullptr) && + IsMonotonicallyIncreasing(ssaDef->GetAssignment()->gtGetOp2(), rejectNegativeConst); } else if (expr->OperGet() == GT_ADD) { @@ -425,7 +425,7 @@ bool RangeCheck::IsMonotonicallyIncreasing(GenTree* expr, bool rejectNegativeCon } // Given a lclvar use, try to find the lclvar's defining assignment and its containing block. -GenTreeOp* RangeCheck::GetSsaDefAsg(GenTreeLclVarCommon* lclUse, BasicBlock** asgBlock) +LclSsaVarDsc* RangeCheck::GetSsaDefAsg(GenTreeLclVarCommon* lclUse) { unsigned ssaNum = lclUse->GetSsaNum(); @@ -434,33 +434,28 @@ GenTreeOp* RangeCheck::GetSsaDefAsg(GenTreeLclVarCommon* lclUse, BasicBlock** as return nullptr; } - LclSsaVarDsc* ssaData = m_pCompiler->lvaTable[lclUse->GetLclNum()].GetPerSsaData(ssaNum); - GenTree* lclDef = ssaData->m_defLoc.m_tree; + LclSsaVarDsc* ssaDef = m_pCompiler->lvaGetDesc(lclUse)->GetPerSsaData(ssaNum); - if (lclDef == nullptr) + // RangeCheck does not care about uninitialized variables. + if (ssaDef->GetAssignment() == nullptr) { return nullptr; } - // We have the def node but we also need the assignment node to get its source. - // gtGetParent can be used to get the assignment node but it's rather expensive - // and not strictly necessary here, there shouldn't be any other node between - // the assignment node and its destination node. - GenTree* asg = lclDef->gtNext; - - if (!asg->OperIs(GT_ASG) || (asg->gtGetOp1() != lclDef)) + // RangeCheck does not understand definitions generated by LCL_FLD nodes + // nor definitions generated by indirect stores to local variables. + if (!ssaDef->GetAssignment()->gtGetOp1()->OperIs(GT_LCL_VAR)) { return nullptr; } #ifdef DEBUG Location* loc = GetDef(lclUse); - assert(loc->parent == asg); - assert(loc->block == ssaData->m_defLoc.m_blk); + assert(loc->parent == ssaDef->GetAssignment()); + assert(loc->block == ssaDef->GetBlock()); #endif - *asgBlock = ssaData->m_defLoc.m_blk; - return asg->AsOp(); + return ssaDef; } #ifdef DEBUG @@ -838,9 +833,8 @@ Range RangeCheck::ComputeRangeForLocalDef(BasicBlock* block, GenTreeLclVarCommon* lcl, bool monotonic DEBUGARG(int indent)) { - BasicBlock* asgBlock; - GenTreeOp* asg = GetSsaDefAsg(lcl, &asgBlock); - if (asg == nullptr) + LclSsaVarDsc* ssaDef = GetSsaDefAsg(lcl); + if (ssaDef == nullptr) { return Range(Limit(Limit::keUnknown)); } @@ -848,17 +842,17 @@ Range RangeCheck::ComputeRangeForLocalDef(BasicBlock* block, if (m_pCompiler->verbose) { JITDUMP("----------------------------------------------------\n"); - m_pCompiler->gtDispTree(asg); + m_pCompiler->gtDispTree(ssaDef->GetAssignment()); JITDUMP("----------------------------------------------------\n"); } #endif - assert(asg->OperIs(GT_ASG)); - Range range = GetRange(asgBlock, asg->gtGetOp2(), monotonic DEBUGARG(indent)); + Range range = GetRange(ssaDef->GetBlock(), ssaDef->GetAssignment()->gtGetOp2(), monotonic DEBUGARG(indent)); if (!BitVecOps::MayBeUninit(block->bbAssertionIn)) { JITDUMP("Merge assertions from " FMT_BB ":%s for assignment about [%06d]\n", block->bbNum, - BitVecOps::ToString(m_pCompiler->apTraits, block->bbAssertionIn), Compiler::dspTreeID(asg->gtGetOp1())); - MergeEdgeAssertions(asg->gtGetOp1()->AsLclVarCommon(), block->bbAssertionIn, &range); + BitVecOps::ToString(m_pCompiler->apTraits, block->bbAssertionIn), + Compiler::dspTreeID(ssaDef->GetAssignment()->gtGetOp1())); + MergeEdgeAssertions(ssaDef->GetAssignment()->gtGetOp1()->AsLclVarCommon(), block->bbAssertionIn, &range); JITDUMP("done merging\n"); } return range; @@ -973,9 +967,8 @@ bool RangeCheck::DoesBinOpOverflow(BasicBlock* block, GenTreeOp* binop) // Check if the var definition the rhs involves arithmetic that overflows. bool RangeCheck::DoesVarDefOverflow(GenTreeLclVarCommon* lcl) { - BasicBlock* asgBlock; - GenTreeOp* asg = GetSsaDefAsg(lcl, &asgBlock); - return (asg == nullptr) || DoesOverflow(asgBlock, asg->gtGetOp2()); + LclSsaVarDsc* ssaDef = GetSsaDefAsg(lcl); + return (ssaDef == nullptr) || DoesOverflow(ssaDef->GetBlock(), ssaDef->GetAssignment()->gtGetOp2()); } bool RangeCheck::DoesPhiOverflow(BasicBlock* block, GenTree* expr) diff --git a/src/jit/rangecheck.h b/src/jit/rangecheck.h index 229c149a42e0..9d3536582317 100644 --- a/src/jit/rangecheck.h +++ b/src/jit/rangecheck.h @@ -523,7 +523,7 @@ class RangeCheck private: // Given a lclvar use, try to find the lclvar's defining assignment and its containing block. - GenTreeOp* GetSsaDefAsg(GenTreeLclVarCommon* lclUse, BasicBlock** asgBlock); + LclSsaVarDsc* GetSsaDefAsg(GenTreeLclVarCommon* lclUse); GenTreeBoundsChk* m_pCurBndsChk; diff --git a/src/jit/ssabuilder.cpp b/src/jit/ssabuilder.cpp index ffe74ea1291d..9e2ada7b0cb4 100644 --- a/src/jit/ssabuilder.cpp +++ b/src/jit/ssabuilder.cpp @@ -136,6 +136,7 @@ SsaBuilder::SsaBuilder(Compiler* pCompiler) : m_pCompiler(pCompiler) , m_allocator(pCompiler->getAllocator(CMK_SSA)) , m_visitedTraits(0, pCompiler) // at this point we do not know the size, SetupBBRoot can add a block + , m_renameStack(m_allocator, pCompiler->lvaCount) #ifdef SSA_FEATURE_DOMARR , m_pDomPreOrder(nullptr) , m_pDomPostOrder(nullptr) @@ -887,162 +888,156 @@ void SsaBuilder::InsertPhiFunctions(BasicBlock** postOrder, int count) EndPhase(PHASE_BUILD_SSA_INSERT_PHIS); } -/** - * Rename the local variable tree node. - * - * If the given tree node is a local variable, then for a def give a new count, if use, - * then give the count in the top of stack, i.e., current count (used for last def.) - * - * @param tree Tree node where an SSA variable is used or def'ed. - * @param pRenameState The incremental rename information stored during renaming process. - * - * @remarks This method has to maintain parity with TreePopStacks corresponding to pushes - * it makes for defs. - */ -void SsaBuilder::TreeRenameVariables(GenTree* tree, BasicBlock* block, SsaRenameState* pRenameState, bool isPhiDefn) +//------------------------------------------------------------------------ +// RenameDef: Rename a local or memory definition generated by a GT_ASG node. +// +// Arguments: +// asgNode - The GT_ASG node that generates the definition +// block - The basic block that contains `asgNode` +// +void SsaBuilder::RenameDef(GenTreeOp* asgNode, BasicBlock* block) { // This is perhaps temporary -- maybe should be done elsewhere. Label GT_INDs on LHS of assignments, so we // can skip these during (at least) value numbering. - if (tree->OperIs(GT_ASG)) + GenTree* lhs = asgNode->gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true); + if (lhs->OperIs(GT_IND, GT_OBJ, GT_BLK, GT_DYN_BLK)) { - GenTree* lhs = tree->AsOp()->gtOp1->gtEffectiveVal(/*commaOnly*/ true); - GenTree* trueLhs = lhs->gtEffectiveVal(/*commaOnly*/ true); - if (trueLhs->OperIsIndir()) - { - trueLhs->gtFlags |= GTF_IND_ASG_LHS; - } - else if (trueLhs->OperGet() == GT_CLS_VAR) + lhs->gtFlags |= GTF_IND_ASG_LHS; + } + else if (lhs->OperIs(GT_CLS_VAR)) + { + lhs->gtFlags |= GTF_CLS_VAR_ASG_LHS; + } + + GenTreeLclVarCommon* lclNode; + bool isFullDef; + bool isLocal = asgNode->DefinesLocal(m_pCompiler, &lclNode, &isFullDef); + + if (isLocal) + { + unsigned lclNum = lclNode->GetLclNum(); + + if (m_pCompiler->lvaInSsa(lclNum)) { - trueLhs->gtFlags |= GTF_CLS_VAR_ASG_LHS; + // Promoted variables are not in SSA, only their fields are. + assert(!m_pCompiler->lvaGetDesc(lclNum)->lvPromoted); + // This should have been marked as defintion. + assert((lclNode->gtFlags & GTF_VAR_DEF) != 0); + + unsigned ssaNum = m_pCompiler->lvaGetDesc(lclNum)->lvPerSsaData.AllocSsaNum(m_allocator, block, asgNode); + + if (!isFullDef) + { + assert((lclNode->gtFlags & GTF_VAR_USEASG) != 0); + + // This is a partial definition of a variable. The node records only the SSA number + // of the use that is implied by this partial definition. The SSA number of the new + // definition will be recorded in the m_opAsgnVarDefSsaNums map. + lclNode->SetSsaNum(m_renameStack.Top(lclNum)); + m_pCompiler->GetOpAsgnVarDefSsaNums()->Set(lclNode, ssaNum); + } + else + { + lclNode->SetSsaNum(ssaNum); + } + + m_renameStack.Push(block, lclNum, ssaNum); + + // If necessary, add "lclNum/ssaNum" to the arg list of a phi def in any + // handlers for try blocks that "block" is within. (But only do this for "real" definitions, + // not phi definitions.) + if (!asgNode->gtGetOp2()->OperIs(GT_PHI)) + { + AddDefToHandlerPhis(block, lclNum, ssaNum); + } + + // If it's a SSA local then it cannot be address exposed and thus does not define SSA memory. + assert(!m_pCompiler->lvaVarAddrExposed(lclNode->GetLclNum())); + return; } + + lclNode->SetSsaNum(SsaConfig::RESERVED_SSA_NUM); } - // Figure out if "tree" may make a new GC heap state (if we care for this block). - if ((block->bbMemoryHavoc & memoryKindSet(GcHeap)) == 0) + // Figure out if "asgNode" may make a new GC heap state (if we care for this block). + if (((block->bbMemoryHavoc & memoryKindSet(GcHeap)) == 0) && m_pCompiler->ehBlockHasExnFlowDsc(block)) { - if (tree->OperIs(GT_ASG) || tree->OperIsBlkOp()) + bool isAddrExposedLocal = isLocal && m_pCompiler->lvaVarAddrExposed(lclNode->GetLclNum()); + bool hasByrefHavoc = ((block->bbMemoryHavoc & memoryKindSet(ByrefExposed)) != 0); + if (!isLocal || (isAddrExposedLocal && !hasByrefHavoc)) { - if (m_pCompiler->ehBlockHasExnFlowDsc(block)) + // It *may* define byref memory in a non-havoc way. Make a new SSA # -- associate with this node. + unsigned ssaNum = m_pCompiler->lvMemoryPerSsaData.AllocSsaNum(m_allocator); + if (!hasByrefHavoc) { - GenTreeLclVarCommon* lclVarNode; - - bool isLocal = tree->DefinesLocal(m_pCompiler, &lclVarNode); - bool isAddrExposedLocal = isLocal && m_pCompiler->lvaVarAddrExposed(lclVarNode->GetLclNum()); - bool hasByrefHavoc = ((block->bbMemoryHavoc & memoryKindSet(ByrefExposed)) != 0); - if (!isLocal || (isAddrExposedLocal && !hasByrefHavoc)) - { - // It *may* define byref memory in a non-havoc way. Make a new SSA # -- associate with this node. - unsigned ssaNum = m_pCompiler->lvMemoryPerSsaData.AllocSsaNum(m_allocator); - if (!hasByrefHavoc) - { - pRenameState->PushMemory(ByrefExposed, block, ssaNum); - m_pCompiler->GetMemorySsaMap(ByrefExposed)->Set(tree, ssaNum); + m_renameStack.PushMemory(ByrefExposed, block, ssaNum); + m_pCompiler->GetMemorySsaMap(ByrefExposed)->Set(asgNode, ssaNum); #ifdef DEBUG - if (JitTls::GetCompiler()->verboseSsa) - { - printf("Node "); - Compiler::printTreeID(tree); - printf(" (in try block) may define memory; ssa # = %d.\n", ssaNum); - } + if (m_pCompiler->verboseSsa) + { + printf("Node "); + Compiler::printTreeID(asgNode); + printf(" (in try block) may define memory; ssa # = %d.\n", ssaNum); + } #endif // DEBUG - // Now add this SSA # to all phis of the reachable catch blocks. - AddMemoryDefToHandlerPhis(ByrefExposed, block, ssaNum); - } + // Now add this SSA # to all phis of the reachable catch blocks. + AddMemoryDefToHandlerPhis(ByrefExposed, block, ssaNum); + } - if (!isLocal) + if (!isLocal) + { + // Add a new def for GcHeap as well + if (m_pCompiler->byrefStatesMatchGcHeapStates) + { + // GcHeap and ByrefExposed share the same stacks, SsaMap, and phis + assert(!hasByrefHavoc); + assert(*m_pCompiler->GetMemorySsaMap(GcHeap)->LookupPointer(asgNode) == ssaNum); + assert(block->bbMemorySsaPhiFunc[GcHeap] == block->bbMemorySsaPhiFunc[ByrefExposed]); + } + else + { + if (!hasByrefHavoc) { - // Add a new def for GcHeap as well - if (m_pCompiler->byrefStatesMatchGcHeapStates) - { - // GcHeap and ByrefExposed share the same stacks, SsaMap, and phis - assert(!hasByrefHavoc); - assert(*m_pCompiler->GetMemorySsaMap(GcHeap)->LookupPointer(tree) == ssaNum); - assert(block->bbMemorySsaPhiFunc[GcHeap] == block->bbMemorySsaPhiFunc[ByrefExposed]); - } - else - { - if (!hasByrefHavoc) - { - // Allocate a distinct defnum for the GC Heap - ssaNum = m_pCompiler->lvMemoryPerSsaData.AllocSsaNum(m_allocator); - } - - pRenameState->PushMemory(GcHeap, block, ssaNum); - m_pCompiler->GetMemorySsaMap(GcHeap)->Set(tree, ssaNum); - AddMemoryDefToHandlerPhis(GcHeap, block, ssaNum); - } + // Allocate a distinct defnum for the GC Heap + ssaNum = m_pCompiler->lvMemoryPerSsaData.AllocSsaNum(m_allocator); } + + m_renameStack.PushMemory(GcHeap, block, ssaNum); + m_pCompiler->GetMemorySsaMap(GcHeap)->Set(asgNode, ssaNum); + AddMemoryDefToHandlerPhis(GcHeap, block, ssaNum); } } } } +} - if (!tree->IsLocal()) - { - return; - } +//------------------------------------------------------------------------ +// RenameLclUse: Rename a use of a local variable. +// +// Arguments: +// lclNode - A GT_LCL_VAR or GT_LCL_FLD node that is not a definition +// +void SsaBuilder::RenameLclUse(GenTreeLclVarCommon* lclNode) +{ + assert((lclNode->gtFlags & GTF_VAR_DEF) == 0); + + unsigned lclNum = lclNode->GetLclNum(); + unsigned ssaNum; - unsigned lclNum = tree->AsLclVarCommon()->GetLclNum(); - // Is this a variable we exclude from SSA? if (!m_pCompiler->lvaInSsa(lclNum)) { - tree->AsLclVarCommon()->SetSsaNum(SsaConfig::RESERVED_SSA_NUM); - return; + ssaNum = SsaConfig::RESERVED_SSA_NUM; } - - if ((tree->gtFlags & GTF_VAR_DEF) != 0) + else { - // Allocate a new SSA number for this definition tree. - unsigned ssaNum = m_pCompiler->lvaTable[lclNum].lvPerSsaData.AllocSsaNum(m_allocator, block, tree); - - if ((tree->gtFlags & GTF_VAR_USEASG) != 0) - { - // This is a partial definition of a variable. The node records only the SSA number - // of the use that is implied by this partial definition. The SSA number of the new - // definition will be recorded in the m_opAsgnVarDefSsaNums map. - tree->AsLclVarCommon()->SetSsaNum(pRenameState->Top(lclNum)); - - m_pCompiler->GetOpAsgnVarDefSsaNums()->Set(tree, ssaNum); - } - else - { - tree->AsLclVarCommon()->SetSsaNum(ssaNum); - } + // Promoted variables are not in SSA, only their fields are. + assert(!m_pCompiler->lvaGetDesc(lclNum)->lvPromoted); - pRenameState->Push(block, lclNum, ssaNum); - - // If necessary, add "lclNum/ssaNum" to the arg list of a phi def in any - // handlers for try blocks that "block" is within. (But only do this for "real" definitions, - // not phi definitions.) - if (!isPhiDefn) - { - AddDefToHandlerPhis(block, lclNum, ssaNum); - } + ssaNum = m_renameStack.Top(lclNum); } - else if (!isPhiDefn) // Phi args already have ssa numbers. - { - // This case is obviated by the short-term "early-out" above...but it's in the right direction. - // Is it a promoted struct local? - if (m_pCompiler->lvaTable[lclNum].lvPromoted) - { - assert(tree->TypeGet() == TYP_STRUCT); - LclVarDsc* varDsc = &m_pCompiler->lvaTable[lclNum]; - // If has only a single field var, treat this as a use of that field var. - // Otherwise, we don't give SSA names to uses of promoted struct vars. - if (varDsc->lvFieldCnt == 1) - { - lclNum = varDsc->lvFieldLclStart; - } - else - { - tree->AsLclVarCommon()->SetSsaNum(SsaConfig::RESERVED_SSA_NUM); - return; - } - } - tree->AsLclVarCommon()->SetSsaNum(pRenameState->Top(lclNum)); - } + lclNode->SetSsaNum(ssaNum); } void SsaBuilder::AddDefToHandlerPhis(BasicBlock* block, unsigned lclNum, unsigned ssaNum) @@ -1177,18 +1172,14 @@ void SsaBuilder::AddMemoryDefToHandlerPhis(MemoryKind memoryKind, BasicBlock* bl } } -/** - * Walk the block's tree in the evaluation order and give var definitions and uses their - * SSA names. - * - * @param block Block for which SSA variables have to be renamed. - * @param pRenameState The incremental rename information stored during renaming process. - * - */ -void SsaBuilder::BlockRenameVariables(BasicBlock* block, SsaRenameState* pRenameState) +//------------------------------------------------------------------------ +// BlockRenameVariables: Rename all definitions and uses within a block. +// +// Arguments: +// block - The block +// +void SsaBuilder::BlockRenameVariables(BasicBlock* block) { - // Walk the statements of the block and rename the tree variables. - // First handle the incoming memory states. for (MemoryKind memoryKind : allMemoryKinds()) { @@ -1199,7 +1190,7 @@ void SsaBuilder::BlockRenameVariables(BasicBlock* block, SsaRenameState* pRename // so we will have already allocated a defnum for it if needed. assert(memoryKind > ByrefExposed); - block->bbMemorySsaNumIn[memoryKind] = pRenameState->TopMemory(ByrefExposed); + block->bbMemorySsaNumIn[memoryKind] = m_renameStack.TopMemory(ByrefExposed); } else { @@ -1207,7 +1198,7 @@ void SsaBuilder::BlockRenameVariables(BasicBlock* block, SsaRenameState* pRename if (block->bbMemorySsaPhiFunc[memoryKind] != nullptr) { unsigned ssaNum = m_pCompiler->lvMemoryPerSsaData.AllocSsaNum(m_allocator); - pRenameState->PushMemory(memoryKind, block, ssaNum); + m_renameStack.PushMemory(memoryKind, block, ssaNum); DBG_SSA_JITDUMP("Ssa # for %s phi on entry to " FMT_BB " is %d.\n", memoryKindNames[memoryKind], block->bbNum, ssaNum); @@ -1216,26 +1207,25 @@ void SsaBuilder::BlockRenameVariables(BasicBlock* block, SsaRenameState* pRename } else { - block->bbMemorySsaNumIn[memoryKind] = pRenameState->TopMemory(memoryKind); + block->bbMemorySsaNumIn[memoryKind] = m_renameStack.TopMemory(memoryKind); } } } - // We need to iterate over phi definitions, to give them SSA names, but we need - // to know which are which, so we don't add phi definitions to handler phi arg lists. - // Statements are phi defns until they aren't. - bool isPhiDefn = true; - Statement* firstNonPhi = block->FirstNonPhiDef(); + // Walk the statements of the block and rename definitions and uses. for (Statement* stmt : block->Statements()) { - if (stmt == firstNonPhi) - { - isPhiDefn = false; - } - - for (GenTree* tree = stmt->GetTreeList(); tree; tree = tree->gtNext) + for (GenTree* tree = stmt->GetTreeList(); tree != nullptr; tree = tree->gtNext) { - TreeRenameVariables(tree, block, pRenameState, isPhiDefn); + if (tree->OperIs(GT_ASG)) + { + RenameDef(tree->AsOp(), block); + } + // PHI_ARG nodes already have SSA numbers so we only need to check LCL_VAR and LCL_FLD nodes. + else if (tree->OperIs(GT_LCL_VAR, GT_LCL_FLD) && ((tree->gtFlags & GTF_VAR_DEF) == 0)) + { + RenameLclUse(tree->AsLclVarCommon()); + } } } @@ -1254,21 +1244,21 @@ void SsaBuilder::BlockRenameVariables(BasicBlock* block, SsaRenameState* pRename assert(((block->bbMemoryDef & memorySet) != 0) == ((block->bbMemoryDef & memoryKindSet(ByrefExposed)) != 0)); - block->bbMemorySsaNumOut[memoryKind] = pRenameState->TopMemory(ByrefExposed); + block->bbMemorySsaNumOut[memoryKind] = m_renameStack.TopMemory(ByrefExposed); } else { if ((block->bbMemoryDef & memorySet) != 0) { unsigned ssaNum = m_pCompiler->lvMemoryPerSsaData.AllocSsaNum(m_allocator); - pRenameState->PushMemory(memoryKind, block, ssaNum); + m_renameStack.PushMemory(memoryKind, block, ssaNum); AddMemoryDefToHandlerPhis(memoryKind, block, ssaNum); block->bbMemorySsaNumOut[memoryKind] = ssaNum; } else { - block->bbMemorySsaNumOut[memoryKind] = pRenameState->TopMemory(memoryKind); + block->bbMemorySsaNumOut[memoryKind] = m_renameStack.TopMemory(memoryKind); } } @@ -1277,16 +1267,13 @@ void SsaBuilder::BlockRenameVariables(BasicBlock* block, SsaRenameState* pRename } } -/** - * Walk through the phi nodes of a given block and assign rhs variables to them. - * - * Also renumber the rhs variables from top of the stack. - * - * @param block Block for which phi nodes have to be assigned their rhs arguments. - * @param pRenameState The incremental rename information stored during renaming process. - * - */ -void SsaBuilder::AssignPhiNodeRhsVariables(BasicBlock* block, SsaRenameState* pRenameState) +//------------------------------------------------------------------------ +// AddPhiArgsToSuccessors: Add GT_PHI_ARG nodes to the GT_PHI nodes within block's successors. +// +// Arguments: +// block - The block +// +void SsaBuilder::AddPhiArgsToSuccessors(BasicBlock* block) { for (BasicBlock* succ : block->GetAllSuccs(m_pCompiler)) { @@ -1304,7 +1291,7 @@ void SsaBuilder::AssignPhiNodeRhsVariables(BasicBlock* block, SsaRenameState* pR GenTreePhi* phi = tree->gtGetOp2()->AsPhi(); unsigned lclNum = tree->AsOp()->gtOp1->AsLclVar()->GetLclNum(); - unsigned ssaNum = pRenameState->Top(lclNum); + unsigned ssaNum = m_renameStack.Top(lclNum); // Search the arglist for an existing definition for ssaNum. // (Can we assert that its the head of the list? This should only happen when we add // during renaming for a definition that occurs within a try, and then that's the last @@ -1449,7 +1436,7 @@ void SsaBuilder::AssignPhiNodeRhsVariables(BasicBlock* block, SsaRenameState* pR GenTreePhi* phi = tree->gtGetOp2()->AsPhi(); - unsigned ssaNum = pRenameState->Top(lclNum); + unsigned ssaNum = m_renameStack.Top(lclNum); // See if this ssaNum is already an arg to the phi. bool alreadyArg = false; @@ -1513,25 +1500,17 @@ void SsaBuilder::AssignPhiNodeRhsVariables(BasicBlock* block, SsaRenameState* pR } } -/** - * Perform variable renaming. - * - * Walks the blocks and renames all var defs with ssa numbers and all uses with the - * SSA number that is in the top of the stack. Assigns phi node rhs variables - * (i.e., the arguments to the phi.) Then, calls the function recursively on child - * nodes in the DOM tree to continue the renaming process. - * - * @param block Block for which SSA variables have to be renamed. - * @param pRenameState The incremental rename information stored during renaming process. - * - * @remarks At the end of the method, m_uses and m_defs should be populated linking the - * uses and defs. - * - * @see Briggs, Cooper, Harvey and Simpson "Practical Improvements to the Construction - * and Destruction of Static Single Assignment Form." - */ - -void SsaBuilder::RenameVariables(BlkToBlkVectorMap* domTree, SsaRenameState* pRenameState) +//------------------------------------------------------------------------ +// RenameVariables: Rename all definitions and uses within the compiled method. +// +// Arguments: +// domTree - The dominator tree +// +// Notes: +// See Briggs, Cooper, Harvey and Simpson "Practical Improvements to the Construction +// and Destruction of Static Single Assignment Form." +// +void SsaBuilder::RenameVariables(BlkToBlkVectorMap* domTree) { JITDUMP("*************** In SsaBuilder::RenameVariables()\n"); @@ -1555,7 +1534,7 @@ void SsaBuilder::RenameVariables(BlkToBlkVectorMap* domTree, SsaRenameState* pRe // In ValueNum we'd assume un-inited variables get FIRST_SSA_NUM. assert(ssaNum == SsaConfig::FIRST_SSA_NUM); - pRenameState->Push(m_pCompiler->fgFirstBB, lclNum, ssaNum); + m_renameStack.Push(m_pCompiler->fgFirstBB, lclNum, ssaNum); } } @@ -1570,7 +1549,7 @@ void SsaBuilder::RenameVariables(BlkToBlkVectorMap* domTree, SsaRenameState* pRe // GcHeap shares its stack with ByrefExposed; don't re-push. continue; } - pRenameState->PushMemory(memoryKind, m_pCompiler->fgFirstBB, initMemorySsaNum); + m_renameStack.PushMemory(memoryKind, m_pCompiler->fgFirstBB, initMemorySsaNum); } // Initialize the memory ssa numbers for unreachable blocks. ValueNum expects @@ -1617,10 +1596,10 @@ void SsaBuilder::RenameVariables(BlkToBlkVectorMap* domTree, SsaRenameState* pRe // been (recursively) processed, we still need to call BlockPopStacks on it. blocksToDo->push_back(BlockWork(block, true)); - BlockRenameVariables(block, pRenameState); + BlockRenameVariables(block); - // Assign arguments to the phi node of successors, corresponding to the block's index. - AssignPhiNodeRhsVariables(block, pRenameState); + // Add arguments to PHI nodes in block's successors. + AddPhiArgsToSuccessors(block); // Recurse with the block's DOM children. BlkVector* domChildren = domTree->LookupPointer(block); @@ -1636,7 +1615,7 @@ void SsaBuilder::RenameVariables(BlkToBlkVectorMap* domTree, SsaRenameState* pRe else { // Done, pop all SSA numbers pushed in this block. - pRenameState->PopBlockStacks(block); + m_renameStack.PopBlockStacks(block); DBG_SSA_JITDUMP("[SsaBuilder::RenameVariables] done with " FMT_BB "\n", block->bbNum); } } @@ -1758,8 +1737,7 @@ void SsaBuilder::Build() InsertPhiFunctions(postOrder, count); // Rename local variables and collect UD information for each ssa var. - SsaRenameState renameState(m_allocator, m_pCompiler->lvaCount); - RenameVariables(domTree, &renameState); + RenameVariables(domTree); EndPhase(PHASE_BUILD_SSA_RENAME); #ifdef DEBUG diff --git a/src/jit/ssabuilder.h b/src/jit/ssabuilder.h index 4b4e57c5718d..af5367f259af 100644 --- a/src/jit/ssabuilder.h +++ b/src/jit/ssabuilder.h @@ -8,8 +8,7 @@ #undef SSA_FEATURE_DOMARR #include "compiler.h" - -class SsaRenameState; +#include "ssarenamestate.h" typedef int LclVarNum; @@ -107,19 +106,14 @@ class SsaBuilder // GT_ASG(GT_LCL_VAR, GT_PHI(GT_PHI_ARG(GT_LCL_VAR, Block*), GT_LIST(GT_PHI_ARG(GT_LCL_VAR, Block*), NULL)); void InsertPhiFunctions(BasicBlock** postOrder, int count); - // Requires "domTree" to be the dominator tree relation defined by a DOM b. - // Requires "pRenameState" to have counts and stacks at their initial state. - // Assigns gtSsaNames to all variables. - void RenameVariables(BlkToBlkVectorMap* domTree, SsaRenameState* pRenameState); - - // Requires "block" to be non-NULL; and is searched for defs and uses to assign ssa numbers. - // Requires "pRenameState" to be non-NULL and be currently used for variables renaming. - void BlockRenameVariables(BasicBlock* block, SsaRenameState* pRenameState); - - // Requires "tree" (assumed to be a statement in "block") to be searched for defs and uses to assign ssa numbers. - // Requires "pRenameState" to be non-NULL and be currently used for variables renaming. Assumes that "isPhiDefn" - // implies that any definition occurring within "tree" is a phi definition. - void TreeRenameVariables(GenTree* tree, BasicBlock* block, SsaRenameState* pRenameState, bool isPhiDefn); + // Rename all definitions and uses within the compiled method. + void RenameVariables(BlkToBlkVectorMap* domTree); + // Rename all definitions and uses within a block. + void BlockRenameVariables(BasicBlock* block); + // Rename a local or memory definition generated by a GT_ASG node. + void RenameDef(GenTreeOp* asgNode, BasicBlock* block); + // Rename a use of a local variable. + void RenameLclUse(GenTreeLclVarCommon* lclNode); // Assumes that "block" contains a definition for local var "lclNum", with SSA number "ssaNum". // IF "block" is within one or more try blocks, @@ -131,9 +125,8 @@ class SsaBuilder // Same as above, for memory. void AddMemoryDefToHandlerPhis(MemoryKind memoryKind, BasicBlock* block, unsigned ssaNum); - // Requires "block" to be non-NULL. Requires "pRenameState" to be non-NULL and be currently used - // for variables renaming. Assigns the rhs arguments to the phi, i.e., block's phi node arguments. - void AssignPhiNodeRhsVariables(BasicBlock* block, SsaRenameState* pRenameState); + // Add GT_PHI_ARG nodes to the GT_PHI nodes within block's successors. + void AddPhiArgsToSuccessors(BasicBlock* block); #ifdef DEBUG void Print(BasicBlock** postOrder, int count); @@ -147,6 +140,8 @@ class SsaBuilder BitVecTraits m_visitedTraits; BitVec m_visited; + SsaRenameState m_renameStack; + #ifdef SSA_FEATURE_DOMARR // To answer queries of type a DOM b. // Do not move these outside of this class, use accessors/interface methods. diff --git a/src/jit/valuenum.cpp b/src/jit/valuenum.cpp index 443d6987177e..2ab3404dfb1a 100644 --- a/src/jit/valuenum.cpp +++ b/src/jit/valuenum.cpp @@ -5702,7 +5702,7 @@ void Compiler::fgValueNumber() ValueNum initVal = vnStore->VNForFunc(varDsc->TypeGet(), VNF_InitVal, vnStore->VNForIntCon(lclNum)); LclSsaVarDsc* ssaDef = varDsc->GetPerSsaData(SsaConfig::FIRST_SSA_NUM); ssaDef->m_vnPair.SetBoth(initVal); - ssaDef->m_defLoc.m_blk = fgFirstBB; + ssaDef->SetBlock(fgFirstBB); } else if (info.compInitMem || varDsc->lvMustInit || VarSetOps::IsMember(this, fgFirstBB->bbLiveIn, varDsc->lvVarIndex)) @@ -5761,7 +5761,7 @@ void Compiler::fgValueNumber() LclSsaVarDsc* ssaDef = varDsc->GetPerSsaData(SsaConfig::FIRST_SSA_NUM); ssaDef->m_vnPair.SetBoth(initVal); - ssaDef->m_defLoc.m_blk = fgFirstBB; + ssaDef->SetBlock(fgFirstBB); } } // Give memory an initial value number (about which we know nothing).