diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e8d9e66cd91363..d6ba71a9826b11 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6586,7 +6586,7 @@ class Compiler #endif // FEATURE_SIMD GenTree* fgMorphIndexAddr(GenTreeIndexAddr* tree); GenTree* fgMorphExpandCast(GenTreeCast* tree); - GenTreeFieldList* fgMorphLclArgToFieldList(GenTreeLclVarCommon* lcl); + GenTreeFieldList* fgMorphLclToFieldList(GenTreeLclVar* lcl); GenTreeCall* fgMorphArgs(GenTreeCall* call); void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg); @@ -6661,7 +6661,7 @@ class Compiler GenTree* fgMorphCopyBlock(GenTree* tree); private: GenTree* fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optAssertionPropDone = nullptr); - void fgTryReplaceStructLocalWithField(GenTree* tree); + void fgTryReplaceStructLocalWithFields(GenTree** use); GenTree* fgMorphFinalizeIndir(GenTreeIndir* indir); GenTree* fgOptimizeCast(GenTreeCast* cast); GenTree* fgOptimizeCastOnStore(GenTree* store); diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index e01d9705eb0d4a..6af651b6860882 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -3095,6 +3095,19 @@ struct GenTreeOp : public GenTreeUnOp // then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant void CheckDivideByConstOptimized(Compiler* comp); + GenTree*& ReturnValueRef() + { + assert(OperIs(GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET)); +#ifdef SWIFT_SUPPORT + if (OperIs(GT_SWIFT_ERROR_RET)) + { + return gtOp2; + } +#endif // SWIFT_SUPPORT + + return gtOp1; + } + GenTree* GetReturnValue() const { assert(OperIs(GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET)); diff --git a/src/coreclr/jit/lclmorph.cpp b/src/coreclr/jit/lclmorph.cpp index 4d1b2697215324..df824216d819d3 100644 --- a/src/coreclr/jit/lclmorph.cpp +++ b/src/coreclr/jit/lclmorph.cpp @@ -1240,38 +1240,6 @@ class LocalAddressVisitor final : public GenTreeVisitor PopValue(); break; - case GT_RETURN: - if (TopValue(0).Node() != node) - { - assert(TopValue(1).Node() == node); - assert(TopValue(0).Node() == node->gtGetOp1()); - GenTreeUnOp* ret = node->AsUnOp(); - GenTree* retVal = ret->gtGetOp1(); - if (retVal->OperIs(GT_LCL_VAR)) - { - // TODO-1stClassStructs: this block is a temporary workaround to keep diffs small, - // having `doNotEnreg` affect block init and copy transformations that affect many methods. - // I have a change that introduces more precise and effective solution for that, but it would - // be merged separately. - GenTreeLclVar* lclVar = retVal->AsLclVar(); - unsigned lclNum = lclVar->GetLclNum(); - if (!m_compiler->compMethodReturnsMultiRegRetType() && - !m_compiler->lvaIsImplicitByRefLocal(lclVar->GetLclNum())) - { - LclVarDsc* varDsc = m_compiler->lvaGetDesc(lclNum); - if (varDsc->lvFieldCnt > 1) - { - m_compiler->lvaSetVarDoNotEnregister( - lclNum DEBUGARG(DoNotEnregisterReason::BlockOpRet)); - } - } - } - - EscapeValue(TopValue(0), node); - PopValue(); - } - break; - case GT_CALL: while (TopValue(0).Node() != node) { diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 71500b34a952a1..6eb70fb1e10b85 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -538,13 +538,14 @@ GenTree* Lowering::LowerNode(GenTree* node) case GT_CAST: { - GenTree* nextNode = LowerCast(node); -#if defined(TARGET_XARCH) - if (nextNode != nullptr) + if (!TryRemoveCast(node->AsCast())) { - return nextNode; + GenTree* nextNode = LowerCast(node); + if (nextNode != nullptr) + { + return nextNode; + } } -#endif // TARGET_XARCH } break; @@ -4752,20 +4753,7 @@ void Lowering::LowerRetFieldList(GenTreeOp* ret, GenTreeFieldList* fieldList) return; } - unsigned regIndex = 0; - for (GenTreeFieldList::Use& use : fieldList->Uses()) - { - var_types regType = retDesc.GetReturnRegType(regIndex); - if (varTypeUsesIntReg(regType) != varTypeUsesIntReg(use.GetNode())) - { - GenTree* bitcast = comp->gtNewOperNode(GT_BITCAST, regType, use.GetNode()); - BlockRange().InsertBefore(fieldList, bitcast); - use.SetNode(bitcast); - LowerNode(bitcast); - } - - regIndex++; - } + LowerFieldListToFieldListOfRegisters(fieldList); } //---------------------------------------------------------------------------------------------- @@ -4777,47 +4765,69 @@ void Lowering::LowerRetFieldList(GenTreeOp* ret, GenTreeFieldList* fieldList) // fieldList - The FIELD_LIST node // // Returns: -// True if the fields of the FIELD_LIST map cleanly to the ABI returned -// registers. Insertions of bitcasts may still be required. +// True if the fields of the FIELD_LIST are all direct insertions into the +// return registers. // bool Lowering::IsFieldListCompatibleWithReturn(GenTreeFieldList* fieldList) { JITDUMP("Checking if field list [%06u] is compatible with return ABI: ", Compiler::dspTreeID(fieldList)); const ReturnTypeDesc& retDesc = comp->compRetTypeDesc; unsigned numRetRegs = retDesc.GetReturnRegCount(); - unsigned regIndex = 0; - for (const GenTreeFieldList::Use& use : fieldList->Uses()) + GenTreeFieldList::Use* use = fieldList->Uses().GetHead(); + for (unsigned i = 0; i < numRetRegs; i++) { - if (regIndex >= numRetRegs) - { - JITDUMP("it is not; too many fields\n"); - return false; - } + unsigned regStart = retDesc.GetReturnFieldOffset(i); + var_types regType = retDesc.GetReturnRegType(i); + unsigned regEnd = regStart + genTypeSize(regType); - unsigned offset = retDesc.GetReturnFieldOffset(regIndex); - if (offset != use.GetOffset()) + // TODO-CQ: Could just create a 0 for this. + if (use == nullptr) { - JITDUMP("it is not; field %u register starts at offset %u, but field starts at offset %u\n", regIndex, - offset, use.GetOffset()); + JITDUMP("it is not; register %u has no corresponding field\n", i); return false; } - var_types fieldType = genActualType(use.GetNode()); - var_types regType = genActualType(retDesc.GetReturnRegType(regIndex)); - if (genTypeSize(fieldType) != genTypeSize(regType)) + do { - JITDUMP("it is not; field %u register has type %s but field has type %s\n", regIndex, varTypeName(regType), - varTypeName(fieldType)); - return false; - } + unsigned fieldStart = use->GetOffset(); - regIndex++; + if (fieldStart < regStart) + { + // Not fully contained in a register. + // TODO-CQ: Could just remove these fields if they don't partially overlap with the next register. + JITDUMP("it is not; field [%06u] starts before register %u\n", Compiler::dspTreeID(use->GetNode()), i); + return false; + } + + if (fieldStart >= regEnd) + { + break; + } + + unsigned fieldEnd = fieldStart + genTypeSize(use->GetType()); + if (fieldEnd > regEnd) + { + JITDUMP("it is not; field [%06u] ends after register %u\n", Compiler::dspTreeID(use->GetNode()), i); + return false; + } + + // float -> float insertions are not yet supported + if (varTypeUsesFloatReg(use->GetNode()) && varTypeUsesFloatReg(regType) && (fieldStart != regStart)) + { + JITDUMP("it is not; field [%06u] requires an insertion into register %u\n", + Compiler::dspTreeID(use->GetNode()), i); + return false; + } + + use = use->GetNext(); + } while (use != nullptr); } - if (regIndex != numRetRegs) + if (use != nullptr) { - JITDUMP("it is not; too few fields\n"); + // TODO-CQ: Could just remove these fields. + JITDUMP("it is not; field [%06u] corresponds to no register\n", Compiler::dspTreeID(use->GetNode())); return false; } @@ -4825,6 +4835,126 @@ bool Lowering::IsFieldListCompatibleWithReturn(GenTreeFieldList* fieldList) return true; } +//---------------------------------------------------------------------------------------------- +// LowerFieldListToFieldListOfRegisters: +// Lower the specified field list into one that is compatible with the return +// registers. +// +// Arguments: +// fieldList - The field list +// +void Lowering::LowerFieldListToFieldListOfRegisters(GenTreeFieldList* fieldList) +{ + const ReturnTypeDesc& retDesc = comp->compRetTypeDesc; + unsigned numRegs = retDesc.GetReturnRegCount(); + + GenTreeFieldList::Use* use = fieldList->Uses().GetHead(); + assert(fieldList->Uses().IsSorted()); + + for (unsigned i = 0; i < numRegs; i++) + { + unsigned regStart = retDesc.GetReturnFieldOffset(i); + var_types regType = genActualType(retDesc.GetReturnRegType(i)); + unsigned regEnd = regStart + genTypeSize(regType); + + GenTreeFieldList::Use* regEntry = use; + + assert(use != nullptr); + + GenTree* fieldListPrev = fieldList->gtPrev; + + do + { + unsigned fieldStart = use->GetOffset(); + + assert(fieldStart >= regStart); + + if (fieldStart >= regEnd) + { + break; + } + + var_types fieldType = use->GetType(); + GenTree* value = use->GetNode(); + + unsigned insertOffset = fieldStart - regStart; + GenTreeFieldList::Use* nextUse = use->GetNext(); + + // First ensure the value does not have upper bits set that + // interfere with the next field. + if ((nextUse != nullptr) && (nextUse->GetOffset() < regEnd) && + (fieldStart + genTypeSize(genActualType(fieldType)) > nextUse->GetOffset())) + { + assert(varTypeIsSmall(fieldType)); + // This value may interfere with the next field. Ensure that doesn't happen. + if (comp->fgCastNeeded(value, varTypeToUnsigned(fieldType))) + { + value = comp->gtNewCastNode(TYP_INT, value, true, varTypeToUnsigned(fieldType)); + BlockRange().InsertBefore(fieldList, value); + } + } + + // If this is a float -> int insertion, then we need the bitcast now. + if (varTypeUsesFloatReg(value) && varTypeUsesIntReg(regType)) + { + assert((genTypeSize(value) == 4) || (genTypeSize(value) == 8)); + var_types castType = genTypeSize(value) == 4 ? TYP_INT : TYP_LONG; + value = comp->gtNewBitCastNode(castType, value); + BlockRange().InsertBefore(fieldList, value); + } + + if (insertOffset + genTypeSize(fieldType) > genTypeSize(genActualType(value))) + { + value = comp->gtNewCastNode(TYP_LONG, value, true, TYP_LONG); + BlockRange().InsertBefore(fieldList, value); + } + + if (fieldStart != regStart) + { + GenTree* shiftAmount = comp->gtNewIconNode((ssize_t)insertOffset * BITS_PER_BYTE); + value = comp->gtNewOperNode(GT_LSH, genActualType(value), value, shiftAmount); + BlockRange().InsertBefore(fieldList, shiftAmount, value); + } + + if (regEntry != use) + { + GenTree* prevValue = regEntry->GetNode(); + if (genActualType(value) != genActualType(regEntry->GetNode())) + { + prevValue = comp->gtNewCastNode(TYP_LONG, prevValue, true, TYP_LONG); + BlockRange().InsertBefore(fieldList, prevValue); + regEntry->SetNode(prevValue); + } + + value = comp->gtNewOperNode(GT_OR, genActualType(value), prevValue, value); + BlockRange().InsertBefore(fieldList, value); + + // Remove this field from the FIELD_LIST. + regEntry->SetNext(use->GetNext()); + } + + regEntry->SetNode(value); + regEntry->SetType(genActualType(value)); + use = regEntry->GetNext(); + } while (use != nullptr); + + assert(regEntry != nullptr); + if (varTypeUsesIntReg(regEntry->GetNode()) != varTypeUsesIntReg(regType)) + { + GenTree* bitCast = comp->gtNewBitCastNode(regType, regEntry->GetNode()); + BlockRange().InsertBefore(fieldList, bitCast); + regEntry->SetNode(bitCast); + } + + if (fieldListPrev->gtNext != fieldList) + { + LowerRange(fieldListPrev->gtNext, fieldList->gtPrev); + } + } + + assert(use == nullptr); +} + //---------------------------------------------------------------------------------------------- // LowerStoreLocCommon: platform independent part of local var or field store lowering. // @@ -8792,6 +8922,45 @@ void Lowering::ContainCheckRet(GenTreeUnOp* ret) #endif // FEATURE_MULTIREG_RET } +//------------------------------------------------------------------------ +// TryRemoveCast: +// Try to remove a cast node by changing its operand. +// +// Arguments: +// node - Cast node +// +// Returns: +// True if the cast was removed. +// +bool Lowering::TryRemoveCast(GenTreeCast* node) +{ + if (comp->opts.OptimizationDisabled()) + { + return false; + } + + if (node->gtOverflow()) + { + return false; + } + + GenTree* op = node->CastOp(); + if (!op->OperIsConst()) + { + return false; + } + + GenTree* folded = comp->gtFoldExprConst(node); + assert(folded == node); + if (folded->OperIs(GT_CAST)) + { + return false; + } + + op->SetUnusedValue(); + return true; +} + //------------------------------------------------------------------------ // TryRemoveBitCast: // Try to remove a bitcast node by changing its operand. diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index e3025e9b9f75ed..f802c2620356a5 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -51,15 +51,23 @@ class Lowering final : public Phase // LowerRange handles new code that is introduced by or after Lowering. void LowerRange(LIR::ReadOnlyRange& range) { - for (GenTree* newNode : range) - { - LowerNode(newNode); - } + LowerRange(range.FirstNode(), range.LastNode()); } void LowerRange(GenTree* firstNode, GenTree* lastNode) { - LIR::ReadOnlyRange range(firstNode, lastNode); - LowerRange(range); + GenTree* cur = firstNode; + + while (true) + { + GenTree* next = LowerNode(cur); + if (cur == lastNode) + { + break; + } + + cur = next; + assert(cur != nullptr); + } } // ContainCheckRange handles new code that is introduced by or after Lowering, @@ -171,6 +179,7 @@ class Lowering final : public Phase void LowerRetSingleRegStructLclVar(GenTreeUnOp* ret); void LowerRetFieldList(GenTreeOp* ret, GenTreeFieldList* fieldList); bool IsFieldListCompatibleWithReturn(GenTreeFieldList* fieldList); + void LowerFieldListToFieldListOfRegisters(GenTreeFieldList* fieldList); void LowerCallStruct(GenTreeCall* call); void LowerStoreSingleRegCallStruct(GenTreeBlk* store); #if !defined(WINDOWS_AMD64_ABI) @@ -387,6 +396,7 @@ class Lowering final : public Phase void LowerPutArgStkOrSplit(GenTreePutArgStk* putArgNode); GenTree* LowerArrLength(GenTreeArrCommon* node); + bool TryRemoveCast(GenTreeCast* node); bool TryRemoveBitCast(GenTreeUnOp* node); #ifdef TARGET_XARCH diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index d0bdd858ef7f9d..3351f05d7ded0e 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -2279,7 +2279,7 @@ bool Compiler::fgTryMorphStructArg(CallArg* arg) // TODO-Arm-CQ: support decomposing "large" promoted structs into field lists. if (!isSplit) { - GenTreeFieldList* fieldList = fgMorphLclArgToFieldList(argNode->AsLclVar()); + GenTreeFieldList* fieldList = fgMorphLclToFieldList(argNode->AsLclVar()); // TODO-Cleanup: The containment/reg optionality for x86 is // conservative in the "no field list" case. #ifdef TARGET_X86 @@ -2321,9 +2321,9 @@ bool Compiler::fgTryMorphStructArg(CallArg* arg) if (argNode->OperIs(GT_LCL_VAR)) { - GenTreeLclVarCommon* lclNode = argNode->AsLclVarCommon(); - unsigned lclNum = lclNode->GetLclNum(); - LclVarDsc* varDsc = lvaGetDesc(lclNum); + GenTreeLclVar* lclNode = argNode->AsLclVar(); + unsigned lclNum = lclNode->GetLclNum(); + LclVarDsc* varDsc = lvaGetDesc(lclNum); if (!arg->AbiInfo.HasExactlyOneRegisterSegment()) { @@ -2365,7 +2365,7 @@ bool Compiler::fgTryMorphStructArg(CallArg* arg) if (fieldsMatch) { - newArg = fgMorphLclArgToFieldList(lclNode)->SoleFieldOrThis(); + newArg = fgMorphLclToFieldList(lclNode)->SoleFieldOrThis(); } } } @@ -2577,7 +2577,7 @@ bool Compiler::fgTryMorphStructArg(CallArg* arg) } //------------------------------------------------------------------------ -// fgMorphLclArgToFieldList: Morph a GT_LCL_VAR node to a GT_FIELD_LIST of its promoted fields +// fgMorphLclToFieldList: Morph a GT_LCL_VAR node to a GT_FIELD_LIST of its promoted fields // // Arguments: // lcl - The GT_LCL_VAR node we will transform @@ -2585,7 +2585,7 @@ bool Compiler::fgTryMorphStructArg(CallArg* arg) // Return value: // The new GT_FIELD_LIST that we have created. // -GenTreeFieldList* Compiler::fgMorphLclArgToFieldList(GenTreeLclVarCommon* lcl) +GenTreeFieldList* Compiler::fgMorphLclToFieldList(GenTreeLclVar* lcl) { LclVarDsc* varDsc = lvaGetDesc(lcl); assert(varDsc->lvPromoted); @@ -7458,7 +7458,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA case GT_RETURN: case GT_SWIFT_ERROR_RET: { - GenTree* retVal = tree->AsOp()->GetReturnValue(); + GenTree*& retVal = tree->AsOp()->ReturnValueRef(); // Apply some optimizations that change the type of the return. // These are not applicable when this is a merged return that will @@ -7470,7 +7470,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA retVal = fgMorphRetInd(tree->AsOp()); } - fgTryReplaceStructLocalWithField(retVal); + fgTryReplaceStructLocalWithFields(&retVal); } // normalize small integer return values @@ -7496,7 +7496,6 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA retVal->gtFlags |= (tree->gtFlags & GTF_COLON_COND); retVal = fgMorphTree(retVal); - tree->AsOp()->SetReturnValue(retVal); // Propagate side effect flags tree->SetAllEffectsFlags(retVal); @@ -8364,10 +8363,10 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA // prop done when morphing this operand changed the local. // Skip this for merged returns that will be changed to a store and // jump to the return BB. - GenTree* const retVal = tree->AsOp()->GetReturnValue(); + GenTree*& retVal = tree->AsOp()->ReturnValueRef(); if ((retVal != nullptr) && ((genReturnBB == nullptr) || (compCurBB == genReturnBB))) { - fgTryReplaceStructLocalWithField(retVal); + fgTryReplaceStructLocalWithFields(&retVal); } break; } @@ -8425,29 +8424,18 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA // Notes: // Currently only called when the tree parent is a GT_RETURN/GT_SWIFT_ERROR_RET. // -void Compiler::fgTryReplaceStructLocalWithField(GenTree* tree) +void Compiler::fgTryReplaceStructLocalWithFields(GenTree** use) { - if (!tree->OperIs(GT_LCL_VAR)) + if (!(*use)->OperIs(GT_LCL_VAR)) { return; } - GenTreeLclVar* lclVar = tree->AsLclVar(); - unsigned lclNum = lclVar->GetLclNum(); - LclVarDsc* const varDsc = lvaGetDesc(lclVar); - if (varDsc->CanBeReplacedWithItsField(this)) - { - // We can replace the struct with its only field and allow copy propagation to replace - // return value that was written as a field. - unsigned const fieldLclNum = varDsc->lvFieldLclStart; - LclVarDsc* const fieldDsc = lvaGetDesc(fieldLclNum); + LclVarDsc* varDsc = lvaGetDesc((*use)->AsLclVar()); - JITDUMP("Replacing an independently promoted local var V%02u with its only field " - "V%02u for " - "the return [%06u]\n", - lclVar->GetLclNum(), fieldLclNum, dspTreeID(tree)); - lclVar->SetLclNum(fieldLclNum); - lclVar->ChangeType(fieldDsc->lvType); + if (!varDsc->lvDoNotEnregister && varDsc->lvPromoted) + { + *use = fgMorphLclToFieldList((*use)->AsLclVar()); } } diff --git a/src/coreclr/jit/promotion.cpp b/src/coreclr/jit/promotion.cpp index e695a32a0c82b2..bbd14401bc53eb 100644 --- a/src/coreclr/jit/promotion.cpp +++ b/src/coreclr/jit/promotion.cpp @@ -2295,11 +2295,6 @@ bool ReplaceVisitor::ReplaceReturnedStructLocal(GenTreeOp* ret, GenTreeLclVarCom return false; } - if (!IsReturnProfitableAsFieldList(value)) - { - return false; - } - StructDeaths deaths = m_liveness->GetDeathsForStructLocal(value); GenTreeFieldList* fieldList = m_compiler->gtNewFieldList(); @@ -2337,49 +2332,6 @@ bool ReplaceVisitor::ReplaceReturnedStructLocal(GenTreeOp* ret, GenTreeLclVarCom return true; } -//------------------------------------------------------------------------ -// IsReturnProfitableAsFieldList: -// Check if a returned local is expected to be profitable to turn into a -// FIELD_LIST. -// -// Parameters: -// value - The struct local -// -// Returns: -// True if so. -// -bool ReplaceVisitor::IsReturnProfitableAsFieldList(GenTreeLclVarCommon* value) -{ - // Currently the backend requires all fields to map cleanly to registers to - // efficiently return them. Otherwise they will be spilled, and we are - // better off decomposing the store here. - auto fieldMapsCleanly = [=](Replacement& rep) { - const ReturnTypeDesc& retDesc = m_compiler->compRetTypeDesc; - unsigned fieldOffset = rep.Offset - value->GetLclOffs(); - unsigned numRegs = retDesc.GetReturnRegCount(); - for (unsigned i = 0; i < numRegs; i++) - { - unsigned offset = retDesc.GetReturnFieldOffset(i); - var_types regType = retDesc.GetReturnRegType(i); - if ((fieldOffset == offset) && (genTypeSize(rep.AccessType) == genTypeSize(regType))) - { - return true; - } - } - - return false; - }; - - unsigned size = value->GetLayout(m_compiler)->GetSize(); - if (!VisitOverlappingReplacements(value->GetLclNum(), value->GetLclOffs(), size, fieldMapsCleanly)) - { - // Aborted early, so a field did not map - return false; - } - - return true; -} - //------------------------------------------------------------------------ // ReplaceCallArgWithFieldList: // Handle a call that may pass a struct local with replacements as the diff --git a/src/coreclr/jit/promotion.h b/src/coreclr/jit/promotion.h index 4fca7bdf9ca1fa..aad98bb3c1f29a 100644 --- a/src/coreclr/jit/promotion.h +++ b/src/coreclr/jit/promotion.h @@ -296,7 +296,6 @@ class ReplaceVisitor : public GenTreeVisitor bool ReplaceStructLocal(GenTree* user, GenTreeLclVarCommon* value); bool ReplaceReturnedStructLocal(GenTreeOp* ret, GenTreeLclVarCommon* value); - bool IsReturnProfitableAsFieldList(GenTreeLclVarCommon* value); bool ReplaceCallArgWithFieldList(GenTreeCall* call, GenTreeLclVarCommon* callArg); bool CanReplaceCallArgWithFieldListOfReplacements(GenTreeCall* call, CallArg* callArg, GenTreeLclVarCommon* lcl); void ReadBackAfterCall(GenTreeCall* call, GenTree* user);