diff options
Diffstat (limited to 'src/jit/morph.cpp')
-rw-r--r-- | src/jit/morph.cpp | 1656 |
1 files changed, 873 insertions, 783 deletions
diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 00df17baa0..678bb34c54 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -204,6 +204,9 @@ GenTreePtr Compiler::fgMorphCast(GenTreePtr tree) { case TYP_INT: #ifdef _TARGET_X86_ // there is no rounding convert to integer instruction on ARM or x64 so skip this +#ifdef LEGACY_BACKEND + // the RyuJIT backend does not use the x87 FPU and therefore + // does not support folding the cast conv.i4(round.d(d)) if ((oper->gtOper == GT_INTRINSIC) && (oper->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Round)) { @@ -212,7 +215,9 @@ GenTreePtr Compiler::fgMorphCast(GenTreePtr tree) return fgMorphTree(oper); } // if SSE2 is not enabled, we need the helper - else if (!opts.compCanUseSSE2) + else +#endif // LEGACY_BACKEND + if (!opts.compCanUseSSE2) { return fgMorphCastIntoHelper(tree, CORINFO_HELP_DBL2INT, oper); } @@ -360,8 +365,17 @@ GenTreePtr Compiler::fgMorphCast(GenTreePtr tree) oper = gtNewCastNode(TYP_LONG, oper, TYP_LONG); oper->gtFlags |= (tree->gtFlags & (GTF_OVERFLOW | GTF_EXCEPT | GTF_UNSIGNED)); tree->gtFlags &= ~GTF_UNSIGNED; +#ifndef LEGACY_BACKEND + return fgMorphCastIntoHelper(tree, CORINFO_HELP_LNG2DBL, oper); +#endif } } +#ifndef LEGACY_BACKEND + else if (((tree->gtFlags & GTF_UNSIGNED) == 0) && (srcType == TYP_LONG) && varTypeIsFloating(dstType)) + { + return fgMorphCastIntoHelper(tree, CORINFO_HELP_LNG2DBL, oper); + } +#endif #endif //_TARGET_XARCH_ else if (varTypeIsGC(srcType) != varTypeIsGC(dstType)) { @@ -1010,12 +1024,12 @@ fgArgInfo::fgArgInfo(GenTreePtr newCall, GenTreePtr oldCall) { /* Get hold of the next argument values for the oldCall and newCall */ - assert(newArgs->IsList()); + assert(newArgs->OperIsList()); newCurr = newArgs->Current(); newArgs = newArgs->Rest(); - assert(oldArgs->IsList()); + assert(oldArgs->OperIsList()); oldCurr = oldArgs->Current(); oldArgs = oldArgs->Rest(); @@ -1047,6 +1061,8 @@ fgArgInfo::fgArgInfo(GenTreePtr newCall, GenTreePtr oldCall) argCount = oldArgInfo->argCount; nextSlotNum = oldArgInfo->nextSlotNum; + hasRegArgs = oldArgInfo->hasRegArgs; + hasStackArgs = oldArgInfo->hasStackArgs; argsComplete = true; argsSorted = true; } @@ -1188,7 +1204,7 @@ fgArgTabEntry* fgArgInfo::RemorphRegArg( GenTreePtr argx; if (curArgTabEntry->parent != nullptr) { - assert(curArgTabEntry->parent->IsList()); + assert(curArgTabEntry->parent->OperIsList()); argx = curArgTabEntry->parent->Current(); isRegArg = (argx->gtFlags & GTF_LATE_ARG) != 0; } @@ -1255,7 +1271,7 @@ void fgArgInfo::RemorphStkArg( if (curArgTabEntry->parent != nullptr) { - assert(curArgTabEntry->parent->IsList()); + assert(curArgTabEntry->parent->OperIsList()); argx = curArgTabEntry->parent->Current(); isRegArg = (argx->gtFlags & GTF_LATE_ARG) != 0; } @@ -1283,7 +1299,7 @@ void fgArgInfo::RemorphStkArg( assert(curArgTabEntry->numSlots == numSlots); assert(curArgTabEntry->alignment == alignment); assert(curArgTabEntry->parent == parent); - assert(parent->IsList()); + assert(parent->OperIsList()); #if FEATURE_FIXED_OUT_ARGS if (curArgTabEntry->node != node) @@ -1512,7 +1528,7 @@ void fgArgInfo::ArgsComplete() #ifndef LEGACY_BACKEND #if FEATURE_MULTIREG_ARGS - // For RyuJIT backend we will expand a Multireg arg into a GT_LIST + // For RyuJIT backend we will expand a Multireg arg into a GT_FIELD_LIST // with multiple indirections, so here we consider spilling it into a tmp LclVar. // // Note that Arm32 is a LEGACY_BACKEND and it defines FEATURE_MULTIREG_ARGS @@ -2364,7 +2380,7 @@ void fgArgInfo::EvalArgsToTemps() { GenTreePtr parent = curArgTabEntry->parent; /* a normal argument from the list */ - noway_assert(parent->IsList()); + noway_assert(parent->OperIsList()); noway_assert(parent->gtOp.gtOp1 == argx); parent->gtOp.gtOp1 = setupArg; @@ -2387,7 +2403,7 @@ void fgArgInfo::EvalArgsToTemps() } else { - noway_assert(tmpRegArgNext->IsList()); + noway_assert(tmpRegArgNext->OperIsList()); noway_assert(tmpRegArgNext->Current()); tmpRegArgNext->gtOp.gtOp2 = compiler->gtNewArgList(defArg); tmpRegArgNext = tmpRegArgNext->Rest(); @@ -2603,7 +2619,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) unsigned argSlots = 0; unsigned nonRegPassedStructSlots = 0; - bool lateArgsComputed = (call->gtCallLateArgs != nullptr); + bool reMorphing = call->AreArgsComplete(); bool callHasRetBuffArg = call->HasRetBufArg(); #ifndef _TARGET_X86_ // i.e. _TARGET_AMD64_ or _TARGET_ARM_ @@ -2731,7 +2747,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // Process the late arguments (which were determined by a previous caller). // Do this before resetting fgPtrArgCntCur as fgMorphTree(call->gtCallLateArgs) // may need to refer to it. - if (lateArgsComputed) + if (reMorphing) { // We need to reMorph the gtCallLateArgs early since that is what triggers // the expression folding and we need to have the final folded gtCallLateArgs @@ -2745,14 +2761,17 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // // Since the late arguments are evaluated last we have pushed all of the // other arguments on the stack before we evaluate these late arguments, - // so we record the stack depth on the first morph call when lateArgsComputed + // so we record the stack depth on the first morph call when reMorphing // was false (via RecordStkLevel) and then retrieve that value here (via RetrieveStkLevel) // unsigned callStkLevel = call->fgArgInfo->RetrieveStkLevel(); - fgPtrArgCntCur += callStkLevel; - call->gtCallLateArgs = fgMorphTree(call->gtCallLateArgs)->AsArgList(); - flagsSummary |= call->gtCallLateArgs->gtFlags; - fgPtrArgCntCur -= callStkLevel; + if (call->gtCallLateArgs != nullptr) + { + fgPtrArgCntCur += callStkLevel; + call->gtCallLateArgs = fgMorphTree(call->gtCallLateArgs)->AsArgList(); + flagsSummary |= call->gtCallLateArgs->gtFlags; + fgPtrArgCntCur -= callStkLevel; + } assert(call->fgArgInfo != nullptr); call->fgArgInfo->RemorphReset(); @@ -2780,7 +2799,8 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // *********** END NOTE ********* CLANG_FORMAT_COMMENT_ANCHOR; -#if !defined(LEGACY_BACKEND) && defined(_TARGET_X86_) +#if !defined(LEGACY_BACKEND) +#if defined(_TARGET_X86_) // The x86 CORINFO_HELP_INIT_PINVOKE_FRAME helper has a custom calling convention. Set the argument registers // correctly here. if (call->IsHelperCall(this, CORINFO_HELP_INIT_PINVOKE_FRAME)) @@ -2792,21 +2812,20 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } // The x86 shift helpers have custom calling conventions and expect the lo part of the long to be in EAX and the // hi part to be in EDX. This sets the argument registers up correctly. - else if (call->IsHelperCall(this, CORINFO_HELP_LLSH) || call->IsHelperCall(this, CORINFO_HELP_LRSH) || call->IsHelperCall(this, CORINFO_HELP_LRSZ)) + else if (call->IsHelperCall(this, CORINFO_HELP_LLSH) || call->IsHelperCall(this, CORINFO_HELP_LRSH) || + call->IsHelperCall(this, CORINFO_HELP_LRSZ)) { GenTreeArgList* args = call->gtCallArgs; - GenTree* arg1 = args->Current(); + GenTree* arg1 = args->Current(); assert(arg1 != nullptr); nonStandardArgs.Add(arg1, REG_LNGARG_LO); - args = args->Rest(); + args = args->Rest(); GenTree* arg2 = args->Current(); assert(arg2 != nullptr); nonStandardArgs.Add(arg2, REG_LNGARG_HI); } -#endif // !defined(LEGACY_BACKEND) && defined(_TARGET_X86_) - -#if !defined(LEGACY_BACKEND) && !defined(_TARGET_X86_) +#else // !defined(_TARGET_X86_) // TODO-X86-CQ: Currently RyuJIT/x86 passes args on the stack, so this is not needed. // If/when we change that, the following code needs to be changed to correctly support the (TBD) managed calling // convention for x86/SSE. @@ -2817,7 +2836,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) { args = call->gtCallArgs; assert(args != nullptr); - assert(args->IsList()); + assert(args->OperIsList()); argx = call->gtCallArgs->Current(); @@ -2871,21 +2890,32 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) nonStandardArgs.Add(arg, REG_VIRTUAL_STUB_PARAM); } - else if (call->gtCallType == CT_INDIRECT && call->gtCallCookie) + else +#endif // defined(_TARGET_X86_) + if (call->gtCallType == CT_INDIRECT && (call->gtCallCookie != nullptr)) { assert(!call->IsUnmanaged()); - // put cookie into R11 GenTree* arg = call->gtCallCookie; noway_assert(arg != nullptr); call->gtCallCookie = nullptr; +#if defined(_TARGET_X86_) + // x86 passes the cookie on the stack as the final argument to the call. + GenTreeArgList** insertionPoint = &call->gtCallArgs; + for (; *insertionPoint != nullptr; insertionPoint = &(*insertionPoint)->Rest()) + { + } + *insertionPoint = gtNewListNode(arg, nullptr); +#else // !defined(_TARGET_X86_) + // All other architectures pass the cookie in a register. call->gtCallArgs = gtNewListNode(arg, call->gtCallArgs); - numArgs++; +#endif // defined(_TARGET_X86_) nonStandardArgs.Add(arg, REG_PINVOKE_COOKIE_PARAM); + numArgs++; - // put destination into R10 + // put destination into R10/EAX arg = gtClone(call->gtCallAddr, true); call->gtCallArgs = gtNewListNode(arg, call->gtCallArgs); numArgs++; @@ -2896,7 +2926,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) call->gtCallType = CT_HELPER; call->gtCallMethHnd = eeFindHelper(CORINFO_HELP_PINVOKE_CALLI); } -#endif // !defined(LEGACY_BACKEND) && !defined(_TARGET_X86_) +#endif // !defined(LEGACY_BACKEND) // Allocate the fgArgInfo for the call node; // @@ -2929,7 +2959,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) /* We must fill in or update the argInfo table */ - if (lateArgsComputed) + if (reMorphing) { /* this is a register argument - possibly update it in the table */ call->fgArgInfo->RemorphRegArg(argIndex, argx, nullptr, genMapIntRegArgNumToRegNum(intArgRegNum), 1, 1); @@ -3075,7 +3105,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) *parentArgx = argx; flagsSummary |= argx->gtFlags; - assert(args->IsList()); + assert(args->OperIsList()); assert(argx == args->Current()); #ifndef LEGACY_BACKEND @@ -3114,13 +3144,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) compFloatingPointUsed = true; } - unsigned size = 0; - CORINFO_CLASS_HANDLE copyBlkClass = nullptr; - bool isRegArg = false; + unsigned size = 0; + CORINFO_CLASS_HANDLE copyBlkClass = nullptr; + bool isRegArg = false; + bool isNonStandard = false; + regNumber nonStdRegNum = REG_NA; fgArgTabEntryPtr argEntry = nullptr; - if (lateArgsComputed) + if (reMorphing) { argEntry = gtArgEntryByArgNum(call, argIndex); } @@ -3128,7 +3160,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #ifdef _TARGET_ARM_ bool passUsingIntRegs; - if (lateArgsComputed) + if (reMorphing) { passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum); passUsingIntRegs = isValidIntArgReg(argEntry->regNum); @@ -3179,7 +3211,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #elif defined(_TARGET_ARM64_) - if (lateArgsComputed) + if (reMorphing) { passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum); } @@ -3189,8 +3221,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } #elif defined(_TARGET_AMD64_) -#if defined(UNIX_AMD64_ABI) - if (lateArgsComputed) + if (reMorphing) { passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum); } @@ -3198,9 +3229,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) { passUsingFloatRegs = varTypeIsFloating(argx); } -#else // WINDOWS_AMD64_ABI - passUsingFloatRegs = varTypeIsFloating(argx); -#endif // !UNIX_AMD64_ABI #elif defined(_TARGET_X86_) passUsingFloatRegs = false; @@ -3216,7 +3244,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) bool isStructArg = varTypeIsStruct(argx); - if (lateArgsComputed) + if (reMorphing) { #if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) // Get the struct description for the already completed struct argument. @@ -3260,7 +3288,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // This size has now been computed assert(size != 0); } - else // !lateArgsComputed + else // !reMorphing { // // Figure out the size of the argument. This is either in number of registers, or number of @@ -3287,7 +3315,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } } #else // !FEATURE_UNIX_AMD64_STRUCT_PASSING - size = 1; // On AMD64, all primitives fit in a single (64-bit) 'slot' + size = 1; // On AMD64, all primitives fit in a single (64-bit) 'slot' #endif // FEATURE_UNIX_AMD64_STRUCT_PASSING #elif defined(_TARGET_ARM64_) if (isStructArg) @@ -3379,7 +3407,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) GenTreePtr argObj = argx; GenTreePtr* parentOfArgObj = parentArgx; - assert(args->IsList()); + assert(args->OperIsList()); assert(argx == args->Current()); /* The GT_OBJ may be be a child of a GT_COMMA */ @@ -3686,11 +3714,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // the obj reading memory past the end of the valuetype CLANG_FORMAT_COMMENT_ANCHOR; -#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) - // TODO-X86-CQ: [1091733] Revisit for small structs, we should use push instruction - copyBlkClass = objClass; - size = roundupSize / TARGET_POINTER_SIZE; // Normalize size to number of pointer sized items -#else // !defined(_TARGET_X86_) || defined(LEGACY_BACKEND) if (roundupSize > originalSize) { copyBlkClass = objClass; @@ -3705,7 +3728,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } size = roundupSize / TARGET_POINTER_SIZE; // Normalize size to number of pointer sized items -#endif // !defined(_TARGET_X86_) || defined(LEGACY_BACKEND) } } } @@ -3841,7 +3863,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } } #else // !defined(UNIX_AMD64_ABI) - isRegArg = (intArgRegNum + (size - 1)) < maxRegArgs; + isRegArg = (intArgRegNum + (size - 1)) < maxRegArgs; #endif // !defined(UNIX_AMD64_ABI) #endif // _TARGET_ARM_ } @@ -3850,8 +3872,19 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) isRegArg = false; } -#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) - if (call->IsTailCallViaHelper()) +#ifndef LEGACY_BACKEND + // If there are nonstandard args (outside the calling convention) they were inserted above + // and noted them in a table so we can recognize them here and build their argInfo. + // + // They should not affect the placement of any other args or stack space required. + // Example: on AMD64 R10 and R11 are used for indirect VSD (generic interface) and cookie calls. + isNonStandard = nonStandardArgs.FindReg(argx, &nonStdRegNum); + if (isNonStandard && (nonStdRegNum == REG_STK)) + { + isRegArg = false; + } +#if defined(_TARGET_X86_) + else if (call->IsTailCallViaHelper()) { // We have already (before calling fgMorphArgs()) appended the 4 special args // required by the x86 tailcall helper. These args are required to go on the @@ -3862,9 +3895,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) isRegArg = false; } } -#endif // defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) - - } // end !lateArgsComputed +#endif // defined(_TARGET_X86_) +#endif // !LEGACY_BACKEND + } // end !reMorphing // // Now we know if the argument goes in registers or not and how big it is, @@ -3943,23 +3976,17 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #endif fgArgTabEntryPtr newArgEntry; - if (lateArgsComputed) + if (reMorphing) { // This is a register argument - possibly update it in the table newArgEntry = call->fgArgInfo->RemorphRegArg(argIndex, argx, args, nextRegNum, size, argAlign); } else { - bool isNonStandard = false; - -#ifndef LEGACY_BACKEND - // If there are nonstandard args (outside the calling convention) they were inserted above - // and noted them in a table so we can recognize them here and build their argInfo. - // - // They should not affect the placement of any other args or stack space required. - // Example: on AMD64 R10 and R11 are used for indirect VSD (generic interface) and cookie calls. - isNonStandard = nonStandardArgs.FindReg(argx, &nextRegNum); -#endif // !LEGACY_BACKEND + if (isNonStandard) + { + nextRegNum = nonStdRegNum; + } // This is a register argument - put it in the table newArgEntry = call->fgArgInfo->AddRegArg(argIndex, argx, args, nextRegNum, size, argAlign @@ -4053,7 +4080,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // If the register arguments have not been determined then we must fill in the argInfo - if (lateArgsComputed) + if (reMorphing) { // This is a stack argument - possibly update it in the table call->fgArgInfo->RemorphStkArg(argIndex, argx, args, size, argAlign); @@ -4068,14 +4095,14 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) if (copyBlkClass != NO_CLASS_HANDLE) { - noway_assert(!lateArgsComputed); + noway_assert(!reMorphing); fgMakeOutgoingStructArgCopy(call, args, argIndex, copyBlkClass FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(&structDesc)); // This can cause a GTF_EXCEPT flag to be set. // TODO-CQ: Fix the cases where this happens. We shouldn't be adding any new flags. // This currently occurs in the case where we are re-morphing the args on x86/RyuJIT, and - // there are no register arguments. Then lateArgsComputed is never true, so we keep re-copying + // there are no register arguments. Then reMorphing is never true, so we keep re-copying // any struct arguments. // i.e. assert(((call->gtFlags & GTF_EXCEPT) != 0) || ((args->Current()->gtFlags & GTF_EXCEPT) == 0) flagsSummary |= (args->Current()->gtFlags & GTF_EXCEPT); @@ -4088,10 +4115,21 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #ifndef LEGACY_BACKEND if (argx->gtOper == GT_MKREFANY) { - NYI_X86("MKREFANY"); - // 'Lower' the MKREFANY tree and insert it. - noway_assert(!lateArgsComputed); + noway_assert(!reMorphing); + +#ifdef _TARGET_X86_ + + // Build the mkrefany as a GT_FIELD_LIST + GenTreeFieldList* fieldList = new (this, GT_FIELD_LIST) + GenTreeFieldList(argx->gtOp.gtOp1, offsetof(CORINFO_RefAny, dataPtr), TYP_BYREF, nullptr); + (void)new (this, GT_FIELD_LIST) + GenTreeFieldList(argx->gtOp.gtOp2, offsetof(CORINFO_RefAny, type), TYP_I_IMPL, fieldList); + fgArgTabEntryPtr fp = Compiler::gtArgEntryByNode(call, argx); + fp->node = fieldList; + args->gtOp.gtOp1 = fieldList; + +#else // !_TARGET_X86_ // Get a new temp // Here we don't need unsafe value cls check since the addr of temp is used only in mkrefany @@ -4117,9 +4155,47 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // EvalArgsToTemps will cause tmp to actually get loaded as the argument call->fgArgInfo->EvalToTmp(argIndex, tmp, asg); lvaSetVarAddrExposed(tmp); +#endif // !_TARGET_X86_ } #endif // !LEGACY_BACKEND +#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) + if (isStructArg) + { + GenTree* lclNode = fgIsIndirOfAddrOfLocal(argx); + if ((lclNode != nullptr) && + (lvaGetPromotionType(lclNode->AsLclVarCommon()->gtLclNum) == Compiler::PROMOTION_TYPE_INDEPENDENT)) + { + // Make a GT_FIELD_LIST of the field lclVars. + GenTreeLclVarCommon* lcl = lclNode->AsLclVarCommon(); + LclVarDsc* varDsc = &(lvaTable[lcl->gtLclNum]); + GenTreeFieldList* fieldList = nullptr; + for (unsigned fieldLclNum = varDsc->lvFieldLclStart; + fieldLclNum < varDsc->lvFieldLclStart + varDsc->lvFieldCnt; ++fieldLclNum) + { + LclVarDsc* fieldVarDsc = &lvaTable[fieldLclNum]; + if (fieldList == nullptr) + { + lcl->SetLclNum(fieldLclNum); + lcl->ChangeOper(GT_LCL_VAR); + lcl->gtType = fieldVarDsc->lvType; + fieldList = new (this, GT_FIELD_LIST) + GenTreeFieldList(lcl, fieldVarDsc->lvFldOffset, fieldVarDsc->lvType, nullptr); + fgArgTabEntryPtr fp = Compiler::gtArgEntryByNode(call, argx); + fp->node = fieldList; + args->gtOp.gtOp1 = fieldList; + } + else + { + GenTree* fieldLcl = gtNewLclvNode(fieldLclNum, fieldVarDsc->lvType); + fieldList = new (this, GT_FIELD_LIST) + GenTreeFieldList(fieldLcl, fieldVarDsc->lvFldOffset, fieldVarDsc->lvType, fieldList); + } + } + } + } +#endif // defined (_TARGET_X86_) && !defined(LEGACY_BACKEND) + #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING if (isStructArg && !isRegArg) { @@ -4132,7 +4208,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } } // end foreach argument loop - if (!lateArgsComputed) + if (!reMorphing) { call->fgArgInfo->ArgsComplete(); #ifdef LEGACY_BACKEND @@ -4240,11 +4316,11 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // For UNIX_AMD64, the condition without hasStackArgCopy cannot catch // all cases of fgMakeOutgoingStructArgCopy() being called. hasStackArgCopy // is added to make sure to call EvalArgsToTemp. - if (!lateArgsComputed && (call->fgArgInfo->HasRegArgs() + if (!reMorphing && (call->fgArgInfo->HasRegArgs() #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING - || hasStackArgCopy + || hasStackArgCopy #endif // FEATURE_UNIX_AMD64_STRUCT_PASSING - )) + )) { // This is the first time that we morph this call AND it has register arguments. // Follow into the code below and do the 'defer or eval to temp' analysis. @@ -4271,7 +4347,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // In the future we can migrate UNIX_AMD64 to use this // method instead of fgMorphSystemVStructArgs - // We only build GT_LISTs for MultiReg structs for the RyuJIT backend + // We only build GT_FIELD_LISTs for MultiReg structs for the RyuJIT backend if (hasMultiregStructArgs) { fgMorphMultiregStructArgs(call); @@ -4334,7 +4410,7 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen { for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); if (argx == argNode) @@ -4355,7 +4431,7 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen { var_types originalType = type; // If we have already processed the arg... - if (arg->OperGet() == GT_LIST && varTypeIsStruct(arg)) + if (arg->OperGet() == GT_FIELD_LIST && varTypeIsStruct(arg)) { continue; } @@ -4386,6 +4462,16 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen // Create LCL_FLD for each eightbyte. argListCreated = true; + // First eightbyte. + arg->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField(); + arg->gtType = + GetTypeFromClassificationAndSizes(fgEntryPtr->structDesc.eightByteClassifications[0], + fgEntryPtr->structDesc.eightByteSizes[0]); + GenTreeFieldList* fieldList = + new (this, GT_FIELD_LIST) GenTreeFieldList(arg, 0, originalType, nullptr); + fieldList->gtType = originalType; // Preserve the type. It is a special case. + arg = fieldList; + // Second eightbyte. GenTreeLclFld* newLclField = new (this, GT_LCL_FLD) GenTreeLclFld(GetTypeFromClassificationAndSizes(fgEntryPtr->structDesc @@ -4393,17 +4479,9 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen fgEntryPtr->structDesc.eightByteSizes[1]), lclCommon->gtLclNum, fgEntryPtr->structDesc.eightByteOffsets[1]); - GenTreeArgList* aggregate = gtNewAggregate(newLclField); - aggregate->gtType = originalType; // Preserve the type. It is a special case. - newLclField->gtFieldSeq = FieldSeqStore::NotAField(); - - // First field - arg->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField(); - arg->gtType = - GetTypeFromClassificationAndSizes(fgEntryPtr->structDesc.eightByteClassifications[0], - fgEntryPtr->structDesc.eightByteSizes[0]); - arg = aggregate->Prepend(this, arg); - arg->gtType = type; // Preserve the type. It is a special case. + fieldList = new (this, GT_FIELD_LIST) GenTreeFieldList(newLclField, 0, originalType, fieldList); + fieldList->gtType = originalType; // Preserve the type. It is a special case. + newLclField->gtFieldSeq = FieldSeqStore::NotAField(); } else { @@ -4450,7 +4528,7 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen { for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); if (argx == argNode) @@ -4490,8 +4568,8 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen // // Notes: // We only call fgMorphMultiregStructArg for the register passed TYP_STRUCT arguments. -// The call to fgMorphMultiregStructArg will mutate the argument into the GT_LIST form -// whicj is only used for register arguments. +// The call to fgMorphMultiregStructArg will mutate the argument into the GT_FIELD_LIST form +// which is only used for struct arguments. // If this method fails to find any TYP_STRUCT arguments it will assert. // void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) @@ -4540,7 +4618,7 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) { for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); if (argx == argNode) @@ -4588,7 +4666,7 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) //----------------------------------------------------------------------------- // fgMorphMultiregStructArg: Given a multireg TYP_STRUCT arg from a call argument list -// Morph the argument into a set of GT_LIST nodes. +// Morph the argument into a set of GT_FIELD_LIST nodes. // // Arguments: // arg - A GenTree node containing a TYP_STRUCT arg that @@ -4600,7 +4678,7 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) // for passing in multiple registers. // If arg is a LclVar we check if it is struct promoted and has the right number of fields // and if they are at the appropriate offsets we will use the struct promted fields -// in the GT_LIST nodes that we create. +// in the GT_FIELD_LIST nodes that we create. // If we have a GT_LCL_VAR that isn't struct promoted or doesn't meet the requirements // we will use a set of GT_LCL_FLDs nodes to access the various portions of the struct // this also forces the struct to be stack allocated into the local frame. @@ -4715,7 +4793,7 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // We should still have a TYP_STRUCT assert(argValue->TypeGet() == TYP_STRUCT); - GenTreeArgList* newArg = nullptr; + GenTreeFieldList* newArg = nullptr; // Are we passing a struct LclVar? // @@ -4817,9 +4895,10 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // Create a new tree for 'arg' // replace the existing LDOBJ(ADDR(LCLVAR)) - // with a LIST(LCLVAR-LO, LIST(LCLVAR-HI, nullptr)) + // with a FIELD_LIST(LCLVAR-LO, FIELD_LIST(LCLVAR-HI, nullptr)) // - newArg = gtNewAggregate(hiLclVar)->Prepend(this, loLclVar); + newArg = new (this, GT_FIELD_LIST) GenTreeFieldList(loLclVar, 0, loType, nullptr); + (void)new (this, GT_FIELD_LIST) GenTreeFieldList(hiLclVar, TARGET_POINTER_SIZE, hiType, newArg); } } } @@ -4885,27 +4964,22 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DNER_LocalField)); - // Start building our list from the last element - unsigned offset = lastOffset; - unsigned inx = elemCount; - // Create a new tree for 'arg' // replace the existing LDOBJ(ADDR(LCLVAR)) - // with a LIST(LCLFLD-LO, LIST(LCLFLD-HI, nullptr) ...) + // with a FIELD_LIST(LCLFLD-LO, FIELD_LIST(LCLFLD-HI, nullptr) ...) // - while (inx > 0) + unsigned offset = 0; + GenTreeFieldList* listEntry = nullptr; + for (unsigned inx = 0; inx < elemCount; inx++) { - inx--; - offset -= elemSize; + elemSize = genTypeSize(type[inx]); GenTreePtr nextLclFld = gtNewLclFldNode(varNum, type[inx], offset); + listEntry = new (this, GT_FIELD_LIST) GenTreeFieldList(nextLclFld, offset, type[inx], listEntry); if (newArg == nullptr) { - newArg = gtNewAggregate(nextLclFld); - } - else - { - newArg = newArg->Prepend(this, nextLclFld); + newArg = listEntry; } + offset += elemSize; } } // Are we passing a GT_OBJ struct? @@ -4918,17 +4992,14 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // Create a new tree for 'arg' // replace the existing LDOBJ(EXPR) - // with a LIST(IND(EXPR), LIST(IND(EXPR+8), nullptr) ...) + // with a FIELD_LIST(IND(EXPR), FIELD_LIST(IND(EXPR+8), nullptr) ...) // - // Start building our list from the last element - unsigned offset = structSize; - unsigned inx = elemCount; - while (inx > 0) + unsigned offset = 0; + GenTreeFieldList* listEntry = nullptr; + for (unsigned inx = 0; inx < elemCount; inx++) { - inx--; - elemSize = genTypeSize(type[inx]); - offset -= elemSize; + elemSize = genTypeSize(type[inx]); GenTreePtr curAddr = baseAddr; if (offset != 0) { @@ -4941,14 +5012,21 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f curAddr = baseAddr; } GenTreePtr curItem = gtNewOperNode(GT_IND, type[inx], curAddr); - if (newArg == nullptr) + + // For safety all GT_IND should have at least GT_GLOB_REF set. + curItem->gtFlags |= GTF_GLOB_REF; + if (fgAddrCouldBeNull(curItem)) { - newArg = gtNewAggregate(curItem); + // This indirection can cause a GPF if the address could be null. + curItem->gtFlags |= GTF_EXCEPT; } - else + + listEntry = new (this, GT_FIELD_LIST) GenTreeFieldList(curItem, offset, type[inx], listEntry); + if (newArg == nullptr) { - newArg = newArg->Prepend(this, curItem); + newArg = listEntry; } + offset += elemSize; } } } @@ -5674,7 +5752,7 @@ GenTreePtr Compiler::fgMorphArrayIndex(GenTreePtr tree) addr = gtNewOperNode(GT_ADD, TYP_BYREF, addr, cns); #if SMALL_TREE_NODES - assert(tree->gtDebugFlags & GTF_DEBUG_NODE_LARGE); + assert((tree->gtDebugFlags & GTF_DEBUG_NODE_LARGE) || GenTree::s_gtNodeSizes[GT_IND] == TREE_NODE_SZ_SMALL); #endif // Change the orginal GT_INDEX node into a GT_IND node @@ -5847,7 +5925,15 @@ GenTreePtr Compiler::fgMorphStackArgForVarArgs(unsigned lclNum, var_types varTyp lclOffs)); // Access the argument through the local - GenTreePtr tree = gtNewOperNode(GT_IND, varType, ptrArg); + GenTreePtr tree; + if (varType == TYP_STRUCT) + { + tree = gtNewBlockVal(ptrArg, varDsc->lvExactSize); + } + else + { + tree = gtNewOperNode(GT_IND, varType, ptrArg); + } tree->gtFlags |= GTF_IND_TGTANYWHERE; if (varDsc->lvAddrExposed) @@ -5884,8 +5970,14 @@ GenTreePtr Compiler::fgMorphLocalVar(GenTreePtr tree) if (info.compIsVarArgs) { GenTreePtr newTree = fgMorphStackArgForVarArgs(lclNum, varType, 0); - if (newTree != NULL) + if (newTree != nullptr) + { + if (newTree->OperIsBlk() && ((tree->gtFlags & GTF_VAR_DEF) == 0)) + { + fgMorphBlkToInd(newTree->AsBlk(), newTree->gtType); + } return newTree; + } } #endif // _TARGET_X86_ @@ -6205,7 +6297,9 @@ GenTreePtr Compiler::fgMorphField(GenTreePtr tree, MorphAddrContext* mac) GenTreePtr baseOffset = gtNewIconEmbHndNode(tree->gtField.gtFieldLookup.addr, nullptr, GTF_ICON_FIELD_HDL); if (tree->gtField.gtFieldLookup.accessType == IAT_PVALUE) + { baseOffset = gtNewOperNode(GT_IND, TYP_I_IMPL, baseOffset); + } addr = gtNewOperNode(GT_ADD, (var_types)(objRefType == TYP_I_IMPL ? TYP_I_IMPL : TYP_BYREF), addr, baseOffset); @@ -6483,8 +6577,8 @@ void Compiler::fgMorphCallInline(GenTreeCall* call, InlineResult* inlineResult) // hanging a "nothing" node to it. Later the "nothing" node will be removed // and the original GT_CALL tree will be picked up by the GT_RET_EXPR node. - noway_assert(fgMorphStmt->gtStmt.gtStmtExpr == call); - fgMorphStmt->gtStmt.gtStmtExpr = gtNewNothingNode(); + noway_assert(fgMorphStmt->gtStmtExpr == call); + fgMorphStmt->gtStmtExpr = gtNewNothingNode(); } // Clear the Inline Candidate flag so we can ensure later we tried @@ -6662,7 +6756,7 @@ bool Compiler::fgCanFastTailCall(GenTreeCall* callee) { nCalleeArgs++; - assert(args->IsList()); + assert(args->OperIsList()); GenTreePtr argx = args->gtOp.gtOp1; if (varTypeIsStruct(argx)) @@ -6980,7 +7074,14 @@ void Compiler::fgMorphTailCall(GenTreeCall* call) } #endif // _TARGET_X86_ +#if defined(_TARGET_X86_) + // When targeting x86, the runtime requires that we perforrm a null check on the `this` argument before tail + // calling to a virtual dispatch stub. This requirement is a consequence of limitations in the runtime's + // ability to map an AV to a NullReferenceException if the AV occurs in a dispatch stub. + if (call->NeedsNullCheck() || call->IsVirtualStub()) +#else if (call->NeedsNullCheck()) +#endif // defined(_TARGET_X86_) { // clone "this" if "this" has no side effects. if ((thisPtr == nullptr) && !(objp->gtFlags & GTF_SIDE_EFFECT)) @@ -7668,17 +7769,39 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) } #endif - GenTreePtr stmtExpr = fgMorphStmt->gtStmt.gtStmtExpr; + GenTreePtr stmtExpr = fgMorphStmt->gtStmtExpr; #ifdef DEBUG // Tail call needs to be in one of the following IR forms // Either a call stmt or - // GT_RETURN(GT_CALL(..)) or - // var = call - noway_assert((stmtExpr->gtOper == GT_CALL && stmtExpr == call) || - (stmtExpr->gtOper == GT_RETURN && - (stmtExpr->gtOp.gtOp1 == call || stmtExpr->gtOp.gtOp1->gtOp.gtOp1 == call)) || - (stmtExpr->gtOper == GT_ASG && stmtExpr->gtOp.gtOp2 == call)); + // GT_RETURN(GT_CALL(..)) or GT_RETURN(GT_CAST(GT_CALL(..))) + // var = GT_CALL(..) or var = (GT_CAST(GT_CALL(..))) + genTreeOps stmtOper = stmtExpr->gtOper; + if (stmtOper == GT_CALL) + { + noway_assert(stmtExpr == call); + } + else + { + noway_assert(stmtOper == GT_RETURN || stmtOper == GT_ASG); + GenTreePtr treeWithCall; + if (stmtOper == GT_RETURN) + { + treeWithCall = stmtExpr->gtGetOp1(); + } + else + { + treeWithCall = stmtExpr->gtGetOp2(); + } + if (treeWithCall->gtOper == GT_CAST) + { + noway_assert(treeWithCall->gtGetOp1() == call && !treeWithCall->gtOverflow()); + } + else + { + noway_assert(treeWithCall == call); + } + } #endif // For void calls, we would have created a GT_CALL in the stmt list. @@ -7687,7 +7810,7 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) // For debuggable code, it would be an assignment of the call to a temp // We want to get rid of any of this extra trees, and just leave // the call. - GenTreePtr nextMorphStmt = fgMorphStmt->gtNext; + GenTreeStmt* nextMorphStmt = fgMorphStmt->gtNextStmt; #ifdef _TARGET_AMD64_ // Legacy Jit64 Compat: @@ -7703,46 +7826,46 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) if ((stmtExpr->gtOper == GT_CALL) || (stmtExpr->gtOper == GT_ASG)) { // First delete all GT_NOPs after the call - GenTreePtr morphStmtToRemove = nullptr; + GenTreeStmt* morphStmtToRemove = nullptr; while (nextMorphStmt != nullptr) { - GenTreePtr nextStmtExpr = nextMorphStmt->gtStmt.gtStmtExpr; + GenTreePtr nextStmtExpr = nextMorphStmt->gtStmtExpr; if (!nextStmtExpr->IsNothingNode()) { break; } morphStmtToRemove = nextMorphStmt; - nextMorphStmt = morphStmtToRemove->gtNext; + nextMorphStmt = morphStmtToRemove->gtNextStmt; fgRemoveStmt(compCurBB, morphStmtToRemove); } // Check to see if there is a pop. // Since tail call is honored, we can get rid of the stmt corresponding to pop. - if (nextMorphStmt != nullptr && nextMorphStmt->gtStmt.gtStmtExpr->gtOper != GT_RETURN) + if (nextMorphStmt != nullptr && nextMorphStmt->gtStmtExpr->gtOper != GT_RETURN) { // Note that pop opcode may or may not result in a new stmt (for details see // impImportBlockCode()). Hence, it is not possible to assert about the IR // form generated by pop but pop tree must be side-effect free so that we can // delete it safely. - GenTreePtr popStmt = nextMorphStmt; - nextMorphStmt = nextMorphStmt->gtNext; + GenTreeStmt* popStmt = nextMorphStmt; + nextMorphStmt = nextMorphStmt->gtNextStmt; - noway_assert((popStmt->gtStmt.gtStmtExpr->gtFlags & GTF_ALL_EFFECT) == 0); + noway_assert((popStmt->gtStmtExpr->gtFlags & GTF_ALL_EFFECT) == 0); fgRemoveStmt(compCurBB, popStmt); } // Next delete any GT_NOP nodes after pop while (nextMorphStmt != nullptr) { - GenTreePtr nextStmtExpr = nextMorphStmt->gtStmt.gtStmtExpr; + GenTreePtr nextStmtExpr = nextMorphStmt->gtStmtExpr; if (!nextStmtExpr->IsNothingNode()) { break; } morphStmtToRemove = nextMorphStmt; - nextMorphStmt = morphStmtToRemove->gtNext; + nextMorphStmt = morphStmtToRemove->gtNextStmt; fgRemoveStmt(compCurBB, morphStmtToRemove); } } @@ -7751,7 +7874,7 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) // Delete GT_RETURN if any if (nextMorphStmt != nullptr) { - GenTreePtr retExpr = nextMorphStmt->gtStmt.gtStmtExpr; + GenTreePtr retExpr = nextMorphStmt->gtStmtExpr; noway_assert(retExpr->gtOper == GT_RETURN); // If var=call, then the next stmt must be a GT_RETURN(TYP_VOID) or GT_RETURN(var). @@ -7766,7 +7889,7 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) fgRemoveStmt(compCurBB, nextMorphStmt); } - fgMorphStmt->gtStmt.gtStmtExpr = call; + fgMorphStmt->gtStmtExpr = call; // Tail call via helper: The VM can't use return address hijacking if we're // not going to return and the helper doesn't have enough info to safely poll, @@ -7855,7 +7978,7 @@ NO_TAIL_CALL: || call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR) #endif ) && - (call == fgMorphStmt->gtStmt.gtStmtExpr)) + (call == fgMorphStmt->gtStmtExpr)) { // This is call to CORINFO_HELP_VIRTUAL_FUNC_PTR with ignored result. // Transform it into a null check. @@ -8008,31 +8131,72 @@ NO_TAIL_CALL: // This needs to be done after the arguments are morphed to ensure constant propagation has already taken place. if ((call->gtCallType == CT_HELPER) && (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ARRADDR_ST))) { - GenTreePtr value = gtArgEntryByArgNum(call, 2)->node; - + GenTree* value = gtArgEntryByArgNum(call, 2)->node; if (value->IsIntegralConst(0)) { assert(value->OperGet() == GT_CNS_INT); - GenTreePtr arr = gtArgEntryByArgNum(call, 0)->node; - GenTreePtr index = gtArgEntryByArgNum(call, 1)->node; - arr = gtClone(arr, true); - if (arr != nullptr) + GenTree* arr = gtArgEntryByArgNum(call, 0)->node; + GenTree* index = gtArgEntryByArgNum(call, 1)->node; + + // Either or both of the array and index arguments may have been spilled to temps by `fgMorphArgs`. Copy + // the spill trees as well if necessary. + GenTreeOp* argSetup = nullptr; + for (GenTreeArgList* earlyArgs = call->gtCallArgs; earlyArgs != nullptr; earlyArgs = earlyArgs->Rest()) { - index = gtClone(index, true); - if (index != nullptr) + GenTree* const arg = earlyArgs->Current(); + if (arg->OperGet() != GT_ASG) { - value = gtClone(value); - noway_assert(value != nullptr); + continue; + } + + assert(arg != arr); + assert(arg != index); - GenTreePtr nullCheckedArr = impCheckForNullPointer(arr); - GenTreePtr arrIndexNode = gtNewIndexRef(TYP_REF, nullCheckedArr, index); - GenTreePtr arrStore = gtNewAssignNode(arrIndexNode, value); - arrStore->gtFlags |= GTF_ASG; + arg->gtFlags &= ~GTF_LATE_ARG; - return fgMorphTree(arrStore); + GenTree* op1 = argSetup; + if (op1 == nullptr) + { + op1 = gtNewNothingNode(); +#if DEBUG + op1->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED; +#endif // DEBUG } + + argSetup = new (this, GT_COMMA) GenTreeOp(GT_COMMA, TYP_VOID, op1, arg); + +#if DEBUG + argSetup->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED; +#endif // DEBUG } + +#ifdef DEBUG + auto resetMorphedFlag = [](GenTree** slot, fgWalkData* data) -> fgWalkResult { + (*slot)->gtDebugFlags &= ~GTF_DEBUG_NODE_MORPHED; + return WALK_CONTINUE; + }; + + fgWalkTreePost(&arr, resetMorphedFlag); + fgWalkTreePost(&index, resetMorphedFlag); + fgWalkTreePost(&value, resetMorphedFlag); +#endif // DEBUG + + GenTree* const nullCheckedArr = impCheckForNullPointer(arr); + GenTree* const arrIndexNode = gtNewIndexRef(TYP_REF, nullCheckedArr, index); + GenTree* const arrStore = gtNewAssignNode(arrIndexNode, value); + arrStore->gtFlags |= GTF_ASG; + + GenTree* result = fgMorphTree(arrStore); + if (argSetup != nullptr) + { + result = new (this, GT_COMMA) GenTreeOp(GT_COMMA, TYP_VOID, argSetup, result); +#if DEBUG + result->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED; +#endif // DEBUG + } + + return result; } } @@ -8187,8 +8351,14 @@ GenTreePtr Compiler::fgMorphLeaf(GenTreePtr tree) { GenTreePtr newTree = fgMorphStackArgForVarArgs(tree->gtLclFld.gtLclNum, tree->gtType, tree->gtLclFld.gtLclOffs); - if (newTree != NULL) + if (newTree != nullptr) + { + if (newTree->OperIsBlk() && ((tree->gtFlags & GTF_VAR_DEF) == 0)) + { + fgMorphBlkToInd(newTree->AsBlk(), newTree->gtType); + } return newTree; + } } } #endif // _TARGET_X86_ @@ -8390,7 +8560,7 @@ GenTreePtr Compiler::fgMorphOneAsgBlockOp(GenTreePtr tree) // with the bits to create a single assigment. noway_assert(size <= REGSIZE_BYTES); - if (isInitBlock && (src->gtOper != GT_CNS_INT)) + if (isInitBlock && !src->IsConstInitVal()) { return nullptr; } @@ -8563,8 +8733,12 @@ GenTreePtr Compiler::fgMorphOneAsgBlockOp(GenTreePtr tree) } else #endif - if (src->IsCnsIntOrI()) { + if (src->OperIsInitVal()) + { + src = src->gtGetOp1(); + } + assert(src->IsCnsIntOrI()); // This will mutate the integer constant, in place, to be the correct // value for the type we are using in the assignment. src->AsIntCon()->FixupInitBlkValue(asgType); @@ -8632,7 +8806,8 @@ GenTreePtr Compiler::fgMorphOneAsgBlockOp(GenTreePtr tree) GenTreePtr Compiler::fgMorphInitBlock(GenTreePtr tree) { - noway_assert(tree->gtOper == GT_ASG && varTypeIsStruct(tree)); + // We must have the GT_ASG form of InitBlkOp. + noway_assert((tree->OperGet() == GT_ASG) && tree->OperIsInitBlkOp()); #ifdef DEBUG bool morphed = false; #endif // DEBUG @@ -8647,6 +8822,12 @@ GenTreePtr Compiler::fgMorphInitBlock(GenTreePtr tree) tree->gtOp.gtOp1 = dest; } tree->gtType = dest->TypeGet(); + // (Constant propagation may cause a TYP_STRUCT lclVar to be changed to GT_CNS_INT, and its + // type will be the type of the original lclVar, in which case we will change it to TYP_INT). + if ((src->OperGet() == GT_CNS_INT) && varTypeIsStruct(src)) + { + src->gtType = TYP_INT; + } JITDUMP("\nfgMorphInitBlock:"); GenTreePtr oneAsgTree = fgMorphOneAsgBlockOp(tree); @@ -8658,7 +8839,7 @@ GenTreePtr Compiler::fgMorphInitBlock(GenTreePtr tree) else { GenTree* destAddr = nullptr; - GenTree* initVal = src; + GenTree* initVal = src->OperIsInitVal() ? src->gtGetOp1() : src; GenTree* blockSize = nullptr; unsigned blockWidth = 0; FieldSeqNode* destFldSeq = nullptr; @@ -8727,6 +8908,7 @@ GenTreePtr Compiler::fgMorphInitBlock(GenTreePtr tree) if (destLclVar->lvPromoted && blockWidthIsConst) { + assert(initVal->OperGet() == GT_CNS_INT); noway_assert(varTypeIsStruct(destLclVar)); noway_assert(!opts.MinOpts()); if (destLclVar->lvAddrExposed & destLclVar->lvContainsHoles) @@ -8786,25 +8968,9 @@ GenTreePtr Compiler::fgMorphInitBlock(GenTreePtr tree) #if CPU_USES_BLOCK_MOVE compBlkOpUsed = true; #endif - if (!dest->OperIsBlk()) - { - GenTree* destAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, dest); - CORINFO_CLASS_HANDLE clsHnd = gtGetStructHandleIfPresent(dest); - if (clsHnd == NO_CLASS_HANDLE) - { - dest = new (this, GT_BLK) GenTreeBlk(GT_BLK, dest->TypeGet(), destAddr, blockWidth); - } - else - { - GenTree* newDest = gtNewObjNode(clsHnd, destAddr); - if (newDest->OperGet() == GT_OBJ) - { - gtSetObjGcInfo(newDest->AsObj()); - } - dest = newDest; - } - tree->gtOp.gtOp1 = dest; - } + dest = fgMorphBlockOperand(dest, dest->TypeGet(), blockWidth, true); + tree->gtOp.gtOp1 = dest; + tree->gtFlags |= (dest->gtFlags & GTF_ALL_EFFECT); } else { @@ -9068,9 +9234,18 @@ GenTree* Compiler::fgMorphBlkNode(GenTreePtr tree, bool isDest) if (blkNode->AsDynBlk()->gtDynamicSize->IsCnsIntOrI()) { unsigned size = (unsigned)blkNode->AsDynBlk()->gtDynamicSize->AsIntConCommon()->IconValue(); - blkNode->AsDynBlk()->gtDynamicSize = nullptr; - blkNode->ChangeOper(GT_BLK); - blkNode->gtBlkSize = size; + // A GT_BLK with size of zero is not supported, + // so if we encounter such a thing we just leave it as a GT_DYN_BLK + if (size != 0) + { + blkNode->AsDynBlk()->gtDynamicSize = nullptr; + blkNode->ChangeOper(GT_BLK); + blkNode->gtBlkSize = size; + } + else + { + return tree; + } } else { @@ -9104,7 +9279,7 @@ GenTree* Compiler::fgMorphBlkNode(GenTreePtr tree, bool isDest) // // Notes: // This does the following: -// - Ensures that a struct operand is a block node. +// - Ensures that a struct operand is a block node or (for non-LEGACY_BACKEND) lclVar. // - Ensures that any COMMAs are above ADDR nodes. // Although 'tree' WAS an operand of a block assignment, the assignment // may have been retyped to be a scalar assignment. @@ -9113,10 +9288,6 @@ GenTree* Compiler::fgMorphBlockOperand(GenTree* tree, var_types asgType, unsigne { GenTree* effectiveVal = tree->gtEffectiveVal(); - // TODO-1stClassStucts: We would like to transform non-TYP_STRUCT nodes to - // either plain lclVars or GT_INDs. However, for now we want to preserve most - // of the block nodes until the Rationalizer. - if (!varTypeIsStruct(asgType)) { if (effectiveVal->OperIsIndir()) @@ -9143,69 +9314,141 @@ GenTree* Compiler::fgMorphBlockOperand(GenTree* tree, var_types asgType, unsigne } else { + GenTreeIndir* indirTree = nullptr; + GenTreeLclVarCommon* lclNode = nullptr; + bool needsIndirection = true; + + if (effectiveVal->OperIsIndir()) + { + indirTree = effectiveVal->AsIndir(); + GenTree* addr = effectiveVal->AsIndir()->Addr(); + if ((addr->OperGet() == GT_ADDR) && (addr->gtGetOp1()->OperGet() == GT_LCL_VAR)) + { + lclNode = addr->gtGetOp1()->AsLclVarCommon(); + } + } + else if (effectiveVal->OperGet() == GT_LCL_VAR) + { + lclNode = effectiveVal->AsLclVarCommon(); + } #ifdef FEATURE_SIMD if (varTypeIsSIMD(asgType)) { - if (effectiveVal->OperIsIndir()) + if ((indirTree != nullptr) && (lclNode == nullptr) && (indirTree->Addr()->OperGet() == GT_ADDR) && + (indirTree->Addr()->gtGetOp1()->gtOper == GT_SIMD)) { - GenTree* addr = effectiveVal->AsIndir()->Addr(); - if (!isDest && (addr->OperGet() == GT_ADDR)) - { - if ((addr->gtGetOp1()->gtOper == GT_SIMD) || (addr->gtGetOp1()->OperGet() == GT_LCL_VAR)) - { - effectiveVal = addr->gtGetOp1(); - } - } - else if (isDest && !effectiveVal->OperIsBlk()) - { - effectiveVal = new (this, GT_BLK) GenTreeBlk(GT_BLK, asgType, addr, blockWidth); - } + assert(!isDest); + needsIndirection = false; + effectiveVal = indirTree->Addr()->gtGetOp1(); } - else if (!effectiveVal->OperIsSIMD() && (!effectiveVal->IsLocal() || isDest) && !effectiveVal->OperIsBlk()) + if (effectiveVal->OperIsSIMD()) { - GenTree* addr = gtNewOperNode(GT_ADDR, TYP_BYREF, effectiveVal); - effectiveVal = new (this, GT_BLK) GenTreeBlk(GT_BLK, asgType, addr, blockWidth); + needsIndirection = false; } } - else #endif // FEATURE_SIMD - if (!effectiveVal->OperIsBlk()) + if (lclNode != nullptr) + { + LclVarDsc* varDsc = &(lvaTable[lclNode->gtLclNum]); + if (varTypeIsStruct(varDsc) && (varDsc->lvExactSize == blockWidth)) + { +#ifndef LEGACY_BACKEND + effectiveVal = lclNode; + needsIndirection = false; +#endif // !LEGACY_BACKEND + } + else + { + // This may be a lclVar that was determined to be address-exposed. + effectiveVal->gtFlags |= (lclNode->gtFlags & GTF_ALL_EFFECT); + } + } + if (needsIndirection) { - GenTree* addr = gtNewOperNode(GT_ADDR, TYP_BYREF, effectiveVal); - CORINFO_CLASS_HANDLE clsHnd = gtGetStructHandleIfPresent(effectiveVal); - GenTree* newTree; - if (clsHnd == NO_CLASS_HANDLE) + if (indirTree != nullptr) { - newTree = new (this, GT_BLK) GenTreeBlk(GT_BLK, TYP_STRUCT, addr, blockWidth); + // We should never find a struct indirection on the lhs of an assignment. + assert(!isDest || indirTree->OperIsBlk()); + if (!isDest && indirTree->OperIsBlk()) + { + (void)fgMorphBlkToInd(effectiveVal->AsBlk(), asgType); + } } else { - newTree = gtNewObjNode(clsHnd, addr); - if (isDest && (newTree->OperGet() == GT_OBJ)) + GenTree* newTree; + GenTree* addr = gtNewOperNode(GT_ADDR, TYP_BYREF, effectiveVal); + if (isDest) { - gtSetObjGcInfo(newTree->AsObj()); + CORINFO_CLASS_HANDLE clsHnd = gtGetStructHandleIfPresent(effectiveVal); + if (clsHnd == NO_CLASS_HANDLE) + { + newTree = new (this, GT_BLK) GenTreeBlk(GT_BLK, TYP_STRUCT, addr, blockWidth); + } + else + { + newTree = gtNewObjNode(clsHnd, addr); + if (isDest && (newTree->OperGet() == GT_OBJ)) + { + gtSetObjGcInfo(newTree->AsObj()); + } + if (effectiveVal->IsLocal() && ((effectiveVal->gtFlags & GTF_GLOB_EFFECT) == 0)) + { + // This is not necessarily a global reference, though gtNewObjNode always assumes it is. + // TODO-1stClassStructs: This check should be done in the GenTreeObj constructor, + // where it currently sets GTF_GLOB_EFFECT unconditionally, but it is handled + // separately now to avoid excess diffs. + newTree->gtFlags &= ~(GTF_GLOB_EFFECT); + } + } } - if (effectiveVal->IsLocal() && ((effectiveVal->gtFlags & GTF_GLOB_EFFECT) == 0)) + else { - // This is not necessarily a global reference, though gtNewObjNode always assumes it is. - // TODO-1stClassStructs: This check should be done in the GenTreeObj constructor, - // where it currently sets GTF_GLOB_EFFECT unconditionally, but it is handled - // separately now to avoid excess diffs. - newTree->gtFlags &= ~(GTF_GLOB_EFFECT); + newTree = new (this, GT_IND) GenTreeIndir(GT_IND, asgType, addr, nullptr); } + effectiveVal = newTree; } - effectiveVal = newTree; } } - if (!isDest && effectiveVal->OperIsBlk()) - { - (void)fgMorphBlkToInd(effectiveVal->AsBlk(), asgType); - } tree = effectiveVal; return tree; } //------------------------------------------------------------------------ +// fgMorphUnsafeBlk: Convert a CopyObj with a dest on the stack to a GC Unsafe CopyBlk +// +// Arguments: +// dest - the GT_OBJ or GT_STORE_OBJ +// +// Assumptions: +// The destination must be known (by the caller) to be on the stack. +// +// Notes: +// If we have a CopyObj with a dest on the stack, and its size is small enouch +// to be completely unrolled (i.e. between [16..64] bytes), we will convert it into a +// GC Unsafe CopyBlk that is non-interruptible. +// This is not supported for the JIT32_GCENCODER, in which case this method is a no-op. +// +void Compiler::fgMorphUnsafeBlk(GenTreeObj* dest) +{ +#if defined(CPBLK_UNROLL_LIMIT) && !defined(JIT32_GCENCODER) + assert(dest->gtGcPtrCount != 0); + unsigned blockWidth = dest->AsBlk()->gtBlkSize; +#ifdef DEBUG + bool destOnStack = false; + GenTree* destAddr = dest->Addr(); + assert(destAddr->IsLocalAddrExpr() != nullptr); +#endif + if ((blockWidth >= (2 * TARGET_POINTER_SIZE)) && (blockWidth <= CPBLK_UNROLL_LIMIT)) + { + genTreeOps newOper = (dest->gtOper == GT_OBJ) ? GT_BLK : GT_STORE_BLK; + dest->SetOper(newOper); + dest->AsBlk()->gtBlkOpGcUnsafe = true; // Mark as a GC unsafe copy block + } +#endif // defined(CPBLK_UNROLL_LIMIT) && !defined(JIT32_GCENCODER) +} + +//------------------------------------------------------------------------ // fgMorphCopyBlock: Perform the Morphing of block copy // // Arguments: @@ -9444,6 +9687,14 @@ GenTreePtr Compiler::fgMorphCopyBlock(GenTreePtr tree) bool requiresCopyBlock = false; bool srcSingleLclVarAsg = false; + if ((destLclVar != nullptr) && (srcLclVar == destLclVar)) + { + // Beyond perf reasons, it is not prudent to have a copy of a struct to itself. + GenTree* nop = gtNewNothingNode(); + INDEBUG(nop->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); + return nop; + } + // If either src or dest is a reg-sized non-field-addressed struct, keep the copyBlock. if ((destLclVar != nullptr && destLclVar->lvRegStruct) || (srcLclVar != nullptr && srcLclVar->lvRegStruct)) { @@ -9485,12 +9736,19 @@ GenTreePtr Compiler::fgMorphCopyBlock(GenTreePtr tree) // Are both dest and src promoted structs? if (destDoFldAsg && srcDoFldAsg) { - // Both structs should be of the same type, if not we will use a copy block + // Both structs should be of the same type, or each have a single field of the same type. + // If not we will use a copy block. if (lvaTable[destLclNum].lvVerTypeInfo.GetClassHandle() != lvaTable[srcLclNum].lvVerTypeInfo.GetClassHandle()) { - requiresCopyBlock = true; // Mismatched types, leave as a CopyBlock - JITDUMP(" with mismatched types"); + unsigned destFieldNum = lvaTable[destLclNum].lvFieldLclStart; + unsigned srcFieldNum = lvaTable[srcLclNum].lvFieldLclStart; + if ((lvaTable[destLclNum].lvFieldCnt != 1) || (lvaTable[srcLclNum].lvFieldCnt != 1) || + (lvaTable[destFieldNum].lvType != lvaTable[srcFieldNum].lvType)) + { + requiresCopyBlock = true; // Mismatched types, leave as a CopyBlock + JITDUMP(" with mismatched types"); + } } } // Are neither dest or src promoted structs? @@ -9584,34 +9842,24 @@ GenTreePtr Compiler::fgMorphCopyBlock(GenTreePtr tree) var_types asgType = dest->TypeGet(); dest = fgMorphBlockOperand(dest, asgType, blockWidth, true /*isDest*/); asg->gtOp.gtOp1 = dest; - hasGCPtrs = ((dest->OperGet() == GT_OBJ) && (dest->AsObj()->gtGcPtrCount != 0)); + asg->gtFlags |= (dest->gtFlags & GTF_ALL_EFFECT); -#ifdef CPBLK_UNROLL_LIMIT // Note that the unrolling of CopyBlk is only implemented on some platforms. - // Currently that includes x64 and Arm64 but not x64 or Arm32. + // Currently that includes x64 and ARM but not x86: the code generation for this + // construct requires the ability to mark certain regions of the generated code + // as non-interruptible, and the GC encoding for the latter platform does not + // have this capability. // If we have a CopyObj with a dest on the stack // we will convert it into an GC Unsafe CopyBlk that is non-interruptible - // when its size is small enouch to be completely unrolled (i.e. between [16..64] bytes) + // when its size is small enouch to be completely unrolled (i.e. between [16..64] bytes). + // (This is not supported for the JIT32_GCENCODER, for which fgMorphUnsafeBlk is a no-op.) // - if (hasGCPtrs && destOnStack && blockWidthIsConst && (blockWidth >= (2 * TARGET_POINTER_SIZE)) && - (blockWidth <= CPBLK_UNROLL_LIMIT)) + if (destOnStack && (dest->OperGet() == GT_OBJ)) { - if (dest->OperGet() == GT_OBJ) - { - dest->SetOper(GT_BLK); - dest->AsBlk()->gtBlkOpGcUnsafe = true; // Mark as a GC unsafe copy block - } - else - { - assert(dest->OperIsLocal()); - GenTree* destAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, dest); - dest = new (this, GT_BLK) GenTreeBlk(GT_BLK, dest->TypeGet(), destAddr, blockWidth); - dest->AsBlk()->gtBlkOpGcUnsafe = true; // Mark as a GC unsafe copy block - tree->gtOp.gtOp1 = dest; - } + fgMorphUnsafeBlk(dest->AsObj()); } -#endif + // Eliminate the "OBJ or BLK" node on the rhs. rhs = fgMorphBlockOperand(rhs, asgType, blockWidth, false /*!isDest*/); asg->gtOp.gtOp2 = rhs; @@ -9659,8 +9907,6 @@ GenTreePtr Compiler::fgMorphCopyBlock(GenTreePtr tree) // To do fieldwise assignments for both sides, they'd better be the same struct type! // All of these conditions were checked above... assert(destLclNum != BAD_VAR_NUM && srcLclNum != BAD_VAR_NUM); - assert(lvaTable[destLclNum].lvVerTypeInfo.GetClassHandle() == - lvaTable[srcLclNum].lvVerTypeInfo.GetClassHandle()); assert(destLclVar != nullptr && srcLclVar != nullptr && destLclVar->lvFieldCnt == srcLclVar->lvFieldCnt); fieldCnt = destLclVar->lvFieldCnt; @@ -10354,23 +10600,12 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) /* fgDoNormalizeOnStore can change op2 */ noway_assert(op1 == tree->gtOp.gtOp1); op2 = tree->gtOp.gtOp2; - // TODO-1stClassStructs: this is here to match previous behavior, but results in some - // unnecessary pessimization in the handling of addresses in fgMorphCopyBlock(). - if (tree->OperIsBlkOp()) - { - op1->gtFlags |= GTF_DONT_CSE; - if (tree->OperIsCopyBlkOp() && - (op2->IsLocal() || (op2->OperIsIndir() && (op2->AsIndir()->Addr()->OperGet() == GT_ADDR)))) - { - op2->gtFlags |= GTF_DONT_CSE; - } - } #ifdef FEATURE_SIMD { // We should check whether op2 should be assigned to a SIMD field or not. // If it is, we should tranlate the tree to simd intrinsic. - assert((tree->gtDebugFlags & GTF_DEBUG_NODE_MORPHED) == 0); + assert(!fgGlobalMorph || ((tree->gtDebugFlags & GTF_DEBUG_NODE_MORPHED) == 0)); GenTreePtr newTree = fgMorphFieldAssignToSIMDIntrinsicSet(tree); typ = tree->TypeGet(); op1 = tree->gtGetOp1(); @@ -10451,8 +10686,8 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) case GT_COLON: #if LOCAL_ASSERTION_PROP if (optLocalAssertionProp) - { #endif + { isQmarkColon = true; } break; @@ -10608,13 +10843,6 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) { op2 = gtFoldExprConst(op2); } - - if (fgShouldUseMagicNumberDivide(tree->AsOp())) - { - tree = fgMorphDivByConst(tree->AsOp()); - op1 = tree->gtOp.gtOp1; - op2 = tree->gtOp.gtOp2; - } #endif // !LEGACY_BACKEND break; @@ -10673,44 +10901,44 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // Note for _TARGET_ARMARCH_ we don't have a remainder instruction, so we don't do this optimization // #else // _TARGET_XARCH - /* If this is an unsigned long mod with op2 which is a cast to long from a - constant int, then don't morph to a call to the helper. This can be done - faster inline using idiv. - */ + /* If this is an unsigned long mod with op2 which is a cast to long from a + constant int, then don't morph to a call to the helper. This can be done + faster inline using idiv. + */ - noway_assert(op2); - if ((typ == TYP_LONG) && opts.OptEnabled(CLFLG_CONSTANTFOLD) && - ((tree->gtFlags & GTF_UNSIGNED) == (op1->gtFlags & GTF_UNSIGNED)) && - ((tree->gtFlags & GTF_UNSIGNED) == (op2->gtFlags & GTF_UNSIGNED))) - { - if (op2->gtOper == GT_CAST && op2->gtCast.CastOp()->gtOper == GT_CNS_INT && - op2->gtCast.CastOp()->gtIntCon.gtIconVal >= 2 && - op2->gtCast.CastOp()->gtIntCon.gtIconVal <= 0x3fffffff && - (tree->gtFlags & GTF_UNSIGNED) == (op2->gtCast.CastOp()->gtFlags & GTF_UNSIGNED)) - { - tree->gtOp.gtOp2 = op2 = fgMorphCast(op2); - noway_assert(op2->gtOper == GT_CNS_NATIVELONG); - } + noway_assert(op2); + if ((typ == TYP_LONG) && opts.OptEnabled(CLFLG_CONSTANTFOLD) && + ((tree->gtFlags & GTF_UNSIGNED) == (op1->gtFlags & GTF_UNSIGNED)) && + ((tree->gtFlags & GTF_UNSIGNED) == (op2->gtFlags & GTF_UNSIGNED))) + { + if (op2->gtOper == GT_CAST && op2->gtCast.CastOp()->gtOper == GT_CNS_INT && + op2->gtCast.CastOp()->gtIntCon.gtIconVal >= 2 && + op2->gtCast.CastOp()->gtIntCon.gtIconVal <= 0x3fffffff && + (tree->gtFlags & GTF_UNSIGNED) == (op2->gtCast.CastOp()->gtFlags & GTF_UNSIGNED)) + { + tree->gtOp.gtOp2 = op2 = fgMorphCast(op2); + noway_assert(op2->gtOper == GT_CNS_NATIVELONG); + } - if (op2->gtOper == GT_CNS_NATIVELONG && op2->gtIntConCommon.LngValue() >= 2 && - op2->gtIntConCommon.LngValue() <= 0x3fffffff) - { - tree->gtOp.gtOp1 = op1 = fgMorphTree(op1); - noway_assert(op1->TypeGet() == TYP_LONG); + if (op2->gtOper == GT_CNS_NATIVELONG && op2->gtIntConCommon.LngValue() >= 2 && + op2->gtIntConCommon.LngValue() <= 0x3fffffff) + { + tree->gtOp.gtOp1 = op1 = fgMorphTree(op1); + noway_assert(op1->TypeGet() == TYP_LONG); - // Update flags for op1 morph - tree->gtFlags &= ~GTF_ALL_EFFECT; + // Update flags for op1 morph + tree->gtFlags &= ~GTF_ALL_EFFECT; - tree->gtFlags |= (op1->gtFlags & GTF_ALL_EFFECT); // Only update with op1 as op2 is a constant + tree->gtFlags |= (op1->gtFlags & GTF_ALL_EFFECT); // Only update with op1 as op2 is a constant - // If op1 is a constant, then do constant folding of the division operator - if (op1->gtOper == GT_CNS_NATIVELONG) - { - tree = gtFoldExpr(tree); + // If op1 is a constant, then do constant folding of the division operator + if (op1->gtOper == GT_CNS_NATIVELONG) + { + tree = gtFoldExpr(tree); + } + return tree; } - return tree; } - } #endif // _TARGET_XARCH ASSIGN_HELPER_FOR_MOD: @@ -10773,16 +11001,28 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) tree = fgMorphModToSubMulDiv(tree->AsOp()); op1 = tree->gtOp.gtOp1; op2 = tree->gtOp.gtOp2; - -#else // !_TARGET_ARM64_ - - if (oper != GT_UMOD && fgShouldUseMagicNumberDivide(tree->AsOp())) - { - tree = fgMorphModByConst(tree->AsOp()); - op1 = tree->gtOp.gtOp1; - op2 = tree->gtOp.gtOp2; +#else //_TARGET_ARM64_ + // If b is not a power of 2 constant then lowering replaces a % b + // with a - (a / b) * b and applies magic division optimization to + // a / b. The code may already contain an a / b expression (e.g. + // x = a / 10; y = a % 10;) and then we end up with redundant code. + // If we convert % to / here we give CSE the opportunity to eliminate + // the redundant division. If there's no redundant division then + // nothing is lost, lowering would have done this transform anyway. + + if ((tree->OperGet() == GT_MOD) && op2->IsIntegralConst()) + { + ssize_t divisorValue = op2->AsIntCon()->IconValue(); + size_t absDivisorValue = (divisorValue == SSIZE_T_MIN) ? static_cast<size_t>(divisorValue) + : static_cast<size_t>(abs(divisorValue)); + + if (!isPow2(absDivisorValue)) + { + tree = fgMorphModToSubMulDiv(tree->AsOp()); + op1 = tree->gtOp.gtOp1; + op2 = tree->gtOp.gtOp2; + } } - #endif //_TARGET_ARM64_ #endif // !LEGACY_BACKEND break; @@ -10857,12 +11097,12 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) ((op2->gtCall.gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC) || (op2->gtCall.gtCallType == CT_HELPER))) #else - if ((((op1->gtOper == GT_INTRINSIC) && - (op1->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Object_GetType)) || - ((op1->gtOper == GT_CALL) && (op1->gtCall.gtCallType == CT_HELPER))) && - (((op2->gtOper == GT_INTRINSIC) && - (op2->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Object_GetType)) || - ((op2->gtOper == GT_CALL) && (op2->gtCall.gtCallType == CT_HELPER)))) + if ((((op1->gtOper == GT_INTRINSIC) && + (op1->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Object_GetType)) || + ((op1->gtOper == GT_CALL) && (op1->gtCall.gtCallType == CT_HELPER))) && + (((op2->gtOper == GT_INTRINSIC) && + (op2->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Object_GetType)) || + ((op2->gtOper == GT_CALL) && (op2->gtCall.gtCallType == CT_HELPER)))) #endif { GenTreePtr pGetClassFromHandle; @@ -10872,8 +11112,8 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) bool bOp1ClassFromHandle = gtIsTypeHandleToRuntimeTypeHelper(op1); bool bOp2ClassFromHandle = gtIsTypeHandleToRuntimeTypeHelper(op2); #else - bool bOp1ClassFromHandle = op1->gtOper == GT_CALL ? gtIsTypeHandleToRuntimeTypeHelper(op1) : false; - bool bOp2ClassFromHandle = op2->gtOper == GT_CALL ? gtIsTypeHandleToRuntimeTypeHelper(op2) : false; + bool bOp1ClassFromHandle = op1->gtOper == GT_CALL ? gtIsTypeHandleToRuntimeTypeHelper(op1) : false; + bool bOp2ClassFromHandle = op2->gtOper == GT_CALL ? gtIsTypeHandleToRuntimeTypeHelper(op2) : false; #endif // Optimize typeof(...) == typeof(...) @@ -10929,8 +11169,8 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) info.compCompHnd->getIntrinsicID(pGetType->gtCall.gtCallMethHnd) == CORINFO_INTRINSIC_Object_GetType && #else - if ((pGetType->gtOper == GT_INTRINSIC) && - (pGetType->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Object_GetType) && + if ((pGetType->gtOper == GT_INTRINSIC) && + (pGetType->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Object_GetType) && #endif pConstLiteral->gtOper == GT_CNS_INT && pConstLiteral->gtType == TYP_I_IMPL) { @@ -10944,7 +11184,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) #ifdef LEGACY_BACKEND GenTreePtr objMT = gtNewOperNode(GT_IND, TYP_I_IMPL, pGetType->gtCall.gtCallObjp); #else - GenTreePtr objMT = gtNewOperNode(GT_IND, TYP_I_IMPL, pGetType->gtUnOp.gtOp1); + GenTreePtr objMT = gtNewOperNode(GT_IND, TYP_I_IMPL, pGetType->gtUnOp.gtOp1); #endif objMT->gtFlags |= GTF_EXCEPT; // Null ref exception if object is null compCurBB->bbFlags |= BBF_HAS_VTABREF; @@ -11041,7 +11281,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // Assume it's an Ind context to start. MorphAddrContext subIndMac1(MACK_Ind); MorphAddrContext* subMac1 = mac; - if (subMac1 == nullptr || subMac1->m_kind == MACK_Ind || subMac1->m_kind == MACK_CopyBlock) + if (subMac1 == nullptr || subMac1->m_kind == MACK_Ind) { switch (tree->gtOper) { @@ -11532,7 +11772,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // // EQ/NE // / \ - // op1 CNS 0/1 + // op1 CNS 0/1 // ival2 = INT_MAX; // The value of INT_MAX for ival2 just means that the constant value is not 0 or 1 @@ -11557,11 +11797,11 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // // EQ/NE Possible REVERSE(RELOP) // / \ / \ - // COMMA CNS 0/1 -> COMMA relop_op2 + // COMMA CNS 0/1 -> COMMA relop_op2 // / \ / \ - // x RELOP x relop_op1 + // x RELOP x relop_op1 // / \ - // relop_op1 relop_op2 + // relop_op1 relop_op2 // // // @@ -11600,13 +11840,13 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // // EQ/NE EQ/NE // / \ / \ - // COMMA CNS 0/1 -> RELOP CNS 0/1 + // COMMA CNS 0/1 -> RELOP CNS 0/1 // / \ / \ - // ASG LCL_VAR + // ASG LCL_VAR // / \ - // LCL_VAR RELOP + // LCL_VAR RELOP // / \ - // + // GenTreePtr asg = op1->gtOp.gtOp1; GenTreePtr lcl = op1->gtOp.gtOp2; @@ -11689,9 +11929,9 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // // EQ/NE -> RELOP/!RELOP // / \ / \ - // RELOP CNS 0/1 + // RELOP CNS 0/1 // / \ - // + // // Note that we will remove/destroy the EQ/NE node and move // the RELOP up into it's location. @@ -11721,11 +11961,11 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // // EQ/NE EQ/NE // / \ / \ - // AND CNS 0/1 -> AND CNS 0 + // AND CNS 0/1 -> AND CNS 0 // / \ / \ - // RSZ/RSH CNS 1 x CNS (1 << y) + // RSZ/RSH CNS 1 x CNS (1 << y) // / \ - // x CNS_INT +y + // x CNS_INT +y if (op1->gtOper == GT_AND) { @@ -12121,38 +12361,42 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) goto CM_OVF_OP; } - /* Check for "op1 - cns2" , we change it to "op1 + (-cns2)" */ - - noway_assert(op2); - if (op2->IsCnsIntOrI()) + // TODO #4104: there are a lot of other places where + // this condition is not checked before transformations. + if (fgGlobalMorph) { - /* Negate the constant and change the node to be "+" */ + /* Check for "op1 - cns2" , we change it to "op1 + (-cns2)" */ - op2->gtIntConCommon.SetIconValue(-op2->gtIntConCommon.IconValue()); - oper = GT_ADD; - tree->ChangeOper(oper); - goto CM_ADD_OP; - } + noway_assert(op2); + if (op2->IsCnsIntOrI()) + { + /* Negate the constant and change the node to be "+" */ - /* Check for "cns1 - op2" , we change it to "(cns1 + (-op2))" */ + op2->gtIntConCommon.SetIconValue(-op2->gtIntConCommon.IconValue()); + oper = GT_ADD; + tree->ChangeOper(oper); + goto CM_ADD_OP; + } - noway_assert(op1); - if (op1->IsCnsIntOrI()) - { - noway_assert(varTypeIsIntOrI(tree)); + /* Check for "cns1 - op2" , we change it to "(cns1 + (-op2))" */ - tree->gtOp.gtOp2 = op2 = - gtNewOperNode(GT_NEG, tree->gtType, op2); // The type of the new GT_NEG node should be the same - // as the type of the tree, i.e. tree->gtType. - fgMorphTreeDone(op2); + noway_assert(op1); + if (op1->IsCnsIntOrI()) + { + noway_assert(varTypeIsIntOrI(tree)); - oper = GT_ADD; - tree->ChangeOper(oper); - goto CM_ADD_OP; - } + tree->gtOp.gtOp2 = op2 = gtNewOperNode(GT_NEG, tree->gtType, op2); // The type of the new GT_NEG + // node should be the same + // as the type of the tree, i.e. tree->gtType. + fgMorphTreeDone(op2); - /* No match - exit */ + oper = GT_ADD; + tree->ChangeOper(oper); + goto CM_ADD_OP; + } + /* No match - exit */ + } break; #ifdef _TARGET_ARM64_ @@ -12281,7 +12525,8 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // Dereferencing the pointer in either case will have the // same effect. - if (!gtIsActiveCSE_Candidate(op1) && varTypeIsGC(op2->TypeGet())) + if (!optValnumCSE_phase && varTypeIsGC(op2->TypeGet()) && + ((op1->gtFlags & GTF_ALL_EFFECT) == 0)) { op2->gtType = tree->gtType; DEBUG_DESTROY_NODE(op1); @@ -12520,7 +12765,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // Also make sure that the tree type matches the fieldVarType and that it's lvFldOffset // is zero - if (fieldVarDsc->TypeGet() == tree->TypeGet() && (fieldVarDsc->lvFldOffset == 0)) + if (fieldVarDsc->TypeGet() == typ && (fieldVarDsc->lvFldOffset == 0)) { // We can just use the existing promoted field LclNum temp->gtLclVarCommon.SetLclNum(lclNumFld); @@ -12538,8 +12783,8 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) else if (varTypeIsSmall(typ) && (genTypeSize(lvaTable[lclNum].lvType) == genTypeSize(typ)) && !lvaTable[lclNum].lvNormalizeOnLoad()) { - tree->gtType = temp->gtType; - foldAndReturnTemp = true; + tree->gtType = typ = temp->TypeGet(); + foldAndReturnTemp = true; } else { @@ -12554,7 +12799,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // Append the field sequence, change the type. temp->AsLclFld()->gtFieldSeq = GetFieldSeqStore()->Append(temp->AsLclFld()->gtFieldSeq, fieldSeq); - temp->gtType = tree->TypeGet(); + temp->gtType = typ; foldAndReturnTemp = true; } @@ -12623,9 +12868,9 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) #ifdef _TARGET_ARM_ // Check for a LclVar TYP_STRUCT with misalignment on a Floating Point field // - if (varTypeIsFloating(tree->TypeGet())) + if (varTypeIsFloating(typ)) { - if ((ival1 % emitTypeSize(tree->TypeGet())) != 0) + if ((ival1 % emitTypeSize(typ)) != 0) { tree->gtFlags |= GTF_IND_UNALIGNED; break; @@ -12638,24 +12883,35 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) } } -#ifdef DEBUG - // If we have decided to fold, then temp cannot be nullptr - if (foldAndReturnTemp) - { - assert(temp != nullptr); - } -#endif - - if (temp != nullptr) - { - noway_assert(op1->gtOper == GT_ADD || op1->gtOper == GT_ADDR); - - // If we haven't already decided to fold this expression - // - if (!foldAndReturnTemp) + // At this point we may have a lclVar or lclFld that might be foldable with a bit of extra massaging: + // - We may have a load of a local where the load has a different type than the local + // - We may have a load of a local plus an offset + // + // In these cases, we will change the lclVar or lclFld into a lclFld of the appropriate type and + // offset if doing so is legal. The only cases in which this transformation is illegal are if the load + // begins before the local or if the load extends beyond the end of the local (i.e. if the load is + // out-of-bounds w.r.t. the local). + if ((temp != nullptr) && !foldAndReturnTemp) + { + assert(temp->OperIsLocal()); + + const unsigned lclNum = temp->AsLclVarCommon()->gtLclNum; + LclVarDsc* const varDsc = &lvaTable[lclNum]; + + const var_types tempTyp = temp->TypeGet(); + const bool useExactSize = + varTypeIsStruct(tempTyp) || (tempTyp == TYP_BLK) || (tempTyp == TYP_LCLBLK); + const unsigned varSize = useExactSize ? varDsc->lvExactSize : genTypeSize(temp); + + // If the size of the load is greater than the size of the lclVar, we cannot fold this access into + // a lclFld: the access represented by an lclFld node must begin at or after the start of the + // lclVar and must not extend beyond the end of the lclVar. + if ((ival1 < 0) || ((ival1 + genTypeSize(typ)) > varSize)) + { + lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_LocalField)); + } + else { - noway_assert(temp->OperIsLocal()); - LclVarDsc* varDsc = &(lvaTable[temp->AsLclVarCommon()->gtLclNum]); // Make sure we don't separately promote the fields of this struct. if (varDsc->lvRegStruct) { @@ -12664,7 +12920,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) } else { - lvaSetVarDoNotEnregister(temp->gtLclVarCommon.gtLclNum DEBUGARG(DNER_LocalField)); + lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_LocalField)); } // We will turn a GT_LCL_VAR into a GT_LCL_FLD with an gtLclOffs of 'ival' @@ -12689,19 +12945,19 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) temp->gtType = tree->gtType; foldAndReturnTemp = true; } + } - assert(foldAndReturnTemp == true); + if (foldAndReturnTemp) + { + assert(temp != nullptr); + assert(temp->TypeGet() == typ); + assert((op1->OperGet() == GT_ADD) || (op1->OperGet() == GT_ADDR)); - // Keep the DONT_CSE flag in sync - // (i.e keep the original value of this flag from tree) - // as it can be set for 'temp' because a GT_ADDR always marks it for it's op1 - // + // Copy the value of GTF_DONT_CSE from the original tree to `temp`: it can be set for + // 'temp' because a GT_ADDR always marks it for its operand. temp->gtFlags &= ~GTF_DONT_CSE; temp->gtFlags |= (tree->gtFlags & GTF_DONT_CSE); - noway_assert(op1->gtOper == GT_ADD || op1->gtOper == GT_ADDR); - noway_assert(temp->gtType == tree->gtType); - if (op1->OperGet() == GT_ADD) { DEBUG_DESTROY_NODE(op1->gtOp.gtOp1); // GT_ADDR @@ -12984,7 +13240,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // If we are in the Valuenum CSE phase then don't morph away anything as these // nodes may have CSE defs/uses in them. // - if (!optValnumCSE_phase && (oper != GT_ASG) && (oper != GT_COLON) && !tree->IsList()) + if (!optValnumCSE_phase && (oper != GT_ASG) && (oper != GT_COLON) && !tree->OperIsAnyList()) { /* Check for op1 as a GT_COMMA with a unconditional throw node */ if (op1 && fgIsCommaThrow(op1, true)) @@ -13530,6 +13786,7 @@ GenTree* Compiler::fgMorphSmpOpOptional(GenTreeOp* tree) /* The target is used as well as being defined */ if (op1->OperIsLocal()) { + op1->gtFlags &= ~GTF_VAR_USEDEF; op1->gtFlags |= GTF_VAR_USEASG; } @@ -13666,7 +13923,7 @@ GenTree* Compiler::fgMorphSmpOpOptional(GenTreeOp* tree) /* Check for the case "(val + icon) << icon" */ - if (op2->IsCnsIntOrI() && op1->gtOper == GT_ADD && !op1->gtOverflow()) + if (!optValnumCSE_phase && op2->IsCnsIntOrI() && op1->gtOper == GT_ADD && !op1->gtOverflow()) { GenTreePtr cns = op1->gtOp.gtOp2; @@ -13731,192 +13988,45 @@ GenTree* Compiler::fgMorphSmpOpOptional(GenTreeOp* tree) break; + case GT_INIT_VAL: + // Initialization values for initBlk have special semantics - their lower + // byte is used to fill the struct. However, we allow 0 as a "bare" value, + // which enables them to get a VNForZero, and be propagated. + if (op1->IsIntegralConst(0)) + { + return op1; + } + break; + default: break; } return tree; } -// code to generate a magic number and shift amount for the magic number division -// optimization. This code is previously from UTC where it notes it was taken from -// _The_PowerPC_Compiler_Writer's_Guide_, pages 57-58. -// The paper it is based on is "Division by invariant integers using multiplication" -// by Torbjorn Granlund and Peter L. Montgomery in PLDI 94 - -template <typename T> -T GetSignedMagicNumberForDivide(T denom, int* shift /*out*/) -{ - // static SMAG smag; - const int bits = sizeof(T) * 8; - const int bits_minus_1 = bits - 1; - - typedef typename jitstd::make_unsigned<T>::type UT; - - const UT two_nminus1 = UT(1) << bits_minus_1; - - int p; - UT absDenom; - UT absNc; - UT delta; - UT q1; - UT r1; - UT r2; - UT q2; - UT t; - T result_magic; - int result_shift; - int iters = 0; - - absDenom = abs(denom); - t = two_nminus1 + ((unsigned int)denom >> 31); - absNc = t - 1 - (t % absDenom); // absolute value of nc - p = bits_minus_1; // initialize p - q1 = two_nminus1 / absNc; // initialize q1 = 2^p / abs(nc) - r1 = two_nminus1 - (q1 * absNc); // initialize r1 = rem(2^p, abs(nc)) - q2 = two_nminus1 / absDenom; // initialize q1 = 2^p / abs(denom) - r2 = two_nminus1 - (q2 * absDenom); // initialize r1 = rem(2^p, abs(denom)) - - do - { - iters++; - p++; - q1 *= 2; // update q1 = 2^p / abs(nc) - r1 *= 2; // update r1 = rem(2^p / abs(nc)) - - if (r1 >= absNc) - { // must be unsigned comparison - q1++; - r1 -= absNc; - } - - q2 *= 2; // update q2 = 2^p / abs(denom) - r2 *= 2; // update r2 = rem(2^p / abs(denom)) - - if (r2 >= absDenom) - { // must be unsigned comparison - q2++; - r2 -= absDenom; - } - - delta = absDenom - r2; - } while (q1 < delta || (q1 == delta && r1 == 0)); - - result_magic = q2 + 1; // resulting magic number - if (denom < 0) - { - result_magic = -result_magic; - } - *shift = p - bits; // resulting shift - - return result_magic; -} - -bool Compiler::fgShouldUseMagicNumberDivide(GenTreeOp* tree) -{ -#ifdef _TARGET_ARM64_ - // TODO-ARM64-NYI: We don't have a 'mulHi' implementation yet for ARM64 - return false; -#else - - // During the optOptimizeValnumCSEs phase we can call fgMorph and when we do, - // if this method returns true we will introduce a new LclVar and - // a couple of new GenTree nodes, including an assignment to the new LclVar. - // None of these new GenTree nodes will have valid ValueNumbers. - // That is an invalid state for a GenTree node during the optOptimizeValnumCSEs phase. - // - // Also during optAssertionProp when extracting side effects we can assert - // during gtBuildCommaList if we have one tree that has Value Numbers - // and another one that does not. - // - if (!fgGlobalMorph) - { - // We only perform the Magic Number Divide optimization during - // the initial global morph phase - return false; - } - - if (tree->gtFlags & GTF_OVERFLOW) - { - return false; - } - - if (tree->gtOp2->gtOper != GT_CNS_INT && tree->gtOp2->gtOper != GT_CNS_LNG) - { - return false; - } - - ssize_t cons = tree->gtOp2->gtIntConCommon.IconValue(); - - if (cons == 0 || cons == -1 || cons == 1) - { - return false; - } - - // codegen will expand these - if (cons == SSIZE_T_MIN || isPow2(abs(cons))) - { - return false; - } - - // someone else will fold this away, so don't make it complicated for them - if (tree->gtOp1->IsCnsIntOrI()) - { - return false; - } - - // There is no technical barrier to handling unsigned, however it is quite rare - // and more work to support and test - if (tree->gtFlags & GTF_UNSIGNED) - { - return false; - } - - return true; -#endif -} - -// transform x%c -> x-((x/c)*c) - -GenTree* Compiler::fgMorphModByConst(GenTreeOp* tree) -{ - assert(fgShouldUseMagicNumberDivide(tree)); - - var_types type = tree->gtType; - - GenTree* cns = tree->gtOp2; - - GenTree* numerator = fgMakeMultiUse(&tree->gtOp1); - - tree->SetOper(GT_DIV); - - GenTree* mul = gtNewOperNode(GT_MUL, type, tree, gtCloneExpr(cns)); - - GenTree* sub = gtNewOperNode(GT_SUB, type, numerator, mul); - -#ifdef DEBUG - sub->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED; -#endif - - return sub; -} - -// For ARM64 we don't have a remainder instruction, -// The architecture manual suggests the following transformation to -// generate code for such operator: +//------------------------------------------------------------------------ +// fgMorphModToSubMulDiv: Transform a % b into the equivalent a - (a / b) * b +// (see ECMA III 3.55 and III.3.56). // -// a % b = a - (a / b) * b; +// Arguments: +// tree - The GT_MOD/GT_UMOD tree to morph // -// This method will produce the above expression in 'a' and 'b' are -// leaf nodes, otherwise, if any of them is not a leaf it will spill -// its value into a temporary variable, an example: -// (x * 2 - 1) % (y + 1) -> t1 - (t2 * ( comma(t1 = x * 2 - 1, t1) / comma(t2 = y + 1, t2) ) ) +// Returns: +// The morphed tree +// +// Notes: +// For ARM64 we don't have a remainder instruction so this transform is +// always done. For XARCH this transform is done if we know that magic +// division will be used, in that case this transform allows CSE to +// eliminate the redundant div from code like "x = a / 3; y = a % 3;". +// +// This method will produce the above expression in 'a' and 'b' are +// leaf nodes, otherwise, if any of them is not a leaf it will spill +// its value into a temporary variable, an example: +// (x * 2 - 1) % (y + 1) -> t1 - (t2 * ( comma(t1 = x * 2 - 1, t1) / comma(t2 = y + 1, t2) ) ) // GenTree* Compiler::fgMorphModToSubMulDiv(GenTreeOp* tree) { -#ifndef _TARGET_ARM64_ - assert(!"This should only be called for ARM64"); -#endif - if (tree->OperGet() == GT_MOD) { tree->SetOper(GT_DIV); @@ -13944,8 +14054,16 @@ GenTree* Compiler::fgMorphModToSubMulDiv(GenTreeOp* tree) denominator = fgMakeMultiUse(&tree->gtOp2); } + // The numerator and denominator may have been assigned to temps, in which case + // their defining assignments are in the current tree. Therefore, we need to + // set the execuction order accordingly on the nodes we create. + // That is, the "mul" will be evaluated in "normal" order, and the "sub" must + // be set to be evaluated in reverse order. + // GenTree* mul = gtNewOperNode(GT_MUL, type, tree, gtCloneExpr(denominator)); + assert(!mul->IsReverseOp()); GenTree* sub = gtNewOperNode(GT_SUB, type, gtCloneExpr(numerator), mul); + sub->gtFlags |= GTF_REVERSE_OPS; #ifdef DEBUG sub->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED; @@ -13954,95 +14072,6 @@ GenTree* Compiler::fgMorphModToSubMulDiv(GenTreeOp* tree) return sub; } -// Turn a division by a constant into a multiplication by constant + some adjustments -// see comments on GetSignedMagicNumberForDivide for source of this algorithm. -// returns: the transformed tree - -GenTree* Compiler::fgMorphDivByConst(GenTreeOp* tree) -{ - assert(fgShouldUseMagicNumberDivide(tree)); - - JITDUMP("doing magic number divide optimization\n"); - - int64_t denominator = tree->gtOp2->gtIntConCommon.IconValue(); - int64_t magic; - int shift; - var_types type = tree->gtType; - - if (tree->gtType == TYP_INT) - { - magic = GetSignedMagicNumberForDivide<int32_t>((int32_t)denominator, &shift); - } - else - { - magic = GetSignedMagicNumberForDivide<int64_t>((int64_t)denominator, &shift); - } - - GenTree* numerator = nullptr; - - // If signs of the denominator and magic number don't match, - // we will need to use the numerator again. - if (signum(denominator) != signum(magic)) - { - numerator = fgMakeMultiUse(&tree->gtOp1); - tree->gtFlags |= GTF_ASG; - } - - if (type == TYP_LONG) - { - tree->gtOp2->gtIntConCommon.SetLngValue(magic); - } - else - { - tree->gtOp2->gtIntConCommon.SetIconValue((ssize_t)magic); - } - - tree->SetOper(GT_MULHI); - - GenTree* t = tree; - GenTree* mulresult = tree; - - JITDUMP("Multiply Result:\n"); - DISPTREE(mulresult); - - GenTree* adjusted = mulresult; - - if (denominator > 0 && magic < 0) - { - // add the numerator back in - adjusted = gtNewOperNode(GT_ADD, type, mulresult, numerator); - } - else if (denominator < 0 && magic > 0) - { - // subtract the numerator off - adjusted = gtNewOperNode(GT_SUB, type, mulresult, numerator); - } - else - { - adjusted = mulresult; - } - - GenTree* result1 = adjusted; - if (shift != 0) - { - result1 = gtNewOperNode(GT_RSH, type, adjusted, gtNewIconNode(shift, TYP_INT)); - } - - GenTree* secondClone = fgMakeMultiUse(&result1); - - GenTree* result2 = gtNewOperNode(GT_RSZ, type, secondClone, gtNewIconNode(genTypeSize(type) * 8 - 1, type)); - - GenTree* result = gtNewOperNode(GT_ADD, type, result1, result2); - JITDUMP("Final Magic Number divide:\n"); - DISPTREE(result); - -#ifdef DEBUG - result->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED; -#endif - - return result; -} - //------------------------------------------------------------------------------ // fgOperIsBitwiseRotationRoot : Check if the operation can be a root of a bitwise rotation tree. // @@ -14238,10 +14267,10 @@ GenTreePtr Compiler::fgRecognizeAndMorphBitwiseRotation(GenTreePtr tree) #ifndef _TARGET_64BIT_ if (!shiftIndexWithoutAdd->IsCnsIntOrI() && (rotatedValueBitSize == 64)) { - // TODO: we need to handle variable-sized long shifts specially on x86. + // TODO-X86-CQ: we need to handle variable-sized long shifts specially on x86. // GT_LSH, GT_RSH, and GT_RSZ have helpers for this case. We may need // to add helpers for GT_ROL and GT_ROR. - NYI("Rotation of a long value by variable amount"); + return tree; } #endif @@ -14276,7 +14305,15 @@ GenTreePtr Compiler::fgRecognizeAndMorphBitwiseRotation(GenTreePtr tree) tree->gtOp.gtOp1 = rotatedValue; tree->gtOp.gtOp2 = rotateIndex; tree->ChangeOper(rotateOp); - noway_assert(inputTreeEffects == ((rotatedValue->gtFlags | rotateIndex->gtFlags) & GTF_ALL_EFFECT)); + + unsigned childFlags = 0; + for (GenTree* op : tree->Operands()) + { + childFlags |= (op->gtFlags & GTF_ALL_EFFECT); + } + + // The parent's flags should be a superset of its operands' flags + noway_assert((inputTreeEffects & childFlags) == childFlags); } else { @@ -14719,29 +14756,15 @@ DONE: } #if LOCAL_ASSERTION_PROP -/***************************************************************************** - * - * Kill all dependent assertions with regard to lclNum. - * - */ - -void Compiler::fgKillDependentAssertions(unsigned lclNum DEBUGARG(GenTreePtr tree)) +//------------------------------------------------------------------------ +// fgKillDependentAssertionsSingle: Kill all assertions specific to lclNum +// +// Arguments: +// lclNum - The varNum of the lclVar for which we're killing assertions. +// tree - (DEBUG only) the tree responsible for killing its assertions. +// +void Compiler::fgKillDependentAssertionsSingle(unsigned lclNum DEBUGARG(GenTreePtr tree)) { - LclVarDsc* varDsc = &lvaTable[lclNum]; - - if (varDsc->lvPromoted) - { - noway_assert(varTypeIsStruct(varDsc)); - - // Kill the field locals. - for (unsigned i = varDsc->lvFieldLclStart; i < varDsc->lvFieldLclStart + varDsc->lvFieldCnt; ++i) - { - fgKillDependentAssertions(i DEBUGARG(tree)); - } - - // Fall through to kill the struct local itself. - } - /* All dependent assertions are killed here */ ASSERT_TP killed = BitVecOps::MakeCopy(apTraits, GetAssertionDep(lclNum)); @@ -14778,6 +14801,48 @@ void Compiler::fgKillDependentAssertions(unsigned lclNum DEBUGARG(GenTreePtr tre noway_assert(BitVecOps::IsEmpty(apTraits, killed)); } } +//------------------------------------------------------------------------ +// fgKillDependentAssertions: Kill all dependent assertions with regard to lclNum. +// +// Arguments: +// lclNum - The varNum of the lclVar for which we're killing assertions. +// tree - (DEBUG only) the tree responsible for killing its assertions. +// +// Notes: +// For structs and struct fields, it will invalidate the children and parent +// respectively. +// Calls fgKillDependentAssertionsSingle to kill the assertions for a single lclVar. +// +void Compiler::fgKillDependentAssertions(unsigned lclNum DEBUGARG(GenTreePtr tree)) +{ + LclVarDsc* varDsc = &lvaTable[lclNum]; + + if (varDsc->lvPromoted) + { + noway_assert(varTypeIsStruct(varDsc)); + + // Kill the field locals. + for (unsigned i = varDsc->lvFieldLclStart; i < varDsc->lvFieldLclStart + varDsc->lvFieldCnt; ++i) + { + fgKillDependentAssertionsSingle(i DEBUGARG(tree)); + } + + // Kill the struct local itself. + fgKillDependentAssertionsSingle(lclNum DEBUGARG(tree)); + } + else if (varDsc->lvIsStructField) + { + // Kill the field local. + fgKillDependentAssertionsSingle(lclNum DEBUGARG(tree)); + + // Kill the parent struct. + fgKillDependentAssertionsSingle(varDsc->lvParentLcl DEBUGARG(tree)); + } + else + { + fgKillDependentAssertionsSingle(lclNum DEBUGARG(tree)); + } +} #endif // LOCAL_ASSERTION_PROP /***************************************************************************** @@ -14841,13 +14906,12 @@ void Compiler::fgMorphTreeDone(GenTreePtr tree, if (optAssertionCount > 0) { /* Is this an assignment to a local variable */ - - if ((tree->OperKind() & GTK_ASGOP) && - (tree->gtOp.gtOp1->gtOper == GT_LCL_VAR || tree->gtOp.gtOp1->gtOper == GT_LCL_FLD)) + GenTreeLclVarCommon* lclVarTree = nullptr; + if (tree->DefinesLocal(this, &lclVarTree)) { - unsigned op1LclNum = tree->gtOp.gtOp1->gtLclVarCommon.gtLclNum; - noway_assert(op1LclNum < lvaCount); - fgKillDependentAssertions(op1LclNum DEBUGARG(tree)); + unsigned lclNum = lclVarTree->gtLclNum; + noway_assert(lclNum < lvaCount); + fgKillDependentAssertions(lclNum DEBUGARG(tree)); } } @@ -15223,14 +15287,15 @@ bool Compiler::fgFoldConditional(BasicBlock* block) // Returns false if 'stmt' is still in the block (even if other statements were removed). // -bool Compiler::fgMorphBlockStmt(BasicBlock* block, GenTreePtr stmt DEBUGARG(const char* msg)) +bool Compiler::fgMorphBlockStmt(BasicBlock* block, GenTreeStmt* stmt DEBUGARG(const char* msg)) { - noway_assert(stmt->gtOper == GT_STMT); + assert(block != nullptr); + assert(stmt != nullptr); compCurBB = block; compCurStmt = stmt; - GenTreePtr morph = fgMorphTree(stmt->gtStmt.gtStmtExpr); + GenTree* morph = fgMorphTree(stmt->gtStmtExpr); // Bug 1106830 - During the CSE phase we can't just remove // morph->gtOp.gtOp2 as it could contain CSE expressions. @@ -15239,7 +15304,7 @@ bool Compiler::fgMorphBlockStmt(BasicBlock* block, GenTreePtr stmt DEBUGARG(cons // if (!optValnumCSE_phase) { - /* Check for morph as a GT_COMMA with an unconditional throw */ + // Check for morph as a GT_COMMA with an unconditional throw if (fgIsCommaThrow(morph, true)) { #ifdef DEBUG @@ -15251,12 +15316,12 @@ bool Compiler::fgMorphBlockStmt(BasicBlock* block, GenTreePtr stmt DEBUGARG(cons printf("\n"); } #endif - /* Use the call as the new stmt */ + // Use the call as the new stmt morph = morph->gtOp.gtOp1; noway_assert(morph->gtOper == GT_CALL); } - /* we can get a throw as a statement root*/ + // we can get a throw as a statement root if (fgIsThrow(morph)) { #ifdef DEBUG @@ -15271,15 +15336,19 @@ bool Compiler::fgMorphBlockStmt(BasicBlock* block, GenTreePtr stmt DEBUGARG(cons } } - stmt->gtStmt.gtStmtExpr = morph; + stmt->gtStmtExpr = morph; - /* Can the entire tree be removed ? */ + if (lvaLocalVarRefCounted) + { + // fgMorphTree may have introduced new lclVar references. Bump the ref counts if requested. + lvaRecursiveIncRefCounts(stmt->gtStmtExpr); + } + // Can the entire tree be removed? bool removedStmt = fgCheckRemoveStmt(block, stmt); - /* Or this is the last statement of a conditional branch that was just folded */ - - if ((!removedStmt) && (stmt->gtNext == nullptr) && !fgRemoveRestOfBlock) + // Or this is the last statement of a conditional branch that was just folded? + if (!removedStmt && (stmt->getNextStmt() == nullptr) && !fgRemoveRestOfBlock) { if (fgFoldConditional(block)) { @@ -15292,11 +15361,10 @@ bool Compiler::fgMorphBlockStmt(BasicBlock* block, GenTreePtr stmt DEBUGARG(cons if (!removedStmt) { - /* Have to re-do the evaluation order since for example - * some later code does not expect constants as op1 */ + // Have to re-do the evaluation order since for example some later code does not expect constants as op1 gtSetStmtInfo(stmt); - /* Have to re-link the nodes for this statement */ + // Have to re-link the nodes for this statement fgSetStmtSeq(stmt); } @@ -15311,18 +15379,13 @@ bool Compiler::fgMorphBlockStmt(BasicBlock* block, GenTreePtr stmt DEBUGARG(cons if (fgRemoveRestOfBlock) { - /* Remove the rest of the stmts in the block */ - - while (stmt->gtNext) + // Remove the rest of the stmts in the block + for (stmt = stmt->getNextStmt(); stmt != nullptr; stmt = stmt->getNextStmt()) { - stmt = stmt->gtNext; - noway_assert(stmt->gtOper == GT_STMT); - fgRemoveStmt(block, stmt); } - // The rest of block has been removed - // and we will always throw an exception + // The rest of block has been removed and we will always throw an exception. // Update succesors of block fgRemoveBlockAsPred(block); @@ -15368,8 +15431,9 @@ void Compiler::fgMorphStmts(BasicBlock* block, bool* mult, bool* lnot, bool* loa fgCurrentlyInUseArgTemps = hashBv::Create(this); - GenTreePtr stmt, prev; - for (stmt = block->bbTreeList, prev = nullptr; stmt; prev = stmt->gtStmt.gtStmtExpr, stmt = stmt->gtNext) + GenTreeStmt* stmt = block->firstStmt(); + GenTreePtr prev = nullptr; + for (; stmt != nullptr; prev = stmt->gtStmtExpr, stmt = stmt->gtNextStmt) { noway_assert(stmt->gtOper == GT_STMT); @@ -15379,8 +15443,7 @@ void Compiler::fgMorphStmts(BasicBlock* block, bool* mult, bool* lnot, bool* loa continue; } #ifdef FEATURE_SIMD - if (!opts.MinOpts() && stmt->gtStmt.gtStmtExpr->TypeGet() == TYP_FLOAT && - stmt->gtStmt.gtStmtExpr->OperGet() == GT_ASG) + if (!opts.MinOpts() && stmt->gtStmtExpr->TypeGet() == TYP_FLOAT && stmt->gtStmtExpr->OperGet() == GT_ASG) { fgMorphCombineSIMDFieldAssignments(block, stmt); } @@ -15388,7 +15451,7 @@ void Compiler::fgMorphStmts(BasicBlock* block, bool* mult, bool* lnot, bool* loa fgMorphStmt = stmt; compCurStmt = stmt; - GenTreePtr tree = stmt->gtStmt.gtStmtExpr; + GenTreePtr tree = stmt->gtStmtExpr; #ifdef DEBUG compCurStmtNum++; @@ -15416,15 +15479,15 @@ void Compiler::fgMorphStmts(BasicBlock* block, bool* mult, bool* lnot, bool* loa // Has fgMorphStmt been sneakily changed ? - if (stmt->gtStmt.gtStmtExpr != tree) + if (stmt->gtStmtExpr != tree) { /* This must be tailcall. Ignore 'morph' and carry on with the tail-call node */ - morph = stmt->gtStmt.gtStmtExpr; + morph = stmt->gtStmtExpr; noway_assert(compTailCallUsed); noway_assert((morph->gtOper == GT_CALL) && morph->AsCall()->IsTailCall()); - noway_assert(stmt->gtNext == nullptr); + noway_assert(stmt->gtNextStmt == nullptr); GenTreeCall* call = morph->AsCall(); // Could either be @@ -15448,7 +15511,7 @@ void Compiler::fgMorphStmts(BasicBlock* block, bool* mult, bool* lnot, bool* loa noway_assert(compTailCallUsed); noway_assert((tree->gtOper == GT_CALL) && tree->AsCall()->IsTailCall()); - noway_assert(stmt->gtNext == nullptr); + noway_assert(stmt->gtNextStmt == nullptr); GenTreeCall* call = morph->AsCall(); @@ -15505,7 +15568,7 @@ void Compiler::fgMorphStmts(BasicBlock* block, bool* mult, bool* lnot, bool* loa fgRemoveRestOfBlock = true; } - stmt->gtStmt.gtStmtExpr = tree = morph; + stmt->gtStmtExpr = tree = morph; noway_assert(fgPtrArgCntCur == 0); @@ -15958,6 +16021,45 @@ void Compiler::fgMorphBlocks() #endif } +//------------------------------------------------------------------------ +// fgCheckArgCnt: Check whether the maximum arg size will change codegen requirements +// +// Notes: +// fpPtrArgCntMax records the maximum number of pushed arguments. +// Depending upon this value of the maximum number of pushed arguments +// we may need to use an EBP frame or be partially interuptible. +// This functionality has been factored out of fgSetOptions() because +// the Rationalizer can create new calls. +// +// Assumptions: +// This must be called before isFramePointerRequired() is called, because it is a +// phased variable (can only be written before it has been read). +// +void Compiler::fgCheckArgCnt() +{ + if (!compCanEncodePtrArgCntMax()) + { +#ifdef DEBUG + if (verbose) + { + printf("Too many pushed arguments for fully interruptible encoding, marking method as partially " + "interruptible\n"); + } +#endif + genInterruptible = false; + } + if (fgPtrArgCntMax >= sizeof(unsigned)) + { +#ifdef DEBUG + if (verbose) + { + printf("Too many pushed arguments for an ESP based encoding, forcing an EBP frame\n"); + } +#endif + codeGen->setFramePointerRequired(true); + } +} + /***************************************************************************** * * Make some decisions about the kind of code to generate. @@ -15974,13 +16076,11 @@ void Compiler::fgSetOptions() } #endif -#ifdef DEBUGGING_SUPPORT if (opts.compDbgCode) { assert(!codeGen->isGCTypeFixed()); genInterruptible = true; // debugging is easier this way ... } -#endif /* Assume we won't need an explicit stack frame if this is allowed */ @@ -16035,32 +16135,7 @@ void Compiler::fgSetOptions() #endif // _TARGET_X86_ - // fpPtrArgCntMax records the maximum number of pushed arguments - // Depending upon this value of the maximum number of pushed arguments - // we may need to use an EBP frame or be partially interuptible - // - - if (!compCanEncodePtrArgCntMax()) - { -#ifdef DEBUG - if (verbose) - { - printf("Too many pushed arguments for fully interruptible encoding, marking method as partially " - "interruptible\n"); - } -#endif - genInterruptible = false; - } - if (fgPtrArgCntMax >= sizeof(unsigned)) - { -#ifdef DEBUG - if (verbose) - { - printf("Too many pushed arguments for an ESP based encoding, forcing an EBP frame\n"); - } -#endif - codeGen->setFramePointerRequiredGCInfo(true); - } + fgCheckArgCnt(); if (info.compCallUnmanaged) { @@ -16121,6 +16196,23 @@ GenTreePtr Compiler::fgInitThisClass() } else { +#ifdef FEATURE_READYTORUN_COMPILER + // Only CoreRT understands CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE. Don't do this on CoreCLR. + if (opts.IsReadyToRun() && IsTargetAbi(CORINFO_CORERT_ABI)) + { + CORINFO_RESOLVED_TOKEN resolvedToken; + memset(&resolvedToken, 0, sizeof(resolvedToken)); + + GenTreePtr ctxTree = getRuntimeContextTree(kind.runtimeLookupKind); + + // CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE with a zeroed out resolvedToken means "get the static + // base of the class that owns the method being compiled". If we're in this method, it means we're not + // inlining and there's no ambiguity. + return impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE, TYP_BYREF, + gtNewArgList(ctxTree), &kind); + } +#endif + // Collectible types requires that for shared generic code, if we use the generic context paramter // that we report it. (This is a conservative approach, we could detect some cases particularly when the // context parameter is this that we don't need the eager reporting logic.) @@ -16774,19 +16866,13 @@ void Compiler::fgMorph() fgRemoveEmptyBlocks(); - /* Add any internal blocks/trees we may need */ - - fgAddInternal(); - -#if OPT_BOOL_OPS - fgMultipleNots = false; -#endif - #ifdef DEBUG /* Inliner could add basic blocks. Check that the flowgraph data is up-to-date */ fgDebugCheckBBlist(false, false); #endif // DEBUG + EndPhase(PHASE_MORPH_INIT); + /* Inline */ fgInline(); #if 0 @@ -16796,6 +16882,16 @@ void Compiler::fgMorph() RecordStateAtEndOfInlining(); // Record "start" values for post-inlining cycles and elapsed time. + EndPhase(PHASE_MORPH_INLINE); + + /* Add any internal blocks/trees we may need */ + + fgAddInternal(); + +#if OPT_BOOL_OPS + fgMultipleNots = false; +#endif + #ifdef DEBUG /* Inliner could add basic blocks. Check that the flowgraph data is up-to-date */ fgDebugCheckBBlist(false, false); @@ -16804,6 +16900,8 @@ void Compiler::fgMorph() /* For x64 and ARM64 we need to mark irregular parameters early so that they don't get promoted */ fgMarkImplicitByRefArgs(); + EndPhase(PHASE_MORPH_IMPBYREF); + /* Promote struct locals if necessary */ fgPromoteStructs(); @@ -16816,10 +16914,14 @@ void Compiler::fgMorph() fgStress64RsltMul(); #endif // DEBUG + EndPhase(PHASE_STR_ADRLCL); + /* Morph the trees in all the blocks of the method */ fgMorphBlocks(); + EndPhase(PHASE_MORPH_GLOBAL); + #if 0 JITDUMP("trees after fgMorphBlocks\n"); DBEXEC(VERBOSE, fgDispBasicBlocks(true)); @@ -17454,9 +17556,6 @@ enum AddrExposedContext AXC_AddrWide, // The address being computed will be dereferenced by a block operation that operates // on more bytes than the width of the storage location addressed. If this is a // field of a promoted struct local, declare the entire struct local address-taken. - AXC_InitBlk, // An GT_INITBLK is the immediate parent. The first argument is in an IND context. - AXC_CopyBlk, // An GT_COPYBLK is the immediate parent. The first argument is in a GT_LIST, whose - // args should be evaluated in an IND context. AXC_IndAdd, // A GT_ADD is the immediate parent, and it was evaluated in an IND contxt. // If one arg is a constant int, evaluate the other in an IND context. Otherwise, none. }; @@ -17572,14 +17671,8 @@ Compiler::fgWalkResult Compiler::fgMarkAddrTakenLocalsPreCB(GenTreePtr* pTree, f return WALK_CONTINUE; case GT_LIST: - if (axc == AXC_InitBlk || axc == AXC_CopyBlk) - { - axcStack->Push(axc); - } - else - { - axcStack->Push(AXC_None); - } + case GT_FIELD_LIST: + axcStack->Push(AXC_None); return WALK_CONTINUE; case GT_INDEX: @@ -18083,9 +18176,6 @@ bool Compiler::fgShouldCreateAssignOp(GenTreePtr tree, bool* bReverse) #endif // defined(LEGACY_BACKEND) } -// Static variables. -Compiler::MorphAddrContext Compiler::s_CopyBlockMAC(Compiler::MACK_CopyBlock); - #ifdef FEATURE_SIMD //----------------------------------------------------------------------------------- |