void Compiler::lvaInitUserArgs()

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
}