Permalink
Fetching contributors…
Cannot retrieve contributors at this time
7461 lines (6436 sloc) 253 KB
// 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 */
lvaRefCountState = RCS_INVALID;
lvaGenericsContextUseCount = 0;
lvaTrackedToVarNum = nullptr;
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;
lvaOutgoingArgSpaceSize = PhasedVar<unsigned>();
#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;
}
/*****************************************************************************/
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);
// We can safely widen the return type for enclosed structs.
if ((howToReturnStruct == SPK_PrimitiveType) || (howToReturnStruct == SPK_EnclosingType))
{
assert(returnType != TYP_UNKNOWN);
assert(!varTypeIsStruct(returnType));
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 = getAllocator(CMK_LvaTable).allocate<LclVarDsc>(lvaTableCnt);
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 corInfoTypeWithMod =
info.compCompHnd->getArgType(&info.compMethodInfo->locals, localsSig, &typeHnd);
CorInfoType corInfoType = strip(corInfoTypeWithMod);
lvaInitVarDsc(varDsc, varNum, corInfoType, typeHnd, localsSig, &info.compMethodInfo->locals);
varDsc->lvPinned = ((corInfoTypeWithMod & CORINFO_TYPE_MOD_PINNED) != 0);
varDsc->lvOnFrame = true; // The final home for this local variable might be our local stack frame
if (corInfoType == CORINFO_TYPE_CLASS)
{
CORINFO_CLASS_HANDLE clsHnd = info.compCompHnd->getArgClass(&info.compMethodInfo->locals, localsSig);
lvaSetClass(varNum, clsHnd);
}
}
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;
}
// Allocate the lvaOutgoingArgSpaceVar now because we can run into problems in the
// emitter when the varNum is greater that 32767 (see emitLclVarAddr::initLclVarAddr)
lvaAllocOutgoingArgSpaceVar();
#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;
codeGen->floatRegState.rsCalleeRegArgCount = varDscInfo->floatRegArgNum;
#if FEATURE_FASTTAILCALL
// Save the stack usage information
// We can get register usage information using codeGen->intRegState and
// codeGen->floatRegState
info.compArgStackSize = varDscInfo->stackArgSize;
#endif // FEATURE_FASTTAILCALL
// The total argument size must be aligned.
noway_assert((compArgSize % TARGET_POINTER_SIZE) == 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;
varDsc->lvExactSize = genTypeSize(type);
}
}
#endif // FEATURE_SIMD
}
else
{
varDsc->lvType = TYP_REF;
lvaSetClass(varDscInfo->varNum, info.compClassHnd);
}
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;
}
}
#ifdef FEATURE_SIMD
else if (featureSIMD && varTypeIsSIMD(info.compRetType))
{
varDsc->lvSIMDType = true;
varDsc->lvBaseType =
getBaseTypeAndSizeOfSIMDType(info.compMethodInfo->args.retTypeClass, &varDsc->lvExactSize);
assert(varDsc->lvBaseType != TYP_UNKNOWN);
}
#endif // FEATURE_SIMD
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);
if (strip(corInfoType) == CORINFO_TYPE_CLASS)
{
CORINFO_CLASS_HANDLE clsHnd = info.compCompHnd->getArgClass(&info.compMethodInfo->args, argLst);
lvaSetClass(varDscInfo->varNum, clsHnd);
}
// 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;
#if defined(_TARGET_ARM64_) && defined(_TARGET_UNIX_)
// Native varargs on arm64 unix use the regular calling convention.
if (!opts.compUseSoftFP)
#else
// Methods that use VarArg or SoftFP cannot have HFA arguments
if (!info.compIsVarArgs && !opts.compUseSoftFP)
#endif // defined(_TARGET_ARM64_) && defined(_TARGET_UNIX_)
{
// 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);
}
}
else if (info.compIsVarArgs)
{
#ifdef _TARGET_UNIX_
// Currently native varargs is not implemented on non windows targets.
//
// Note that some targets like Arm64 Unix should not need much work as
// the ABI is the same. While other targets may only need small changes
// such as amd64 Unix, which just expects RAX to pass numFPArguments.
NYI("InitUserArgs for Vararg callee is not yet implemented on non Windows targets.");
#endif
}
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;
#if defined(_TARGET_ARM64_) && FEATURE_ARG_SPLIT
// On arm64 Windows we will need to properly handle the case where a >8byte <=16byte
// struct is split between register r7 and virtual stack slot s[0]
// We will only do this for calls to vararg methods on Windows Arm64
//
// !!This does not affect the normal arm64 calling convention or Unix Arm64!!
if (this->info.compIsVarArgs && argType == TYP_STRUCT)
{
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
{
cSlotsToEnregister = 1; // Force the split
}
}
#endif // defined(_TARGET_ARM64_) && FEATURE_ARG_SPLIT
#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)
{
// TODO-Arm32-Windows: vararg struct should be forced to split like
// ARM64 above.
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 // !_TARGET_ARM_
#if defined(UNIX_AMD64_ABI)
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 // UNIX_AMD64_ABI
#endif // !_TARGET_ARM_
// 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;
bool canPassArgInRegisters = false;
#if defined(UNIX_AMD64_ABI)
if (varTypeIsStruct(argType))
{
canPassArgInRegisters = structDesc.passedInRegisters;
}
else
#endif // defined(UNIX_AMD64_ABI)
{
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(UNIX_AMD64_ABI)
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(UNIX_AMD64_ABI)
{
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(UNIX_AMD64_ABI)
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(UNIX_AMD64_ABI)
}
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(UNIX_AMD64_ABI)
if (varTypeIsStruct(argType) && (structDesc.eightByteCount >= 1))
{
isFloat = varTypeIsFloating(firstEightByteType);
}
else
#else
{
isFloat = varTypeIsFloating(argType);
}
#endif // !UNIX_AMD64_ABI
#if defined(UNIX_AMD64_ABI)
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(UNIX_AMD64_ABI)
{
isFloat = varTypeIsFloating(argType);
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_
#if FEATURE_FASTTAILCALL
varDscInfo->stackArgSize += (unsigned)roundUp(argSize, TARGET_POINTER_SIZE);
#endif // FEATURE_FASTTAILCALL
}
#ifdef UNIX_AMD64_ABI
// 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 // !UNIX_AMD64_ABI
compArgSize += argSize;
#endif // !UNIX_AMD64_ABI
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
}
else
{
// 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;
#if FEATURE_FASTTAILCALL
varDscInfo->stackArgSize += TARGET_POINTER_SIZE;
#endif // FEATURE_FASTTAILCALL
}
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
}
else
{
// 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;
#if FEATURE_FASTTAILCALL
varDscInfo->stackArgSize += TARGET_POINTER_SIZE;
#endif // FEATURE_FASTTAILCALL
}
/* 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);
if (info.compIsVarArgs)
{
lvaSetStructUsedAsVarArg(varNum);
}
}
else
{
varDsc->lvType = type;
}
#if OPT_BOOL_OPS
if (type == TYP_BOOL)
{
varDsc->lvIsBoolean = true;
}
#endif
#ifdef DEBUG
varDsc->lvStkOffs = BAD_STK_OFFS;
#endif
#if FEATURE_MULTIREG_ARGS
varDsc->lvOtherArgReg = REG_NA;
#endif // FEATURE_MULTIREG_ARGS
}
/*****************************************************************************
* 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;
}
#if FEATURE_FIXED_OUT_ARGS
if (varNum == lvaOutgoingArgSpaceVar)
{
return (unsigned)ICorDebugInfo::UNKNOWN_ILNUM; // Cannot be mapped
}
#endif // FEATURE_FIXED_OUT_ARGS
// 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.
// Note: MaxOffset is used below to declare a local array, and therefore must be a compile-time constant.
CLANG_FORMAT_COMMENT_ANCHOR;
#if defined(FEATURE_SIMD)
#if defined(_TARGET_XARCH_)
// This will allow promotion of 2 Vector<T> fields on AVX2, or 4 Vector<T> fields on SSE2.
const int MaxOffset = MAX_NumOfFieldsInPromotableStruct * XMM_REGSIZE_BYTES;
#elif defined(_TARGET_ARM64_)
const int MaxOffset = MAX_NumOfFieldsInPromotableStruct * FP_REGSIZE_BYTES;
#endif // defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_)
#else // !FEATURE_SIMD
const int MaxOffset = MAX_NumOfFieldsInPromotableStruct * sizeof(double);
#endif // !FEATURE_SIMD
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 leaves the inlining behavior the same as before while still
// performing extra struct promotion 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);
pFieldInfo->fldType = JITtype2varType(corType);
pFieldInfo->fldSize = genTypeSize(pFieldInfo->fldType);
#ifdef FEATURE_SIMD
// Check to see if this is a SIMD type.
// We will only check this if we have already found a SIMD type, which will be true if
// we have encountered any SIMD intrinsics.
if (usesSIMDTypes() && (pFieldInfo->fldSize == 0) && isSIMDorHWSIMDClass(pFieldInfo->fldTypeHnd))
{
unsigned simdSize;
var_types simdBaseType = getBaseTypeAndSizeOfSIMDType(pFieldInfo->fldTypeHnd, &simdSize);
if (simdBaseType != TYP_UNKNOWN)
{
pFieldInfo->fldType = getSIMDTypeForSize(simdSize);
pFieldInfo->fldSize = simdSize;
}
}
#endif // FEATURE_SIMD
if (pFieldInfo->fldSize == 0)
{
// Size of TYP_BLK, TYP_FUNC, TYP_VOID and TYP_STRUCT is zero.
// Early out if field type is other than TYP_STRUCT.
// This is a defensive check as we don't expect a struct to have
// fields of TYP_BLK, TYP_FUNC or TYP_VOID.
if (pFieldInfo->fldType != TYP_STRUCT)
{
return;
}
// Non-primitive struct field.
// Try to promote structs of single field of scalar types aligned at their
// natural boundary.
// Do Not promote if the struct field in turn has more than one field.
if (info.compCompHnd->getClassNumInstanceFields(pFieldInfo->fldTypeHnd) != 1)
{
return;
}
// Do not promote if the single field is not aligned at its natural boundary within
// the struct field.
CORINFO_FIELD_HANDLE fHnd = info.compCompHnd->getFieldInClass(pFieldInfo->fldTypeHnd, 0);
unsigned fOffset = info.compCompHnd->getFieldOffset(fHnd);
if (fOffset != 0)
{
return;
}
CORINFO_CLASS_HANDLE cHnd;
CorInfoType fieldCorType = info.compCompHnd->getFieldType(fHnd, &cHnd);
var_types fieldVarType = JITtype2varType(fieldCorType);
unsigned fieldSize = genTypeSize(fieldVarType);
// Do not promote if either not a primitive type or size equal to ptr size on
// target or a struct containing a single floating-point field.
//
// TODO-PERF: Structs containing a single floating-point field on Amd64
// needs to be passed in integer registers. Right now LSRA doesn't support
// passing of floating-point LCL_VARS in integer registers. Enabling promotion
// of such structs results in an assert in lsra right now.
//
// TODO-PERF: Right now promotion is confined to struct containing a ptr sized
// field (int/uint/ref/byref on 32-bits and long/ulong/ref/byref on 64-bits).
// Though this would serve the purpose of promoting Span<T> containing ByReference<T>,
// this can be extended to other primitive types as long as they are aligned at their
// natural boundary.
if (fieldSize == 0 || fieldSize != TARGET_POINTER_SIZE || varTypeIsFloating(fieldVarType))
{
return;
}
// Retype the field as the type of the single field of the struct
pFieldInfo->fldType = fieldVarType;
pFieldInfo->fldSize = fieldSize;
}
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;
}
if (varTypeIsGC(pFieldInfo->fldType))
{
containsGCpointers = true;
}
// 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 or by-ref fields above then CORINFO_FLG_CONTAINS_GC_PTR or
// CORINFO_FLG_CONTAINS_STACK_PTR has to be set!
noway_assert((containsGCpointers == false) ||
((typeFlags & (CORINFO_FLG_CONTAINS_GC_PTR | CORINFO_FLG_CONTAINS_STACK_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 :)
// 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())
{
JITDUMP(" struct promotion of V%02u is disabled because lvIsUsedInSIMDIntrinsic()\n", lclNum);
StructPromotionInfo->canPromote = false;
return;
}
// Reject struct promotion of parameters when -GS stack reordering is enabled
// as we could introduce shadow copies of them.
if (varDsc->lvIsParam && compGSReorderStackLayout)
{
JITDUMP(" struct promotion of V%02u is disabled because lvIsParam and compGSReorderStackLayout\n", lclNum);
StructPromotionInfo->canPromote = false;
return;
}
// 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.
//
// TODO-PERF - Allow struct promotion for HFA register arguments
if (varDsc->lvIsHfaRegArg())
{
JITDUMP(" struct promotion of V%02u is disabled because lvIsHfaRegArg()\n", lclNum);
StructPromotionInfo->canPromote = false;
return;
}
#if !FEATURE_MULTIREG_STRUCT_PROMOTE
if (varDsc->lvIsMultiRegArg)
{
JITDUMP(" struct promotion of V%02u is disabled because lvIsMultiRegArg\n", lclNum);
StructPromotionInfo->canPromote = false;
return;
}
#endif
if (varDsc->lvIsMultiRegRet)
{
JITDUMP(" struct promotion of V%02u is disabled because lvIsMultiRegRet\n", lclNum);
StructPromotionInfo->canPromote = false;
return;
}
CORINFO_CLASS_HANDLE typeHnd = varDsc->lvVerTypeInfo.GetClassHandle();
lvaCanPromoteStructType(typeHnd, StructPromotionInfo, true);
}
//--------------------------------------------------------------------------------------------
// lvaShouldPromoteStructVar - Should a struct var be promoted if it can be promoted?
// This routine mainly performs profitability checks. Right now it also has
// some correctness checks due to limitations of down-stream phases.
//
// Arguments:
// lclNum - Struct local number
// structPromotionInfo - In Parameter; struct promotion information
//
// Returns
// true if the struct should be promoted
bool Compiler::lvaShouldPromoteStructVar(unsigned lclNum, lvaStructPromotionInfo* structPromotionInfo)
{
assert(lclNum < lvaCount);
assert(structPromotionInfo->canPromote);
LclVarDsc* varDsc = &lvaTable[lclNum];
assert(varTypeIsStruct(varDsc));
bool shouldPromote = true;
// We *can* promote; *should* we promote?
// We should only do so if promotion has potential savings. One source of savings
// is if a field of the struct is accessed, since this access will be turned into
// an access of the corresponding promoted field variable. Even if there are no
// field accesses, but only block-level operations on the whole struct, if the struct
// has only one or two fields, then doing those block operations field-wise is probably faster
// than doing a whole-variable block operation (e.g., a hardware "copy loop" on x86).
// Struct promotion also provides the following benefits: reduce stack frame size,
// reduce the need for zero init of stack frame and fine grained constant/copy prop.
// Asm diffs indicate that promoting structs up to 3 fields is a net size win.
// So if no fields are accessed independently, and there are four or more fields,
// then do not promote.
//
// TODO: Ideally we would want to consider the impact of whether the struct is
// passed as a parameter or assigned the return value of a call. Because once promoted,
// struct copying is done by field by field assignment instead of a more efficient
// rep.stos or xmm reg based copy.
if (structPromotionInfo->fieldCnt > 3 && !varDsc->lvFieldAccessed)
{
JITDUMP("Not promoting promotable struct local V%02u: #fields = %d, fieldAccessed = %d.\n", lclNum,
structPromotionInfo->fieldCnt, varDsc->lvFieldAccessed);
shouldPromote = false;
}
#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) || defined(_TARGET_ARM_)
// TODO-PERF - Only do this when the LclVar is used in an argument context
// TODO-ARM64 - HFA support should also eliminate the need for this.
// TODO-ARM32 - HFA support should also eliminate the need for this.
// TODO-LSRA - Currently doesn't support the passing of floating point LCL_VARS in the integer registers
//
// For now we currently don't promote structs with a single float field
// Promoting it can cause us to shuffle it back and forth between the int and
// the float regs when it is used as a argument, which is very expensive for XARCH
//
else if ((structPromotionInfo->fieldCnt == 1) && varTypeIsFloating(structPromotionInfo->fields[0].fldType))
{
JITDUMP("Not promoting promotable struct local V%02u: #fields = %d because it is a struct with "
"single float field.\n",
lclNum, structPromotionInfo->fieldCnt);
shouldPromote = false;
}
#endif // _TARGET_AMD64_ || _TARGET_ARM64_ || _TARGET_ARM_
else if (varDsc->lvIsParam && !lvaIsImplicitByRefLocal(lclNum))
{
#if FEATURE_MULTIREG_STRUCT_PROMOTE
// Is this a variable holding a value with exactly two fields passed in
// multiple registers?
if ((structPromotionInfo->fieldCnt != 2) && lvaIsMultiregStruct(varDsc, this->info.compIsVarArgs))
{
JITDUMP("Not promoting multireg struct local V%02u, because lvIsParam is true and #fields != 2\n", lclNum);
shouldPromote = false;
}
else
#endif // !FEATURE_MULTIREG_STRUCT_PROMOTE
// TODO-PERF - Implement struct promotion for incoming multireg structs
// Currently it hits assert(lvFieldCnt==1) in lclvar.cpp line 4417
// Also the implementation of jmp uses the 4 byte move to store
// byte parameters to the stack, so that if we have a byte field
// with something else occupying the same 4-byte slot, it will
// overwrite other fields.
if (structPromotionInfo->fieldCnt != 1)
{
JITDUMP("Not promoting promotable struct local V%02u, because lvIsParam is true and #fields = "
"%d.\n",
lclNum, structPromotionInfo->fieldCnt);
shouldPromote = false;
}
}
//
// If the lvRefCnt is zero and we have a struct promoted parameter we can end up with an extra store of
// the the incoming register into the stack frame slot.
// In that case, we would like to avoid promortion.
// However we haven't yet computed the lvRefCnt values so we can't do that.
//
CLANG_FORMAT_COMMENT_ANCHOR;
return shouldPromote;
}
/*****************************************************************************
* 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) || varTypeIsSIMD(pFieldInfo->fldType))
{
// 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 must be long lifetime temps.
varDsc = &lvaTable[lclNum]; // lvaGrabTemp can reallocate the lvaTable
LclVarDsc* fieldVarDsc = &lvaTable[varNum];
fieldVarDsc->lvType = pFieldInfo->fldType;
fieldVarDsc->lvExactSize = pFieldInfo->fldSize;
fieldVarDsc->lvIsStructField = true;
fieldVarDsc->lvFieldHnd = pFieldInfo->fldHnd;
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
#if FEATURE_MULTIREG_ARGS && defined(FEATURE_SIMD)
if (varTypeIsSIMD(fieldVarDsc) && !lvaIsImplicitByRefLocal(lclNum))
{
// This field is a SIMD type, and will be considered to be passed in multiple registers
// if the parent struct was. Note that this code relies on the fact that if there is
// a SIMD field of an enregisterable struct, it is the only field.
// We will assert that, in case future changes are made to the ABI.
assert(varDsc->lvFieldCnt == 1);
fieldVarDsc->lvOtherArgReg = varDsc->lvOtherArgReg;
}
#endif // FEATURE_MULTIREG_ARGS && defined(FEATURE_SIMD)
lvaMarkRefsWeight = BB_UNITY_WEIGHT; // incRefCnts can use this compiler global variable
fieldVarDsc->incRefCnts(BB_UNITY_WEIGHT, this,
RCS_EARLY); // increment the ref count for prolog initialization
}
#endif
#ifdef FEATURE_SIMD
if (varTypeIsSIMD(pFieldInfo->fldType))
{
// Set size to zero so that lvaSetStruct will appropriately set the SIMD-relevant fields.
fieldVarDsc->lvExactSize = 0;
lvaSetStruct(varNum, pFieldInfo->fldTypeHnd, false, true);
}
#endif // FEATURE_SIMD
#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) || varDsc->lvIsStructField || (fgNoStructPromotion && varDsc->lvIsParam))
{
continue;
}
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;
// Currently we do not support enregistering incoming promoted aggregates with more than one field.
if (isParam)
{
fieldVarDsc->lvIsParam = true;
lvaSetVarDoNotEnregister(varNum DEBUGARG(DNER_LongParamField));
}
}
}
#ifdef DEBUG
if (verbose)
{
printf("\nlvaTable after lvaPromoteLongVars\n");
lvaTableDump();
}
#endif // DEBUG
}
#endif // !defined(_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_IsStructArg:
JITDUMP("it is a struct arg\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;
case DNER_DepField:
JITDUMP("field of a dependently promoted struct\n");
assert(varDsc->lvIsStructField && (lvaGetParentPromotionType(varNum) != PROMOTION_TYPE_INDEPENDENT));
break;
case DNER_NoRegVars:
JITDUMP("opts.compFlags & CLFLG_REGVAR is not set\n");
assert((opts.compFlags & CLFLG_REGVAR) == 0);
break;
case DNER_MinOptsGC:
JITDUMP("It is a GC Ref and we are compiling MinOpts\n");
assert(!JitConfig.JitMinOptsTrackGCrefs() && varTypeIsGC(varDsc->TypeGet()));
break;
#ifdef JIT32_GCENCODER
case DNER_PinningRef:
JITDUMP("pinning ref\n");
assert(varDsc->lvPinned);
break;
#endif
#if !defined(_TARGET_64BIT_)
case DNER_LongParamField:
JITDUMP("it is a decomposed field of a long parameter\n");
break;
#endif
default:
unreached();
break;
}
#endif
}
// Returns true if this local var is a multireg struct
bool Compiler::lvaIsMultiregStruct(LclVarDsc* varDsc, bool isVarArg)
{
if (varTypeIsStruct(varDsc->TypeGet()))
{
CORINFO_CLASS_HANDLE clsHnd = varDsc->lvVerTypeInfo.GetClassHandleForValueClass();
structPassingKind howToPassStruct;
var_types type = getArgTypeForStruct(clsHnd, &howToPassStruct, isVarArg, varDsc->lvExactSize);
if (howToPassStruct == SPK_ByValueAsHfa)
{
assert(type == TYP_STRUCT);
return true;
}
#if defined(UNIX_AMD64_ABI) || 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 % TARGET_POINTER_SIZE) ==
0); // The struct needs to be a multiple of TARGET_POINTER_SIZE bytes for getClassGClayout() to be valid.
varDsc->lvGcLayout = getAllocator(CMK_LvaTable).allocate<BYTE>(lvSize / TARGET_POINTER_SIZE);
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
{
#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;
}
}
//------------------------------------------------------------------------
// lvaSetStructUsedAsVarArg: update hfa information for vararg struct args
//
// Arguments:
// varNum -- number of the variable
//
// Notes:
// This only affects arm64 varargs on windows where we need to pass
// hfa arguments as if they are not HFAs.
//
// This function should only be called if the struct is used in a varargs
// method.
void Compiler::lvaSetStructUsedAsVarArg(unsigned varNum)
{
#if defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
LclVarDsc* varDsc = &lvaTable[varNum];
// For varargs methods incoming and outgoing arguments should not be treated
// as HFA.
varDsc->_lvIsHfa = false;
varDsc->_lvHfaTypeIsFloat = false;
#endif // defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
}
//------------------------------------------------------------------------
// lvaSetClass: set class information for a local var.
//
// Arguments:
// varNum -- number of the variable
// clsHnd -- class handle to use in set or update
// isExact -- true if class is known exactly
//
// Notes:
// varNum must not already have a ref class handle.
void Compiler::lvaSetClass(unsigned varNum, CORINFO_CLASS_HANDLE clsHnd, bool isExact)
{
noway_assert(varNum < lvaCount);
// If we are just importing, we cannot reliably track local ref types,
// since the jit maps CORINFO_TYPE_VAR to TYP_REF.
if (compIsForImportOnly())
{
return;
}
// Else we should have a type handle.
assert(clsHnd != nullptr);
LclVarDsc* varDsc = &lvaTable[varNum];
assert(varDsc->lvType == TYP_REF);
// We shoud not have any ref type information for this var.
assert(varDsc->lvClassHnd == nullptr);
assert(!varDsc->lvClassIsExact);
JITDUMP("\nlvaSetClass: setting class for V%02i to (%p) %s %s\n", varNum, dspPtr(clsHnd),
info.compCompHnd->getClassName(clsHnd), isExact ? " [exact]" : "");
varDsc->lvClassHnd = clsHnd;
varDsc->lvClassIsExact = isExact;
}
//------------------------------------------------------------------------
// lvaSetClass: set class information for a local var from a tree or stack type
//
// Arguments:
// varNum -- number of the variable. Must be a single def local
// tree -- tree establishing the variable's value
// stackHnd -- handle for the type from the evaluation stack
//
// Notes:
// Preferentially uses the tree's type, when available. Since not all
// tree kinds can track ref types, the stack type is used as a
// fallback.
void Compiler::lvaSetClass(unsigned varNum, GenTree* tree, CORINFO_CLASS_HANDLE stackHnd)
{
bool isExact = false;
bool isNonNull = false;
CORINFO_CLASS_HANDLE clsHnd = gtGetClassHandle(tree, &isExact, &isNonNull);
if (clsHnd != nullptr)
{
lvaSetClass(varNum, clsHnd, isExact);
}
else if (stackHnd != nullptr)
{
lvaSetClass(varNum, stackHnd);
}
}
//------------------------------------------------------------------------
// lvaUpdateClass: update class information for a local var.
//
// Arguments:
// varNum -- number of the variable
// clsHnd -- class handle to use in set or update
// isExact -- true if class is known exactly
//
// Notes:
//
// This method models the type update rule for an assignment.
//
// Updates currently should only happen for single-def user args or
// locals, when we are processing the expression actually being
// used to initialize the local (or inlined arg). The update will
// change the local from the declared type to the type of the
// initial value.
//
// These updates should always *improve* what we know about the
// type, that is making an inexact type exact, or changing a type
// to some subtype. However the jit lacks precise type information
// for shared code, so ensuring this is so is currently not
// possible.
void Compiler::lvaUpdateClass(unsigned varNum, CORINFO_CLASS_HANDLE clsHnd, bool isExact)
{
assert(varNum < lvaCount);
// If we are just importing, we cannot reliably track local ref types,
// since the jit maps CORINFO_TYPE_VAR to TYP_REF.
if (compIsForImportOnly())
{
return;
}
// Else we should have a class handle to consider
assert(clsHnd != nullptr);
LclVarDsc* varDsc = &lvaTable[varNum];
assert(varDsc->lvType == TYP_REF);
// We should already have a class
assert(varDsc->lvClassHnd != nullptr);
#if defined(DEBUG)
// In general we only expect one update per local var. However if
// a block is re-imported and that block has the only STLOC for
// the var, we may see multiple updates. All subsequent updates
// should agree on the type, since reimportation is triggered by
// type mismatches for things other than ref types.
if (varDsc->lvClassInfoUpdated)
{
assert(varDsc->lvClassHnd == clsHnd);
assert(varDsc->lvClassIsExact == isExact);
}
// This counts as an update, even if nothing changes.
varDsc->lvClassInfoUpdated = true;
#endif // defined(DEBUG)
// If previous type was exact, there is nothing to update. Would
// like to verify new type is compatible but can't do this yet.
if (varDsc->lvClassIsExact)
{
return;
}
// Are we updating the type?
if (varDsc->lvClassHnd != clsHnd)
{
JITDUMP("\nlvaUpdateClass: Updating class for V%02i from (%p) %s to (%p) %s %s\n", varNum,
dspPtr(varDsc->lvClassHnd), info.compCompHnd->getClassName(varDsc->lvClassHnd), dspPtr(clsHnd),
info.compCompHnd->getClassName(clsHnd), isExact ? " [exact]" : "");
varDsc->lvClassHnd = clsHnd;
varDsc->lvClassIsExact = isExact;
return;
}
// Class info matched. Are we updating exactness?
if (isExact)
{
JITDUMP("\nlvaUpdateClass: Updating class for V%02i (%p) %s to be exact\n", varNum, dspPtr(varDsc->lvClassHnd),
info.compCompHnd->getClassName(varDsc->lvClassHnd));
varDsc->lvClassIsExact = isExact;
return;
}
// Else we have the same handle and (in)exactness as before. Do nothing.
return;
}
//------------------------------------------------------------------------
// lvaUpdateClass: Uupdate class information for a local var from a tree
// or stack type
//
// Arguments:
// varNum -- number of the variable. Must be a single def local
// tree -- tree establishing the variable's value
// stackHnd -- handle for the type from the evaluation stack
//
// Notes:
// Preferentially uses the tree's type, when available. Since not all
// tree kinds can track ref types, the stack type is used as a
// fallback.
void Compiler::lvaUpdateClass(unsigned varNum, GenTree* tree, CORINFO_CLASS_HANDLE stackHnd)
{
bool isExact = false;
bool isNonNull = false;
CORINFO_CLASS_HANDLE clsHnd = gtGetClassHandle(tree, &isExact, &isNonNull);
if (clsHnd != nullptr)
{
lvaUpdateClass(varNum, clsHnd, isExact);
}
else if (stackHnd != nullptr)
{
lvaUpdateClass(varNum, stackHnd);
}
}
/*****************************************************************************
* Returns the array of BYTEs containing the GC layout information
*/
BYTE* Compiler::lvaGetGcLayout(unsigned varNum)
{
assert(varTypeIsStruct(lvaTable[varNum].lvType) && (lvaTable[varNum].lvExactSize >= TARGET_POINTER_SIZE));
return lvaTable[varNum].lvGcLayout;
}
//------------------------------------------------------------------------
// lvaLclSize: returns size of a local variable, in bytes
//
// Arguments:
// varNum -- variable to query
//
// Returns:
// Number of bytes needed on the frame for such a local.
unsigned Compiler::lvaLclSize(unsigned varNum)
{
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
// Note that this operation performs a read of a PhasedVar
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)
{
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
// Note that this operation performs a read of a PhasedVar
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);
}
// getCalledCount -- get the value used to normalized weights for this method
// if we don't have profile data then getCalledCount will return BB_UNITY_WEIGHT (100)
// otherwise it returns the number of times that profile data says the method was called.
//
BasicBlock::weight_t BasicBlock::getCalledCount(Compiler* comp)
{
// when we don't have profile data then fgCalledCount will be BB_UNITY_WEIGHT (100)
BasicBlock::weight_t calledCount = comp->fgCalledCount;
// If we haven't yet reach the place where we setup fgCalledCount it could still be zero
// so return a reasonable value to use until we set it.
//
if (calledCount == 0)
{
if (comp->fgIsUsingProfileWeights())
{
// When we use profile data block counts we have exact counts,
// not multiples of BB_UNITY_WEIGHT (100)
calledCount = 1;
}
else
{
calledCount = comp->fgFirstBB->bbWeight;
if (calledCount == 0)
{
calledCount = BB_UNITY_WEIGHT;
}
}
}
return calledCount;
}
// getBBWeight -- get the normalized weight of this block
BasicBlock::weight_t BasicBlock::getBBWeight(Compiler* comp)
{
if (this->bbWeight == 0)
{
return 0;
}
else
{
weight_t calledCount = getCalledCount(comp);
// Normalize the bbWeights by multiplying by BB_UNITY_WEIGHT and dividing by the calledCount.
//
// 1. For methods that do not have IBC data the called weight will always be 100 (BB_UNITY_WEIGHT)
// and the entry point bbWeight value is almost always 100 (BB_UNITY_WEIGHT)
// 2. For methods that do have IBC data the called weight is the actual number of calls
// from the IBC data and the entry point bbWeight value is almost always the actual
// number of calls from the IBC data.
//
// "almost always" - except for the rare case where a loop backedge jumps to BB01
//
// We also perform a rounding operation by adding half of the 'calledCount' before performing
// the division.
//
// Thus for both cases we will return 100 (BB_UNITY_WEIGHT) for the entry point BasicBlock
//
// Note that with a 100 (BB_UNITY_WEIGHT) values between 1 and 99 represent decimal fractions.
// (i.e. 33 represents 33% and 75 represents 75%, and values greater than 100 require
// some kind of loop backedge)
//
if (this->bbWeight < (BB_MAX_WEIGHT / BB_UNITY_WEIGHT))
{
// Calculate the result using unsigned arithmetic
weight_t result = ((this->bbWeight * BB_UNITY_WEIGHT) + (calledCount / 2)) / calledCount;
// We don't allow a value of zero, as that would imply rarely run
return max(1, result);
}
else
{
// Calculate the full result using floating point
double fullResult = ((double)this->bbWeight * (double)BB_UNITY_WEIGHT) / (double)calledCount;
if (fullResult < (double)BB_MAX_WEIGHT)
{
// Add 0.5 and truncate to unsigned
return (weight_t)(fullResult + 0.5);
}
else
{
return BB_MAX_WEIGHT;
}
}
}
}
// Decrement the ref counts for all locals contained in the tree and its children.
void Compiler::lvaRecursiveDecRefCounts(GenTree* 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
{
DecLclVarRefCountsVisitor::WalkTree(this, tree);
}
}
DecLclVarRefCountsVisitor::DecLclVarRefCountsVisitor(Compiler* compiler)
: GenTreeVisitor<DecLclVarRefCountsVisitor>(compiler)
{
}
Compiler::fgWalkResult DecLclVarRefCountsVisitor::PreOrderVisit(GenTree** use, GenTree* user)
{
m_compiler->lvaDecRefCnts(*use);
return fgWalkResult::WALK_CONTINUE;
}
Compiler::fgWalkResult DecLclVarRefCountsVisitor::WalkTree(Compiler* compiler, GenTree* tree)
{
DecLclVarRefCountsVisitor visitor(compiler);
return static_cast<GenTreeVisitor<DecLclVarRefCountsVisitor>*>(&visitor)->WalkTree(&tree, nullptr);
}
/*****************************************************************************
*
* Helper passed to the tree walker to decrement the refCnts for
* all local variables in an expression
*/
void Compiler::lvaDecRefCnts(GenTree* tree)
{
assert(compCurBB != nullptr);
lvaDecRefCnts(compCurBB, tree);
}
void Compiler::lvaDecRefCnts(BasicBlock* block, GenTree* tree)
{
assert(block != nullptr);
assert(tree != nullptr);
unsigned lclNum;
LclVarDsc* varDsc;
noway_assert(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;
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;
assert(lclNum < lvaCount);
varDsc = lvaTable + lclNum;
/* Decrement its lvRefCnt and lvRefCntWtd */
varDsc->decRefCnts(block->getBBWeight(this), this);
}
}
// Increment the ref counts for all locals contained in the tree and its children.
void Compiler::lvaRecursiveIncRefCounts(GenTree* 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
{
IncLclVarRefCountsVisitor::WalkTree(this, tree);
}
}
IncLclVarRefCountsVisitor::IncLclVarRefCountsVisitor(Compiler* compiler)
: GenTreeVisitor<IncLclVarRefCountsVisitor>(compiler)
{
}
Compiler::fgWalkResult IncLclVarRefCountsVisitor::PreOrderVisit(GenTree** use, GenTree* user)
{
m_compiler->lvaIncRefCnts(*use);
return fgWalkResult::WALK_CONTINUE;
}
Compiler::fgWalkResult IncLclVarRefCountsVisitor::WalkTree(Compiler* compiler, GenTree* tree)
{
IncLclVarRefCountsVisitor visitor(compiler);
return static_cast<GenTreeVisitor<IncLclVarRefCountsVisitor>*>(&visitor)->WalkTree(&tree, nullptr);
}
/*****************************************************************************
*
* Helper passed to the tree walker to increment the refCnts for
* all local variables in an expression
*/
void Compiler::lvaIncRefCnts(GenTree* tree)
{
unsigned lclNum;
LclVarDsc* varDsc;
noway_assert(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;
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;
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();
#ifndef _TARGET_ARM_
// ARM-TODO: this was disabled for ARM under !FEATURE_FP_REGALLOC; it was probably a left-over from
// legacy backend. It should be enabled and verified.
/* 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()))
{
weight2 += 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();
#ifndef _TARGET_ARM_
// ARM-TODO: this was disabled for ARM under !FEATURE_FP_REGALLOC; it was probably a left-over from
// legacy backend. It should be enabled and verified.
/* 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;
#ifdef DEBUG
VarSetOps::AssignNoCopy(this, lvaTrackedVars, VarSetOps::MakeEmpty(this));
#endif
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->setLvRefCntWtd(0);
}
#if !defined(_TARGET_64BIT_)
if (varTypeIsLong(varDsc) && varDsc->lvPromoted)
{
varDsc->lvTracked = 0;
}
#endif // !defined(_TARGET_64BIT_)
// 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
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_DepField));
}
else if (varDsc->lvPinned)
{
varDsc->lvTracked = 0;
#ifdef JIT32_GCENCODER
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_PinningRef));
#endif
}
else if (opts.MinOpts() && !JitConfig.JitMinOptsTrackGCrefs() && varTypeIsGC(varDsc->TypeGet()))
{
varDsc->lvTracked = 0;
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_MinOptsGC));
}
else if ((opts.compFlags & CLFLG_REGVAR) == 0)
{
lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_NoRegVars));
}
#if defined(JIT32_GCENCODER) && defined(WIN64EXCEPTIONS)
else if (lvaIsOriginalThisArg(lclNum) && (info.compMethodInfo->options & CORINFO_GENERICS_CTXT_FROM_THIS) != 0)
{
// For x86/Linux, we need to track "this".
// However we cannot have it in tracked variables, so we set "this" pointer always untracked
varDsc->lvTracked = 0;
}
#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;
}
}
if (lvaTrackedToVarNum == nullptr)
{
lvaTrackedToVarNum = new (getAllocator(CMK_LvaTable)) unsigned[lclMAX_TRACKED];
}
#ifdef DEBUG
// Re-Initialize to -1 for safety in debug build.
memset(lvaTrackedToVarNum, -1, lclMAX_TRACKED * sizeof(unsigned));
#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
/**********************************************************************************
* Get stack size of the varDsc.
*/
const size_t LclVarDsc::lvArgStackSize() const
{
// Make sure this will have a stack size
assert(!this->lvIsRegArg);
size_t stackSize = 0;
if (varTypeIsStruct(this))
{
#if defined(WINDOWS_AMD64_ABI)
// Structs are either passed by reference or can be passed by value using one pointer
stackSize = TARGET_POINTER_SIZE;
#elif defined(_TARGET_ARM64_) || defined(UNIX_AMD64_ABI)
// lvSize performs a roundup.
stackSize = this->lvSize();
#if defined(_TARGET_ARM64_)
if ((stackSize > TARGET_POINTER_SIZE * 2) && (!this->lvIsHfa()))
{
// If the size is greater than 16 bytes then it will
// be passed by reference.
stackSize = TARGET_POINTER_SIZE;
}
#endif // defined(_TARGET_ARM64_)
#else // !_TARGET_ARM64_ !WINDOWS_AMD64_ABI !UNIX_AMD64_ABI
NYI("Unsupported target.");
unreached();
#endif // !_TARGET_ARM64_ !WINDOWS_AMD64_ABI !UNIX_AMD64_ABI
}
else
{
stackSize = TARGET_POINTER_SIZE;
}
return stackSize;
}
/**********************************************************************************
* Get type of a variable when passed as an argument.
*/
var_types LclVarDsc::lvaArgType()
{
var_types type = TypeGet();
#ifdef _TARGET_AMD64_
#ifdef UNIX_AMD64_ABI
if (type == TYP_STRUCT)
{
NYI("lvaArgType");
}
#else //! UNIX_AMD64_ABI
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;
}
}
#endif // !UNIX_AMD64_ABI
#elif defined(_TARGET_ARM64_)
if (type == TYP_STRUCT)
{
NYI("lvaArgType");
}
#elif defined(_TARGET_X86_)
// Nothing to do; use the type as is.
#else
NYI("lvaArgType");
#endif //_TARGET_AMD64_
return type;
}
/*****************************************************************************
*
* This is called by lvaMarkLclRefsCallback() to do variable ref marking
*/
void Compiler::lvaMarkLclRefs(GenTree* 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->OperIsAssignment())
{
GenTree* op1 = tree->gtOp.gtOp1;
GenTree* 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;
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
}
#ifdef _TARGET_XARCH_
/* Special case: integer shift node by a variable amount */
if (tree->OperIsShiftOrRotate())
{
if (tree->gtType == TYP_INT)
{
GenTree* op2 = tree->gtOp.gtOp2;
if (op2->gtOper == GT_LCL_VAR)
{
unsigned lclNum = op2->gtLclVarCommon.gtLclNum;
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 */
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
if (fgDomsComputed && IsDominatedByExceptionalEntry(lvaMarkRefsCurBlock))
{
SetVolatileHint(varDsc);
}
/* 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);