in src/coreclr/jit/lclvars.cpp [604:1382]
void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, unsigned takeArgs)
{
//-------------------------------------------------------------------------
// Walk the function signature for the explicit arguments
//-------------------------------------------------------------------------
#if defined(TARGET_X86)
// Only (some of) the implicit args are enregistered for varargs
if (info.compIsVarArgs)
{
varDscInfo->maxIntRegArgNum = varDscInfo->intRegArgNum;
}
#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;
// We will process at most takeArgs arguments from the signature after skipping skipArgs arguments
const int64_t numUserArgs = min((int64_t)takeArgs, (argSigLen - (int64_t)skipArgs));
// If there are no user args or less than skipArgs args, return here since there's no work to do.
if (numUserArgs <= 0)
{
return;
}
#ifdef TARGET_ARM
regMaskTP doubleAlignMask = RBM_NONE;
#endif // TARGET_ARM
// Skip skipArgs arguments from the signature.
for (unsigned i = 0; i < skipArgs; i++, argLst = info.compCompHnd->getArgNext(argLst))
{
;
}
// Process each user arg.
for (unsigned i = 0; i < numUserArgs; i++, varDscInfo->nextParam(), 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;
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);
}
// The final home for this incoming parameter might be our local stack frame.
varDsc->lvOnFrame = true;
#ifdef SWIFT_SUPPORT
if (info.compCallConv == CorInfoCallConvExtension::Swift)
{
if (varTypeIsSIMD(varDsc))
{
IMPL_LIMITATION("SIMD types are currently unsupported in Swift reverse pinvokes");
}
if (lvaInitSpecialSwiftParam(argLst, varDscInfo, strip(corInfoType), typeHnd))
{
continue;
}
if (varDsc->TypeGet() == TYP_STRUCT)
{
// Struct parameters are lowered to separate primitives in the
// Swift calling convention. We cannot handle these patterns
// efficiently, so we always DNER them and home them to stack
// in the prolog.
lvaSetVarDoNotEnregister(varDscInfo->varNum DEBUGARG(DoNotEnregisterReason::IsStructArg));
}
}
#endif
// For ARM, ARM64, LOONGARCH64, RISCV64 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(strip(corInfoType), typeHnd);
unsigned cSlots =
(argSize + TARGET_POINTER_SIZE - 1) / TARGET_POINTER_SIZE; // the total number of slots of this argument
bool isHfaArg = false;
var_types hfaType = TYP_UNDEF;
// Methods that use VarArg or SoftFP cannot have HFA arguments except
// Native varargs on arm64 unix use the regular calling convention.
if (((TargetOS::IsUnix && TargetArchitecture::IsArm64) || !info.compIsVarArgs) && !opts.compUseSoftFP)
{
// If the argType is a struct, then check if it is an HFA
if (varTypeIsStruct(argType))
{
// hfaType is set to float, double, or SIMD type if it is an HFA, otherwise TYP_UNDEF
hfaType = GetHfaType(typeHnd);
isHfaArg = varTypeIsValidHfaType(hfaType);
}
}
else if (info.compIsVarArgs)
{
// 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.
if (TargetOS::IsUnix)
{
NYI("InitUserArgs for Vararg callee is not yet implemented on non Windows targets.");
}
}
if (isHfaArg)
{
// We have an HFA argument, so from here on out treat the type as a float, double, or vector.
// The original struct type is available by using origArgType.
// We also update the cSlots to be the number of float/double/vector fields in the HFA.
argType = hfaType; // TODO-Cleanup: remove this assignment and mark `argType` as const.
varDsc->SetHfaType(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)
if (compFeatureArgSplit())
{
// On arm64 Windows we will need to properly handle the case where a >8byte <=16byte
// struct (or vector) is split between register r7 and virtual stack slot s[0].
// We will only do this for calls to vararg methods on Windows Arm64.
// SIMD types (for which `varTypeIsStruct()` returns `true`) are also passed in general-purpose
// registers and can be split between registers and stack with Windows arm64 native varargs.
//
// !!This does not affect the normal arm64 calling convention or Unix Arm64!!
if (info.compIsVarArgs && (cSlots > 1))
{
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
varDscInfo->stackArgSize += TARGET_POINTER_SIZE;
}
}
}
#endif // defined(TARGET_ARM64)
#ifdef TARGET_ARM
// On ARM we pass the first 4 words of integer arguments and non-HFA structs in registers.
// But we pre-spill user arguments in varargs methods and structs.
//
unsigned cAlign;
bool preSpill = info.compIsVarArgs || isSoftFPPreSpill;
switch (origArgType)
{
case TYP_STRUCT:
assert(varDsc->lvSize() == argSize);
cAlign = varDsc->lvStructDoubleAlign ? 2 : 1;
// HFA arguments go on the stack frame. They don't get spilled in the prolog like struct
// arguments passed in the integer registers but get homed immediately after the prolog.
if (!isHfaArg)
{
cSlotsToEnregister = 1; // HFAs must be totally enregistered or not, but other structs can be split.
preSpill = true;
}
break;
case TYP_DOUBLE:
case TYP_LONG:
cAlign = 2;
break;
default:
cAlign = 1;
break;
}
if (isRegParamType(argType))
{
compArgSize += varDscInfo->alignReg(argType, cAlign) * REGSIZE_BYTES;
}
if (argType == TYP_STRUCT)
{
// Are we going to split the struct between registers and stack? We can do that as long as
// no floating-point arguments have been put on the stack.
//
// From the ARM Procedure Call Standard:
// Rule C.5: "If the NCRN is less than r4 **and** the NSAA is equal to the SP,"
// then split the argument between registers and stack. Implication: if something
// has already been spilled to the stack, then anything that would normally be
// split between the core registers and the stack will be put on the stack.
// Anything that follows will also be on the stack. However, if something from
// floating point regs has been spilled to the stack, we can still use r0-r3 until they are full.
if (varDscInfo->canEnreg(TYP_INT, 1) && // The beginning of the struct can go in a register
!varDscInfo->canEnreg(TYP_INT, cSlots) && // The end of the struct can't fit in a register
varDscInfo->existAnyFloatStackArgs()) // There's at least one stack-based FP arg already
{
varDscInfo->setAllRegArgUsed(TYP_INT); // Prevent all future use of integer registers
preSpill = false; // This struct won't be prespilled, since it will go on the stack
}
}
if (preSpill)
{
for (unsigned ix = 0; ix < cSlots; ix++)
{
if (!varDscInfo->canEnreg(TYP_INT, ix + 1))
{
break;
}
regMaskTP regMask = genMapArgNumToRegMask(varDscInfo->regArgNum(TYP_INT) + ix, TYP_INT);
if (cAlign == 2)
{
doubleAlignMask |= regMask;
}
codeGen->regSet.rsMaskPreSpillRegArg |= regMask;
}
}
#endif // 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
bool canPassArgInRegisters = false;
#if defined(UNIX_AMD64_ABI)
if (varTypeIsStruct(argType))
{
canPassArgInRegisters = structDesc.passedInRegisters;
}
else
#elif defined(TARGET_X86)
if (varTypeIsStruct(argType) && isTrivialPointerSizedStruct(typeHnd))
{
canPassArgInRegisters = varDscInfo->canEnreg(TYP_I_IMPL, cSlotsToEnregister);
}
else
#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
uint32_t floatFlags = STRUCT_NO_FLOAT_FIELD;
var_types argRegTypeInStruct1 = TYP_UNKNOWN;
var_types argRegTypeInStruct2 = TYP_UNKNOWN;
if ((strip(corInfoType) == CORINFO_TYPE_VALUECLASS) && (argSize <= MAX_PASS_MULTIREG_BYTES))
{
#if defined(TARGET_LOONGARCH64)
floatFlags = info.compCompHnd->getLoongArch64PassStructInRegisterFlags(typeHnd);
#else
floatFlags = info.compCompHnd->getRISCV64PassStructInRegisterFlags(typeHnd);
#endif
}
if ((floatFlags & STRUCT_HAS_FLOAT_FIELDS_MASK) != 0)
{
assert(varTypeIsStruct(argType));
int floatNum = 0;
if ((floatFlags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0)
{
assert(argSize <= 8);
assert(varDsc->lvExactSize() <= argSize);
floatNum = 1;
canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 1);
argRegTypeInStruct1 = (varDsc->lvExactSize() == 8) ? TYP_DOUBLE : TYP_FLOAT;
}
else if ((floatFlags & STRUCT_FLOAT_FIELD_ONLY_TWO) != 0)
{
floatNum = 2;
canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 2);
argRegTypeInStruct1 = (floatFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT;
argRegTypeInStruct2 = (floatFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT;
}
else if ((floatFlags & STRUCT_FLOAT_FIELD_FIRST) != 0)
{
floatNum = 1;
canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 1);
canPassArgInRegisters = canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1);
argRegTypeInStruct1 = (floatFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT;
argRegTypeInStruct2 = (floatFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? TYP_LONG : TYP_INT;
}
else if ((floatFlags & STRUCT_FLOAT_FIELD_SECOND) != 0)
{
floatNum = 1;
canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 1);
canPassArgInRegisters = canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1);
argRegTypeInStruct1 = (floatFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? TYP_LONG : TYP_INT;
argRegTypeInStruct2 = (floatFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT;
}
assert((floatNum == 1) || (floatNum == 2));
if (!canPassArgInRegisters)
{
// On LoongArch64, if there aren't any remaining floating-point registers to pass the argument,
// integer registers (if any) are used instead.
canPassArgInRegisters = varDscInfo->canEnreg(argType, cSlotsToEnregister);
argRegTypeInStruct1 = TYP_UNKNOWN;
argRegTypeInStruct2 = TYP_UNKNOWN;
if (cSlotsToEnregister == 2)
{
if (!canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1))
{
// Here a struct-arg which needs two registers but only one integer register available,
// it has to be split.
argRegTypeInStruct1 = TYP_I_IMPL;
canPassArgInRegisters = true;
}
}
}
}
else
#endif // defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
{
canPassArgInRegisters = varDscInfo->canEnreg(argType, cSlotsToEnregister);
#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
// On LoongArch64 and RISCV64, if there aren't any remaining floating-point registers to pass the
// argument, integer registers (if any) are used instead.
if (!canPassArgInRegisters && varTypeIsFloating(argType))
{
canPassArgInRegisters = varDscInfo->canEnreg(TYP_I_IMPL, cSlotsToEnregister);
argType = canPassArgInRegisters ? TYP_I_IMPL : argType;
}
if (!canPassArgInRegisters && (cSlots > 1))
{
// If a struct-arg which needs two registers but only one integer register available,
// it has to be split.
canPassArgInRegisters = varDscInfo->canEnreg(TYP_I_IMPL, 1);
argRegTypeInStruct1 = canPassArgInRegisters ? TYP_I_IMPL : TYP_UNKNOWN;
}
#endif
}
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->SetOtherArgReg(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
#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
unsigned secondAllocatedRegArgNum = 0;
if (argRegTypeInStruct1 != TYP_UNKNOWN)
{
firstAllocatedRegArgNum = varDscInfo->allocRegArg(argRegTypeInStruct1, 1);
}
else
#endif // defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
{
firstAllocatedRegArgNum = varDscInfo->allocRegArg(argType, cSlots);
}
if (isHfaArg)
{
// We need to save the fact that this HFA is enregistered.
// Note that we can have HVAs of SIMD types even if we are not recognizing intrinsics.
// In that case, we won't have normalized the vector types on the varDsc, so if we have a single vector
// register, we need to set the type now. Otherwise, later we'll assume this is passed by reference.
if (varDsc->lvHfaSlots() != 1)
{
varDsc->lvIsMultiRegArg = true;
}
}
varDsc->lvIsRegArg = 1;
#if FEATURE_MULTIREG_ARGS
#ifdef TARGET_ARM64
if (argType == TYP_STRUCT)
{
varDsc->SetArgReg(genMapRegArgNumToRegNum(firstAllocatedRegArgNum, TYP_I_IMPL, info.compCallConv));
if (cSlots == 2)
{
varDsc->SetOtherArgReg(
genMapRegArgNumToRegNum(firstAllocatedRegArgNum + 1, TYP_I_IMPL, info.compCallConv));
varDsc->lvIsMultiRegArg = true;
}
}
#elif defined(UNIX_AMD64_ABI)
if (varTypeIsStruct(argType))
{
varDsc->SetArgReg(
genMapRegArgNumToRegNum(firstAllocatedRegArgNum, firstEightByteType, info.compCallConv));
// 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);
varDsc->lvIsMultiRegArg = true;
}
if (secondEightByteType != TYP_UNDEF)
{
varDsc->SetOtherArgReg(
genMapRegArgNumToRegNum(secondAllocatedRegArgNum, secondEightByteType, info.compCallConv));
}
assert(structDesc.eightByteCount <= 2);
}
#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
if (argType == TYP_STRUCT)
{
if (argRegTypeInStruct1 != TYP_UNKNOWN)
{
varDsc->SetArgReg(
genMapRegArgNumToRegNum(firstAllocatedRegArgNum, argRegTypeInStruct1, info.compCallConv));
varDsc->lvIs4Field1 = (genTypeSize(argRegTypeInStruct1) == 4) ? 1 : 0;
if (argRegTypeInStruct2 != TYP_UNKNOWN)
{
secondAllocatedRegArgNum = varDscInfo->allocRegArg(argRegTypeInStruct2, 1);
varDsc->SetOtherArgReg(
genMapRegArgNumToRegNum(secondAllocatedRegArgNum, argRegTypeInStruct2, info.compCallConv));
varDsc->lvIs4Field2 = (genTypeSize(argRegTypeInStruct2) == 4) ? 1 : 0;
}
else if (cSlots > 1)
{
// Here a struct-arg which needs two registers but only one integer register available,
// it has to be split. But we reserved extra 8-bytes for the whole struct.
varDsc->lvIsSplit = 1;
varDsc->SetOtherArgReg(REG_STK);
varDscInfo->setAllRegArgUsed(argRegTypeInStruct1);
varDscInfo->stackArgSize += TARGET_POINTER_SIZE;
#ifdef TARGET_RISCV64
varDscInfo->hasSplitParam = true;
#endif
}
}
else
{
varDsc->SetArgReg(genMapRegArgNumToRegNum(firstAllocatedRegArgNum, TYP_I_IMPL, info.compCallConv));
if (cSlots == 2)
{
varDsc->SetOtherArgReg(
genMapRegArgNumToRegNum(firstAllocatedRegArgNum + 1, TYP_I_IMPL, info.compCallConv));
}
assert(cSlots <= 2);
}
}
#else // ARM32
if (varTypeIsStruct(argType))
{
varDsc->SetArgReg(genMapRegArgNumToRegNum(firstAllocatedRegArgNum, TYP_I_IMPL, info.compCallConv));
}
#endif // ARM32
else
#endif // FEATURE_MULTIREG_ARGS
{
varDsc->SetArgReg(genMapRegArgNumToRegNum(firstAllocatedRegArgNum, argType, info.compCallConv));
}
#ifdef TARGET_ARM
if (varDsc->TypeGet() == TYP_LONG)
{
varDsc->SetOtherArgReg(
genMapRegArgNumToRegNum(firstAllocatedRegArgNum + 1, TYP_INT, info.compCallConv));
}
unsigned numEnregistered = 0;
unsigned stackSize = 0;
// Check if arg was split between registers and stack.
if (varTypeUsesIntReg(argType))
{
unsigned firstRegArgNum = genMapIntRegNumToRegArgNum(varDsc->GetArgReg(), info.compCallConv);
unsigned lastRegArgNum = firstRegArgNum + cSlots - 1;
if (lastRegArgNum >= varDscInfo->maxIntRegArgNum)
{
assert(varDscInfo->stackArgSize == 0);
numEnregistered = varDscInfo->maxIntRegArgNum - firstRegArgNum;
varDsc->SetStackOffset(-(int)numEnregistered * REGSIZE_BYTES);
stackSize = (cSlots - numEnregistered) * REGSIZE_BYTES;
varDscInfo->stackArgSize += stackSize;
varDscInfo->hasSplitParam = true;
JITDUMP("set user arg V%02u offset to %d\n", varDscInfo->varNum, varDsc->GetStackOffset());
}
else
{
numEnregistered = cSlots;
}
}
else
{
numEnregistered = cSlots;
}
#endif // TARGET_ARM
#ifdef DEBUG
if (verbose)
{
printf("Arg #%u passed in register(s) ", varDscInfo->varNum);
#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,
info.compCallConv)));
}
if (secondEightByteType == TYP_UNDEF)
{
printf(", secondEightByte: <not used>");
}
else
{
printf(", secondEightByte: %s",
getRegName(genMapRegArgNumToRegNum(secondAllocatedRegArgNum, secondEightByteType,
info.compCallConv)));
}
}
else
#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
if (varTypeIsStruct(argType))
{
if (argRegTypeInStruct1 == TYP_UNKNOWN)
{
printf("first: <not used>");
}
else
{
printf("first: %s",
getRegName(genMapRegArgNumToRegNum(firstAllocatedRegArgNum, argRegTypeInStruct1,
info.compCallConv)));
}
if (argRegTypeInStruct2 == TYP_UNKNOWN)
{
printf(", second: <not used>");
}
else
{
printf(", second: %s",
getRegName(genMapRegArgNumToRegNum(secondAllocatedRegArgNum, argRegTypeInStruct2,
info.compCallConv)));
}
}
else
#endif // UNIX_AMD64_ABI, TARGET_LOONGARCH64, TARGET_RISCV64
{
assert(varTypeUsesFloatReg(argType) || varTypeUsesIntReg(argType));
bool isFloat = varTypeUsesFloatReg(argType);
unsigned regArgNum = genMapRegNumToRegArgNum(varDsc->GetArgReg(), argType, info.compCallConv);
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, info.compCallConv)),
getRegName(genMapRegArgNumToRegNum(regArgNum + 1, argType, info.compCallConv)));
// doubles take 2 slots
assert(ix + 1 < cSlots);
++ix;
++regArgNum;
}
else
{
printf("%s",
getRegName(genMapRegArgNumToRegNum(regArgNum, argType, info.compCallConv)));
}
}
else
#endif // TARGET_ARM
{
printf("%s", getRegName(genMapRegArgNumToRegNum(regArgNum, argType, info.compCallConv)));
}
}
}
printf("\n");
}
#endif // DEBUG
} // end if (canPassArgInRegisters)
else
{
#if defined(TARGET_ARM)
varDscInfo->setAllRegArgUsed(argType);
if (varTypeUsesFloatReg(argType))
{
varDscInfo->setAnyFloatStackArgs();
}
else
{
assert(varTypeUsesIntReg(argType));
}
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
// 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 occurring on ARM64/LoongArch64.
//
varDscInfo->setAllRegArgUsed(argType);
#endif // TARGET_XXX
#ifdef TARGET_ARM
unsigned argAlignment = cAlign * TARGET_POINTER_SIZE;
#else
unsigned argAlignment = eeGetArgSizeAlignment(origArgType, (hfaType == TYP_FLOAT));
// We expect the following rounding operation to be a noop on all
// ABIs except ARM (where we have 8-byte aligned args) and Apple
// ARM64 (that allows to pack multiple smaller parameters in a
// single stack slot).
assert(compAppleArm64Abi() || ((varDscInfo->stackArgSize % argAlignment) == 0));
#endif
varDscInfo->stackArgSize = roundUp(varDscInfo->stackArgSize, argAlignment);
JITDUMP("set user arg V%02u offset to %u\n", varDscInfo->varNum, varDscInfo->stackArgSize);
varDsc->SetStackOffset(varDscInfo->stackArgSize);
varDscInfo->stackArgSize += argSize;
}
#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 += roundUp(argSize, TARGET_POINTER_SIZE);
#else // !UNIX_AMD64_ABI
compArgSize += argSize;
#endif // !UNIX_AMD64_ABI
if (info.compIsVarArgs || isSoftFPPreSpill)
{
#if defined(TARGET_X86)
varDsc->SetStackOffset(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 DEBUGARG(AddressExposedReason::TOO_CONSERVATIVE));
#endif // !TARGET_X86
}
}
compArgSize = GetOutgoingArgByteSize(compArgSize);
#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)
{
// 'double aligned types' can begin only at r0 or r2 and we always expect at least two registers to be used
// Note that in rare cases, we can have double-aligned structs of 12 bytes (if specified explicitly with
// attributes)
assert((doubleAlignMask == 0b0011) || (doubleAlignMask == 0b1100) ||
(doubleAlignMask == 0b0111) /* || 0b1111 is if'ed out */);
// Now if doubleAlignMask is xyz1 i.e., the struct starts in r0, 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
bool startsAtR0 = (doubleAlignMask & 1) == 1;
bool r2XorR3 = ((codeGen->regSet.rsMaskPreSpillRegArg & RBM_R2) == 0) !=
((codeGen->regSet.rsMaskPreSpillRegArg & RBM_R3) == 0);
if (startsAtR0 && r2XorR3)
{
codeGen->regSet.rsMaskPreSpillAlign =
(~codeGen->regSet.rsMaskPreSpillRegArg & ~doubleAlignMask) & RBM_ARG_REGS;
}
}
}
#endif // TARGET_ARM
}