diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 5aab07895a7b85..930a9344706044 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -5128,6 +5128,9 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl // Expand thread local access DoPhase(this, PHASE_EXPAND_TLS, &Compiler::fgExpandThreadLocalAccess); + // Expand stack allocated arrays + DoPhase(this, PHASE_EXPAND_STACK_ARR, &Compiler::fgExpandStackArrayAllocations); + // Insert GC Polls DoPhase(this, PHASE_INSERT_GC_POLLS, &Compiler::fgInsertGCPolls); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 0e3986969c161e..7ef1c97e943237 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -691,8 +691,8 @@ class LclVarDsc unsigned char lvSingleDefDisqualifyReason = 'H'; #endif - unsigned char lvAllDefsAreNoGc : 1; // For pinned locals: true if all defs of this local are no-gc - unsigned char lvStackAllocatedBox : 1; // Local is a stack allocated box + unsigned char lvAllDefsAreNoGc : 1; // For pinned locals: true if all defs of this local are no-gc + unsigned char lvStackAllocatedObject : 1; // Local is a stack allocated object (class, box, array, ...) #if FEATURE_MULTIREG_ARGS regNumber lvRegNumForSlot(unsigned slotNum) @@ -807,9 +807,9 @@ class LclVarDsc return lvIsMultiRegArg || lvIsMultiRegRet; } - bool IsStackAllocatedBox() const + bool IsStackAllocatedObject() const { - return lvStackAllocatedBox; + return lvStackAllocatedObject; } #if defined(DEBUG) @@ -6106,6 +6106,9 @@ class Compiler PhaseStatus fgExpandStaticInit(); bool fgExpandStaticInitForCall(BasicBlock** pBlock, Statement* stmt, GenTreeCall* call); + PhaseStatus fgExpandStackArrayAllocations(); + bool fgExpandStackArrayAllocation(BasicBlock* pBlock, Statement* stmt, GenTreeCall* call); + PhaseStatus fgVNBasedIntrinsicExpansion(); bool fgVNBasedIntrinsicExpansionForCall(BasicBlock** pBlock, Statement* stmt, GenTreeCall* call); bool fgVNBasedIntrinsicExpansionForCall_ReadUtf8(BasicBlock** pBlock, Statement* stmt, GenTreeCall* call); @@ -7537,6 +7540,7 @@ class Compiler #define OMF_HAS_SPECIAL_INTRINSICS 0x00020000 // Method contains special intrinsics expanded in late phases #define OMF_HAS_RECURSIVE_TAILCALL 0x00040000 // Method contains recursive tail call #define OMF_HAS_EXPANDABLE_CAST 0x00080000 // Method contains casts eligible for late expansion +#define OMF_HAS_STACK_ARRAY 0x00100000 // Method contains stack allocated arrays // clang-format on @@ -7627,6 +7631,16 @@ class Compiler optMethodFlags |= OMF_HAS_RECURSIVE_TAILCALL; } + bool doesMethodHaveStackAllocatedArray() + { + return (optMethodFlags & OMF_HAS_STACK_ARRAY) != 0; + } + + void setMethodHasStackAllocatedArray() + { + optMethodFlags |= OMF_HAS_STACK_ARRAY; + } + void pickGDV(GenTreeCall* call, IL_OFFSET ilOffset, bool isInterface, diff --git a/src/coreclr/jit/compphases.h b/src/coreclr/jit/compphases.h index 25811944345743..94eee3ba9bf5cd 100644 --- a/src/coreclr/jit/compphases.h +++ b/src/coreclr/jit/compphases.h @@ -109,6 +109,7 @@ CompPhaseNameMacro(PHASE_EXPAND_RTLOOKUPS, "Expand runtime lookups", CompPhaseNameMacro(PHASE_EXPAND_STATIC_INIT, "Expand static init", false, -1, true) CompPhaseNameMacro(PHASE_EXPAND_CASTS, "Expand casts", false, -1, true) CompPhaseNameMacro(PHASE_EXPAND_TLS, "Expand TLS access", false, -1, true) +CompPhaseNameMacro(PHASE_EXPAND_STACK_ARR, "Expand stack array allocation", false, -1, true) CompPhaseNameMacro(PHASE_INSERT_GC_POLLS, "Insert GC Polls", false, -1, true) CompPhaseNameMacro(PHASE_CREATE_THROW_HELPERS, "Create throw helper blocks", false, -1, true) CompPhaseNameMacro(PHASE_DETERMINE_FIRST_COLD_BLOCK, "Determine first cold block", false, -1, true) diff --git a/src/coreclr/jit/fgdiagnostic.cpp b/src/coreclr/jit/fgdiagnostic.cpp index 7c5e7638ada5b2..811511217cca9d 100644 --- a/src/coreclr/jit/fgdiagnostic.cpp +++ b/src/coreclr/jit/fgdiagnostic.cpp @@ -955,6 +955,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.cpp b/src/coreclr/jit/gentree.cpp index ba5407c7d02886..344fb8d2c7749b 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -13192,6 +13192,8 @@ const char* Compiler::gtGetWellKnownArgNameForArgMsg(WellKnownArg arg) return "swift self"; case WellKnownArg::X86TailCallSpecialArg: return "tail call"; + case WellKnownArg::StackArrayLocal: + return "&lcl arr"; default: return nullptr; } @@ -19814,7 +19816,10 @@ void GenTreeArrAddr::ParseArrayAddress(Compiler* comp, GenTree** pArr, ValueNum* /* static */ void GenTreeArrAddr::ParseArrayAddressWork( GenTree* tree, Compiler* comp, target_ssize_t inputMul, GenTree** pArr, ValueNum* pInxVN, target_ssize_t* pOffset) { - if (tree->TypeIs(TYP_REF)) + ValueNum vn = comp->GetValueNumStore()->VNLiberalNormalValue(tree->gtVNPair); + VNFuncApp vnf; + + if (tree->TypeIs(TYP_REF) || comp->GetValueNumStore()->IsVNNewArr(vn, &vnf)) { // This must be the array pointer. assert(*pArr == nullptr); @@ -19917,7 +19922,7 @@ void GenTreeArrAddr::ParseArrayAddress(Compiler* comp, GenTree** pArr, ValueNum* // If we didn't return above, must be a contribution to the non-constant part of the index VN. // We don't get here for GT_CNS_INT, GT_ADD, or GT_SUB, or for GT_MUL by constant, or GT_LSH of // constant shift. Thus, the generated index VN does not include the parsed constant offset. - ValueNum vn = comp->GetValueNumStore()->VNLiberalNormalValue(tree->gtVNPair); + // if (inputMul != 1) { ValueNum mulVN = comp->GetValueNumStore()->VNForLongCon(inputMul); diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index d5b7b6066afe9b..6947bf173adcb4 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -3586,7 +3586,7 @@ class SsaNumInfo final } }; -// Common supertype of [STORE_]LCL_VAR, [STORE_]LCL_FLD, PHI_ARG, LCL_VAR_ADDR, LCL_FLD_ADDR. +// Common supertype of [STORE_]LCL_VAR, [STORE_]LCL_FLD, PHI_ARG, LCL_ADDR. // This inherits from UnOp because lclvar stores are unary. // struct GenTreeLclVarCommon : public GenTreeUnOp @@ -4227,6 +4227,7 @@ enum GenTreeCallFlags : unsigned int GTF_CALL_M_CAST_CAN_BE_EXPANDED = 0x04000000, // this cast (helper call) can be expanded if it's profitable. To be removed. GTF_CALL_M_CAST_OBJ_NONNULL = 0x08000000, // if we expand this specific cast we don't need to check the input object for null // NOTE: if needed, this flag can be removed, and we can introduce new _NONNUL cast helpers + GTF_CALL_M_STACK_ARRAY = 0x10000000, // this call is a new array helper for a stack allocated array. }; inline constexpr GenTreeCallFlags operator ~(GenTreeCallFlags a) @@ -4566,6 +4567,7 @@ enum class WellKnownArg : unsigned SwiftError, SwiftSelf, X86TailCallSpecialArg, + StackArrayLocal, }; #ifdef DEBUG @@ -7586,12 +7588,12 @@ 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(addr->TypeIs(TYP_BYREF, TYP_I_IMPL)); assert(((elemType == TYP_STRUCT) && (elemClassHandle != NO_CLASS_HANDLE)) || (elemClassHandle == NO_CLASS_HANDLE)); } diff --git a/src/coreclr/jit/helperexpansion.cpp b/src/coreclr/jit/helperexpansion.cpp index 56fd853acb0d60..95da55c7b9576f 100644 --- a/src/coreclr/jit/helperexpansion.cpp +++ b/src/coreclr/jit/helperexpansion.cpp @@ -2712,3 +2712,158 @@ bool Compiler::fgLateCastExpansionForCall(BasicBlock** pBlock, Statement* stmt, return true; } + +//------------------------------------------------------------------------------ +// fgExpandStackArrayAllocations : expand "new helpers" for stack arrays +// +// Returns: +// PhaseStatus indicating what, if anything, was changed. +// +PhaseStatus Compiler::fgExpandStackArrayAllocations() +{ + PhaseStatus result = PhaseStatus::MODIFIED_NOTHING; + + if (!doesMethodHaveStackAllocatedArray()) + { + // The method being compiled doesn't have any stack allocated arrays. + return result; + } + + // Find allocation sites, and transform them into initializations of the + // array method table and length, and replace the allocation call with + // the address of the local array. + // + bool modified = false; + + for (BasicBlock* const block : Blocks()) + { + for (Statement* const stmt : block->Statements()) + { + if ((stmt->GetRootNode()->gtFlags & GTF_CALL) == 0) + { + continue; + } + + for (GenTree* const tree : stmt->TreeList()) + { + if (!tree->IsCall()) + { + continue; + } + + if (fgExpandStackArrayAllocation(block, stmt, tree->AsCall())) + { + // If we expand, we split the statement's tree + // so will be done with this statment. + // + modified = true; + break; + } + } + } + } + + // we cant assert(modified) here as array allocation sites may + // have been unreachable or dead-coded. + // + return modified ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; +} + +//------------------------------------------------------------------------------ +// fgExpandStackArrayAllocation: expand new array helpers for stack allocated arrays +// +// Arguments: +// block - block containing the helper call to expand +// stmt - Statement containing the helper call +// call - The helper call +// +// Returns: +// true if a runtime lookup was found and expanded. +// +bool Compiler::fgExpandStackArrayAllocation(BasicBlock* block, Statement* stmt, GenTreeCall* call) +{ + if (!call->IsHelperCall()) + { + return false; + } + + const CorInfoHelpFunc helper = eeGetHelperNum(call->gtCallMethHnd); + int lengthArgIndex = -1; + + switch (helper) + { + case CORINFO_HELP_NEWARR_1_DIRECT: + case CORINFO_HELP_NEWARR_1_VC: + case CORINFO_HELP_NEWARR_1_OBJ: + case CORINFO_HELP_NEWARR_1_ALIGN8: + lengthArgIndex = 1; + break; + + case CORINFO_HELP_READYTORUN_NEWARR_1: + lengthArgIndex = 0; + break; + + default: + return false; + } + + // If this is a local array, the new helper will have an arg for the array's address + // + CallArg* const stackLocalAddressArg = call->gtArgs.FindWellKnownArg(WellKnownArg::StackArrayLocal); + + if (stackLocalAddressArg == nullptr) + { + return false; + } + + JITDUMP("Expanding new array helper for stack allocated array at [%06d] in " FMT_BB ":\n", dspTreeID(call), + block->bbNum); + DISPTREE(call); + JITDUMP("\n"); + + Statement* newStmt = nullptr; + GenTree** callUse = nullptr; + bool split = gtSplitTree(block, stmt, call, &newStmt, &callUse); + + if (split) + { + while ((newStmt != nullptr) && (newStmt != stmt)) + { + fgMorphStmtBlockOps(block, newStmt); + newStmt = newStmt->GetNextStmt(); + } + } + + GenTree* const stackLocalAddress = stackLocalAddressArg->GetNode(); + + // Initialize the array method table pointer. + // + CORINFO_CLASS_HANDLE arrayHnd = (CORINFO_CLASS_HANDLE)call->compileTimeHelperArgumentHandle; + + GenTree* const mt = gtNewIconEmbClsHndNode(arrayHnd); + GenTree* const mtStore = gtNewStoreValueNode(TYP_I_IMPL, stackLocalAddress, mt); + Statement* const mtStmt = fgNewStmtFromTree(mtStore); + + fgInsertStmtBefore(block, stmt, mtStmt); + + // Initialize the array length. + // + GenTree* const lengthArg = call->gtArgs.GetArgByIndex(lengthArgIndex)->GetNode(); + GenTree* const lengthArgInt = fgOptimizeCast(gtNewCastNode(TYP_INT, lengthArg, false, TYP_INT)); + GenTree* const lengthAddress = gtNewOperNode(GT_ADD, TYP_I_IMPL, gtCloneExpr(stackLocalAddress), + gtNewIconNode(OFFSETOF__CORINFO_Array__length, TYP_I_IMPL)); + GenTree* const lengthStore = gtNewStoreValueNode(TYP_INT, lengthAddress, lengthArgInt); + Statement* const lenStmt = fgNewStmtFromTree(lengthStore); + + fgInsertStmtBefore(block, stmt, lenStmt); + + // Replace call with local address + // + *callUse = gtCloneExpr(stackLocalAddress); + DEBUG_DESTROY_NODE(call); + + fgMorphStmtBlockOps(block, stmt); + gtUpdateStmtSideEffects(stmt); + + return true; +} diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 7b82ac1afc22cb..134d5ffc99ca00 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -668,6 +668,8 @@ CONFIG_STRING(JitObjectStackAllocationRange, "JitObjectStackAllocationRange") RELEASE_CONFIG_INTEGER(JitObjectStackAllocation, "JitObjectStackAllocation", 1) RELEASE_CONFIG_INTEGER(JitObjectStackAllocationRefClass, "JitObjectStackAllocationRefClass", 1) RELEASE_CONFIG_INTEGER(JitObjectStackAllocationBoxedValueClass, "JitObjectStackAllocationBoxedValueClass", 1) +RELEASE_CONFIG_INTEGER(JitObjectStackAllocationArray, "JitObjectStackAllocationArray", 1) +RELEASE_CONFIG_INTEGER(JitObjectStackAllocationSize, "JitObjectStackAllocationSize", 528) RELEASE_CONFIG_INTEGER(JitEECallTimingInfo, "JitEECallTimingInfo", 0) diff --git a/src/coreclr/jit/jitmetadatalist.h b/src/coreclr/jit/jitmetadatalist.h index db0f66f3f2c97e..1918c5ade2b3ed 100644 --- a/src/coreclr/jit/jitmetadatalist.h +++ b/src/coreclr/jit/jitmetadatalist.h @@ -86,6 +86,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) JITMETADATAMETRIC(LocalAssertionCount, int, 0) JITMETADATAMETRIC(LocalAssertionOverflow, int, 0) JITMETADATAMETRIC(MorphTrackedLocals, int, 0) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 5e41f5cea8ee3c..8de5ff872cae21 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -2503,9 +2503,9 @@ bool Compiler::StructPromotionHelper::CanPromoteStructVar(unsigned lclNum) return false; } - if (varDsc->lvStackAllocatedBox) + if (varDsc->lvStackAllocatedObject) { - JITDUMP(" struct promotion of V%02u is disabled because it is a stack allocated box\n", lclNum); + JITDUMP(" struct promotion of V%02u is disabled because it is a stack allocated object\n", lclNum); return false; } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 8b2269bbd677de..9b976c927d19ec 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -780,6 +780,8 @@ const char* getWellKnownArgName(WellKnownArg arg) return "SwiftSelf"; case WellKnownArg::X86TailCallSpecialArg: return "X86TailCallSpecialArg"; + case WellKnownArg::StackArrayLocal: + return "StackArrayLocal"; } return "N/A"; @@ -3633,7 +3635,7 @@ GenTree* Compiler::fgMorphIndexAddr(GenTreeIndexAddr* indexAddr) } if (((index->gtFlags & (GTF_ASG | GTF_CALL | GTF_GLOB_REF)) != 0) || - gtComplexityExceeds(index, MAX_ARR_COMPLEXITY) || index->OperIs(GT_LCL_FLD) || + gtComplexityExceeds(index, MAX_INDEX_COMPLEXITY) || index->OperIs(GT_LCL_FLD) || (index->OperIs(GT_LCL_VAR) && lvaIsLocalImplicitlyAccessedByRef(index->AsLclVar()->GetLclNum()))) { unsigned indexTmpNum = lvaGrabTemp(true DEBUGARG("index expr")); @@ -5137,6 +5139,11 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call) assert(lvaIsImplicitByRefLocal(lvaTable[varDsc->lvFieldLclStart].lvParentLcl)); assert(fgGlobalMorph); } + else if (varDsc->IsStackAllocatedObject()) + { + // Stack allocated objects currently cannot be passed to callees + // so won't be live at tail call sites. + } #if FEATURE_FIXED_OUT_ARGS else if (varNum == lvaOutgoingArgSpaceVar) { diff --git a/src/coreclr/jit/objectalloc.cpp b/src/coreclr/jit/objectalloc.cpp index 29fa2bce242768..8a18a39015c74e 100644 --- a/src/coreclr/jit/objectalloc.cpp +++ b/src/coreclr/jit/objectalloc.cpp @@ -30,9 +30,17 @@ 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"); + comp->fgInvalidateDfsTree(); + return PhaseStatus::MODIFIED_NOTHING; + } + + // If optimizations are disabled and there are no newobjs, we don't need to morph anything. + if (comp->opts.OptimizationDisabled() && (comp->optMethodFlags & OMF_HAS_NEWOBJ) == 0) + { + JITDUMP("optimizations are disabled and there are no newobjs; punting\n"); comp->fgInvalidateDfsTree(); return PhaseStatus::MODIFIED_NOTHING; } @@ -388,20 +396,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)) { @@ -409,38 +417,39 @@ bool ObjectAllocator::MorphAllocObjNodes() if (data->OperGet() == GT_ALLOCOBJ) { - canonicalAllocObjFound = true; + allocType = OAT_NEWOBJ; + } + else if (!comp->opts.IsReadyToRun() && 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.CountUserArgs() == 2) && + data->AsCall()->gtArgs.GetUserArgByIndex(1)->GetNode()->IsCnsIntOrI()) + { + allocType = OAT_NEWARR; + } + break; + } + + 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; + bool bashCall = 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. // @@ -454,51 +463,158 @@ 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); + + // R2R not yet supported + // + assert(!comp->opts.IsReadyToRun()); + + //------------------------------------------------------------------------ + // 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* const len = data->AsCall()->gtArgs.GetUserArgByIndex(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); + + // Note we do not want to rewrite uses of the array temp, so we + // do not update m_HeapLocalToStackLocalMap. + // + 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++; + } + + bashCall = true; + } + } } - 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 + if (bashCall) { - comp->Metrics.StackAllocatedRefClasses++; + stmt->GetRootNode()->gtBashToNOP(); } + + comp->optMethodFlags |= OMF_HAS_OBJSTACKALLOC; + didStackAllocate = true; } 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 @@ -566,6 +682,95 @@ GenTree* ObjectAllocator::MorphAllocObjNodeIntoHelperCall(GenTreeAllocObj* alloc return helperCall; } +//------------------------------------------------------------------------ +// MorphNewArrNodeIntoStackAlloc: Morph a newarray helper call node into stack allocation. +// +// Arguments: +// newArr - GT_CALL that will be replaced by helper call. +// clsHnd - 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")); + LclVarDsc* const lclDsc = comp->lvaGetDesc(lclNum); + + if (alignTo8) + { + blockSize = AlignUp(blockSize, 8); + } + + comp->lvaSetStruct(lclNum, comp->typGetBlkLayout(blockSize), /* unsafeValueClsCheck */ false); + lclDsc->lvStackAllocatedObject = true; + + // Initialize the object memory if necessary. + bool bbInALoop = block->HasFlag(BBF_BACKWARD_JUMP); + bool bbIsReturn = block->KindIs(BBJ_RETURN); + 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 + + // Mark the newarr call as being "on stack", and add the address + // of the stack local as an argument + // + GenTree* const stackLocalAddr = comp->gtNewLclAddrNode(lclNum, 0); + newArr->gtArgs.PushBack(comp, NewCallArg::Primitive(stackLocalAddr).WellKnown(WellKnownArg::StackArrayLocal)); + newArr->gtCallMoreFlags |= GTF_CALL_M_STACK_ARRAY; + + // Retype the call result as an unmanaged pointer + // + newArr->ChangeType(TYP_I_IMPL); + newArr->gtReturnType = TYP_I_IMPL; + + // Note that we have stack allocated arrays in this method + // + comp->setMethodHasStackAllocatedArray(); + + return lclNum; +} + //------------------------------------------------------------------------ // MorphAllocObjNodeIntoStackAlloc: Morph a GT_ALLOCOBJ node into stack // allocation. @@ -596,10 +801,10 @@ unsigned int ObjectAllocator::MorphAllocObjNodeIntoStackAlloc( comp->lvaSetStruct(lclNum, clsHnd, /* 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); - lclDsc->lvStackAllocatedBox = isValueClass; + bool bbInALoop = block->HasFlag(BBF_BACKWARD_JUMP); + bool bbIsReturn = block->KindIs(BBJ_RETURN); + LclVarDsc* const lclDsc = comp->lvaGetDesc(lclNum); + lclDsc->lvStackAllocatedObject = true; if (comp->fgVarNeedsExplicitZeroInit(lclNum, bbInALoop, bbIsReturn)) { //------------------------------------------------------------------------ @@ -740,7 +945,12 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent 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; @@ -755,6 +965,7 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent case GT_COLON: case GT_QMARK: case GT_ADD: + case GT_SUB: case GT_BOX: case GT_FIELD_ADDR: // Check whether the local escapes via its grandparent. @@ -762,6 +973,18 @@ bool ObjectAllocator::CanLclVarEscapeViaParentStack(ArrayStack* parent keepChecking = true; break; + case GT_INDEX_ADDR: + if (tree == parent->AsIndexAddr()->Index()) + { + // The index is not taken so the local doesn't escape. + canLclVarEscapeViaParentStack = false; + break; + } + // Check whether the local escapes via its grandparent. + ++parentIndex; + keepChecking = true; + break; + case GT_STOREIND: case GT_STORE_BLK: case GT_BLK: @@ -836,7 +1059,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: @@ -848,7 +1076,9 @@ 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: if (parent->TypeGet() == TYP_REF) { parent->ChangeType(newType); @@ -979,11 +1209,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 8a873be8b34ad7..35e704974969ac 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 @@ -35,6 +41,7 @@ class ObjectAllocator final : public Phase BitVec m_DefinitelyStackPointingPointers; LocalToLocalMap m_HeapLocalToStackLocalMap; BitSetShortLongRep* m_ConnGraphAdjacencyMatrix; + unsigned int m_StackAllocMaxSize; //=============================================================================== // Methods @@ -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,11 +76,15 @@ 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); - - static const unsigned int s_StackAllocMaxSize = 0x2000U; }; //=============================================================================== @@ -84,6 +100,8 @@ inline ObjectAllocator::ObjectAllocator(Compiler* comp) m_PossiblyStackPointingPointers = BitVecOps::UninitVal(); m_DefinitelyStackPointingPointers = BitVecOps::UninitVal(); m_ConnGraphAdjacencyMatrix = nullptr; + + m_StackAllocMaxSize = (unsigned)JitConfig.JitObjectStackAllocationSize(); } //------------------------------------------------------------------------ @@ -110,64 +128,119 @@ 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; + unsigned 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) || (length > CORINFO_Array_MaxLength)) { - *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)) + const unsigned elemSize = elemLayout != nullptr ? elemLayout->GetSize() : genTypeSize(type); + + ClrSafeInt totalSize(elemSize); + totalSize *= static_cast(length); + totalSize += static_cast(OFFSETOF__CORINFO_Array__data); + + if (totalSize.IsOverflow()) { - *reason = "[runtime disallows]"; + *reason = "[overflow array length]"; return false; } - classSize = comp->info.compCompHnd->getHeapClassSize(clsHnd); + classSize = totalSize.Value(); + } + else if (allocType == OAT_NEWOBJ) + { + if (comp->info.compCompHnd->isValueClass(clsHnd)) + { + 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); + } + } + else + { + assert(!"Unexpected allocation type"); + return false; } - if (classSize > s_StackAllocMaxSize) + if (classSize > m_StackAllocMaxSize) { *reason = "[too large]"; return false; @@ -181,6 +254,11 @@ inline bool ObjectAllocator::CanAllocateLclVarOnStack(unsigned int lclNu return false; } + if (blockSize != nullptr) + { + *blockSize = classSize; + } + return true; } diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 60ac86cf8e54e6..fd13668c405e8d 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -6245,7 +6245,19 @@ void Compiler::fgValueNumberArrayElemLoad(GenTree* loadTree, VNFuncApp* addrFunc ValueNum loadValueVN = vnStore->VNForLoad(VNK_Liberal, wholeElem, elemSize, loadType, offset, loadSize); loadTree->gtVNPair.SetLiberal(loadValueVN); - loadTree->gtVNPair.SetConservative(vnStore->VNForExpr(compCurBB, loadType)); + + // If this is a local array, there are no asyncronous modifications, so we can set the + // conservative VN to the liberal VN. + // + VNFuncApp arrFn; + if (vnStore->IsVNNewLocalArr(arrVN, &arrFn)) + { + loadTree->gtVNPair.SetConservative(loadValueVN); + } + else + { + loadTree->gtVNPair.SetConservative(vnStore->VNForExpr(compCurBB, loadType)); + } } //------------------------------------------------------------------------ @@ -7359,7 +7371,22 @@ bool ValueNumStore::IsVNNewArr(ValueNum vn, VNFuncApp* funcApp) bool result = false; if (GetVNFunc(vn, funcApp)) { - result = (funcApp->m_func == VNF_JitNewArr) || (funcApp->m_func == VNF_JitReadyToRunNewArr); + result = (funcApp->m_func == VNF_JitNewArr) || (funcApp->m_func == VNF_JitNewLclArr) || + (funcApp->m_func == VNF_JitReadyToRunNewArr) || (funcApp->m_func == VNF_JitReadyToRunNewLclArr); + } + return result; +} + +bool ValueNumStore::IsVNNewLocalArr(ValueNum vn, VNFuncApp* funcApp) +{ + if (vn == NoVN) + { + return false; + } + bool result = false; + if (GetVNFunc(vn, funcApp)) + { + result = (funcApp->m_func == VNF_JitNewLclArr) || (funcApp->m_func == VNF_JitReadyToRunNewLclArr); } return result; } @@ -13489,6 +13516,7 @@ void Compiler::fgValueNumberHelperCallFunc(GenTreeCall* call, VNFunc vnf, ValueN break; case VNF_JitNewArr: + case VNF_JitNewLclArr: { generateUniqueVN = true; ValueNumPair vnp1 = vnStore->VNPNormalPair(args->GetArgByIndex(1)->GetNode()->gtVNPair); @@ -13527,6 +13555,7 @@ void Compiler::fgValueNumberHelperCallFunc(GenTreeCall* call, VNFunc vnf, ValueN break; case VNF_JitReadyToRunNewArr: + case VNF_JitReadyToRunNewLclArr: { generateUniqueVN = true; ValueNumPair vnp1 = vnStore->VNPNormalPair(args->GetArgByIndex(0)->GetNode()->gtVNPair); @@ -14338,7 +14367,22 @@ bool Compiler::fgValueNumberHelperCall(GenTreeCall* call) } } + if (isAlloc && ((call->gtCallMoreFlags & GTF_CALL_M_STACK_ARRAY) != 0)) + { + if (vnf == VNF_JitNewArr) + { + vnf = VNF_JitNewLclArr; + // modHeap = false; + } + else if (vnf == VNF_JitReadyToRunNewArr) + { + vnf = VNF_JitReadyToRunNewLclArr; + // modHeap = false; + } + } + fgValueNumberHelperCallFunc(call, vnf, vnpExc); + return modHeap; } else diff --git a/src/coreclr/jit/valuenum.h b/src/coreclr/jit/valuenum.h index ec7b6fec04a5bf..f2ae7be7a4cabe 100644 --- a/src/coreclr/jit/valuenum.h +++ b/src/coreclr/jit/valuenum.h @@ -1150,6 +1150,9 @@ class ValueNumStore // Check if "vn" is "new [] (type handle, size)" bool IsVNNewArr(ValueNum vn, VNFuncApp* funcApp); + // Check if "vn" is "new [] (type handle, size) [stack allocated]" + bool IsVNNewLocalArr(ValueNum vn, VNFuncApp* funcApp); + // Check if "vn" IsVNNewArr and return false if arr size cannot be determined. bool TryGetNewArrSize(ValueNum vn, int* size); diff --git a/src/coreclr/jit/valuenumfuncs.h b/src/coreclr/jit/valuenumfuncs.h index 2db7c4ffa6c9b0..227f4f26b11e9b 100644 --- a/src/coreclr/jit/valuenumfuncs.h +++ b/src/coreclr/jit/valuenumfuncs.h @@ -149,9 +149,11 @@ ValueNumFuncDef(GetStaticAddrTLS, 1, false, true, false) ValueNumFuncDef(JitNew, 2, false, true, false) ValueNumFuncDef(JitNewArr, 3, false, true, false) +ValueNumFuncDef(JitNewLclArr, 3, false, true, false) ValueNumFuncDef(JitNewMdArr, 4, false, true, false) ValueNumFuncDef(JitReadyToRunNew, 2, false, true, false) ValueNumFuncDef(JitReadyToRunNewArr, 3, false, true, false) +ValueNumFuncDef(JitReadyToRunNewLclArr, 3, false, true, false) ValueNumFuncDef(Box, 3, false, true, false) ValueNumFuncDef(BoxNullable, 3, false, false, false) diff --git a/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs b/src/tests/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs index 33303daf7a07b2..07114d7ebd9b7c 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() {