diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index be2ba15e25469..e9ad2d072ec55 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -574,6 +574,7 @@ void BasicBlock::dspFlags() const {BBF_HAS_IDX_LEN, "idxlen"}, {BBF_HAS_MD_IDX_LEN, "mdidxlen"}, {BBF_HAS_NEWOBJ, "newobj"}, + {BBF_HAS_NEWARR, "newarr"}, {BBF_HAS_NULLCHECK, "nullcheck"}, {BBF_BACKWARD_JUMP, "bwd"}, {BBF_BACKWARD_JUMP_TARGET, "bwd-target"}, diff --git a/src/coreclr/jit/block.h b/src/coreclr/jit/block.h index e0989df456900..c70c4abdfe289 100644 --- a/src/coreclr/jit/block.h +++ b/src/coreclr/jit/block.h @@ -462,13 +462,14 @@ enum BasicBlockFlags : uint64_t BBF_NO_CSE_IN = MAKE_BBFLAG(38), // Block should kill off any incoming CSE BBF_CAN_ADD_PRED = MAKE_BBFLAG(39), // Ok to add pred edge to this block, even when "safe" edge creation disabled BBF_HAS_VALUE_PROFILE = MAKE_BBFLAG(40), // Block has a node that needs a value probing - + + BBF_HAS_NEWARR = MAKE_BBFLAG(41), // BB contains 'new' of an array type. // The following are sets of flags. // Flags to update when two blocks are compacted BBF_COMPACT_UPD = BBF_GC_SAFE_POINT | BBF_NEEDS_GCPOLL | BBF_HAS_JMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_BACKWARD_JUMP | \ - BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK | BBF_HAS_MDARRAYREF | BBF_LOOP_HEAD, + BBF_HAS_NEWOBJ | BBF_HAS_NEWARR | BBF_HAS_NULLCHECK | BBF_HAS_MDARRAYREF | BBF_LOOP_HEAD, // Flags a block should not have had before it is split. @@ -486,7 +487,7 @@ enum BasicBlockFlags : uint64_t // For example, the bottom block might or might not have BBF_HAS_NULLCHECK, but we assume it has BBF_HAS_NULLCHECK. // TODO: Should BBF_RUN_RARELY be added to BBF_SPLIT_GAINED ? - BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_PROF_WEIGHT | \ + BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_PROF_WEIGHT | BBF_HAS_NEWARR | \ BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_NULLCHECK | BBF_HAS_HISTOGRAM_PROFILE | BBF_HAS_VALUE_PROFILE | BBF_HAS_MDARRAYREF | BBF_NEEDS_GCPOLL, // Flags that must be propagated to a new block if code is copied from a block to a new block. These are flags that @@ -494,7 +495,7 @@ enum BasicBlockFlags : uint64_t // have actually copied one of these type of tree nodes, but if we only copy a portion of the block's statements, // we don't know (unless we actually pay close attention during the copy). - BBF_COPY_PROPAGATE = BBF_HAS_NEWOBJ | BBF_HAS_NULLCHECK | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_HAS_MDARRAYREF, + BBF_COPY_PROPAGATE = BBF_HAS_NEWOBJ | BBF_HAS_NEWARR | BBF_HAS_NULLCHECK | BBF_HAS_IDX_LEN | BBF_HAS_MD_IDX_LEN | BBF_HAS_MDARRAYREF, }; FORCEINLINE diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index 9f1a72afce63e..90e94b69c9f3c 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -973,6 +973,10 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos) { fprintf(fgxFile, "\n hot=\"true\""); } + if (block->HasFlag(BBF_HAS_NEWARR)) + { + fprintf(fgxFile, "\n callsNewArr=\"true\""); + } if (block->HasFlag(BBF_HAS_NEWOBJ)) { fprintf(fgxFile, "\n callsNew=\"true\""); diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index c7b5caa4b38ae..2db18cd508f72 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -7496,12 +7496,11 @@ struct GenTreeArrAddr : GenTreeUnOp public: GenTreeArrAddr(GenTree* addr, var_types elemType, CORINFO_CLASS_HANDLE elemClassHandle, uint8_t firstElemOffset) - : GenTreeUnOp(GT_ARR_ADDR, TYP_BYREF, addr DEBUGARG(/* largeNode */ false)) + : GenTreeUnOp(GT_ARR_ADDR, addr->TypeGet(), addr DEBUGARG(/* largeNode */ false)) , m_elemClassHandle(elemClassHandle) , m_elemType(elemType) , m_firstElemOffset(firstElemOffset) { - assert(addr->TypeIs(TYP_BYREF)); assert(((elemType == TYP_STRUCT) && (elemClassHandle != NO_CLASS_HANDLE)) || (elemClassHandle == NO_CLASS_HANDLE)); } diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 85682710ea199..f2e434a934a73 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -9790,11 +9790,25 @@ void Compiler::impImportBlockCode(BasicBlock* block) op1->AsCall()->compileTimeHelperArgumentHandle = (CORINFO_GENERIC_HANDLE)resolvedToken.hClass; // Remember that this function contains 'new' of an SD array. + block->SetFlags(BBF_HAS_NEWARR); optMethodFlags |= OMF_HAS_NEWARRAY; + // We assign the newly allocated object (by a GT_CALL to newarr node) + // to a temp. Note that the pattern "temp = allocArr" is required + // by ObjectAllocator phase to be able to determine newarr nodes + // without exhaustive walk over all expressions. + lclNum = lvaGrabTemp(true DEBUGARG("NewArr temp")); + + impStoreToTemp(lclNum, op1, CHECK_SPILL_NONE); + + assert(lvaTable[lclNum].lvSingleDef == 0); + lvaTable[lclNum].lvSingleDef = 1; + JITDUMP("Marked V%02u as a single def local\n", lclNum); + lvaSetClass(lclNum, resolvedToken.hClass, true /* is Exact */); + /* Push the result of the call on the stack */ - impPushOnStack(op1, tiRetVal); + impPushOnStack(gtNewLclvNode(lclNum, TYP_REF), tiRetVal); callTyp = TYP_REF; } diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index e8de998ebed2c..754cd0c05548c 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -2579,12 +2579,28 @@ GenTree* Compiler::impInitializeArrayIntrinsic(CORINFO_SIG_INFO* sig) } // - // We start by looking at the last statement, making sure it's a store, and - // that the target of the store is the array passed to InitializeArray. + // We start by looking at the last statement, making sure it's a store. // GenTree* arrayLocalStore = impLastStmt->GetRootNode(); - if (!arrayLocalStore->OperIs(GT_STORE_LCL_VAR) || !arrayLocalNode->OperIs(GT_LCL_VAR) || - (arrayLocalStore->AsLclVar()->GetLclNum() != arrayLocalNode->AsLclVar()->GetLclNum())) + if (arrayLocalStore->OperIs(GT_STORE_LCL_VAR) && arrayLocalNode->OperIs(GT_LCL_VAR)) + { + // Make sure the target of the store is the array passed to InitializeArray. + if (arrayLocalStore->AsLclVar()->GetLclNum() != arrayLocalNode->AsLclVar()->GetLclNum()) + { + // The array can be spilled to a temp for stack allocation. + // Try getting the actual store node from the previous statement. + if (arrayLocalStore->AsLclVar()->Data()->OperIs(GT_LCL_VAR) && impLastStmt->GetPrevStmt() != nullptr) + { + arrayLocalStore = impLastStmt->GetPrevStmt()->GetRootNode(); + if (!arrayLocalStore->OperIs(GT_STORE_LCL_VAR) || + arrayLocalStore->AsLclVar()->GetLclNum() != arrayLocalNode->AsLclVar()->GetLclNum()) + { + return nullptr; + } + } + } + } + else { return nullptr; } diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index bcb373fb3a4fc..5a843292574d0 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -650,6 +650,7 @@ CONFIG_STRING(JitObjectStackAllocationRange, W("JitObjectStackAllocationRange")) RELEASE_CONFIG_INTEGER(JitObjectStackAllocation, W("JitObjectStackAllocation"), 1) RELEASE_CONFIG_INTEGER(JitObjectStackAllocationRefClass, W("JitObjectStackAllocationRefClass"), 1) RELEASE_CONFIG_INTEGER(JitObjectStackAllocationBoxedValueClass, W("JitObjectStackAllocationBoxedValueClass"), 1) +RELEASE_CONFIG_INTEGER(JitObjectStackAllocationArray, W("JitObjectStackAllocationArray"), 1) RELEASE_CONFIG_INTEGER(JitEECallTimingInfo, W("JitEECallTimingInfo"), 0) diff --git a/src/coreclr/jit/jitmetadatalist.h b/src/coreclr/jit/jitmetadatalist.h index 51ef3c8104464..60a665e9bf0bd 100644 --- a/src/coreclr/jit/jitmetadatalist.h +++ b/src/coreclr/jit/jitmetadatalist.h @@ -77,6 +77,8 @@ JITMETADATAMETRIC(NewRefClassHelperCalls, int, 0) JITMETADATAMETRIC(StackAllocatedRefClasses, int, 0) JITMETADATAMETRIC(NewBoxedValueClassHelperCalls, int, 0) JITMETADATAMETRIC(StackAllocatedBoxedValueClasses, int, 0) +JITMETADATAMETRIC(NewArrayHelperCalls, int, 0) +JITMETADATAMETRIC(StackAllocatedArrays, int, 0) #undef JITMETADATA #undef JITMETADATAINFO diff --git a/src/coreclr/jit/lclmorph.cpp b/src/coreclr/jit/lclmorph.cpp index 2a9d0f9a36914..47e8b894573c5 100644 --- a/src/coreclr/jit/lclmorph.cpp +++ b/src/coreclr/jit/lclmorph.cpp @@ -1106,6 +1106,75 @@ class LocalAddressVisitor final : public GenTreeVisitor break; } + case GT_INDEX_ADDR: + { + assert(TopValue(2).Node() == node); + assert(TopValue(1).Node() == node->gtGetOp1()); + assert(TopValue(0).Node() == node->gtGetOp2()); + + if (node->gtGetOp2()->IsCnsIntOrI() && TopValue(1).IsAddress()) + { + ssize_t offset = node->AsIndexAddr()->gtElemOffset + + node->gtGetOp2()->AsIntCon()->IconValue() * node->AsIndexAddr()->gtElemSize; + + if (!node->AsIndexAddr()->IsBoundsChecked() || + (static_cast(node->AsIndexAddr()->gtElemOffset) <= offset && + offset < static_cast(m_compiler->lvaLclExactSize(TopValue(1).LclNum())))) + { + if (FitsIn(offset) && + TopValue(2).AddOffset(TopValue(1), static_cast(offset))) + { + INDEBUG(TopValue(0).Consume()); + PopValue(); + PopValue(); + break; + } + } + else + { + *use = m_compiler->gtNewOperNode(GT_COMMA, node->TypeGet(), + m_compiler->gtNewHelperCallNode(CORINFO_HELP_RNGCHKFAIL, + TYP_VOID), + m_compiler->gtNewIconNode(0, TYP_BYREF)); + m_stmtModified = true; + INDEBUG(TopValue(0).Consume()); + PopValue(); + INDEBUG(TopValue(0).Consume()); + PopValue(); + break; + } + } + + EscapeValue(TopValue(0), node); + PopValue(); + EscapeValue(TopValue(0), node); + PopValue(); + break; + } + + case GT_ARR_LENGTH: + { + assert(TopValue(1).Node() == node); + assert(TopValue(0).Node() == node->gtGetOp1()); + + if (TopValue(0).IsAddress()) + { + GenTree* gtLclFld = + m_compiler->gtNewLclFldNode(TopValue(0).LclNum(), TYP_INT, + TopValue(0).Offset() + OFFSETOF__CORINFO_Array__length); + SequenceLocal(gtLclFld->AsLclVarCommon()); + *use = gtLclFld; + m_stmtModified = true; + INDEBUG(TopValue(0).Consume()); + PopValue(); + break; + } + + EscapeValue(TopValue(0), node); + PopValue(); + break; + } + default: while (TopValue(0).Node() != node) { diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 3b971780ff232..11c5ecb86c579 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8344,10 +8344,20 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA return tree; } +#ifdef DEBUG + if ((tree->gtDebugFlags & GTF_DEBUG_NODE_MORPHED) != 0) + { + return tree; + } +#endif + /* If we created a comma-throw tree then we need to morph op1 */ if (fgIsCommaThrow(tree)) { - tree->AsOp()->gtOp1 = fgMorphTree(tree->AsOp()->gtOp1); + INDEBUG(if ((tree->AsOp()->gtOp1->gtDebugFlags & GTF_DEBUG_NODE_MORPHED) == 0)) + { + tree->AsOp()->gtOp1 = fgMorphTree(tree->AsOp()->gtOp1); + } fgMorphTreeDone(tree); return tree; } diff --git a/src/coreclr/jit/objectalloc.cpp b/src/coreclr/jit/objectalloc.cpp index 89e28c5978c6d..ee16546f528e0 100644 --- a/src/coreclr/jit/objectalloc.cpp +++ b/src/coreclr/jit/objectalloc.cpp @@ -30,9 +30,9 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // PhaseStatus ObjectAllocator::DoPhase() { - if ((comp->optMethodFlags & OMF_HAS_NEWOBJ) == 0) + if ((comp->optMethodFlags & OMF_HAS_NEWOBJ) == 0 && (comp->optMethodFlags & OMF_HAS_NEWARRAY) == 0) { - JITDUMP("no newobjs in this method; punting\n"); + JITDUMP("no newobjs or newarr in this method; punting\n"); return PhaseStatus::MODIFIED_NOTHING; } @@ -54,12 +54,12 @@ PhaseStatus ObjectAllocator::DoPhase() if (enabled) { - JITDUMP("enabled, analyzing...\n"); + JITDUMP("Enabled, analyzing...\n"); DoAnalysis(); } else { - JITDUMP("disabled%s, punting\n", IsObjectStackAllocationEnabled() ? disableReason : ""); + JITDUMP("Disabled%s, punting\n", IsObjectStackAllocationEnabled() ? disableReason : ""); m_IsObjectStackAllocationEnabled = false; } @@ -190,20 +190,13 @@ void ObjectAllocator::MarkEscapingVarsAndBuildConnGraph() GenTree* const tree = *use; unsigned const lclNum = tree->AsLclVarCommon()->GetLclNum(); - // If this local already escapes, no need to look further. - // - if (m_allocator->CanLclVarEscape(lclNum)) - { - return Compiler::fgWalkResult::WALK_CONTINUE; - } - bool lclEscapes = true; if (tree->OperIsLocalStore()) { lclEscapes = false; } - else if (tree->OperIs(GT_LCL_VAR) && tree->TypeIs(TYP_REF, TYP_BYREF, TYP_I_IMPL)) + else if (tree->OperIs(GT_LCL_VAR, GT_LCL_ADDR) && tree->TypeIs(TYP_REF, TYP_BYREF, TYP_I_IMPL, TYP_STRUCT)) { assert(tree == m_ancestors.Top()); @@ -228,22 +221,12 @@ void ObjectAllocator::MarkEscapingVarsAndBuildConnGraph() for (unsigned int lclNum = 0; lclNum < comp->lvaCount; ++lclNum) { - var_types type = comp->lvaTable[lclNum].TypeGet(); + m_ConnGraphAdjacencyMatrix[lclNum] = BitVecOps::MakeEmpty(&m_bitVecTraits); - if (type == TYP_REF || genActualType(type) == TYP_I_IMPL || type == TYP_BYREF) + if (comp->lvaTable[lclNum].IsAddressExposed()) { - m_ConnGraphAdjacencyMatrix[lclNum] = BitVecOps::MakeEmpty(&m_bitVecTraits); - - if (comp->lvaTable[lclNum].IsAddressExposed()) - { - JITDUMP(" V%02u is address exposed\n", lclNum); - MarkLclVarAsEscaping(lclNum); - } - } - else - { - // Variable that may not point to objects will not participate in our analysis. - m_ConnGraphAdjacencyMatrix[lclNum] = BitVecOps::UninitVal(); + JITDUMP(" V%02u is address exposed\n", lclNum); + MarkLclVarAsEscaping(lclNum); } } @@ -272,7 +255,20 @@ void ObjectAllocator::ComputeEscapingNodes(BitVecTraits* bitVecTraits, BitVec& e unsigned int lclNum; + BitVecOps::Iter lclStoreIterator(bitVecTraits, m_IndirectRefStoredPointers); + while (lclStoreIterator.NextElem(&lclNum)) + { + BitVecOps::Iter targetLclIter(bitVecTraits, m_ConnGraphAdjacencyMatrix[lclNum]); + unsigned int targetLclNum; + while (targetLclIter.NextElem(&targetLclNum)) + { + JITDUMP("V%02u may point to objects that V%02u points to\n", lclNum, targetLclNum); + BitVecOps::AddElemD(bitVecTraits, m_ConnGraphAdjacencyMatrix[targetLclNum], lclNum); + } + } + bool doOneMoreIteration = true; + while (doOneMoreIteration) { BitVecOps::Iter iterator(bitVecTraits, escapingNodesToProcess); @@ -336,17 +332,25 @@ void ObjectAllocator::ComputeStackObjectPointers(BitVecTraits* bitVecTraits) { // Check if we know what is assigned to this pointer. unsigned bitCount = BitVecOps::Count(bitVecTraits, m_ConnGraphAdjacencyMatrix[lclNum]); - assert(bitCount <= 1); - if (bitCount == 1) + if (bitCount > 0) { BitVecOps::Iter iter(bitVecTraits, m_ConnGraphAdjacencyMatrix[lclNum]); - unsigned rhsLclNum = 0; - iter.NextElem(&rhsLclNum); + unsigned rhsLclNum = 0; + bool definitelyStackPointing = true; - if (DoesLclVarPointToStack(rhsLclNum)) + while (iter.NextElem(&rhsLclNum)) { - // The only store to lclNum local is the definitely-stack-pointing - // rhsLclNum local so lclNum local is also definitely-stack-pointing. + if (!DoesLclVarPointToStack(rhsLclNum)) + { + definitelyStackPointing = false; + break; + } + } + + if (definitelyStackPointing) + { + // All stores to lclNum local are the definitely-stack-pointing + // rhsLclNum locals so lclNum local is also definitely-stack-pointing. MarkLclVarAsDefinitelyStackPointing(lclNum); } } @@ -377,20 +381,20 @@ bool ObjectAllocator::MorphAllocObjNodes() for (BasicBlock* const block : comp->Blocks()) { const bool basicBlockHasNewObj = block->HasFlag(BBF_HAS_NEWOBJ); + const bool basicBlockHasNewArr = block->HasFlag(BBF_HAS_NEWARR); const bool basicBlockHasBackwardJump = block->HasFlag(BBF_BACKWARD_JUMP); -#ifndef DEBUG - if (!basicBlockHasNewObj) + + if (!basicBlockHasNewObj && !basicBlockHasNewArr) { continue; } -#endif // DEBUG for (Statement* const stmt : block->Statements()) { GenTree* stmtExpr = stmt->GetRootNode(); GenTree* data = nullptr; - bool canonicalAllocObjFound = false; + ObjectAllocationType allocType = OAT_NONE; if (stmtExpr->OperIs(GT_STORE_LCL_VAR) && stmtExpr->TypeIs(TYP_REF)) { @@ -398,38 +402,48 @@ bool ObjectAllocator::MorphAllocObjNodes() if (data->OperGet() == GT_ALLOCOBJ) { - canonicalAllocObjFound = true; + allocType = OAT_NEWOBJ; + } + else if (data->IsHelperCall()) + { + switch (data->AsCall()->GetHelperNum()) + { + case CORINFO_HELP_NEWARR_1_VC: + case CORINFO_HELP_NEWARR_1_OBJ: + case CORINFO_HELP_NEWARR_1_DIRECT: + case CORINFO_HELP_NEWARR_1_ALIGN8: + { + if (data->AsCall()->gtArgs.CountArgs() == 2 && + data->AsCall()->gtArgs.GetArgByIndex(1)->GetNode()->IsCnsIntOrI()) + { + allocType = OAT_NEWARR; + } + break; + } +#ifdef FEATURE_READYTORUN + case CORINFO_HELP_READYTORUN_NEWARR_1: + { + if (data->AsCall()->gtArgs.CountArgs() == 1 && + data->AsCall()->gtArgs.GetArgByIndex(0)->GetNode()->IsCnsIntOrI()) + { + allocType = OAT_NEWARR; + } + break; + } +#endif + default: + { + break; + } + } } } - if (canonicalAllocObjFound) + if (allocType != OAT_NONE) { - assert(basicBlockHasNewObj); - //------------------------------------------------------------------------ - // We expect the following expression tree at this point - // STMTx (IL 0x... ???) - // * STORE_LCL_VAR ref - // \--* ALLOCOBJ ref - // \--* CNS_INT(h) long - //------------------------------------------------------------------------ - - GenTreeAllocObj* asAllocObj = data->AsAllocObj(); - unsigned int lclNum = stmtExpr->AsLclVar()->GetLclNum(); - CORINFO_CLASS_HANDLE clsHnd = data->AsAllocObj()->gtAllocObjClsHnd; - CORINFO_CLASS_HANDLE stackClsHnd = clsHnd; - const bool isValueClass = comp->info.compCompHnd->isValueClass(clsHnd); - const char* onHeapReason = nullptr; - bool canStack = false; - - if (isValueClass) - { - comp->Metrics.NewBoxedValueClassHelperCalls++; - stackClsHnd = comp->info.compCompHnd->getTypeForBoxOnStack(clsHnd); - } - else - { - comp->Metrics.NewRefClassHelperCalls++; - } + bool canStack = false; + const char* onHeapReason = nullptr; + unsigned int lclNum = stmtExpr->AsLclVar()->GetLclNum(); // Don't attempt to do stack allocations inside basic blocks that may be in a loop. // @@ -443,51 +457,153 @@ bool ObjectAllocator::MorphAllocObjNodes() onHeapReason = "[alloc in loop]"; canStack = false; } - else if (!CanAllocateLclVarOnStack(lclNum, clsHnd, &onHeapReason)) - { - // reason set by the call - canStack = false; - } - else if (stackClsHnd == NO_CLASS_HANDLE) + else { - assert(isValueClass); - onHeapReason = "[no class handle for this boxed value class]"; - canStack = false; + if (allocType == OAT_NEWARR) + { + assert(basicBlockHasNewArr); + //------------------------------------------------------------------------ + // We expect the following expression tree at this point + // For non-ReadyToRun: + // STMTx (IL 0x... ???) + // * STORE_LCL_VAR ref + // \--* CALL help ref + // +--* CNS_INT(h) long + // \--* CNS_INT long + // For ReadyToRun: + // STMTx (IL 0x... ???) + // * STORE_LCL_VAR ref + // \--* CALL help ref + // \--* CNS_INT long + //------------------------------------------------------------------------ + + bool isExact = false; + bool isNonNull = false; + CORINFO_CLASS_HANDLE clsHnd = + comp->gtGetHelperCallClassHandle(data->AsCall(), &isExact, &isNonNull); + GenTree* len = nullptr; +#ifdef FEATURE_READYTORUN + if (comp->opts.IsReadyToRun() && data->IsHelperCall(comp, CORINFO_HELP_READYTORUN_NEWARR_1)) + { + len = data->AsCall()->gtArgs.GetArgByIndex(0)->GetNode(); + } + else +#endif + { + len = data->AsCall()->gtArgs.GetArgByIndex(1)->GetNode(); + } + + assert(len != nullptr); + + unsigned int blockSize = 0; + comp->Metrics.NewArrayHelperCalls++; + + if (!isExact || !isNonNull) + { + onHeapReason = "[array type is either non-exact or null]"; + canStack = false; + } + else if (!len->IsCnsIntOrI()) + { + onHeapReason = "[non-constant size]"; + canStack = false; + } + else if (!CanAllocateLclVarOnStack(lclNum, clsHnd, allocType, len->AsIntCon()->IconValue(), + &blockSize, &onHeapReason)) + { + // reason set by the call + canStack = false; + } + else + { + JITDUMP("Allocating V%02u on the stack\n", lclNum); + canStack = true; + const unsigned int stackLclNum = + MorphNewArrNodeIntoStackAlloc(data->AsCall(), clsHnd, + (unsigned int)len->AsIntCon()->IconValue(), blockSize, + block, stmt); + m_HeapLocalToStackLocalMap.AddOrUpdate(lclNum, stackLclNum); + comp->Metrics.StackAllocatedArrays++; + } + } + else if (allocType == OAT_NEWOBJ) + { + assert(basicBlockHasNewObj); + //------------------------------------------------------------------------ + // We expect the following expression tree at this point + // STMTx (IL 0x... ???) + // * STORE_LCL_VAR ref + // \--* ALLOCOBJ ref + // \--* CNS_INT(h) long + //------------------------------------------------------------------------ + + CORINFO_CLASS_HANDLE clsHnd = data->AsAllocObj()->gtAllocObjClsHnd; + CORINFO_CLASS_HANDLE stackClsHnd = clsHnd; + const bool isValueClass = comp->info.compCompHnd->isValueClass(clsHnd); + + if (isValueClass) + { + comp->Metrics.NewBoxedValueClassHelperCalls++; + stackClsHnd = comp->info.compCompHnd->getTypeForBoxOnStack(clsHnd); + } + else + { + comp->Metrics.NewRefClassHelperCalls++; + } + + if (!CanAllocateLclVarOnStack(lclNum, clsHnd, allocType, 0, nullptr, &onHeapReason)) + { + // reason set by the call + canStack = false; + } + else if (stackClsHnd == NO_CLASS_HANDLE) + { + assert(isValueClass); + onHeapReason = "[no class handle for this boxed value class]"; + canStack = false; + } + else + { + JITDUMP("Allocating V%02u on the stack\n", lclNum); + canStack = true; + const unsigned int stackLclNum = + MorphAllocObjNodeIntoStackAlloc(data->AsAllocObj(), stackClsHnd, isValueClass, block, + stmt); + m_HeapLocalToStackLocalMap.AddOrUpdate(lclNum, stackLclNum); + + if (isValueClass) + { + comp->Metrics.StackAllocatedBoxedValueClasses++; + } + else + { + comp->Metrics.StackAllocatedRefClasses++; + } + } + } } - else + + if (canStack) { - JITDUMP("Allocating V%02u on the stack\n", lclNum); - canStack = true; - const unsigned int stackLclNum = - MorphAllocObjNodeIntoStackAlloc(asAllocObj, stackClsHnd, isValueClass, block, stmt); - m_HeapLocalToStackLocalMap.AddOrUpdate(lclNum, stackLclNum); // We keep the set of possibly-stack-pointing pointers as a superset of the set of - // definitely-stack-pointing pointers. All definitely-stack-pointing pointers are in both sets. + // definitely-stack-pointing pointers. All definitely-stack-pointing pointers are in both + // sets. MarkLclVarAsDefinitelyStackPointing(lclNum); MarkLclVarAsPossiblyStackPointing(lclNum); stmt->GetRootNode()->gtBashToNOP(); comp->optMethodFlags |= OMF_HAS_OBJSTACKALLOC; didStackAllocate = true; } - - if (canStack) - { - if (isValueClass) - { - comp->Metrics.StackAllocatedBoxedValueClasses++; - } - else - { - comp->Metrics.StackAllocatedRefClasses++; - } - } else { assert(onHeapReason != nullptr); JITDUMP("Allocating V%02u on the heap: %s\n", lclNum, onHeapReason); - data = MorphAllocObjNodeIntoHelperCall(asAllocObj); - stmtExpr->AsLclVar()->Data() = data; - stmtExpr->AddAllEffectsFlags(data); + if (allocType == OAT_NEWOBJ) + { + data = MorphAllocObjNodeIntoHelperCall(data->AsAllocObj()); + stmtExpr->AsLclVar()->Data() = data; + stmtExpr->AddAllEffectsFlags(data); + } } } #ifdef DEBUG @@ -555,6 +671,107 @@ GenTree* ObjectAllocator::MorphAllocObjNodeIntoHelperCall(GenTreeAllocObj* alloc return helperCall; } +//------------------------------------------------------------------------ +// MorphNewArrNodeIntoStackAlloc: Morph a GT_CALL CORINFO_HELP_NEWARR_1_VC +// node into stack allocation. +// +// Arguments: +// newArr - GT_CALL that will be replaced by helper call. +// clsHndclsHnd - class representing the type of the array +// length - length of the array +// blockSize - size of the layout +// block - a basic block where newArr is +// stmt - a statement where newArr is +// +// Return Value: +// local num for the new stack allocated local +// +// Notes: +// This function can insert additional statements before stmt. +// +unsigned int ObjectAllocator::MorphNewArrNodeIntoStackAlloc(GenTreeCall* newArr, + CORINFO_CLASS_HANDLE clsHnd, + unsigned int length, + unsigned int blockSize, + BasicBlock* block, + Statement* stmt) +{ + assert(newArr != nullptr); + assert(m_AnalysisDone); + assert(clsHnd != NO_CLASS_HANDLE); + assert(newArr->IsHelperCall()); + assert(newArr->GetHelperNum() != CORINFO_HELP_NEWARR_1_MAYBEFROZEN); + + const bool shortLifetime = false; + const bool alignTo8 = newArr->GetHelperNum() == CORINFO_HELP_NEWARR_1_ALIGN8; + const unsigned int lclNum = comp->lvaGrabTemp(shortLifetime DEBUGARG("stack allocated array temp")); + + if (alignTo8) + { + blockSize = AlignUp(blockSize, 8); + } + + comp->lvaSetStruct(lclNum, comp->typGetBlkLayout(blockSize), /* unsafeValueClsCheck */ false); + + // Initialize the object memory if necessary. + bool bbInALoop = block->HasFlag(BBF_BACKWARD_JUMP); + bool bbIsReturn = block->KindIs(BBJ_RETURN); + LclVarDsc* const lclDsc = comp->lvaGetDesc(lclNum); + if (comp->fgVarNeedsExplicitZeroInit(lclNum, bbInALoop, bbIsReturn)) + { + //------------------------------------------------------------------------ + // STMTx (IL 0x... ???) + // * STORE_LCL_VAR struct + // \--* CNS_INT int 0 + //------------------------------------------------------------------------ + + GenTree* init = comp->gtNewStoreLclVarNode(lclNum, comp->gtNewIconNode(0)); + Statement* initStmt = comp->gtNewStmt(init); + + comp->fgInsertStmtBefore(block, stmt, initStmt); + } + else + { + JITDUMP("\nSuppressing zero-init for V%02u -- expect to zero in prolog\n", lclNum); + lclDsc->lvSuppressedZeroInit = 1; + comp->compSuppressedZeroInit = true; + } + +#ifndef TARGET_64BIT + lclDsc->lvStructDoubleAlign = alignTo8; +#endif + + // Initialize the vtable slot. + // + //------------------------------------------------------------------------ + // STMTx (IL 0x... ???) + // * STORE_LCL_FLD long + // \--* CNS_INT(h) long + //------------------------------------------------------------------------ + + // Initialize the method table pointer. + GenTree* init = comp->gtNewStoreLclFldNode(lclNum, TYP_I_IMPL, 0, newArr->gtArgs.GetArgByIndex(0)->GetNode()); + Statement* initStmt = comp->gtNewStmt(init); + + comp->fgInsertStmtBefore(block, stmt, initStmt); + + // Initialize the array length. + // + //------------------------------------------------------------------------ + // STMTx (IL 0x... ???) + // * STORE_LCL_FLD int + // \--* CNS_INT int + //------------------------------------------------------------------------ + + // Pass the total length of the array. + GenTree* len = comp->gtNewStoreLclFldNode(lclNum, TYP_INT, OFFSETOF__CORINFO_Array__length, + comp->gtNewIconNode(length, TYP_INT)); + Statement* lenStmt = comp->gtNewStmt(len); + comp->fgInsertStmtBefore(block, stmt, lenStmt); + + return lclNum; +} + //------------------------------------------------------------------------ // MorphAllocObjNodeIntoStackAlloc: Morph a GT_ALLOCOBJ node into stack // allocation. @@ -662,7 +879,7 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent GenTree* parent = parentStack->Top(parentIndex); keepChecking = false; - JITDUMP("... L%02u ... checking [%06u]\n", lclNum, comp->dspTreeID(parent)); + JITDUMP("... V%02u ... checking [%06u]\n", lclNum, comp->dspTreeID(parent)); switch (parent->OperGet()) { @@ -675,13 +892,24 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent const unsigned int srcLclNum = lclNum; AddConnGraphEdge(dstLclNum, srcLclNum); + + if (parent->TypeIs(TYP_REF, TYP_BYREF, TYP_I_IMPL, TYP_STRUCT)) + { + AddConnGraphEdge(srcLclNum, dstLclNum); + } + canLclVarEscapeViaParentStack = false; + break; } - break; case GT_EQ: case GT_NE: + case GT_LT: + case GT_GT: + case GT_LE: + case GT_GE: case GT_NULLCHECK: + case GT_ARR_LENGTH: canLclVarEscapeViaParentStack = false; break; @@ -696,8 +924,14 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent case GT_COLON: case GT_QMARK: case GT_ADD: + case GT_SUB: case GT_BOX: case GT_FIELD_ADDR: + case GT_INDEX_ADDR: + case GT_LCL_ADDR: + case GT_CAST: + case GT_BLK: + case GT_IND: // Check whether the local escapes via its grandparent. ++parentIndex; keepChecking = true; @@ -705,26 +939,95 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent case GT_STOREIND: case GT_STORE_BLK: - case GT_BLK: if (tree != parent->AsIndir()->Addr()) { - // TODO-ObjectStackAllocation: track stores to fields. + GenTree* target = parent->AsIndir()->Addr(); + switch (target->OperGet()) + { + case GT_FIELD_ADDR: + { + GenTree* fieldObj = target->AsFieldAddr()->GetFldObj(); + if (fieldObj != nullptr && fieldObj->IsAnyLocal()) + { + const unsigned int dstLclNum = fieldObj->AsLclVarCommon()->GetLclNum(); + const unsigned int srcLclNum = lclNum; + LclVarDsc* lclVarDsc = comp->lvaGetDesc(dstLclNum); + + // Escapes through 'this' pointer + if (!comp->info.compIsStatic && dstLclNum == comp->info.compThisArg) + { + break; + } + + // Add an edge to the connection graph. + AddConnGraphEdge(dstLclNum, srcLclNum); + BitVecOps::AddElemD(&m_bitVecTraits, m_IndirectRefStoredPointers, dstLclNum); + JITDUMP(" V%02u is indirect accessed via field at [%06u]\n", dstLclNum, + comp->dspTreeID(target)); + ++parentIndex; + keepChecking = true; + break; + } + break; + } + default: + { + if (target->IsAnyLocal()) + { + const unsigned int dstLclNum = target->AsLclVarCommon()->GetLclNum(); + const unsigned int srcLclNum = lclNum; + LclVarDsc* lclVarDsc = comp->lvaGetDesc(dstLclNum); + + BitVecOps::AddElemD(&m_bitVecTraits, m_IndirectRefStoredPointers, dstLclNum); + + // Escapes through 'this' pointer + if (dstLclNum == 0 && !comp->info.compIsStatic) + { + break; + } + + // Escapes through a byref parameter + if (lclVarDsc->lvIsParam && lclVarDsc->TypeGet() == TYP_BYREF) + { + break; + } + + // Add an edge to the connection graph. + AddConnGraphEdge(dstLclNum, srcLclNum); + JITDUMP(" V%02u is indirect accessed at [%06u]\n", dstLclNum, + comp->dspTreeID(target)); + canLclVarEscapeViaParentStack = false; + } + } + } break; } - FALLTHROUGH; - case GT_IND: + // Address of the field/ind is not taken so the local doesn't escape. canLclVarEscapeViaParentStack = false; break; case GT_CALL: { - GenTreeCall* asCall = parent->AsCall(); + GenTreeCall* const call = parent->AsCall(); - if (asCall->IsHelperCall()) + if (call->IsHelperCall()) { canLclVarEscapeViaParentStack = - !Compiler::s_helperCallProperties.IsNoEscape(comp->eeGetHelperNum(asCall->gtCallMethHnd)); + !Compiler::s_helperCallProperties.IsNoEscape(comp->eeGetHelperNum(call->gtCallMethHnd)); + } + else if (call->gtCallType == CT_USER_FUNC) + { + // Delegate invoke won't escape the delegate which is passed as "this" + // And gets expanded inline later. + // + if ((call->gtCallMoreFlags & GTF_CALL_M_DELEGATE_INV) != 0) + { + GenTree* const thisArg = call->gtArgs.GetThisArg()->GetNode(); + canLclVarEscapeViaParentStack = thisArg != tree; + ++parentIndex; + keepChecking = true; + } } break; } @@ -777,7 +1080,12 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p case GT_EQ: case GT_NE: + case GT_LT: + case GT_GT: + case GT_LE: + case GT_GE: case GT_NULLCHECK: + case GT_ARR_LENGTH: break; case GT_COMMA: @@ -789,7 +1097,10 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p FALLTHROUGH; case GT_QMARK: case GT_ADD: + case GT_SUB: case GT_FIELD_ADDR: + case GT_INDEX_ADDR: + case GT_LCL_ADDR: if (parent->TypeGet() == TYP_REF) { parent->ChangeType(newType); @@ -827,17 +1138,25 @@ void ObjectAllocator::UpdateAncestorTypes(GenTree* tree, ArrayStack* p case GT_STOREIND: case GT_STORE_BLK: case GT_BLK: - assert(tree == parent->AsIndir()->Addr()); - - // The new target could be *not* on the heap. - parent->gtFlags &= ~GTF_IND_TGT_HEAP; + if (tree == parent->AsIndir()->Addr()) + { + // The new target could be *not* on the heap. + parent->gtFlags &= ~GTF_IND_TGT_HEAP; - if (newType != TYP_BYREF) + if (newType != TYP_BYREF) + { + // This indicates that a write barrier is not needed when writing + // to this field/indirection since the address is not pointing to the heap. + // It's either null or points to inside a stack-allocated object. + parent->gtFlags |= GTF_IND_TGT_NOT_HEAP; + } + } + else { - // This indicates that a write barrier is not needed when writing - // to this field/indirection since the address is not pointing to the heap. - // It's either null or points to inside a stack-allocated object. - parent->gtFlags |= GTF_IND_TGT_NOT_HEAP; + parent->ChangeType(newType); + + ++parentIndex; + keepChecking = true; } break; @@ -898,9 +1217,6 @@ void ObjectAllocator::RewriteUses() if ((lclNum < BitVecTraits::GetSize(&m_allocator->m_bitVecTraits)) && m_allocator->MayLclVarPointToStack(lclNum)) { - // Analysis does not handle indirect access to pointer locals. - assert(tree->OperIsScalarLocal()); - var_types newType; if (m_allocator->m_HeapLocalToStackLocalMap.TryGetValue(lclNum, &newLclNum)) { @@ -912,7 +1228,7 @@ void ObjectAllocator::RewriteUses() else { newType = m_allocator->DoesLclVarPointToStack(lclNum) ? TYP_I_IMPL : TYP_BYREF; - if (tree->TypeGet() == TYP_REF) + if (tree->TypeIs(TYP_REF, TYP_I_IMPL)) { tree->ChangeType(newType); } @@ -920,11 +1236,17 @@ void ObjectAllocator::RewriteUses() if (lclVarDsc->lvType != newType) { - JITDUMP("changing the type of V%02u from %s to %s\n", lclNum, varTypeName(lclVarDsc->lvType), + JITDUMP("Changing the type of V%02u from %s to %s\n", lclNum, varTypeName(lclVarDsc->lvType), varTypeName(newType)); lclVarDsc->lvType = newType; } m_allocator->UpdateAncestorTypes(tree, &m_ancestors, newType); + + if (newLclNum != BAD_VAR_NUM) + { + JITDUMP("Update V%02u to V%02u from use [%06u]\n", lclNum, newLclNum, m_compiler->dspTreeID(tree)); + DISPTREE(tree); + } } return Compiler::fgWalkResult::WALK_CONTINUE; diff --git a/src/coreclr/jit/objectalloc.h b/src/coreclr/jit/objectalloc.h index 8a873be8b34ad..4caa4c198b41d 100644 --- a/src/coreclr/jit/objectalloc.h +++ b/src/coreclr/jit/objectalloc.h @@ -22,6 +22,12 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX class ObjectAllocator final : public Phase { typedef SmallHashTable LocalToLocalMap; + enum ObjectAllocationType + { + OAT_NONE, + OAT_NEWOBJ, + OAT_NEWARR + }; //=============================================================================== // Data members @@ -29,6 +35,7 @@ class ObjectAllocator final : public Phase bool m_AnalysisDone; BitVecTraits m_bitVecTraits; BitVec m_EscapingPointers; + BitVec m_IndirectRefStoredPointers; // We keep the set of possibly-stack-pointing pointers as a superset of the set of // definitely-stack-pointing pointers. All definitely-stack-pointing pointers are in both sets. BitVec m_PossiblyStackPointingPointers; @@ -47,7 +54,12 @@ class ObjectAllocator final : public Phase virtual PhaseStatus DoPhase() override; private: - bool CanAllocateLclVarOnStack(unsigned int lclNum, CORINFO_CLASS_HANDLE clsHnd, const char** reason); + bool CanAllocateLclVarOnStack(unsigned int lclNum, + CORINFO_CLASS_HANDLE clsHnd, + ObjectAllocationType allocType, + ssize_t length, + unsigned int* blockSize, + const char** reason); bool CanLclVarEscape(unsigned int lclNum); void MarkLclVarAsPossiblyStackPointing(unsigned int lclNum); void MarkLclVarAsDefinitelyStackPointing(unsigned int lclNum); @@ -64,6 +76,12 @@ class ObjectAllocator final : public Phase GenTree* MorphAllocObjNodeIntoHelperCall(GenTreeAllocObj* allocObj); unsigned int MorphAllocObjNodeIntoStackAlloc( GenTreeAllocObj* allocObj, CORINFO_CLASS_HANDLE clsHnd, bool isValueClass, BasicBlock* block, Statement* stmt); + unsigned int MorphNewArrNodeIntoStackAlloc(GenTreeCall* newArr, + CORINFO_CLASS_HANDLE clsHnd, + unsigned int length, + unsigned int blockSize, + BasicBlock* block, + Statement* stmt); struct BuildConnGraphVisitorCallbackData; bool CanLclVarEscapeViaParentStack(ArrayStack* parentStack, unsigned int lclNum); void UpdateAncestorTypes(GenTree* tree, ArrayStack* parentStack, var_types newType); @@ -83,6 +101,7 @@ inline ObjectAllocator::ObjectAllocator(Compiler* comp) m_EscapingPointers = BitVecOps::UninitVal(); m_PossiblyStackPointingPointers = BitVecOps::UninitVal(); m_DefinitelyStackPointingPointers = BitVecOps::UninitVal(); + m_IndirectRefStoredPointers = BitVecOps::MakeEmpty(&m_bitVecTraits); m_ConnGraphAdjacencyMatrix = nullptr; } @@ -110,61 +129,104 @@ inline void ObjectAllocator::EnableObjectStackAllocation() // allocated on the stack. // // Arguments: -// lclNum - Local variable number -// clsHnd - Class/struct handle of the variable class -// reason - [out, required] if result is false, reason why +// lclNum - Local variable number +// clsHnd - Class/struct handle of the variable class +// allocType - Type of allocation (newobj or newarr) +// length - Length of the array (for newarr) +// blockSize - [out, optional] exact size of the object +// reason - [out, required] if result is false, reason why // // Return Value: // Returns true iff local variable can be allocated on the stack. // inline bool ObjectAllocator::CanAllocateLclVarOnStack(unsigned int lclNum, CORINFO_CLASS_HANDLE clsHnd, + ObjectAllocationType allocType, + ssize_t length, + unsigned int* blockSize, const char** reason) { assert(m_AnalysisDone); bool enableBoxedValueClasses = true; bool enableRefClasses = true; + bool enableArrays = true; *reason = "[ok]"; #ifdef DEBUG enableBoxedValueClasses = (JitConfig.JitObjectStackAllocationBoxedValueClass() != 0); enableRefClasses = (JitConfig.JitObjectStackAllocationRefClass() != 0); + enableArrays = (JitConfig.JitObjectStackAllocationArray() != 0); #endif unsigned int classSize = 0; - if (comp->info.compCompHnd->isValueClass(clsHnd)) + if (allocType == OAT_NEWARR) { - if (!enableBoxedValueClasses) + if (!enableArrays) { *reason = "[disabled by config]"; return false; } - if (comp->info.compCompHnd->getTypeForBoxOnStack(clsHnd) == NO_CLASS_HANDLE) + if (length < 0 || !FitsIn(length)) { - *reason = "[no boxed type available]"; + *reason = "[invalid array length]"; return false; } - classSize = comp->info.compCompHnd->getClassSize(clsHnd); - } - else - { - if (!enableRefClasses) + CORINFO_CLASS_HANDLE elemClsHnd = NO_CLASS_HANDLE; + CorInfoType corType = comp->info.compCompHnd->getChildType(clsHnd, &elemClsHnd); + var_types type = JITtype2varType(corType); + ClassLayout* elemLayout = type == TYP_STRUCT ? comp->typGetObjLayout(elemClsHnd) : nullptr; + if (varTypeIsGC(type) || ((elemLayout != nullptr) && elemLayout->HasGCPtr())) { - *reason = "[disabled by config]"; + *reason = "[array contains gc refs]"; return false; } - if (!comp->info.compCompHnd->canAllocateOnStack(clsHnd)) + unsigned elemSize = elemLayout != nullptr ? elemLayout->GetSize() : genTypeSize(type); + classSize = (unsigned int)OFFSETOF__CORINFO_Array__data + elemSize * static_cast(length); + } + else if (allocType == OAT_NEWOBJ) + { + if (comp->info.compCompHnd->isValueClass(clsHnd)) { - *reason = "[runtime disallows]"; - return false; + if (!enableBoxedValueClasses) + { + *reason = "[disabled by config]"; + return false; + } + + if (comp->info.compCompHnd->getTypeForBoxOnStack(clsHnd) == NO_CLASS_HANDLE) + { + *reason = "[no boxed type available]"; + return false; + } + + classSize = comp->info.compCompHnd->getClassSize(clsHnd); + } + else + { + if (!enableRefClasses) + { + *reason = "[disabled by config]"; + return false; + } + + if (!comp->info.compCompHnd->canAllocateOnStack(clsHnd)) + { + *reason = "[runtime disallows]"; + return false; + } + + classSize = comp->info.compCompHnd->getHeapClassSize(clsHnd); } - - classSize = comp->info.compCompHnd->getHeapClassSize(clsHnd); + } + else + { + assert(!"Unexpected allocation type"); + return false; } if (classSize > s_StackAllocMaxSize) @@ -181,6 +243,11 @@ inline bool ObjectAllocator::CanAllocateLclVarOnStack(unsigned int lclNu return false; } + if (blockSize != nullptr) + { + *blockSize = (unsigned int)classSize; + } + return true; } diff --git a/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs b/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs index 33303daf7a07b..07114d7ebd9b7 100644 --- a/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs +++ b/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs @@ -156,6 +156,8 @@ public static int TestEntryPoint() // Stack allocation of boxed structs is now enabled CallTestAndVerifyAllocation(BoxSimpleStructAndAddFields, 12, expectedAllocationKind); + CallTestAndVerifyAllocation(AllocateArrayWithNonGCElements, 84, expectedAllocationKind); + // The remaining tests currently never allocate on the stack if (expectedAllocationKind == AllocationKind.Stack) { expectedAllocationKind = AllocationKind.Heap; @@ -167,6 +169,20 @@ public static int TestEntryPoint() // This test calls CORINFO_HELP_CHKCASTCLASS_SPECIAL CallTestAndVerifyAllocation(AllocateSimpleClassAndCast, 7, expectedAllocationKind); + CallTestAndVerifyAllocation(AllocateArrayWithNonGCElementsEscape, 42, expectedAllocationKind); + + // This test calls CORINFO_HELP_OVERFLOW + CallTestAndVerifyAllocation(AllocateArrayWithNonGCElementsOutOfRangeLeft, 0, expectedAllocationKind, true); + + // This test calls CORINFO_HELP_OVERFLOW + CallTestAndVerifyAllocation(AllocateArrayWithNonGCElementsOutOfRangeRight, 0, expectedAllocationKind, true); + + // This test calls CORINFO_HELP_ARTHEMIC_OVERFLOW + CallTestAndVerifyAllocation(AllocateNegativeLengthArrayWithNonGCElements, 0, expectedAllocationKind, true); + + // This test calls CORINFO_HELP_ARTHEMIC_OVERFLOW + CallTestAndVerifyAllocation(AllocateLongLengthArrayWithNonGCElements, 0, expectedAllocationKind, true); + return methodResult; } @@ -175,27 +191,38 @@ static bool GCStressEnabled() return Environment.GetEnvironmentVariable("DOTNET_GCStress") != null; } - static void CallTestAndVerifyAllocation(Test test, int expectedResult, AllocationKind expectedAllocationsKind) + static void CallTestAndVerifyAllocation(Test test, int expectedResult, AllocationKind expectedAllocationsKind, bool throws = false) { - long allocatedBytesBefore = GC.GetAllocatedBytesForCurrentThread(); - int testResult = test(); - long allocatedBytesAfter = GC.GetAllocatedBytesForCurrentThread(); string methodName = test.Method.Name; - - if (testResult != expectedResult) { - Console.WriteLine($"FAILURE ({methodName}): expected {expectedResult}, got {testResult}"); - methodResult = -1; - } - else if ((expectedAllocationsKind == AllocationKind.Stack) && (allocatedBytesBefore != allocatedBytesAfter)) { - Console.WriteLine($"FAILURE ({methodName}): unexpected allocation of {allocatedBytesAfter - allocatedBytesBefore} bytes"); - methodResult = -1; - } - else if ((expectedAllocationsKind == AllocationKind.Heap) && (allocatedBytesBefore == allocatedBytesAfter)) { - Console.WriteLine($"FAILURE ({methodName}): unexpected stack allocation"); - methodResult = -1; + try + { + long allocatedBytesBefore = GC.GetAllocatedBytesForCurrentThread(); + int testResult = test(); + long allocatedBytesAfter = GC.GetAllocatedBytesForCurrentThread(); + + if (testResult != expectedResult) { + Console.WriteLine($"FAILURE ({methodName}): expected {expectedResult}, got {testResult}"); + methodResult = -1; + } + else if ((expectedAllocationsKind == AllocationKind.Stack) && (allocatedBytesBefore != allocatedBytesAfter)) { + Console.WriteLine($"FAILURE ({methodName}): unexpected allocation of {allocatedBytesAfter - allocatedBytesBefore} bytes"); + methodResult = -1; + } + else if ((expectedAllocationsKind == AllocationKind.Heap) && (allocatedBytesBefore == allocatedBytesAfter)) { + Console.WriteLine($"FAILURE ({methodName}): unexpected stack allocation"); + methodResult = -1; + } + else { + Console.WriteLine($"SUCCESS ({methodName})"); + } } - else { - Console.WriteLine($"SUCCESS ({methodName})"); + catch { + if (throws) { + Console.WriteLine($"SUCCESS ({methodName})"); + } + else { + throw; + } } } @@ -339,6 +366,64 @@ static int AllocateClassWithGcFieldAndInt() return c.i; } + [MethodImpl(MethodImplOptions.NoInlining)] + static int AllocateArrayWithNonGCElements() + { + int[] array = new int[42]; + array[24] = 42; + GC.Collect(); + return array[24] + array.Length; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int AllocateArrayWithNonGCElementsEscape() + { + int[] array = new int[42]; + Use(ref array[24]); + GC.Collect(); + return array[24]; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int AllocateArrayWithNonGCElementsOutOfRangeRight() + { + int[] array = new int[42]; + array[43] = 42; + GC.Collect(); + return 1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int AllocateArrayWithNonGCElementsOutOfRangeLeft() + { + int[] array = new int[42]; + array[-1] = 42; + GC.Collect(); + return 1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int AllocateNegativeLengthArrayWithNonGCElements() + { + int[] array = new int["".Length - 2]; + GC.Collect(); + return 1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int AllocateLongLengthArrayWithNonGCElements() + { + int[] array = new int[long.MaxValue]; + GC.Collect(); + return 1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Use(ref int v) + { + v = 42; + } + [MethodImpl(MethodImplOptions.NoInlining)] private static void ZeroAllocTest() {