summaryrefslogtreecommitdiff
path: root/src/jit/lclvars.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/lclvars.cpp')
-rw-r--r--src/jit/lclvars.cpp6788
1 files changed, 6788 insertions, 0 deletions
diff --git a/src/jit/lclvars.cpp b/src/jit/lclvars.cpp
new file mode 100644
index 0000000000..369c96322d
--- /dev/null
+++ b/src/jit/lclvars.cpp
@@ -0,0 +1,6788 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XX XX
+XX LclVarsInfo XX
+XX XX
+XX The variables to be used by the code generator. XX
+XX XX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+*/
+
+#include "jitpch.h"
+#ifdef _MSC_VER
+#pragma hdrstop
+#endif
+#include "emit.h"
+
+#include "register_arg_convention.h"
+
+/*****************************************************************************/
+
+#ifdef DEBUG
+#if DOUBLE_ALIGN
+/* static */
+unsigned Compiler::s_lvaDoubleAlignedProcsCount = 0;
+#endif
+#endif
+
+/*****************************************************************************/
+
+void Compiler::lvaInit()
+{
+ /* We haven't allocated stack variables yet */
+ lvaRefCountingStarted = false;
+ lvaLocalVarRefCounted = false;
+
+ lvaSortAgain = false; // false: We don't need to call lvaSortOnly()
+ lvaTrackedFixed = false; // false: We can still add new tracked variables
+
+ lvaDoneFrameLayout = NO_FRAME_LAYOUT;
+#if !FEATURE_EH_FUNCLETS
+ lvaShadowSPslotsVar = BAD_VAR_NUM;
+#endif // !FEATURE_EH_FUNCLETS
+ lvaInlinedPInvokeFrameVar = BAD_VAR_NUM;
+ lvaReversePInvokeFrameVar = BAD_VAR_NUM;
+#if FEATURE_FIXED_OUT_ARGS
+ lvaPInvokeFrameRegSaveVar = BAD_VAR_NUM;
+ lvaOutgoingArgSpaceVar = BAD_VAR_NUM;
+#endif // FEATURE_FIXED_OUT_ARGS
+#ifdef _TARGET_ARM_
+ lvaPromotedStructAssemblyScratchVar = BAD_VAR_NUM;
+#endif // _TARGET_ARM_
+ lvaLocAllocSPvar = BAD_VAR_NUM;
+ lvaNewObjArrayArgs = BAD_VAR_NUM;
+ lvaGSSecurityCookie = BAD_VAR_NUM;
+#ifdef _TARGET_X86_
+ lvaVarargsBaseOfStkArgs = BAD_VAR_NUM;
+#endif // _TARGET_X86_
+ lvaVarargsHandleArg = BAD_VAR_NUM;
+ lvaSecurityObject = BAD_VAR_NUM;
+ lvaStubArgumentVar = BAD_VAR_NUM;
+ lvaArg0Var = BAD_VAR_NUM;
+ lvaMonAcquired = BAD_VAR_NUM;
+
+ lvaInlineeReturnSpillTemp = BAD_VAR_NUM;
+
+ gsShadowVarInfo = nullptr;
+#if FEATURE_EH_FUNCLETS
+ lvaPSPSym = BAD_VAR_NUM;
+#endif
+#if FEATURE_SIMD
+ lvaSIMDInitTempVarNum = BAD_VAR_NUM;
+#endif // FEATURE_SIMD
+ lvaCurEpoch = 0;
+#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+ lvaFirstStackIncomingArgNum = BAD_VAR_NUM;
+#endif // !FEATURE_UNIX_AMD64_STRUCT_PASSING
+}
+
+/*****************************************************************************/
+
+void Compiler::lvaInitTypeRef()
+{
+
+ /* x86 args look something like this:
+ [this ptr] [hidden return buffer] [declared arguments]* [generic context] [var arg cookie]
+
+ x64 is closer to the native ABI:
+ [this ptr] [hidden return buffer] [generic context] [var arg cookie] [declared arguments]*
+ (Note: prior to .NET Framework 4.5.1 for Windows 8.1 (but not .NET Framework 4.5.1 "downlevel"),
+ the "hidden return buffer" came before the "this ptr". Now, the "this ptr" comes first. This
+ is different from the C++ order, where the "hidden return buffer" always comes first.)
+
+ ARM and ARM64 are the same as the current x64 convention:
+ [this ptr] [hidden return buffer] [generic context] [var arg cookie] [declared arguments]*
+
+ Key difference:
+ The var arg cookie and generic context are swapped with respect to the user arguments
+ */
+
+ /* Set compArgsCount and compLocalsCount */
+
+ info.compArgsCount = info.compMethodInfo->args.numArgs;
+
+ // Is there a 'this' pointer
+
+ if (!info.compIsStatic)
+ {
+ info.compArgsCount++;
+ }
+ else
+ {
+ info.compThisArg = BAD_VAR_NUM;
+ }
+
+ info.compILargsCount = info.compArgsCount;
+
+#ifdef FEATURE_SIMD
+ if (featureSIMD && (info.compRetNativeType == TYP_STRUCT))
+ {
+ var_types structType = impNormStructType(info.compMethodInfo->args.retTypeClass);
+ info.compRetType = structType;
+ }
+#endif // FEATURE_SIMD
+
+ // Are we returning a struct using a return buffer argument?
+ //
+ const bool hasRetBuffArg = impMethodInfo_hasRetBuffArg(info.compMethodInfo);
+
+ // Possibly change the compRetNativeType from TYP_STRUCT to a "primitive" type
+ // when we are returning a struct by value and it fits in one register
+ //
+ if (!hasRetBuffArg && varTypeIsStruct(info.compRetNativeType))
+ {
+ CORINFO_CLASS_HANDLE retClsHnd = info.compMethodInfo->args.retTypeClass;
+
+ Compiler::structPassingKind howToReturnStruct;
+ var_types returnType = getReturnTypeForStruct(retClsHnd, &howToReturnStruct);
+
+ if (howToReturnStruct == SPK_PrimitiveType)
+ {
+ assert(returnType != TYP_UNKNOWN);
+ assert(returnType != TYP_STRUCT);
+
+ info.compRetNativeType = returnType;
+
+ // ToDo: Refactor this common code sequence into its own method as it is used 4+ times
+ if ((returnType == TYP_LONG) && (compLongUsed == false))
+ {
+ compLongUsed = true;
+ }
+ else if (((returnType == TYP_FLOAT) || (returnType == TYP_DOUBLE)) && (compFloatingPointUsed == false))
+ {
+ compFloatingPointUsed = true;
+ }
+ }
+ }
+
+ // Do we have a RetBuffArg?
+
+ if (hasRetBuffArg)
+ {
+ info.compArgsCount++;
+ }
+ else
+ {
+ info.compRetBuffArg = BAD_VAR_NUM;
+ }
+
+ /* There is a 'hidden' cookie pushed last when the
+ calling convention is varargs */
+
+ if (info.compIsVarArgs)
+ {
+ info.compArgsCount++;
+ }
+
+ // Is there an extra parameter used to pass instantiation info to
+ // shared generic methods and shared generic struct instance methods?
+ if (info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE)
+ {
+ info.compArgsCount++;
+ }
+ else
+ {
+ info.compTypeCtxtArg = BAD_VAR_NUM;
+ }
+
+ lvaCount = info.compLocalsCount = info.compArgsCount + info.compMethodInfo->locals.numArgs;
+
+ info.compILlocalsCount = info.compILargsCount + info.compMethodInfo->locals.numArgs;
+
+ /* Now allocate the variable descriptor table */
+
+ if (compIsForInlining())
+ {
+ lvaTable = impInlineInfo->InlinerCompiler->lvaTable;
+ lvaCount = impInlineInfo->InlinerCompiler->lvaCount;
+ lvaTableCnt = impInlineInfo->InlinerCompiler->lvaTableCnt;
+
+ // No more stuff needs to be done.
+ return;
+ }
+
+ lvaTableCnt = lvaCount * 2;
+
+ if (lvaTableCnt < 16)
+ {
+ lvaTableCnt = 16;
+ }
+
+ lvaTable = (LclVarDsc*)compGetMemArray(lvaTableCnt, sizeof(*lvaTable), CMK_LvaTable);
+ size_t tableSize = lvaTableCnt * sizeof(*lvaTable);
+ memset(lvaTable, 0, tableSize);
+ for (unsigned i = 0; i < lvaTableCnt; i++)
+ {
+ new (&lvaTable[i], jitstd::placement_t()) LclVarDsc(this); // call the constructor.
+ }
+
+ //-------------------------------------------------------------------------
+ // Count the arguments and initialize the respective lvaTable[] entries
+ //
+ // First the implicit arguments
+ //-------------------------------------------------------------------------
+
+ InitVarDscInfo varDscInfo;
+ varDscInfo.Init(lvaTable, hasRetBuffArg);
+
+ lvaInitArgs(&varDscInfo);
+
+ //-------------------------------------------------------------------------
+ // Finally the local variables
+ //-------------------------------------------------------------------------
+
+ unsigned varNum = varDscInfo.varNum;
+ LclVarDsc* varDsc = varDscInfo.varDsc;
+ CORINFO_ARG_LIST_HANDLE localsSig = info.compMethodInfo->locals.args;
+
+ for (unsigned i = 0; i < info.compMethodInfo->locals.numArgs;
+ i++, varNum++, varDsc++, localsSig = info.compCompHnd->getArgNext(localsSig))
+ {
+ CORINFO_CLASS_HANDLE typeHnd;
+ CorInfoTypeWithMod corInfoType =
+ info.compCompHnd->getArgType(&info.compMethodInfo->locals, localsSig, &typeHnd);
+ lvaInitVarDsc(varDsc, varNum, strip(corInfoType), typeHnd, localsSig, &info.compMethodInfo->locals);
+
+ varDsc->lvPinned = ((corInfoType & CORINFO_TYPE_MOD_PINNED) != 0);
+ varDsc->lvOnFrame = true; // The final home for this local variable might be our local stack frame
+ }
+
+ if ( // If there already exist unsafe buffers, don't mark more structs as unsafe
+ // as that will cause them to be placed along with the real unsafe buffers,
+ // unnecessarily exposing them to overruns. This can affect GS tests which
+ // intentionally do buffer-overruns.
+ !getNeedsGSSecurityCookie() &&
+ // GS checks require the stack to be re-ordered, which can't be done with EnC
+ !opts.compDbgEnC && compStressCompile(STRESS_UNSAFE_BUFFER_CHECKS, 25))
+ {
+ setNeedsGSSecurityCookie();
+ compGSReorderStackLayout = true;
+
+ for (unsigned i = 0; i < lvaCount; i++)
+ {
+ if ((lvaTable[i].lvType == TYP_STRUCT) && compStressCompile(STRESS_GENERIC_VARN, 60))
+ {
+ lvaTable[i].lvIsUnsafeBuffer = true;
+ }
+ }
+ }
+
+ if (getNeedsGSSecurityCookie())
+ {
+ // Ensure that there will be at least one stack variable since
+ // we require that the GSCookie does not have a 0 stack offset.
+ unsigned dummy = lvaGrabTempWithImplicitUse(false DEBUGARG("GSCookie dummy"));
+ lvaTable[dummy].lvType = TYP_INT;
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ lvaTableDump(INITIAL_FRAME_LAYOUT);
+ }
+#endif
+}
+
+/*****************************************************************************/
+void Compiler::lvaInitArgs(InitVarDscInfo* varDscInfo)
+{
+ compArgSize = 0;
+
+#if defined(_TARGET_ARM_) && defined(PROFILING_SUPPORTED)
+ // Prespill all argument regs on to stack in case of Arm when under profiler.
+ if (compIsProfilerHookNeeded())
+ {
+ codeGen->regSet.rsMaskPreSpillRegArg |= RBM_ARG_REGS;
+ }
+#endif
+
+ //----------------------------------------------------------------------
+
+ /* Is there a "this" pointer ? */
+ lvaInitThisPtr(varDscInfo);
+
+ /* If we have a hidden return-buffer parameter, that comes here */
+ lvaInitRetBuffArg(varDscInfo);
+
+//======================================================================
+
+#if USER_ARGS_COME_LAST
+ //@GENERICS: final instantiation-info argument for shared generic methods
+ // and shared generic struct instance methods
+ lvaInitGenericsCtxt(varDscInfo);
+
+ /* If the method is varargs, process the varargs cookie */
+ lvaInitVarArgsHandle(varDscInfo);
+#endif
+
+ //-------------------------------------------------------------------------
+ // Now walk the function signature for the explicit user arguments
+ //-------------------------------------------------------------------------
+ lvaInitUserArgs(varDscInfo);
+
+#if !USER_ARGS_COME_LAST
+ //@GENERICS: final instantiation-info argument for shared generic methods
+ // and shared generic struct instance methods
+ lvaInitGenericsCtxt(varDscInfo);
+
+ /* If the method is varargs, process the varargs cookie */
+ lvaInitVarArgsHandle(varDscInfo);
+#endif
+
+ //----------------------------------------------------------------------
+
+ // We have set info.compArgsCount in compCompile()
+ noway_assert(varDscInfo->varNum == info.compArgsCount);
+ assert(varDscInfo->intRegArgNum <= MAX_REG_ARG);
+
+ codeGen->intRegState.rsCalleeRegArgCount = varDscInfo->intRegArgNum;
+#if !FEATURE_STACK_FP_X87
+ codeGen->floatRegState.rsCalleeRegArgCount = varDscInfo->floatRegArgNum;
+#endif // FEATURE_STACK_FP_X87
+
+ // The total argument size must be aligned.
+ noway_assert((compArgSize % sizeof(void*)) == 0);
+
+#ifdef _TARGET_X86_
+ /* We can not pass more than 2^16 dwords as arguments as the "ret"
+ instruction can only pop 2^16 arguments. Could be handled correctly
+ but it will be very difficult for fully interruptible code */
+
+ if (compArgSize != (size_t)(unsigned short)compArgSize)
+ NO_WAY("Too many arguments for the \"ret\" instruction to pop");
+#endif
+}
+
+/*****************************************************************************/
+void Compiler::lvaInitThisPtr(InitVarDscInfo* varDscInfo)
+{
+ LclVarDsc* varDsc = varDscInfo->varDsc;
+ if (!info.compIsStatic)
+ {
+ varDsc->lvIsParam = 1;
+#if ASSERTION_PROP
+ varDsc->lvSingleDef = 1;
+#endif
+
+ varDsc->lvIsPtr = 1;
+
+ lvaArg0Var = info.compThisArg = varDscInfo->varNum;
+ noway_assert(info.compThisArg == 0);
+
+ if (eeIsValueClass(info.compClassHnd))
+ {
+ varDsc->lvType = TYP_BYREF;
+#ifdef FEATURE_SIMD
+ if (featureSIMD)
+ {
+ var_types simdBaseType = TYP_UNKNOWN;
+ var_types type = impNormStructType(info.compClassHnd, nullptr, nullptr, &simdBaseType);
+ if (simdBaseType != TYP_UNKNOWN)
+ {
+ assert(varTypeIsSIMD(type));
+ varDsc->lvSIMDType = true;
+ varDsc->lvBaseType = simdBaseType;
+ }
+ }
+#endif // FEATURE_SIMD
+ }
+ else
+ {
+ varDsc->lvType = TYP_REF;
+ }
+
+ if (tiVerificationNeeded)
+ {
+ varDsc->lvVerTypeInfo = verMakeTypeInfo(info.compClassHnd);
+
+ if (varDsc->lvVerTypeInfo.IsValueClass())
+ {
+ varDsc->lvVerTypeInfo.MakeByRef();
+ }
+ }
+ else
+ {
+ varDsc->lvVerTypeInfo = typeInfo();
+ }
+
+ // Mark the 'this' pointer for the method
+ varDsc->lvVerTypeInfo.SetIsThisPtr();
+
+ varDsc->lvIsRegArg = 1;
+ noway_assert(varDscInfo->intRegArgNum == 0);
+
+ varDsc->lvArgReg = genMapRegArgNumToRegNum(varDscInfo->allocRegArg(TYP_INT), varDsc->TypeGet());
+#if FEATURE_MULTIREG_ARGS
+ varDsc->lvOtherArgReg = REG_NA;
+#endif
+ varDsc->setPrefReg(varDsc->lvArgReg, this);
+ varDsc->lvOnFrame = true; // The final home for this incoming register might be our local stack frame
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("'this' passed in register %s\n", getRegName(varDsc->lvArgReg));
+ }
+#endif
+ compArgSize += TARGET_POINTER_SIZE;
+
+ varDscInfo->varNum++;
+ varDscInfo->varDsc++;
+ }
+}
+
+/*****************************************************************************/
+void Compiler::lvaInitRetBuffArg(InitVarDscInfo* varDscInfo)
+{
+ LclVarDsc* varDsc = varDscInfo->varDsc;
+ bool hasRetBuffArg = impMethodInfo_hasRetBuffArg(info.compMethodInfo);
+
+ // These two should always match
+ noway_assert(hasRetBuffArg == varDscInfo->hasRetBufArg);
+
+ if (hasRetBuffArg)
+ {
+ info.compRetBuffArg = varDscInfo->varNum;
+ varDsc->lvType = TYP_BYREF;
+ varDsc->lvIsParam = 1;
+ varDsc->lvIsRegArg = 1;
+#if ASSERTION_PROP
+ varDsc->lvSingleDef = 1;
+#endif
+ if (hasFixedRetBuffReg())
+ {
+ varDsc->lvArgReg = theFixedRetBuffReg();
+ }
+ else
+ {
+ unsigned retBuffArgNum = varDscInfo->allocRegArg(TYP_INT);
+ varDsc->lvArgReg = genMapIntRegArgNumToRegNum(retBuffArgNum);
+ }
+
+#if FEATURE_MULTIREG__ARGS
+ varDsc->lvOtherArgReg = REG_NA;
+#endif
+ varDsc->setPrefReg(varDsc->lvArgReg, this);
+ varDsc->lvOnFrame = true; // The final home for this incoming register might be our local stack frame
+
+ info.compRetBuffDefStack = 0;
+ if (info.compRetType == TYP_STRUCT)
+ {
+ CORINFO_SIG_INFO sigInfo;
+ info.compCompHnd->getMethodSig(info.compMethodHnd, &sigInfo);
+ assert(JITtype2varType(sigInfo.retType) == info.compRetType); // Else shouldn't have a ret buff.
+
+ info.compRetBuffDefStack =
+ (info.compCompHnd->isStructRequiringStackAllocRetBuf(sigInfo.retTypeClass) == TRUE);
+ if (info.compRetBuffDefStack)
+ {
+ // If we're assured that the ret buff argument points into a callers stack, we will type it as
+ // "TYP_I_IMPL"
+ // (native int/unmanaged pointer) so that it's not tracked as a GC ref.
+ varDsc->lvType = TYP_I_IMPL;
+ }
+ }
+ assert(isValidIntArgReg(varDsc->lvArgReg));
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("'__retBuf' passed in register %s\n", getRegName(varDsc->lvArgReg));
+ }
+#endif
+
+ /* Update the total argument size, count and varDsc */
+
+ compArgSize += TARGET_POINTER_SIZE;
+ varDscInfo->varNum++;
+ varDscInfo->varDsc++;
+ }
+}
+
+/*****************************************************************************/
+void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo)
+{
+//-------------------------------------------------------------------------
+// Walk the function signature for the explicit arguments
+//-------------------------------------------------------------------------
+
+#if defined(_TARGET_X86_)
+ // Only (some of) the implicit args are enregistered for varargs
+ varDscInfo->maxIntRegArgNum = info.compIsVarArgs ? varDscInfo->intRegArgNum : MAX_REG_ARG;
+#elif defined(_TARGET_AMD64_) && !defined(UNIX_AMD64_ABI)
+ // On System V type environment the float registers are not indexed together with the int ones.
+ varDscInfo->floatRegArgNum = varDscInfo->intRegArgNum;
+#endif // _TARGET_*
+
+ CORINFO_ARG_LIST_HANDLE argLst = info.compMethodInfo->args.args;
+
+ const unsigned argSigLen = info.compMethodInfo->args.numArgs;
+
+ regMaskTP doubleAlignMask = RBM_NONE;
+ for (unsigned i = 0; i < argSigLen;
+ i++, varDscInfo->varNum++, varDscInfo->varDsc++, argLst = info.compCompHnd->getArgNext(argLst))
+ {
+ LclVarDsc* varDsc = varDscInfo->varDsc;
+ CORINFO_CLASS_HANDLE typeHnd = nullptr;
+
+ CorInfoTypeWithMod corInfoType = info.compCompHnd->getArgType(&info.compMethodInfo->args, argLst, &typeHnd);
+ varDsc->lvIsParam = 1;
+#if ASSERTION_PROP
+ varDsc->lvSingleDef = 1;
+#endif
+
+ lvaInitVarDsc(varDsc, varDscInfo->varNum, strip(corInfoType), typeHnd, argLst, &info.compMethodInfo->args);
+
+ // For ARM, ARM64, and AMD64 varargs, all arguments go in integer registers
+ var_types argType = mangleVarArgsType(varDsc->TypeGet());
+ var_types origArgType = argType;
+ // ARM softfp calling convention should affect only the floating point arguments.
+ // Otherwise there appear too many surplus pre-spills and other memory operations
+ // with the associated locations .
+ bool isSoftFPPreSpill = opts.compUseSoftFP && varTypeIsFloating(varDsc->TypeGet());
+ unsigned argSize = eeGetArgSize(argLst, &info.compMethodInfo->args);
+ unsigned cSlots = argSize / TARGET_POINTER_SIZE; // the total number of slots of this argument
+ bool isHfaArg = false;
+ var_types hfaType = TYP_UNDEF;
+
+ // Methods that use VarArg or SoftFP cannot have HFA arguments
+ if (!info.compIsVarArgs && !opts.compUseSoftFP)
+ {
+ // If the argType is a struct, then check if it is an HFA
+ if (varTypeIsStruct(argType))
+ {
+ hfaType = GetHfaType(typeHnd); // set to float or double if it is an HFA, otherwise TYP_UNDEF
+ isHfaArg = varTypeIsFloating(hfaType);
+ }
+ }
+ if (isHfaArg)
+ {
+ // We have an HFA argument, so from here on out treat the type as a float or double.
+ // The orginal struct type is available by using origArgType
+ // We also update the cSlots to be the number of float/double fields in the HFA
+ argType = hfaType;
+ cSlots = varDsc->lvHfaSlots();
+ }
+ // The number of slots that must be enregistered if we are to consider this argument enregistered.
+ // This is normally the same as cSlots, since we normally either enregister the entire object,
+ // or none of it. For structs on ARM, however, we only need to enregister a single slot to consider
+ // it enregistered, as long as we can split the rest onto the stack.
+ unsigned cSlotsToEnregister = cSlots;
+
+#ifdef _TARGET_ARM_
+ // On ARM we pass the first 4 words of integer arguments and non-HFA structs in registers.
+ // But we pre-spill user arguments in varargs methods and structs.
+ //
+ unsigned cAlign;
+ bool preSpill = info.compIsVarArgs || isSoftFPPreSpill;
+
+ switch (origArgType)
+ {
+ case TYP_STRUCT:
+ assert(varDsc->lvSize() == argSize);
+ cAlign = varDsc->lvStructDoubleAlign ? 2 : 1;
+
+ // HFA arguments go on the stack frame. They don't get spilled in the prolog like struct
+ // arguments passed in the integer registers but get homed immediately after the prolog.
+ if (!isHfaArg)
+ {
+ cSlotsToEnregister = 1; // HFAs must be totally enregistered or not, but other structs can be split.
+ preSpill = true;
+ }
+ break;
+
+ case TYP_DOUBLE:
+ case TYP_LONG:
+ cAlign = 2;
+ break;
+
+ default:
+ cAlign = 1;
+ break;
+ }
+
+ if (isRegParamType(argType))
+ {
+ compArgSize += varDscInfo->alignReg(argType, cAlign) * REGSIZE_BYTES;
+ }
+
+ if (argType == TYP_STRUCT)
+ {
+ // Are we going to split the struct between registers and stack? We can do that as long as
+ // no floating-point arguments have been put on the stack.
+ //
+ // From the ARM Procedure Call Standard:
+ // Rule C.5: "If the NCRN is less than r4 **and** the NSAA is equal to the SP,"
+ // then split the argument between registers and stack. Implication: if something
+ // has already been spilled to the stack, then anything that would normally be
+ // split between the core registers and the stack will be put on the stack.
+ // Anything that follows will also be on the stack. However, if something from
+ // floating point regs has been spilled to the stack, we can still use r0-r3 until they are full.
+
+ if (varDscInfo->canEnreg(TYP_INT, 1) && // The beginning of the struct can go in a register
+ !varDscInfo->canEnreg(TYP_INT, cSlots) && // The end of the struct can't fit in a register
+ varDscInfo->existAnyFloatStackArgs()) // There's at least one stack-based FP arg already
+ {
+ varDscInfo->setAllRegArgUsed(TYP_INT); // Prevent all future use of integer registers
+ preSpill = false; // This struct won't be prespilled, since it will go on the stack
+ }
+ }
+
+ if (preSpill)
+ {
+ for (unsigned ix = 0; ix < cSlots; ix++)
+ {
+ if (!varDscInfo->canEnreg(TYP_INT, ix + 1))
+ {
+ break;
+ }
+ regMaskTP regMask = genMapArgNumToRegMask(varDscInfo->regArgNum(TYP_INT) + ix, TYP_INT);
+ if (cAlign == 2)
+ {
+ doubleAlignMask |= regMask;
+ }
+ codeGen->regSet.rsMaskPreSpillRegArg |= regMask;
+ }
+ }
+ else
+ {
+ varDsc->lvOnFrame = true; // The final home for this incoming register might be our local stack frame
+ }
+
+#else // !_TARGET_ARM_
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
+ if (varTypeIsStruct(argType))
+ {
+ assert(typeHnd != nullptr);
+ eeGetSystemVAmd64PassStructInRegisterDescriptor(typeHnd, &structDesc);
+ if (structDesc.passedInRegisters)
+ {
+ unsigned intRegCount = 0;
+ unsigned floatRegCount = 0;
+
+ for (unsigned int i = 0; i < structDesc.eightByteCount; i++)
+ {
+ if (structDesc.IsIntegralSlot(i))
+ {
+ intRegCount++;
+ }
+ else if (structDesc.IsSseSlot(i))
+ {
+ floatRegCount++;
+ }
+ else
+ {
+ assert(false && "Invalid eightbyte classification type.");
+ break;
+ }
+ }
+
+ if (intRegCount != 0 && !varDscInfo->canEnreg(TYP_INT, intRegCount))
+ {
+ structDesc.passedInRegisters = false; // No register to enregister the eightbytes.
+ }
+
+ if (floatRegCount != 0 && !varDscInfo->canEnreg(TYP_FLOAT, floatRegCount))
+ {
+ structDesc.passedInRegisters = false; // No register to enregister the eightbytes.
+ }
+ }
+ }
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+
+ // The final home for this incoming register might be our local stack frame.
+ // For System V platforms the final home will always be on the local stack frame.
+ varDsc->lvOnFrame = true;
+
+#endif // !_TARGET_ARM_
+
+ bool canPassArgInRegisters = false;
+
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ if (varTypeIsStruct(argType))
+ {
+ canPassArgInRegisters = structDesc.passedInRegisters;
+ }
+ else
+#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ {
+ canPassArgInRegisters = varDscInfo->canEnreg(argType, cSlotsToEnregister);
+ }
+
+ if (canPassArgInRegisters)
+ {
+ /* Another register argument */
+
+ // Allocate the registers we need. allocRegArg() returns the first argument register number of the set.
+ // For non-HFA structs, we still "try" to enregister the whole thing; it will just max out if splitting
+ // to the stack happens.
+ unsigned firstAllocatedRegArgNum = 0;
+
+#if FEATURE_MULTIREG_ARGS
+ varDsc->lvOtherArgReg = REG_NA;
+#endif // FEATURE_MULTIREG_ARGS
+
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ unsigned secondAllocatedRegArgNum = 0;
+ var_types firstEightByteType = TYP_UNDEF;
+ var_types secondEightByteType = TYP_UNDEF;
+
+ if (varTypeIsStruct(argType))
+ {
+ if (structDesc.eightByteCount >= 1)
+ {
+ firstEightByteType = GetEightByteType(structDesc, 0);
+ firstAllocatedRegArgNum = varDscInfo->allocRegArg(firstEightByteType, 1);
+ }
+ }
+ else
+#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ {
+ firstAllocatedRegArgNum = varDscInfo->allocRegArg(argType, cSlots);
+ }
+
+ if (isHfaArg)
+ {
+ // We need to save the fact that this HFA is enregistered
+ varDsc->lvSetIsHfa();
+ varDsc->lvSetIsHfaRegArg();
+ varDsc->SetHfaType(hfaType);
+ varDsc->lvIsMultiRegArg = (varDsc->lvHfaSlots() > 1);
+ }
+
+ varDsc->lvIsRegArg = 1;
+
+#if FEATURE_MULTIREG_ARGS
+ if (varTypeIsStruct(argType))
+ {
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ varDsc->lvArgReg = genMapRegArgNumToRegNum(firstAllocatedRegArgNum, firstEightByteType);
+
+ // If there is a second eightbyte, get a register for it too and map the arg to the reg number.
+ if (structDesc.eightByteCount >= 2)
+ {
+ secondEightByteType = GetEightByteType(structDesc, 1);
+ secondAllocatedRegArgNum = varDscInfo->allocRegArg(secondEightByteType, 1);
+ }
+
+ if (secondEightByteType != TYP_UNDEF)
+ {
+ varDsc->lvOtherArgReg = genMapRegArgNumToRegNum(secondAllocatedRegArgNum, secondEightByteType);
+ varDsc->addPrefReg(genRegMask(varDsc->lvOtherArgReg), this);
+ }
+#else // ARM32 or ARM64
+ varDsc->lvArgReg = genMapRegArgNumToRegNum(firstAllocatedRegArgNum, TYP_I_IMPL);
+#ifdef _TARGET_ARM64_
+ if (cSlots == 2)
+ {
+ varDsc->lvOtherArgReg = genMapRegArgNumToRegNum(firstAllocatedRegArgNum + 1, TYP_I_IMPL);
+ varDsc->addPrefReg(genRegMask(varDsc->lvOtherArgReg), this);
+ }
+#endif // _TARGET_ARM64_
+#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ }
+ else
+#endif // FEATURE_MULTIREG_ARGS
+ {
+ varDsc->lvArgReg = genMapRegArgNumToRegNum(firstAllocatedRegArgNum, argType);
+ }
+
+ varDsc->setPrefReg(varDsc->lvArgReg, this);
+
+#ifdef _TARGET_ARM_
+ if (varDsc->TypeGet() == TYP_LONG)
+ {
+ varDsc->lvOtherReg = genMapRegArgNumToRegNum(firstAllocatedRegArgNum + 1, TYP_INT);
+ varDsc->addPrefReg(genRegMask(varDsc->lvOtherReg), this);
+ }
+#endif // _TARGET_ARM_
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Arg #%u passed in register(s) ", varDscInfo->varNum);
+ bool isFloat = false;
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ // In case of one eightbyte struct the type is already normalized earlier.
+ // The varTypeIsFloating(argType) is good for this case.
+ if (varTypeIsStruct(argType) && (structDesc.eightByteCount >= 1))
+ {
+ isFloat = varTypeIsFloating(firstEightByteType);
+ }
+ else
+#else // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ {
+ isFloat = varTypeIsFloating(argType);
+ }
+#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ if (varTypeIsStruct(argType))
+ {
+ // Print both registers, just to be clear
+ if (firstEightByteType == TYP_UNDEF)
+ {
+ printf("firstEightByte: <not used>");
+ }
+ else
+ {
+ printf("firstEightByte: %s",
+ getRegName(genMapRegArgNumToRegNum(firstAllocatedRegArgNum, firstEightByteType),
+ isFloat));
+ }
+
+ if (secondEightByteType == TYP_UNDEF)
+ {
+ printf(", secondEightByte: <not used>");
+ }
+ else
+ {
+ printf(", secondEightByte: %s",
+ getRegName(genMapRegArgNumToRegNum(secondAllocatedRegArgNum, secondEightByteType),
+ varTypeIsFloating(secondEightByteType)));
+ }
+ }
+ else
+#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+ {
+ unsigned regArgNum = genMapRegNumToRegArgNum(varDsc->lvArgReg, argType);
+
+ for (unsigned ix = 0; ix < cSlots; ix++, regArgNum++)
+ {
+ if (ix > 0)
+ {
+ printf(",");
+ }
+
+ if (!isFloat && (regArgNum >= varDscInfo->maxIntRegArgNum)) // a struct has been split between
+ // registers and stack
+ {
+ printf(" stack slots:%d", cSlots - ix);
+ break;
+ }
+
+#ifdef _TARGET_ARM_
+ if (isFloat)
+ {
+ // Print register size prefix
+ if (argType == TYP_DOUBLE)
+ {
+ // Print both registers, just to be clear
+ printf("%s/%s", getRegName(genMapRegArgNumToRegNum(regArgNum, argType), isFloat),
+ getRegName(genMapRegArgNumToRegNum(regArgNum + 1, argType), isFloat));
+
+ // doubles take 2 slots
+ assert(ix + 1 < cSlots);
+ ++ix;
+ ++regArgNum;
+ }
+ else
+ {
+ printf("%s", getRegName(genMapRegArgNumToRegNum(regArgNum, argType), isFloat));
+ }
+ }
+ else
+#endif // _TARGET_ARM_
+ {
+ printf("%s", getRegName(genMapRegArgNumToRegNum(regArgNum, argType), isFloat));
+ }
+ }
+ }
+ printf("\n");
+ }
+#endif // DEBUG
+ } // end if (canPassArgInRegisters)
+ else
+ {
+#if defined(_TARGET_ARM_)
+
+ varDscInfo->setAllRegArgUsed(argType);
+ if (varTypeIsFloating(argType))
+ {
+ varDscInfo->setAnyFloatStackArgs();
+ }
+
+#elif defined(_TARGET_ARM64_)
+
+ // If we needed to use the stack in order to pass this argument then
+ // record the fact that we have used up any remaining registers of this 'type'
+ // This prevents any 'backfilling' from occuring on ARM64
+ //
+ varDscInfo->setAllRegArgUsed(argType);
+
+#endif // _TARGET_XXX_
+ }
+
+#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+ // The arg size is returning the number of bytes of the argument. For a struct it could return a size not a
+ // multiple of TARGET_POINTER_SIZE. The stack allocated space should always be multiple of TARGET_POINTER_SIZE,
+ // so round it up.
+ compArgSize += (unsigned)roundUp(argSize, TARGET_POINTER_SIZE);
+#else // !FEATURE_UNIX_AMD64_STRUCT_PASSING
+ compArgSize += argSize;
+#endif // !FEATURE_UNIX_AMD64_STRUCT_PASSING
+ if (info.compIsVarArgs || isHfaArg || isSoftFPPreSpill)
+ {
+#if defined(_TARGET_X86_)
+ varDsc->lvStkOffs = compArgSize;
+#else // !_TARGET_X86_
+ // TODO-CQ: We shouldn't have to go as far as to declare these
+ // address-exposed -- DoNotEnregister should suffice.
+ lvaSetVarAddrExposed(varDscInfo->varNum);
+#endif // !_TARGET_X86_
+ }
+ } // for each user arg
+
+#ifdef _TARGET_ARM_
+ if (doubleAlignMask != RBM_NONE)
+ {
+ assert(RBM_ARG_REGS == 0xF);
+ assert((doubleAlignMask & RBM_ARG_REGS) == doubleAlignMask);
+ if (doubleAlignMask != RBM_NONE && doubleAlignMask != RBM_ARG_REGS)
+ {
+ // doubleAlignMask can only be 0011 and/or 1100 as 'double aligned types' can
+ // begin at r0 or r2.
+ assert(doubleAlignMask == 0x3 || doubleAlignMask == 0xC /* || 0xF is if'ed out */);
+
+ // Now if doubleAlignMask is 0011 i.e., {r0,r1} and we prespill r2 or r3
+ // but not both, then the stack would be misaligned for r0. So spill both
+ // r2 and r3.
+ //
+ // ; +0 --- caller SP double aligned ----
+ // ; -4 r2 r3
+ // ; -8 r1 r1
+ // ; -c r0 r0 <-- misaligned.
+ // ; callee saved regs
+ if (doubleAlignMask == 0x3 && doubleAlignMask != codeGen->regSet.rsMaskPreSpillRegArg)
+ {
+ codeGen->regSet.rsMaskPreSpillAlign =
+ (~codeGen->regSet.rsMaskPreSpillRegArg & ~doubleAlignMask) & RBM_ARG_REGS;
+ }
+ }
+ }
+#endif // _TARGET_ARM_
+}
+
+/*****************************************************************************/
+void Compiler::lvaInitGenericsCtxt(InitVarDscInfo* varDscInfo)
+{
+ //@GENERICS: final instantiation-info argument for shared generic methods
+ // and shared generic struct instance methods
+ if (info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE)
+ {
+ info.compTypeCtxtArg = varDscInfo->varNum;
+
+ LclVarDsc* varDsc = varDscInfo->varDsc;
+ varDsc->lvIsParam = 1;
+#if ASSERTION_PROP
+ varDsc->lvSingleDef = 1;
+#endif
+
+ varDsc->lvType = TYP_I_IMPL;
+
+ if (varDscInfo->canEnreg(TYP_I_IMPL))
+ {
+ /* Another register argument */
+
+ varDsc->lvIsRegArg = 1;
+ varDsc->lvArgReg = genMapRegArgNumToRegNum(varDscInfo->regArgNum(TYP_INT), varDsc->TypeGet());
+#if FEATURE_MULTIREG_ARGS
+ varDsc->lvOtherArgReg = REG_NA;
+#endif
+ varDsc->setPrefReg(varDsc->lvArgReg, this);
+ varDsc->lvOnFrame = true; // The final home for this incoming register might be our local stack frame
+
+ varDscInfo->intRegArgNum++;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("'GenCtxt' passed in register %s\n", getRegName(varDsc->lvArgReg));
+ }
+#endif
+ }
+#ifndef LEGACY_BACKEND
+ else
+ {
+ // For the RyuJIT backend, we need to mark these as being on the stack,
+ // as this is not done elsewhere in the case that canEnreg returns false.
+ varDsc->lvOnFrame = true;
+ }
+#endif // !LEGACY_BACKEND
+
+ compArgSize += TARGET_POINTER_SIZE;
+
+#if defined(_TARGET_X86_)
+ if (info.compIsVarArgs)
+ varDsc->lvStkOffs = compArgSize;
+#endif // _TARGET_X86_
+
+ varDscInfo->varNum++;
+ varDscInfo->varDsc++;
+ }
+}
+
+/*****************************************************************************/
+void Compiler::lvaInitVarArgsHandle(InitVarDscInfo* varDscInfo)
+{
+ if (info.compIsVarArgs)
+ {
+ lvaVarargsHandleArg = varDscInfo->varNum;
+
+ LclVarDsc* varDsc = varDscInfo->varDsc;
+ varDsc->lvType = TYP_I_IMPL;
+ varDsc->lvIsParam = 1;
+ // Make sure this lives in the stack -- address may be reported to the VM.
+ // TODO-CQ: This should probably be:
+ // lvaSetVarDoNotEnregister(varDscInfo->varNum DEBUGARG(DNER_VMNeedsStackAddr));
+ // But that causes problems, so, for expedience, I switched back to this heavyweight
+ // hammer. But I think it should be possible to switch; it may just work now
+ // that other problems are fixed.
+ lvaSetVarAddrExposed(varDscInfo->varNum);
+
+#if ASSERTION_PROP
+ varDsc->lvSingleDef = 1;
+#endif
+
+ if (varDscInfo->canEnreg(TYP_I_IMPL))
+ {
+ /* Another register argument */
+
+ unsigned varArgHndArgNum = varDscInfo->allocRegArg(TYP_I_IMPL);
+
+ varDsc->lvIsRegArg = 1;
+ varDsc->lvArgReg = genMapRegArgNumToRegNum(varArgHndArgNum, TYP_I_IMPL);
+#if FEATURE_MULTIREG__ARGS
+ varDsc->lvOtherArgReg = REG_NA;
+#endif
+ varDsc->setPrefReg(varDsc->lvArgReg, this);
+ varDsc->lvOnFrame = true; // The final home for this incoming register might be our local stack frame
+#ifdef _TARGET_ARM_
+ // This has to be spilled right in front of the real arguments and we have
+ // to pre-spill all the argument registers explicitly because we only have
+ // have symbols for the declared ones, not any potential variadic ones.
+ for (unsigned ix = varArgHndArgNum; ix < ArrLen(intArgMasks); ix++)
+ {
+ codeGen->regSet.rsMaskPreSpillRegArg |= intArgMasks[ix];
+ }
+#endif // _TARGET_ARM_
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("'VarArgHnd' passed in register %s\n", getRegName(varDsc->lvArgReg));
+ }
+#endif // DEBUG
+ }
+#ifndef LEGACY_BACKEND
+ else
+ {
+ // For the RyuJIT backend, we need to mark these as being on the stack,
+ // as this is not done elsewhere in the case that canEnreg returns false.
+ varDsc->lvOnFrame = true;
+ }
+#endif // !LEGACY_BACKEND
+
+ /* Update the total argument size, count and varDsc */
+
+ compArgSize += TARGET_POINTER_SIZE;
+
+ varDscInfo->varNum++;
+ varDscInfo->varDsc++;
+
+#if defined(_TARGET_X86_)
+ varDsc->lvStkOffs = compArgSize;
+
+ // Allocate a temp to point at the beginning of the args
+
+ lvaVarargsBaseOfStkArgs = lvaGrabTemp(false DEBUGARG("Varargs BaseOfStkArgs"));
+ lvaTable[lvaVarargsBaseOfStkArgs].lvType = TYP_I_IMPL;
+
+#endif // _TARGET_X86_
+ }
+}
+
+/*****************************************************************************/
+void Compiler::lvaInitVarDsc(LclVarDsc* varDsc,
+ unsigned varNum,
+ CorInfoType corInfoType,
+ CORINFO_CLASS_HANDLE typeHnd,
+ CORINFO_ARG_LIST_HANDLE varList,
+ CORINFO_SIG_INFO* varSig)
+{
+ noway_assert(varDsc == &lvaTable[varNum]);
+
+ switch (corInfoType)
+ {
+ // Mark types that looks like a pointer for doing shadow-copying of
+ // parameters if we have an unsafe buffer.
+ // Note that this does not handle structs with pointer fields. Instead,
+ // we rely on using the assign-groups/equivalence-groups in
+ // gsFindVulnerableParams() to determine if a buffer-struct contains a
+ // pointer. We could do better by having the EE determine this for us.
+ // Note that we want to keep buffers without pointers at lower memory
+ // addresses than buffers with pointers.
+ case CORINFO_TYPE_PTR:
+ case CORINFO_TYPE_BYREF:
+ case CORINFO_TYPE_CLASS:
+ case CORINFO_TYPE_STRING:
+ case CORINFO_TYPE_VAR:
+ case CORINFO_TYPE_REFANY:
+ varDsc->lvIsPtr = 1;
+ break;
+ default:
+ break;
+ }
+
+ var_types type = JITtype2varType(corInfoType);
+ if (varTypeIsFloating(type))
+ {
+ compFloatingPointUsed = true;
+ }
+
+ if (tiVerificationNeeded)
+ {
+ varDsc->lvVerTypeInfo = verParseArgSigToTypeInfo(varSig, varList);
+ }
+
+ if (tiVerificationNeeded)
+ {
+ if (varDsc->lvIsParam)
+ {
+ // For an incoming ValueType we better be able to have the full type information
+ // so that we can layout the parameter offsets correctly
+
+ if (varTypeIsStruct(type) && varDsc->lvVerTypeInfo.IsDead())
+ {
+ BADCODE("invalid ValueType parameter");
+ }
+
+ // For an incoming reference type we need to verify that the actual type is
+ // a reference type and not a valuetype.
+
+ if (type == TYP_REF &&
+ !(varDsc->lvVerTypeInfo.IsType(TI_REF) || varDsc->lvVerTypeInfo.IsUnboxedGenericTypeVar()))
+ {
+ BADCODE("parameter type mismatch");
+ }
+ }
+
+ // Disallow byrefs to byref like objects (ArgTypeHandle)
+ // techncally we could get away with just not setting them
+ if (varDsc->lvVerTypeInfo.IsByRef() && verIsByRefLike(DereferenceByRef(varDsc->lvVerTypeInfo)))
+ {
+ varDsc->lvVerTypeInfo = typeInfo();
+ }
+
+ // we don't want the EE to assert in lvaSetStruct on bad sigs, so change
+ // the JIT type to avoid even trying to call back
+ if (varTypeIsStruct(type) && varDsc->lvVerTypeInfo.IsDead())
+ {
+ type = TYP_VOID;
+ }
+ }
+
+ if (typeHnd)
+ {
+ unsigned cFlags = info.compCompHnd->getClassAttribs(typeHnd);
+
+ // We can get typeHnds for primitive types, these are value types which only contain
+ // a primitive. We will need the typeHnd to distinguish them, so we store it here.
+ if ((cFlags & CORINFO_FLG_VALUECLASS) && !varTypeIsStruct(type))
+ {
+ if (tiVerificationNeeded == false)
+ {
+ // printf("This is a struct that the JIT will treat as a primitive\n");
+ varDsc->lvVerTypeInfo = verMakeTypeInfo(typeHnd);
+ }
+ }
+
+ varDsc->lvOverlappingFields = StructHasOverlappingFields(cFlags);
+ }
+
+ if (varTypeIsGC(type))
+ {
+ varDsc->lvStructGcCount = 1;
+ }
+
+ // Set the lvType (before this point it is TYP_UNDEF).
+ if ((varTypeIsStruct(type)))
+ {
+ lvaSetStruct(varNum, typeHnd, typeHnd != nullptr, !tiVerificationNeeded);
+ }
+ else
+ {
+ varDsc->lvType = type;
+ }
+
+#if OPT_BOOL_OPS
+ if (type == TYP_BOOL)
+ {
+ varDsc->lvIsBoolean = true;
+ }
+#endif
+
+#ifdef DEBUG
+ varDsc->lvStkOffs = BAD_STK_OFFS;
+#endif
+}
+
+/*****************************************************************************
+ * Returns our internal varNum for a given IL variable.
+ * Asserts assume it is called after lvaTable[] has been set up.
+ */
+
+unsigned Compiler::compMapILvarNum(unsigned ILvarNum)
+{
+ noway_assert(ILvarNum < info.compILlocalsCount || ILvarNum > unsigned(ICorDebugInfo::UNKNOWN_ILNUM));
+
+ unsigned varNum;
+
+ if (ILvarNum == (unsigned)ICorDebugInfo::VARARGS_HND_ILNUM)
+ {
+ // The varargs cookie is the last argument in lvaTable[]
+ noway_assert(info.compIsVarArgs);
+
+ varNum = lvaVarargsHandleArg;
+ noway_assert(lvaTable[varNum].lvIsParam);
+ }
+ else if (ILvarNum == (unsigned)ICorDebugInfo::RETBUF_ILNUM)
+ {
+ noway_assert(info.compRetBuffArg != BAD_VAR_NUM);
+ varNum = info.compRetBuffArg;
+ }
+ else if (ILvarNum == (unsigned)ICorDebugInfo::TYPECTXT_ILNUM)
+ {
+ noway_assert(info.compTypeCtxtArg >= 0);
+ varNum = unsigned(info.compTypeCtxtArg);
+ }
+ else if (ILvarNum < info.compILargsCount)
+ {
+ // Parameter
+ varNum = compMapILargNum(ILvarNum);
+ noway_assert(lvaTable[varNum].lvIsParam);
+ }
+ else if (ILvarNum < info.compILlocalsCount)
+ {
+ // Local variable
+ unsigned lclNum = ILvarNum - info.compILargsCount;
+ varNum = info.compArgsCount + lclNum;
+ noway_assert(!lvaTable[varNum].lvIsParam);
+ }
+ else
+ {
+ unreached();
+ }
+
+ noway_assert(varNum < info.compLocalsCount);
+ return varNum;
+}
+
+/*****************************************************************************
+ * Returns the IL variable number given our internal varNum.
+ * Special return values are VARG_ILNUM, RETBUF_ILNUM, TYPECTXT_ILNUM.
+ *
+ * Returns UNKNOWN_ILNUM if it can't be mapped.
+ */
+
+unsigned Compiler::compMap2ILvarNum(unsigned varNum)
+{
+ if (compIsForInlining())
+ {
+ return impInlineInfo->InlinerCompiler->compMap2ILvarNum(varNum);
+ }
+
+ noway_assert(varNum < lvaCount);
+
+ if (varNum == info.compRetBuffArg)
+ {
+ return (unsigned)ICorDebugInfo::RETBUF_ILNUM;
+ }
+
+ // Is this a varargs function?
+ if (info.compIsVarArgs && varNum == lvaVarargsHandleArg)
+ {
+ return (unsigned)ICorDebugInfo::VARARGS_HND_ILNUM;
+ }
+
+ // We create an extra argument for the type context parameter
+ // needed for shared generic code.
+ if ((info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) && varNum == (unsigned)info.compTypeCtxtArg)
+ {
+ return (unsigned)ICorDebugInfo::TYPECTXT_ILNUM;
+ }
+
+ // Now mutate varNum to remove extra parameters from the count.
+ if ((info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) && varNum > (unsigned)info.compTypeCtxtArg)
+ {
+ varNum--;
+ }
+
+ if (info.compIsVarArgs && varNum > lvaVarargsHandleArg)
+ {
+ varNum--;
+ }
+
+ /* Is there a hidden argument for the return buffer.
+ Note that this code works because if the RetBuffArg is not present,
+ compRetBuffArg will be BAD_VAR_NUM */
+ if (info.compRetBuffArg != BAD_VAR_NUM && varNum > info.compRetBuffArg)
+ {
+ varNum--;
+ }
+
+ if (varNum >= info.compLocalsCount)
+ {
+ return (unsigned)ICorDebugInfo::UNKNOWN_ILNUM; // Cannot be mapped
+ }
+
+ return varNum;
+}
+
+/*****************************************************************************
+ * Returns true if variable "varNum" may be address-exposed.
+ */
+
+bool Compiler::lvaVarAddrExposed(unsigned varNum)
+{
+ noway_assert(varNum < lvaCount);
+ LclVarDsc* varDsc = &lvaTable[varNum];
+
+ return varDsc->lvAddrExposed;
+}
+
+/*****************************************************************************
+ * Returns true iff variable "varNum" should not be enregistered (or one of several reasons).
+ */
+
+bool Compiler::lvaVarDoNotEnregister(unsigned varNum)
+{
+ noway_assert(varNum < lvaCount);
+ LclVarDsc* varDsc = &lvaTable[varNum];
+
+ return varDsc->lvDoNotEnregister;
+}
+
+/*****************************************************************************
+ * Returns the handle to the class of the local variable varNum
+ */
+
+CORINFO_CLASS_HANDLE Compiler::lvaGetStruct(unsigned varNum)
+{
+ noway_assert(varNum < lvaCount);
+ LclVarDsc* varDsc = &lvaTable[varNum];
+
+ return varDsc->lvVerTypeInfo.GetClassHandleForValueClass();
+}
+
+/*****************************************************************************
+ *
+ * Compare function passed to qsort() by Compiler::lvaCanPromoteStructVar().
+ */
+
+/* static */
+int __cdecl Compiler::lvaFieldOffsetCmp(const void* field1, const void* field2)
+{
+ lvaStructFieldInfo* pFieldInfo1 = (lvaStructFieldInfo*)field1;
+ lvaStructFieldInfo* pFieldInfo2 = (lvaStructFieldInfo*)field2;
+
+ if (pFieldInfo1->fldOffset == pFieldInfo2->fldOffset)
+ {
+ return 0;
+ }
+ else
+ {
+ return (pFieldInfo1->fldOffset > pFieldInfo2->fldOffset) ? +1 : -1;
+ }
+}
+
+/*****************************************************************************
+ * Is this type promotable? */
+
+void Compiler::lvaCanPromoteStructType(CORINFO_CLASS_HANDLE typeHnd,
+ lvaStructPromotionInfo* StructPromotionInfo,
+ bool sortFields)
+{
+ assert(eeIsValueClass(typeHnd));
+
+ if (typeHnd != StructPromotionInfo->typeHnd)
+ {
+ // sizeof(double) represents the size of the largest primitive type that we can struct promote
+ // In the future this may be changing to XMM_REGSIZE_BYTES
+ const int MaxOffset = MAX_NumOfFieldsInPromotableStruct * sizeof(double); // must be a compile time constant
+
+ assert((BYTE)MaxOffset == MaxOffset); // because lvaStructFieldInfo.fldOffset is byte-sized
+ assert((BYTE)MAX_NumOfFieldsInPromotableStruct ==
+ MAX_NumOfFieldsInPromotableStruct); // because lvaStructFieldInfo.fieldCnt is byte-sized
+
+ bool requiresScratchVar = false;
+ bool containsHoles = false;
+ bool customLayout = false;
+ bool containsGCpointers = false;
+
+ StructPromotionInfo->typeHnd = typeHnd;
+ StructPromotionInfo->canPromote = false;
+
+ unsigned structSize = info.compCompHnd->getClassSize(typeHnd);
+ if (structSize >= MaxOffset)
+ {
+ return; // struct is too large
+ }
+
+ unsigned fieldCnt = info.compCompHnd->getClassNumInstanceFields(typeHnd);
+ if (fieldCnt == 0 || fieldCnt > MAX_NumOfFieldsInPromotableStruct)
+ {
+ return; // struct must have between 1 and MAX_NumOfFieldsInPromotableStruct fields
+ }
+
+ StructPromotionInfo->fieldCnt = (BYTE)fieldCnt;
+ DWORD typeFlags = info.compCompHnd->getClassAttribs(typeHnd);
+
+ bool treatAsOverlapping = StructHasOverlappingFields(typeFlags);
+
+#if 1 // TODO-Cleanup: Consider removing this entire #if block in the future
+
+ // This method has two callers. The one in Importer.cpp passes sortFields == false
+ // and the other passes sortFields == true.
+ // This is a workaround that leave the inlining behavior the same and before while still
+ // performing extra struct promotions when compiling the method.
+ //
+ if (!sortFields) // the condition "!sortFields" really means "we are inlining"
+ {
+ treatAsOverlapping = StructHasCustomLayout(typeFlags);
+ }
+#endif
+
+ if (treatAsOverlapping)
+ {
+ return;
+ }
+
+ // Don't struct promote if we have an CUSTOMLAYOUT flag on an HFA type
+ if (StructHasCustomLayout(typeFlags) && IsHfa(typeHnd))
+ {
+ return;
+ }
+
+#ifdef _TARGET_ARM_
+ // On ARM, we have a requirement on the struct alignment; see below.
+ unsigned structAlignment =
+ roundUp(info.compCompHnd->getClassAlignmentRequirement(typeHnd), TARGET_POINTER_SIZE);
+#endif // _TARGET_ARM
+
+ bool isHole[MaxOffset]; // isHole[] is initialized to true for every valid offset in the struct and false for
+ // the rest
+ unsigned i; // then as we process the fields we clear the isHole[] values that the field spans.
+ for (i = 0; i < MaxOffset; i++)
+ {
+ isHole[i] = (i < structSize) ? true : false;
+ }
+
+ for (BYTE ordinal = 0; ordinal < fieldCnt; ++ordinal)
+ {
+ lvaStructFieldInfo* pFieldInfo = &StructPromotionInfo->fields[ordinal];
+ pFieldInfo->fldHnd = info.compCompHnd->getFieldInClass(typeHnd, ordinal);
+ unsigned fldOffset = info.compCompHnd->getFieldOffset(pFieldInfo->fldHnd);
+
+ // The fldOffset value should never be larger than our structSize.
+ if (fldOffset >= structSize)
+ {
+ noway_assert(false);
+ return;
+ }
+
+ pFieldInfo->fldOffset = (BYTE)fldOffset;
+ pFieldInfo->fldOrdinal = ordinal;
+ CorInfoType corType = info.compCompHnd->getFieldType(pFieldInfo->fldHnd, &pFieldInfo->fldTypeHnd);
+ var_types varType = JITtype2varType(corType);
+ pFieldInfo->fldType = varType;
+ pFieldInfo->fldSize = genTypeSize(varType);
+
+ if (varTypeIsGC(varType))
+ {
+ containsGCpointers = true;
+ }
+
+ if (pFieldInfo->fldSize == 0)
+ {
+ // Non-primitive struct field. Don't promote.
+ return;
+ }
+
+ if ((pFieldInfo->fldOffset % pFieldInfo->fldSize) != 0)
+ {
+ // The code in Compiler::genPushArgList that reconstitutes
+ // struct values on the stack from promoted fields expects
+ // those fields to be at their natural alignment.
+ return;
+ }
+
+ // The end offset for this field should never be larger than our structSize.
+ noway_assert(fldOffset + pFieldInfo->fldSize <= structSize);
+
+ for (i = 0; i < pFieldInfo->fldSize; i++)
+ {
+ isHole[fldOffset + i] = false;
+ }
+
+#ifdef _TARGET_ARM_
+ // On ARM, for struct types that don't use explicit layout, the alignment of the struct is
+ // at least the max alignment of its fields. We take advantage of this invariant in struct promotion,
+ // so verify it here.
+ if (pFieldInfo->fldSize > structAlignment)
+ {
+ // Don't promote vars whose struct types violates the invariant. (Alignment == size for primitives.)
+ return;
+ }
+ // If we have any small fields we will allocate a single PromotedStructScratch local var for the method.
+ // This is a stack area that we use to assemble the small fields in order to place them in a register
+ // argument.
+ //
+ if (pFieldInfo->fldSize < TARGET_POINTER_SIZE)
+ {
+ requiresScratchVar = true;
+ }
+#endif // _TARGET_ARM_
+ }
+
+ // If we saw any GC pointer fields above then the CORINFO_FLG_CONTAINS_GC_PTR has to be set!
+ noway_assert((containsGCpointers == false) || ((typeFlags & CORINFO_FLG_CONTAINS_GC_PTR) != 0));
+
+ // If we have "Custom Layout" then we might have an explicit Size attribute
+ // Managed C++ uses this for its structs, such C++ types will not contain GC pointers.
+ //
+ // The current VM implementation also incorrectly sets the CORINFO_FLG_CUSTOMLAYOUT
+ // whenever a managed value class contains any GC pointers.
+ // (See the comment for VMFLAG_NOT_TIGHTLY_PACKED in class.h)
+ //
+ // It is important to struct promote managed value classes that have GC pointers
+ // So we compute the correct value for "CustomLayout" here
+ //
+ if (StructHasCustomLayout(typeFlags) && ((typeFlags & CORINFO_FLG_CONTAINS_GC_PTR) == 0))
+ {
+ customLayout = true;
+ }
+
+ // Check if this promoted struct contains any holes
+ //
+ for (i = 0; i < structSize; i++)
+ {
+ if (isHole[i])
+ {
+ containsHoles = true;
+ break;
+ }
+ }
+
+ // Cool, this struct is promotable.
+ StructPromotionInfo->canPromote = true;
+ StructPromotionInfo->requiresScratchVar = requiresScratchVar;
+ StructPromotionInfo->containsHoles = containsHoles;
+ StructPromotionInfo->customLayout = customLayout;
+
+ if (sortFields)
+ {
+ // Sort the fields according to the increasing order of the field offset.
+ // This is needed because the fields need to be pushed on stack (when referenced
+ // as a struct) in order.
+ qsort(StructPromotionInfo->fields, StructPromotionInfo->fieldCnt, sizeof(*StructPromotionInfo->fields),
+ lvaFieldOffsetCmp);
+ }
+ }
+ else
+ {
+ // Asking for the same type of struct as the last time.
+ // Nothing need to be done.
+ // Fall through ...
+ }
+}
+
+/*****************************************************************************
+ * Is this struct type local variable promotable? */
+
+void Compiler::lvaCanPromoteStructVar(unsigned lclNum, lvaStructPromotionInfo* StructPromotionInfo)
+{
+ noway_assert(lclNum < lvaCount);
+
+ LclVarDsc* varDsc = &lvaTable[lclNum];
+
+ noway_assert(varTypeIsStruct(varDsc));
+ noway_assert(!varDsc->lvPromoted); // Don't ask again :)
+
+#ifdef FEATURE_SIMD
+ // If this lclVar is used in a SIMD intrinsic, then we don't want to struct promote it.
+ // Note, however, that SIMD lclVars that are NOT used in a SIMD intrinsic may be
+ // profitably promoted.
+ if (varDsc->lvIsUsedInSIMDIntrinsic())
+ {
+ StructPromotionInfo->canPromote = false;
+ return;
+ }
+
+#endif
+
+ // TODO-PERF - Allow struct promotion for HFA register arguments
+
+ // Explicitly check for HFA reg args and reject them for promotion here.
+ // Promoting HFA args will fire an assert in lvaAssignFrameOffsets
+ // when the HFA reg arg is struct promoted.
+ //
+ if (varDsc->lvIsHfaRegArg())
+ {
+ StructPromotionInfo->canPromote = false;
+ return;
+ }
+
+ CORINFO_CLASS_HANDLE typeHnd = varDsc->lvVerTypeInfo.GetClassHandle();
+ lvaCanPromoteStructType(typeHnd, StructPromotionInfo, true);
+}
+
+/*****************************************************************************
+ * Promote a struct type local */
+
+void Compiler::lvaPromoteStructVar(unsigned lclNum, lvaStructPromotionInfo* StructPromotionInfo)
+{
+ LclVarDsc* varDsc = &lvaTable[lclNum];
+
+ // We should never see a reg-sized non-field-addressed struct here.
+ noway_assert(!varDsc->lvRegStruct);
+
+ noway_assert(StructPromotionInfo->canPromote);
+ noway_assert(StructPromotionInfo->typeHnd == varDsc->lvVerTypeInfo.GetClassHandle());
+
+ varDsc->lvFieldCnt = StructPromotionInfo->fieldCnt;
+ varDsc->lvFieldLclStart = lvaCount;
+ varDsc->lvPromoted = true;
+ varDsc->lvContainsHoles = StructPromotionInfo->containsHoles;
+ varDsc->lvCustomLayout = StructPromotionInfo->customLayout;
+
+#ifdef DEBUG
+ // Don't change the source to a TYP_BLK either.
+ varDsc->lvKeepType = 1;
+#endif
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nPromoting struct local V%02u (%s):", lclNum, eeGetClassName(StructPromotionInfo->typeHnd));
+ }
+#endif
+
+ for (unsigned index = 0; index < StructPromotionInfo->fieldCnt; ++index)
+ {
+ lvaStructFieldInfo* pFieldInfo = &StructPromotionInfo->fields[index];
+
+ if (varTypeIsFloating(pFieldInfo->fldType))
+ {
+ lvaTable[lclNum].lvContainsFloatingFields = 1;
+ // Whenever we promote a struct that contains a floating point field
+ // it's possible we transition from a method that originally only had integer
+ // local vars to start having FP. We have to communicate this through this flag
+ // since LSRA later on will use this flag to determine whether or not to track FP register sets.
+ compFloatingPointUsed = true;
+ }
+
+// Now grab the temp for the field local.
+
+#ifdef DEBUG
+ char buf[200];
+ char* bufp = &buf[0];
+
+ sprintf_s(bufp, sizeof(buf), "%s V%02u.%s (fldOffset=0x%x)", "field", lclNum,
+ eeGetFieldName(pFieldInfo->fldHnd), pFieldInfo->fldOffset);
+
+ if (index > 0)
+ {
+ noway_assert(pFieldInfo->fldOffset > (pFieldInfo - 1)->fldOffset);
+ }
+#endif
+
+ unsigned varNum = lvaGrabTemp(false DEBUGARG(bufp)); // Lifetime of field locals might span multiple BBs, so
+ // they are long lifetime temps.
+
+ LclVarDsc* fieldVarDsc = &lvaTable[varNum];
+ fieldVarDsc->lvType = pFieldInfo->fldType;
+ fieldVarDsc->lvExactSize = pFieldInfo->fldSize;
+ fieldVarDsc->lvIsStructField = true;
+ fieldVarDsc->lvFldOffset = pFieldInfo->fldOffset;
+ fieldVarDsc->lvFldOrdinal = pFieldInfo->fldOrdinal;
+ fieldVarDsc->lvParentLcl = lclNum;
+ fieldVarDsc->lvIsParam = varDsc->lvIsParam;
+#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
+ // Do we have a parameter that can be enregistered?
+ //
+ if (varDsc->lvIsRegArg)
+ {
+ fieldVarDsc->lvIsRegArg = true;
+ fieldVarDsc->lvArgReg = varDsc->lvArgReg;
+ fieldVarDsc->setPrefReg(varDsc->lvArgReg, this); // Set the preferred register
+
+ lvaMarkRefsWeight = BB_UNITY_WEIGHT; // incRefCnts can use this compiler global variable
+ fieldVarDsc->incRefCnts(BB_UNITY_WEIGHT, this); // increment the ref count for prolog initialization
+ }
+#endif
+
+#ifdef DEBUG
+ // This temporary should not be converted to a double in stress mode,
+ // because we introduce assigns to it after the stress conversion
+ fieldVarDsc->lvKeepType = 1;
+#endif
+ }
+}
+
+#if !defined(_TARGET_64BIT_)
+//------------------------------------------------------------------------
+// lvaPromoteLongVars: "Struct promote" all register candidate longs as if they are structs of two ints.
+//
+// Arguments:
+// None.
+//
+// Return Value:
+// None.
+//
+void Compiler::lvaPromoteLongVars()
+{
+ if ((opts.compFlags & CLFLG_REGVAR) == 0)
+ {
+ return;
+ }
+ // The lvaTable might grow as we grab temps. Make a local copy here.
+ unsigned startLvaCount = lvaCount;
+ for (unsigned lclNum = 0; lclNum < startLvaCount; lclNum++)
+ {
+ LclVarDsc* varDsc = &lvaTable[lclNum];
+ if (!varTypeIsLong(varDsc) || varDsc->lvDoNotEnregister || varDsc->lvIsMultiRegArgOrRet() ||
+ (varDsc->lvRefCnt == 0))
+ {
+ continue;
+ }
+
+ // Will this work ???
+ // We can't have nested promoted structs.
+ if (varDsc->lvIsStructField)
+ {
+ if (lvaGetPromotionType(varDsc->lvParentLcl) != PROMOTION_TYPE_INDEPENDENT)
+ {
+ continue;
+ }
+ varDsc->lvIsStructField = false;
+ varDsc->lvTracked = false;
+ }
+
+ varDsc->lvFieldCnt = 2;
+ varDsc->lvFieldLclStart = lvaCount;
+ varDsc->lvPromoted = true;
+ varDsc->lvContainsHoles = false;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nPromoting long local V%02u:", lclNum);
+ }
+#endif
+
+ bool isParam = varDsc->lvIsParam;
+
+ for (unsigned index = 0; index < 2; ++index)
+ {
+ // Grab the temp for the field local.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef DEBUG
+ char buf[200];
+ char* bufp = &buf[0];
+
+ sprintf_s(bufp, sizeof(buf), "%s V%02u.%s (fldOffset=0x%x)", "field", lclNum, index == 0 ? "lo" : "hi",
+ index * 4);
+#endif
+ unsigned varNum = lvaGrabTemp(false DEBUGARG(bufp)); // Lifetime of field locals might span multiple BBs, so
+ // they are long lifetime temps.
+
+ LclVarDsc* fieldVarDsc = &lvaTable[varNum];
+ fieldVarDsc->lvType = TYP_INT;
+ fieldVarDsc->lvExactSize = genTypeSize(TYP_INT);
+ fieldVarDsc->lvIsStructField = true;
+ fieldVarDsc->lvFldOffset = (unsigned char)(index * genTypeSize(TYP_INT));
+ fieldVarDsc->lvFldOrdinal = (unsigned char)index;
+ fieldVarDsc->lvParentLcl = lclNum;
+ fieldVarDsc->lvIsParam = isParam;
+ }
+ }
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nlvaTable after lvaPromoteLongVars\n");
+ lvaTableDump();
+ }
+#endif // DEBUG
+}
+#endif // !_TARGET_64BIT_
+
+/*****************************************************************************
+ * Given a fldOffset in a promoted struct var, return the index of the local
+ that represents this field.
+*/
+
+unsigned Compiler::lvaGetFieldLocal(LclVarDsc* varDsc, unsigned int fldOffset)
+{
+ noway_assert(varTypeIsStruct(varDsc));
+ noway_assert(varDsc->lvPromoted);
+
+ for (unsigned i = varDsc->lvFieldLclStart; i < varDsc->lvFieldLclStart + varDsc->lvFieldCnt; ++i)
+ {
+ noway_assert(lvaTable[i].lvIsStructField);
+ noway_assert(lvaTable[i].lvParentLcl == (unsigned)(varDsc - lvaTable));
+ if (lvaTable[i].lvFldOffset == fldOffset)
+ {
+ return i;
+ }
+ }
+
+ // This is the not-found error return path, the caller should check for BAD_VAR_NUM
+ return BAD_VAR_NUM;
+}
+
+/*****************************************************************************
+ *
+ * Set the local var "varNum" as address-exposed.
+ * If this is a promoted struct, label it's fields the same way.
+ */
+
+void Compiler::lvaSetVarAddrExposed(unsigned varNum)
+{
+ noway_assert(varNum < lvaCount);
+
+ LclVarDsc* varDsc = &lvaTable[varNum];
+
+ varDsc->lvAddrExposed = 1;
+
+ if (varDsc->lvPromoted)
+ {
+ noway_assert(varTypeIsStruct(varDsc));
+
+ for (unsigned i = varDsc->lvFieldLclStart; i < varDsc->lvFieldLclStart + varDsc->lvFieldCnt; ++i)
+ {
+ noway_assert(lvaTable[i].lvIsStructField);
+ lvaTable[i].lvAddrExposed = 1; // Make field local as address-exposed.
+ lvaSetVarDoNotEnregister(i DEBUGARG(DNER_AddrExposed));
+ }
+ }
+
+ lvaSetVarDoNotEnregister(varNum DEBUGARG(DNER_AddrExposed));
+}
+
+/*****************************************************************************
+ *
+ * Record that the local var "varNum" should not be enregistered (for one of several reasons.)
+ */
+
+void Compiler::lvaSetVarDoNotEnregister(unsigned varNum DEBUGARG(DoNotEnregisterReason reason))
+{
+ noway_assert(varNum < lvaCount);
+ LclVarDsc* varDsc = &lvaTable[varNum];
+ varDsc->lvDoNotEnregister = 1;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nLocal V%02u should not be enregistered because: ", varNum);
+ }
+ switch (reason)
+ {
+ case DNER_AddrExposed:
+ JITDUMP("it is address exposed\n");
+ assert(varDsc->lvAddrExposed);
+ break;
+ case DNER_IsStruct:
+ JITDUMP("it is a struct\n");
+ assert(varTypeIsStruct(varDsc));
+ break;
+ case DNER_BlockOp:
+ JITDUMP("written in a block op\n");
+ varDsc->lvLclBlockOpAddr = 1;
+ break;
+ case DNER_LocalField:
+ JITDUMP("was accessed as a local field\n");
+ varDsc->lvLclFieldExpr = 1;
+ break;
+ case DNER_VMNeedsStackAddr:
+ JITDUMP("needs stack addr\n");
+ varDsc->lvVMNeedsStackAddr = 1;
+ break;
+ case DNER_LiveInOutOfHandler:
+ JITDUMP("live in/out of a handler\n");
+ varDsc->lvLiveInOutOfHndlr = 1;
+ break;
+ case DNER_LiveAcrossUnmanagedCall:
+ JITDUMP("live across unmanaged call\n");
+ varDsc->lvLiveAcrossUCall = 1;
+ break;
+#ifdef JIT32_GCENCODER
+ case DNER_PinningRef:
+ JITDUMP("pinning ref\n");
+ assert(varDsc->lvPinned);
+ break;
+#endif
+ default:
+ unreached();
+ break;
+ }
+#endif
+}
+
+// Returns true if this local var is a multireg struct
+bool Compiler::lvaIsMultiregStruct(LclVarDsc* varDsc)
+{
+ if (varDsc->TypeGet() == TYP_STRUCT)
+ {
+ CORINFO_CLASS_HANDLE clsHnd = varDsc->lvVerTypeInfo.GetClassHandleForValueClass();
+ structPassingKind howToPassStruct;
+
+ var_types type = getArgTypeForStruct(clsHnd, &howToPassStruct, varDsc->lvExactSize);
+
+ if (howToPassStruct == SPK_ByValueAsHfa)
+ {
+ assert(type = TYP_STRUCT);
+ return true;
+ }
+
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) || defined(_TARGET_ARM64_)
+ if (howToPassStruct == SPK_ByValue)
+ {
+ assert(type = TYP_STRUCT);
+ return true;
+ }
+#endif
+ }
+ return false;
+}
+
+/*****************************************************************************
+ * Set the lvClass for a local variable of a struct type */
+
+void Compiler::lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool unsafeValueClsCheck, bool setTypeInfo)
+{
+ noway_assert(varNum < lvaCount);
+
+ LclVarDsc* varDsc = &lvaTable[varNum];
+ if (setTypeInfo)
+ {
+ varDsc->lvVerTypeInfo = typeInfo(TI_STRUCT, typeHnd);
+ }
+
+ // Set the type and associated info if we haven't already set it.
+ var_types structType = varDsc->lvType;
+ if (varDsc->lvType == TYP_UNDEF)
+ {
+ varDsc->lvType = TYP_STRUCT;
+ }
+ if (varDsc->lvExactSize == 0)
+ {
+ varDsc->lvExactSize = info.compCompHnd->getClassSize(typeHnd);
+
+ size_t lvSize = varDsc->lvSize();
+ assert((lvSize % sizeof(void*)) ==
+ 0); // The struct needs to be a multiple of sizeof(void*) bytes for getClassGClayout() to be valid.
+ varDsc->lvGcLayout = (BYTE*)compGetMemA((lvSize / sizeof(void*)) * sizeof(BYTE), CMK_LvaTable);
+ unsigned numGCVars;
+ var_types simdBaseType = TYP_UNKNOWN;
+ varDsc->lvType = impNormStructType(typeHnd, varDsc->lvGcLayout, &numGCVars, &simdBaseType);
+
+ // We only save the count of GC vars in a struct up to 7.
+ if (numGCVars >= 8)
+ {
+ numGCVars = 7;
+ }
+ varDsc->lvStructGcCount = numGCVars;
+#if FEATURE_SIMD
+ if (simdBaseType != TYP_UNKNOWN)
+ {
+ assert(varTypeIsSIMD(varDsc));
+ varDsc->lvSIMDType = true;
+ varDsc->lvBaseType = simdBaseType;
+ }
+#endif // FEATURE_SIMD
+#ifdef FEATURE_HFA
+ // for structs that are small enough, we check and set lvIsHfa and lvHfaTypeIsFloat
+ if (varDsc->lvExactSize <= MAX_PASS_MULTIREG_BYTES)
+ {
+ var_types hfaType = GetHfaType(typeHnd); // set to float or double if it is an HFA, otherwise TYP_UNDEF
+ if (varTypeIsFloating(hfaType))
+ {
+ varDsc->_lvIsHfa = true;
+ varDsc->lvSetHfaTypeIsFloat(hfaType == TYP_FLOAT);
+
+ // hfa variables can never contain GC pointers
+ assert(varDsc->lvStructGcCount == 0);
+ // The size of this struct should be evenly divisible by 4 or 8
+ assert((varDsc->lvExactSize % genTypeSize(hfaType)) == 0);
+ // The number of elements in the HFA should fit into our MAX_ARG_REG_COUNT limit
+ assert((varDsc->lvExactSize / genTypeSize(hfaType)) <= MAX_ARG_REG_COUNT);
+ }
+ }
+#endif // FEATURE_HFA
+ }
+ else
+ {
+ assert(varDsc->lvExactSize != 0);
+#if FEATURE_SIMD
+ assert(!varTypeIsSIMD(varDsc) || (varDsc->lvBaseType != TYP_UNKNOWN));
+#endif // FEATURE_SIMD
+ }
+
+#ifndef _TARGET_64BIT_
+ bool fDoubleAlignHint = FALSE;
+#ifdef _TARGET_X86_
+ fDoubleAlignHint = TRUE;
+#endif
+
+ if (info.compCompHnd->getClassAlignmentRequirement(typeHnd, fDoubleAlignHint) == 8)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Marking struct in V%02i with double align flag\n", varNum);
+ }
+#endif
+ varDsc->lvStructDoubleAlign = 1;
+ }
+#endif // not _TARGET_64BIT_
+
+ unsigned classAttribs = info.compCompHnd->getClassAttribs(typeHnd);
+
+ varDsc->lvOverlappingFields = StructHasOverlappingFields(classAttribs);
+
+ // Check whether this local is an unsafe value type and requires GS cookie protection.
+ // GS checks require the stack to be re-ordered, which can't be done with EnC.
+ if (unsafeValueClsCheck && (classAttribs & CORINFO_FLG_UNSAFE_VALUECLASS) && !opts.compDbgEnC)
+ {
+ setNeedsGSSecurityCookie();
+ compGSReorderStackLayout = true;
+ varDsc->lvIsUnsafeBuffer = true;
+ }
+}
+
+/*****************************************************************************
+ * Returns the array of BYTEs containing the GC layout information
+ */
+
+BYTE* Compiler::lvaGetGcLayout(unsigned varNum)
+{
+ noway_assert(varTypeIsStruct(lvaTable[varNum].lvType) && (lvaTable[varNum].lvExactSize >= TARGET_POINTER_SIZE));
+
+ return lvaTable[varNum].lvGcLayout;
+}
+
+/*****************************************************************************
+ * Return the number of bytes needed for a local variable
+ */
+
+unsigned Compiler::lvaLclSize(unsigned varNum)
+{
+ noway_assert(varNum < lvaCount);
+
+ var_types varType = lvaTable[varNum].TypeGet();
+
+ switch (varType)
+ {
+ case TYP_STRUCT:
+ case TYP_BLK:
+ return lvaTable[varNum].lvSize();
+
+ case TYP_LCLBLK:
+#if FEATURE_FIXED_OUT_ARGS
+ noway_assert(lvaOutgoingArgSpaceSize >= 0);
+ noway_assert(varNum == lvaOutgoingArgSpaceVar);
+ return lvaOutgoingArgSpaceSize;
+
+#else // FEATURE_FIXED_OUT_ARGS
+ assert(!"Unknown size");
+ NO_WAY("Target doesn't support TYP_LCLBLK");
+
+ // Keep prefast happy
+ __fallthrough;
+
+#endif // FEATURE_FIXED_OUT_ARGS
+
+ default: // This must be a primitive var. Fall out of switch statement
+ break;
+ }
+#ifdef _TARGET_64BIT_
+ // We only need this Quirk for _TARGET_64BIT_
+ if (lvaTable[varNum].lvQuirkToLong)
+ {
+ noway_assert(lvaTable[varNum].lvAddrExposed);
+ return genTypeStSz(TYP_LONG) * sizeof(int); // return 8 (2 * 4)
+ }
+#endif
+ return genTypeStSz(varType) * sizeof(int);
+}
+
+//
+// Return the exact width of local variable "varNum" -- the number of bytes
+// you'd need to copy in order to overwrite the value.
+//
+unsigned Compiler::lvaLclExactSize(unsigned varNum)
+{
+ noway_assert(varNum < lvaCount);
+
+ var_types varType = lvaTable[varNum].TypeGet();
+
+ switch (varType)
+ {
+ case TYP_STRUCT:
+ case TYP_BLK:
+ return lvaTable[varNum].lvExactSize;
+
+ case TYP_LCLBLK:
+#if FEATURE_FIXED_OUT_ARGS
+ noway_assert(lvaOutgoingArgSpaceSize >= 0);
+ noway_assert(varNum == lvaOutgoingArgSpaceVar);
+ return lvaOutgoingArgSpaceSize;
+
+#else // FEATURE_FIXED_OUT_ARGS
+ assert(!"Unknown size");
+ NO_WAY("Target doesn't support TYP_LCLBLK");
+
+ // Keep prefast happy
+ __fallthrough;
+
+#endif // FEATURE_FIXED_OUT_ARGS
+
+ default: // This must be a primitive var. Fall out of switch statement
+ break;
+ }
+
+ return genTypeSize(varType);
+}
+
+// getBBWeight -- get the normalized weight of this block
+unsigned BasicBlock::getBBWeight(Compiler* comp)
+{
+ if (this->bbWeight == 0)
+ {
+ return 0;
+ }
+ else
+ {
+ unsigned calledWeight = comp->fgCalledWeight;
+ if (calledWeight == 0)
+ {
+ calledWeight = comp->fgFirstBB->bbWeight;
+ if (calledWeight == 0)
+ {
+ calledWeight = BB_UNITY_WEIGHT;
+ }
+ }
+ if (this->bbWeight < (BB_MAX_WEIGHT / BB_UNITY_WEIGHT))
+ {
+ return max(1, (((this->bbWeight * BB_UNITY_WEIGHT) + (calledWeight / 2)) / calledWeight));
+ }
+ else
+ {
+ return (unsigned)((((double)this->bbWeight * (double)BB_UNITY_WEIGHT) / (double)calledWeight) + 0.5);
+ }
+ }
+}
+
+/*****************************************************************************
+ *
+ * Callback used by the tree walker to call lvaDecRefCnts
+ */
+Compiler::fgWalkResult Compiler::lvaDecRefCntsCB(GenTreePtr* pTree, fgWalkData* data)
+{
+ data->compiler->lvaDecRefCnts(*pTree);
+ return WALK_CONTINUE;
+}
+
+// Decrement the ref counts for all locals contained in the tree and its children.
+void Compiler::lvaRecursiveDecRefCounts(GenTreePtr tree)
+{
+ assert(lvaLocalVarRefCounted);
+
+ // We could just use the recursive walker for all cases but that is a
+ // fairly heavyweight thing to spin up when we're usually just handling a leaf.
+ if (tree->OperIsLeaf())
+ {
+ if (tree->OperIsLocal())
+ {
+ lvaDecRefCnts(tree);
+ }
+ }
+ else
+ {
+ fgWalkTreePre(&tree, Compiler::lvaDecRefCntsCB, (void*)this, true);
+ }
+}
+
+// Increment the ref counts for all locals contained in the tree and its children.
+void Compiler::lvaRecursiveIncRefCounts(GenTreePtr tree)
+{
+ assert(lvaLocalVarRefCounted);
+
+ // We could just use the recursive walker for all cases but that is a
+ // fairly heavyweight thing to spin up when we're usually just handling a leaf.
+ if (tree->OperIsLeaf())
+ {
+ if (tree->OperIsLocal())
+ {
+ lvaIncRefCnts(tree);
+ }
+ }
+ else
+ {
+ fgWalkTreePre(&tree, Compiler::lvaIncRefCntsCB, (void*)this, true);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Helper passed to the tree walker to decrement the refCnts for
+ * all local variables in an expression
+ */
+void Compiler::lvaDecRefCnts(GenTreePtr tree)
+{
+ assert(compCurBB != nullptr);
+ lvaDecRefCnts(compCurBB, tree);
+}
+
+void Compiler::lvaDecRefCnts(BasicBlock* block, GenTreePtr tree)
+{
+ assert(block != nullptr);
+ assert(tree != nullptr);
+
+ unsigned lclNum;
+ LclVarDsc* varDsc;
+
+ noway_assert(lvaRefCountingStarted || lvaLocalVarRefCounted);
+
+ if ((tree->gtOper == GT_CALL) && (tree->gtFlags & GTF_CALL_UNMANAGED))
+ {
+ assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
+ if (!opts.ShouldUsePInvokeHelpers())
+ {
+ /* Get the special variable descriptor */
+
+ lclNum = info.compLvFrameListRoot;
+
+ noway_assert(lclNum <= lvaCount);
+ varDsc = lvaTable + lclNum;
+
+ /* Decrement the reference counts twice */
+
+ varDsc->decRefCnts(block->getBBWeight(this), this);
+ varDsc->decRefCnts(block->getBBWeight(this), this);
+ }
+ }
+ else
+ {
+ /* This must be a local variable */
+
+ noway_assert(tree->OperIsLocal());
+
+ /* Get the variable descriptor */
+
+ lclNum = tree->gtLclVarCommon.gtLclNum;
+
+ noway_assert(lclNum < lvaCount);
+ varDsc = lvaTable + lclNum;
+
+ /* Decrement its lvRefCnt and lvRefCntWtd */
+
+ varDsc->decRefCnts(block->getBBWeight(this), this);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Callback used by the tree walker to call lvaIncRefCnts
+ */
+Compiler::fgWalkResult Compiler::lvaIncRefCntsCB(GenTreePtr* pTree, fgWalkData* data)
+{
+ data->compiler->lvaIncRefCnts(*pTree);
+ return WALK_CONTINUE;
+}
+
+/*****************************************************************************
+ *
+ * Helper passed to the tree walker to increment the refCnts for
+ * all local variables in an expression
+ */
+void Compiler::lvaIncRefCnts(GenTreePtr tree)
+{
+ unsigned lclNum;
+ LclVarDsc* varDsc;
+
+ noway_assert(lvaRefCountingStarted || lvaLocalVarRefCounted);
+
+ if ((tree->gtOper == GT_CALL) && (tree->gtFlags & GTF_CALL_UNMANAGED))
+ {
+ assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
+ if (!opts.ShouldUsePInvokeHelpers())
+ {
+ /* Get the special variable descriptor */
+
+ lclNum = info.compLvFrameListRoot;
+
+ noway_assert(lclNum <= lvaCount);
+ varDsc = lvaTable + lclNum;
+
+ /* Increment the reference counts twice */
+
+ varDsc->incRefCnts(compCurBB->getBBWeight(this), this);
+ varDsc->incRefCnts(compCurBB->getBBWeight(this), this);
+ }
+ }
+ else
+ {
+ /* This must be a local variable */
+
+ noway_assert(tree->gtOper == GT_LCL_VAR || tree->gtOper == GT_LCL_FLD || tree->gtOper == GT_STORE_LCL_VAR ||
+ tree->gtOper == GT_STORE_LCL_FLD);
+
+ /* Get the variable descriptor */
+
+ lclNum = tree->gtLclVarCommon.gtLclNum;
+
+ noway_assert(lclNum < lvaCount);
+ varDsc = lvaTable + lclNum;
+
+ /* Increment its lvRefCnt and lvRefCntWtd */
+
+ varDsc->incRefCnts(compCurBB->getBBWeight(this), this);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Compare function passed to qsort() by Compiler::lclVars.lvaSortByRefCount().
+ * when generating SMALL_CODE.
+ * Return positive if dsc2 has a higher ref count
+ * Return negative if dsc1 has a higher ref count
+ * Return zero if the ref counts are the same
+ * lvPrefReg is only used to break ties
+ */
+
+/* static */
+int __cdecl Compiler::RefCntCmp(const void* op1, const void* op2)
+{
+ LclVarDsc* dsc1 = *(LclVarDsc**)op1;
+ LclVarDsc* dsc2 = *(LclVarDsc**)op2;
+
+ /* Make sure we preference tracked variables over untracked variables */
+
+ if (dsc1->lvTracked != dsc2->lvTracked)
+ {
+ return (dsc2->lvTracked) ? +1 : -1;
+ }
+
+ unsigned weight1 = dsc1->lvRefCnt;
+ unsigned weight2 = dsc2->lvRefCnt;
+
+#if !FEATURE_FP_REGALLOC
+ /* Force integer candidates to sort above float candidates */
+
+ bool isFloat1 = isFloatRegType(dsc1->lvType);
+ bool isFloat2 = isFloatRegType(dsc2->lvType);
+
+ if (isFloat1 != isFloat2)
+ {
+ if (weight2 && isFloat1)
+ {
+ return +1;
+ }
+ if (weight1 && isFloat2)
+ {
+ return -1;
+ }
+ }
+#endif
+
+ int diff = weight2 - weight1;
+
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ /* The unweighted ref counts were the same */
+ /* If the weighted ref counts are different then use their difference */
+ diff = dsc2->lvRefCntWtd - dsc1->lvRefCntWtd;
+
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ /* We have equal ref counts and weighted ref counts */
+
+ /* Break the tie by: */
+ /* Increasing the weight by 2 if we have exactly one bit set in lvPrefReg */
+ /* Increasing the weight by 1 if we have more than one bit set in lvPrefReg */
+ /* Increasing the weight by 0.5 if we are a GC type */
+ /* Increasing the weight by 0.5 if we were enregistered in the previous pass */
+
+ if (weight1)
+ {
+ if (dsc1->lvPrefReg)
+ {
+ if ((dsc1->lvPrefReg & ~RBM_BYTE_REG_FLAG) && genMaxOneBit((unsigned)dsc1->lvPrefReg))
+ {
+ weight1 += 2 * BB_UNITY_WEIGHT;
+ }
+ else
+ {
+ weight1 += 1 * BB_UNITY_WEIGHT;
+ }
+ }
+ if (varTypeIsGC(dsc1->TypeGet()))
+ {
+ weight1 += BB_UNITY_WEIGHT / 2;
+ }
+
+ if (dsc1->lvRegister)
+ {
+ weight1 += BB_UNITY_WEIGHT / 2;
+ }
+ }
+
+ if (weight2)
+ {
+ if (dsc2->lvPrefReg)
+ {
+ if ((dsc2->lvPrefReg & ~RBM_BYTE_REG_FLAG) && genMaxOneBit((unsigned)dsc2->lvPrefReg))
+ {
+ weight2 += 2 * BB_UNITY_WEIGHT;
+ }
+ else
+ {
+ weight2 += 1 * BB_UNITY_WEIGHT;
+ }
+ }
+ if (varTypeIsGC(dsc2->TypeGet()))
+ {
+ weight1 += BB_UNITY_WEIGHT / 2;
+ }
+
+ if (dsc2->lvRegister)
+ {
+ weight2 += BB_UNITY_WEIGHT / 2;
+ }
+ }
+
+ diff = weight2 - weight1;
+
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ /* To achieve a Stable Sort we use the LclNum (by way of the pointer address) */
+
+ if (dsc1 < dsc2)
+ {
+ return -1;
+ }
+ if (dsc1 > dsc2)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ *
+ * Compare function passed to qsort() by Compiler::lclVars.lvaSortByRefCount().
+ * when not generating SMALL_CODE.
+ * Return positive if dsc2 has a higher weighted ref count
+ * Return negative if dsc1 has a higher weighted ref count
+ * Return zero if the ref counts are the same
+ */
+
+/* static */
+int __cdecl Compiler::WtdRefCntCmp(const void* op1, const void* op2)
+{
+ LclVarDsc* dsc1 = *(LclVarDsc**)op1;
+ LclVarDsc* dsc2 = *(LclVarDsc**)op2;
+
+ /* Make sure we preference tracked variables over untracked variables */
+
+ if (dsc1->lvTracked != dsc2->lvTracked)
+ {
+ return (dsc2->lvTracked) ? +1 : -1;
+ }
+
+ unsigned weight1 = dsc1->lvRefCntWtd;
+ unsigned weight2 = dsc2->lvRefCntWtd;
+
+#if !FEATURE_FP_REGALLOC
+ /* Force integer candidates to sort above float candidates */
+
+ bool isFloat1 = isFloatRegType(dsc1->lvType);
+ bool isFloat2 = isFloatRegType(dsc2->lvType);
+
+ if (isFloat1 != isFloat2)
+ {
+ if (weight2 && isFloat1)
+ {
+ return +1;
+ }
+ if (weight1 && isFloat2)
+ {
+ return -1;
+ }
+ }
+#endif
+
+ /* Increase the weight by 2 if we have exactly one bit set in lvPrefReg */
+ /* Increase the weight by 1 if we have more than one bit set in lvPrefReg */
+
+ if (weight1 && dsc1->lvPrefReg)
+ {
+ if ((dsc1->lvPrefReg & ~RBM_BYTE_REG_FLAG) && genMaxOneBit((unsigned)dsc1->lvPrefReg))
+ {
+ weight1 += 2 * BB_UNITY_WEIGHT;
+ }
+ else
+ {
+ weight1 += 1 * BB_UNITY_WEIGHT;
+ }
+ }
+
+ if (weight2 && dsc2->lvPrefReg)
+ {
+ if ((dsc2->lvPrefReg & ~RBM_BYTE_REG_FLAG) && genMaxOneBit((unsigned)dsc2->lvPrefReg))
+ {
+ weight2 += 2 * BB_UNITY_WEIGHT;
+ }
+ else
+ {
+ weight2 += 1 * BB_UNITY_WEIGHT;
+ }
+ }
+
+ if (weight2 > weight1)
+ {
+ return 1;
+ }
+ else if (weight2 < weight1)
+ {
+ return -1;
+ }
+
+ // Otherwise, we have equal weighted ref counts.
+
+ /* If the unweighted ref counts are different then use their difference */
+ int diff = (int)dsc2->lvRefCnt - (int)dsc1->lvRefCnt;
+
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ /* If one is a GC type and the other is not the GC type wins */
+ if (varTypeIsGC(dsc1->TypeGet()) != varTypeIsGC(dsc2->TypeGet()))
+ {
+ if (varTypeIsGC(dsc1->TypeGet()))
+ {
+ diff = -1;
+ }
+ else
+ {
+ diff = +1;
+ }
+
+ return diff;
+ }
+
+ /* If one was enregistered in the previous pass then it wins */
+ if (dsc1->lvRegister != dsc2->lvRegister)
+ {
+ if (dsc1->lvRegister)
+ {
+ diff = -1;
+ }
+ else
+ {
+ diff = +1;
+ }
+
+ return diff;
+ }
+
+ /* We have a tie! */
+
+ /* To achieve a Stable Sort we use the LclNum (by way of the pointer address) */
+
+ if (dsc1 < dsc2)
+ {
+ return -1;
+ }
+ if (dsc1 > dsc2)
+ {
+ return +1;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ *
+ * Sort the local variable table by refcount and assign tracking indices.
+ */
+
+void Compiler::lvaSortOnly()
+{
+ /* Now sort the variable table by ref-count */
+
+ qsort(lvaRefSorted, lvaCount, sizeof(*lvaRefSorted), (compCodeOpt() == SMALL_CODE) ? RefCntCmp : WtdRefCntCmp);
+
+ lvaSortAgain = false;
+
+ lvaDumpRefCounts();
+}
+
+void Compiler::lvaDumpRefCounts()
+{
+#ifdef DEBUG
+
+ if (verbose && lvaCount)
+ {
+ printf("refCnt table for '%s':\n", info.compMethodName);
+
+ for (unsigned lclNum = 0; lclNum < lvaCount; lclNum++)
+ {
+ unsigned refCnt = lvaRefSorted[lclNum]->lvRefCnt;
+ if (refCnt == 0)
+ {
+ break;
+ }
+ unsigned refCntWtd = lvaRefSorted[lclNum]->lvRefCntWtd;
+
+ printf(" ");
+ gtDispLclVar((unsigned)(lvaRefSorted[lclNum] - lvaTable));
+ printf(" [%6s]: refCnt = %4u, refCntWtd = %6s", varTypeName(lvaRefSorted[lclNum]->TypeGet()), refCnt,
+ refCntWtd2str(refCntWtd));
+
+ regMaskSmall pref = lvaRefSorted[lclNum]->lvPrefReg;
+ if (pref)
+ {
+ printf(" pref ");
+ dspRegMask(pref);
+ }
+ printf("\n");
+ }
+
+ printf("\n");
+ }
+
+#endif
+}
+
+/*****************************************************************************
+ *
+ * Sort the local variable table by refcount and assign tracking indices.
+ */
+
+void Compiler::lvaSortByRefCount()
+{
+ lvaTrackedCount = 0;
+ lvaTrackedCountInSizeTUnits = 0;
+
+ if (lvaCount == 0)
+ {
+ return;
+ }
+
+ unsigned lclNum;
+ LclVarDsc* varDsc;
+
+ LclVarDsc** refTab;
+
+ /* We'll sort the variables by ref count - allocate the sorted table */
+
+ lvaRefSorted = refTab = new (this, CMK_LvaTable) LclVarDsc*[lvaCount];
+
+ /* Fill in the table used for sorting */
+
+ for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ /* Append this variable to the table for sorting */
+
+ *refTab++ = varDsc;
+
+ /* If we have JMP, all arguments must have a location
+ * even if we don't use them inside the method */
+
+ if (compJmpOpUsed && varDsc->lvIsParam)
+ {
+ /* ...except when we have varargs and the argument is
+ passed on the stack. In that case, it's important
+ for the ref count to be zero, so that we don't attempt
+ to track them for GC info (which is not possible since we
+ don't know their offset in the stack). See the assert at the
+ end of raMarkStkVars and bug #28949 for more info. */
+
+ if (!raIsVarargsStackArg(lclNum))
+ {
+ varDsc->incRefCnts(1, this);
+ }
+ }
+
+ /* For now assume we'll be able to track all locals */
+
+ varDsc->lvTracked = 1;
+
+ /* If the ref count is zero */
+ if (varDsc->lvRefCnt == 0)
+ {
+ /* Zero ref count, make this untracked */
+ varDsc->lvTracked = 0;
+ varDsc->lvRefCntWtd = 0;
+ }
+
+#if !defined(_TARGET_64BIT_) && !defined(LEGACY_BACKEND)
+ if (varTypeIsLong(varDsc) && varDsc->lvPromoted)
+ {
+ varDsc->lvTracked = 0;
+ }
+#endif // !defined(_TARGET_64BIT_) && !defined(LEGACY_BACKEND)
+
+ // Variables that are address-exposed, and all struct locals, are never enregistered, or tracked.
+ // (The struct may be promoted, and its field variables enregistered/tracked, or the VM may "normalize"
+ // its type so that its not seen by the JIT as a struct.)
+ // Pinned variables may not be tracked (a condition of the GCInfo representation)
+ // or enregistered, on x86 -- it is believed that we can enregister pinned (more properly, "pinning")
+ // references when using the general GC encoding.
+ if (varDsc->lvAddrExposed)
+ {
+ varDsc->lvTracked = 0;
+ assert(varDsc->lvType != TYP_STRUCT ||
+ varDsc->lvDoNotEnregister); // For structs, should have set this when we set lvAddrExposed.
+ }
+ else if (varTypeIsStruct(varDsc))
+ {
+ // Promoted structs will never be considered for enregistration anyway,
+ // and the DoNotEnregister flag was used to indicate whether promotion was
+ // independent or dependent.
+ if (varDsc->lvPromoted)
+ {
+ varDsc->lvTracked = 0;
+ }
+ else if ((varDsc->lvType == TYP_STRUCT) && !varDsc->lvRegStruct)
+ {
+ lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_IsStruct));
+ }
+ }
+ else if (varDsc->lvIsStructField && (lvaGetParentPromotionType(lclNum) != PROMOTION_TYPE_INDEPENDENT))
+ {
+ // SSA must exclude struct fields that are not independently promoted
+ // as dependent fields could be assigned using a CopyBlock
+ // resulting in a single node causing multiple SSA definitions
+ // which isn't currently supported by SSA
+ //
+ // TODO-CQ: Consider using lvLclBlockOpAddr and only marking these LclVars
+ // untracked when a blockOp is used to assign the struct.
+ //
+ varDsc->lvTracked = 0; // so, don't mark as tracked
+ }
+ else if (varDsc->lvPinned)
+ {
+ varDsc->lvTracked = 0;
+#ifdef JIT32_GCENCODER
+ lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_PinningRef));
+#endif
+ }
+
+ // Are we not optimizing and we have exception handlers?
+ // if so mark all args and locals "do not enregister".
+ //
+ if (opts.MinOpts() && compHndBBtabCount > 0)
+ {
+ lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_LiveInOutOfHandler));
+ continue;
+ }
+
+ var_types type = genActualType(varDsc->TypeGet());
+
+ switch (type)
+ {
+#if CPU_HAS_FP_SUPPORT
+ case TYP_FLOAT:
+ case TYP_DOUBLE:
+#endif
+ case TYP_INT:
+ case TYP_LONG:
+ case TYP_REF:
+ case TYP_BYREF:
+#ifdef FEATURE_SIMD
+ case TYP_SIMD8:
+ case TYP_SIMD12:
+ case TYP_SIMD16:
+ case TYP_SIMD32:
+#endif // FEATURE_SIMD
+ case TYP_STRUCT:
+ break;
+
+ case TYP_UNDEF:
+ case TYP_UNKNOWN:
+ noway_assert(!"lvType not set correctly");
+ varDsc->lvType = TYP_INT;
+
+ __fallthrough;
+
+ default:
+ varDsc->lvTracked = 0;
+ }
+ }
+
+ /* Now sort the variable table by ref-count */
+
+ lvaSortOnly();
+
+ /* Decide which variables will be worth tracking */
+
+ if (lvaCount > lclMAX_TRACKED)
+ {
+ /* Mark all variables past the first 'lclMAX_TRACKED' as untracked */
+
+ for (lclNum = lclMAX_TRACKED; lclNum < lvaCount; lclNum++)
+ {
+ lvaRefSorted[lclNum]->lvTracked = 0;
+ }
+ }
+
+#ifdef DEBUG
+ // Re-Initialize to -1 for safety in debug build.
+ memset(lvaTrackedToVarNum, -1, sizeof(lvaTrackedToVarNum));
+#endif
+
+ /* Assign indices to all the variables we've decided to track */
+
+ for (lclNum = 0; lclNum < min(lvaCount, lclMAX_TRACKED); lclNum++)
+ {
+ varDsc = lvaRefSorted[lclNum];
+ if (varDsc->lvTracked)
+ {
+ noway_assert(varDsc->lvRefCnt > 0);
+
+ /* This variable will be tracked - assign it an index */
+
+ lvaTrackedToVarNum[lvaTrackedCount] = (unsigned)(varDsc - lvaTable); // The type of varDsc and lvaTable
+ // is LclVarDsc. Subtraction will give us
+ // the index.
+ varDsc->lvVarIndex = lvaTrackedCount++;
+ }
+ }
+
+ // We have a new epoch, and also cache the tracked var count in terms of size_t's sufficient to hold that many bits.
+ lvaCurEpoch++;
+ lvaTrackedCountInSizeTUnits = unsigned(roundUp(lvaTrackedCount, sizeof(size_t) * 8)) / unsigned(sizeof(size_t) * 8);
+
+#ifdef DEBUG
+ VarSetOps::AssignNoCopy(this, lvaTrackedVars, VarSetOps::MakeFull(this));
+#endif
+}
+
+#if ASSERTION_PROP
+/*****************************************************************************
+ *
+ * This is called by lvaMarkLclRefs to disqualify a variable from being
+ * considered by optAddCopies()
+ */
+void LclVarDsc::lvaDisqualifyVar()
+{
+ this->lvDisqualify = true;
+ this->lvSingleDef = false;
+ this->lvDefStmt = nullptr;
+}
+#endif // ASSERTION_PROP
+
+#ifndef LEGACY_BACKEND
+/**********************************************************************************
+* Get type of a variable when passed as an argument.
+*/
+var_types LclVarDsc::lvaArgType()
+{
+ var_types type = TypeGet();
+
+#ifdef _TARGET_AMD64_
+ if (type == TYP_STRUCT)
+ {
+ switch (lvExactSize)
+ {
+ case 1:
+ type = TYP_BYTE;
+ break;
+ case 2:
+ type = TYP_SHORT;
+ break;
+ case 4:
+ type = TYP_INT;
+ break;
+ case 8:
+ switch (*lvGcLayout)
+ {
+ case TYPE_GC_NONE:
+ type = TYP_I_IMPL;
+ break;
+
+ case TYPE_GC_REF:
+ type = TYP_REF;
+ break;
+
+ case TYPE_GC_BYREF:
+ type = TYP_BYREF;
+ break;
+
+ default:
+ unreached();
+ }
+ break;
+
+ default:
+ type = TYP_BYREF;
+ break;
+ }
+ }
+#elif defined(_TARGET_X86_)
+// Nothing to do; use the type as is.
+#else
+ NYI("lvaArgType");
+#endif //_TARGET_AMD64_
+
+ return type;
+}
+#endif // !LEGACY_BACKEND
+
+/*****************************************************************************
+ *
+ * This is called by lvaMarkLclRefsCallback() to do variable ref marking
+ */
+
+void Compiler::lvaMarkLclRefs(GenTreePtr tree)
+{
+ /* Is this a call to unmanaged code ? */
+ if (tree->gtOper == GT_CALL && tree->gtFlags & GTF_CALL_UNMANAGED)
+ {
+ assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
+ if (!opts.ShouldUsePInvokeHelpers())
+ {
+ /* Get the special variable descriptor */
+
+ unsigned lclNum = info.compLvFrameListRoot;
+
+ noway_assert(lclNum <= lvaCount);
+ LclVarDsc* varDsc = lvaTable + lclNum;
+
+ /* Increment the ref counts twice */
+ varDsc->incRefCnts(lvaMarkRefsWeight, this);
+ varDsc->incRefCnts(lvaMarkRefsWeight, this);
+ }
+ }
+
+ /* Is this an assigment? */
+
+ if (tree->OperKind() & GTK_ASGOP)
+ {
+ GenTreePtr op1 = tree->gtOp.gtOp1;
+ GenTreePtr op2 = tree->gtOp.gtOp2;
+
+ /* Set target register for RHS local if assignment is of a "small" type */
+
+ if (varTypeIsByte(tree->gtType))
+ {
+ unsigned lclNum;
+ LclVarDsc* varDsc = nullptr;
+
+ /* GT_CHS is special it doesn't have a valid op2 */
+ if (tree->gtOper == GT_CHS)
+ {
+ if (op1->gtOper == GT_LCL_VAR)
+ {
+ lclNum = op1->gtLclVarCommon.gtLclNum;
+ noway_assert(lclNum < lvaCount);
+ varDsc = &lvaTable[lclNum];
+ }
+ }
+ else
+ {
+ if (op2->gtOper == GT_LCL_VAR)
+ {
+ lclNum = op2->gtLclVarCommon.gtLclNum;
+ noway_assert(lclNum < lvaCount);
+ varDsc = &lvaTable[lclNum];
+ }
+ }
+#if CPU_HAS_BYTE_REGS
+ if (varDsc)
+ varDsc->addPrefReg(RBM_BYTE_REG_FLAG, this);
+#endif
+ }
+
+#if OPT_BOOL_OPS
+
+ /* Is this an assignment to a local variable? */
+
+ if (op1->gtOper == GT_LCL_VAR && op2->gtType != TYP_BOOL)
+ {
+ /* Only simple assignments allowed for booleans */
+
+ if (tree->gtOper != GT_ASG)
+ {
+ goto NOT_BOOL;
+ }
+
+ /* Is the RHS clearly a boolean value? */
+
+ switch (op2->gtOper)
+ {
+ unsigned lclNum;
+
+ case GT_CNS_INT:
+
+ if (op2->gtIntCon.gtIconVal == 0)
+ {
+ break;
+ }
+ if (op2->gtIntCon.gtIconVal == 1)
+ {
+ break;
+ }
+
+ // Not 0 or 1, fall through ....
+ __fallthrough;
+
+ default:
+
+ if (op2->OperIsCompare())
+ {
+ break;
+ }
+
+ NOT_BOOL:
+
+ lclNum = op1->gtLclVarCommon.gtLclNum;
+ noway_assert(lclNum < lvaCount);
+
+ lvaTable[lclNum].lvIsBoolean = false;
+ break;
+ }
+ }
+#endif
+ }
+
+#if FANCY_ARRAY_OPT
+
+ /* Special case: assignment node */
+
+ if (tree->gtOper == GT_ASG)
+ {
+ if (tree->gtType == TYP_INT)
+ {
+ unsigned lclNum1;
+ LclVarDsc* varDsc1;
+
+ GenTreePtr op1 = tree->gtOp.gtOp1;
+
+ if (op1->gtOper != GT_LCL_VAR)
+ return;
+
+ lclNum1 = op1->gtLclVarCommon.gtLclNum;
+ noway_assert(lclNum1 < lvaCount);
+ varDsc1 = lvaTable + lclNum1;
+
+ if (varDsc1->lvAssignOne)
+ varDsc1->lvAssignTwo = true;
+ else
+ varDsc1->lvAssignOne = true;
+ }
+
+ return;
+ }
+
+#endif
+
+#ifdef _TARGET_XARCH_
+ /* Special case: integer shift node by a variable amount */
+
+ if (tree->OperIsShiftOrRotate())
+ {
+ if (tree->gtType == TYP_INT)
+ {
+ GenTreePtr op2 = tree->gtOp.gtOp2;
+
+ if (op2->gtOper == GT_LCL_VAR)
+ {
+ unsigned lclNum = op2->gtLclVarCommon.gtLclNum;
+ noway_assert(lclNum < lvaCount);
+ lvaTable[lclNum].setPrefReg(REG_ECX, this);
+ }
+ }
+
+ return;
+ }
+#endif
+
+ if ((tree->gtOper != GT_LCL_VAR) && (tree->gtOper != GT_LCL_FLD))
+ {
+ return;
+ }
+
+ /* This must be a local variable reference */
+
+ noway_assert((tree->gtOper == GT_LCL_VAR) || (tree->gtOper == GT_LCL_FLD));
+ unsigned lclNum = tree->gtLclVarCommon.gtLclNum;
+
+ noway_assert(lclNum < lvaCount);
+ LclVarDsc* varDsc = lvaTable + lclNum;
+
+ /* Increment the reference counts */
+
+ varDsc->incRefCnts(lvaMarkRefsWeight, this);
+
+ if (lvaVarAddrExposed(lclNum))
+ {
+ varDsc->lvIsBoolean = false;
+ }
+
+ if (tree->gtOper == GT_LCL_FLD)
+ {
+#if ASSERTION_PROP
+ // variables that have uses inside a GT_LCL_FLD
+ // cause problems, so we will disqualify them here
+ varDsc->lvaDisqualifyVar();
+#endif // ASSERTION_PROP
+ return;
+ }
+
+#if ASSERTION_PROP
+ /* Exclude the normal entry block */
+ if (fgDomsComputed && (lvaMarkRefsCurBlock->bbNum != 1) && lvaMarkRefsCurBlock->bbIDom != nullptr)
+ {
+ // If any entry block except the normal entry block dominates the block, then mark the local with the
+ // lvVolatileHint flag.
+
+ if (BlockSetOps::MayBeUninit(lvaMarkRefsCurBlock->bbDoms))
+ {
+ // Lazy init (If a block is not dominated by any other block, we'll redo this every time, but it'll be fast)
+ BlockSetOps::AssignNoCopy(this, lvaMarkRefsCurBlock->bbDoms, fgGetDominatorSet(lvaMarkRefsCurBlock));
+ BlockSetOps::RemoveElemD(this, lvaMarkRefsCurBlock->bbDoms, fgFirstBB->bbNum);
+ }
+ assert(fgEnterBlksSetValid);
+ if (!BlockSetOps::IsEmptyIntersection(this, lvaMarkRefsCurBlock->bbDoms, fgEnterBlks))
+ {
+ varDsc->lvVolatileHint = 1;
+ }
+ }
+
+ /* Record if the variable has a single def or not */
+
+ if (!varDsc->lvDisqualify) // If this variable is already disqualified we can skip this
+ {
+ if (tree->gtFlags & GTF_VAR_DEF) // Is this is a def of our variable
+ {
+ /*
+ If we have one of these cases:
+ 1. We have already seen a definition (i.e lvSingleDef is true)
+ 2. or info.CompInitMem is true (thus this would be the second definition)
+ 3. or we have an assignment inside QMARK-COLON trees
+ 4. or we have an update form of assignment (i.e. +=, -=, *=)
+ Then we must disqualify this variable for use in optAddCopies()
+
+ Note that all parameters start out with lvSingleDef set to true
+ */
+ if ((varDsc->lvSingleDef == true) || (info.compInitMem == true) || (tree->gtFlags & GTF_COLON_COND) ||
+ (tree->gtFlags & GTF_VAR_USEASG))
+ {
+ varDsc->lvaDisqualifyVar();
+ }
+ else
+ {
+ varDsc->lvSingleDef = true;
+ varDsc->lvDefStmt = lvaMarkRefsCurStmt;
+ }
+ }
+ else // otherwise this is a ref of our variable
+ {
+ if (BlockSetOps::MayBeUninit(varDsc->lvRefBlks))
+ {
+ // Lazy initialization
+ BlockSetOps::AssignNoCopy(this, varDsc->lvRefBlks, BlockSetOps::MakeEmpty(this));
+ }
+ BlockSetOps::AddElemD(this, varDsc->lvRefBlks, lvaMarkRefsCurBlock->bbNum);
+ }
+ }
+#endif // ASSERTION_PROP
+
+ bool allowStructs = false;
+#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+ // On System V the type of the var could be a struct type.
+ allowStructs = varTypeIsStruct(varDsc);
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+
+ /* Variables must be used as the same type throughout the method */
+ noway_assert(tiVerificationNeeded || varDsc->lvType == TYP_UNDEF || tree->gtType == TYP_UNKNOWN || allowStructs ||
+ genActualType(varDsc->TypeGet()) == genActualType(tree->gtType) ||
+ (tree->gtType == TYP_BYREF && varDsc->TypeGet() == TYP_I_IMPL) ||
+ (tree->gtType == TYP_I_IMPL && varDsc->TypeGet() == TYP_BYREF) || (tree->gtFlags & GTF_VAR_CAST) ||
+ varTypeIsFloating(varDsc->TypeGet()) && varTypeIsFloating(tree->gtType));
+
+ /* Remember the type of the reference */
+
+ if (tree->gtType == TYP_UNKNOWN || varDsc->lvType == TYP_UNDEF)
+ {
+ varDsc->lvType = tree->gtType;
+ noway_assert(genActualType(varDsc->TypeGet()) == tree->gtType); // no truncation
+ }
+
+#ifdef DEBUG
+ if (tree->gtFlags & GTF_VAR_CAST)
+ {
+ // it should never be bigger than the variable slot
+
+ // Trees don't store the full information about structs
+ // so we can't check them.
+ if (tree->TypeGet() != TYP_STRUCT)
+ {
+ unsigned treeSize = genTypeSize(tree->TypeGet());
+ unsigned varSize = genTypeSize(varDsc->TypeGet());
+ if (varDsc->TypeGet() == TYP_STRUCT)
+ {
+ varSize = varDsc->lvSize();
+ }
+
+ assert(treeSize <= varSize);
+ }
+ }
+#endif
+}
+
+/*****************************************************************************
+ *
+ * Helper passed to Compiler::fgWalkTreePre() to do variable ref marking.
+ */
+
+/* static */
+Compiler::fgWalkResult Compiler::lvaMarkLclRefsCallback(GenTreePtr* pTree, fgWalkData* data)
+{
+ data->compiler->lvaMarkLclRefs(*pTree);
+
+ return WALK_CONTINUE;
+}
+
+/*****************************************************************************
+ *
+ * Update the local variable reference counts for one basic block
+ */
+
+void Compiler::lvaMarkLocalVars(BasicBlock* block)
+{
+#if ASSERTION_PROP
+ lvaMarkRefsCurBlock = block;
+#endif
+ lvaMarkRefsWeight = block->getBBWeight(this);
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*** marking local variables in block BB%02u (weight=%s)\n", block->bbNum,
+ refCntWtd2str(lvaMarkRefsWeight));
+ }
+#endif
+
+ for (GenTreePtr tree = block->FirstNonPhiDef(); tree; tree = tree->gtNext)
+ {
+ noway_assert(tree->gtOper == GT_STMT);
+
+#if ASSERTION_PROP
+ lvaMarkRefsCurStmt = tree;
+#endif
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ gtDispTree(tree);
+ }
+#endif
+
+ fgWalkTreePre(&tree->gtStmt.gtStmtExpr, Compiler::lvaMarkLclRefsCallback, (void*)this, false);
+ }
+}
+
+/*****************************************************************************
+ *
+ * Create the local variable table and compute local variable reference
+ * counts.
+ */
+
+void Compiler::lvaMarkLocalVars()
+{
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\n*************** In lvaMarkLocalVars()");
+ }
+#endif
+
+ /* If there is a call to an unmanaged target, we already grabbed a
+ local slot for the current thread control block.
+ */
+
+ if (info.compCallUnmanaged != 0)
+ {
+ assert((!opts.ShouldUsePInvokeHelpers()) || (info.compLvFrameListRoot == BAD_VAR_NUM));
+ if (!opts.ShouldUsePInvokeHelpers())
+ {
+ noway_assert(info.compLvFrameListRoot >= info.compLocalsCount && info.compLvFrameListRoot < lvaCount);
+
+ lvaTable[info.compLvFrameListRoot].lvType = TYP_I_IMPL;
+
+ /* Set the refCnt, it is used in the prolog and return block(s) */
+
+ lvaTable[info.compLvFrameListRoot].lvRefCnt = 2;
+ lvaTable[info.compLvFrameListRoot].lvRefCntWtd = 2 * BB_UNITY_WEIGHT;
+ }
+ }
+
+ lvaAllocOutgoingArgSpace();
+
+#if !FEATURE_EH_FUNCLETS
+
+ // Grab space for exception handling
+
+ if (ehNeedsShadowSPslots())
+ {
+ // The first slot is reserved for ICodeManager::FixContext(ppEndRegion)
+ // ie. the offset of the end-of-last-executed-filter
+ unsigned slotsNeeded = 1;
+
+ unsigned handlerNestingLevel = ehMaxHndNestingCount;
+
+ if (opts.compDbgEnC && (handlerNestingLevel < (unsigned)MAX_EnC_HANDLER_NESTING_LEVEL))
+ handlerNestingLevel = (unsigned)MAX_EnC_HANDLER_NESTING_LEVEL;
+
+ slotsNeeded += handlerNestingLevel;
+
+ // For a filter (which can be active at the same time as a catch/finally handler)
+ slotsNeeded++;
+ // For zero-termination of the shadow-Stack-pointer chain
+ slotsNeeded++;
+
+ lvaShadowSPslotsVar = lvaGrabTempWithImplicitUse(false DEBUGARG("lvaShadowSPslotsVar"));
+ LclVarDsc* shadowSPslotsVar = &lvaTable[lvaShadowSPslotsVar];
+ shadowSPslotsVar->lvType = TYP_BLK;
+ shadowSPslotsVar->lvExactSize = (slotsNeeded * TARGET_POINTER_SIZE);
+ }
+
+#endif // !FEATURE_EH_FUNCLETS
+
+#if FEATURE_EH_FUNCLETS
+ if (ehNeedsPSPSym())
+ {
+ lvaPSPSym = lvaGrabTempWithImplicitUse(false DEBUGARG("PSPSym"));
+ LclVarDsc* lclPSPSym = &lvaTable[lvaPSPSym];
+ lclPSPSym->lvType = TYP_I_IMPL;
+ }
+#endif // FEATURE_EH_FUNCLETS
+
+ if (compLocallocUsed)
+ {
+ lvaLocAllocSPvar = lvaGrabTempWithImplicitUse(false DEBUGARG("LocAllocSPvar"));
+ LclVarDsc* locAllocSPvar = &lvaTable[lvaLocAllocSPvar];
+ locAllocSPvar->lvType = TYP_I_IMPL;
+ }
+
+ BasicBlock* block;
+
+#if defined(DEBUGGING_SUPPORT) || defined(DEBUG)
+
+#ifndef DEBUG
+ // Assign slot numbers to all variables.
+ // If compiler generated local variables, slot numbers will be
+ // invalid (out of range of info.compVarScopes).
+
+ // Also have to check if variable was not reallocated to another
+ // slot in which case we have to register the original slot #.
+
+ // We don't need to do this for IL, but this keeps lvSlotNum consistent.
+
+ if (opts.compScopeInfo && (info.compVarScopesCount > 0))
+#endif
+ {
+ unsigned lclNum;
+ LclVarDsc* varDsc;
+
+ for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ varDsc->lvSlotNum = lclNum;
+ }
+ }
+
+#endif // defined(DEBUGGING_SUPPORT) || defined(DEBUG)
+
+ /* Mark all local variable references */
+
+ lvaRefCountingStarted = true;
+ for (block = fgFirstBB; block; block = block->bbNext)
+ {
+ lvaMarkLocalVars(block);
+ }
+
+ /* For incoming register arguments, if there are references in the body
+ * then we will have to copy them to the final home in the prolog
+ * This counts as an extra reference with a weight of 2
+ */
+
+ unsigned lclNum;
+ LclVarDsc* varDsc;
+
+ for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ if (lclNum >= info.compArgsCount)
+ {
+ break; // early exit for loop
+ }
+
+ if ((varDsc->lvIsRegArg) && (varDsc->lvRefCnt > 0))
+ {
+ // Fix 388376 ARM JitStress WP7
+ varDsc->incRefCnts(BB_UNITY_WEIGHT, this);
+ varDsc->incRefCnts(BB_UNITY_WEIGHT, this);
+ }
+ }
+
+#if ASSERTION_PROP
+ if (!opts.MinOpts() && !opts.compDbgCode)
+ {
+ // Note: optAddCopies() depends on lvaRefBlks, which is set in lvaMarkLocalVars(BasicBlock*), called above.
+ optAddCopies();
+ }
+#endif
+
+ if (lvaKeepAliveAndReportThis() && lvaTable[0].lvRefCnt == 0)
+ {
+ lvaTable[0].lvRefCnt = 1;
+ // This isn't strictly needed as we will make a copy of the param-type-arg
+ // in the prolog. However, this ensures that the LclVarDsc corresponding to
+ // info.compTypeCtxtArg is valid.
+ }
+ else if (lvaReportParamTypeArg() && lvaTable[info.compTypeCtxtArg].lvRefCnt == 0)
+ {
+ lvaTable[info.compTypeCtxtArg].lvRefCnt = 1;
+ }
+
+ lvaLocalVarRefCounted = true;
+ lvaRefCountingStarted = false;
+
+ lvaSortByRefCount();
+}
+
+void Compiler::lvaAllocOutgoingArgSpace()
+{
+#if FEATURE_FIXED_OUT_ARGS
+
+ // Setup the outgoing argument region, in case we end up using it later
+
+ if (lvaOutgoingArgSpaceVar == BAD_VAR_NUM)
+ {
+ lvaOutgoingArgSpaceVar = lvaGrabTemp(false DEBUGARG("OutgoingArgSpace"));
+
+ lvaTable[lvaOutgoingArgSpaceVar].lvType = TYP_LCLBLK;
+
+ /* Set the refCnts */
+
+ lvaTable[lvaOutgoingArgSpaceVar].lvRefCnt = 1;
+ lvaTable[lvaOutgoingArgSpaceVar].lvRefCntWtd = BB_UNITY_WEIGHT;
+
+ if (lvaOutgoingArgSpaceSize == 0)
+ {
+ if (compUsesThrowHelper || compIsProfilerHookNeeded())
+ {
+ // Need to make sure the MIN_ARG_AREA_FOR_CALL space is added to the frame if:
+ // 1. there are calls to THROW_HEPLPER methods.
+ // 2. we are generating profiling Enter/Leave/TailCall hooks. This will ensure
+ // that even methods without any calls will have outgoing arg area space allocated.
+ //
+ // An example for these two cases is Windows Amd64, where the ABI requires to have 4 slots for
+ // the outgoing arg space if the method makes any calls.
+ lvaOutgoingArgSpaceSize = MIN_ARG_AREA_FOR_CALL;
+ }
+ }
+ }
+
+ noway_assert(lvaOutgoingArgSpaceVar >= info.compLocalsCount && lvaOutgoingArgSpaceVar < lvaCount);
+
+#endif // FEATURE_FIXED_OUT_ARGS
+}
+
+inline void Compiler::lvaIncrementFrameSize(unsigned size)
+{
+ if (size > MAX_FrameSize || compLclFrameSize + size > MAX_FrameSize)
+ {
+ BADCODE("Frame size overflow");
+ }
+
+ compLclFrameSize += size;
+}
+
+/****************************************************************************
+*
+* Return true if absolute offsets of temps are larger than vars, or in other
+* words, did we allocate temps before of after vars. The /GS buffer overrun
+* checks want temps to be at low stack addresses than buffers
+*/
+bool Compiler::lvaTempsHaveLargerOffsetThanVars()
+{
+#ifdef _TARGET_ARM_
+ // We never want to place the temps with larger offsets for ARM
+ return false;
+#else
+ if (compGSReorderStackLayout)
+ {
+ return codeGen->isFramePointerUsed();
+ }
+ else
+ {
+ return true;
+ }
+#endif
+}
+
+/****************************************************************************
+*
+* Return an upper bound estimate for the size of the compiler spill temps
+*
+*/
+unsigned Compiler::lvaGetMaxSpillTempSize()
+{
+ unsigned result = 0;
+
+#ifndef LEGACY_BACKEND
+ if (lvaDoneFrameLayout >= REGALLOC_FRAME_LAYOUT)
+ {
+ result = tmpSize;
+ }
+ else
+ {
+ result = MAX_SPILL_TEMP_SIZE;
+ }
+#else // LEGACY_BACKEND
+ if (lvaDoneFrameLayout >= FINAL_FRAME_LAYOUT)
+ {
+ result = tmpSize;
+ }
+ else
+ {
+ if (lvaDoneFrameLayout >= REGALLOC_FRAME_LAYOUT)
+ {
+ unsigned maxTmpSize = sizeof(double) + sizeof(int);
+
+ maxTmpSize += (tmpDoubleSpillMax * sizeof(double)) + (tmpIntSpillMax * sizeof(int));
+
+ result = maxTmpSize;
+ }
+ else
+ {
+ result = MAX_SPILL_TEMP_SIZE;
+ }
+#ifdef DEBUG
+ // When StressRegs is >=1, there can be a bunch of spills that are not
+ // predicted by the predictor (see logic in rsPickReg). It is very hard
+ // to teach the predictor about the behavior of rsPickReg for StressRegs >= 1,
+ // so instead let's make MaxTmpSize large enough so that we won't be wrong.
+
+ if (codeGen->regSet.rsStressRegs() >= 1)
+ {
+ result += (REG_TMP_ORDER_COUNT * REGSIZE_BYTES);
+ }
+#endif // DEBUG
+ }
+#endif // LEGACY_BACKEND
+ return result;
+}
+
+// clang-format off
+/*****************************************************************************
+ *
+ * Compute stack frame offsets for arguments, locals and optionally temps.
+ *
+ * The frame is laid out as follows for x86:
+ *
+ * ESP frames
+ *
+ * | |
+ * |-----------------------|
+ * | incoming |
+ * | arguments |
+ * |-----------------------| <---- Virtual '0'
+ * | return address |
+ * +=======================+
+ * |Callee saved registers |
+ * |-----------------------|
+ * | Temps |
+ * |-----------------------|
+ * | Variables |
+ * |-----------------------| <---- Ambient ESP
+ * | Arguments for the |
+ * ~ next function ~
+ * | |
+ * | | |
+ * | | Stack grows |
+ * | downward
+ * V
+ *
+ *
+ * EBP frames
+ *
+ * | |
+ * |-----------------------|
+ * | incoming |
+ * | arguments |
+ * |-----------------------| <---- Virtual '0'
+ * | return address |
+ * +=======================+
+ * | incoming EBP |
+ * |-----------------------| <---- EBP
+ * |Callee saved registers |
+ * |-----------------------|
+ * | security object |
+ * |-----------------------|
+ * | ParamTypeArg |
+ * |-----------------------|
+ * | Last-executed-filter |
+ * |-----------------------|
+ * | |
+ * ~ Shadow SPs ~
+ * | |
+ * |-----------------------|
+ * | |
+ * ~ Variables ~
+ * | |
+ * ~-----------------------|
+ * | Temps |
+ * |-----------------------|
+ * | localloc |
+ * |-----------------------| <---- Ambient ESP
+ * | Arguments for the |
+ * | next function ~
+ * | |
+ * | | |
+ * | | Stack grows |
+ * | downward
+ * V
+ *
+ *
+ * The frame is laid out as follows for x64:
+ *
+ * RSP frames
+ * | |
+ * |-----------------------|
+ * | incoming |
+ * | arguments |
+ * |-----------------------|
+ * | 4 fixed incoming |
+ * | argument slots |
+ * |-----------------------| <---- Caller's SP & Virtual '0'
+ * | return address |
+ * +=======================+
+ * | Callee saved Int regs |
+ * -------------------------
+ * | Padding | <---- this padding (0 or 8 bytes) is to ensure flt registers are saved at a mem location aligned at 16-bytes
+ * | | so that we can save 128-bit callee saved xmm regs using performant "movaps" instruction instead of "movups"
+ * -------------------------
+ * | Callee saved Flt regs | <----- entire 128-bits of callee saved xmm registers are stored here
+ * |-----------------------|
+ * | Temps |
+ * |-----------------------|
+ * | Variables |
+ * |-----------------------|
+ * | Arguments for the |
+ * ~ next function ~
+ * | |
+ * |-----------------------|
+ * | 4 fixed outgoing |
+ * | argument slots |
+ * |-----------------------| <---- Ambient RSP
+ * | | |
+ * ~ | Stack grows ~
+ * | | downward |
+ * V
+ *
+ *
+ * RBP frames
+ * | |
+ * |-----------------------|
+ * | incoming |
+ * | arguments |
+ * |-----------------------|
+ * | 4 fixed incoming |
+ * | argument slots |
+ * |-----------------------| <---- Caller's SP & Virtual '0'
+ * | return address |
+ * +=======================+
+ * | Callee saved Int regs |
+ * -------------------------
+ * | Padding |
+ * -------------------------
+ * | Callee saved Flt regs |
+ * |-----------------------|
+ * | security object |
+ * |-----------------------|
+ * | ParamTypeArg |
+ * |-----------------------|
+ * | |
+ * | |
+ * ~ Variables ~
+ * | |
+ * | |
+ * |-----------------------|
+ * | Temps |
+ * |-----------------------|
+ * | |
+ * ~ localloc ~ // not in frames with EH
+ * | |
+ * |-----------------------|
+ * | PSPSym | // only in frames with EH (thus no localloc)
+ * | |
+ * |-----------------------| <---- RBP in localloc frames (max 240 bytes from Initial-SP)
+ * | Arguments for the |
+ * ~ next function ~
+ * | |
+ * |-----------------------|
+ * | 4 fixed outgoing |
+ * | argument slots |
+ * |-----------------------| <---- Ambient RSP (before localloc, this is Initial-SP)
+ * | | |
+ * ~ | Stack grows ~
+ * | | downward |
+ * V
+ *
+ *
+ * The frame is laid out as follows for ARM (this is a general picture; details may differ for different conditions):
+ *
+ * SP frames
+ * | |
+ * |-----------------------|
+ * | incoming |
+ * | arguments |
+ * +=======================+ <---- Caller's SP
+ * | Pre-spill registers |
+ * |-----------------------| <---- Virtual '0'
+ * |Callee saved registers |
+ * |-----------------------|
+ * ~ possible double align ~
+ * |-----------------------|
+ * | security object |
+ * |-----------------------|
+ * | ParamTypeArg |
+ * |-----------------------|
+ * | possible GS cookie |
+ * |-----------------------|
+ * | Variables |
+ * |-----------------------|
+ * | possible GS cookie |
+ * |-----------------------|
+ * | Temps |
+ * |-----------------------|
+ * | Stub Argument Var |
+ * |-----------------------|
+ * |Inlined PInvoke Frame V|
+ * |-----------------------|
+ * ~ possible double align ~
+ * |-----------------------|
+ * | Arguments for the |
+ * ~ next function ~
+ * | |
+ * |-----------------------| <---- Ambient SP
+ * | | |
+ * ~ | Stack grows ~
+ * | | downward |
+ * V
+ *
+ *
+ * FP / R11 frames
+ * | |
+ * |-----------------------|
+ * | incoming |
+ * | arguments |
+ * +=======================+ <---- Caller's SP
+ * | Pre-spill registers |
+ * |-----------------------| <---- Virtual '0'
+ * |Callee saved registers |
+ * |-----------------------|
+ * | PSPSym | // Only for frames with EH, which means FP-based frames
+ * |-----------------------|
+ * ~ possible double align ~
+ * |-----------------------|
+ * | security object |
+ * |-----------------------|
+ * | ParamTypeArg |
+ * |-----------------------|
+ * | possible GS cookie |
+ * |-----------------------|
+ * | Variables |
+ * |-----------------------|
+ * | possible GS cookie |
+ * |-----------------------|
+ * | Temps |
+ * |-----------------------|
+ * | Stub Argument Var |
+ * |-----------------------|
+ * |Inlined PInvoke Frame V|
+ * |-----------------------|
+ * ~ possible double align ~
+ * |-----------------------|
+ * | localloc |
+ * |-----------------------|
+ * | Arguments for the |
+ * ~ next function ~
+ * | |
+ * |-----------------------| <---- Ambient SP
+ * | | |
+ * ~ | Stack grows ~
+ * | | downward |
+ * V
+ *
+ *
+ * The frame is laid out as follows for ARM64 (this is a general picture; details may differ for different conditions):
+ * TODO-ARM64-NYI: this is preliminary (copied from ARM and modified), and needs to be reviewed.
+ * NOTE: SP must be 16-byte aligned, so there may be alignment slots in the frame.
+ * We will often save and establish a frame pointer to create better ETW stack walks.
+ *
+ * SP frames
+ * | |
+ * |-----------------------|
+ * | incoming |
+ * | arguments |
+ * +=======================+ <---- Caller's SP
+ * | homed | // this is only needed if reg argument need to be homed, e.g., for varargs
+ * | register arguments |
+ * |-----------------------| <---- Virtual '0'
+ * |Callee saved registers |
+ * | except fp/lr |
+ * |-----------------------|
+ * | security object |
+ * |-----------------------|
+ * | ParamTypeArg |
+ * |-----------------------|
+ * | possible GS cookie |
+ * |-----------------------|
+ * | Variables |
+ * |-----------------------|
+ * | possible GS cookie |
+ * |-----------------------|
+ * | Temps |
+ * |-----------------------|
+ * | Stub Argument Var |
+ * |-----------------------|
+ * |Inlined PInvoke Frame V|
+ * |-----------------------|
+ * | Saved LR |
+ * |-----------------------|
+ * | Saved FP | <---- Frame pointer
+ * |-----------------------|
+ * | Stack arguments for |
+ * | the next function |
+ * |-----------------------| <---- SP
+ * | | |
+ * ~ | Stack grows ~
+ * | | downward |
+ * V
+ *
+ *
+ * FP (R29 / x29) frames
+ * | |
+ * |-----------------------|
+ * | incoming |
+ * | arguments |
+ * +=======================+ <---- Caller's SP
+ * | optional homed | // this is only needed if reg argument need to be homed, e.g., for varargs
+ * | register arguments |
+ * |-----------------------| <---- Virtual '0'
+ * |Callee saved registers |
+ * | except fp/lr |
+ * |-----------------------|
+ * | PSPSym | // Only for frames with EH, which requires FP-based frames
+ * |-----------------------|
+ * | security object |
+ * |-----------------------|
+ * | ParamTypeArg |
+ * |-----------------------|
+ * | possible GS cookie |
+ * |-----------------------|
+ * | Variables |
+ * |-----------------------|
+ * | possible GS cookie |
+ * |-----------------------|
+ * | Temps |
+ * |-----------------------|
+ * | Stub Argument Var |
+ * |-----------------------|
+ * |Inlined PInvoke Frame V|
+ * |-----------------------|
+ * | Saved LR |
+ * |-----------------------|
+ * | Saved FP | <---- Frame pointer
+ * |-----------------------|
+ * ~ localloc ~
+ * |-----------------------|
+ * | Stack arguments for |
+ * | the next function |
+ * |-----------------------| <---- Ambient SP
+ * | | |
+ * ~ | Stack grows ~
+ * | | downward |
+ * V
+ *
+ *
+ * Doing this all in one pass is 'hard'. So instead we do it in 2 basic passes:
+ * 1. Assign all the offsets relative to the Virtual '0'. Offsets above (the
+ * incoming arguments) are positive. Offsets below (everything else) are
+ * negative. This pass also calcuates the total frame size (between Caller's
+ * SP/return address and the Ambient SP).
+ * 2. Figure out where to place the frame pointer, and then adjust the offsets
+ * as needed for the final stack size and whether the offset is frame pointer
+ * relative or stack pointer relative.
+ *
+ */
+// clang-format on
+
+void Compiler::lvaAssignFrameOffsets(FrameLayoutState curState)
+{
+ noway_assert(lvaDoneFrameLayout < curState);
+
+ lvaDoneFrameLayout = curState;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+
+ printf("*************** In lvaAssignFrameOffsets");
+ if (curState == INITIAL_FRAME_LAYOUT)
+ {
+ printf("(INITIAL_FRAME_LAYOUT)");
+ }
+ else if (curState == PRE_REGALLOC_FRAME_LAYOUT)
+ {
+ printf("(PRE_REGALLOC_FRAME_LAYOUT)");
+ }
+ else if (curState == REGALLOC_FRAME_LAYOUT)
+ {
+ printf("(REGALLOC_FRAME_LAYOUT)");
+ }
+ else if (curState == TENTATIVE_FRAME_LAYOUT)
+ {
+ printf("(TENTATIVE_FRAME_LAYOUT)");
+ }
+ else if (curState == FINAL_FRAME_LAYOUT)
+ {
+ printf("(FINAL_FRAME_LAYOUT)");
+ }
+ else
+ {
+ printf("(UNKNOWN)");
+ unreached();
+ }
+ printf("\n");
+ }
+#endif
+
+#if FEATURE_FIXED_OUT_ARGS
+ assert(lvaOutgoingArgSpaceVar != BAD_VAR_NUM);
+#endif // FEATURE_FIXED_OUT_ARGS
+
+ /*-------------------------------------------------------------------------
+ *
+ * First process the arguments.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ lvaAssignVirtualFrameOffsetsToArgs();
+
+ /*-------------------------------------------------------------------------
+ *
+ * Now compute stack offsets for any variables that don't live in registers
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ lvaAssignVirtualFrameOffsetsToLocals();
+
+ lvaAlignFrame();
+
+ /*-------------------------------------------------------------------------
+ *
+ * Now patch the offsets
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ lvaFixVirtualFrameOffsets();
+
+ // Modify the stack offset for fields of promoted structs.
+ lvaAssignFrameOffsetsToPromotedStructs();
+
+ /*-------------------------------------------------------------------------
+ *
+ * Finalize
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ // If it's not the final frame layout, then it's just an estimate. This means
+ // we're allowed to once again write to these variables, even if we've read
+ // from them to make tentative code generation or frame layout decisions.
+ if (curState < FINAL_FRAME_LAYOUT)
+ {
+ codeGen->resetFramePointerUsedWritePhase();
+ }
+}
+
+/*****************************************************************************
+ * lvaFixVirtualFrameOffsets() : Now that everything has a virtual offset,
+ * determine the final value for the frame pointer (if needed) and then
+ * adjust all the offsets appropriately.
+ *
+ * This routine fixes virtual offset to be relative to frame pointer or SP
+ * based on whether varDsc->lvFramePointerBased is true or false respectively.
+ */
+void Compiler::lvaFixVirtualFrameOffsets()
+{
+ LclVarDsc* varDsc;
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_AMD64_)
+ if (ehNeedsPSPSym())
+ {
+ // We need to fix the offset of the PSPSym so there is no padding between it and the outgoing argument space.
+ // Without this code, lvaAlignFrame might have put the padding lower than the PSPSym, which would be between
+ // the PSPSym and the outgoing argument space.
+ assert(lvaPSPSym != BAD_VAR_NUM);
+ varDsc = &lvaTable[lvaPSPSym];
+ assert(varDsc->lvFramePointerBased); // We always access it RBP-relative.
+ assert(!varDsc->lvMustInit); // It is never "must init".
+ varDsc->lvStkOffs = codeGen->genCallerSPtoInitialSPdelta() + lvaLclSize(lvaOutgoingArgSpaceVar);
+ }
+#endif
+
+ // The delta to be added to virtual offset to adjust it relative to frame pointer or SP
+ int delta = 0;
+
+#ifdef _TARGET_XARCH_
+ delta += REGSIZE_BYTES; // pushed PC (return address) for x86/x64
+
+ if (codeGen->doubleAlignOrFramePointerUsed())
+ {
+ delta += REGSIZE_BYTES; // pushed EBP (frame pointer)
+ }
+#endif
+
+ if (!codeGen->isFramePointerUsed())
+ {
+ // pushed registers, return address, and padding
+ delta += codeGen->genTotalFrameSize();
+ }
+#if defined(_TARGET_ARM_)
+ else
+ {
+ // We set FP to be after LR, FP
+ delta += 2 * REGSIZE_BYTES;
+ }
+#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
+ else
+ {
+ // FP is used.
+ delta += codeGen->genTotalFrameSize() - codeGen->genSPtoFPdelta();
+ }
+#endif //_TARGET_AMD64_
+
+ unsigned lclNum;
+ for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ bool doAssignStkOffs = true;
+
+ // Can't be relative to EBP unless we have an EBP
+ noway_assert(!varDsc->lvFramePointerBased || codeGen->doubleAlignOrFramePointerUsed());
+
+ // Is this a non-param promoted struct field?
+ // if so then set doAssignStkOffs to false.
+ //
+ if (varDsc->lvIsStructField && !varDsc->lvIsParam)
+ {
+ LclVarDsc* parentvarDsc = &lvaTable[varDsc->lvParentLcl];
+ lvaPromotionType promotionType = lvaGetPromotionType(parentvarDsc);
+
+ if (promotionType == PROMOTION_TYPE_DEPENDENT)
+ {
+ doAssignStkOffs = false; // Assigned later in lvaAssignFrameOffsetsToPromotedStructs()
+ }
+ }
+
+ if (!varDsc->lvOnFrame)
+ {
+ if (!varDsc->lvIsParam
+#if !defined(_TARGET_AMD64_)
+ || (varDsc->lvIsRegArg
+#if defined(_TARGET_ARM_) && defined(PROFILING_SUPPORTED)
+ && compIsProfilerHookNeeded() &&
+ !lvaIsPreSpilled(lclNum, codeGen->regSet.rsMaskPreSpillRegs(false)) // We need assign stack offsets
+ // for prespilled arguments
+#endif
+ )
+#endif // !defined(_TARGET_AMD64_)
+ )
+ {
+ doAssignStkOffs = false; // Not on frame or an incomming stack arg
+ }
+ }
+
+ if (doAssignStkOffs)
+ {
+ varDsc->lvStkOffs += delta;
+
+#if DOUBLE_ALIGN
+ if (genDoubleAlign() && !codeGen->isFramePointerUsed())
+ {
+ if (varDsc->lvFramePointerBased)
+ {
+ varDsc->lvStkOffs -= delta;
+
+ // We need to re-adjust the offsets of the parameters so they are EBP
+ // relative rather than stack/frame pointer relative
+
+ varDsc->lvStkOffs += (2 * sizeof(void*)); // return address and pushed EBP
+
+ noway_assert(varDsc->lvStkOffs >= FIRST_ARG_STACK_OFFS);
+ }
+ }
+#endif
+ // On System V environments the stkOffs could be 0 for params passed in registers.
+ assert(codeGen->isFramePointerUsed() ||
+ varDsc->lvStkOffs >= 0); // Only EBP relative references can have negative offsets
+ }
+ }
+
+ assert(tmpAllFree());
+ for (TempDsc* temp = tmpListBeg(); temp != nullptr; temp = tmpListNxt(temp))
+ {
+ temp->tdAdjustTempOffs(delta);
+ }
+
+ lvaCachedGenericContextArgOffs += delta;
+
+#if FEATURE_FIXED_OUT_ARGS
+
+ if (lvaOutgoingArgSpaceVar != BAD_VAR_NUM)
+ {
+ varDsc = &lvaTable[lvaOutgoingArgSpaceVar];
+ varDsc->lvStkOffs = 0;
+ varDsc->lvFramePointerBased = false;
+ varDsc->lvMustInit = false;
+ }
+
+#endif // FEATURE_FIXED_OUT_ARGS
+}
+
+#ifdef _TARGET_ARM_
+bool Compiler::lvaIsPreSpilled(unsigned lclNum, regMaskTP preSpillMask)
+{
+ const LclVarDsc& desc = lvaTable[lclNum];
+ return desc.lvIsRegArg && (preSpillMask & genRegMask(desc.lvArgReg));
+}
+#endif // _TARGET_ARM_
+
+#ifndef LEGACY_BACKEND
+/*****************************************************************************
+ * lvaUpdateArgsWithInitialReg() : For each argument variable descriptor, update
+ * its current register with the initial register as assigned by LSRA.
+ */
+void Compiler::lvaUpdateArgsWithInitialReg()
+{
+ if (!compLSRADone)
+ {
+ return;
+ }
+
+ for (unsigned lclNum = 0; lclNum < info.compArgsCount; lclNum++)
+ {
+ LclVarDsc* varDsc = lvaTable + lclNum;
+
+ if (varDsc->lvPromotedStruct())
+ {
+ noway_assert(varDsc->lvFieldCnt == 1); // We only handle one field here
+
+ unsigned fieldVarNum = varDsc->lvFieldLclStart;
+ varDsc = lvaTable + fieldVarNum;
+ }
+
+ noway_assert(varDsc->lvIsParam);
+
+ if (varDsc->lvIsRegCandidate())
+ {
+ if (varTypeIsMultiReg(varDsc))
+ {
+ regPairNo initialRegPair = varDsc->lvArgInitRegPair;
+ varDsc->lvRegNum = genRegPairLo(initialRegPair);
+ varDsc->lvOtherReg = genRegPairHi(initialRegPair);
+ }
+ else
+ {
+ varDsc->lvRegNum = varDsc->lvArgInitReg;
+ }
+ }
+ }
+}
+#endif // !LEGACY_BACKEND
+
+/*****************************************************************************
+ * lvaAssignVirtualFrameOffsetsToArgs() : Assign virtual stack offsets to the
+ * arguments, and implicit arguments (this ptr, return buffer, generics,
+ * and varargs).
+ */
+void Compiler::lvaAssignVirtualFrameOffsetsToArgs()
+{
+ unsigned lclNum = 0;
+ int argOffs = 0;
+#ifdef UNIX_AMD64_ABI
+ int callerArgOffset = 0;
+#endif // UNIX_AMD64_ABI
+
+ /*
+ Assign stack offsets to arguments (in reverse order of passing).
+
+ This means that if we pass arguments left->right, we start at
+ the end of the list and work backwards, for right->left we start
+ with the first argument and move forward.
+
+ This is all relative to our Virtual '0'
+ */
+
+ if (Target::g_tgtArgOrder == Target::ARG_ORDER_L2R)
+ {
+ argOffs = compArgSize;
+ }
+
+ /* Update the argOffs to reflect arguments that are passed in registers */
+
+ noway_assert(codeGen->intRegState.rsCalleeRegArgCount <= MAX_REG_ARG);
+ noway_assert(compArgSize >= codeGen->intRegState.rsCalleeRegArgCount * sizeof(void*));
+
+#ifdef _TARGET_X86_
+ argOffs -= codeGen->intRegState.rsCalleeRegArgCount * sizeof(void*);
+#endif
+
+#ifndef LEGACY_BACKEND
+ // Update the arg initial register locations.
+ lvaUpdateArgsWithInitialReg();
+#endif // !LEGACY_BACKEND
+
+ /* Is there a "this" argument? */
+
+ if (!info.compIsStatic)
+ {
+ noway_assert(lclNum == info.compThisArg);
+#ifndef _TARGET_X86_
+ argOffs =
+ lvaAssignVirtualFrameOffsetToArg(lclNum, REGSIZE_BYTES, argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset));
+#endif // _TARGET_X86_
+ lclNum++;
+ }
+
+ /* if we have a hidden buffer parameter, that comes here */
+
+ if (info.compRetBuffArg != BAD_VAR_NUM)
+ {
+ noway_assert(lclNum == info.compRetBuffArg);
+ noway_assert(lvaTable[lclNum].lvIsRegArg);
+#ifndef _TARGET_X86_
+ argOffs =
+ lvaAssignVirtualFrameOffsetToArg(lclNum, REGSIZE_BYTES, argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset));
+#endif // _TARGET_X86_
+ lclNum++;
+ }
+
+#if USER_ARGS_COME_LAST
+
+ //@GENERICS: extra argument for instantiation info
+ if (info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE)
+ {
+ noway_assert(lclNum == (unsigned)info.compTypeCtxtArg);
+ argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, sizeof(void*),
+ argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset));
+ }
+
+ if (info.compIsVarArgs)
+ {
+ argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, sizeof(void*),
+ argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset));
+ }
+
+#endif // USER_ARGS_COME_LAST
+
+ CORINFO_ARG_LIST_HANDLE argLst = info.compMethodInfo->args.args;
+ unsigned argSigLen = info.compMethodInfo->args.numArgs;
+
+#ifdef _TARGET_ARM_
+ //
+ // struct_n { int; int; ... n times };
+ //
+ // Consider signature:
+ //
+ // Foo (float a,double b,float c,double d,float e,double f,float g,double h,
+ // float i,double j,float k,double l,struct_3 m) { }
+ //
+ // Basically the signature is: (all float regs full, 1 double, struct_3);
+ //
+ // The double argument occurs before pre spill in the argument iteration and
+ // computes an argOffset of 0. struct_3 offset becomes 8. This is wrong.
+ // Because struct_3 is prespilled and double occurs after prespill.
+ // The correct offsets are double = 16 (aligned stk), struct_3 = 0..12,
+ // Offset 12 will be skipped for double alignment of double.
+ //
+ // Another example is (struct_2, all float regs full, double, struct_2);
+ // Here, notice the order is similarly messed up because of 2 pre-spilled
+ // struct_2.
+ //
+ // Succinctly,
+ // ARG_INDEX(i) > ARG_INDEX(j) DOES NOT IMPLY |ARG_OFFSET(i)| > |ARG_OFFSET(j)|
+ //
+ // Therefore, we'll do a two pass offset calculation, one that considers pre-spill
+ // and the next, stack args.
+ //
+
+ unsigned argLcls = 0;
+
+ // Take care of pre spill registers first.
+ regMaskTP preSpillMask = codeGen->regSet.rsMaskPreSpillRegs(false);
+ regMaskTP tempMask = RBM_NONE;
+ for (unsigned i = 0, preSpillLclNum = lclNum; i < argSigLen; ++i, ++preSpillLclNum)
+ {
+ if (lvaIsPreSpilled(preSpillLclNum, preSpillMask))
+ {
+ unsigned argSize = eeGetArgSize(argLst, &info.compMethodInfo->args);
+ argOffs = lvaAssignVirtualFrameOffsetToArg(preSpillLclNum, argSize, argOffs);
+ argLcls++;
+
+ // Early out if we can. If size is 8 and base reg is 2, then the mask is 0x1100
+ tempMask |= ((((1 << (roundUp(argSize) / REGSIZE_BYTES))) - 1) << lvaTable[preSpillLclNum].lvArgReg);
+ if (tempMask == preSpillMask)
+ {
+ // We won't encounter more pre-spilled registers,
+ // so don't bother iterating further.
+ break;
+ }
+ }
+ argLst = info.compCompHnd->getArgNext(argLst);
+ }
+
+ // Take care of non pre-spilled stack arguments.
+ argLst = info.compMethodInfo->args.args;
+ for (unsigned i = 0, stkLclNum = lclNum; i < argSigLen; ++i, ++stkLclNum)
+ {
+ if (!lvaIsPreSpilled(stkLclNum, preSpillMask))
+ {
+ argOffs =
+ lvaAssignVirtualFrameOffsetToArg(stkLclNum, eeGetArgSize(argLst, &info.compMethodInfo->args), argOffs);
+ argLcls++;
+ }
+ argLst = info.compCompHnd->getArgNext(argLst);
+ }
+
+ lclNum += argLcls;
+#else // !_TARGET_ARM_
+ for (unsigned i = 0; i < argSigLen; i++)
+ {
+ unsigned argumentSize = eeGetArgSize(argLst, &info.compMethodInfo->args);
+
+#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+ // On the stack frame the homed arg always takes a full number of slots
+ // for proper stack alignment. Make sure the real struct size is properly rounded up.
+ argumentSize = (unsigned)roundUp(argumentSize, TARGET_POINTER_SIZE);
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+
+ argOffs =
+ lvaAssignVirtualFrameOffsetToArg(lclNum++, argumentSize, argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset));
+ argLst = info.compCompHnd->getArgNext(argLst);
+ }
+#endif // !_TARGET_ARM_
+
+#if !USER_ARGS_COME_LAST
+
+ //@GENERICS: extra argument for instantiation info
+ if (info.compMethodInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE)
+ {
+ noway_assert(lclNum == (unsigned)info.compTypeCtxtArg);
+ argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, sizeof(void*),
+ argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset));
+ }
+
+ if (info.compIsVarArgs)
+ {
+ argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum++, sizeof(void*),
+ argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset));
+ }
+
+#endif // USER_ARGS_COME_LAST
+}
+
+#ifdef UNIX_AMD64_ABI
+//
+// lvaAssignVirtualFrameOffsetToArg() : Assign virtual stack offsets to an
+// individual argument, and return the offset for the next argument.
+// Note: This method only calculates the initial offset of the stack passed/spilled arguments
+// (if any - the RA might decide to spill(home on the stack) register passed arguments, if rarely used.)
+// The final offset is calculated in lvaFixVirtualFrameOffsets method. It accounts for FP existance,
+// ret address slot, stack frame padding, alloca instructions, etc.
+// Note: This is the implementation for UNIX_AMD64 System V platforms.
+//
+int Compiler::lvaAssignVirtualFrameOffsetToArg(unsigned lclNum,
+ unsigned argSize,
+ int argOffs UNIX_AMD64_ABI_ONLY_ARG(int* callerArgOffset))
+{
+ noway_assert(lclNum < info.compArgsCount);
+ noway_assert(argSize);
+
+ if (Target::g_tgtArgOrder == Target::ARG_ORDER_L2R)
+ argOffs -= argSize;
+
+ unsigned fieldVarNum = BAD_VAR_NUM;
+
+ noway_assert(lclNum < lvaCount);
+ LclVarDsc* varDsc = lvaTable + lclNum;
+
+ if (varDsc->lvPromotedStruct())
+ {
+ noway_assert(varDsc->lvFieldCnt == 1); // We only handle one field here
+ fieldVarNum = varDsc->lvFieldLclStart;
+
+ lvaPromotionType promotionType = lvaGetPromotionType(varDsc);
+
+ if (promotionType == PROMOTION_TYPE_INDEPENDENT)
+ {
+ lclNum = fieldVarNum;
+ noway_assert(lclNum < lvaCount);
+ varDsc = lvaTable + lclNum;
+ assert(varDsc->lvIsStructField);
+ }
+ }
+
+ noway_assert(varDsc->lvIsParam);
+
+ if (varDsc->lvIsRegArg)
+ {
+ // Argument is passed in a register, don't count it
+ // when updating the current offset on the stack.
+
+ if (varDsc->lvOnFrame)
+ {
+ // The offset for args needs to be set only for the stack homed arguments for System V.
+ varDsc->lvStkOffs = argOffs;
+ }
+ else
+ {
+ varDsc->lvStkOffs = 0;
+ }
+ }
+ else
+ {
+ // For Windows AMD64 there are 4 slots for the register passed arguments on the top of the caller's stack.
+ // This is where they are always homed. So, they can be accessed with positive offset.
+ // On System V platforms, if the RA decides to home a register passed arg on the stack, it creates a stack
+ // location on the callee stack (like any other local var.) In such a case, the register passed, stack homed
+ // arguments are accessed using negative offsets and the stack passed arguments are accessed using positive
+ // offset (from the caller's stack.)
+ // For System V platforms if there is no frame pointer the caller stack parameter offset should include the
+ // callee allocated space. If frame register is used, the callee allocated space should not be included for
+ // accessing the caller stack parameters. The last two requirements are met in lvaFixVirtualFrameOffsets
+ // method, which fixes the offsets, based on frame pointer existence, existence of alloca instructions, ret
+ // address pushed, ets.
+
+ varDsc->lvStkOffs = *callerArgOffset;
+ // Structs passed on stack could be of size less than TARGET_POINTER_SIZE.
+ // Make sure they get at least TARGET_POINTER_SIZE on the stack - this is required for alignment.
+ if (argSize > TARGET_POINTER_SIZE)
+ {
+ *callerArgOffset += (int)roundUp(argSize, TARGET_POINTER_SIZE);
+ }
+ else
+ {
+ *callerArgOffset += TARGET_POINTER_SIZE;
+ }
+ }
+
+ // For struct promoted parameters we need to set the offsets for both LclVars.
+ //
+ // For a dependent promoted struct we also assign the struct fields stack offset
+ if (varDsc->lvPromotedStruct())
+ {
+ lvaPromotionType promotionType = lvaGetPromotionType(varDsc);
+
+ if (promotionType == PROMOTION_TYPE_DEPENDENT)
+ {
+ noway_assert(varDsc->lvFieldCnt == 1); // We only handle one field here
+
+ assert(fieldVarNum == varDsc->lvFieldLclStart);
+ lvaTable[fieldVarNum].lvStkOffs = varDsc->lvStkOffs;
+ }
+ }
+ // For an independent promoted struct field we also assign the parent struct stack offset
+ else if (varDsc->lvIsStructField)
+ {
+ noway_assert(varDsc->lvParentLcl < lvaCount);
+ lvaTable[varDsc->lvParentLcl].lvStkOffs = varDsc->lvStkOffs;
+ }
+
+ if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L && !varDsc->lvIsRegArg)
+ argOffs += argSize;
+
+ return argOffs;
+}
+
+#else // !UNIX_AMD64_ABI
+
+//
+// lvaAssignVirtualFrameOffsetToArg() : Assign virtual stack offsets to an
+// individual argument, and return the offset for the next argument.
+// Note: This method only calculates the initial offset of the stack passed/spilled arguments
+// (if any - the RA might decide to spill(home on the stack) register passed arguments, if rarely used.)
+// The final offset is calculated in lvaFixVirtualFrameOffsets method. It accounts for FP existance,
+// ret address slot, stack frame padding, alloca instructions, etc.
+// Note: This implementation for all the platforms but UNIX_AMD64 OSs (System V 64 bit.)
+int Compiler::lvaAssignVirtualFrameOffsetToArg(unsigned lclNum,
+ unsigned argSize,
+ int argOffs UNIX_AMD64_ABI_ONLY_ARG(int* callerArgOffset))
+{
+ noway_assert(lclNum < info.compArgsCount);
+ noway_assert(argSize);
+
+ if (Target::g_tgtArgOrder == Target::ARG_ORDER_L2R)
+ {
+ argOffs -= argSize;
+ }
+
+ unsigned fieldVarNum = BAD_VAR_NUM;
+
+ noway_assert(lclNum < lvaCount);
+ LclVarDsc* varDsc = lvaTable + lclNum;
+
+ if (varDsc->lvPromotedStruct())
+ {
+ noway_assert(varDsc->lvFieldCnt == 1); // We only handle one field here
+ fieldVarNum = varDsc->lvFieldLclStart;
+
+ lvaPromotionType promotionType = lvaGetPromotionType(varDsc);
+
+ if (promotionType == PROMOTION_TYPE_INDEPENDENT)
+ {
+ lclNum = fieldVarNum;
+ noway_assert(lclNum < lvaCount);
+ varDsc = lvaTable + lclNum;
+ assert(varDsc->lvIsStructField);
+ }
+ }
+
+ noway_assert(varDsc->lvIsParam);
+
+ if (varDsc->lvIsRegArg)
+ {
+ /* Argument is passed in a register, don't count it
+ * when updating the current offset on the stack */
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#if !defined(_TARGET_ARMARCH_)
+#if DEBUG
+ // TODO: Remove this noway_assert and replace occurrences of sizeof(void *) with argSize
+ // Also investigate why we are incrementing argOffs for X86 as this seems incorrect
+ //
+ noway_assert(argSize == sizeof(void*));
+#endif // DEBUG
+#endif
+
+#if defined(_TARGET_X86_)
+ argOffs += sizeof(void*);
+#elif defined(_TARGET_AMD64_)
+ // Register arguments on AMD64 also takes stack space. (in the backing store)
+ varDsc->lvStkOffs = argOffs;
+ argOffs += sizeof(void*);
+#elif defined(_TARGET_ARM64_)
+// Register arguments on ARM64 only take stack space when they have a frame home.
+#elif defined(_TARGET_ARM_)
+ // On ARM we spill the registers in codeGen->regSet.rsMaskPreSpillRegArg
+ // in the prolog, so we have to fill in lvStkOffs here
+ //
+ regMaskTP regMask = genRegMask(varDsc->lvArgReg);
+ if (codeGen->regSet.rsMaskPreSpillRegArg & regMask)
+ {
+ // Signature: void foo(struct_8, int, struct_4)
+ // ------- CALLER SP -------
+ // r3 struct_4
+ // r2 int - not prespilled, but added for alignment. argOffs should skip this.
+ // r1 struct_8
+ // r0 struct_8
+ // -------------------------
+ // If we added alignment we need to fix argOffs for all registers above alignment.
+ if (codeGen->regSet.rsMaskPreSpillAlign != RBM_NONE)
+ {
+ assert(genCountBits(codeGen->regSet.rsMaskPreSpillAlign) == 1);
+ // Is register beyond the alignment pos?
+ if (regMask > codeGen->regSet.rsMaskPreSpillAlign)
+ {
+ // Increment argOffs just once for the _first_ register after alignment pos
+ // in the prespill mask.
+ if (!BitsBetween(codeGen->regSet.rsMaskPreSpillRegArg, regMask,
+ codeGen->regSet.rsMaskPreSpillAlign))
+ {
+ argOffs += TARGET_POINTER_SIZE;
+ }
+ }
+ }
+
+ switch (varDsc->lvType)
+ {
+ case TYP_STRUCT:
+ if (!varDsc->lvStructDoubleAlign)
+ {
+ break;
+ }
+ __fallthrough;
+
+ case TYP_DOUBLE:
+ case TYP_LONG:
+ {
+ //
+ // Let's assign offsets to arg1, a double in r2. argOffs has to be 4 not 8.
+ //
+ // ------- CALLER SP -------
+ // r3
+ // r2 double -- argOffs = 4, but it doesn't need to be skipped, because there is no skipping.
+ // r1 VACookie -- argOffs = 0
+ // -------------------------
+ //
+ // Consider argOffs as if it accounts for number of prespilled registers before the current
+ // register. In the above example, for r2, it is r1 that is prespilled, but since r1 is
+ // accounted for by argOffs being 4, there should have been no skipping. Instead, if we didn't
+ // assign r1 to any variable, then argOffs would still be 0 which implies it is not accounting
+ // for r1, equivalently r1 is skipped.
+ //
+ // If prevRegsSize is unaccounted for by a corresponding argOffs, we must have skipped a register.
+ int prevRegsSize =
+ genCountBits(codeGen->regSet.rsMaskPreSpillRegArg & (regMask - 1)) * TARGET_POINTER_SIZE;
+ if (argOffs < prevRegsSize)
+ {
+ // We must align up the argOffset to a multiple of 8 to account for skipped registers.
+ argOffs = roundUp(argOffs, 2 * TARGET_POINTER_SIZE);
+ }
+ // We should've skipped only a single register.
+ assert(argOffs == prevRegsSize);
+ }
+ break;
+
+ default:
+ // No alignment of argOffs required
+ break;
+ }
+ varDsc->lvStkOffs = argOffs;
+ argOffs += argSize;
+ }
+#else // _TARGET_*
+#error Unsupported or unset target architecture
+#endif // _TARGET_*
+ }
+ else
+ {
+#if defined(_TARGET_ARM_)
+ // Dev11 Bug 42817: incorrect codegen for DrawFlatCheckBox causes A/V in WinForms
+ //
+ // Here we have method with a signature (int a1, struct a2, struct a3, int a4, int a5).
+ // Struct parameter 'a2' is 16-bytes with no alignment requirements;
+ // it uses r1,r2,r3 and [OutArg+0] when passed.
+ // Struct parameter 'a3' is 16-bytes that is required to be double aligned;
+ // the caller skips [OutArg+4] and starts the argument at [OutArg+8].
+ // Thus the caller generates the correct code to pass the arguments.
+ // When generating code to receive the arguments we set codeGen->regSet.rsMaskPreSpillRegArg to [r1,r2,r3]
+ // and spill these three registers as the first instruction in the prolog.
+ // Then when we layout the arguments' stack offsets we have an argOffs 0 which
+ // points at the location that we spilled r1 into the stack. For this first
+ // struct we take the lvIsRegArg path above with "codeGen->regSet.rsMaskPreSpillRegArg &" matching.
+ // Next when we calculate the argOffs for the second 16-byte struct we have an argOffs
+ // of 16, which appears to be aligned properly so we don't skip a stack slot.
+ //
+ // To fix this we must recover the actual OutArg offset by subtracting off the
+ // sizeof of the PreSpill register args.
+ // Then we align this offset to a multiple of 8 and add back the sizeof
+ // of the PreSpill register args.
+ //
+ // Dev11 Bug 71767: failure of assert(sizeofPreSpillRegArgs <= argOffs)
+ //
+ // We have a method with 'this' passed in r0, RetBuf arg in r1, VarArgs cookie
+ // in r2. The first user arg is a 144 byte struct with double alignment required,
+ // r3 is skipped, and the struct is passed on the stack. However, 'r3' is added
+ // to the codeGen->regSet.rsMaskPreSpillRegArg mask by the VarArgs cookie code, since we need to
+ // home all the potential varargs arguments in registers, even if we don't have
+ // signature type information for the variadic arguments. However, due to alignment,
+ // we have skipped a register that doesn't have a corresponding symbol. Make up
+ // for that by increasing argOffs here.
+ //
+
+ int sizeofPreSpillRegArgs = genCountBits(codeGen->regSet.rsMaskPreSpillRegs(true)) * REGSIZE_BYTES;
+
+ if (argOffs < sizeofPreSpillRegArgs)
+ {
+ // This can only happen if we skipped the last register spot because current stk arg
+ // is a struct requiring alignment or a pre-spill alignment was required because the
+ // first reg arg needed alignment.
+ //
+ // Example 1: First Stk Argument requiring alignment in vararg case (same as above comment.)
+ // Signature (int a0, int a1, int a2, struct {long} a3, ...)
+ //
+ // stk arg a3 --> argOffs here will be 12 (r0-r2) but pre-spill will be 16.
+ // ---- Caller SP ----
+ // r3 --> Stack slot is skipped in this case.
+ // r2 int a2
+ // r1 int a1
+ // r0 int a0
+ //
+ // Example 2: First Reg Argument requiring alignment in no-vararg case.
+ // Signature (struct {long} a0, struct {int} a1, int a2, int a3)
+ //
+ // stk arg --> argOffs here will be 12 {r0-r2} but pre-spill will be 16.
+ // ---- Caller SP ----
+ // r3 int a2 --> pushed (not pre-spilled) for alignment of a0 by lvaInitUserArgs.
+ // r2 struct { int } a1
+ // r0-r1 struct { long } a0
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef PROFILING_SUPPORTED
+ // On Arm under profiler, r0-r3 are always prespilled on stack.
+ // It is possible to have methods that accept only HFAs as parameters e.g. Signature(struct hfa1, struct
+ // hfa2), in which case hfa1 and hfa2 will be en-registered in co-processor registers and will have an
+ // argument offset less than size of preSpill.
+ //
+ // For this reason the following conditions are asserted when not under profiler.
+ if (!compIsProfilerHookNeeded())
+#endif
+ {
+ bool cond = ((info.compIsVarArgs || opts.compUseSoftFP) &&
+ // Does cur stk arg require double alignment?
+ ((varDsc->lvType == TYP_STRUCT && varDsc->lvStructDoubleAlign) ||
+ (varDsc->lvType == TYP_DOUBLE) || (varDsc->lvType == TYP_LONG))) ||
+ // Did first reg arg require alignment?
+ (codeGen->regSet.rsMaskPreSpillAlign & genRegMask(REG_ARG_LAST));
+
+ noway_assert(cond);
+ noway_assert(sizeofPreSpillRegArgs <=
+ argOffs + TARGET_POINTER_SIZE); // at most one register of alignment
+ }
+ argOffs = sizeofPreSpillRegArgs;
+ }
+
+ noway_assert(argOffs >= sizeofPreSpillRegArgs);
+ int argOffsWithoutPreSpillRegArgs = argOffs - sizeofPreSpillRegArgs;
+
+ switch (varDsc->lvType)
+ {
+ case TYP_STRUCT:
+ if (!varDsc->lvStructDoubleAlign)
+ break;
+
+ __fallthrough;
+
+ case TYP_DOUBLE:
+ case TYP_LONG:
+ // We must align up the argOffset to a multiple of 8
+ argOffs = roundUp(argOffsWithoutPreSpillRegArgs, 2 * TARGET_POINTER_SIZE) + sizeofPreSpillRegArgs;
+ break;
+
+ default:
+ // No alignment of argOffs required
+ break;
+ }
+#endif // _TARGET_ARM_
+
+ varDsc->lvStkOffs = argOffs;
+ }
+
+ // For struct promoted parameters we need to set the offsets for both LclVars.
+ //
+ // For a dependent promoted struct we also assign the struct fields stack offset
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#if !defined(_TARGET_64BIT_)
+ if ((varDsc->TypeGet() == TYP_LONG) && varDsc->lvPromoted)
+ {
+ noway_assert(varDsc->lvFieldCnt == 2);
+ fieldVarNum = varDsc->lvFieldLclStart;
+ lvaTable[fieldVarNum].lvStkOffs = varDsc->lvStkOffs;
+ lvaTable[fieldVarNum + 1].lvStkOffs = varDsc->lvStkOffs + genTypeSize(TYP_INT);
+ }
+ else
+#endif // !defined(_TARGET_64BIT_)
+ if (varDsc->lvPromotedStruct())
+ {
+ lvaPromotionType promotionType = lvaGetPromotionType(varDsc);
+
+ if (promotionType == PROMOTION_TYPE_DEPENDENT)
+ {
+ noway_assert(varDsc->lvFieldCnt == 1); // We only handle one field here
+
+ assert(fieldVarNum == varDsc->lvFieldLclStart);
+ lvaTable[fieldVarNum].lvStkOffs = varDsc->lvStkOffs;
+ }
+ }
+ // For an independent promoted struct field we also assign the parent struct stack offset
+ else if (varDsc->lvIsStructField)
+ {
+ noway_assert(varDsc->lvParentLcl < lvaCount);
+ lvaTable[varDsc->lvParentLcl].lvStkOffs = varDsc->lvStkOffs;
+ }
+
+ if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L && !varDsc->lvIsRegArg)
+ {
+ argOffs += argSize;
+ }
+
+ return argOffs;
+}
+#endif // !UNIX_AMD64_ABI
+
+/*****************************************************************************
+ * lvaAssignVirtualFrameOffsetsToLocals() : Assign virtual stack offsets to
+ * locals, temps, and anything else. These will all be negative offsets
+ * (stack grows down) relative to the virtual '0'/return address
+ */
+void Compiler::lvaAssignVirtualFrameOffsetsToLocals()
+{
+ int stkOffs = 0;
+ // codeGen->isFramePointerUsed is set in regalloc phase. Initialize it to a guess for pre-regalloc layout.
+ if (lvaDoneFrameLayout <= PRE_REGALLOC_FRAME_LAYOUT)
+ {
+ codeGen->setFramePointerUsed(codeGen->isFramePointerRequired());
+ }
+
+#ifdef _TARGET_XARCH_
+ // On x86/amd64, the return address has already been pushed by the call instruction in the caller.
+ stkOffs -= sizeof(void*); // return address;
+
+ // TODO-AMD64-CQ: for X64 eventually this should be pushed with all the other
+ // calleeregs. When you fix this, you'll also need to fix
+ // the assert at the bottom of this method
+ if (codeGen->doubleAlignOrFramePointerUsed())
+ {
+ stkOffs -= REGSIZE_BYTES;
+ }
+#endif //_TARGET_XARCH_
+
+ int preSpillSize = 0;
+ bool mustDoubleAlign = false;
+
+#ifdef _TARGET_ARM_
+ mustDoubleAlign = true;
+ preSpillSize = genCountBits(codeGen->regSet.rsMaskPreSpillRegs(true)) * REGSIZE_BYTES;
+#else // !_TARGET_ARM_
+#if DOUBLE_ALIGN
+ if (genDoubleAlign())
+ {
+ mustDoubleAlign = true; // X86 only
+ }
+#endif
+#endif // !_TARGET_ARM_
+
+#ifdef _TARGET_ARM64_
+ // If the frame pointer is used, then we'll save FP/LR at the bottom of the stack.
+ // Otherwise, we won't store FP, and we'll store LR at the top, with the other callee-save
+ // registers (if any).
+
+ int initialStkOffs = 0;
+ if (info.compIsVarArgs)
+ {
+ // For varargs we always save all of the integer register arguments
+ // so that they are contiguous with the incoming stack arguments.
+ initialStkOffs = MAX_REG_ARG * REGSIZE_BYTES;
+ stkOffs -= initialStkOffs;
+ }
+
+ if (isFramePointerUsed())
+ {
+ // Subtract off FP and LR.
+ assert(compCalleeRegsPushed >= 2);
+ stkOffs -= (compCalleeRegsPushed - 2) * REGSIZE_BYTES;
+ }
+ else
+ {
+ stkOffs -= compCalleeRegsPushed * REGSIZE_BYTES;
+ }
+
+#else // !_TARGET_ARM64_
+ stkOffs -= compCalleeRegsPushed * REGSIZE_BYTES;
+#endif // !_TARGET_ARM64_
+
+ compLclFrameSize = 0;
+
+#ifdef _TARGET_AMD64_
+ // In case of Amd64 compCalleeRegsPushed includes float regs (Xmm6-xmm15) that
+ // need to be pushed. But Amd64 doesn't support push/pop of xmm registers.
+ // Instead we need to allocate space for them on the stack and save them in prolog.
+ // Therefore, we consider xmm registers being saved while computing stack offsets
+ // but space for xmm registers is considered part of compLclFrameSize.
+ // Notes
+ // 1) We need to save the entire 128-bits of xmm register to stack, since amd64
+ // prolog unwind codes allow encoding of an instruction that stores the entire xmm reg
+ // at an offset relative to SP
+ // 2) We adjust frame size so that SP is aligned at 16-bytes after pushing integer registers.
+ // This means while saving the first xmm register to its allocated stack location we might
+ // have to skip 8-bytes. The reason for padding is to use efficient "movaps" to save/restore
+ // xmm registers to/from stack to match Jit64 codegen. Without the aligning on 16-byte
+ // boundary we would have to use movups when offset turns out unaligned. Movaps is more
+ // performant than movups.
+ unsigned calleeFPRegsSavedSize = genCountBits(compCalleeFPRegsSavedMask) * XMM_REGSIZE_BYTES;
+ if (calleeFPRegsSavedSize > 0 && ((stkOffs % XMM_REGSIZE_BYTES) != 0))
+ {
+ // Take care of alignment
+ int alignPad = (int)AlignmentPad((unsigned)-stkOffs, XMM_REGSIZE_BYTES);
+ stkOffs -= alignPad;
+ lvaIncrementFrameSize(alignPad);
+ }
+
+ stkOffs -= calleeFPRegsSavedSize;
+ lvaIncrementFrameSize(calleeFPRegsSavedSize);
+
+ // Quirk for VS debug-launch scenario to work
+ if (compVSQuirkStackPaddingNeeded > 0)
+ {
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("\nAdding VS quirk stack padding of %d bytes between save-reg area and locals\n",
+ compVSQuirkStackPaddingNeeded);
+ }
+#endif // DEBUG
+
+ stkOffs -= compVSQuirkStackPaddingNeeded;
+ lvaIncrementFrameSize(compVSQuirkStackPaddingNeeded);
+ }
+#endif //_TARGET_AMD64_
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_ARMARCH_)
+ if (ehNeedsPSPSym())
+ {
+ // On ARM/ARM64, if we need a PSPSym, allocate it first, before anything else, including
+ // padding (so we can avoid computing the same padding in the funclet
+ // frame). Note that there is no special padding requirement for the PSPSym.
+ noway_assert(codeGen->isFramePointerUsed()); // We need an explicit frame pointer
+ assert(lvaPSPSym != BAD_VAR_NUM); // We should have created the PSPSym variable
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaPSPSym, TARGET_POINTER_SIZE, stkOffs);
+ }
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARMARCH_)
+
+ if (mustDoubleAlign)
+ {
+ if (lvaDoneFrameLayout != FINAL_FRAME_LAYOUT)
+ {
+ // Allocate a pointer sized stack slot, since we may need to double align here
+ // when lvaDoneFrameLayout == FINAL_FRAME_LAYOUT
+ //
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+
+ // If we have any TYP_LONG, TYP_DOUBLE or double aligned structs
+ // then we need to allocate a second pointer sized stack slot,
+ // since we may need to double align that LclVar when we see it
+ // in the loop below. We will just always do this so that the
+ // offsets that we calculate for the stack frame will always
+ // be greater (or equal) to what they can be in the final layout.
+ //
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ }
+ else // FINAL_FRAME_LAYOUT
+ {
+ if (((stkOffs + preSpillSize) % (2 * TARGET_POINTER_SIZE)) != 0)
+ {
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ }
+ // We should now have a double-aligned (stkOffs+preSpillSize)
+ noway_assert(((stkOffs + preSpillSize) % (2 * TARGET_POINTER_SIZE)) == 0);
+ }
+ }
+
+ if (lvaMonAcquired != BAD_VAR_NUM)
+ {
+ // This var must go first, in what is called the 'frame header' for EnC so that it is
+ // preserved when remapping occurs. See vm\eetwain.cpp for detailed comment specifying frame
+ // layout requirements for EnC to work.
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaMonAcquired, lvaLclSize(lvaMonAcquired), stkOffs);
+ }
+
+ if (opts.compNeedSecurityCheck)
+ {
+#ifdef JIT32_GCENCODER
+ /* This can't work without an explicit frame, so make sure */
+ noway_assert(codeGen->isFramePointerUsed());
+#endif
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaSecurityObject, TARGET_POINTER_SIZE, stkOffs);
+ }
+
+ if (compLocallocUsed)
+ {
+#ifdef JIT32_GCENCODER
+ noway_assert(codeGen->isFramePointerUsed()); // else offsets of locals of frameless methods will be incorrect
+#endif
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaLocAllocSPvar, TARGET_POINTER_SIZE, stkOffs);
+ }
+
+ if (lvaReportParamTypeArg())
+ {
+#ifdef JIT32_GCENCODER
+ noway_assert(codeGen->isFramePointerUsed());
+#endif
+ // For CORINFO_CALLCONV_PARAMTYPE (if needed)
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ lvaCachedGenericContextArgOffs = stkOffs;
+ }
+#ifndef JIT32_GCENCODER
+ else if (lvaKeepAliveAndReportThis())
+ {
+ // When "this" is also used as generic context arg.
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ lvaCachedGenericContextArgOffs = stkOffs;
+ }
+#endif
+
+#if !FEATURE_EH_FUNCLETS
+ /* If we need space for slots for shadow SP, reserve it now */
+ if (ehNeedsShadowSPslots())
+ {
+ noway_assert(codeGen->isFramePointerUsed()); // else offsets of locals of frameless methods will be incorrect
+ if (!lvaReportParamTypeArg())
+ {
+#ifndef JIT32_GCENCODER
+ if (!lvaKeepAliveAndReportThis())
+#endif
+ {
+ // In order to keep the gc info encoding smaller, the VM assumes that all methods with EH
+ // have also saved space for a ParamTypeArg, so we need to do that here
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ }
+ }
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaShadowSPslotsVar, lvaLclSize(lvaShadowSPslotsVar), stkOffs);
+ }
+#endif // !FEATURE_EH_FUNCLETS
+
+ if (compGSReorderStackLayout)
+ {
+ assert(getNeedsGSSecurityCookie());
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaGSSecurityCookie, lvaLclSize(lvaGSSecurityCookie), stkOffs);
+ }
+
+ /*
+ If we're supposed to track lifetimes of pointer temps, we'll
+ assign frame offsets in the following order:
+
+ non-pointer local variables (also untracked pointer variables)
+ pointer local variables
+ pointer temps
+ non-pointer temps
+ */
+
+ enum Allocation
+ {
+ ALLOC_NON_PTRS = 0x1, // assign offsets to non-ptr
+ ALLOC_PTRS = 0x2, // Second pass, assign offsets to tracked ptrs
+ ALLOC_UNSAFE_BUFFERS = 0x4,
+ ALLOC_UNSAFE_BUFFERS_WITH_PTRS = 0x8
+ };
+ UINT alloc_order[5];
+
+ unsigned int cur = 0;
+
+ if (compGSReorderStackLayout)
+ {
+ noway_assert(getNeedsGSSecurityCookie());
+
+ if (codeGen->isFramePointerUsed())
+ {
+ alloc_order[cur++] = ALLOC_UNSAFE_BUFFERS;
+ alloc_order[cur++] = ALLOC_UNSAFE_BUFFERS_WITH_PTRS;
+ }
+ }
+
+ bool tempsAllocated = false;
+
+#ifdef _TARGET_ARM_
+ // On ARM, SP based offsets use smaller encoding. Since temps are relatively
+ // rarer than lcl usage, allocate them farther from SP.
+ if (!opts.MinOpts() && !compLocallocUsed)
+#else
+ if (lvaTempsHaveLargerOffsetThanVars() && !codeGen->isFramePointerUsed())
+#endif
+ {
+ // Because we want the temps to have a larger offset than locals
+ // and we're not using a frame pointer, we have to place the temps
+ // above the vars. Otherwise we place them after the vars (at the
+ // bottom of the frame).
+ noway_assert(!tempsAllocated);
+ stkOffs = lvaAllocateTemps(stkOffs, mustDoubleAlign);
+ tempsAllocated = true;
+ }
+
+ alloc_order[cur++] = ALLOC_NON_PTRS;
+
+ if (opts.compDbgEnC)
+ {
+ /* We will use just one pass, and assign offsets to all variables */
+ alloc_order[cur - 1] |= ALLOC_PTRS;
+ noway_assert(compGSReorderStackLayout == false);
+ }
+ else
+ {
+ alloc_order[cur++] = ALLOC_PTRS;
+ }
+
+ if (!codeGen->isFramePointerUsed() && compGSReorderStackLayout)
+ {
+ alloc_order[cur++] = ALLOC_UNSAFE_BUFFERS_WITH_PTRS;
+ alloc_order[cur++] = ALLOC_UNSAFE_BUFFERS;
+ }
+
+ alloc_order[cur] = 0;
+
+ noway_assert(cur < sizeof(alloc_order) / sizeof(alloc_order[0]));
+
+ // Force first pass to happen
+ UINT assignMore = 0xFFFFFFFF;
+ bool have_LclVarDoubleAlign = false;
+
+ for (cur = 0; alloc_order[cur]; cur++)
+ {
+ if ((assignMore & alloc_order[cur]) == 0)
+ {
+ continue;
+ }
+
+ assignMore = 0;
+
+ unsigned lclNum;
+ LclVarDsc* varDsc;
+
+ for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ /* Ignore field locals of the promotion type PROMOTION_TYPE_FIELD_DEPENDENT.
+ In other words, we will not calculate the "base" address of the struct local if
+ the promotion type is PROMOTION_TYPE_FIELD_DEPENDENT.
+ */
+ if (lvaIsFieldOfDependentlyPromotedStruct(varDsc))
+ {
+ continue;
+ }
+
+#if FEATURE_FIXED_OUT_ARGS
+ // The scratch mem is used for the outgoing arguments, and it must be absolutely last
+ if (lclNum == lvaOutgoingArgSpaceVar)
+ {
+ continue;
+ }
+#endif
+
+ bool allocateOnFrame = varDsc->lvOnFrame;
+
+ if (varDsc->lvRegister && (lvaDoneFrameLayout == REGALLOC_FRAME_LAYOUT) &&
+ ((varDsc->TypeGet() != TYP_LONG) || (varDsc->lvOtherReg != REG_STK)))
+ {
+ allocateOnFrame = false;
+ }
+
+ /* Ignore variables that are not on the stack frame */
+
+ if (!allocateOnFrame)
+ {
+ /* For EnC, all variables have to be allocated space on the
+ stack, even though they may actually be enregistered. This
+ way, the frame layout can be directly inferred from the
+ locals-sig.
+ */
+
+ if (!opts.compDbgEnC)
+ {
+ continue;
+ }
+ else if (lclNum >= info.compLocalsCount)
+ { // ignore temps for EnC
+ continue;
+ }
+ }
+ else if (lvaGSSecurityCookie == lclNum && getNeedsGSSecurityCookie())
+ {
+ continue; // This is allocated outside of this loop.
+ }
+
+ // These need to be located as the very first variables (highest memory address)
+ // and so they have already been assigned an offset
+ if (
+#if FEATURE_EH_FUNCLETS
+ lclNum == lvaPSPSym ||
+#else
+ lclNum == lvaShadowSPslotsVar ||
+#endif // FEATURE_EH_FUNCLETS
+ lclNum == lvaLocAllocSPvar || lclNum == lvaSecurityObject)
+ {
+ assert(varDsc->lvStkOffs != BAD_STK_OFFS);
+ continue;
+ }
+
+ if (lclNum == lvaMonAcquired)
+ {
+ continue;
+ }
+
+ // This should be low on the stack. Hence, it will be assigned later.
+ if (lclNum == lvaStubArgumentVar)
+ {
+#ifdef JIT32_GCENCODER
+ noway_assert(codeGen->isFramePointerUsed());
+#endif
+ continue;
+ }
+
+ // This should be low on the stack. Hence, it will be assigned later.
+ if (lclNum == lvaInlinedPInvokeFrameVar)
+ {
+ noway_assert(codeGen->isFramePointerUsed());
+ continue;
+ }
+
+ if (varDsc->lvIsParam)
+ {
+#if defined(_TARGET_AMD64_) && !defined(UNIX_AMD64_ABI)
+
+ // On Windows AMD64 we can use the caller-reserved stack area that is already setup
+ assert(varDsc->lvStkOffs != BAD_STK_OFFS);
+ continue;
+
+#else // !_TARGET_AMD64_
+
+ // A register argument that is not enregistered ends up as
+ // a local variable which will need stack frame space.
+ //
+ if (!varDsc->lvIsRegArg)
+ continue;
+
+#ifdef _TARGET_ARM64_
+ if (info.compIsVarArgs)
+ {
+ // Stack offset to varargs (parameters) should point to home area which will be preallocated.
+ varDsc->lvStkOffs =
+ -initialStkOffs + genMapIntRegNumToRegArgNum(varDsc->GetArgReg()) * REGSIZE_BYTES;
+ continue;
+ }
+#endif
+
+#ifdef _TARGET_ARM_
+ // On ARM we spill the registers in codeGen->regSet.rsMaskPreSpillRegArg
+ // in the prolog, thus they don't need stack frame space.
+ //
+ if ((codeGen->regSet.rsMaskPreSpillRegs(false) & genRegMask(varDsc->lvArgReg)) != 0)
+ {
+ assert(varDsc->lvStkOffs != BAD_STK_OFFS);
+ continue;
+ }
+#endif
+
+#endif // !_TARGET_AMD64_
+ }
+
+ /* Make sure the type is appropriate */
+
+ if (varDsc->lvIsUnsafeBuffer && compGSReorderStackLayout)
+ {
+ if (varDsc->lvIsPtr)
+ {
+ if ((alloc_order[cur] & ALLOC_UNSAFE_BUFFERS_WITH_PTRS) == 0)
+ {
+ assignMore |= ALLOC_UNSAFE_BUFFERS_WITH_PTRS;
+ continue;
+ }
+ }
+ else
+ {
+ if ((alloc_order[cur] & ALLOC_UNSAFE_BUFFERS) == 0)
+ {
+ assignMore |= ALLOC_UNSAFE_BUFFERS;
+ continue;
+ }
+ }
+ }
+ else if (varTypeIsGC(varDsc->TypeGet()) && varDsc->lvTracked)
+ {
+ if ((alloc_order[cur] & ALLOC_PTRS) == 0)
+ {
+ assignMore |= ALLOC_PTRS;
+ continue;
+ }
+ }
+ else
+ {
+ if ((alloc_order[cur] & ALLOC_NON_PTRS) == 0)
+ {
+ assignMore |= ALLOC_NON_PTRS;
+ continue;
+ }
+ }
+
+ /* Need to align the offset? */
+
+ if (mustDoubleAlign && (varDsc->lvType == TYP_DOUBLE // Align doubles for ARM and x86
+#ifdef _TARGET_ARM_
+ || varDsc->lvType == TYP_LONG // Align longs for ARM
+#endif
+#ifndef _TARGET_64BIT_
+ || varDsc->lvStructDoubleAlign // Align when lvStructDoubleAlign is true
+#endif // !_TARGET_64BIT_
+ ))
+ {
+ noway_assert((compLclFrameSize % TARGET_POINTER_SIZE) == 0);
+
+ if ((lvaDoneFrameLayout != FINAL_FRAME_LAYOUT) && !have_LclVarDoubleAlign)
+ {
+ // If this is the first TYP_LONG, TYP_DOUBLE or double aligned struct
+ // then we have seen in this loop then we allocate a pointer sized
+ // stack slot since we may need to double align this LclVar
+ // when lvaDoneFrameLayout == FINAL_FRAME_LAYOUT
+ //
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ }
+ else
+ {
+ if (((stkOffs + preSpillSize) % (2 * TARGET_POINTER_SIZE)) != 0)
+ {
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ }
+
+ // We should now have a double-aligned (stkOffs+preSpillSize)
+ noway_assert(((stkOffs + preSpillSize) % (2 * TARGET_POINTER_SIZE)) == 0);
+ }
+
+ // Remember that we had to double align a LclVar
+ have_LclVarDoubleAlign = true;
+ }
+
+ // Reserve the stack space for this variable
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lclNum, lvaLclSize(lclNum), stkOffs);
+#ifdef _TARGET_ARM64_
+ // If we have an incoming register argument that has a struct promoted field
+ // then we need to copy the lvStkOff (the stack home) from the reg arg to the field lclvar
+ //
+ if (varDsc->lvIsRegArg && varDsc->lvPromotedStruct())
+ {
+ noway_assert(varDsc->lvFieldCnt == 1); // We only handle one field here
+
+ unsigned fieldVarNum = varDsc->lvFieldLclStart;
+ lvaTable[fieldVarNum].lvStkOffs = varDsc->lvStkOffs;
+ }
+#endif
+ }
+ }
+
+ if (getNeedsGSSecurityCookie() && !compGSReorderStackLayout)
+ {
+ // LOCALLOC used, but we have no unsafe buffer. Allocated cookie last, close to localloc buffer.
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaGSSecurityCookie, lvaLclSize(lvaGSSecurityCookie), stkOffs);
+ }
+
+ if (tempsAllocated == false)
+ {
+ /*-------------------------------------------------------------------------
+ *
+ * Now the temps
+ *
+ *-------------------------------------------------------------------------
+ */
+ stkOffs = lvaAllocateTemps(stkOffs, mustDoubleAlign);
+ }
+
+ /*-------------------------------------------------------------------------
+ *
+ * Now do some final stuff
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ // lvaInlinedPInvokeFrameVar and lvaStubArgumentVar need to be assigned last
+ // Important: The stack walker depends on lvaStubArgumentVar immediately
+ // following lvaInlinedPInvokeFrameVar in the frame.
+
+ if (lvaStubArgumentVar != BAD_VAR_NUM)
+ {
+#ifdef JIT32_GCENCODER
+ noway_assert(codeGen->isFramePointerUsed());
+#endif
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaStubArgumentVar, lvaLclSize(lvaStubArgumentVar), stkOffs);
+ }
+
+ if (lvaInlinedPInvokeFrameVar != BAD_VAR_NUM)
+ {
+ noway_assert(codeGen->isFramePointerUsed());
+ stkOffs =
+ lvaAllocLocalAndSetVirtualOffset(lvaInlinedPInvokeFrameVar, lvaLclSize(lvaInlinedPInvokeFrameVar), stkOffs);
+ }
+
+ if (mustDoubleAlign)
+ {
+ if (lvaDoneFrameLayout != FINAL_FRAME_LAYOUT)
+ {
+ // Allocate a pointer sized stack slot, since we may need to double align here
+ // when lvaDoneFrameLayout == FINAL_FRAME_LAYOUT
+ //
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+
+ if (have_LclVarDoubleAlign)
+ {
+ // If we have any TYP_LONG, TYP_DOUBLE or double aligned structs
+ // the we need to allocate a second pointer sized stack slot,
+ // since we may need to double align the last LclVar that we saw
+ // in the loop above. We do this so that the offsets that we
+ // calculate for the stack frame are always greater than they will
+ // be in the final layout.
+ //
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ }
+ }
+ else // FINAL_FRAME_LAYOUT
+ {
+ if (((stkOffs + preSpillSize) % (2 * TARGET_POINTER_SIZE)) != 0)
+ {
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ }
+ // We should now have a double-aligned (stkOffs+preSpillSize)
+ noway_assert(((stkOffs + preSpillSize) % (2 * TARGET_POINTER_SIZE)) == 0);
+ }
+ }
+
+#if FEATURE_EH_FUNCLETS && defined(_TARGET_AMD64_)
+ if (ehNeedsPSPSym())
+ {
+ // On AMD64, if we need a PSPSym, allocate it last, immediately above the outgoing argument
+ // space. Any padding will be higher on the stack than this
+ // (including the padding added by lvaAlignFrame()).
+ noway_assert(codeGen->isFramePointerUsed()); // We need an explicit frame pointer
+ assert(lvaPSPSym != BAD_VAR_NUM); // We should have created the PSPSym variable
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaPSPSym, TARGET_POINTER_SIZE, stkOffs);
+ }
+#endif // FEATURE_EH_FUNCLETS && defined(_TARGET_AMD64_)
+
+#ifdef _TARGET_ARM64_
+ if (isFramePointerUsed())
+ {
+ // Create space for saving FP and LR.
+ stkOffs -= 2 * REGSIZE_BYTES;
+ }
+#endif // _TARGET_ARM64_
+
+#if FEATURE_FIXED_OUT_ARGS
+ if (lvaOutgoingArgSpaceSize > 0)
+ {
+#if defined(_TARGET_AMD64_) && !defined(UNIX_AMD64_ABI) // No 4 slots for outgoing params on System V.
+ noway_assert(lvaOutgoingArgSpaceSize >= (4 * sizeof(void*)));
+#endif
+ noway_assert((lvaOutgoingArgSpaceSize % sizeof(void*)) == 0);
+
+ // Give it a value so we can avoid asserts in CHK builds.
+ // Since this will always use an SP relative offset of zero
+ // at the end of lvaFixVirtualFrameOffsets, it will be set to absolute '0'
+
+ stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaOutgoingArgSpaceVar, lvaLclSize(lvaOutgoingArgSpaceVar), stkOffs);
+ }
+#endif // FEATURE_FIXED_OUT_ARGS
+
+ // compLclFrameSize equals our negated virtual stack offset minus the pushed registers and return address
+ // and the pushed frame pointer register which for some strange reason isn't part of 'compCalleeRegsPushed'.
+ int pushedCount = compCalleeRegsPushed;
+
+#ifdef _TARGET_ARM64_
+ if (info.compIsVarArgs)
+ {
+ pushedCount += MAX_REG_ARG;
+ }
+#endif
+
+#ifdef _TARGET_XARCH_
+ if (codeGen->doubleAlignOrFramePointerUsed())
+ {
+ pushedCount += 1; // pushed EBP (frame pointer)
+ }
+ pushedCount += 1; // pushed PC (return address)
+#endif
+
+ noway_assert(compLclFrameSize == (unsigned)-(stkOffs + (pushedCount * (int)sizeof(void*))));
+}
+
+int Compiler::lvaAllocLocalAndSetVirtualOffset(unsigned lclNum, unsigned size, int stkOffs)
+{
+ noway_assert(lclNum != BAD_VAR_NUM);
+
+#ifdef _TARGET_64BIT_
+ // Before final frame layout, assume the worst case, that every >=8 byte local will need
+ // maximum padding to be aligned. This is because we generate code based on the stack offset
+ // computed during tentative frame layout. These offsets cannot get bigger during final
+ // frame layout, as that would possibly require different code generation (for example,
+ // using a 4-byte offset instead of a 1-byte offset in an instruction). The offsets can get
+ // smaller. It is possible there is different alignment at the point locals are allocated
+ // between tentative and final frame layout which would introduce padding between locals
+ // and thus increase the offset (from the stack pointer) of one of the locals. Hence the
+ // need to assume the worst alignment before final frame layout.
+ // We could probably improve this by sorting all the objects by alignment,
+ // such that all 8 byte objects are together, 4 byte objects are together, etc., which
+ // would require at most one alignment padding per group.
+ //
+ // TYP_SIMD structs locals have alignment preference given by getSIMDTypeAlignment() for
+ // better performance.
+ if ((size >= 8) && ((lvaDoneFrameLayout != FINAL_FRAME_LAYOUT) || ((stkOffs % 8) != 0)
+#if defined(FEATURE_SIMD) && ALIGN_SIMD_TYPES
+ || lclVarIsSIMDType(lclNum)
+#endif
+ ))
+ {
+ // Note that stack offsets are negative
+ assert(stkOffs < 0);
+
+ // alignment padding
+ unsigned pad = 0;
+#if defined(FEATURE_SIMD) && ALIGN_SIMD_TYPES
+ if (lclVarIsSIMDType(lclNum) && !lvaIsImplicitByRefLocal(lclNum))
+ {
+ int alignment = getSIMDTypeAlignment(lvaTable[lclNum].lvType);
+
+ if (stkOffs % alignment != 0)
+ {
+ if (lvaDoneFrameLayout != FINAL_FRAME_LAYOUT)
+ {
+ pad = alignment - 1;
+ // Note that all the objects will probably be misaligned, but we'll fix that in final layout.
+ }
+ else
+ {
+ pad = alignment + (stkOffs % alignment); // +1 to +(alignment-1) bytes
+ }
+ }
+ }
+ else
+#endif // FEATURE_SIMD && ALIGN_SIMD_TYPES
+ {
+ if (lvaDoneFrameLayout != FINAL_FRAME_LAYOUT)
+ {
+ pad = 7;
+ // Note that all the objects will probably be misaligned, but we'll fix that in final layout.
+ }
+ else
+ {
+ pad = 8 + (stkOffs % 8); // +1 to +7 bytes
+ }
+ }
+ // Will the pad ever be anything except 4? Do we put smaller-than-4-sized objects on the stack?
+ lvaIncrementFrameSize(pad);
+ stkOffs -= pad;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Pad ");
+ gtDispLclVar(lclNum, /*pad*/ false);
+ printf(", size=%d, stkOffs=%c0x%x, pad=%d\n", size, stkOffs < 0 ? '-' : '+',
+ stkOffs < 0 ? -stkOffs : stkOffs, pad);
+ }
+#endif
+ }
+#endif // _TARGET_64BIT_
+
+ /* Reserve space on the stack by bumping the frame size */
+
+ lvaIncrementFrameSize(size);
+ stkOffs -= size;
+ lvaTable[lclNum].lvStkOffs = stkOffs;
+
+#ifdef DEBUG
+ if (verbose)
+ {
+ printf("Assign ");
+ gtDispLclVar(lclNum, /*pad*/ false);
+ printf(", size=%d, stkOffs=%c0x%x\n", size, stkOffs < 0 ? '-' : '+', stkOffs < 0 ? -stkOffs : stkOffs);
+ }
+#endif
+
+ return stkOffs;
+}
+
+#ifdef _TARGET_AMD64_
+/*****************************************************************************
+ * lvaIsCalleeSavedIntRegCountEven() : returns true if the number of integer registers
+ * pushed onto stack is even including RBP if used as frame pointer
+ *
+ * Note that this excludes return address (PC) pushed by caller. To know whether
+ * the SP offset after pushing integer registers is aligned, we need to take
+ * negation of this routine.
+ */
+bool Compiler::lvaIsCalleeSavedIntRegCountEven()
+{
+ unsigned regsPushed = compCalleeRegsPushed + (codeGen->isFramePointerUsed() ? 1 : 0);
+ return (regsPushed % (16 / REGSIZE_BYTES)) == 0;
+}
+#endif //_TARGET_AMD64_
+
+/*****************************************************************************
+ * lvaAlignFrame() : After allocating everything on the frame, reserve any
+ * extra space needed to keep the frame aligned
+ */
+void Compiler::lvaAlignFrame()
+{
+#if defined(_TARGET_AMD64_)
+
+ // Leaf frames do not need full alignment, but the unwind info is smaller if we
+ // are at least 8 byte aligned (and we assert as much)
+ if ((compLclFrameSize % 8) != 0)
+ {
+ lvaIncrementFrameSize(8 - (compLclFrameSize % 8));
+ }
+ else if (lvaDoneFrameLayout != FINAL_FRAME_LAYOUT)
+ {
+ // If we are not doing final layout, we don't know the exact value of compLclFrameSize
+ // and thus do not know how much we will need to add in order to be aligned.
+ // We add 8 so compLclFrameSize is still a multiple of 8.
+ lvaIncrementFrameSize(8);
+ }
+ assert((compLclFrameSize % 8) == 0);
+
+ // Ensure that the stack is always 16-byte aligned by grabbing an unused QWORD
+ // if needed, but off by 8 because of the return value.
+ // And don't forget that compCalleeRegsPused does *not* include RBP if we are
+ // using it as the frame pointer.
+ //
+ bool regPushedCountAligned = lvaIsCalleeSavedIntRegCountEven();
+ bool lclFrameSizeAligned = (compLclFrameSize % 16) == 0;
+
+ // If this isn't the final frame layout, assume we have to push an extra QWORD
+ // Just so the offsets are true upper limits.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef UNIX_AMD64_ABI
+ // The compNeedToAlignFrame flag is indicating if there is a need to align the frame.
+ // On AMD64-Windows, if there are calls, 4 slots for the outgoing ars are allocated, except for
+ // FastTailCall. This slots makes the frame size non-zero, so alignment logic will be called.
+ // On AMD64-Unix, there are no such slots. There is a possibility to have calls in the method with frame size of 0.
+ // The frame alignment logic won't kick in. This flags takes care of the AMD64-Unix case by remembering that there
+ // are calls and making sure the frame alignment logic is executed.
+ bool stackNeedsAlignment = (compLclFrameSize != 0 || opts.compNeedToAlignFrame);
+#else // !UNIX_AMD64_ABI
+ bool stackNeedsAlignment = compLclFrameSize != 0;
+#endif // !UNIX_AMD64_ABI
+ if ((!codeGen->isFramePointerUsed() && (lvaDoneFrameLayout != FINAL_FRAME_LAYOUT)) ||
+ (stackNeedsAlignment && (regPushedCountAligned == lclFrameSizeAligned)))
+ {
+ lvaIncrementFrameSize(REGSIZE_BYTES);
+ }
+
+#elif defined(_TARGET_ARM64_)
+
+ // The stack on ARM64 must be 16 byte aligned.
+
+ // First, align up to 8.
+ if ((compLclFrameSize % 8) != 0)
+ {
+ lvaIncrementFrameSize(8 - (compLclFrameSize % 8));
+ }
+ else if (lvaDoneFrameLayout != FINAL_FRAME_LAYOUT)
+ {
+ // If we are not doing final layout, we don't know the exact value of compLclFrameSize
+ // and thus do not know how much we will need to add in order to be aligned.
+ // We add 8 so compLclFrameSize is still a multiple of 8.
+ lvaIncrementFrameSize(8);
+ }
+ assert((compLclFrameSize % 8) == 0);
+
+ // Ensure that the stack is always 16-byte aligned by grabbing an unused QWORD
+ // if needed.
+ bool regPushedCountAligned = (compCalleeRegsPushed % (16 / REGSIZE_BYTES)) == 0;
+ bool lclFrameSizeAligned = (compLclFrameSize % 16) == 0;
+
+ // If this isn't the final frame layout, assume we have to push an extra QWORD
+ // Just so the offsets are true upper limits.
+ if ((lvaDoneFrameLayout != FINAL_FRAME_LAYOUT) || (regPushedCountAligned != lclFrameSizeAligned))
+ {
+ lvaIncrementFrameSize(REGSIZE_BYTES);
+ }
+
+#elif defined(_TARGET_ARM_)
+
+ // Ensure that stack offsets will be double-aligned by grabbing an unused DWORD if needed.
+ //
+ bool lclFrameSizeAligned = (compLclFrameSize % sizeof(double)) == 0;
+ bool regPushedCountAligned = ((compCalleeRegsPushed + genCountBits(codeGen->regSet.rsMaskPreSpillRegs(true))) %
+ (sizeof(double) / sizeof(void*))) == 0;
+
+ if (regPushedCountAligned != lclFrameSizeAligned)
+ {
+ lvaIncrementFrameSize(sizeof(void*));
+ }
+
+#elif defined(_TARGET_X86_)
+
+ if (genDoubleAlign())
+ {
+ // Double Frame Alignement for x86 is handled in Compiler::lvaAssignVirtualFrameOffsetsToLocals()
+
+ if (compLclFrameSize == 0)
+ {
+ // This can only happen with JitStress=1 or JitDoubleAlign=2
+ lvaIncrementFrameSize(sizeof(void*));
+ }
+ }
+
+#else
+ NYI("TARGET specific lvaAlignFrame");
+#endif // !_TARGET_AMD64_
+}
+
+/*****************************************************************************
+ * lvaAssignFrameOffsetsToPromotedStructs() : Assign offsets to fields
+ * within a promoted struct (worker for lvaAssignFrameOffsets).
+ */
+void Compiler::lvaAssignFrameOffsetsToPromotedStructs()
+{
+ LclVarDsc* varDsc = lvaTable;
+ for (unsigned lclNum = 0; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ // For promoted struct fields that are params, we will
+ // assign their offsets in lvaAssignVirtualFrameOffsetToArg().
+ // This is not true for the System V systems since there is no
+ // outgoing args space. Assign the dependently promoted fields properly.
+ //
+ if (varDsc->lvIsStructField
+#ifndef UNIX_AMD64_ABI
+ // For System V platforms there is no outgoing args space.
+ // A register passed struct arg is homed on the stack in a separate local var.
+ // The offset of these structs is already calculated in lvaAssignVirtualFrameOffsetToArg methos.
+ // Make sure the code below is not executed for these structs and the offset is not changed.
+ && !varDsc->lvIsParam
+#endif // UNIX_AMD64_ABI
+ )
+ {
+ LclVarDsc* parentvarDsc = &lvaTable[varDsc->lvParentLcl];
+ lvaPromotionType promotionType = lvaGetPromotionType(parentvarDsc);
+
+ if (promotionType == PROMOTION_TYPE_INDEPENDENT)
+ {
+ // The stack offset for these field locals must have been calculated
+ // by the normal frame offset assignment.
+ continue;
+ }
+ else
+ {
+ noway_assert(promotionType == PROMOTION_TYPE_DEPENDENT);
+ noway_assert(varDsc->lvOnFrame);
+ varDsc->lvStkOffs = parentvarDsc->lvStkOffs + varDsc->lvFldOffset;
+ }
+ }
+ }
+}
+
+/*****************************************************************************
+ * lvaAllocateTemps() : Assign virtual offsets to temps (always negative).
+ */
+int Compiler::lvaAllocateTemps(int stkOffs, bool mustDoubleAlign)
+{
+ unsigned spillTempSize = 0;
+
+ if (lvaDoneFrameLayout == FINAL_FRAME_LAYOUT)
+ {
+ int preSpillSize = 0;
+#ifdef _TARGET_ARM_
+ preSpillSize = genCountBits(codeGen->regSet.rsMaskPreSpillRegs(true)) * TARGET_POINTER_SIZE;
+#endif
+ bool assignDone;
+ bool assignNptr;
+ bool assignPtrs = true;
+
+ /* Allocate temps */
+
+ if (TRACK_GC_TEMP_LIFETIMES)
+ {
+ /* first pointers, then non-pointers in second pass */
+ assignNptr = false;
+ assignDone = false;
+ }
+ else
+ {
+ /* Pointers and non-pointers together in single pass */
+ assignNptr = true;
+ assignDone = true;
+ }
+
+ assert(tmpAllFree());
+
+ AGAIN2:
+
+ for (TempDsc* temp = tmpListBeg(); temp != nullptr; temp = tmpListNxt(temp))
+ {
+ var_types tempType = temp->tdTempType();
+ unsigned size;
+
+ /* Make sure the type is appropriate */
+
+ if (!assignPtrs && varTypeIsGC(tempType))
+ {
+ continue;
+ }
+ if (!assignNptr && !varTypeIsGC(tempType))
+ {
+ continue;
+ }
+
+ size = temp->tdTempSize();
+
+ /* Figure out and record the stack offset of the temp */
+
+ /* Need to align the offset? */
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef _TARGET_64BIT_
+ if (varTypeIsGC(tempType) && ((stkOffs % TARGET_POINTER_SIZE) != 0))
+ {
+ // Calculate 'pad' as the number of bytes to align up 'stkOffs' to be a multiple of TARGET_POINTER_SIZE
+ // In practice this is really just a fancy way of writing 4. (as all stack locations are at least 4-byte
+ // aligned). Note stkOffs is always negative, so (stkOffs % TARGET_POINTER_SIZE) yields a negative
+ // value.
+ //
+ int alignPad = (int)AlignmentPad((unsigned)-stkOffs, TARGET_POINTER_SIZE);
+
+ spillTempSize += alignPad;
+ lvaIncrementFrameSize(alignPad);
+ stkOffs -= alignPad;
+
+ noway_assert((stkOffs % TARGET_POINTER_SIZE) == 0);
+ }
+#endif
+
+ if (mustDoubleAlign && (tempType == TYP_DOUBLE)) // Align doubles for x86 and ARM
+ {
+ noway_assert((compLclFrameSize % TARGET_POINTER_SIZE) == 0);
+
+ if (((stkOffs + preSpillSize) % (2 * TARGET_POINTER_SIZE)) != 0)
+ {
+ spillTempSize += TARGET_POINTER_SIZE;
+ lvaIncrementFrameSize(TARGET_POINTER_SIZE);
+ stkOffs -= TARGET_POINTER_SIZE;
+ }
+ // We should now have a double-aligned (stkOffs+preSpillSize)
+ noway_assert(((stkOffs + preSpillSize) % (2 * TARGET_POINTER_SIZE)) == 0);
+ }
+
+ spillTempSize += size;
+ lvaIncrementFrameSize(size);
+ stkOffs -= size;
+ temp->tdSetTempOffs(stkOffs);
+ }
+#ifdef _TARGET_ARM_
+ // Only required for the ARM platform that we have an accurate estimate for the spillTempSize
+ noway_assert(spillTempSize <= lvaGetMaxSpillTempSize());
+#endif
+
+ /* If we've only assigned some temps, go back and do the rest now */
+
+ if (!assignDone)
+ {
+ assignNptr = !assignNptr;
+ assignPtrs = !assignPtrs;
+ assignDone = true;
+
+ goto AGAIN2;
+ }
+ }
+ else // We haven't run codegen, so there are no Spill temps yet!
+ {
+ unsigned size = lvaGetMaxSpillTempSize();
+
+ lvaIncrementFrameSize(size);
+ stkOffs -= size;
+ }
+
+ return stkOffs;
+}
+
+#ifdef DEBUG
+
+/*****************************************************************************
+ *
+ * Dump the register a local is in right now.
+ * For non-LSRA, this will be the register it is always in. For LSRA, it's only the current
+ * location, since the location changes and it is updated throughout code generation based on
+ * LSRA register assignments.
+ */
+
+void Compiler::lvaDumpRegLocation(unsigned lclNum)
+{
+ LclVarDsc* varDsc = lvaTable + lclNum;
+ var_types type = varDsc->TypeGet();
+
+#if FEATURE_STACK_FP_X87
+ if (varTypeIsFloating(type))
+ {
+ printf("fpu stack ");
+ }
+ else
+#endif
+ if (isRegPairType(type))
+ {
+ if (!doLSRA())
+ {
+ noway_assert(varDsc->lvRegNum != REG_STK);
+ }
+ if (doLSRA() && varDsc->lvRegNum == REG_STK)
+ {
+ /* Hi-only enregistered long */
+ int offset = varDsc->lvStkOffs;
+ printf("%-3s:[%1s0x%02X]",
+ getRegName(varDsc->lvOtherReg), // hi32
+ (offset < 0 ? "-" : "+"), (offset < 0 ? -offset : offset));
+ }
+ else if (varDsc->lvOtherReg != REG_STK)
+ {
+ /* Fully enregistered long */
+ printf("%3s:%-3s ",
+ getRegName(varDsc->lvOtherReg), // hi32
+ getRegName(varDsc->lvRegNum)); // lo32
+ }
+ else
+ {
+ /* Partially enregistered long */
+ int offset = varDsc->lvStkOffs + 4;
+ printf("[%1s0x%02X]:%-3s", (offset < 0 ? "-" : "+"), (offset < 0 ? -offset : offset),
+ getRegName(varDsc->lvRegNum)); // lo32
+ }
+ }
+#ifdef _TARGET_ARM_
+ else if (varDsc->TypeGet() == TYP_DOUBLE)
+ {
+ printf("%3s:%-3s ", getRegName(varDsc->lvRegNum), getRegName(varDsc->lvOtherReg));
+ }
+#endif
+ else
+ {
+ printf("%3s ", getRegName(varDsc->lvRegNum));
+ }
+}
+
+/*****************************************************************************
+ *
+ * Dump the frame location assigned to a local.
+ * For non-LSRA, this will only be valid if there is no assigned register.
+ * For LSRA, it's the home location, even though the variable doesn't always live
+ * in its home location.
+ */
+
+void Compiler::lvaDumpFrameLocation(unsigned lclNum)
+{
+ int offset;
+ regNumber baseReg;
+
+#ifdef _TARGET_ARM_
+ offset = lvaFrameAddress(lclNum, compLocallocUsed, &baseReg, 0);
+#else
+ bool EBPbased;
+ offset = lvaFrameAddress(lclNum, &EBPbased);
+ baseReg = EBPbased ? REG_FPBASE : REG_SPBASE;
+#endif
+
+ printf("[%2s%1s0x%02X] ", getRegName(baseReg), (offset < 0 ? "-" : "+"), (offset < 0 ? -offset : offset));
+}
+
+/*****************************************************************************
+ *
+ * dump a single lvaTable entry
+ */
+
+void Compiler::lvaDumpEntry(unsigned lclNum, FrameLayoutState curState, size_t refCntWtdWidth)
+{
+ LclVarDsc* varDsc = lvaTable + lclNum;
+ var_types type = varDsc->TypeGet();
+
+ if (curState == INITIAL_FRAME_LAYOUT)
+ {
+ printf("; ");
+ gtDispLclVar(lclNum);
+
+ printf(" %7s ", varTypeName(type));
+ if (genTypeSize(type) == 0)
+ {
+ printf("(%2d) ", lvaLclSize(lclNum));
+ }
+ }
+ else
+ {
+ if (varDsc->lvRefCnt == 0)
+ {
+ // Print this with a special indicator that the variable is unused. Even though the
+ // variable itself is unused, it might be a struct that is promoted, so seeing it
+ // can be useful when looking at the promoted struct fields. It's also weird to see
+ // missing var numbers if these aren't printed.
+ printf(";* ");
+ }
+ else
+#if FEATURE_FIXED_OUT_ARGS
+ if ((lclNum == lvaOutgoingArgSpaceVar) && (lvaLclSize(lclNum) == 0))
+ {
+ // Similar to above; print this anyway.
+ printf(";# ");
+ }
+ else
+#endif
+ {
+ printf("; ");
+ }
+
+ gtDispLclVar(lclNum);
+
+ printf("[V%02u", lclNum);
+ if (varDsc->lvTracked)
+ {
+ printf(",T%02u]", varDsc->lvVarIndex);
+ }
+ else
+ {
+ printf(" ]");
+ }
+
+ printf(" (%3u,%*s)", varDsc->lvRefCnt, (int)refCntWtdWidth, refCntWtd2str(varDsc->lvRefCntWtd));
+
+ printf(" %7s ", varTypeName(type));
+ if (genTypeSize(type) == 0)
+ {
+ printf("(%2d) ", lvaLclSize(lclNum));
+ }
+ else
+ {
+ printf(" -> ");
+ }
+
+ // The register or stack location field is 11 characters wide.
+ if (varDsc->lvRefCnt == 0)
+ {
+ printf("zero-ref ");
+ }
+ else if (varDsc->lvRegister != 0)
+ {
+ // It's always a register, and always in the same register.
+ lvaDumpRegLocation(lclNum);
+ }
+ else if (varDsc->lvOnFrame == 0)
+ {
+ printf("registers ");
+ }
+ else
+ {
+ // For RyuJIT backend, it might be in a register part of the time, but it will definitely have a stack home
+ // location. Otherwise, it's always on the stack.
+ if (lvaDoneFrameLayout != NO_FRAME_LAYOUT)
+ {
+ lvaDumpFrameLocation(lclNum);
+ }
+ }
+ }
+
+ if (varDsc->lvIsHfaRegArg())
+ {
+ if (varDsc->lvHfaTypeIsFloat())
+ {
+ printf(" (enregistered HFA: float) ");
+ }
+ else
+ {
+ printf(" (enregistered HFA: double)");
+ }
+ }
+
+ if (varDsc->lvDoNotEnregister)
+ {
+ printf(" do-not-enreg[");
+ if (varDsc->lvAddrExposed)
+ {
+ printf("X");
+ }
+ if (varTypeIsStruct(varDsc))
+ {
+ printf("S");
+ }
+ if (varDsc->lvVMNeedsStackAddr)
+ {
+ printf("V");
+ }
+ if (varDsc->lvLiveInOutOfHndlr)
+ {
+ printf("H");
+ }
+ if (varDsc->lvLclFieldExpr)
+ {
+ printf("F");
+ }
+ if (varDsc->lvLclBlockOpAddr)
+ {
+ printf("B");
+ }
+ if (varDsc->lvLiveAcrossUCall)
+ {
+ printf("U");
+ }
+ if (varDsc->lvIsMultiRegArg)
+ {
+ printf("A");
+ }
+ if (varDsc->lvIsMultiRegRet)
+ {
+ printf("R");
+ }
+#ifdef JIT32_GCENCODER
+ if (varDsc->lvPinned)
+ printf("P");
+#endif // JIT32_GCENCODER
+ printf("]");
+ }
+
+ if (varDsc->lvIsMultiRegArg)
+ {
+ printf(" multireg-arg");
+ }
+ if (varDsc->lvIsMultiRegRet)
+ {
+ printf(" multireg-ret");
+ }
+ if (varDsc->lvMustInit)
+ {
+ printf(" must-init");
+ }
+ if (varDsc->lvAddrExposed)
+ {
+ printf(" addr-exposed");
+ }
+ if (varDsc->lvHasLdAddrOp)
+ {
+ printf(" ld-addr-op");
+ }
+ if (varDsc->lvVerTypeInfo.IsThisPtr())
+ {
+ printf(" this");
+ }
+ if (varDsc->lvPinned)
+ {
+ printf(" pinned");
+ }
+ if (varDsc->lvRefAssign)
+ {
+ printf(" ref-asgn");
+ }
+ if (varDsc->lvStackByref)
+ {
+ printf(" stack-byref");
+ }
+#ifndef _TARGET_64BIT_
+ if (varDsc->lvStructDoubleAlign)
+ printf(" double-align");
+#endif // !_TARGET_64BIT_
+ if (varDsc->lvOverlappingFields)
+ {
+ printf(" overlapping-fields");
+ }
+
+ if (compGSReorderStackLayout && !varDsc->lvRegister)
+ {
+ if (varDsc->lvIsPtr)
+ {
+ printf(" ptr");
+ }
+ if (varDsc->lvIsUnsafeBuffer)
+ {
+ printf(" unsafe-buffer");
+ }
+ }
+ if (varDsc->lvIsStructField)
+ {
+ LclVarDsc* parentvarDsc = &lvaTable[varDsc->lvParentLcl];
+#if !defined(_TARGET_64BIT_)
+ if (varTypeIsLong(parentvarDsc))
+ {
+ bool isLo = (lclNum == parentvarDsc->lvFieldLclStart);
+ printf(" V%02u.%s(offs=0x%02x)", varDsc->lvParentLcl, isLo ? "lo" : "hi", isLo ? 0 : genTypeSize(TYP_INT));
+ }
+ else
+#endif // !defined(_TARGET_64BIT_)
+ {
+ CORINFO_CLASS_HANDLE typeHnd = parentvarDsc->lvVerTypeInfo.GetClassHandle();
+ CORINFO_FIELD_HANDLE fldHnd = info.compCompHnd->getFieldInClass(typeHnd, varDsc->lvFldOrdinal);
+
+ printf(" V%02u.%s(offs=0x%02x)", varDsc->lvParentLcl, eeGetFieldName(fldHnd), varDsc->lvFldOffset);
+
+ lvaPromotionType promotionType = lvaGetPromotionType(parentvarDsc);
+ // We should never have lvIsStructField set if it is a reg-sized non-field-addressed struct.
+ assert(!varDsc->lvRegStruct);
+ switch (promotionType)
+ {
+ case PROMOTION_TYPE_NONE:
+ printf(" P-NONE");
+ break;
+ case PROMOTION_TYPE_DEPENDENT:
+ printf(" P-DEP");
+ break;
+ case PROMOTION_TYPE_INDEPENDENT:
+ printf(" P-INDEP");
+ break;
+ }
+ }
+ }
+
+ printf("\n");
+}
+
+/*****************************************************************************
+*
+* dump the lvaTable
+*/
+
+void Compiler::lvaTableDump(FrameLayoutState curState)
+{
+ if (curState == NO_FRAME_LAYOUT)
+ {
+ curState = lvaDoneFrameLayout;
+ if (curState == NO_FRAME_LAYOUT)
+ {
+ // Still no layout? Could be a bug, but just display the initial layout
+ curState = INITIAL_FRAME_LAYOUT;
+ }
+ }
+
+ if (curState == INITIAL_FRAME_LAYOUT)
+ {
+ printf("; Initial");
+ }
+ else if (curState == PRE_REGALLOC_FRAME_LAYOUT)
+ {
+ printf("; Pre-RegAlloc");
+ }
+ else if (curState == REGALLOC_FRAME_LAYOUT)
+ {
+ printf("; RegAlloc");
+ }
+ else if (curState == TENTATIVE_FRAME_LAYOUT)
+ {
+ printf("; Tentative");
+ }
+ else if (curState == FINAL_FRAME_LAYOUT)
+ {
+ printf("; Final");
+ }
+ else
+ {
+ printf("UNKNOWN FrameLayoutState!");
+ unreached();
+ }
+
+ printf(" local variable assignments\n");
+ printf(";\n");
+
+ unsigned lclNum;
+ LclVarDsc* varDsc;
+
+ // Figure out some sizes, to help line things up
+
+ size_t refCntWtdWidth = 6; // Use 6 as the minimum width
+
+ if (curState != INITIAL_FRAME_LAYOUT) // don't need this info for INITIAL_FRAME_LAYOUT
+ {
+ for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ size_t width = strlen(refCntWtd2str(varDsc->lvRefCntWtd));
+ if (width > refCntWtdWidth)
+ {
+ refCntWtdWidth = width;
+ }
+ }
+ }
+
+ // Do the actual output
+
+ for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ lvaDumpEntry(lclNum, curState, refCntWtdWidth);
+ }
+
+ //-------------------------------------------------------------------------
+ // Display the code-gen temps
+
+ assert(tmpAllFree());
+ for (TempDsc* temp = tmpListBeg(); temp != nullptr; temp = tmpListNxt(temp))
+ {
+ printf("; TEMP_%02u %26s%*s%7s -> ", -temp->tdTempNum(), " ", refCntWtdWidth, " ",
+ varTypeName(temp->tdTempType()));
+ int offset = temp->tdTempOffs();
+ printf(" [%2s%1s0x%02X]\n", isFramePointerUsed() ? STR_FPBASE : STR_SPBASE, (offset < 0 ? "-" : "+"),
+ (offset < 0 ? -offset : offset));
+ }
+
+ if (curState >= TENTATIVE_FRAME_LAYOUT)
+ {
+ printf(";\n");
+ printf("; Lcl frame size = %d\n", compLclFrameSize);
+ }
+}
+#endif // DEBUG
+
+/*****************************************************************************
+ *
+ * Conservatively estimate the layout of the stack frame.
+ *
+ * This function is only used before final frame layout. It conservatively estimates the
+ * number of callee-saved registers that must be saved, then calls lvaAssignFrameOffsets().
+ * To do final frame layout, the callee-saved registers are known precisely, so
+ * lvaAssignFrameOffsets() is called directly.
+ *
+ * Returns the (conservative, that is, overly large) estimated size of the frame,
+ * including the callee-saved registers. This is only used by the emitter during code
+ * generation when estimating the size of the offset of instructions accessing temps,
+ * and only if temps have a larger offset than variables.
+ */
+
+unsigned Compiler::lvaFrameSize(FrameLayoutState curState)
+{
+ assert(curState < FINAL_FRAME_LAYOUT);
+
+ unsigned result;
+
+ /* Layout the stack frame conservatively.
+ Assume all callee-saved registers are spilled to stack */
+
+ compCalleeRegsPushed = CNT_CALLEE_SAVED;
+
+#if defined(_TARGET_ARMARCH_)
+ if (compFloatingPointUsed)
+ compCalleeRegsPushed += CNT_CALLEE_SAVED_FLOAT;
+
+ compCalleeRegsPushed++; // we always push LR. See genPushCalleeSavedRegisters
+#elif defined(_TARGET_AMD64_)
+ if (compFloatingPointUsed)
+ {
+ compCalleeFPRegsSavedMask = RBM_FLT_CALLEE_SAVED;
+ }
+ else
+ {
+ compCalleeFPRegsSavedMask = RBM_NONE;
+ }
+#endif
+
+#if DOUBLE_ALIGN
+ if (genDoubleAlign())
+ {
+ // X86 only - account for extra 4-byte pad that may be created by "and esp, -8" instruction
+ compCalleeRegsPushed++;
+ }
+#endif
+
+#ifdef _TARGET_XARCH_
+ // Since FP/EBP is included in the SAVED_REG_MAXSZ we need to
+ // subtract 1 register if codeGen->isFramePointerUsed() is true.
+ if (codeGen->isFramePointerUsed())
+ {
+ compCalleeRegsPushed--;
+ }
+#endif
+
+ lvaAssignFrameOffsets(curState);
+
+ unsigned calleeSavedRegMaxSz = CALLEE_SAVED_REG_MAXSZ;
+#if defined(_TARGET_ARMARCH_)
+ if (compFloatingPointUsed)
+ {
+ calleeSavedRegMaxSz += CALLEE_SAVED_FLOAT_MAXSZ;
+ }
+ calleeSavedRegMaxSz += REGSIZE_BYTES; // we always push LR. See genPushCalleeSavedRegisters
+#endif
+
+ result = compLclFrameSize + calleeSavedRegMaxSz;
+ return result;
+}
+
+//------------------------------------------------------------------------
+// lvaGetSPRelativeOffset: Given a variable, return the offset of that
+// variable in the frame from the stack pointer. This number will be positive,
+// since the stack pointer must be at a lower address than everything on the
+// stack.
+//
+// This can't be called for localloc functions, since the stack pointer
+// varies, and thus there is no fixed offset to a variable from the stack pointer.
+//
+// Arguments:
+// varNum - the variable number
+//
+// Return Value:
+// The offset.
+
+int Compiler::lvaGetSPRelativeOffset(unsigned varNum)
+{
+ assert(!compLocallocUsed);
+ assert(lvaDoneFrameLayout == FINAL_FRAME_LAYOUT);
+ assert(varNum < lvaCount);
+ const LclVarDsc* varDsc = lvaTable + varNum;
+ assert(varDsc->lvOnFrame);
+ int spRelativeOffset;
+
+ if (varDsc->lvFramePointerBased)
+ {
+ // The stack offset is relative to the frame pointer, so convert it to be
+ // relative to the stack pointer (which makes no sense for localloc functions).
+ spRelativeOffset = varDsc->lvStkOffs + codeGen->genSPtoFPdelta();
+ }
+ else
+ {
+ spRelativeOffset = varDsc->lvStkOffs;
+ }
+
+ assert(spRelativeOffset >= 0);
+ return spRelativeOffset;
+}
+
+/*****************************************************************************
+ *
+ * Return the caller-SP-relative stack offset of a local/parameter.
+ * Requires the local to be on the stack and frame layout to be complete.
+ */
+
+int Compiler::lvaGetCallerSPRelativeOffset(unsigned varNum)
+{
+ assert(lvaDoneFrameLayout == FINAL_FRAME_LAYOUT);
+ assert(varNum < lvaCount);
+ LclVarDsc* varDsc = lvaTable + varNum;
+ assert(varDsc->lvOnFrame);
+
+ return lvaToCallerSPRelativeOffset(varDsc->lvStkOffs, varDsc->lvFramePointerBased);
+}
+
+int Compiler::lvaToCallerSPRelativeOffset(int offset, bool isFpBased)
+{
+ assert(lvaDoneFrameLayout == FINAL_FRAME_LAYOUT);
+
+ if (isFpBased)
+ {
+ offset += codeGen->genCallerSPtoFPdelta();
+ }
+ else
+ {
+ offset += codeGen->genCallerSPtoInitialSPdelta();
+ }
+
+ return offset;
+}
+
+/*****************************************************************************
+ *
+ * Return the Initial-SP-relative stack offset of a local/parameter.
+ * Requires the local to be on the stack and frame layout to be complete.
+ */
+
+int Compiler::lvaGetInitialSPRelativeOffset(unsigned varNum)
+{
+ assert(lvaDoneFrameLayout == FINAL_FRAME_LAYOUT);
+ assert(varNum < lvaCount);
+ LclVarDsc* varDsc = lvaTable + varNum;
+ assert(varDsc->lvOnFrame);
+
+ return lvaToInitialSPRelativeOffset(varDsc->lvStkOffs, varDsc->lvFramePointerBased);
+}
+
+// Given a local variable offset, and whether that offset is frame-pointer based, return its offset from Initial-SP.
+// This is used, for example, to figure out the offset of the frame pointer from Initial-SP.
+int Compiler::lvaToInitialSPRelativeOffset(unsigned offset, bool isFpBased)
+{
+ assert(lvaDoneFrameLayout == FINAL_FRAME_LAYOUT);
+#ifdef _TARGET_AMD64_
+ if (isFpBased)
+ {
+ // Currently, the frame starts by pushing ebp, ebp points to the saved ebp
+ // (so we have ebp pointer chaining). Add the fixed-size frame size plus the
+ // size of the callee-saved regs (not including ebp itself) to find Initial-SP.
+
+ assert(codeGen->isFramePointerUsed());
+ offset += codeGen->genSPtoFPdelta();
+ }
+ else
+ {
+ // The offset is correct already!
+ }
+#else // !_TARGET_AMD64_
+ NYI("lvaToInitialSPRelativeOffset");
+#endif // !_TARGET_AMD64_
+
+ return offset;
+}
+
+/*****************************************************************************/
+
+#ifdef DEBUG
+/*****************************************************************************
+ * Pick a padding size at "random" for the local.
+ * 0 means that it should not be converted to a GT_LCL_FLD
+ */
+
+static unsigned LCL_FLD_PADDING(unsigned lclNum)
+{
+ // Convert every 2nd variable
+ if (lclNum % 2)
+ {
+ return 0;
+ }
+
+ // Pick a padding size at "random"
+ unsigned size = lclNum % 7;
+
+ return size;
+}
+
+/*****************************************************************************
+ *
+ * Callback for fgWalkAllTreesPre()
+ * Convert as many GT_LCL_VAR's to GT_LCL_FLD's
+ */
+
+/* static */
+/*
+ The stress mode does 2 passes.
+
+ In the first pass we will mark the locals where we CAN't apply the stress mode.
+ In the second pass we will do the appropiate morphing wherever we've not determined we can't do it.
+*/
+Compiler::fgWalkResult Compiler::lvaStressLclFldCB(GenTreePtr* pTree, fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+ genTreeOps oper = tree->OperGet();
+ GenTreePtr lcl;
+
+ switch (oper)
+ {
+ case GT_LCL_VAR:
+ lcl = tree;
+ break;
+
+ case GT_ADDR:
+ if (tree->gtOp.gtOp1->gtOper != GT_LCL_VAR)
+ {
+ return WALK_CONTINUE;
+ }
+ lcl = tree->gtOp.gtOp1;
+ break;
+
+ default:
+ return WALK_CONTINUE;
+ }
+
+ Compiler* pComp = ((lvaStressLclFldArgs*)data->pCallbackData)->m_pCompiler;
+ bool bFirstPass = ((lvaStressLclFldArgs*)data->pCallbackData)->m_bFirstPass;
+ noway_assert(lcl->gtOper == GT_LCL_VAR);
+ unsigned lclNum = lcl->gtLclVarCommon.gtLclNum;
+ var_types type = lcl->TypeGet();
+ LclVarDsc* varDsc = &pComp->lvaTable[lclNum];
+
+ if (varDsc->lvNoLclFldStress)
+ {
+ // Already determined we can't do anything for this var
+ return WALK_SKIP_SUBTREES;
+ }
+
+ if (bFirstPass)
+ {
+ // Ignore arguments and temps
+ if (varDsc->lvIsParam || lclNum >= pComp->info.compLocalsCount)
+ {
+ varDsc->lvNoLclFldStress = true;
+ return WALK_SKIP_SUBTREES;
+ }
+
+ // Fix for lcl_fld stress mode
+ if (varDsc->lvKeepType)
+ {
+ varDsc->lvNoLclFldStress = true;
+ return WALK_SKIP_SUBTREES;
+ }
+
+ // Can't have GC ptrs in TYP_BLK.
+ if (!varTypeIsArithmetic(type))
+ {
+ varDsc->lvNoLclFldStress = true;
+ return WALK_SKIP_SUBTREES;
+ }
+
+ // Weed out "small" types like TYP_BYTE as we don't mark the GT_LCL_VAR
+ // node with the accurate small type. If we bash lvaTable[].lvType,
+ // then there will be no indication that it was ever a small type.
+ var_types varType = varDsc->TypeGet();
+ if (varType != TYP_BLK && genTypeSize(varType) != genTypeSize(genActualType(varType)))
+ {
+ varDsc->lvNoLclFldStress = true;
+ return WALK_SKIP_SUBTREES;
+ }
+
+ // Offset some of the local variable by a "random" non-zero amount
+ unsigned padding = LCL_FLD_PADDING(lclNum);
+ if (padding == 0)
+ {
+ varDsc->lvNoLclFldStress = true;
+ return WALK_SKIP_SUBTREES;
+ }
+ }
+ else
+ {
+ // Do the morphing
+ noway_assert(varDsc->lvType == lcl->gtType || varDsc->lvType == TYP_BLK);
+ var_types varType = varDsc->TypeGet();
+
+ // Calculate padding
+ unsigned padding = LCL_FLD_PADDING(lclNum);
+
+ // Change the variable to a TYP_BLK
+ if (varType != TYP_BLK)
+ {
+ varDsc->lvExactSize = (unsigned)(roundUp(padding + pComp->lvaLclSize(lclNum)));
+ varDsc->lvType = TYP_BLK;
+ pComp->lvaSetVarAddrExposed(lclNum);
+ }
+
+ tree->gtFlags |= GTF_GLOB_REF;
+
+ /* Now morph the tree appropriately */
+ if (oper == GT_LCL_VAR)
+ {
+ /* Change lclVar(lclNum) to lclFld(lclNum,padding) */
+
+ tree->ChangeOper(GT_LCL_FLD);
+ tree->gtLclFld.gtLclOffs = padding;
+ }
+ else
+ {
+ /* Change addr(lclVar) to addr(lclVar)+padding */
+
+ noway_assert(oper == GT_ADDR);
+ GenTreePtr newAddr = new (pComp, GT_NONE) GenTreeOp(*tree->AsOp());
+
+ tree->ChangeOper(GT_ADD);
+ tree->gtOp.gtOp1 = newAddr;
+ tree->gtOp.gtOp2 = pComp->gtNewIconNode(padding);
+
+ lcl->gtType = TYP_BLK;
+ }
+ }
+
+ return WALK_SKIP_SUBTREES;
+}
+
+/*****************************************************************************/
+
+void Compiler::lvaStressLclFld()
+{
+ if (!compStressCompile(STRESS_LCL_FLDS, 5))
+ {
+ return;
+ }
+
+ lvaStressLclFldArgs Args;
+ Args.m_pCompiler = this;
+ Args.m_bFirstPass = true;
+
+ // Do First pass
+ fgWalkAllTreesPre(lvaStressLclFldCB, &Args);
+
+ // Second pass
+ Args.m_bFirstPass = false;
+ fgWalkAllTreesPre(lvaStressLclFldCB, &Args);
+}
+
+#endif // DEBUG
+
+/*****************************************************************************
+ *
+ * A little routine that displays a local variable bitset.
+ * 'set' is mask of variables that have to be displayed
+ * 'allVars' is the complete set of interesting variables (blank space is
+ * inserted if its corresponding bit is not in 'set').
+ */
+
+#ifdef DEBUG
+void Compiler::lvaDispVarSet(VARSET_VALARG_TP set)
+{
+ VARSET_TP VARSET_INIT_NOCOPY(allVars, VarSetOps::MakeEmpty(this));
+ lvaDispVarSet(set, allVars);
+}
+
+void Compiler::lvaDispVarSet(VARSET_VALARG_TP set, VARSET_VALARG_TP allVars)
+{
+ printf("{");
+
+ bool needSpace = false;
+
+ for (unsigned index = 0; index < lvaTrackedCount; index++)
+ {
+ if (VarSetOps::IsMember(this, set, index))
+ {
+ unsigned lclNum;
+ LclVarDsc* varDsc;
+
+ /* Look for the matching variable */
+
+ for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
+ {
+ if ((varDsc->lvVarIndex == index) && varDsc->lvTracked)
+ {
+ break;
+ }
+ }
+
+ if (needSpace)
+ {
+ printf(" ");
+ }
+ else
+ {
+ needSpace = true;
+ }
+
+ printf("V%02u", lclNum);
+ }
+ else if (VarSetOps::IsMember(this, allVars, index))
+ {
+ if (needSpace)
+ {
+ printf(" ");
+ }
+ else
+ {
+ needSpace = true;
+ }
+
+ printf(" ");
+ }
+ }
+
+ printf("}");
+}
+
+#endif // DEBUG