void RegSet::SetLockedRegFloat(GenTree * tree, bool bValue) { regNumber reg = tree->gtRegNum; var_types type = tree->TypeGet(); assert(varTypeIsFloating(type)); regMaskTP regMask = genRegMaskFloat(reg, tree->TypeGet()); if (bValue) { JITDUMP("locking register %s\n", getRegNameFloat(reg, type)); assert((rsGetMaskUsed() & regMask) == regMask); assert((rsGetMaskLock() & regMask) == 0); rsSetMaskLock( (rsGetMaskLock() | regMask) ); } else { JITDUMP("unlocking register %s\n", getRegNameFloat(reg, type)); assert((rsGetMaskUsed() & regMask) == regMask); assert((rsGetMaskLock() & regMask) == regMask); rsSetMaskLock( (rsGetMaskLock() & ~regMask) ); } }
//------------------------------------------------------------------------ // LowerCast: Lower GT_CAST(srcType, DstType) nodes. // // Arguments: // tree - GT_CAST node to be lowered // // Return Value: // None. // // Notes: // Casts from float/double to a smaller int type are transformed as follows: // GT_CAST(float/double, byte) = GT_CAST(GT_CAST(float/double, int32), byte) // GT_CAST(float/double, sbyte) = GT_CAST(GT_CAST(float/double, int32), sbyte) // GT_CAST(float/double, int16) = GT_CAST(GT_CAST(double/double, int32), int16) // GT_CAST(float/double, uint16) = GT_CAST(GT_CAST(double/double, int32), uint16) // // Note that for the overflow conversions we still depend on helper calls and // don't expect to see them here. // i) GT_CAST(float/double, int type with overflow detection) // void Lowering::LowerCast(GenTree* tree) { assert(tree->OperGet() == GT_CAST); JITDUMP("LowerCast for: "); DISPNODE(tree); JITDUMP("\n"); GenTree* op1 = tree->gtOp.gtOp1; var_types dstType = tree->CastToType(); var_types srcType = genActualType(op1->TypeGet()); var_types tmpType = TYP_UNDEF; if (varTypeIsFloating(srcType)) { noway_assert(!tree->gtOverflow()); assert(!varTypeIsSmall(dstType)); // fgMorphCast creates intermediate casts when converting from float to small // int. } assert(!varTypeIsSmall(srcType)); if (tmpType != TYP_UNDEF) { GenTree* tmp = comp->gtNewCastNode(tmpType, op1, tree->IsUnsigned(), tmpType); tmp->gtFlags |= (tree->gtFlags & (GTF_OVERFLOW | GTF_EXCEPT)); tree->gtFlags &= ~GTF_UNSIGNED; tree->gtOp.gtOp1 = tmp; BlockRange().InsertAfter(op1, tmp); } // Now determine if we have operands that should be contained. ContainCheckCast(tree->AsCast()); }
void CodeGen::siEndScope(unsigned varNum) { for (siScope* scope = siOpenScopeList.scNext; scope; scope = scope->scNext) { if (scope->scVarNum == varNum) { siEndScope(scope); return; } } JITDUMP("siEndScope: Failed to end scope for V%02u\n", varNum); // At this point, we probably have a bad LocalVarTab if (compiler->opts.compDbgCode) { JITDUMP("...checking var tab validity\n"); // Note the following assert is saying that we expect // the VM supplied info to be invalid... assert(!siVerifyLocalVarTab()); compiler->opts.compScopeInfo = false; } }
bool RangeCheck::IsBinOpMonotonicallyIncreasing(GenTreeOp* binop) { assert(binop->OperIs(GT_ADD)); GenTree* op1 = binop->gtGetOp1(); GenTree* op2 = binop->gtGetOp2(); JITDUMP("[RangeCheck::IsBinOpMonotonicallyIncreasing] [%06d], [%06d]\n", Compiler::dspTreeID(op1), Compiler::dspTreeID(op2)); // Check if we have a var + const. if (op2->OperGet() == GT_LCL_VAR) { jitstd::swap(op1, op2); } if (op1->OperGet() != GT_LCL_VAR) { JITDUMP("Not monotonic because op1 is not lclVar.\n"); return false; } switch (op2->OperGet()) { case GT_LCL_VAR: // When adding two local variables, we also must ensure that any constant is non-negative. return IsMonotonicallyIncreasing(op1, true) && IsMonotonicallyIncreasing(op2, true); case GT_CNS_INT: return (op2->AsIntConCommon()->IconValue() >= 0) && IsMonotonicallyIncreasing(op1, false); default: JITDUMP("Not monotonic because expression is not recognized.\n"); return false; } }
bool RangeCheck::IsBinOpMonotonicallyIncreasing(GenTreePtr op1, GenTreePtr op2, genTreeOps oper, SearchPath* path) { JITDUMP("[RangeCheck::IsBinOpMonotonicallyIncreasing] %p, %p\n", dspPtr(op1), dspPtr(op2)); // Check if we have a var + const. if (op2->OperGet() == GT_LCL_VAR) { jitstd::swap(op1, op2); } if (op1->OperGet() != GT_LCL_VAR) { JITDUMP("Not monotonic because op1 is not lclVar.\n"); return false; } switch (op2->OperGet()) { case GT_LCL_VAR: return IsMonotonicallyIncreasing(op1, path) && IsMonotonicallyIncreasing(op2, path); case GT_CNS_INT: return oper == GT_ADD && op2->AsIntConCommon()->IconValue() >= 0 && IsMonotonicallyIncreasing(op1, path); default: JITDUMP("Not monotonic because expression is not recognized.\n"); return false; } }
void CodeGen::siEndBlock(BasicBlock* block) { assert(compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0)); #if FEATURE_EH_FUNCLETS if (siInFuncletRegion) return; #endif // FEATURE_EH_FUNCLETS #ifdef DEBUG if (verbose) { printf("\nScope info: end block BB%02u, IL range ", block->bbNum); block->dspBlockILRange(); printf("\n"); } #endif // DEBUG unsigned endOffs = block->bbCodeOffsEnd; if (endOffs == BAD_IL_OFFSET) { JITDUMP("Scope info: ignoring block end\n"); return; } // If non-debuggable code, find all scopes which end over this block // and close them. For debuggable code, scopes will only end on block // boundaries. VarScopeDsc* varScope; while ((varScope = compiler->compGetNextExitScope(endOffs, !compiler->opts.compDbgCode)) != NULL) { // brace-matching editor workaround for following line: ( JITDUMP("Scope info: ending scope, LVnum=%u [%03X..%03X)\n", varScope->vsdLVnum, varScope->vsdLifeBeg, varScope->vsdLifeEnd); unsigned varNum = varScope->vsdVarNum; LclVarDsc * lclVarDsc1 = &compiler->lvaTable[varNum]; assert(lclVarDsc1); if (lclVarDsc1->lvTracked) { siEndTrackedScope(lclVarDsc1->lvVarIndex); } else { siEndScope(varNum); } } siLastEndOffs = endOffs; #ifdef DEBUG if (verbose) siDispOpenScopes(); #endif }
void Rationalizer::RewriteAddress(LIR::Use& use) { assert(use.IsInitialized()); GenTreeUnOp* address = use.Def()->AsUnOp(); assert(address->OperGet() == GT_ADDR); GenTree* location = address->gtGetOp1(); genTreeOps locationOp = location->OperGet(); if (location->IsLocal()) { // We are changing the child from GT_LCL_VAR TO GT_LCL_VAR_ADDR. // Therefore gtType of the child needs to be changed to a TYP_BYREF #ifdef DEBUG if (locationOp == GT_LCL_VAR) { JITDUMP("Rewriting GT_ADDR(GT_LCL_VAR) to GT_LCL_VAR_ADDR:\n"); } else { assert(locationOp == GT_LCL_FLD); JITDUMP("Rewriting GT_ADDR(GT_LCL_FLD) to GT_LCL_FLD_ADDR:\n"); } #endif // DEBUG location->SetOper(addrForm(locationOp)); location->gtType = TYP_BYREF; copyFlags(location, address, GTF_ALL_EFFECT); use.ReplaceWith(comp, location); BlockRange().Remove(address); } else if (locationOp == GT_CLS_VAR) { location->SetOper(GT_CLS_VAR_ADDR); location->gtType = TYP_BYREF; copyFlags(location, address, GTF_ALL_EFFECT); use.ReplaceWith(comp, location); BlockRange().Remove(address); JITDUMP("Rewriting GT_ADDR(GT_CLS_VAR) to GT_CLS_VAR_ADDR:\n"); } else if (location->OperIsIndir()) { use.ReplaceWith(comp, location->gtGetOp1()); BlockRange().Remove(location); BlockRange().Remove(address); JITDUMP("Rewriting GT_ADDR(GT_IND(X)) to X:\n"); } DISPTREERANGE(BlockRange(), use.Def()); JITDUMP("\n"); }
void Compiler::optDumpCopyPropStack(LclNumToGenTreePtrStack* curSsaName) { JITDUMP("{ "); for (LclNumToGenTreePtrStack::KeyIterator iter = curSsaName->Begin(); !iter.Equal(curSsaName->End()); ++iter) { GenTree* node = iter.GetValue()->Index(0); JITDUMP("%d-[%06d]:V%02u ", iter.Get(), dspTreeID(node), node->AsLclVarCommon()->gtLclNum); } JITDUMP("}\n\n"); }
//------------------------------------------------------------------------ // DecomposeInd: Decompose GT_IND. // // Arguments: // use - the LIR::Use object for the def that needs to be decomposed. // // Return Value: // The next node to process. // GenTree* DecomposeLongs::DecomposeInd(LIR::Use& use) { GenTree* indLow = use.Def(); LIR::Use address(Range(), &indLow->gtOp.gtOp1, indLow); address.ReplaceWithLclVar(m_compiler, m_blockWeight); JITDUMP("[DecomposeInd]: Saving addr tree to a temp var:\n"); DISPTREERANGE(Range(), address.Def()); // Change the type of lower ind. indLow->gtType = TYP_INT; // Create tree of ind(addr+4) GenTreePtr addrBase = indLow->gtGetOp1(); GenTreePtr addrBaseHigh = new (m_compiler, GT_LCL_VAR) GenTreeLclVar(GT_LCL_VAR, addrBase->TypeGet(), addrBase->AsLclVarCommon()->GetLclNum(), BAD_IL_OFFSET); GenTreePtr addrHigh = new (m_compiler, GT_LEA) GenTreeAddrMode(TYP_REF, addrBaseHigh, nullptr, 0, genTypeSize(TYP_INT)); GenTreePtr indHigh = new (m_compiler, GT_IND) GenTreeIndir(GT_IND, TYP_INT, addrHigh, nullptr); m_compiler->lvaIncRefCnts(addrBaseHigh); Range().InsertAfter(indLow, addrBaseHigh, addrHigh, indHigh); return FinalizeDecomposition(use, indLow, indHigh); }
void RangeCheck::Widen(BasicBlock* block, GenTreePtr stmt, GenTreePtr tree, SearchPath* path, Range* pRange) { #ifdef DEBUG if (m_pCompiler->verbose) { printf("[RangeCheck::Widen] BB%02d, \n", block->bbNum); Compiler::printTreeID(tree); printf("\n"); } #endif // DEBUG Range& range = *pRange; // Try to deduce the lower bound, if it is not known already. if (range.LowerLimit().IsDependent() || range.LowerLimit().IsUnknown()) { // To determine the lower bound, ask if the loop increases monotonically. bool increasing = IsMonotonicallyIncreasing(tree, path); JITDUMP("IsMonotonicallyIncreasing %d", increasing); if (increasing) { GetRangeMap()->RemoveAll(); *pRange = GetRange(block, stmt, tree, path, true DEBUGARG(0)); } } }
//-------------------------------------------------------------------------------------------------- // CancelLoopOptInfo - Cancel loop cloning optimization for this loop. // // Arguments: // loopNum the loop index. // // Return Values: // None. // void LoopCloneContext::CancelLoopOptInfo(unsigned loopNum) { JITDUMP("Cancelling loop cloning for loop L_%02u\n", loopNum); optInfo[loopNum] = nullptr; if (conditions[loopNum] != nullptr) { conditions[loopNum]->Reset(); conditions[loopNum] = nullptr; } }
void CodeGen::UnspillFloat(RegSet::SpillDsc *spillDsc) { JITDUMP("UnspillFloat() for SpillDsc [%08p]\n", dspPtr(spillDsc)); RemoveSpillDsc(spillDsc); UnspillFloatMachineDep(spillDsc); RegSet::SpillDsc::freeDsc(®Set, spillDsc); compiler->tmpRlsTemp(spillDsc->spillTemp); }
void CodeGen::UnspillFloat(LclVarDsc * varDsc) { JITDUMP("UnspillFloat() for var [%08p]\n", dspPtr(varDsc)); RegSet::SpillDsc* cur = regSet.rsSpillFloat; assert(cur); while (cur->spillVarDsc != varDsc) cur = cur->spillNext; UnspillFloat(cur); }
//------------------------------------------------------------------------ // LowerCast: Lower GT_CAST(srcType, DstType) nodes. // // Arguments: // tree - GT_CAST node to be lowered // // Return Value: // None. // // Notes: // Casts from float/double to a smaller int type are transformed as follows: // GT_CAST(float/double, byte) = GT_CAST(GT_CAST(float/double, int32), byte) // GT_CAST(float/double, sbyte) = GT_CAST(GT_CAST(float/double, int32), sbyte) // GT_CAST(float/double, int16) = GT_CAST(GT_CAST(double/double, int32), int16) // GT_CAST(float/double, uint16) = GT_CAST(GT_CAST(double/double, int32), uint16) // // Note that for the overflow conversions we still depend on helper calls and // don't expect to see them here. // i) GT_CAST(float/double, int type with overflow detection) // void Lowering::LowerCast(GenTree* tree) { assert(tree->OperGet() == GT_CAST); JITDUMP("LowerCast for: "); DISPNODE(tree); JITDUMP("\n"); GenTreePtr op1 = tree->gtOp.gtOp1; var_types dstType = tree->CastToType(); var_types srcType = genActualType(op1->TypeGet()); var_types tmpType = TYP_UNDEF; if (varTypeIsFloating(srcType)) { noway_assert(!tree->gtOverflow()); } assert(!varTypeIsSmall(srcType)); // case of src is a floating point type and dst is a small type. if (varTypeIsFloating(srcType) && varTypeIsSmall(dstType)) { NYI_ARM("Lowering for cast from float to small type"); // Not tested yet. tmpType = TYP_INT; } if (tmpType != TYP_UNDEF) { GenTreePtr tmp = comp->gtNewCastNode(tmpType, op1, tmpType); tmp->gtFlags |= (tree->gtFlags & (GTF_UNSIGNED | GTF_OVERFLOW | GTF_EXCEPT)); tree->gtFlags &= ~GTF_UNSIGNED; tree->gtOp.gtOp1 = tmp; BlockRange().InsertAfter(op1, tmp); } // Now determine if we have operands that should be contained. ContainCheckCast(tree->AsCast()); }
static void RewriteAssignmentIntoStoreLclCore(GenTreeOp* assignment, GenTree* location, GenTree* value, genTreeOps locationOp) { assert(assignment != nullptr); assert(assignment->OperGet() == GT_ASG); assert(location != nullptr); assert(value != nullptr); genTreeOps storeOp = storeForm(locationOp); #ifdef DEBUG JITDUMP("rewriting asg(%s, X) to %s(X)\n", GenTree::NodeName(locationOp), GenTree::NodeName(storeOp)); #endif // DEBUG assignment->SetOper(storeOp); GenTreeLclVarCommon* store = assignment->AsLclVarCommon(); GenTreeLclVarCommon* var = location->AsLclVarCommon(); store->SetLclNum(var->gtLclNum); store->SetSsaNum(var->gtSsaNum); if (locationOp == GT_LCL_FLD) { store->gtLclFld.gtLclOffs = var->gtLclFld.gtLclOffs; store->gtLclFld.gtFieldSeq = var->gtLclFld.gtFieldSeq; } copyFlags(store, var, GTF_LIVENESS_MASK); store->gtFlags &= ~GTF_REVERSE_OPS; store->gtType = var->TypeGet(); store->gtOp1 = value; DISPNODE(store); JITDUMP("\n"); }
void LoopCloneContext::PrintBlockConditions(unsigned loopNum) { JitExpandArrayStack<JitExpandArrayStack<LC_Condition>*>* levelCond = blockConditions[loopNum]; if (levelCond == nullptr || levelCond->Size() == 0) { JITDUMP("No block conditions\n"); return; } for (unsigned i = 0; i < levelCond->Size(); ++i) { JITDUMP("%d = {", i); for (unsigned j = 0; j < ((*levelCond)[i])->Size(); ++j) { if (j != 0) { JITDUMP(" & "); } (*((*levelCond)[i]))[j].Print(); } JITDUMP("}\n"); } }
void Compiler::fgFixupArgTabEntryPtr(GenTreePtr parentCall, GenTreePtr oldArg, GenTreePtr newArg) { assert(parentCall != nullptr); assert(oldArg != nullptr); assert(newArg != nullptr); JITDUMP("parent call was :\n"); DISPNODE(parentCall); JITDUMP("old child was :\n"); DISPNODE(oldArg); if (oldArg->gtFlags & GTF_LATE_ARG) { newArg->gtFlags |= GTF_LATE_ARG; } else { fgArgTabEntryPtr fp = Compiler::gtArgEntryByNode(parentCall, oldArg); assert(fp->node == oldArg); fp->node = newArg; } }
// Add the def location to the hash table. void RangeCheck::SetDef(UINT64 hash, Location* loc) { if (m_pDefTable == nullptr) { m_pDefTable = new (m_pCompiler->getAllocator()) VarToLocMap(m_pCompiler->getAllocator()); } #ifdef DEBUG Location* loc2; if (m_pDefTable->Lookup(hash, &loc2)) { JITDUMP("Already have BB%02d, %08X, %08X for hash => %0I64X", loc2->block->bbNum, dspPtr(loc2->stmt), dspPtr(loc2->tree), hash); assert(false); } #endif m_pDefTable->Set(hash, loc); }
// Add the def location to the hash table. void RangeCheck::SetDef(UINT64 hash, Location* loc) { if (m_pDefTable == nullptr) { m_pDefTable = new (m_alloc) VarToLocMap(m_alloc); } #ifdef DEBUG Location* loc2; if (m_pDefTable->Lookup(hash, &loc2)) { JITDUMP("Already have " FMT_BB ", [%06d], [%06d] for hash => %0I64X", loc2->block->bbNum, Compiler::dspTreeID(loc2->stmt), Compiler::dspTreeID(loc2->tree), hash); assert(false); } #endif m_pDefTable->Set(hash, loc); }
void InlineResult::report() { // User may have suppressed reporting via setReported(). If so, do nothing. if (inlReported) { return; } inlReported = true; #ifdef DEBUG // Optionally dump the result if (VERBOSE) { const char* format = "INLINER: during '%s' result '%s' reason '%s' for '%s' calling '%s'\n"; const char* caller = (inlCaller == nullptr) ? "n/a" : inlCompiler->eeGetMethodFullName(inlCaller); const char* callee = (inlCallee == nullptr) ? "n/a" : inlCompiler->eeGetMethodFullName(inlCallee); JITDUMP(format, inlContext, resultString(), reasonString(), caller, callee); } // If the inline failed, leave information on the call so we can // later recover what observation lead to the failure. if (isFailure() && (inlCall != nullptr)) { // compiler should have revoked candidacy on the call by now assert((inlCall->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0); inlCall->gtInlineObservation = static_cast<unsigned>(inlObservation); } #endif // DEBUG if (isDecided()) { const char* format = "INLINER: during '%s' result '%s' reason '%s'\n"; JITLOG_THIS(inlCompiler, (LL_INFO100000, format, inlContext, resultString(), reasonString())); COMP_HANDLE comp = inlCompiler->info.compCompHnd; comp->reportInliningDecision(inlCaller, inlCallee, result(), reasonString()); } }
//------------------------------------------------------------------------ // shouldDoubleAlign: Determine whether to double-align the frame // // Arguments: // refCntStk - sum of ref counts for all stack based variables // refCntEBP - sum of ref counts for EBP enregistered variables // refCntWtdEBP - sum of wtd ref counts for EBP enregistered variables // refCntStkParam - sum of ref counts for all stack based parameters // refCntWtdStkDbl - sum of wtd ref counts for stack based doubles (including structs // with double fields). // // Return Value: // Returns true if this method estimates that a double-aligned frame would be beneficial // // Notes: // The impact of a double-aligned frame is computed as follows: // - We save a byte of code for each parameter reference (they are frame-pointer relative) // - We pay a byte of code for each non-parameter stack reference. // - We save the misalignment penalty and possible cache-line crossing penalty. // This is estimated as 0 for SMALL_CODE, 16 for FAST_CODE and 4 otherwise. // - We pay 7 extra bytes for: // MOV EBP,ESP, // LEA ESP,[EBP-offset] // AND ESP,-8 to double align ESP // - We pay one extra memory reference for each variable that could have been enregistered in EBP (refCntWtdEBP). // // If the misalignment penalty is estimated to be less than the bytes used, we don't double align. // Otherwise, we compare the weighted ref count of ebp-enregistered variables against double the // ref count for double-aligned values. // bool Compiler::shouldDoubleAlign( unsigned refCntStk, unsigned refCntEBP, unsigned refCntWtdEBP, unsigned refCntStkParam, unsigned refCntWtdStkDbl) { bool doDoubleAlign = false; const unsigned DBL_ALIGN_SETUP_SIZE = 7; unsigned bytesUsed = refCntStk + refCntEBP - refCntStkParam + DBL_ALIGN_SETUP_SIZE; unsigned misaligned_weight = 4; if (compCodeOpt() == Compiler::SMALL_CODE) misaligned_weight = 0; if (compCodeOpt() == Compiler::FAST_CODE) misaligned_weight *= 4; JITDUMP("\nDouble alignment:\n"); JITDUMP(" Bytes that could be saved by not using EBP frame: %i\n", bytesUsed); JITDUMP(" Sum of weighted ref counts for EBP enregistered variables: %i\n", refCntWtdEBP); JITDUMP(" Sum of weighted ref counts for weighted stack based doubles: %i\n", refCntWtdStkDbl); if (bytesUsed > ((refCntWtdStkDbl * misaligned_weight) / BB_UNITY_WEIGHT)) { JITDUMP(" Predicting not to double-align ESP to save %d bytes of code.\n", bytesUsed); } else if (refCntWtdEBP > refCntWtdStkDbl * 2) { // TODO-CQ: On P4 2 Proc XEON's, SciMark.FFT degrades if SciMark.FFT.transform_internal is // not double aligned. // Here are the numbers that make this not double-aligned. // refCntWtdStkDbl = 0x164 // refCntWtdEBP = 0x1a4 // We think we do need to change the heuristic to be in favor of double-align. JITDUMP(" Predicting not to double-align ESP to allow EBP to be used to enregister variables.\n"); } else { // OK we passed all of the benefit tests, so we'll predict a double aligned frame. JITDUMP(" Predicting to create a double-aligned frame\n"); doDoubleAlign = true; } return doDoubleAlign; }
//------------------------------------------------------------------------ // DecomposeInd: Decompose GT_IND. // // Arguments: // tree - the tree to decompose // // Return Value: // None. // void DecomposeLongs::DecomposeInd(GenTree** ppTree, Compiler::fgWalkData* data) { GenTreePtr indLow = *ppTree; GenTreeStmt* addrStmt = CreateTemporary(&indLow->gtOp.gtOp1); JITDUMP("[DecomposeInd]: Saving addr tree to a temp var:\n"); DISPTREE(addrStmt); // Change the type of lower ind. indLow->gtType = TYP_INT; // Create tree of ind(addr+4) GenTreePtr addrBase = indLow->gtGetOp1(); GenTreePtr addrBaseHigh = new(m_compiler, GT_LCL_VAR) GenTreeLclVar(GT_LCL_VAR, addrBase->TypeGet(), addrBase->AsLclVarCommon()->GetLclNum(), BAD_IL_OFFSET); GenTreePtr addrHigh = new(m_compiler, GT_LEA) GenTreeAddrMode(TYP_REF, addrBaseHigh, nullptr, 0, genTypeSize(TYP_INT)); GenTreePtr indHigh = new (m_compiler, GT_IND) GenTreeIndir(GT_IND, TYP_INT, addrHigh, nullptr); // Connect linear links SimpleLinkNodeAfter(addrBaseHigh, addrHigh); SimpleLinkNodeAfter(addrHigh, indHigh); FinalizeDecomposition(ppTree, data, indLow, indHigh); }
//------------------------------------------------------------------------ // DecomposeNode: Decompose long-type trees into lower and upper halves. // // Arguments: // *ppTree - A node that may or may not require decomposition. // data - The tree-walk data that provides the context. // // Return Value: // None. It the tree at *ppTree is of TYP_LONG, it will generally be replaced. // void DecomposeLongs::DecomposeNode(GenTree** ppTree, Compiler::fgWalkData* data) { GenTree* tree = *ppTree; // Handle the case where we are implicitly using the lower half of a long lclVar. if ((tree->TypeGet() == TYP_INT) && tree->OperIsLocal()) { LclVarDsc* varDsc = m_compiler->lvaTable + tree->AsLclVarCommon()->gtLclNum; if (varTypeIsLong(varDsc) && varDsc->lvPromoted) { #ifdef DEBUG if (m_compiler->verbose) { printf("Changing implicit reference to lo half of long lclVar to an explicit reference of its promoted half:\n"); m_compiler->gtDispTree(tree); } #endif // DEBUG m_compiler->lvaDecRefCnts(tree); unsigned loVarNum = varDsc->lvFieldLclStart; tree->AsLclVarCommon()->SetLclNum(loVarNum); m_compiler->lvaIncRefCnts(tree); return; } } if (tree->TypeGet() != TYP_LONG) { return; } #ifdef DEBUG if (m_compiler->verbose) { printf("Decomposing TYP_LONG tree. BEFORE:\n"); m_compiler->gtDispTree(tree); } #endif // DEBUG switch (tree->OperGet()) { case GT_PHI: case GT_PHI_ARG: break; case GT_LCL_VAR: DecomposeLclVar(ppTree, data); break; case GT_LCL_FLD: DecomposeLclFld(ppTree, data); break; case GT_STORE_LCL_VAR: DecomposeStoreLclVar(ppTree, data); break; case GT_CAST: DecomposeCast(ppTree, data); break; case GT_CNS_LNG: DecomposeCnsLng(ppTree, data); break; case GT_CALL: DecomposeCall(ppTree, data); break; case GT_RETURN: assert(tree->gtOp.gtOp1->OperGet() == GT_LONG); break; case GT_STOREIND: DecomposeStoreInd(ppTree, data); break; case GT_STORE_LCL_FLD: assert(tree->gtOp.gtOp1->OperGet() == GT_LONG); NYI("st.lclFld of of TYP_LONG"); break; case GT_IND: DecomposeInd(ppTree, data); break; case GT_NOT: DecomposeNot(ppTree, data); break; case GT_NEG: DecomposeNeg(ppTree, data); break; // Binary operators. Those that require different computation for upper and lower half are // handled by the use of GetHiOper(). case GT_ADD: case GT_SUB: case GT_OR: case GT_XOR: case GT_AND: DecomposeArith(ppTree, data); break; case GT_MUL: NYI("Arithmetic binary operators on TYP_LONG - GT_MUL"); break; case GT_DIV: NYI("Arithmetic binary operators on TYP_LONG - GT_DIV"); break; case GT_MOD: NYI("Arithmetic binary operators on TYP_LONG - GT_MOD"); break; case GT_UDIV: NYI("Arithmetic binary operators on TYP_LONG - GT_UDIV"); break; case GT_UMOD: NYI("Arithmetic binary operators on TYP_LONG - GT_UMOD"); break; case GT_LSH: case GT_RSH: case GT_RSZ: NYI("Arithmetic binary operators on TYP_LONG - SHIFT"); break; case GT_ROL: case GT_ROR: NYI("Arithmetic binary operators on TYP_LONG - ROTATE"); break; case GT_MULHI: NYI("Arithmetic binary operators on TYP_LONG - MULHI"); break; case GT_LOCKADD: case GT_XADD: case GT_XCHG: case GT_CMPXCHG: NYI("Interlocked operations on TYP_LONG"); break; default: { JITDUMP("Illegal TYP_LONG node %s in Decomposition.", GenTree::NodeName(tree->OperGet())); noway_assert(!"Illegal TYP_LONG node in Decomposition."); break; } } #ifdef DEBUG if (m_compiler->verbose) { printf(" AFTER:\n"); m_compiler->gtDispTree(*ppTree); } #endif }
void CodeGen::siBeginBlock(BasicBlock* block) { assert(block != nullptr); if (!compiler->opts.compScopeInfo) return; if (compiler->info.compVarScopesCount == 0) return; #if FEATURE_EH_FUNCLETS if (siInFuncletRegion) return; if (block->bbFlags & BBF_FUNCLET_BEG) { // For now, don't report any scopes in funclets. JIT64 doesn't. siInFuncletRegion = true; JITDUMP("Scope info: found beginning of funclet region at block BB%02u; ignoring following blocks\n", block->bbNum); return; } #endif // FEATURE_EH_FUNCLETS #ifdef DEBUG if (verbose) { printf("\nScope info: begin block BB%02u, IL range ", block->bbNum); block->dspBlockILRange(); printf("\n"); } #endif // DEBUG unsigned beginOffs = block->bbCodeOffs; if (beginOffs == BAD_IL_OFFSET) { JITDUMP("Scope info: ignoring block beginning\n"); return; } if (!compiler->opts.compDbgCode) { /* For non-debuggable code */ // End scope of variables which are not live for this block siUpdate(); // Check that vars which are live on entry have an open scope VARSET_ITER_INIT(compiler, iter, block->bbLiveIn, i); while (iter.NextElem(compiler, &i)) { unsigned varNum = compiler->lvaTrackedToVarNum[i]; // lvRefCnt may go down to 0 after liveness-analysis. // So we need to check if this tracked variable is actually used. if (!compiler->lvaTable[varNum].lvIsInReg() && !compiler->lvaTable[varNum].lvOnFrame) { assert(compiler->lvaTable[varNum].lvRefCnt == 0); continue; } siCheckVarScope(varNum, beginOffs); } } else { // For debuggable code, scopes can begin only on block boundaries. // Check if there are any scopes on the current block's start boundary. VarScopeDsc* varScope; #if FEATURE_EH_FUNCLETS // If we find a spot where the code offset isn't what we expect, because // there is a gap, it might be because we've moved the funclets out of // line. Catch up with the enter and exit scopes of the current block. // Ignore the enter/exit scope changes of the missing scopes, which for // funclets must be matched. if (siLastEndOffs != beginOffs) { assert(beginOffs > 0); assert(siLastEndOffs < beginOffs); JITDUMP("Scope info: found offset hole. lastOffs=%u, currOffs=%u\n", siLastEndOffs, beginOffs); // Skip enter scopes while ((varScope = compiler->compGetNextEnterScope(beginOffs - 1, true)) != NULL) { /* do nothing */ JITDUMP("Scope info: skipping enter scope, LVnum=%u\n", varScope->vsdLVnum); } // Skip exit scopes while ((varScope = compiler->compGetNextExitScope(beginOffs - 1, true)) != NULL) { /* do nothing */ JITDUMP("Scope info: skipping exit scope, LVnum=%u\n", varScope->vsdLVnum); } } #else // FEATURE_EH_FUNCLETS if (siLastEndOffs != beginOffs) { assert(siLastEndOffs < beginOffs); return; } #endif // FEATURE_EH_FUNCLETS while ((varScope = compiler->compGetNextEnterScope(beginOffs)) != NULL) { // brace-matching editor workaround for following line: ( JITDUMP("Scope info: opening scope, LVnum=%u [%03X..%03X)\n", varScope->vsdLVnum, varScope->vsdLifeBeg, varScope->vsdLifeEnd); siNewScope(varScope->vsdLVnum, varScope->vsdVarNum); #ifdef DEBUG LclVarDsc* lclVarDsc1 = &compiler->lvaTable[varScope->vsdVarNum]; if (VERBOSE) { printf("Scope info: >> new scope, VarNum=%u, tracked? %s, VarIndex=%u, bbLiveIn=%s ", varScope->vsdVarNum, lclVarDsc1->lvTracked ? "yes" : "no", lclVarDsc1->lvVarIndex, VarSetOps::ToString(compiler, block->bbLiveIn)); dumpConvertedVarSet(compiler, block->bbLiveIn); printf("\n"); } assert(!lclVarDsc1->lvTracked || VarSetOps::IsMember(compiler, block->bbLiveIn, lclVarDsc1->lvVarIndex)); #endif // DEBUG } } #ifdef DEBUG if (verbose) siDispOpenScopes(); #endif }
// Merge assertions on the edge flowing into the block about a variable. void RangeCheck::MergeEdgeAssertions(GenTreePtr tree, EXPSET_TP assertions, Range* pRange) { if (assertions == 0) { return; } GenTreeLclVarCommon* lcl = (GenTreeLclVarCommon*) tree; if (lcl->gtSsaNum == SsaConfig::RESERVED_SSA_NUM) { return; } // Walk through the "assertions" to check if the apply. unsigned index = 1; for (EXPSET_TP mask = 1; index <= m_pCompiler->GetAssertionCount(); index++, mask <<= 1) { if ((assertions & mask) == 0) { continue; } Compiler::AssertionDsc* curAssertion = m_pCompiler->optGetAssertion(index); // Current assertion is about array length. if (!curAssertion->IsArrLenArithBound() && !curAssertion->IsArrLenBound()) { continue; } #ifdef DEBUG if (m_pCompiler->verbose) { m_pCompiler->optPrintAssertion(curAssertion, index); } #endif assert(m_pCompiler->vnStore->IsVNArrLenArithBound(curAssertion->op1.vn) || m_pCompiler->vnStore->IsVNArrLenBound(curAssertion->op1.vn)); ValueNumStore::ArrLenArithBoundInfo info; Limit limit(Limit::keUndef); // Current assertion is of the form (i < a.len - cns) != 0 if (curAssertion->IsArrLenArithBound()) { // Get i, a.len, cns and < as "info." m_pCompiler->vnStore->GetArrLenArithBoundInfo(curAssertion->op1.vn, &info); if (m_pCompiler->lvaTable[lcl->gtLclNum].GetPerSsaData(lcl->gtSsaNum)->m_vnPair.GetConservative() != info.cmpOp) { continue; } switch (info.arrOper) { case GT_SUB: case GT_ADD: { // If the operand that operates on the array is not constant, then done. if (!m_pCompiler->vnStore->IsVNConstant(info.arrOp) || m_pCompiler->vnStore->TypeOfVN(info.arrOp) != TYP_INT) { break; } int cons = m_pCompiler->vnStore->ConstantValue<int>(info.arrOp); limit = Limit(Limit::keBinOpArray, info.vnArray, info.arrOper == GT_SUB ? -cons : cons); } } } // Current assertion is of the form (i < a.len) != 0 else if (curAssertion->IsArrLenBound()) { // Get the info as "i", "<" and "a.len" m_pCompiler->vnStore->GetArrLenBoundInfo(curAssertion->op1.vn, &info); ValueNum lclVn = m_pCompiler->lvaTable[lcl->gtLclNum].GetPerSsaData(lcl->gtSsaNum)->m_vnPair.GetConservative(); // If we don't have the same variable we are comparing against, bail. if (lclVn != info.cmpOp) { continue; } limit.type = Limit::keArray; limit.vn = info.vnArray; } else { noway_assert(false); } if (limit.IsUndef()) { continue; } // Make sure the assertion is of the form != 0 or == 0. if (curAssertion->op2.vn != m_pCompiler->vnStore->VNZeroForType(TYP_INT)) { continue; } #ifdef DEBUG if (m_pCompiler->verbose) m_pCompiler->optPrintAssertion(curAssertion, index); #endif noway_assert(limit.IsBinOpArray() || limit.IsArray()); ValueNum arrLenVN = m_pCurBndsChk->gtArrLen->gtVNPair.GetConservative(); ValueNum arrRefVN = m_pCompiler->vnStore->GetArrForLenVn(arrLenVN); // During assertion prop we add assertions of the form: // // (i < a.Length) == 0 // (i < a.Length) != 0 // // At this point, we have detected that op1.vn is (i < a.Length) or (i < a.Length + cns), // and the op2.vn is 0. // // Now, let us check if we are == 0 (i.e., op1 assertion is false) or != 0 (op1 assertion // is true.), // // If we have an assertion of the form == 0 (i.e., equals false), then reverse relop. // The relop has to be reversed because we have: (i < a.Length) is false which is the same // as (i >= a.Length). genTreeOps cmpOper = (genTreeOps) info.cmpOper; if (curAssertion->assertionKind == Compiler::OAK_EQUAL) { cmpOper = GenTree::ReverseRelop(cmpOper); } // Bounds are inclusive, so add -1 for upper bound when "<". But make sure we won't overflow. if (cmpOper == GT_LT && !limit.AddConstant(-1)) { continue; } // Bounds are inclusive, so add +1 for lower bound when ">". But make sure we won't overflow. if (cmpOper == GT_GT && !limit.AddConstant(1)) { continue; } // Doesn't tighten the current bound. So skip. if (pRange->uLimit.IsConstant() && limit.vn != arrRefVN) { continue; } // Check if the incoming limit from assertions tightens the existing upper limit. if ((pRange->uLimit.IsArray() || pRange->uLimit.IsBinOpArray()) && pRange->uLimit.vn == arrRefVN) { // We have checked the current range's (pRange's) upper limit is either of the form: // a.Length // a.Length + cns // and a == the bndsChkCandidate's arrRef // // We want to check if the incoming limit tightens the bound, and for that the // we need to make sure that incoming limit is also on a.Length or a.Length + cns // and not b.Length or some c.Length. if (limit.vn != arrRefVN) { JITDUMP("Array ref did not match cur=$%x, assert=$%x\n", arrRefVN, limit.vn); continue; } int curCns = (pRange->uLimit.IsBinOpArray()) ? pRange->uLimit.cns : 0; int limCns = (limit.IsBinOpArray()) ? limit.cns : 0; // Incoming limit doesn't tighten the existing upper limit. if (limCns >= curCns) { JITDUMP("Bound limit %d doesn't tighten current bound %d\n", limCns, curCns); continue; } } else { // Current range's upper bound is not "a.Length or a.Length + cns" and the // incoming limit is not on the same arrRef as the bounds check candidate. // So we could skip this assertion. But in cases, of Dependent or Unknown // type of upper limit, the incoming assertion still tightens the upper // bound to a saner value. So do not skip the assertion. } // cmpOp (loop index i) cmpOper a.len +/- cns switch (cmpOper) { case GT_LT: pRange->uLimit = limit; break; case GT_GT: pRange->lLimit = limit; break; case GT_GE: pRange->lLimit = limit; break; case GT_LE: pRange->uLimit = limit; break; } JITDUMP("The range after edge merging:"); JITDUMP(pRange->ToString(m_pCompiler->getAllocatorDebugOnly())); JITDUMP("\n"); } }
// Check if the computed range is within bounds. bool RangeCheck::BetweenBounds(Range& range, int lower, GenTreePtr upper) { #ifdef DEBUG if (m_pCompiler->verbose) { printf("%s BetweenBounds <%d, ", range.ToString(m_pCompiler->getAllocatorDebugOnly()), lower); Compiler::printTreeID(upper); printf(">\n"); } #endif // DEBUG // Get the VN for the upper limit. ValueNum uLimitVN = upper->gtVNPair.GetConservative(); #ifdef DEBUG JITDUMP("VN%04X upper bound is: ", uLimitVN); if (m_pCompiler->verbose) { m_pCompiler->vnStore->vnDump(m_pCompiler, uLimitVN); } JITDUMP("\n"); #endif // If the upper limit is not length, then bail. if (!m_pCompiler->vnStore->IsVNArrLen(uLimitVN)) { return false; } // Get the array reference from the length. ValueNum arrRefVN = m_pCompiler->vnStore->GetArrForLenVn(uLimitVN); #ifdef DEBUG JITDUMP("Array ref VN"); if (m_pCompiler->verbose) { m_pCompiler->vnStore->vnDump(m_pCompiler, arrRefVN); } JITDUMP("\n"); #endif // Check if array size can be obtained. int arrSize = m_pCompiler->vnStore->GetNewArrSize(arrRefVN); JITDUMP("Array size is: %d\n", arrSize); // Upper limit: a.len + ucns (upper limit constant). if (range.UpperLimit().IsBinOpArray()) { if (range.UpperLimit().vn != arrRefVN) { return false; } int ucns = range.UpperLimit().GetConstant(); // Upper limit: a.Len + [0..n] if (ucns >= 0) { return false; } // If lower limit is a.len return false. if (range.LowerLimit().IsArray()) { return false; } // Since upper limit is bounded by the array, return true if lower bound is good. // TODO-CQ: I am retaining previous behavior to minimize ASM diffs, but we could // return "true" here if GetConstant() >= 0 instead of "== 0". if (range.LowerLimit().IsConstant() && range.LowerLimit().GetConstant() == 0) { return true; } // Check if we have the array size allocated by new. if (arrSize <= 0) { return false; } // At this point, // upper limit = a.len + ucns. ucns < 0 // lower limit = a.len + lcns. if (range.LowerLimit().IsBinOpArray()) { int lcns = range.LowerLimit().GetConstant(); if (lcns >= 0 || -lcns > arrSize) { return false; } return (range.LowerLimit().vn == arrRefVN && lcns <= ucns); } } // If upper limit is constant else if (range.UpperLimit().IsConstant()) { if (arrSize <= 0) { return false; } int ucns = range.UpperLimit().GetConstant(); if (ucns >= arrSize) { return false; } if (range.LowerLimit().IsConstant()) { int lcns = range.LowerLimit().GetConstant(); // Make sure lcns < ucns which is already less than arrSize. return (lcns >= 0 && lcns <= ucns); } if (range.LowerLimit().IsBinOpArray()) { int lcns = range.LowerLimit().GetConstant(); // a.len + lcns, make sure we don't subtract too much from a.len. if (lcns >= 0 || -lcns > arrSize) { return false; } // Make sure a.len + lcns <= ucns. return (range.LowerLimit().vn == arrRefVN && (arrSize + lcns) <= ucns); } } return false; }
/************************************************************************************** * * Perform copy propagation on a given tree as we walk the graph and if it is a local * variable, then look up all currently live definitions and check if any of those * definitions share the same value number. If so, then we can make the replacement. * */ void Compiler::optCopyProp(BasicBlock* block, GenTreePtr stmt, GenTreePtr tree, LclNumToGenTreePtrStack* curSsaName) { // TODO-Review: EH successor/predecessor iteration seems broken. if (block->bbCatchTyp == BBCT_FINALLY || block->bbCatchTyp == BBCT_FAULT) { return; } // If not local nothing to do. if (!tree->IsLocal()) { return; } if (tree->OperGet() == GT_PHI_ARG || tree->OperGet() == GT_LCL_FLD) { return; } // Propagate only on uses. if (tree->gtFlags & GTF_VAR_DEF) { return; } unsigned lclNum = tree->AsLclVarCommon()->GetLclNum(); // Skip address exposed variables. if (fgExcludeFromSsa(lclNum)) { return; } assert(tree->gtVNPair.GetConservative() != ValueNumStore::NoVN); for (LclNumToGenTreePtrStack::KeyIterator iter = curSsaName->Begin(); !iter.Equal(curSsaName->End()); ++iter) { unsigned newLclNum = iter.Get(); GenTreePtr op = iter.GetValue()->Index(0); // Nothing to do if same. if (lclNum == newLclNum) { continue; } // Skip variables with assignments embedded in the statement (i.e., with a comma). Because we // are not currently updating their SSA names as live in the copy-prop pass of the stmt. if (VarSetOps::IsMember(this, optCopyPropKillSet, lvaTable[newLclNum].lvVarIndex)) { continue; } if (op->gtFlags & GTF_VAR_CAST) { continue; } if (gsShadowVarInfo != nullptr && lvaTable[newLclNum].lvIsParam && gsShadowVarInfo[newLclNum].shadowCopy == lclNum) { continue; } ValueNum opVN = GetUseAsgDefVNOrTreeVN(op); if (opVN == ValueNumStore::NoVN) { continue; } if (op->TypeGet() != tree->TypeGet()) { continue; } if (opVN != tree->gtVNPair.GetConservative()) { continue; } if (optCopyProp_LclVarScore(&lvaTable[lclNum], &lvaTable[newLclNum], true) <= 0) { continue; } // Check whether the newLclNum is live before being substituted. Otherwise, we could end // up in a situation where there must've been a phi node that got pruned because the variable // is not live anymore. For example, // if // x0 = 1 // else // x1 = 2 // print(c) <-- x is not live here. Let's say 'c' shares the value number with "x0." // // If we simply substituted 'c' with "x0", we would be wrong. Ideally, there would be a phi // node x2 = phi(x0, x1) which can then be used to substitute 'c' with. But because of pruning // there would be no such phi node. To solve this we'll check if 'x' is live, before replacing // 'c' with 'x.' if (!lvaTable[newLclNum].lvVerTypeInfo.IsThisPtr()) { if (lvaTable[newLclNum].lvAddrExposed) { continue; } // We compute liveness only on tracked variables. So skip untracked locals. if (!lvaTable[newLclNum].lvTracked) { continue; } // Because of this dependence on live variable analysis, CopyProp phase is immediately // after Liveness, SSA and VN. if (!VarSetOps::IsMember(this, compCurLife, lvaTable[newLclNum].lvVarIndex)) { continue; } } unsigned newSsaNum = SsaConfig::RESERVED_SSA_NUM; if (op->gtFlags & GTF_VAR_DEF) { newSsaNum = GetSsaNumForLocalVarDef(op); } else // parameters, this pointer etc. { newSsaNum = op->AsLclVarCommon()->GetSsaNum(); } if (newSsaNum == SsaConfig::RESERVED_SSA_NUM) { continue; } #ifdef DEBUG if (verbose) { JITDUMP("VN based copy assertion for "); printTreeID(tree); printf(" V%02d @%08X by ", lclNum, tree->GetVN(VNK_Conservative)); printTreeID(op); printf(" V%02d @%08X.\n", newLclNum, op->GetVN(VNK_Conservative)); gtDispTree(tree, nullptr, nullptr, true); } #endif lvaTable[lclNum].decRefCnts(block->getBBWeight(this), this); lvaTable[newLclNum].incRefCnts(block->getBBWeight(this), this); tree->gtLclVarCommon.SetLclNum(newLclNum); tree->AsLclVarCommon()->SetSsaNum(newSsaNum); #ifdef DEBUG if (verbose) { printf("copy propagated to:\n"); gtDispTree(tree, nullptr, nullptr, true); } #endif break; } return; }
void InlineResult::Report() { // If we weren't actually inlining, user may have suppressed // reporting via setReported(). If so, do nothing. if (m_Reported) { return; } m_Reported = true; #ifdef DEBUG const char* callee = nullptr; // Optionally dump the result if (VERBOSE) { const char* format = "INLINER: during '%s' result '%s' reason '%s' for '%s' calling '%s'\n"; const char* caller = (m_Caller == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Caller); callee = (m_Callee == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Callee); JITDUMP(format, m_Context, ResultString(), ReasonString(), caller, callee); } // If the inline failed, leave information on the call so we can // later recover what observation lead to the failure. if (IsFailure() && (m_Call != nullptr)) { // compiler should have revoked candidacy on the call by now assert((m_Call->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0); m_Call->gtInlineObservation = m_Policy->GetObservation(); } #endif // DEBUG // Was the result NEVER? If so we might want to propagate this to // the runtime. if (IsNever() && m_Policy->PropagateNeverToRuntime()) { // If we know the callee, and if the observation that got us // to this Never inline state is something *other* than // IS_NOINLINE, then we've uncovered a reason why this method // can't ever be inlined. Update the callee method attributes // so that future inline attempts for this callee fail faster. InlineObservation obs = m_Policy->GetObservation(); if ((m_Callee != nullptr) && (obs != InlineObservation::CALLEE_IS_NOINLINE)) { #ifdef DEBUG if (VERBOSE) { const char* obsString = InlGetObservationString(obs); JITDUMP("\nINLINER: Marking %s as NOINLINE because of %s\n", callee, obsString); } #endif // DEBUG COMP_HANDLE comp = m_RootCompiler->info.compCompHnd; comp->setMethodAttribs(m_Callee, CORINFO_FLG_BAD_INLINEE); } } if (IsDecided()) { const char* format = "INLINER: during '%s' result '%s' reason '%s'\n"; JITLOG_THIS(m_RootCompiler, (LL_INFO100000, format, m_Context, ResultString(), ReasonString())); COMP_HANDLE comp = m_RootCompiler->info.compCompHnd; comp->reportInliningDecision(m_Caller, m_Callee, Result(), ReasonString()); } }
/************************************************************************************** * * Perform copy propagation using currently live definitions on the current block's * variables. Also as new definitions are encountered update the "curSsaName" which * tracks the currently live definitions. * */ void Compiler::optBlockCopyProp(BasicBlock* block, LclNumToGenTreePtrStack* curSsaName) { JITDUMP("Copy Assertion for BB%02u\n", block->bbNum); // There are no definitions at the start of the block. So clear it. compCurLifeTree = nullptr; VarSetOps::Assign(this, compCurLife, block->bbLiveIn); for (GenTreePtr stmt = block->bbTreeList; stmt; stmt = stmt->gtNext) { VarSetOps::OldStyleClearD(this, optCopyPropKillSet); // Walk the tree to find if any local variable can be replaced with current live definitions. for (GenTreePtr tree = stmt->gtStmt.gtStmtList; tree; tree = tree->gtNext) { compUpdateLife</*ForCodeGen*/ false>(tree); optCopyProp(block, stmt, tree, curSsaName); // TODO-Review: Merge this loop with the following loop to correctly update the // live SSA num while also propagating copies. // // 1. This loop performs copy prop with currently live (on-top-of-stack) SSA num. // 2. The subsequent loop maintains a stack for each lclNum with // currently active SSA numbers when definitions are encountered. // // If there is an embedded definition using a "comma" in a stmt, then the currently // live SSA number will get updated only in the next loop (2). However, this new // definition is now supposed to be live (on tos). If we did not update the stacks // using (2), copy prop (1) will use a SSA num defined outside the stmt ignoring the // embedded update. Killing the variable is a simplification to produce 0 ASM diffs // for an update release. // if (optIsSsaLocal(tree) && (tree->gtFlags & GTF_VAR_DEF)) { VarSetOps::AddElemD(this, optCopyPropKillSet, lvaTable[tree->gtLclVarCommon.gtLclNum].lvVarIndex); } } // This logic must be in sync with SSA renaming process. for (GenTreePtr tree = stmt->gtStmt.gtStmtList; tree; tree = tree->gtNext) { if (!optIsSsaLocal(tree)) { continue; } unsigned lclNum = tree->gtLclVarCommon.gtLclNum; // As we encounter a definition add it to the stack as a live definition. if (tree->gtFlags & GTF_VAR_DEF) { GenTreePtrStack* stack; if (!curSsaName->Lookup(lclNum, &stack)) { stack = new (getAllocator()) GenTreePtrStack(this); } stack->Push(tree); curSsaName->Set(lclNum, stack); } // If we encounter first use of a param or this pointer add it as a live definition. // Since they are always live, do it only once. else if ((tree->gtOper == GT_LCL_VAR) && !(tree->gtFlags & GTF_VAR_USEASG) && (lvaTable[lclNum].lvIsParam || lvaTable[lclNum].lvVerTypeInfo.IsThisPtr())) { GenTreePtrStack* stack; if (!curSsaName->Lookup(lclNum, &stack)) { stack = new (getAllocator()) GenTreePtrStack(this); stack->Push(tree); curSsaName->Set(lclNum, stack); } } } } }
//------------------------------------------------------------------------ // DecomposeStoreInd: Decompose GT_STOREIND. // // Arguments: // tree - the tree to decompose // // Return Value: // None. // void DecomposeLongs::DecomposeStoreInd(GenTree** ppTree, Compiler::fgWalkData* data) { assert(ppTree != nullptr); assert(*ppTree != nullptr); assert(data != nullptr); assert((*ppTree)->OperGet() == GT_STOREIND); assert(m_compiler->compCurStmt != nullptr); GenTree* tree = *ppTree; assert(tree->gtOp.gtOp2->OperGet() == GT_LONG); GenTreeStmt* curStmt = m_compiler->compCurStmt->AsStmt(); bool isEmbeddedStmt = !curStmt->gtStmtIsTopLevel(); // Example input trees (a nested embedded statement case) // // <linkBegin Node> // * stmtExpr void (top level) (IL ???... ???) // | /--* argPlace ref $280 // | +--* argPlace int $4a // | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | /--* lclVar ref V11 tmp9 u:3 $21c // | | { | +--* const int 4 $44 // | | { | /--* + byref $2c8 // | | { | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | | { | /--* lclFld long V01 arg1 u:2[+8] Fseq[i] $380 // | | { | | { \--* st.lclVar long (P) V21 cse8 // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 // | | { | | /--* lclVar int V22 rat0 $380 // | | { | | +--* lclVar int V23 rat1 // | | { | +--* gt_long long // | | { \--* storeIndir long // | +--* lclVar ref V11 tmp9 u:3 (last use) $21c // | +--* lclVar ref V02 tmp0 u:3 $280 // | +--* const int 8 $4a // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 // <linkEndNode> // // (editor brace matching compensation: }}}}}}}}}}}}}}}}}}) GenTree* linkBegin = m_compiler->fgGetFirstNode(tree)->gtPrev; GenTree* linkEnd = tree->gtNext; GenTree* gtLong = tree->gtOp.gtOp2; // Save address to a temp. It is used in storeIndLow and storeIndHigh trees. GenTreeStmt* addrStmt = CreateTemporary(&tree->gtOp.gtOp1); JITDUMP("[DecomposeStoreInd]: Saving address tree to a temp var:\n"); DISPTREE(addrStmt); if (!gtLong->gtOp.gtOp1->OperIsLeaf()) { GenTreeStmt* dataLowStmt = CreateTemporary(>Long->gtOp.gtOp1); JITDUMP("[DecomposeStoreInd]: Saving low data tree to a temp var:\n"); DISPTREE(dataLowStmt); } if (!gtLong->gtOp.gtOp2->OperIsLeaf()) { GenTreeStmt* dataHighStmt = CreateTemporary(>Long->gtOp.gtOp2); JITDUMP("[DecomposeStoreInd]: Saving high data tree to a temp var:\n"); DISPTREE(dataHighStmt); } // Example trees after embedded statements for address and data are added. // This example saves all address and data trees into temp variables // to show how those embedded statements are created. // // * stmtExpr void (top level) (IL ???... ???) // | /--* argPlace ref $280 // | +--* argPlace int $4a // | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | /--* lclVar ref V11 tmp9 u:3 $21c // | | { | +--* const int 4 $44 // | | { | /--* + byref $2c8 // | | { \--* st.lclVar byref V24 rat2 // | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | /--* lclVar byref V24 rat2 // | | { | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | | { | /--* lclFld long V01 arg1 u:2[+8] Fseq[i] $380380 // | | { | | { \--* st.lclVar long (P) V21 cse8 // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 // | | { | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | | { | /--* lclVar int V22 rat0 $380 // | | { | | { \--* st.lclVar int V25 rat3 // | | { | | /--* lclVar int V25 rat3 // | | { | | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | | | { | /--* lclVar int V23 rat1 // | | { | | | { \--* st.lclVar int V26 rat4 // | | { | | +--* lclVar int V26 rat4 // | | { | +--* gt_long long // | | { \--* storeIndir long // | +--* lclVar ref V11 tmp9 u:3 (last use) $21c // | +--* lclVar ref V02 tmp0 u:3 $280 // | +--* const int 8 $4a // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 // // (editor brace matching compensation: }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) GenTree* addrBase = tree->gtOp.gtOp1; GenTree* dataHigh = gtLong->gtOp.gtOp2; GenTree* dataLow = gtLong->gtOp.gtOp1; GenTree* storeIndLow = tree; // Rewrite storeIndLow tree to save only lower 32-bit data. // // | | { | /--* lclVar byref V24 rat2 (address) // ... // | | { | +--* lclVar int V25 rat3 (lower 32-bit data) // | | { | { * stmtExpr void (embedded) (IL ???... ???) // | | { | { | /--* lclVar int V23 rat1 // | | { | { \--* st.lclVar int V26 rat4 // | | { \--* storeIndir int // // (editor brace matching compensation: }}}}}}}}}) m_compiler->fgSnipNode(curStmt, gtLong); m_compiler->fgSnipNode(curStmt, dataHigh); storeIndLow->gtOp.gtOp2 = dataLow; storeIndLow->gtType = TYP_INT; // Construct storeIndHigh tree // // | | { *stmtExpr void (embedded)(IL ? ? ? ... ? ? ? ) // | | { | / --* lclVar int V26 rat4 // | | { | | / --* lclVar byref V24 rat2 // | | { | +--* lea(b + 4) ref // | | { \--* storeIndir int // // (editor brace matching compensation: }}}}}) GenTree* addrBaseHigh = new(m_compiler, GT_LCL_VAR) GenTreeLclVar(GT_LCL_VAR, addrBase->TypeGet(), addrBase->AsLclVarCommon()->GetLclNum(), BAD_IL_OFFSET); GenTree* addrHigh = new(m_compiler, GT_LEA) GenTreeAddrMode(TYP_REF, addrBaseHigh, nullptr, 0, genTypeSize(TYP_INT)); GenTree* storeIndHigh = new(m_compiler, GT_STOREIND) GenTreeStoreInd(TYP_INT, addrHigh, dataHigh); storeIndHigh->gtFlags = (storeIndLow->gtFlags & (GTF_ALL_EFFECT | GTF_LIVENESS_MASK)); storeIndHigh->gtFlags |= GTF_REVERSE_OPS; storeIndHigh->CopyCosts(storeIndLow); // Internal links of storeIndHigh tree dataHigh->gtPrev = nullptr; dataHigh->gtNext = nullptr; SimpleLinkNodeAfter(dataHigh, addrBaseHigh); SimpleLinkNodeAfter(addrBaseHigh, addrHigh); SimpleLinkNodeAfter(addrHigh, storeIndHigh); // External links of storeIndHigh tree //dataHigh->gtPrev = nullptr; if (isEmbeddedStmt) { // If storeIndTree is an embedded statement, connect storeIndLow // and dataHigh storeIndLow->gtNext = dataHigh; dataHigh->gtPrev = storeIndLow; } storeIndHigh->gtNext = linkEnd; if (linkEnd != nullptr) { linkEnd->gtPrev = storeIndHigh; } InsertNodeAsStmt(storeIndHigh); // Example final output // // * stmtExpr void (top level) (IL ???... ???) // | /--* argPlace ref $280 // | +--* argPlace int $4a // | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | /--* lclVar ref V11 tmp9 u:3 $21c // | | { | +--* const int 4 $44 // | | { | /--* + byref $2c8 // | | { \--* st.lclVar byref V24 rat2 // | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | /--* lclVar byref V24 rat2 // | | { | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | | { | /--* lclFld int V01 arg1 u:2[+8] Fseq[i] $380 // | | { | | { | +--* lclFld int V01 arg1 [+12] // | | { | | { | /--* gt_long long // | | { | | { \--* st.lclVar long (P) V21 cse8 // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 // | | { | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | | { | /--* lclVar int V22 rat0 $380 // | | { | | { \--* st.lclVar int V25 rat3 // | | { | +--* lclVar int V25 rat3 // | | { | { * stmtExpr void (embedded) (IL ???... ???) // | | { | { | /--* lclVar int V23 rat1 // | | { | { \--* st.lclVar int V26 rat4 // | | { \--* storeIndir int // | | { * stmtExpr void (embedded) (IL ???... ???) // | | { | /--* lclVar int V26 rat4 // | | { | | /--* lclVar byref V24 rat2 // | | { | +--* lea(b+4) ref // | | { \--* storeIndir int // | | /--* lclVar ref V11 tmp9 u:3 (last use) $21c // | +--* putarg_stk [+0x00] ref // | | /--* lclVar ref V02 tmp0 u:3 $280 // | +--* putarg_reg ref // | | /--* const int 8 $4a // | +--* putarg_reg int // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 // // (editor brace matching compensation: }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) }