From 13a90361bb8a527482d98b2f31eb245c1dcbf183 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 25 Mar 2024 18:34:38 +0100 Subject: [PATCH] JIT: Add a uniform representation for parameter ABI information (#100138) This adds a uniform representation that can represent the ABI information for all of our targets without needing to fall back to handling ABI specific details in all places that need to handle calling conventions. Currently nothing is using this information. I want to incrementally migrate our ABI handling to use this representation. Also, there are several potential future improvements: - Split out ABI classification per ABI instead of keeping them all within the same function - Unify `InitVarDscInfo::stackArgSize` and `InitVarDscInfo::argSize`. I am unsure why the latter is needed - Remove `LclVarDsc::GetArgReg()`, `LclVarDscInfo::GetOtherArgReg()`, HFA related members - Reuse the representation in `CallArgABIInformation` and unify the classifiers The end goal here is rewriting `genFnPrologCalleeRegArgs` to handle float and integer registers simultaneously, and to support some of the registers that the Swift calling convention is using. --- src/coreclr/jit/CMakeLists.txt | 2 + src/coreclr/jit/abi.cpp | 122 ++++++++++++++ src/coreclr/jit/abi.h | 53 ++++++ src/coreclr/jit/codegencommon.cpp | 11 +- src/coreclr/jit/compiler.h | 5 +- src/coreclr/jit/ee_il_dll.cpp | 26 ++- src/coreclr/jit/lclvars.cpp | 213 +++++++++++++++++++----- src/coreclr/jit/registerargconvention.h | 29 ++-- 8 files changed, 385 insertions(+), 76 deletions(-) create mode 100644 src/coreclr/jit/abi.cpp create mode 100644 src/coreclr/jit/abi.h diff --git a/src/coreclr/jit/CMakeLists.txt b/src/coreclr/jit/CMakeLists.txt index 748b44872b6c7..806ce48dce902 100644 --- a/src/coreclr/jit/CMakeLists.txt +++ b/src/coreclr/jit/CMakeLists.txt @@ -93,6 +93,7 @@ if(CLR_CMAKE_TARGET_WIN32) endif(CLR_CMAKE_TARGET_WIN32) set( JIT_SOURCES + abi.cpp alloc.cpp assertionprop.cpp bitset.cpp @@ -289,6 +290,7 @@ set( JIT_HEADERS ../inc/corjitflags.h ../inc/corjithost.h _typeinfo.h + abi.h alloc.h arraystack.h bitset.h diff --git a/src/coreclr/jit/abi.cpp b/src/coreclr/jit/abi.cpp new file mode 100644 index 0000000000000..d28e11f6129b3 --- /dev/null +++ b/src/coreclr/jit/abi.cpp @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "jitpch.h" +#include "abi.h" + +//----------------------------------------------------------------------------- +// IsPassedInRegister: +// Check if this segment is passed in a register. +// +// Return Value: +// True if this is passed in a register. +// +bool ABIPassingSegment::IsPassedInRegister() const +{ + return m_register != REG_NA; +} + +//----------------------------------------------------------------------------- +// IsPassedOnStack: +// Check if this segment is passed on the stack. +// +// Return Value: +// True if this is passed on the stack. +// +bool ABIPassingSegment::IsPassedOnStack() const +{ + return m_register == REG_NA; +} + +//----------------------------------------------------------------------------- +// GetRegister: +// Get the register that this segment is passed in. +// +// Return Value: +// The register. +// +regNumber ABIPassingSegment::GetRegister() const +{ + assert(IsPassedInRegister()); + return m_register; +} + +//----------------------------------------------------------------------------- +// GetStackOffset: +// Get the stack offset where this segment is passed. +// +// Return Value: +// Offset relative to the first stack argument. +// +unsigned ABIPassingSegment::GetStackOffset() const +{ + assert(IsPassedOnStack()); + return m_stackOffset; +} + +//----------------------------------------------------------------------------- +// InRegister: +// Create an ABIPassingSegment representing that a segment is passed in a +// register. +// +// Parameters: +// reg - The register the segment is passed in +// offset - The offset of the segment that is passed in the register +// size - The size of the segment passed in the register +// +// Return Value: +// New instance of ABIPassingSegment. +// +ABIPassingSegment ABIPassingSegment::InRegister(regNumber reg, unsigned offset, unsigned size) +{ + assert(reg != REG_NA); + ABIPassingSegment segment; + segment.m_register = reg; + segment.m_stackOffset = 0; + segment.Offset = offset; + segment.Size = size; + return segment; +} + +//----------------------------------------------------------------------------- +// OnStack: +// Create an ABIPassingSegment representing that a segment is passed on the +// stack. +// +// Parameters: +// stackOffset - Offset relative to the first stack parameter/argument +// offset - The offset of the segment that is passed in the register +// size - The size of the segment passed in the register +// +// Return Value: +// New instance of ABIPassingSegment. +// +ABIPassingSegment ABIPassingSegment::OnStack(unsigned stackOffset, unsigned offset, unsigned size) +{ + ABIPassingSegment segment; + segment.m_register = REG_NA; + segment.m_stackOffset = stackOffset; + segment.Offset = offset; + segment.Size = size; + return segment; +} + +//----------------------------------------------------------------------------- +// IsSplitAcrossRegistersAndStack: +// Check if this ABIPassingInformation represents passing a value in both +// registers and on stack. +// +// Return Value: +// True if the value is passed in both registers and on stack. +// +bool ABIPassingInformation::IsSplitAcrossRegistersAndStack() const +{ + bool anyReg = false; + bool anyStack = false; + for (unsigned i = 0; i < NumSegments; i++) + { + anyReg |= Segments[i].IsPassedInRegister(); + anyStack |= Segments[i].IsPassedOnStack(); + } + return anyReg && anyStack; +} diff --git a/src/coreclr/jit/abi.h b/src/coreclr/jit/abi.h new file mode 100644 index 0000000000000..4d50c46851fa9 --- /dev/null +++ b/src/coreclr/jit/abi.h @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +class ABIPassingSegment +{ + regNumber m_register = REG_NA; + unsigned m_stackOffset = 0; + +public: + bool IsPassedInRegister() const; + bool IsPassedOnStack() const; + + // Start offset of the segment within the parameter/argument. For example, a struct like { int32_t x; uint64_t y } + // may have two segments + // 1. Register(Offset=0, Type=TYP_INT, Size=4, Register=REG_ESI) + // 2. Register(Offset=8, Type=TYP_LONG, Size=8, Register=REG_EDI) + // on some ABIs, where the size of the first segment is not sufficient to + // compute the offset of the second. + unsigned Offset = 0; + // Size of the segment being passed. + unsigned Size = 0; + + // If this segment is passed in a register, return the particular register. + regNumber GetRegister() const; + + // If this segment is passed on the stack then return the particular stack + // offset, relative to the first stack argument's offset. + unsigned GetStackOffset() const; + + static ABIPassingSegment InRegister(regNumber reg, unsigned offset, unsigned size); + static ABIPassingSegment OnStack(unsigned stackOffset, unsigned offset, unsigned size); +}; + +struct ABIPassingInformation +{ + // The number of segments used to pass the value. Examples: + // - On x86, TYP_LONG can be passed in two registers, resulting in two + // register segments + // - On SysV x64, structs can be passed in two registers, resulting in two + // register segments + // - On arm64/arm32, HFAs can be passed in up to four registers, giving + // four register segments + // - On arm32, structs can be split out over register and stack, giving + // multiple register segments and a struct segment. + // - On Windows x64, all parameters always belong into one stack slot or register, + // and thus always have NumSegments == 1 + unsigned NumSegments = 0; + ABIPassingSegment* Segments = nullptr; + + bool IsSplitAcrossRegistersAndStack() const; +}; diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index c2d1d3c2e90bc..24f5a922f1887 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -6796,13 +6796,11 @@ unsigned Compiler::GetHfaCount(CORINFO_CLASS_HANDLE hClass) unsigned CodeGen::getFirstArgWithStackSlot() { #if defined(UNIX_AMD64_ABI) || defined(TARGET_ARMARCH) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - unsigned baseVarNum = 0; // Iterate over all the lvParam variables in the Lcl var table until we find the first one // that's passed on the stack. - LclVarDsc* varDsc = nullptr; for (unsigned i = 0; i < compiler->info.compArgsCount; i++) { - varDsc = compiler->lvaGetDesc(i); + LclVarDsc* varDsc = compiler->lvaGetDesc(i); // We should have found a stack parameter (and broken out of this loop) before // we find any non-parameters. @@ -6810,13 +6808,12 @@ unsigned CodeGen::getFirstArgWithStackSlot() if (varDsc->GetArgReg() == REG_STK) { - baseVarNum = i; - break; + return i; } } - assert(varDsc != nullptr); - return baseVarNum; + assert(!"Expected to find a parameter passed on the stack"); + return BAD_VAR_NUM; #elif defined(TARGET_AMD64) return 0; #else // TARGET_X86 diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 739f8c66f2e71..4e9ec67b31a46 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -50,6 +50,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "codegeninterface.h" #include "regset.h" +#include "abi.h" #include "jitgcinfo.h" #if DUMP_GC_TABLES && defined(JIT32_GCENCODER) @@ -3783,6 +3784,8 @@ class Compiler LclVarDsc* lvaTable; // variable descriptor table unsigned lvaTableCnt; // lvaTable size (>= lvaCount) + ABIPassingInformation* lvaParameterPassingInfo; + unsigned lvaTrackedCount; // actual # of locals being tracked unsigned lvaTrackedCountInSizeTUnits; // min # of size_t's sufficient to hold a bit for all the locals being tracked @@ -8136,7 +8139,7 @@ class Compiler var_types eeGetArgType(CORINFO_ARG_LIST_HANDLE list, CORINFO_SIG_INFO* sig, bool* isPinned); CORINFO_CLASS_HANDLE eeGetArgClass(CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_HANDLE list); CORINFO_CLASS_HANDLE eeGetClassFromContext(CORINFO_CONTEXT_HANDLE context); - unsigned eeGetArgSize(CORINFO_ARG_LIST_HANDLE list, CORINFO_SIG_INFO* sig); + unsigned eeGetArgSize(CorInfoType corInfoType, CORINFO_CLASS_HANDLE typeHnd); static unsigned eeGetArgSizeAlignment(var_types type, bool isFloatHfa); // VOM info, method sigs diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 6fa3b32f8cead..cfe50b492bb32 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -345,11 +345,11 @@ void CILJit::setTargetOS(CORINFO_OS os) // including padding after the actual value. // // Arguments: -// list - the arg list handle pointing to the argument -// sig - the signature for the arg's method +// corInfoType - EE type of the argument +// typeHnd - if the type is a value class, its class handle // // Return value: -// the number of stack slots in stack arguments for the call. +// the size in bytes when the type is passed on the stack for the call. // // Notes: // - On most platforms arguments are passed with TARGET_POINTER_SIZE alignment, @@ -357,8 +357,10 @@ void CILJit::setTargetOS(CORINFO_OS os) // It is different for arm64 apple that packs some types without alignment and padding. // If the argument is passed by reference then the method returns REF size. // -unsigned Compiler::eeGetArgSize(CORINFO_ARG_LIST_HANDLE list, CORINFO_SIG_INFO* sig) +unsigned Compiler::eeGetArgSize(CorInfoType corInfoType, CORINFO_CLASS_HANDLE typeHnd) { + var_types argType = JITtype2varType(corInfoType); + #if defined(TARGET_AMD64) // Everything fits into a single 'slot' size @@ -366,12 +368,9 @@ unsigned Compiler::eeGetArgSize(CORINFO_ARG_LIST_HANDLE list, CORINFO_SIG_INFO* CLANG_FORMAT_COMMENT_ANCHOR; #ifdef UNIX_AMD64_ABI - CORINFO_CLASS_HANDLE argClass; - CorInfoType argTypeJit = strip(info.compCompHnd->getArgType(sig, list, &argClass)); - var_types argType = JITtype2varType(argTypeJit); if (varTypeIsStruct(argType)) { - unsigned structSize = info.compCompHnd->getClassSize(argClass); + unsigned structSize = info.compCompHnd->getClassSize(typeHnd); return roundUp(structSize, TARGET_POINTER_SIZE); } #endif // UNIX_AMD64_ABI @@ -379,22 +378,19 @@ unsigned Compiler::eeGetArgSize(CORINFO_ARG_LIST_HANDLE list, CORINFO_SIG_INFO* #else // !TARGET_AMD64 - CORINFO_CLASS_HANDLE argClass; - CorInfoType argTypeJit = strip(info.compCompHnd->getArgType(sig, list, &argClass)); - var_types argType = JITtype2varType(argTypeJit); - unsigned argSize; + unsigned argSize; var_types hfaType = TYP_UNDEF; bool isHfa = false; if (varTypeIsStruct(argType)) { - hfaType = GetHfaType(argClass); + hfaType = GetHfaType(typeHnd); isHfa = (hfaType != TYP_UNDEF); - unsigned structSize = info.compCompHnd->getClassSize(argClass); + unsigned structSize = info.compCompHnd->getClassSize(typeHnd); // make certain the EE passes us back the right thing for refanys - assert(argTypeJit != CORINFO_TYPE_REFANY || structSize == 2 * TARGET_POINTER_SIZE); + assert(corInfoType != CORINFO_TYPE_REFANY || structSize == 2 * TARGET_POINTER_SIZE); // For each target that supports passing struct args in multiple registers // apply the target specific rules for them here: diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 6ee822ec226f6..3630020fc1287 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -241,6 +241,8 @@ void Compiler::lvaInitTypeRef() new (&lvaTable[i], jitstd::placement_t()) LclVarDsc(); // call the constructor. } + lvaParameterPassingInfo = new (this, CMK_LvaTable) ABIPassingInformation[max(info.compArgsCount, 1)]{}; + //------------------------------------------------------------------------- // Count the arguments and initialize the respective lvaTable[] entries // @@ -256,23 +258,23 @@ void Compiler::lvaInitTypeRef() { case CorInfoCallConvExtension::Thiscall: // In thiscall the this parameter goes into a register. - varDscInfo.Init(lvaTable, hasRetBuffArg, 1, 0); + varDscInfo.Init(lvaTable, lvaParameterPassingInfo, hasRetBuffArg, 1, 0); break; case CorInfoCallConvExtension::C: case CorInfoCallConvExtension::Stdcall: case CorInfoCallConvExtension::CMemberFunction: case CorInfoCallConvExtension::StdcallMemberFunction: - varDscInfo.Init(lvaTable, hasRetBuffArg, 0, 0); + varDscInfo.Init(lvaTable, lvaParameterPassingInfo, hasRetBuffArg, 0, 0); break; case CorInfoCallConvExtension::Managed: case CorInfoCallConvExtension::Fastcall: case CorInfoCallConvExtension::FastcallMemberFunction: default: - varDscInfo.Init(lvaTable, hasRetBuffArg, MAX_REG_ARG, MAX_FLOAT_REG_ARG); + varDscInfo.Init(lvaTable, lvaParameterPassingInfo, hasRetBuffArg, MAX_REG_ARG, MAX_FLOAT_REG_ARG); break; } #else - varDscInfo.Init(lvaTable, hasRetBuffArg, MAX_REG_ARG, MAX_FLOAT_REG_ARG); + varDscInfo.Init(lvaTable, lvaParameterPassingInfo, hasRetBuffArg, MAX_REG_ARG, MAX_FLOAT_REG_ARG); #endif lvaInitArgs(&varDscInfo); @@ -531,8 +533,7 @@ void Compiler::lvaInitThisPtr(InitVarDscInfo* varDscInfo) #endif compArgSize += TARGET_POINTER_SIZE; - varDscInfo->varNum++; - varDscInfo->varDsc++; + varDscInfo->nextParam(); } } @@ -559,6 +560,12 @@ void Compiler::lvaInitRetBuffArg(InitVarDscInfo* varDscInfo, bool useFixedRetBuf unsigned retBuffArgNum = varDscInfo->allocRegArg(TYP_INT); varDsc->SetArgReg(genMapIntRegArgNumToRegNum(retBuffArgNum, info.compCallConv)); } + else + { + varDscInfo->stackArgSize = roundUp(varDscInfo->stackArgSize, TARGET_POINTER_SIZE); + varDsc->SetStackOffset(varDscInfo->stackArgSize); + varDscInfo->stackArgSize += TARGET_POINTER_SIZE; + } #if FEATURE_MULTIREG_ARGS varDsc->SetOtherArgReg(REG_NA); @@ -574,11 +581,22 @@ void Compiler::lvaInitRetBuffArg(InitVarDscInfo* varDscInfo, bool useFixedRetBuf } #endif - /* Update the total argument size, count and varDsc */ + ABIPassingInformation* abiInfo = varDscInfo->abiInfo; + abiInfo->NumSegments = 1; + abiInfo->Segments = new (this, CMK_LvaTable) ABIPassingSegment[1]; + + if (varDsc->lvIsRegArg) + { + abiInfo->Segments[0] = ABIPassingSegment::InRegister(varDsc->GetArgReg(), 0, TARGET_POINTER_SIZE); + } + else + { + abiInfo->Segments[0] = ABIPassingSegment::OnStack(varDsc->GetStackOffset(), 0, TARGET_POINTER_SIZE); + } compArgSize += TARGET_POINTER_SIZE; - varDscInfo->varNum++; - varDscInfo->varDsc++; + + varDscInfo->nextParam(); } } @@ -632,8 +650,7 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un } // Process each user arg. - for (unsigned i = 0; i < numUserArgs; - i++, varDscInfo->varNum++, varDscInfo->varDsc++, argLst = info.compCompHnd->getArgNext(argLst)) + for (unsigned i = 0; i < numUserArgs; i++, varDscInfo->nextParam(), argLst = info.compCompHnd->getArgNext(argLst)) { LclVarDsc* varDsc = varDscInfo->varDsc; CORINFO_CLASS_HANDLE typeHnd = nullptr; @@ -660,6 +677,7 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un } #endif + ABIPassingInformation* abiInfo = varDscInfo->abiInfo; // For ARM, ARM64, LOONGARCH64, RISCV64 and AMD64 varargs, all arguments go in integer registers var_types argType = mangleVarArgsType(varDsc->TypeGet()); @@ -669,7 +687,7 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un // 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 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; @@ -756,8 +774,6 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un // 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; } @@ -1037,6 +1053,16 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un genMapRegArgNumToRegNum(firstAllocatedRegArgNum + 1, TYP_I_IMPL, info.compCallConv)); varDsc->lvIsMultiRegArg = true; } + + abiInfo->NumSegments = cSlots; + abiInfo->Segments = new (this, CMK_LvaTable) ABIPassingSegment[cSlots]; + for (unsigned i = 0; i < cSlots; i++) + { + abiInfo->Segments[i] = + ABIPassingSegment::InRegister(genMapRegArgNumToRegNum(firstAllocatedRegArgNum + i, TYP_I_IMPL, + info.compCallConv), + i * TARGET_POINTER_SIZE, TARGET_POINTER_SIZE); + } } #elif defined(UNIX_AMD64_ABI) if (varTypeIsStruct(argType)) @@ -1057,6 +1083,16 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un varDsc->SetOtherArgReg( genMapRegArgNumToRegNum(secondAllocatedRegArgNum, secondEightByteType, info.compCallConv)); } + + assert(structDesc.eightByteCount <= 2); + abiInfo->NumSegments = structDesc.eightByteCount; + abiInfo->Segments = new (this, CMK_LvaTable) ABIPassingSegment[structDesc.eightByteCount]; + for (int i = 0; i < structDesc.eightByteCount; i++) + { + regNumber reg = i == 0 ? varDsc->GetArgReg() : varDsc->GetOtherArgReg(); + abiInfo->Segments[i] = ABIPassingSegment::InRegister(reg, structDesc.eightByteOffsets[i], + structDesc.eightByteSizes[i]); + } } #elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) if (argType == TYP_STRUCT) @@ -1072,6 +1108,15 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un varDsc->SetOtherArgReg( genMapRegArgNumToRegNum(secondAllocatedRegArgNum, argRegTypeInStruct2, info.compCallConv)); varDsc->lvIs4Field2 = (genTypeSize(argRegTypeInStruct2) == 4) ? 1 : 0; + + abiInfo->NumSegments = 2; + abiInfo->Segments = new (this, CMK_LvaTable) ABIPassingSegment[2]; + abiInfo->Segments[0] = + ABIPassingSegment::InRegister(varDsc->GetArgReg(), 0, genTypeSize(argRegTypeInStruct1)); + abiInfo->Segments[1] = ABIPassingSegment::InRegister(varDsc->GetOtherArgReg(), + roundUp(genTypeSize(argRegTypeInStruct1), + genTypeSize(argRegTypeInStruct2)), + genTypeSize(argRegTypeInStruct2)); } else if (cSlots > 1) { @@ -1080,9 +1125,15 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un varDsc->lvIsSplit = 1; varDsc->SetOtherArgReg(REG_STK); varDscInfo->setAllRegArgUsed(argRegTypeInStruct1); -#if FEATURE_FASTTAILCALL + + abiInfo->NumSegments = 2; + abiInfo->Segments = new (this, CMK_LvaTable) ABIPassingSegment[2]; + abiInfo->Segments[0] = + ABIPassingSegment::InRegister(varDsc->GetArgReg(), 0, genTypeSize(argRegTypeInStruct1)); + abiInfo->Segments[1] = ABIPassingSegment::OnStack(varDscInfo->stackArgSize, TARGET_POINTER_SIZE, + TARGET_POINTER_SIZE); + varDscInfo->stackArgSize += TARGET_POINTER_SIZE; -#endif #ifdef TARGET_RISCV64 varDscInfo->hasSplitParam = true; #endif @@ -1096,6 +1147,16 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un varDsc->SetOtherArgReg( genMapRegArgNumToRegNum(firstAllocatedRegArgNum + 1, TYP_I_IMPL, info.compCallConv)); } + + assert(cSlots <= 2); + abiInfo->NumSegments = cSlots; + abiInfo->Segments = new (this, CMK_LvaTable) ABIPassingSegment[cSlots]; + for (unsigned i = 0; i < cSlots; i++) + { + regNumber reg = i == 0 ? varDsc->GetArgReg() : varDsc->GetOtherArgReg(); + abiInfo->Segments[i] = + ABIPassingSegment::InRegister(reg, TARGET_POINTER_SIZE * i, argRegTypeInStruct1); + } } } #else // ARM32 @@ -1117,7 +1178,8 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un genMapRegArgNumToRegNum(firstAllocatedRegArgNum + 1, TYP_INT, info.compCallConv)); } -#if FEATURE_FASTTAILCALL + unsigned numEnregistered = 0; + unsigned stackSize = 0; // Check if arg was split between registers and stack. if (varTypeUsesIntReg(argType)) { @@ -1126,16 +1188,60 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un if (lastRegArgNum >= varDscInfo->maxIntRegArgNum) { assert(varDscInfo->stackArgSize == 0); - unsigned numEnregistered = varDscInfo->maxIntRegArgNum - firstRegArgNum; + numEnregistered = varDscInfo->maxIntRegArgNum - firstRegArgNum; varDsc->SetStackOffset(-(int)numEnregistered * REGSIZE_BYTES); - varDscInfo->stackArgSize += (cSlots - 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; + } } -#endif + else + { + numEnregistered = cSlots; + } + + abiInfo->NumSegments = numEnregistered + (stackSize > 0 ? 1 : 0); + abiInfo->Segments = new (this, CMK_LvaTable) ABIPassingSegment[abiInfo->NumSegments]; + for (unsigned i = 0; i < numEnregistered; i++) + { + abiInfo->Segments[i] = + ABIPassingSegment::InRegister(genMapRegArgNumToRegNum(firstAllocatedRegArgNum + i, argType, + info.compCallConv), + i * TARGET_POINTER_SIZE, TARGET_POINTER_SIZE); + } + + if (stackSize > 0) + { + abiInfo->Segments[numEnregistered] = + ABIPassingSegment::OnStack(0, numEnregistered * TARGET_POINTER_SIZE, stackSize); + } + #endif // TARGET_ARM + if (abiInfo->NumSegments == 0) + { + // Structs at this point are always passed in 1 slot (either + // because it is small enough, or because it is an implicit + // byref). HFA's are also handled here, but they were retyped + // to have argType equal to their element type. + assert((argType != TYP_STRUCT) || (cSlots == 1)); + abiInfo->NumSegments = cSlots; + unsigned size = argType == TYP_STRUCT ? TARGET_POINTER_SIZE : genTypeSize(argType); + abiInfo->Segments = new (this, CMK_LvaTable) ABIPassingSegment[cSlots]; + for (unsigned i = 0; i < cSlots; i++) + { + abiInfo->Segments[i] = + ABIPassingSegment::InRegister(genMapRegArgNumToRegNum(firstAllocatedRegArgNum + i, argType, + info.compCallConv), + i * size, size); + } + } + #ifdef DEBUG if (verbose) { @@ -1207,9 +1313,9 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un printf(","); } - if (!isFloat && (regArgNum >= varDscInfo->maxIntRegArgNum)) // a struct has been split between - // registers and stack + if (!isFloat && (regArgNum >= varDscInfo->maxIntRegArgNum)) { + // a struct has been split between registers and stack printf(" stack slots:%d", cSlots - ix); break; } @@ -1271,7 +1377,6 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un #endif // TARGET_XXX -#if FEATURE_FASTTAILCALL #ifdef TARGET_ARM unsigned argAlignment = cAlign * TARGET_POINTER_SIZE; #else @@ -1286,8 +1391,12 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un JITDUMP("set user arg V%02u offset to %u\n", varDscInfo->varNum, varDscInfo->stackArgSize); varDsc->SetStackOffset(varDscInfo->stackArgSize); + + abiInfo->NumSegments = 1; + abiInfo->Segments = new (this, CMK_LvaTable) + ABIPassingSegment(ABIPassingSegment::OnStack(varDscInfo->stackArgSize, 0, argSize)); + varDscInfo->stackArgSize += argSize; -#endif // FEATURE_FASTTAILCALL } #ifdef UNIX_AMD64_ABI @@ -1377,11 +1486,18 @@ bool Compiler::lvaInitSpecialSwiftParam(InitVarDscInfo* varDscInfo, CorInfoType const char* className = info.compCompHnd->getClassNameFromMetadata(typeHnd, &namespaceName); if ((strcmp(className, "SwiftSelf") == 0) && (strcmp(namespaceName, "System.Runtime.InteropServices.Swift") == 0)) { - LclVarDsc* varDsc = varDscInfo->varDsc; + LclVarDsc* varDsc = varDscInfo->varDsc; + ABIPassingInformation* abiInfo = varDscInfo->abiInfo; varDsc->SetArgReg(REG_SWIFT_SELF); varDsc->SetOtherArgReg(REG_NA); varDsc->lvIsRegArg = true; - lvaSwiftSelfArg = varDscInfo->varNum; + + abiInfo->NumSegments = 1; + abiInfo->Segments = new (this, CMK_LvaTable) + ABIPassingSegment(ABIPassingSegment::InRegister(REG_SWIFT_SELF, 0, TARGET_POINTER_SIZE)); + compArgSize += TARGET_POINTER_SIZE; + + lvaSwiftSelfArg = varDscInfo->varNum; lvaSetVarDoNotEnregister(lvaSwiftSelfArg DEBUGARG(DoNotEnregisterReason::NonStandardParameter)); return true; } @@ -1399,9 +1515,11 @@ void Compiler::lvaInitGenericsCtxt(InitVarDscInfo* varDscInfo) { info.compTypeCtxtArg = varDscInfo->varNum; - LclVarDsc* varDsc = varDscInfo->varDsc; - varDsc->lvIsParam = 1; - varDsc->lvType = TYP_I_IMPL; + LclVarDsc* varDsc = varDscInfo->varDsc; + varDsc->lvIsParam = 1; + varDsc->lvType = TYP_I_IMPL; + ABIPassingInformation* abiInfo = varDscInfo->abiInfo; + abiInfo->NumSegments = 1; if (varDscInfo->canEnreg(TYP_I_IMPL)) { @@ -1413,6 +1531,8 @@ void Compiler::lvaInitGenericsCtxt(InitVarDscInfo* varDscInfo) #if FEATURE_MULTIREG_ARGS varDsc->SetOtherArgReg(REG_NA); #endif + abiInfo->Segments = new (this, CMK_LvaTable) + ABIPassingSegment(ABIPassingSegment::InRegister(varDsc->GetArgReg(), 0, TARGET_POINTER_SIZE)); varDsc->lvOnFrame = true; // The final home for this incoming register might be our local stack frame varDscInfo->intRegArgNum++; @@ -1429,10 +1549,10 @@ void Compiler::lvaInitGenericsCtxt(InitVarDscInfo* varDscInfo) // 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 + abiInfo->Segments = new (this, CMK_LvaTable) + ABIPassingSegment(ABIPassingSegment::OnStack(varDscInfo->stackArgSize, 0, TARGET_POINTER_SIZE)); varDsc->SetStackOffset(varDscInfo->stackArgSize); varDscInfo->stackArgSize += TARGET_POINTER_SIZE; -#endif // FEATURE_FASTTAILCALL } compArgSize += TARGET_POINTER_SIZE; @@ -1442,8 +1562,7 @@ void Compiler::lvaInitGenericsCtxt(InitVarDscInfo* varDscInfo) varDsc->SetStackOffset(compArgSize); #endif // TARGET_X86 - varDscInfo->varNum++; - varDscInfo->varDsc++; + varDscInfo->nextParam(); } } @@ -1463,12 +1582,13 @@ void Compiler::lvaInitVarArgsHandle(InitVarDscInfo* varDscInfo) #endif // TARGET_X86 varDsc->lvHasLdAddrOp = 1; + ABIPassingInformation* abiInfo = varDscInfo->abiInfo; + abiInfo->NumSegments = 1; + lvaSetVarDoNotEnregister(lvaVarargsHandleArg DEBUGARG(DoNotEnregisterReason::VMNeedsStackAddr)); assert(mostRecentlyActivePhase == PHASE_PRE_IMPORT); - // TODO-Cleanup: this is preImportation phase, why do we try to work with regs here? - // Should it be just deleted? if (varDscInfo->canEnreg(TYP_I_IMPL)) { /* Another register argument */ @@ -1477,6 +1597,8 @@ void Compiler::lvaInitVarArgsHandle(InitVarDscInfo* varDscInfo) varDsc->lvIsRegArg = 1; varDsc->SetArgReg(genMapRegArgNumToRegNum(varArgHndArgNum, TYP_I_IMPL, info.compCallConv)); + abiInfo->Segments = new (this, CMK_LvaTable) + ABIPassingSegment(ABIPassingSegment::InRegister(varDsc->GetArgReg(), 0, TARGET_POINTER_SIZE)); #if FEATURE_MULTIREG_ARGS varDsc->SetOtherArgReg(REG_NA); #endif @@ -1503,18 +1625,17 @@ void Compiler::lvaInitVarArgsHandle(InitVarDscInfo* varDscInfo) // 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 + abiInfo->Segments = new (this, CMK_LvaTable) + ABIPassingSegment(ABIPassingSegment::OnStack(varDscInfo->stackArgSize, 0, TARGET_POINTER_SIZE)); varDsc->SetStackOffset(varDscInfo->stackArgSize); varDscInfo->stackArgSize += TARGET_POINTER_SIZE; -#endif // FEATURE_FASTTAILCALL } /* Update the total argument size, count and varDsc */ compArgSize += TARGET_POINTER_SIZE; - varDscInfo->varNum++; - varDscInfo->varDsc++; + varDscInfo->nextParam(); #if defined(TARGET_X86) varDsc->SetStackOffset(compArgSize); @@ -5652,8 +5773,10 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() { if (lvaIsPreSpilled(preSpillLclNum, preSpillMask)) { - unsigned argSize = eeGetArgSize(argLst, &info.compMethodInfo->args); - argOffs = lvaAssignVirtualFrameOffsetToArg(preSpillLclNum, argSize, argOffs); + CORINFO_CLASS_HANDLE argClass = NO_CLASS_HANDLE; + CorInfoType argTypeJit = strip(info.compCompHnd->getArgType(&info.compMethodInfo->args, argLst, &argClass)); + unsigned argSize = eeGetArgSize(argTypeJit, argClass); + argOffs = lvaAssignVirtualFrameOffsetToArg(preSpillLclNum, argSize, argOffs); argLcls++; // Early out if we can. If size is 8 and base reg is 2, then the mask is 0x1100 @@ -5675,7 +5798,9 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() { if (!lvaIsPreSpilled(stkLclNum, preSpillMask)) { - const unsigned argSize = eeGetArgSize(argLst, &info.compMethodInfo->args); + CORINFO_CLASS_HANDLE argClass = NO_CLASS_HANDLE; + CorInfoType argTypeJit = strip(info.compCompHnd->getArgType(&info.compMethodInfo->args, argLst, &argClass)); + const unsigned argSize = eeGetArgSize(argTypeJit, argClass); argOffs = lvaAssignVirtualFrameOffsetToArg(stkLclNum, argSize, argOffs); argLcls++; } @@ -5686,7 +5811,9 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() #else // !TARGET_ARM for (unsigned i = 0; i < argSigLen; i++) { - unsigned argumentSize = eeGetArgSize(argLst, &info.compMethodInfo->args); + CORINFO_CLASS_HANDLE argClass = NO_CLASS_HANDLE; + CorInfoType argTypeJit = strip(info.compCompHnd->getArgType(&info.compMethodInfo->args, argLst, &argClass)); + unsigned argumentSize = eeGetArgSize(argTypeJit, argClass); assert(compAppleArm64Abi() || argumentSize % TARGET_POINTER_SIZE == 0); diff --git a/src/coreclr/jit/registerargconvention.h b/src/coreclr/jit/registerargconvention.h index 858efdc9d22c6..bddb5ae1477df 100644 --- a/src/coreclr/jit/registerargconvention.h +++ b/src/coreclr/jit/registerargconvention.h @@ -8,8 +8,9 @@ class LclVarDsc; struct InitVarDscInfo { - LclVarDsc* varDsc; - unsigned varNum; + LclVarDsc* varDsc; + unsigned varNum; + ABIPassingInformation* abiInfo; unsigned intRegArgNum; unsigned floatRegArgNum; @@ -29,18 +30,21 @@ struct InitVarDscInfo bool hasSplitParam; #endif // TARGET_ARM || TARGET_RISCV64 -#if FEATURE_FASTTAILCALL - // It is used to calculate argument stack size information in byte + // Bytes passed on the stack (including things like padding after structs) unsigned stackArgSize; -#endif // FEATURE_FASTTAILCALL public: // set to initial values - void Init(LclVarDsc* lvaTable, bool _hasRetBufArg, unsigned _maxIntRegArgNum, unsigned _maxFloatRegArgNum) + void Init(LclVarDsc* lvaTable, + ABIPassingInformation* abiInfoTable, + bool _hasRetBufArg, + unsigned _maxIntRegArgNum, + unsigned _maxFloatRegArgNum) { hasRetBufArg = _hasRetBufArg; - varDsc = &lvaTable[0]; // the first argument LclVar 0 - varNum = 0; // the first argument varNum 0 + varDsc = lvaTable; // the first argument LclVar 0 + varNum = 0; // the first argument varNum 0 + this->abiInfo = abiInfoTable; intRegArgNum = 0; floatRegArgNum = 0; maxIntRegArgNum = _maxIntRegArgNum; @@ -55,9 +59,7 @@ struct InitVarDscInfo hasSplitParam = false; #endif // TARGET_ARM || TARGET_RISCV64 -#if FEATURE_FASTTAILCALL stackArgSize = 0; -#endif // FEATURE_FASTTAILCALL } // return ref to current register arg for this type @@ -111,6 +113,13 @@ struct InitVarDscInfo #endif // TARGET_ARM + void nextParam() + { + varDsc++; + varNum++; + abiInfo++; + } + private: // return max register arg for this type unsigned maxRegArgNum(var_types type)