From b324afc3e3175a41e53e46d8dfd9d5916908b348 Mon Sep 17 00:00:00 2001 From: Brian Sullivan Date: Wed, 27 Apr 2016 15:53:15 -0700 Subject: [PATCH 1/2] StructPromote_0428 Enable Struct promotion for 16-byte structs on Arm64 --- src/jit/codegenarm64.cpp | 45 +++++++++++-- src/jit/compiler.h | 3 +- src/jit/gentree.cpp | 29 ++++++-- src/jit/lowerarm64.cpp | 51 +++++++++------ src/jit/morph.cpp | 138 +++++++++++++++++++++------------------ src/jit/target.h | 2 +- 6 files changed, 171 insertions(+), 97 deletions(-) diff --git a/src/jit/codegenarm64.cpp b/src/jit/codegenarm64.cpp index 0791c7337141..7b1b2fa92b15 100644 --- a/src/jit/codegenarm64.cpp +++ b/src/jit/codegenarm64.cpp @@ -6445,6 +6445,7 @@ CodeGen::genIntrinsic(GenTreePtr treeNode) // void CodeGen::genPutArgStk(GenTreePtr treeNode) { + assert(treeNode->OperGet() == GT_PUTARG_STK); var_types targetType = treeNode->TypeGet(); emitter *emit = getEmitter(); @@ -6493,8 +6494,9 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) { varNum = compiler->lvaOutgoingArgSpaceVar; } + bool isStruct = (targetType == TYP_STRUCT) || (data->OperGet() == GT_LIST); - if (targetType != TYP_STRUCT) // a normal non-Struct argument + if (!isStruct) // a normal non-Struct argument { instruction storeIns = ins_Store(targetType); emitAttr storeAttr = emitTypeSize(targetType); @@ -6512,7 +6514,7 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) emit->emitIns_S_R(storeIns, storeAttr, data->gtRegNum, varNum, argOffset); } } - else // We have a TYP_STRUCT argument (it also must be a 16-byte multi-reg struct) + else // We have a TYP_STRUCT argument (it currently must be a 16-byte multi-reg struct) { // We will use two store instructions that each write a register sized value @@ -6520,12 +6522,14 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) assert(curArgTabEntry->numSlots == 2); assert(data->isContained()); // We expect that this node was marked as contained in LowerArm64 - // In lowerArm64 we reserved two internal integer registers for this 16-byte TYP_STRUCT regNumber loReg = REG_NA; regNumber hiReg = REG_NA; - genGetRegPairFromMask(treeNode->gtRsvdRegs, &loReg, &hiReg); - assert(loReg != REG_NA); - assert(hiReg != REG_NA); + + if (data->OperGet() != GT_LIST) + { + // In lowerArm64 we reserved two internal integer registers for this 16-byte TYP_STRUCT + genGetRegPairFromMask(treeNode->gtRsvdRegs, &loReg, &hiReg); + } // We will need to record the GC type used by each of the load instructions // so that we use the same type in each of the store instructions @@ -6697,6 +6701,31 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) } } } + else if (data->OperGet() == GT_LIST) + { + // Deal with multi register passed struct args. + GenTreeArgList* argListPtr = data->AsArgList(); + unsigned iterationNum = 0; + for (; argListPtr != nullptr; argListPtr = argListPtr->Rest(), iterationNum++) + { + GenTreePtr nextArgNode = argListPtr->gtOp.gtOp1; + genConsumeReg(nextArgNode); + + if (iterationNum == 0) + { + // record loReg and type0 for the store to the out arg space + loReg = nextArgNode->gtRegNum; + type0 = nextArgNode->TypeGet(); + } + else + { + assert(iterationNum == 1); + // record hiReg and type1 for the store to the out arg space + hiReg = nextArgNode->gtRegNum;; + type1 = nextArgNode->TypeGet(); + } + } + } if ((data->OperGet() == GT_LCL_VAR) || (data->OperGet() == GT_LCL_VAR_ADDR)) { @@ -6719,6 +6748,10 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) emit->emitIns_R_S(ins_Load(type1), emitTypeSize(type1), hiReg, varNum, TARGET_POINTER_SIZE); } + // We are required to set these two values above + assert(loReg != REG_NA); + assert(hiReg != REG_NA); + // We are required to set these two values above, so that the stores have the same GC type as the loads assert(type0 != TYP_UNKNOWN); assert(type1 != TYP_UNKNOWN); diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 841856cebce3..ff1b0d4c12b7 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -2031,6 +2031,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void gtGetArgMsg (GenTreePtr call, GenTreePtr arg, unsigned argNum, + int listCount, char* bufp, unsigned bufLength); void gtGetLateArgMsg (GenTreePtr call, @@ -8817,7 +8818,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) void fgMorphMultiregStructArgs(GenTreeCall* call); - GenTreePtr fgMorphMultiregStructArg (GenTreePtr arg); + GenTreePtr fgMorphMultiregStructArg (GenTreePtr arg, fgArgTabEntryPtr fgEntryPtr); }; // end of class Compiler diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index 1f22f377eaa3..5dbdfe31f06d 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -8717,6 +8717,8 @@ void Compiler::gtDispTree(GenTreePtr tree, // call - The call for which 'arg' is an argument // arg - The argument for which a message should be constructed // argNum - The ordinal number of the arg in the argument list +// listCount - When printing in Linear form this is the count for a multireg GT_LIST +// or -1 if we are not printing in Linear form // bufp - A pointer to the buffer into which the message is written // bufLength - The length of the buffer pointed to by bufp // @@ -8730,14 +8732,14 @@ void Compiler::gtDispTree(GenTreePtr tree, void Compiler::gtGetArgMsg(GenTreePtr call, GenTreePtr arg, unsigned argNum, + int listCount, char* bufp, unsigned bufLength) { if (call->gtCall.gtCallLateArgs != NULL) { - fgArgTabEntryPtr curArgTabEntry = gtArgEntryByNode(call, arg); + fgArgTabEntryPtr curArgTabEntry = gtArgEntryByArgNum(call, argNum); assert(curArgTabEntry); - assert(curArgTabEntry->argNum == argNum); if (arg->gtFlags & GTF_LATE_ARG) { @@ -8746,7 +8748,19 @@ void Compiler::gtGetArgMsg(GenTreePtr call, else { #if FEATURE_FIXED_OUT_ARGS - sprintf_s(bufp, bufLength, "arg%d in out+%02x%c", argNum, curArgTabEntry->slotNum * TARGET_POINTER_SIZE, 0); + if (listCount == -1) + { + sprintf_s(bufp, bufLength, "arg%d out+%02x%c", argNum, curArgTabEntry->slotNum * TARGET_POINTER_SIZE, 0); + } + else if (listCount == 1) + { + sprintf_s(bufp, bufLength, "arg%d hi +%02x%c", argNum, (curArgTabEntry->slotNum + 1) * TARGET_POINTER_SIZE, 0); + } + else + { + assert(listCount == 0); + sprintf_s(bufp, bufLength, "arg%d lo +%02x%c", argNum, (curArgTabEntry->slotNum + 0) * TARGET_POINTER_SIZE, 0); + } #else sprintf_s(bufp, bufLength, "arg%d on STK%c", argNum, 0); #endif @@ -8869,7 +8883,7 @@ void Compiler::gtDispArgList(GenTreePtr tree, GenTree* arg = args->gtOp.gtOp1; if (!arg->IsNothingNode() && !arg->IsArgPlaceHolderNode()) { - gtGetArgMsg(tree, arg, argnum, bufp, BufLength); + gtGetArgMsg(tree, arg, argnum, -1, bufp, BufLength); if (argListIsLastChild && (args->gtOp.gtOp2 == nullptr)) { arcType = IIArcBottom; @@ -9105,13 +9119,14 @@ GenTreePtr Compiler::gtDispLinearTree(GenTreeStmt* curStmt, indentStack->Push(indentInfo); if (child == tree->gtCall.gtCallArgs) { - gtGetArgMsg(tree, listNested, listElemNum, bufp, BufLength); + gtGetArgMsg(tree, listNested, listElemNum, listCount, bufp, BufLength); } else { assert(child == tree->gtCall.gtCallLateArgs); - gtGetLateArgMsg(tree, listNested, listElemNum, listCount++, bufp, BufLength); + gtGetLateArgMsg(tree, listNested, listElemNum, listCount, bufp, BufLength); } + listCount++; nextLinearNode = gtDispLinearTree(curStmt, nextLinearNode, listElemNested, indentStack, bufp); indentStack->Pop(); } @@ -9129,7 +9144,7 @@ GenTreePtr Compiler::gtDispLinearTree(GenTreeStmt* curStmt, if (child == tree->gtCall.gtCallArgs) { - gtGetArgMsg(tree, listElem, listElemNum, bufp, BufLength); + gtGetArgMsg(tree, listElem, listElemNum, -1, bufp, BufLength); } else { diff --git a/src/jit/lowerarm64.cpp b/src/jit/lowerarm64.cpp index 091c4cc7eedc..5c53e253c8c0 100644 --- a/src/jit/lowerarm64.cpp +++ b/src/jit/lowerarm64.cpp @@ -626,7 +626,7 @@ void Lowering::TreeNodeInfoInit(GenTree* stmt) // To avoid redundant moves, request that the argument child tree be // computed in the register in which the argument is passed to the call. - putArgChild ->gtLsraInfo.setSrcCandidates(l, targetMask); + putArgChild->gtLsraInfo.setSrcCandidates(l, targetMask); // We consume one source for each item in this list info->srcCount++; @@ -694,7 +694,7 @@ void Lowering::TreeNodeInfoInit(GenTree* stmt) { GenTreePtr arg = args->gtOp.gtOp1; - // Skip arguments that havew been moved to the Late Arg list + // Skip arguments that have been moved to the Late Arg list if (!(args->gtFlags & GTF_LATE_ARG)) { if (arg->gtOper == GT_PUTARG_STK) @@ -1040,33 +1040,46 @@ void Lowering::TreeNodeInfoInitPutArgStk(GenTree* argNode, fgArgTabEntryPtr info argNode->gtLsraInfo.srcCount = 1; argNode->gtLsraInfo.dstCount = 0; - // Do we have a TYP_STRUCT argument, if so it must be a 16-byte pass-by-value struct - if (putArgChild->TypeGet() == TYP_STRUCT) + // Do we have a TYP_STRUCT argument (or a GT_LIST), if so it must be a 16-byte pass-by-value struct + if ((putArgChild->TypeGet() == TYP_STRUCT) || (putArgChild->OperGet() == GT_LIST)) { // We will use two store instructions that each write a register sized value // We must have a multi-reg struct assert(info->numSlots >= 2); - // We can use a ldp/stp sequence so we need two internal registers - argNode->gtLsraInfo.internalIntCount = 2; - - if (putArgChild->OperGet() == GT_OBJ) + if (putArgChild->OperGet() == GT_LIST) + { + // We consume all of the items in the GT_LIST + argNode->gtLsraInfo.srcCount = info->numSlots; + } + else { - GenTreePtr objChild = putArgChild->gtOp.gtOp1; - if (objChild->OperGet() == GT_LCL_VAR_ADDR) + // We could use a ldp/stp sequence so we need two internal registers + argNode->gtLsraInfo.internalIntCount = 2; + + if (putArgChild->OperGet() == GT_OBJ) { - // We will generate all of the code for the GT_PUTARG_STK, the GT_OBJ and the GT_LCL_VAR_ADDR - // as one contained operation - // - MakeSrcContained(putArgChild, objChild); + GenTreePtr objChild = putArgChild->gtOp.gtOp1; + if (objChild->OperGet() == GT_LCL_VAR_ADDR) + { + // We will generate all of the code for the GT_PUTARG_STK, the GT_OBJ and the GT_LCL_VAR_ADDR + // as one contained operation + // + MakeSrcContained(putArgChild, objChild); + } } - } - // We will generate all of the code for the GT_PUTARG_STK and it's child node - // as one contained operation - // - MakeSrcContained(argNode, putArgChild); + // We will generate all of the code for the GT_PUTARG_STK and it's child node + // as one contained operation + // + MakeSrcContained(argNode, putArgChild); + } + } + else + { + // We must not have a multi-reg struct + assert(info->numSlots == 1); } } diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 75f09309e739..b08bc38080e2 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -4309,41 +4309,14 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) { foundStructArg = true; - // We don't create GT_LIST for any multireg TYP_STRUCT arguments - if (fgEntryPtr->regNum == REG_STK) - { - continue; - } - - arg = fgMorphMultiregStructArg(arg); + arg = fgMorphMultiregStructArg(arg, fgEntryPtr); // Did we replace 'argx' with a new tree? if (arg != argx) { - bool isLateArg = (args->gtOp.gtOp1->gtFlags & GTF_LATE_ARG) != 0; - fgArgTabEntryPtr fgEntryPtr = gtArgEntryByNode(call, args->gtOp.gtOp1); - assert(fgEntryPtr != nullptr); - GenTreePtr argx = fgEntryPtr->node; - GenTreePtr lateList = nullptr; - GenTreePtr lateNode = nullptr; - if (isLateArg) - { - for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) - { - assert(list->IsList()); - - GenTreePtr argNode = list->Current(); - if (argx == argNode) - { - lateList = list; - lateNode = argNode; - break; - } - } - assert(lateList != nullptr && lateNode != nullptr); - } + fgEntryPtr->node = arg; // Record the new value for the arg in the fgEntryPtr->node - fgEntryPtr->node = arg; + // link the new arg node into either the late arg list or the gtCallArgs list if (isLateArg) { lateList->gtOp.gtOp1 = arg; @@ -4385,7 +4358,7 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) // Currently the implementation only handles ARM64 and will NYI for other architectures. // And for ARM64 we do not ye handle HFA arguments, so only 16-byte struct sizes are supported. // -GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg) +GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr fgEntryPtr) { GenTreeArgList* newArg = nullptr; assert(arg->TypeGet() == TYP_STRUCT); @@ -4444,43 +4417,76 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg) var_types loType = loVarDsc->lvType; var_types hiType = hiVarDsc->lvType; - GenTreePtr loLclVar = gtNewLclvNode(loVarNum, loType, loVarNum); - GenTreePtr hiLclVar = gtNewLclvNode(hiVarNum, hiType, hiVarNum); + if (varTypeIsFloating(loType) || varTypeIsFloating(hiType)) + { + // TODO-LSRA - It currently doesn't support the passing of floating point LCL_VARS in the integer registers + // So for now we will use GT_LCLFLD's to pass this struct (it won't be enregistered) + // + JITDUMP("Multireg struct V%02u will be passed using GT_LCLFLD because it has float fields.\n", varNum); + // + // we call lvaSetVarDoNotEnregister and do the proper transformation below. + // + } + else + { + // We can use the struct promoted field as the two arguments - // Create a new tree for 'arg' - // replace the existing LDOBJ(ADDR(LCLVAR)) - // with a LIST(LCLVAR-LO, LIST(LCLVAR-HI, nullptr)) - // - newArg = gtNewListNode(loLclVar, gtNewArgList(hiLclVar)); + GenTreePtr loLclVar = gtNewLclvNode(loVarNum, loType, loVarNum); + GenTreePtr hiLclVar = gtNewLclvNode(hiVarNum, hiType, hiVarNum); + + // Create a new tree for 'arg' + // replace the existing LDOBJ(ADDR(LCLVAR)) + // with a LIST(LCLVAR-LO, LIST(LCLVAR-HI, nullptr)) + // + newArg = gtNewListNode(loLclVar, gtNewArgList(hiLclVar)); + } } } + + // Check if we couldn't transform the LDOBJ(ADDR(LCLVAR)) into a struct promoted GT_LIST above if (newArg == nullptr) { - GenTreeLclVarCommon* varNode = argValue->AsLclVarCommon(); - unsigned varNum = varNode->gtLclNum; - assert(varNum < lvaCount); - LclVarDsc* varDsc = &lvaTable[varNum]; - // - // We weren't able to pass this LclVar using it's struct promted fields + // We weren't able to pass this LclVar using it's struct promted fields // - // Instead we will create a list of GT_LCL_FLDs nodes to pass this struct + // So instead we will create a list of GT_LCL_FLDs nodes to pass this struct // lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DNER_LocalField)); - GenTreePtr loLclFld = gtNewLclFldNode(varNum, type0, 0); - GenTreePtr hiLclFld = gtNewLclFldNode(varNum, type1, TARGET_POINTER_SIZE); - - // Create a new tree for 'arg' - // replace the existing LDOBJ(ADDR(LCLVAR)) - // with a LIST(LCLFLD-LO, LIST(LCLFLD-HI, nullptr)) + // If this is going in the register area, we transform it here into a GT_LIST of LCLFLD's + // If this is going in the outgoing arg area, it will be transformed later // - newArg = gtNewListNode(loLclFld, gtNewArgList(hiLclFld)); + if (fgEntryPtr->regNum != REG_STK) + { + GenTreeLclVarCommon* varNode = argValue->AsLclVarCommon(); + unsigned varNum = varNode->gtLclNum; + assert(varNum < lvaCount); + LclVarDsc* varDsc = &lvaTable[varNum]; + + GenTreePtr loLclFld = gtNewLclFldNode(varNum, type0, 0); + GenTreePtr hiLclFld = gtNewLclFldNode(varNum, type1, TARGET_POINTER_SIZE); + + // Create a new tree for 'arg' + // replace the existing LDOBJ(ADDR(LCLVAR)) + // with a LIST(LCLFLD-LO, LIST(LCLFLD-HI, nullptr)) + // + newArg = gtNewListNode(loLclFld, gtNewArgList(hiLclFld)); + } } } + // Check if we already created a replacement newArg above + if (newArg == nullptr) + { + if (fgEntryPtr->regNum == REG_STK) + { + // We leave this stack passed argument alone + return arg; + } + } + // Are we passing a GT_LCL_FLD which contain a 16-byte struct inside it? // - else if (argValue->OperGet() == GT_LCL_FLD) + if (argValue->OperGet() == GT_LCL_FLD) { GenTreeLclVarCommon* varNode = argValue->AsLclVarCommon(); unsigned varNum = varNode->gtLclNum; @@ -4544,13 +4550,16 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg) // newArg = gtNewListNode(loValue, gtNewArgList(hiValue)); } - else + + // If we reach here we should have set newArg to something + if (newArg == nullptr) { +#ifdef DEBUG + gtDispTree(argValue); +#endif assert(!"Missing case in fgMorphMultiregStructArg"); } - assert(newArg != nullptr); - #ifdef DEBUG if (verbose) { @@ -15601,6 +15610,7 @@ void Compiler::fgPromoteStructs() #if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) // TODO-PERF - Only do this when the LclVar is used in an argument context // TODO-ARM64 - HFA support should also eliminate the need for this. + // TODO-LSRA - Currently doesn't support the passing of floating point LCL_VARS in the integer registers // // For now we currently don't promote structs with a single float field // Promoting it can cause us to shuffle it back and forth between the int and @@ -15631,14 +15641,15 @@ void Compiler::fgPromoteStructs() if (varDsc->lvIsParam) { #if FEATURE_MULTIREG_STRUCT_PROMOTE - if (varDsc->lvIsMultiRegArgOrRet) // Is this argument variable holding a value passed in multiple registers? + + if (varDsc->lvIsMultiregStruct() && // Is this a variable holding a value that is passed in multiple registers? + (structPromotionInfo.fieldCnt != 2) && // An argument with two exactly two fields + false) // TODO-PERF - Disabled as this needs additional implementation: + // it hits assert(lvFieldCnt==1) in lclvar.cpp line 4417 { - if (structPromotionInfo.fieldCnt != 2) - { - JITDUMP("Not promoting multireg struct local V%02u, because lvIsParam is true and #fields = %d.\n", - lclNum, structPromotionInfo.fieldCnt); - continue; - } + JITDUMP("Not promoting multireg struct local V%02u, because lvIsParam is true and #fields = %d.\n", + lclNum, structPromotionInfo.fieldCnt); + continue; } else #endif // !FEATURE_MULTIREG_STRUCT_PROMOTE @@ -15649,6 +15660,7 @@ void Compiler::fgPromoteStructs() continue; } } + // // If the lvRefCnt is zero and we have a struct promoted parameter we can end up with an extra store of the the // incoming register into the stack frame slot. diff --git a/src/jit/target.h b/src/jit/target.h index 674dc12f5e09..3213f4205fa3 100644 --- a/src/jit/target.h +++ b/src/jit/target.h @@ -1450,7 +1450,7 @@ typedef unsigned short regPairNoSmall; // arm: need 12 bits #define FEATURE_WRITE_BARRIER 1 // Generate the proper WriteBarrier calls for GC #define FEATURE_FIXED_OUT_ARGS 1 // Preallocate the outgoing arg area in the prolog #define FEATURE_STRUCTPROMOTE 1 // JIT Optimization to promote fields of structs into registers - #define FEATURE_MULTIREG_STRUCT_PROMOTE 0 // True when we want to promote fields of a multireg struct into registers + #define FEATURE_MULTIREG_STRUCT_PROMOTE 1 // True when we want to promote fields of a multireg struct into registers #define FEATURE_FASTTAILCALL 0 // Tail calls made as epilog+jmp #define FEATURE_TAILCALL_OPT 0 // opportunistic Tail calls (i.e. without ".tail" prefix) made as fast tail calls. #define FEATURE_SET_FLAGS 1 // Set to true to force the JIT to mark the trees with GTF_SET_FLAGS when the flags need to be set From 3409b720cb88934d7ee9dd1201357db926c26c4f Mon Sep 17 00:00:00 2001 From: Brian Sullivan Date: Thu, 28 Apr 2016 11:49:29 -0700 Subject: [PATCH 2/2] Updated morph.cpp --- src/jit/morph.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index b08bc38080e2..5a33736871bf 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -15640,19 +15640,19 @@ void Compiler::fgPromoteStructs() if (varDsc->lvIsParam) { -#if FEATURE_MULTIREG_STRUCT_PROMOTE - - if (varDsc->lvIsMultiregStruct() && // Is this a variable holding a value that is passed in multiple registers? - (structPromotionInfo.fieldCnt != 2) && // An argument with two exactly two fields - false) // TODO-PERF - Disabled as this needs additional implementation: - // it hits assert(lvFieldCnt==1) in lclvar.cpp line 4417 +#if FEATURE_MULTIREG_STRUCT_PROMOTE + if (varDsc->lvIsMultiregStruct() && // Is this a variable holding a value that is passed in multiple registers? + (structPromotionInfo.fieldCnt != 2)) // Does it have exactly two fields { - JITDUMP("Not promoting multireg struct local V%02u, because lvIsParam is true and #fields = %d.\n", - lclNum, structPromotionInfo.fieldCnt); + JITDUMP("Not promoting multireg struct local V%02u, because lvIsParam is true and #fields != 2\n", + lclNum); continue; } - else #endif // !FEATURE_MULTIREG_STRUCT_PROMOTE + + // TODO-PERF - Implement struct promotion for incoming multireg structs + // Currently it hits assert(lvFieldCnt==1) in lclvar.cpp line 4417 + if (structPromotionInfo.fieldCnt != 1) { JITDUMP("Not promoting promotable struct local V%02u, because lvIsParam is true and #fields = %d.\n",