From f4324370f9c680fc7142542b5392ee45dd309ced Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Tue, 9 Jun 2020 07:43:48 -0700 Subject: [PATCH 01/10] Disable retyping by default. --- src/coreclr/src/jit/jitconfigvalues.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/src/jit/jitconfigvalues.h b/src/coreclr/src/jit/jitconfigvalues.h index 92ad41c611886..18fa1719d3b89 100644 --- a/src/coreclr/src/jit/jitconfigvalues.h +++ b/src/coreclr/src/jit/jitconfigvalues.h @@ -439,7 +439,7 @@ CONFIG_INTEGER(JitSaveFpLrWithCalleeSavedRegisters, W("JitSaveFpLrWithCalleeSave #endif // defined(TARGET_ARM64) #endif // DEBUG -CONFIG_INTEGER(JitDoOldStructRetyping, W("JitDoOldStructRetyping"), 1) // Allow Jit to retype structs as primitive types +CONFIG_INTEGER(JitDoOldStructRetyping, W("JitDoOldStructRetyping"), 0) // Allow Jit to retype structs as primitive types // when possible. #undef CONFIG_INTEGER From 06e7c52f2c1b1757923cc2ef195de20a08e5ac1a Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Fri, 12 Jun 2020 21:07:47 -0700 Subject: [PATCH 02/10] Keep block init/copy as baseline. Total bytes of diff: -21971 (-0.07% of base) 3075 total methods with Code Size differences (1589 improved, 1486 regressed), 184523 unchanged. Note: it improves code with retyping as well: 808 total methods with Code Size differences (808 improved, 0 regressed), 186790 unchanged. Found 55 files with textual diffs. Crossgen CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for default jit Summary of Code Size diffs: (Lower is better) Total bytes of diff: -22923 (-0.07% of base) --- src/coreclr/src/jit/lclmorph.cpp | 30 ++++++++++++++++++++++++++++++ src/coreclr/src/jit/lclvars.cpp | 5 +++++ 2 files changed, 35 insertions(+) diff --git a/src/coreclr/src/jit/lclmorph.cpp b/src/coreclr/src/jit/lclmorph.cpp index 176f1994d85ad..cdb8cc7e7637d 100644 --- a/src/coreclr/src/jit/lclmorph.cpp +++ b/src/coreclr/src/jit/lclmorph.cpp @@ -521,6 +521,36 @@ 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 (!m_compiler->compDoOldStructRetyping() && 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 separatly. + GenTreeLclVar* lclVar = retVal->AsLclVar(); + unsigned lclNum = lclVar->GetLclNum(); + if (!m_compiler->lvaIsImplicitByRefLocal(lclVar->GetLclNum())) + { + LclVarDsc* varDsc = m_compiler->lvaGetDesc(lclNum); + if (varDsc->lvFieldCnt > 1) + { + m_compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(Compiler::DNER_IsStructArg)); + } + } + } + + EscapeValue(TopValue(0), node); + PopValue(); + } + break; + default: while (TopValue(0).Node() != node) { diff --git a/src/coreclr/src/jit/lclvars.cpp b/src/coreclr/src/jit/lclvars.cpp index f8757328e8513..7a274991934b8 100644 --- a/src/coreclr/src/jit/lclvars.cpp +++ b/src/coreclr/src/jit/lclvars.cpp @@ -1974,6 +1974,11 @@ bool Compiler::StructPromotionHelper::ShouldPromoteStructVar(unsigned lclNum) shouldPromote = false; } } + else if (!compiler->compDoOldStructRetyping() && (lclNum == compiler->genReturnLocal) && (structPromotionInfo.fieldCnt > 1)) + { + // TODO-1stClassStructs: a temporary solution to keep diffs small, it will be fixed later. + shouldPromote = false; + } // // If the lvRefCnt is zero and we have a struct promoted parameter we can end up with an extra store of From f53bb3050503c3bd81fdc4c9b49e1e0e1cb7d84b Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Wed, 10 Jun 2020 09:23:27 -0700 Subject: [PATCH 03/10] Don't mark LCL_VAR that is used in RETURN(IND(ADDR(LCL_VAR)) as address taken when possible. Protect against a promoted struct with a hole like struct<8> {hole 4; int a;}; --- src/coreclr/src/jit/codegenarmarch.cpp | 2 +- src/coreclr/src/jit/compiler.h | 9 +- src/coreclr/src/jit/flowgraph.cpp | 3 +- src/coreclr/src/jit/lclmorph.cpp | 5 +- src/coreclr/src/jit/lclvars.cpp | 50 ++++++- src/coreclr/src/jit/lower.cpp | 198 ++++++++++++++++--------- src/coreclr/src/jit/lsraarm.cpp | 6 +- src/coreclr/src/jit/morph.cpp | 120 ++++++++++++--- 8 files changed, 287 insertions(+), 106 deletions(-) diff --git a/src/coreclr/src/jit/codegenarmarch.cpp b/src/coreclr/src/jit/codegenarmarch.cpp index 496f723f82767..45c0beee51010 100644 --- a/src/coreclr/src/jit/codegenarmarch.cpp +++ b/src/coreclr/src/jit/codegenarmarch.cpp @@ -1107,7 +1107,7 @@ void CodeGen::genCodeForBitCast(GenTreeOp* treeNode) JITDUMP("Changing type of BITCAST source to load directly."); genCodeForTreeNode(op1); } - else if (varTypeIsFloating(treeNode) != varTypeIsFloating(op1)) + else if (varTypeUsesFloatReg(treeNode) != varTypeUsesFloatReg(op1)) { regNumber srcReg = op1->GetRegNum(); assert(genTypeSize(op1->TypeGet()) == genTypeSize(targetType)); diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index be64fa8c3e6b4..3766200fe195e 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -985,6 +985,8 @@ class LclVarDsc return GetLayout()->GetRegisterType(); } + bool CanBeReplacedWithItsField(Compiler* comp) const; + #ifdef DEBUG public: const char* lvReason; @@ -5602,6 +5604,7 @@ class Compiler GenTree* fgMorphCopyBlock(GenTree* tree); GenTree* fgMorphForRegisterFP(GenTree* tree); GenTree* fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac = nullptr); + GenTree* fgMorphRetInd(GenTreeUnOp* tree); GenTree* fgMorphModToSubMulDiv(GenTreeOp* tree); GenTree* fgMorphSmpOpOptional(GenTreeOp* tree); @@ -9179,7 +9182,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // Returns true if the method returns a value in more than one return register, // it should replace/be merged with compMethodReturnsMultiRegRetType when #36868 is fixed. - // The difference from original `compMethodReturnsMultiRegRetType` is in ARM64 SIMD16 handling, + // The difference from original `compMethodReturnsMultiRegRetType` is in ARM64 SIMD* handling, // this method correctly returns false for it (it is passed as HVA), when the original returns true. bool compMethodReturnsMultiRegRegTypeAlternate() { @@ -9189,8 +9192,8 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX return varTypeIsLong(info.compRetNativeType); #else // targets: X64-UNIX, ARM64 or ARM32 #if defined(TARGET_ARM64) - // TYP_SIMD16 is returned in one register. - if (info.compRetNativeType == TYP_SIMD16) + // TYP_SIMD* are returned in one register. + if (varTypeIsSIMD(info.compRetNativeType)) { return false; } diff --git a/src/coreclr/src/jit/flowgraph.cpp b/src/coreclr/src/jit/flowgraph.cpp index 61747171a4cdd..d1aea4d915cdc 100644 --- a/src/coreclr/src/jit/flowgraph.cpp +++ b/src/coreclr/src/jit/flowgraph.cpp @@ -89,7 +89,8 @@ void Compiler::fgInit() fgCurBBEpochSize = 0; fgBBSetCountInSizeTUnits = 0; - genReturnBB = nullptr; + genReturnBB = nullptr; + genReturnLocal = BAD_VAR_NUM; /* We haven't reached the global morphing phase */ fgGlobalMorph = false; diff --git a/src/coreclr/src/jit/lclmorph.cpp b/src/coreclr/src/jit/lclmorph.cpp index cdb8cc7e7637d..282761fde64d2 100644 --- a/src/coreclr/src/jit/lclmorph.cpp +++ b/src/coreclr/src/jit/lclmorph.cpp @@ -536,12 +536,13 @@ class LocalAddressVisitor final : public GenTreeVisitor // be merged separatly. GenTreeLclVar* lclVar = retVal->AsLclVar(); unsigned lclNum = lclVar->GetLclNum(); - if (!m_compiler->lvaIsImplicitByRefLocal(lclVar->GetLclNum())) + if (!m_compiler->compMethodReturnsMultiRegRegTypeAlternate() && + !m_compiler->lvaIsImplicitByRefLocal(lclVar->GetLclNum())) { LclVarDsc* varDsc = m_compiler->lvaGetDesc(lclNum); if (varDsc->lvFieldCnt > 1) { - m_compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(Compiler::DNER_IsStructArg)); + m_compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(Compiler::DNER_BlockOp)); } } } diff --git a/src/coreclr/src/jit/lclvars.cpp b/src/coreclr/src/jit/lclvars.cpp index 7a274991934b8..d4b1dc502eead 100644 --- a/src/coreclr/src/jit/lclvars.cpp +++ b/src/coreclr/src/jit/lclvars.cpp @@ -1974,7 +1974,8 @@ bool Compiler::StructPromotionHelper::ShouldPromoteStructVar(unsigned lclNum) shouldPromote = false; } } - else if (!compiler->compDoOldStructRetyping() && (lclNum == compiler->genReturnLocal) && (structPromotionInfo.fieldCnt > 1)) + else if (!compiler->compDoOldStructRetyping() && (lclNum == compiler->genReturnLocal) && + (structPromotionInfo.fieldCnt > 1)) { // TODO-1stClassStructs: a temporary solution to keep diffs small, it will be fixed later. shouldPromote = false; @@ -3645,6 +3646,53 @@ var_types LclVarDsc::lvaArgType() return type; } +//---------------------------------------------------------------------------------------------- +// CanBeReplacedWithItsField: check if a whole struct reference could be replaced by a field. +// +// Arguments: +// comp - the compiler instance; +// +// Return Value: +// true if that can be replaced, false otherwise. +// +// Notes: +// The replacement can be made only for independently promoted structs +// with 1 field without holes. +// +bool LclVarDsc::CanBeReplacedWithItsField(Compiler* comp) const +{ + if (!lvPromoted) + { + return false; + } + + if (comp->lvaGetPromotionType(this) != Compiler::PROMOTION_TYPE_INDEPENDENT) + { + return false; + } + if (lvFieldCnt != 1) + { + return false; + } + if (lvContainsHoles) + { + return false; + } + +#if defined(FEATURE_SIMD) + // If we return `struct A { SIMD16 a; }` we split the struct into several fields. + // In order to do that we have to have its field in memory. Right now lowering cannot + // handle RETURN struct(multiple registers)->SIMD16(one register), but it can be improved. + LclVarDsc* fieldDsc = comp->lvaGetDesc(lvFieldLclStart); + if (fieldDsc->TypeGet() == TYP_SIMD12 || fieldDsc->TypeGet() == TYP_SIMD16) + { + return false; + } +#endif // FEATURE_SIMD + + return true; +} + //------------------------------------------------------------------------ // lvaMarkLclRefs: increment local var references counts and more // diff --git a/src/coreclr/src/jit/lower.cpp b/src/coreclr/src/jit/lower.cpp index 2a99949e14162..cde4f37020b6c 100644 --- a/src/coreclr/src/jit/lower.cpp +++ b/src/coreclr/src/jit/lower.cpp @@ -1310,7 +1310,7 @@ void Lowering::LowerArg(GenTreeCall* call, GenTree** ppArg) // TYP_SIMD8 parameters that are passed as longs if (type == TYP_SIMD8 && genIsValidIntReg(info->GetRegNum())) { - GenTreeUnOp* bitcast = new (comp, GT_BITCAST) GenTreeOp(GT_BITCAST, TYP_LONG, arg, nullptr); + GenTree* bitcast = comp->gtNewBitCastNode(TYP_LONG, arg); BlockRange().InsertAfter(arg, bitcast); *ppArg = arg = bitcast; @@ -2938,65 +2938,86 @@ void Lowering::LowerRet(GenTreeUnOp* ret) DISPNODE(ret); JITDUMP("============"); - GenTree* op1 = ret->gtGetOp1(); - if ((ret->TypeGet() != TYP_VOID) && !varTypeIsStruct(ret) && - (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(ret->gtGetOp1()))) - { - assert(comp->compDoOldStructRetyping()); - GenTreeUnOp* bitcast = new (comp, GT_BITCAST) GenTreeOp(GT_BITCAST, ret->TypeGet(), ret->gtGetOp1(), nullptr); - ret->gtOp1 = bitcast; + GenTree* retVal = ret->gtGetOp1(); + bool needBitcast = + (ret->TypeGet() != TYP_VOID) && (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(ret->gtGetOp1())); + if (needBitcast && !varTypeIsStruct(ret) && (!varTypeIsStruct(retVal) || comp->compDoOldStructRetyping())) + { + // Add a simple bitcast for an old retyping or when both types are not structs. + // If one type is a struct it will be handled below for !compDoOldStructRetyping. + GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), retVal); + ret->gtOp1 = bitcast; BlockRange().InsertBefore(ret, bitcast); ContainCheckBitCast(bitcast); } else if (ret->TypeGet() != TYP_VOID) { - GenTree* retVal = ret->gtGetOp1(); #if FEATURE_MULTIREG_RET - if (op1->OperIs(GT_LCL_VAR) && varTypeIsStruct(op1)) + if (retVal->OperIs(GT_LCL_VAR) && varTypeIsStruct(retVal)) { ReturnTypeDesc retTypeDesc; LclVarDsc* varDsc = nullptr; - varDsc = comp->lvaGetDesc(op1->AsLclVar()->GetLclNum()); + varDsc = comp->lvaGetDesc(retVal->AsLclVar()->GetLclNum()); retTypeDesc.InitializeStructReturnType(comp, varDsc->lvVerTypeInfo.GetClassHandle()); if (retTypeDesc.GetReturnRegCount() > 1) { - CheckMultiRegLclVar(op1->AsLclVar(), &retTypeDesc); + CheckMultiRegLclVar(retVal->AsLclVar(), &retTypeDesc); } } - else +#endif // FEATURE_MULTIREG_RET #ifdef DEBUG - if (varTypeIsStruct(ret->TypeGet()) != varTypeIsStruct(retVal->TypeGet())) + if (varTypeIsStruct(ret->TypeGet()) != varTypeIsStruct(retVal->TypeGet())) { - if (varTypeIsStruct(ret->TypeGet())) + if (!comp->compDoOldStructRetyping() && varTypeIsStruct(ret->TypeGet())) { assert(!comp->compDoOldStructRetyping()); - bool actualTypesMatch = false; - if (genActualType(comp->info.compRetNativeType) == genActualType(retVal->TypeGet())) + assert(comp->info.compRetNativeType != TYP_STRUCT); + + bool constStructInit = retVal->IsConstInitVal(); + bool actualTypesMatch = false; + var_types retActualType = genActualType(comp->info.compRetNativeType); + var_types retValActualType = genActualType(retVal->TypeGet()); + if (retActualType == retValActualType) { // This could happen if we have retyped op1 as a primitive type during struct promotion, // check `retypedFieldsMap` for details. actualTypesMatch = true; } - bool constStructInit = retVal->IsConstInitVal(); - assert(actualTypesMatch || constStructInit); + + bool implicitCastFromSameOrBiggerSize = false; + if (genTypeSize(retActualType) <= genTypeSize(retValActualType)) + { + implicitCastFromSameOrBiggerSize = true; + } + + assert(actualTypesMatch || constStructInit || implicitCastFromSameOrBiggerSize); } - else + } +#endif // DEBUG + + if (varTypeIsStruct(ret)) + { + LowerRetStruct(ret); + } + else if (!ret->TypeIs(TYP_VOID) && varTypeIsStruct(retVal)) + { + if (comp->compDoOldStructRetyping()) { #ifdef FEATURE_SIMD - assert(comp->compDoOldStructRetyping()); assert(ret->TypeIs(TYP_DOUBLE)); assert(retVal->TypeIs(TYP_SIMD8)); -#else // !FEATURE_SIMD +#else unreached(); -#endif // !FEATURE_SIMD +#endif + } + else + { + // Return struct as a primitive using Unsafe cast. + assert(!comp->compDoOldStructRetyping()); + assert(retVal->OperIs(GT_LCL_VAR)); + LowerRetStructLclVar(ret); } } -#endif // DEBUG - if (varTypeIsStruct(ret)) - { - LowerRetStruct(ret); - } -#endif // !FEATURE_MULTIREG_RET } // Method doing PInvokes has exactly one return block unless it has tail calls. @@ -3016,16 +3037,44 @@ void Lowering::LowerRet(GenTreeUnOp* ret) void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) { assert(lclStore->OperIs(GT_STORE_LCL_FLD, GT_STORE_LCL_VAR)); - GenTree* src = lclStore->gtGetOp1(); - LclVarDsc* varDsc = comp->lvaGetDesc(lclStore); + JITDUMP("lowering store lcl var/field (before):\n"); + DISPTREERANGE(BlockRange(), lclStore); + JITDUMP("\n"); + + GenTree* src = lclStore->gtGetOp1(); + LclVarDsc* varDsc = comp->lvaGetDesc(lclStore); + bool srcIsMultiReg = src->IsMultiRegNode(); + bool dstIsMultiReg = lclStore->IsMultiRegLclVar(); + + if (!dstIsMultiReg && varTypeIsStruct(varDsc)) + { + // TODO: we want to check `varDsc->lvRegStruct` as the last condition instead of `!varDsc->lvPromoted`, + // but we do not set it for `CSE` vars so it is currently failing. + assert(varDsc->CanBeReplacedWithItsField(comp) || varDsc->lvDoNotEnregister || !varDsc->lvPromoted); + if (varDsc->CanBeReplacedWithItsField(comp)) + { + assert(!comp->compDoOldStructRetyping()); + assert(varDsc->lvFieldCnt == 1); + unsigned fldNum = varDsc->lvFieldLclStart; + LclVarDsc* fldDsc = comp->lvaGetDesc(fldNum); + lclStore->SetLclNum(fldNum); + lclStore->ChangeType(fldDsc->TypeGet()); + + JITDUMP("Replacing an independently promoted local var V%02u with its only field V%02u for the store " + "from a call [%06u]\n", + lclStore->GetLclNum(), fldNum, comp->dspTreeID(lclStore)); + varDsc = fldDsc; + } + } + if ((varTypeUsesFloatReg(lclStore) != varTypeUsesFloatReg(src)) && !lclStore->IsPhiDefn() && (src->TypeGet() != TYP_STRUCT)) { if (m_lsra->isRegCandidate(varDsc)) { - GenTreeUnOp* bitcast = new (comp, GT_BITCAST) GenTreeOp(GT_BITCAST, lclStore->TypeGet(), src, nullptr); - lclStore->gtOp1 = bitcast; - src = lclStore->gtGetOp1(); + GenTree* bitcast = comp->gtNewBitCastNode(lclStore->TypeGet(), src); + lclStore->gtOp1 = bitcast; + src = lclStore->gtGetOp1(); BlockRange().InsertBefore(lclStore, bitcast); ContainCheckBitCast(bitcast); } @@ -3036,7 +3085,6 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) } } - bool srcIsMultiReg = src->IsMultiRegNode(); if (srcIsMultiReg || lclStore->IsMultiRegLclVar()) { const ReturnTypeDesc* retTypeDesc = nullptr; @@ -3046,7 +3094,8 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) } CheckMultiRegLclVar(lclStore->AsLclVar(), retTypeDesc); } - if (!srcIsMultiReg && (lclStore->TypeGet() == TYP_STRUCT) && (src->OperGet() != GT_PHI)) + if ((lclStore->TypeGet() == TYP_STRUCT) && !srcIsMultiReg && varTypeIsStruct(lclStore->TypeGet()) && + (src->OperGet() != GT_PHI)) { if (src->OperGet() == GT_CALL) { @@ -3090,12 +3139,13 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) GenTreeLclVar* spilledCall = SpillStructCallResult(call); lclStore->gtOp1 = spilledCall; src = lclStore->gtOp1; + JITDUMP("lowering store lcl var/field has to spill call src.\n"); LowerStoreLocCommon(lclStore); return; } #endif // !WINDOWS_AMD64_ABI } - else if (!src->OperIs(GT_LCL_VAR) || varDsc->GetLayout()->GetRegisterType() == TYP_UNDEF) + else if (!src->OperIs(GT_LCL_VAR) || (varDsc->GetLayout()->GetRegisterType() == TYP_UNDEF)) { GenTreeLclVar* addr = comp->gtNewLclVarAddrNode(lclStore->GetLclNum(), TYP_BYREF); @@ -3119,7 +3169,11 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) return; } } + LowerStoreLoc(lclStore); + JITDUMP("lowering store lcl var/field (after):\n"); + DISPTREERANGE(BlockRange(), lclStore); + JITDUMP("\n"); } //---------------------------------------------------------------------------------------------- @@ -3131,11 +3185,11 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) void Lowering::LowerRetStruct(GenTreeUnOp* ret) { #if defined(FEATURE_HFA) && defined(TARGET_ARM64) - if (ret->TypeIs(TYP_SIMD16)) + if (varTypeIsSIMD(ret)) { if (comp->info.compRetNativeType == TYP_STRUCT) { - assert(ret->gtGetOp1()->TypeIs(TYP_SIMD16)); + assert(varTypeIsSIMD(ret->gtGetOp1())); assert(comp->compMethodReturnsMultiRegRegTypeAlternate()); if (!comp->compDoOldStructRetyping()) { @@ -3144,13 +3198,20 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) else { // With old struct retyping a value that is returned as HFA - // could have both SIMD16 or STRUCT types, keep it as it. + // could have both SIMD* or STRUCT types, keep it as it. return; } } else { - assert(comp->info.compRetNativeType == TYP_SIMD16); + assert(comp->info.compRetNativeType == ret->TypeGet()); + GenTree* retVal = ret->gtGetOp1(); + if (retVal->TypeGet() != ret->TypeGet()) + { + assert(retVal->OperIs(GT_LCL_VAR)); + assert(!comp->compDoOldStructRetyping()); + LowerRetStructLclVar(ret); + } return; } } @@ -3210,8 +3271,8 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) assert(!retVal->TypeIs(TYP_STRUCT)); if (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(retVal)) { - GenTreeUnOp* bitcast = new (comp, GT_BITCAST) GenTreeOp(GT_BITCAST, ret->TypeGet(), retVal, nullptr); - ret->gtOp1 = bitcast; + GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), retVal); + ret->gtOp1 = bitcast; BlockRange().InsertBefore(ret, bitcast); ContainCheckBitCast(bitcast); } @@ -3257,37 +3318,26 @@ void Lowering::LowerRetStructLclVar(GenTreeUnOp* ret) unsigned lclNum = lclVar->GetLclNum(); LclVarDsc* varDsc = comp->lvaGetDesc(lclNum); -#ifdef DEBUG - if (comp->gtGetStructHandleIfPresent(lclVar) == NO_CLASS_HANDLE) + if (varDsc->CanBeReplacedWithItsField(comp)) { - // a promoted struct field was retyped as its only field. - assert(varDsc->lvIsStructField); + // We can replace the struct with its only field and keep the field on a register. + unsigned fieldLclNum = varDsc->lvFieldLclStart; + LclVarDsc* fieldDsc = comp->lvaGetDesc(fieldLclNum); + + lclVar->SetLclNum(fieldLclNum); + JITDUMP("Replacing an independently promoted local var V%02u with its only field V%02u for the return " + "[%06u]\n", + lclNum, fieldLclNum, comp->dspTreeID(ret)); + lclVar->ChangeType(fieldDsc->lvType); + lclNum = fieldLclNum; + varDsc = comp->lvaGetDesc(lclNum); } -#endif - if (varDsc->lvPromoted && (comp->lvaGetPromotionType(lclNum) == Compiler::PROMOTION_TYPE_INDEPENDENT)) + else if (!varDsc->lvRegStruct && !varTypeIsEnregisterable(varDsc)) + { - if (varDsc->lvFieldCnt == 1) - { - // We can replace the struct with its only field and keep the field on a register. - assert(varDsc->lvRefCnt() == 0); - unsigned fieldLclNum = varDsc->lvFieldLclStart; - LclVarDsc* fieldDsc = comp->lvaGetDesc(fieldLclNum); - if (fieldDsc->lvFldOffset == 0) - { - lclVar->SetLclNum(fieldLclNum); - JITDUMP("Replacing an independently promoted local var with its only field for the return %u, %u\n", - lclNum, fieldLclNum); - lclVar->ChangeType(fieldDsc->lvType); - lclNum = fieldLclNum; - varDsc = comp->lvaGetDesc(lclNum); - } - } - else - { - // TODO-1stClassStructs: We can no longer promote or enregister this struct, - // since it is referenced as a whole. - comp->lvaSetVarDoNotEnregister(lclNum DEBUGARG(Compiler::DNER_VMNeedsStackAddr)); - } + // TODO-1stClassStructs: We can no longer promote or enregister this struct, + // since it is referenced as a whole. + comp->lvaSetVarDoNotEnregister(lclNum DEBUGARG(Compiler::DNER_BlockOp)); } if (varDsc->lvDoNotEnregister) @@ -3304,8 +3354,8 @@ void Lowering::LowerRetStructLclVar(GenTreeUnOp* ret) if (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(lclVarType)) { - GenTreeUnOp* bitcast = new (comp, GT_BITCAST) GenTreeOp(GT_BITCAST, ret->TypeGet(), lclVar, nullptr); - ret->gtOp1 = bitcast; + GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), lclVar); + ret->gtOp1 = bitcast; BlockRange().InsertBefore(ret, bitcast); ContainCheckBitCast(bitcast); } @@ -6228,7 +6278,7 @@ void Lowering::ContainCheckRet(GenTreeUnOp* ret) } #endif // !defined(TARGET_64BIT) #if FEATURE_MULTIREG_RET - if (varTypeIsStruct(ret)) + if (ret->TypeIs(TYP_STRUCT)) { GenTree* op1 = ret->gtGetOp1(); // op1 must be either a lclvar or a multi-reg returning call diff --git a/src/coreclr/src/jit/lsraarm.cpp b/src/coreclr/src/jit/lsraarm.cpp index 653a678c641e6..afd41f2b49719 100644 --- a/src/coreclr/src/jit/lsraarm.cpp +++ b/src/coreclr/src/jit/lsraarm.cpp @@ -764,7 +764,11 @@ int LinearScan::BuildNode(GenTree* tree) { assert(dstCount == 1); regNumber argReg = tree->GetRegNum(); - regMaskTP argMask = genRegMask(argReg); + regMaskTP argMask = RBM_NONE; + if (argReg != REG_COUNT) + { + argMask = genRegMask(argReg); + } // If type of node is `long` then it is actually `double`. // The actual `long` types must have been transformed as a field list with two fields. diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index e522917694376..81f77188e8556 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -10336,6 +10336,19 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree) } #endif // FEATURE_MULTIREG_RET + if (src->IsCall() && dest->OperIs(GT_LCL_VAR) && !compDoOldStructRetyping()) + { + LclVarDsc* varDsc = lvaGetDesc(dest->AsLclVar()); + if (varTypeIsStruct(varDsc) && varDsc->CanBeReplacedWithItsField(this)) + { + JITDUMP(" not morphing a single reg call return\n"); + // Set `lvIsMultiRegRet` to exclude it from SSA, it is a temporary solution, + // inspired by multi-reg call work. We should support SSA for such structs in the future. + varDsc->lvIsMultiRegRet = true; + return tree; + } + } + // If we have an array index on the lhs, we need to create an obj node. dest = fgMorphBlkNode(dest, true); @@ -11969,30 +11982,9 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) return tree; } - if (varTypeIsStruct(tree) && op1->OperIs(GT_OBJ, GT_BLK)) + if (!compDoOldStructRetyping() && !tree->TypeIs(TYP_VOID) && op1->OperIs(GT_OBJ, GT_BLK, GT_IND)) { - assert(!compDoOldStructRetyping()); - GenTree* addr = op1->AsBlk()->Addr(); - // if we return `OBJ` or `BLK` from a local var, lcl var has to have a stack address. - if (addr->OperIs(GT_ADDR) && addr->gtGetOp1()->OperIs(GT_LCL_VAR)) - { - GenTreeLclVar* lclVar = addr->gtGetOp1()->AsLclVar(); - assert(!gtIsActiveCSE_Candidate(addr) && !gtIsActiveCSE_Candidate(op1)); - if (gtGetStructHandle(tree) == gtGetStructHandleIfPresent(lclVar)) - { - // Fold *(&x). - tree->AsUnOp()->gtOp1 = op1; - DEBUG_DESTROY_NODE(op1); - DEBUG_DESTROY_NODE(addr); - op1 = lclVar; - } - else - { - // TODO-1stClassStructs: It is not address-taken or block operation, - // but the current IR doesn't allow to express that cast without stack, see #11413. - lvaSetVarDoNotEnregister(lclVar->GetLclNum() DEBUGARG(DNER_BlockOp)); - } - } + op1 = fgMorphRetInd(tree->AsUnOp()); } break; @@ -14219,6 +14211,88 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) return tree; } + +//---------------------------------------------------------------------------------------------- +// fgMorphRetInd: Try to get rid of extra IND(ADDR()) pairs in a return tree. +// +// Arguments: +// node - The return node that uses an indirection. +// +// Return Value: +// the original op1 of the ret if there was no optimization or an optimized new op1. +// +GenTree* Compiler::fgMorphRetInd(GenTreeUnOp* ret) +{ + assert(!compDoOldStructRetyping()); + assert(ret->OperIs(GT_RETURN)); + assert(ret->gtGetOp1()->OperIs(GT_IND, GT_BLK, GT_OBJ)); + GenTreeIndir* ind = ret->gtGetOp1()->AsIndir(); + GenTree* addr = ind->Addr(); + + if (addr->OperIs(GT_ADDR) && addr->gtGetOp1()->OperIs(GT_LCL_VAR)) + { + // If `return` retypes LCL_VAR as a smaller struct it should not set `doNotEnregister` on that + // LclVar. + // Example: in `Vector128:AsVector2` we have RETURN SIMD8(OBJ SIMD8(ADDR byref(LCL_VAR SIMD16))). + GenTreeLclVar* lclVar = addr->gtGetOp1()->AsLclVar(); + if (!lvaIsImplicitByRefLocal(lclVar->GetLclNum())) + { + assert(!gtIsActiveCSE_Candidate(addr) && !gtIsActiveCSE_Candidate(ind)); + unsigned indSize; + if (ind->OperIs(GT_IND)) + { + indSize = genTypeSize(ind); + } + else + { + indSize = ind->AsBlk()->GetLayout()->GetSize(); + } + + LclVarDsc* varDsc = lvaGetDesc(lclVar); + + unsigned lclVarSize; + if (!lclVar->TypeIs(TYP_STRUCT)) + + { + lclVarSize = genTypeSize(varDsc->TypeGet()); + } + else + { + lclVarSize = varDsc->lvExactSize; + } + // TODO: change conditions in `canFold` to `indSize <= lclVarSize`, but currently do not support `BITCAST + // int<-SIMD16` etc. + assert((indSize <= lclVarSize) || varDsc->lvDoNotEnregister); + +#if defined(TARGET_64BIT) + bool canFold = (indSize == lclVarSize); +#else // !TARGET_64BIT + // TODO: improve 32 bit targets handling for LONG returns if necessary, nowadays we do not support `BITCAST + // long<->double` there. + bool canFold = (indSize == lclVarSize) && (lclVarSize <= REGSIZE_BYTES); +#endif + // TODO: support `genReturnBB != nullptr`, it requires #11413 to avoid `Incompatible types for + // gtNewTempAssign`. + if (canFold && (genReturnBB == nullptr)) + { + // Fold (TYPE1)*(&(TYPE2)x) even if types do not match, lowering will handle it. + // Getting rid of this IND(ADDR()) pair allows to keep lclVar as not address taken + // and enregister it. + DEBUG_DESTROY_NODE(ind); + DEBUG_DESTROY_NODE(addr); + + ret->gtOp1 = lclVar; + return ret->gtGetOp1(); + } + else if (!varDsc->lvDoNotEnregister) + { + lvaSetVarDoNotEnregister(lclVar->GetLclNum() DEBUGARG(Compiler::DNER_VMNeedsStackAddr)); + } + } + } + return ind; +} + #ifdef _PREFAST_ #pragma warning(pop) #endif From 3965e9d3b404048c6a55f15f4102158e541a1fe1 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Sat, 20 Jun 2020 19:44:59 -0700 Subject: [PATCH 04/10] Replace 1-field structs with the field for returns. --- src/coreclr/src/jit/lower.cpp | 29 ++++++++++++++++++++++----- src/coreclr/src/jit/morph.cpp | 37 +++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/coreclr/src/jit/lower.cpp b/src/coreclr/src/jit/lower.cpp index cde4f37020b6c..0283725f327cb 100644 --- a/src/coreclr/src/jit/lower.cpp +++ b/src/coreclr/src/jit/lower.cpp @@ -3057,12 +3057,12 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) assert(varDsc->lvFieldCnt == 1); unsigned fldNum = varDsc->lvFieldLclStart; LclVarDsc* fldDsc = comp->lvaGetDesc(fldNum); - lclStore->SetLclNum(fldNum); - lclStore->ChangeType(fldDsc->TypeGet()); JITDUMP("Replacing an independently promoted local var V%02u with its only field V%02u for the store " "from a call [%06u]\n", lclStore->GetLclNum(), fldNum, comp->dspTreeID(lclStore)); + lclStore->SetLclNum(fldNum); + lclStore->ChangeType(fldDsc->TypeGet()); varDsc = fldDsc; } } @@ -3238,10 +3238,13 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) break; case GT_CNS_INT: - assert(retVal->TypeIs(TYP_INT)); - assert(retVal->AsIntCon()->IconValue() == 0); + // When we promote LCL_VAR single fields into return + // we could have all type of constans here. if (varTypeUsesFloatReg(nativeReturnType)) { + // Do not expect `initblock` for SIMD* types, + // only 'initobj'. + assert(retVal->AsIntCon()->IconValue() == 0); retVal->ChangeOperConst(GT_CNS_DBL); retVal->ChangeType(TYP_FLOAT); retVal->AsDblCon()->gtDconVal = 0; @@ -3292,8 +3295,24 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) } break; - default: +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_ARM) + case GT_CNS_DBL: + // Currently we are not promoting structs with a single float field. + // TODO: can improve `GT_CNS_DBL` handling for supported platforms, but + // because it is only x86 nowadays it is not worth it. unreached(); +#endif + + default: + assert(varTypeIsEnregisterable(retVal)); + if (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(retVal)) + { + GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), retVal); + ret->gtOp1 = bitcast; + BlockRange().InsertBefore(ret, bitcast); + ContainCheckBitCast(bitcast); + } + break; } } diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index 81f77188e8556..00c548c288423 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -11982,9 +11982,39 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) return tree; } - if (!compDoOldStructRetyping() && !tree->TypeIs(TYP_VOID) && op1->OperIs(GT_OBJ, GT_BLK, GT_IND)) + if (!compDoOldStructRetyping() && !tree->TypeIs(TYP_VOID)) { - op1 = fgMorphRetInd(tree->AsUnOp()); + if (op1->OperIs(GT_OBJ, GT_BLK, GT_IND)) + { + op1 = fgMorphRetInd(tree->AsUnOp()); + } + if (op1->OperIs(GT_LCL_VAR) && (genReturnBB == nullptr)) + { + // With a `genReturnBB` it will be done via field by field asignment. + GenTreeLclVar* lclVar = op1->AsLclVar(); + LclVarDsc* varDsc = lvaGetDesc(lclVar); + if (varDsc->CanBeReplacedWithItsField(this)) + { + // We can replace the struct with its only field and allow copy propogation to replace + // return value that was written as a field. + unsigned fieldLclNum = varDsc->lvFieldLclStart; + LclVarDsc* fieldDsc = lvaGetDesc(fieldLclNum); + + if (!varTypeIsSmallInt(fieldDsc->lvType)) + { + // TODO: support that substitution for small types without creating `CAST` node. + // When a small struct is returned in a register higher bits could be left in undefined + // state. + 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); + var_types fieldType = fieldDsc->lvType; + lclVar->ChangeType(fieldDsc->lvType); + } + } + } } break; @@ -14280,13 +14310,12 @@ GenTree* Compiler::fgMorphRetInd(GenTreeUnOp* ret) // and enregister it. DEBUG_DESTROY_NODE(ind); DEBUG_DESTROY_NODE(addr); - ret->gtOp1 = lclVar; return ret->gtGetOp1(); } else if (!varDsc->lvDoNotEnregister) { - lvaSetVarDoNotEnregister(lclVar->GetLclNum() DEBUGARG(Compiler::DNER_VMNeedsStackAddr)); + lvaSetVarDoNotEnregister(lclVar->GetLclNum() DEBUGARG(Compiler::DNER_BlockOp)); } } } From f7a90a380f06287c17a57f391b68df7152de8b0e Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Mon, 22 Jun 2020 03:20:59 -0700 Subject: [PATCH 05/10] Add SSA support. --- src/coreclr/src/jit/morph.cpp | 3 --- src/coreclr/src/jit/rangecheck.cpp | 22 +++++++++++++++++++--- src/coreclr/src/jit/ssabuilder.cpp | 14 +++++++++++--- src/coreclr/src/jit/valuenum.cpp | 6 ++++++ 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index 00c548c288423..b67531a101fa5 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -10342,9 +10342,6 @@ GenTree* Compiler::fgMorphCopyBlock(GenTree* tree) if (varTypeIsStruct(varDsc) && varDsc->CanBeReplacedWithItsField(this)) { JITDUMP(" not morphing a single reg call return\n"); - // Set `lvIsMultiRegRet` to exclude it from SSA, it is a temporary solution, - // inspired by multi-reg call work. We should support SSA for such structs in the future. - varDsc->lvIsMultiRegRet = true; return tree; } } diff --git a/src/coreclr/src/jit/rangecheck.cpp b/src/coreclr/src/jit/rangecheck.cpp index 9fb0eaf0357d2..663c3a1537d34 100644 --- a/src/coreclr/src/jit/rangecheck.cpp +++ b/src/coreclr/src/jit/rangecheck.cpp @@ -444,7 +444,12 @@ LclSsaVarDsc* RangeCheck::GetSsaDefAsg(GenTreeLclVarCommon* lclUse) return nullptr; } - LclSsaVarDsc* ssaDef = m_pCompiler->lvaGetDesc(lclUse)->GetPerSsaData(ssaNum); + LclVarDsc* varDsc = m_pCompiler->lvaGetDesc(lclUse); + if (varDsc->CanBeReplacedWithItsField(m_pCompiler)) + { + varDsc = m_pCompiler->lvaGetDesc(varDsc->lvFieldLclStart); + } + LclSsaVarDsc* ssaDef = varDsc->GetPerSsaData(ssaNum); // RangeCheck does not care about uninitialized variables. if (ssaDef->GetAssignment() == nullptr) @@ -472,6 +477,11 @@ LclSsaVarDsc* RangeCheck::GetSsaDefAsg(GenTreeLclVarCommon* lclUse) #ifdef DEBUG UINT64 RangeCheck::HashCode(unsigned lclNum, unsigned ssaNum) { + LclVarDsc* varDsc = m_pCompiler->lvaGetDesc(lclNum); + if (varDsc->CanBeReplacedWithItsField(m_pCompiler)) + { + lclNum = varDsc->lvFieldLclStart; + } assert(ssaNum != SsaConfig::RESERVED_SSA_NUM); return UINT64(lclNum) << 32 | ssaNum; } @@ -499,7 +509,8 @@ RangeCheck::Location* RangeCheck::GetDef(unsigned lclNum, unsigned ssaNum) RangeCheck::Location* RangeCheck::GetDef(GenTreeLclVarCommon* lcl) { - return GetDef(lcl->GetLclNum(), lcl->GetSsaNum()); + unsigned lclNum = lcl->GetLclNum(); + return GetDef(lclNum, lcl->GetSsaNum()); } // Add the def location to the hash table. @@ -546,7 +557,12 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP Limit limit(Limit::keUndef); genTreeOps cmpOper = GT_NONE; - LclSsaVarDsc* ssaData = m_pCompiler->lvaTable[lcl->GetLclNum()].GetPerSsaData(lcl->GetSsaNum()); + LclVarDsc* varDsc = m_pCompiler->lvaGetDesc(lcl); + if (varDsc->CanBeReplacedWithItsField(m_pCompiler)) + { + varDsc = m_pCompiler->lvaGetDesc(varDsc->lvFieldLclStart); + } + LclSsaVarDsc* ssaData = varDsc->GetPerSsaData(lcl->GetSsaNum()); ValueNum normalLclVN = m_pCompiler->vnStore->VNConservativeNormalValue(ssaData->m_vnPair); // Current assertion is of the form (i < len - cns) != 0 diff --git a/src/coreclr/src/jit/ssabuilder.cpp b/src/coreclr/src/jit/ssabuilder.cpp index 3bd0b44c4f2b4..5a1df78f21e59 100644 --- a/src/coreclr/src/jit/ssabuilder.cpp +++ b/src/coreclr/src/jit/ssabuilder.cpp @@ -749,7 +749,15 @@ void SsaBuilder::RenameDef(GenTreeOp* asgNode, BasicBlock* block) if (isLocal) { - unsigned lclNum = lclNode->GetLclNum(); + unsigned lclNum = lclNode->GetLclNum(); + LclVarDsc* varDsc = m_pCompiler->lvaGetDesc(lclNum); + + if (!m_pCompiler->lvaInSsa(lclNum) && varDsc->CanBeReplacedWithItsField(m_pCompiler)) + { + lclNum = varDsc->lvFieldLclStart; + varDsc = m_pCompiler->lvaGetDesc(lclNum); + assert(isFullDef); + } if (m_pCompiler->lvaInSsa(lclNum)) { @@ -758,7 +766,7 @@ void SsaBuilder::RenameDef(GenTreeOp* asgNode, BasicBlock* block) // This should have been marked as defintion. assert((lclNode->gtFlags & GTF_VAR_DEF) != 0); - unsigned ssaNum = m_pCompiler->lvaGetDesc(lclNum)->lvPerSsaData.AllocSsaNum(m_allocator, block, asgNode); + unsigned ssaNum = varDsc->lvPerSsaData.AllocSsaNum(m_allocator, block, asgNode); if (!isFullDef) { @@ -787,7 +795,7 @@ void SsaBuilder::RenameDef(GenTreeOp* asgNode, BasicBlock* block) } // If it's a SSA local then it cannot be address exposed and thus does not define SSA memory. - assert(!m_pCompiler->lvaVarAddrExposed(lclNode->GetLclNum())); + assert(!m_pCompiler->lvaVarAddrExposed(lclNum)); return; } diff --git a/src/coreclr/src/jit/valuenum.cpp b/src/coreclr/src/jit/valuenum.cpp index 2fb9c3ce14e46..2e65f36aa163b 100644 --- a/src/coreclr/src/jit/valuenum.cpp +++ b/src/coreclr/src/jit/valuenum.cpp @@ -6859,6 +6859,12 @@ void Compiler::fgValueNumberTree(GenTree* tree) unsigned lclNum = lcl->GetLclNum(); LclVarDsc* varDsc = &lvaTable[lclNum]; + if (varDsc->CanBeReplacedWithItsField(this)) + { + lclNum = varDsc->lvFieldLclStart; + varDsc = &lvaTable[lclNum]; + } + // Do we have a Use (read) of the LclVar? // if ((lcl->gtFlags & GTF_VAR_DEF) == 0 || From 8911b08f1ca59de7f62f6f1c06d731e8232ff2e3 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Wed, 1 Jul 2020 17:54:39 -0700 Subject: [PATCH 06/10] Review response. --- src/coreclr/src/jit/compiler.h | 4 +- src/coreclr/src/jit/lclvars.cpp | 8 +++- src/coreclr/src/jit/lower.cpp | 77 ++++++++++++++++++++------------- src/coreclr/src/jit/lower.h | 4 +- src/coreclr/src/jit/morph.cpp | 46 +++++++++++--------- 5 files changed, 83 insertions(+), 56 deletions(-) diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index 3766200fe195e..9b039ebfde552 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -7974,7 +7974,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // vector type (i.e. do not analyze or promote its fields). // Note that all but the fixed vector types are opaque, even though they may // actually be declared as having fields. - bool isOpaqueSIMDType(CORINFO_CLASS_HANDLE structHandle) + bool isOpaqueSIMDType(CORINFO_CLASS_HANDLE structHandle) const { return ((m_simdHandleCache != nullptr) && (structHandle != m_simdHandleCache->SIMDVector2Handle) && (structHandle != m_simdHandleCache->SIMDVector3Handle) && @@ -7990,7 +7990,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX } // Returns true if the lclVar is an opaque SIMD type. - bool isOpaqueSIMDLclVar(LclVarDsc* varDsc) + bool isOpaqueSIMDLclVar(const LclVarDsc* varDsc) const { if (!varDsc->lvSIMDType) { diff --git a/src/coreclr/src/jit/lclvars.cpp b/src/coreclr/src/jit/lclvars.cpp index d4b1dc502eead..1680e5cf6ca6a 100644 --- a/src/coreclr/src/jit/lclvars.cpp +++ b/src/coreclr/src/jit/lclvars.cpp @@ -3680,13 +3680,19 @@ bool LclVarDsc::CanBeReplacedWithItsField(Compiler* comp) const } #if defined(FEATURE_SIMD) + assert(!comp->isOpaqueSIMDLclVar(this)); // opaque SIMD can't be promoted. // If we return `struct A { SIMD16 a; }` we split the struct into several fields. - // In order to do that we have to have its field in memory. Right now lowering cannot + // In order to do that we have to have its field `a` in memory. Right now lowering cannot // handle RETURN struct(multiple registers)->SIMD16(one register), but it can be improved. LclVarDsc* fieldDsc = comp->lvaGetDesc(lvFieldLclStart); if (fieldDsc->TypeGet() == TYP_SIMD12 || fieldDsc->TypeGet() == TYP_SIMD16) { +#if defined(TARGET_ARM64) return false; +#else // !TARGET_ARM64 + // TODO-X64-CQ: it will be reachable after https://github.com/dotnet/runtime/issues/9578. + unreached(); +#endif //! TARGET_ARM64 } #endif // FEATURE_SIMD diff --git a/src/coreclr/src/jit/lower.cpp b/src/coreclr/src/jit/lower.cpp index 0283725f327cb..5f6faaa69fd5a 100644 --- a/src/coreclr/src/jit/lower.cpp +++ b/src/coreclr/src/jit/lower.cpp @@ -245,7 +245,7 @@ GenTree* Lowering::LowerNode(GenTree* node) if (node->AsBlk()->Data()->IsCall()) { assert(!comp->compDoOldStructRetyping()); - LowerStoreCallStruct(node->AsBlk()); + LowerStoreSingleRegCallStruct(node->AsBlk()); break; } __fallthrough; @@ -2939,12 +2939,30 @@ void Lowering::LowerRet(GenTreeUnOp* ret) JITDUMP("============"); GenTree* retVal = ret->gtGetOp1(); - bool needBitcast = + // There are two kinds of retyping: + // - A simple bitcast can be inserted when: + // - We're returning a floating type as an integral type or vice-versa, or + // - We're returning a struct as a primitive type and using the old form of retyping. + // - If we're returning a struct as a primitive type and *not* using old retying, we change the type of + // 'retval' in 'LowerRetStructLclVar()' + bool needBitcast = (ret->TypeGet() != TYP_VOID) && (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(ret->gtGetOp1())); - if (needBitcast && !varTypeIsStruct(ret) && (!varTypeIsStruct(retVal) || comp->compDoOldStructRetyping())) + if (needBitcast && ((!varTypeIsStruct(ret) && !varTypeIsStruct(retVal)) || comp->compDoOldStructRetyping())) { - // Add a simple bitcast for an old retyping or when both types are not structs. - // If one type is a struct it will be handled below for !compDoOldStructRetyping. +// Add a simple bitcast for an old retyping or when both types are not structs. +// If one type is a struct it will be handled below for !compDoOldStructRetyping. +#if defined(DEBUG) + if (comp->compDoOldStructRetyping()) + { + assert(varTypeIsSIMD(ret) || !!varTypeIsStruct(ret)); + assert(varTypeIsSIMD(retVal) || !!varTypeIsStruct(retVal)); + } + else + { + assert(!varTypeIsStruct(ret) && !varTypeIsStruct(retVal)); + } +#endif + GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), retVal); ret->gtOp1 = bitcast; BlockRange().InsertBefore(ret, bitcast); @@ -2973,22 +2991,15 @@ void Lowering::LowerRet(GenTreeUnOp* ret) assert(!comp->compDoOldStructRetyping()); assert(comp->info.compRetNativeType != TYP_STRUCT); - bool constStructInit = retVal->IsConstInitVal(); - bool actualTypesMatch = false; var_types retActualType = genActualType(comp->info.compRetNativeType); var_types retValActualType = genActualType(retVal->TypeGet()); - if (retActualType == retValActualType) - { - // This could happen if we have retyped op1 as a primitive type during struct promotion, - // check `retypedFieldsMap` for details. - actualTypesMatch = true; - } - bool implicitCastFromSameOrBiggerSize = false; - if (genTypeSize(retActualType) <= genTypeSize(retValActualType)) - { - implicitCastFromSameOrBiggerSize = true; - } + bool constStructInit = retVal->IsConstInitVal(); + bool implicitCastFromSameOrBiggerSize = (genTypeSize(retActualType) <= genTypeSize(retValActualType)); + + // This could happen if we have retyped op1 as a primitive type during struct promotion, + // check `retypedFieldsMap` for details. + bool actualTypesMatch = (retActualType == retValActualType); assert(actualTypesMatch || constStructInit || implicitCastFromSameOrBiggerSize); } @@ -3015,7 +3026,7 @@ void Lowering::LowerRet(GenTreeUnOp* ret) // Return struct as a primitive using Unsafe cast. assert(!comp->compDoOldStructRetyping()); assert(retVal->OperIs(GT_LCL_VAR)); - LowerRetStructLclVar(ret); + LowerRetSingleRegStructLclVar(ret); } } } @@ -3048,7 +3059,7 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) if (!dstIsMultiReg && varTypeIsStruct(varDsc)) { - // TODO: we want to check `varDsc->lvRegStruct` as the last condition instead of `!varDsc->lvPromoted`, + // TODO-Cleanup: we want to check `varDsc->lvRegStruct` as the last condition instead of `!varDsc->lvPromoted`, // but we do not set it for `CSE` vars so it is currently failing. assert(varDsc->CanBeReplacedWithItsField(comp) || varDsc->lvDoNotEnregister || !varDsc->lvPromoted); if (varDsc->CanBeReplacedWithItsField(comp)) @@ -3094,8 +3105,7 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) } CheckMultiRegLclVar(lclStore->AsLclVar(), retTypeDesc); } - if ((lclStore->TypeGet() == TYP_STRUCT) && !srcIsMultiReg && varTypeIsStruct(lclStore->TypeGet()) && - (src->OperGet() != GT_PHI)) + if ((lclStore->TypeGet() == TYP_STRUCT) && !srcIsMultiReg && (src->OperGet() != GT_PHI)) { if (src->OperGet() == GT_CALL) { @@ -3210,7 +3220,7 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) { assert(retVal->OperIs(GT_LCL_VAR)); assert(!comp->compDoOldStructRetyping()); - LowerRetStructLclVar(ret); + LowerRetSingleRegStructLclVar(ret); } return; } @@ -3260,7 +3270,7 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) break; case GT_LCL_VAR: - LowerRetStructLclVar(ret); + LowerRetSingleRegStructLclVar(ret); break; #if defined(FEATURE_SIMD) || defined(FEATURE_HW_INTRINSICS) @@ -3297,8 +3307,10 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) #if defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_ARM) case GT_CNS_DBL: - // Currently we are not promoting structs with a single float field. - // TODO: can improve `GT_CNS_DBL` handling for supported platforms, but + // Currently we are not promoting structs with a single float field, + // https://github.com/dotnet/runtime/issues/4323 + + // TODO-CQ: can improve `GT_CNS_DBL` handling for supported platforms, but // because it is only x86 nowadays it is not worth it. unreached(); #endif @@ -3317,7 +3329,7 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) } //---------------------------------------------------------------------------------------------- -// LowerRetStructLclVar: Lowers a return node with a struct lclVar as a source. +// LowerRetSingleRegStructLclVar: Lowers a return node with a struct lclVar as a source. // // Arguments: // node - The return node to lower. @@ -3327,7 +3339,7 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) // - if LclVar is allocated in memory then read it as return type; // - if LclVar can be enregistered read it as register type and add a bitcast if necessary; // -void Lowering::LowerRetStructLclVar(GenTreeUnOp* ret) +void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret) { assert(!comp->compMethodReturnsMultiRegRegTypeAlternate()); assert(!comp->compDoOldStructRetyping()); @@ -3342,6 +3354,7 @@ void Lowering::LowerRetStructLclVar(GenTreeUnOp* ret) // We can replace the struct with its only field and keep the field on a register. unsigned fieldLclNum = varDsc->lvFieldLclStart; LclVarDsc* fieldDsc = comp->lvaGetDesc(fieldLclNum); + assert(varTypeIsSmallInt(fieldDsc->lvType)); // For a non-small type it had to be done in morph. lclVar->SetLclNum(fieldLclNum); JITDUMP("Replacing an independently promoted local var V%02u with its only field V%02u for the return " @@ -3469,20 +3482,22 @@ void Lowering::LowerCallStruct(GenTreeCall* call) } //---------------------------------------------------------------------------------------------- -// LowerStoreCallStruct: Lowers a store block where source is a struct typed call. +// LowerStoreSingleRegCallStruct: Lowers a store block where the source is a struct typed call. // // Arguments: // store - The store node to lower. // // Notes: -// - it spills the call's result if it can be retyped as a primitive type. +// - the function is only for calls that return one register; +// - it spills the call's result if it can be retyped as a primitive type; // -void Lowering::LowerStoreCallStruct(GenTreeBlk* store) +void Lowering::LowerStoreSingleRegCallStruct(GenTreeBlk* store) { assert(!comp->compDoOldStructRetyping()); assert(varTypeIsStruct(store)); assert(store->Data()->IsCall()); GenTreeCall* call = store->Data()->AsCall(); + assert(!call->HasMultiRegRetVal()); const ClassLayout* layout = store->GetLayout(); const var_types regType = layout->GetRegisterType(); diff --git a/src/coreclr/src/jit/lower.h b/src/coreclr/src/jit/lower.h index 49b72630eb707..592f363993844 100644 --- a/src/coreclr/src/jit/lower.h +++ b/src/coreclr/src/jit/lower.h @@ -134,9 +134,9 @@ class Lowering final : public Phase void LowerRet(GenTreeUnOp* ret); void LowerStoreLocCommon(GenTreeLclVarCommon* lclVar); void LowerRetStruct(GenTreeUnOp* ret); - void LowerRetStructLclVar(GenTreeUnOp* ret); + void LowerRetSingleRegStructLclVar(GenTreeUnOp* ret); void LowerCallStruct(GenTreeCall* call); - void LowerStoreCallStruct(GenTreeBlk* store); + void LowerStoreSingleRegCallStruct(GenTreeBlk* store); #if !defined(WINDOWS_AMD64_ABI) GenTreeLclVar* SpillStructCallResult(GenTreeCall* call) const; #endif // WINDOWS_AMD64_ABI diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index b67531a101fa5..e4fdf05e5fc7f 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -11985,30 +11985,36 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) { op1 = fgMorphRetInd(tree->AsUnOp()); } - if (op1->OperIs(GT_LCL_VAR) && (genReturnBB == nullptr)) + if (op1->OperIs(GT_LCL_VAR)) { - // With a `genReturnBB` it will be done via field by field asignment. + // With a `genReturnBB` this `RETURN(src)` tree will be replaced by a `ASG(genReturnLocal, src)` + // and `ASG` will be tranformed into field by field copy without parent local referencing if + // possible. GenTreeLclVar* lclVar = op1->AsLclVar(); - LclVarDsc* varDsc = lvaGetDesc(lclVar); - if (varDsc->CanBeReplacedWithItsField(this)) + unsigned lclNum = lclVar->GetLclNum(); + if ((genReturnLocal == BAD_VAR_NUM) || (genReturnLocal == lclNum)) { - // We can replace the struct with its only field and allow copy propogation to replace - // return value that was written as a field. - unsigned fieldLclNum = varDsc->lvFieldLclStart; - LclVarDsc* fieldDsc = lvaGetDesc(fieldLclNum); - - if (!varTypeIsSmallInt(fieldDsc->lvType)) + LclVarDsc* varDsc = lvaGetDesc(lclVar); + if (varDsc->CanBeReplacedWithItsField(this)) { - // TODO: support that substitution for small types without creating `CAST` node. - // When a small struct is returned in a register higher bits could be left in undefined - // state. - 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); - var_types fieldType = fieldDsc->lvType; - lclVar->ChangeType(fieldDsc->lvType); + // We can replace the struct with its only field and allow copy propogation to replace + // return value that was written as a field. + unsigned fieldLclNum = varDsc->lvFieldLclStart; + LclVarDsc* fieldDsc = lvaGetDesc(fieldLclNum); + + if (!varTypeIsSmallInt(fieldDsc->lvType)) + { + // TODO-CQ: support that substitution for small types without creating `CAST` node. + // When a small struct is returned in a register higher bits could be left in undefined + // state. + 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); + var_types fieldType = fieldDsc->lvType; + lclVar->ChangeType(fieldDsc->lvType); + } } } } From 0d8cf178e5f575ac09bb0da4fc34bec3fd031ae6 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Thu, 2 Jul 2020 23:37:33 -0700 Subject: [PATCH 07/10] isOpaqueSIMDLclVar fix --- src/coreclr/src/jit/lclvars.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/coreclr/src/jit/lclvars.cpp b/src/coreclr/src/jit/lclvars.cpp index 1680e5cf6ca6a..2216fda4dd7d2 100644 --- a/src/coreclr/src/jit/lclvars.cpp +++ b/src/coreclr/src/jit/lclvars.cpp @@ -3680,7 +3680,6 @@ bool LclVarDsc::CanBeReplacedWithItsField(Compiler* comp) const } #if defined(FEATURE_SIMD) - assert(!comp->isOpaqueSIMDLclVar(this)); // opaque SIMD can't be promoted. // If we return `struct A { SIMD16 a; }` we split the struct into several fields. // In order to do that we have to have its field `a` in memory. Right now lowering cannot // handle RETURN struct(multiple registers)->SIMD16(one register), but it can be improved. @@ -3688,10 +3687,13 @@ bool LclVarDsc::CanBeReplacedWithItsField(Compiler* comp) const if (fieldDsc->TypeGet() == TYP_SIMD12 || fieldDsc->TypeGet() == TYP_SIMD16) { #if defined(TARGET_ARM64) - return false; + if (!comp->isOpaqueSIMDLclVar(fieldDsc)) + { + return false; + } #else // !TARGET_ARM64 - // TODO-X64-CQ: it will be reachable after https://github.com/dotnet/runtime/issues/9578. - unreached(); + // TODO-X64-CQ: check for `isOpaqueSIMDLclVar` after https://github.com/dotnet/runtime/issues/9578. + return false; #endif //! TARGET_ARM64 } #endif // FEATURE_SIMD From c16fc9f73cb08ea9fd29223ca112dd2f62536cd1 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Mon, 6 Jul 2020 17:14:11 -0700 Subject: [PATCH 08/10] Add tests for structs with independently promoted SIMD fields. --- .../JIT/Directed/StructABI/structreturn.cs | 110 +++++++++++++++++- 1 file changed, 106 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tests/src/JIT/Directed/StructABI/structreturn.cs b/src/coreclr/tests/src/JIT/Directed/StructABI/structreturn.cs index da4738a021a23..7b8b9492e6e40 100644 --- a/src/coreclr/tests/src/JIT/Directed/StructABI/structreturn.cs +++ b/src/coreclr/tests/src/JIT/Directed/StructABI/structreturn.cs @@ -1078,7 +1078,7 @@ static Doubles4Wrapper ReturnDoubles4Wrapper() struct Vector2Wrapper { - Vector2 f1; + public Vector2 f1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -1087,9 +1087,20 @@ static Vector2Wrapper ReturnVector2Wrapper() return new Vector2Wrapper(); } + [MethodImpl(MethodImplOptions.NoInlining)] + static Vector2Wrapper ReturnVector2WrapperPromoted() + { + var a = new Vector2Wrapper + { + f1 = Vector2.Zero + }; + + return a; + } + struct Vector3Wrapper { - Vector3 f1; + public Vector3 f1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -1098,9 +1109,20 @@ static Vector3Wrapper ReturnVector3Wrapper() return new Vector3Wrapper(); } + [MethodImpl(MethodImplOptions.NoInlining)] + static Vector3Wrapper ReturnVector3WrapperPromoted() + { + var a = new Vector3Wrapper + { + f1 = Vector3.Zero + }; + + return a; + } + struct Vector4Wrapper { - Vector4 f1; + public Vector4 f1; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -1109,6 +1131,17 @@ static Vector4Wrapper ReturnVector4Wrapper() return new Vector4Wrapper(); } + [MethodImpl(MethodImplOptions.NoInlining)] + static Vector4Wrapper ReturnVector4WrapperPromoted() + { + var a = new Vector4Wrapper + { + f1 = Vector4.Zero + }; + + return a; + } + struct Vector2x2Wrapper { Vector2 f1; @@ -1133,8 +1166,11 @@ static void TestReturnPrimitivesInWrappers() ReturnFloats4Wrapper(); ReturnDoubles4Wrapper(); ReturnVector2Wrapper(); + ReturnVector2WrapperPromoted(); ReturnVector3Wrapper(); + ReturnVector3WrapperPromoted(); ReturnVector4Wrapper(); + ReturnVector4WrapperPromoted(); ReturnVector2x2Wrapper(); } @@ -1226,7 +1262,7 @@ static Vector ReturnVectorInt2(Vector left, Vector right) where T struct VectorShortWrapper { - Vector f; + public Vector f; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -1235,6 +1271,16 @@ static VectorShortWrapper ReturnVectorShortWrapper() return new VectorShortWrapper(); } + [MethodImpl(MethodImplOptions.NoInlining)] + static VectorShortWrapper ReturnVectorShortWrapperPromoted() + { + var a = new VectorShortWrapper() + { + f = Vector.Zero + }; + return a; + } + struct VectorLongWrapper { Vector f; @@ -1355,6 +1401,7 @@ static void TestReturnVectorT() ReturnVectorTWithMerge>(3, new Vector(0), new Vector(0), new Vector(0), new Vector(0)); ReturnVectorShortWrapper(); + ReturnVectorShortWrapperPromoted(); ReturnVectorLongWrapper(); ReturnVectorDoubleWrapper(); ReturnVectorTWrapper(); @@ -1508,6 +1555,60 @@ static void TestReturnVector256(int v) } } + public struct Vector64Wrapped + { + public Vector64 f; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector64Wrapped TestReturnVector64WrappedPromoted() + { + var a = new Vector64Wrapped + { + f = Vector64.Zero + }; + return a; + } + + public struct Vector128Wrapped + { + public Vector128 f; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128Wrapped TestReturnVector128WrappedPromoted() + { + var a = new Vector128Wrapped + { + f = Vector128.Zero + }; + return a; + } + + public struct Vector256Wrapped + { + public Vector256 f; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector256Wrapped TestReturnVector256WrappedPromoted() + { + var a = new Vector256Wrapped + { + f = Vector256.Zero + }; + return a; + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void TestReturnVectorNWrappedPromoted() + { + TestReturnVector64WrappedPromoted(); + TestReturnVector128WrappedPromoted(); + TestReturnVector256WrappedPromoted(); + } + [MethodImpl(MethodImplOptions.NoInlining)] public static void Test() { @@ -1517,6 +1618,7 @@ public static void Test() TestReturnVector64(1); TestReturnVector128(1); TestReturnVector256(1); + TestReturnVectorNWrappedPromoted(); } } From e0fbd5fbdde9201bdb2ada040e4f8e60a0462baa Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Mon, 6 Jul 2020 17:21:04 -0700 Subject: [PATCH 09/10] Old retyping fix. --- src/coreclr/src/jit/lower.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/coreclr/src/jit/lower.cpp b/src/coreclr/src/jit/lower.cpp index 5f6faaa69fd5a..aa7c9c3c19cb9 100644 --- a/src/coreclr/src/jit/lower.cpp +++ b/src/coreclr/src/jit/lower.cpp @@ -2947,15 +2947,31 @@ void Lowering::LowerRet(GenTreeUnOp* ret) // 'retval' in 'LowerRetStructLclVar()' bool needBitcast = (ret->TypeGet() != TYP_VOID) && (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(ret->gtGetOp1())); - if (needBitcast && ((!varTypeIsStruct(ret) && !varTypeIsStruct(retVal)) || comp->compDoOldStructRetyping())) + bool doPrimitiveBitcast = false; + if (needBitcast) + { + if (comp->compDoOldStructRetyping()) + { + // `struct A { SIMD12/16 }` on `UNIX_AMD64_ABI` is an example when + // `varTypeUsesFloatReg` returns different values for `ret` and `ret->gtGetOp1()`, + // but doesn't need a primitive bitcase. + doPrimitiveBitcast = !ret->TypeIs(TYP_STRUCT); + } + else + { + doPrimitiveBitcast = (!varTypeIsStruct(ret) && !varTypeIsStruct(retVal)); + } + } + + if (doPrimitiveBitcast) { // Add a simple bitcast for an old retyping or when both types are not structs. // If one type is a struct it will be handled below for !compDoOldStructRetyping. #if defined(DEBUG) if (comp->compDoOldStructRetyping()) { - assert(varTypeIsSIMD(ret) || !!varTypeIsStruct(ret)); - assert(varTypeIsSIMD(retVal) || !!varTypeIsStruct(retVal)); + assert(varTypeIsSIMD(ret) || !varTypeIsStruct(ret)); + assert(varTypeIsSIMD(retVal) || !varTypeIsStruct(retVal)); } else { From 9daf9c1e9db930195a50c4f81d92285a43244e35 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Mon, 6 Jul 2020 19:14:24 -0700 Subject: [PATCH 10/10] Don't try to replace SIMD fields. --- src/coreclr/src/jit/lclvars.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/coreclr/src/jit/lclvars.cpp b/src/coreclr/src/jit/lclvars.cpp index 2216fda4dd7d2..ff2657df34c8f 100644 --- a/src/coreclr/src/jit/lclvars.cpp +++ b/src/coreclr/src/jit/lclvars.cpp @@ -3684,17 +3684,9 @@ bool LclVarDsc::CanBeReplacedWithItsField(Compiler* comp) const // In order to do that we have to have its field `a` in memory. Right now lowering cannot // handle RETURN struct(multiple registers)->SIMD16(one register), but it can be improved. LclVarDsc* fieldDsc = comp->lvaGetDesc(lvFieldLclStart); - if (fieldDsc->TypeGet() == TYP_SIMD12 || fieldDsc->TypeGet() == TYP_SIMD16) + if (varTypeIsSIMD(fieldDsc)) { -#if defined(TARGET_ARM64) - if (!comp->isOpaqueSIMDLclVar(fieldDsc)) - { - return false; - } -#else // !TARGET_ARM64 - // TODO-X64-CQ: check for `isOpaqueSIMDLclVar` after https://github.com/dotnet/runtime/issues/9578. return false; -#endif //! TARGET_ARM64 } #endif // FEATURE_SIMD