From 9154f89be397490a326193c04e9b3a0f63b41391 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Apr 2024 17:01:12 +0200 Subject: [PATCH 1/3] JIT: Add ABI classifier for arm32 --- src/coreclr/jit/abi.cpp | 33 +++++++ src/coreclr/jit/abi.h | 30 ++++++ src/coreclr/jit/lclvars.cpp | 20 +++- src/coreclr/jit/targetarm.cpp | 180 ++++++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/abi.cpp b/src/coreclr/jit/abi.cpp index c52c13273c63c..3dd8fcec32fcc 100644 --- a/src/coreclr/jit/abi.cpp +++ b/src/coreclr/jit/abi.cpp @@ -140,6 +140,39 @@ ABIPassingInformation ABIPassingInformation::FromSegment(Compiler* comp, const A return info; } +#ifdef DEBUG +//----------------------------------------------------------------------------- +// Dump: +// Dump the ABIPassingInformation to stdout. +// +void ABIPassingInformation::Dump() const +{ + if (NumSegments != 1) + { + printf("%u segments\n", NumSegments); + } + + for (unsigned i = 0; i < NumSegments; i++) + { + if (NumSegments > 1) + { + printf(" [%u] ", i); + } + + const ABIPassingSegment& seg = Segments[i]; + + if (Segments[i].IsPassedInRegister()) + { + printf("[%02u..%02u) reg %s\n", seg.Offset, seg.Offset + seg.Size, getRegName(seg.GetRegister())); + } + else + { + printf("[%02u..%02u) stack @ +%02u\n", seg.Offset, seg.Offset + seg.Size, seg.GetStackOffset()); + } + } +} +#endif + //----------------------------------------------------------------------------- // RegisterQueue::Dequeue: // Dequeue a register from the queue. diff --git a/src/coreclr/jit/abi.h b/src/coreclr/jit/abi.h index f6303899b2509..27e53c27efc7e 100644 --- a/src/coreclr/jit/abi.h +++ b/src/coreclr/jit/abi.h @@ -50,6 +50,10 @@ struct ABIPassingInformation bool IsSplitAcrossRegistersAndStack() const; static ABIPassingInformation FromSegment(Compiler* comp, const ABIPassingSegment& segment); + +#ifdef DEBUG + void Dump() const; +#endif }; class RegisterQueue @@ -141,6 +145,30 @@ class Arm64Classifier WellKnownArg wellKnownParam); }; +class Arm32Classifier +{ + const ClassifierInfo& m_info; + // 4 int regs are available for parameters. This gives the index of the + // next one. + // A.k.a. "NCRN": Next Core Register Number + unsigned m_nextIntReg = 0; + // 16 float regs are available for parameters. We keep them as a mask as + // they can be backfilled. + unsigned m_floatRegs = 0xFFFF; + // A.k.a. "NSAA": Next Stack Argument Address + unsigned m_stackArgSize = 0; + + ABIPassingInformation ClassifyFloat(Compiler* comp, var_types type, unsigned elems); + +public: + Arm32Classifier(const ClassifierInfo& info); + + ABIPassingInformation Classify(Compiler* comp, + var_types type, + ClassLayout* structLayout, + WellKnownArg wellKnownParam); +}; + #if defined(TARGET_X86) typedef X86Classifier PlatformClassifier; #elif defined(WINDOWS_AMD64_ABI) @@ -149,6 +177,8 @@ typedef WinX64Classifier PlatformClassifier; typedef SysVX64Classifier PlatformClassifier; #elif defined(TARGET_ARM64) typedef Arm64Classifier PlatformClassifier; +#elif defined(TARGET_ARM) +typedef Arm32Classifier PlatformClassifier; #endif #ifdef SWIFT_SUPPORT diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 978fff461e146..d5307d7097cc1 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1647,6 +1647,14 @@ void Compiler::lvaClassifyParameterABI(Classifier& classifier) #endif lvaParameterPassingInfo[i] = classifier.Classify(this, dsc->TypeGet(), structLayout, wellKnownArg); + +#ifdef DEBUG + if (verbose) + { + printf("Parameter #%u ABI info: ", i); + lvaParameterPassingInfo[i].Dump(); + } +#endif } } @@ -1675,7 +1683,7 @@ void Compiler::lvaClassifyParameterABI() } else #endif -#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) +#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_ARM) { PlatformClassifier classifier(cInfo); lvaClassifyParameterABI(classifier); @@ -1698,11 +1706,19 @@ void Compiler::lvaClassifyParameterABI() unsigned numSegmentsToCompare = abiInfo.NumSegments; if (dsc->lvIsHfa()) { - assert(abiInfo.NumSegments >= 1); // LclVarDsc only has one register set for HFAs numSegmentsToCompare = 1; } +#ifdef TARGET_ARM + // On arm the old representation only represents the start register for + // struct multireg args. + if (varTypeIsStruct(dsc)) + { + numSegmentsToCompare = 1; + } +#endif + for (unsigned i = 0; i < numSegmentsToCompare; i++) { const ABIPassingSegment& expected = abiInfo.Segments[i]; diff --git a/src/coreclr/jit/targetarm.cpp b/src/coreclr/jit/targetarm.cpp index 8e117bae81027..9a8badd052f74 100644 --- a/src/coreclr/jit/targetarm.cpp +++ b/src/coreclr/jit/targetarm.cpp @@ -26,4 +26,184 @@ const regMaskTP fltArgMasks[] = {RBM_F0, RBM_F1, RBM_F2, RBM_F3, RBM_F4, RBM_F5, static_assert_no_msg(RBM_ALLDOUBLE == (RBM_ALLDOUBLE_HIGH >> 1)); +//----------------------------------------------------------------------------- +// Arm32Classifier: +// Construct a new instance of the arm32 ABI classifier. +// +// Parameters: +// info - Info about the method being classified. +// +Arm32Classifier::Arm32Classifier(const ClassifierInfo& info) : m_info(info) +{ +} + +//----------------------------------------------------------------------------- +// Classify: +// Classify a parameter for the arm32 ABI. +// +// Parameters: +// comp - Compiler instance +// type - The type of the parameter +// structLayout - The layout of the struct. Expected to be non-null if +// varTypeIsStruct(type) is true. +// wellKnownParam - Well known type of the parameter (if it may affect its ABI classification) +// +// Returns: +// Classification information for the parameter. +// +ABIPassingInformation Arm32Classifier::Classify(Compiler* comp, + var_types type, + ClassLayout* structLayout, + WellKnownArg wellKnownParam) +{ + if (!comp->opts.compUseSoftFP) + { + if (varTypeIsStruct(type)) + { + var_types hfaType = comp->GetHfaType(structLayout->GetClassHandle()); + + if (hfaType != TYP_UNDEF) + { + unsigned slots = structLayout->GetSize() / genTypeSize(hfaType); + return ClassifyFloat(comp, hfaType, slots); + } + } + + if (varTypeIsFloating(type)) + { + return ClassifyFloat(comp, type, 1); + } + } + + unsigned alignment = 4; + if ((type == TYP_LONG) || ((type == TYP_STRUCT) && (comp->info.compCompHnd->getClassAlignmentRequirement( + structLayout->GetClassHandle()) == 8))) + { + alignment = 8; + m_nextIntReg = roundUp(m_nextIntReg, 2); + } + + unsigned size = type == TYP_STRUCT ? structLayout->GetSize() : genTypeSize(type); + unsigned alignedSize = roundUp(size, alignment); + + unsigned numInRegs = min(alignedSize / 4, 4 - m_nextIntReg); + bool anyOnStack = numInRegs < (alignedSize / 4); + + // If we already passed anything on stack (due to float args) then we + // cannot split an arg. + if ((numInRegs > 0) && anyOnStack && (m_stackArgSize != 0)) + { + numInRegs = 0; + } + + ABIPassingInformation info; + info.NumSegments = numInRegs + (anyOnStack ? 1 : 0); + info.Segments = new (comp, CMK_ABI) ABIPassingSegment[info.NumSegments]; + + for (unsigned i = 0; i < numInRegs; i++) + { + unsigned endOffs = min((i + 1) * 4, size); + info.Segments[i] = + ABIPassingSegment::InRegister(static_cast(static_cast(REG_R0) + m_nextIntReg + i), + i * 4, endOffs - (i * 4)); + } + + m_nextIntReg += numInRegs; + + if (anyOnStack) + { + m_stackArgSize = roundUp(m_stackArgSize, alignment); + unsigned stackSize = size - (numInRegs * 4); + info.Segments[numInRegs] = ABIPassingSegment::OnStack(m_stackArgSize, 0, stackSize); + m_stackArgSize += roundUp(stackSize, 4); + + // As soon as any int arg goes on stack we cannot put anything else in + // int registers. This situation can happen if an arg would normally be + // split but wasn't because a float arg was already passed on stack. + m_nextIntReg = 4; + } + + return info; +} + +//----------------------------------------------------------------------------- +// ClassifyFloat: +// Classify a parameter that uses float registers. +// +// Parameters: +// comp - Compiler instance +// type - The type of the parameter +// numElems - Number of elements for the parameter. +// +// Returns: +// Classification information for the parameter. +// +// Remarks: +// Float parameters can require multiple registers; the double registers are +// overlaid on top of the float registers so that d0 = s0, s1, d1 = s2, s3 +// etc. This means that allocating a double register automatically makes the +// two corresponding float registers unavailable. +// +// The ABI also supports HFAs that similarly require multiple registers for +// passing. When multiple registers are required for a single argument they +// must always be allocated into consecutive float registers. However, +// backfilling is allowed. For example, a signature like +// Foo(float x, double y, float z) allocates x in REG_F0 = s0, y in REG_F2 = +// d1, z in REG_F1 = s1. +// +ABIPassingInformation Arm32Classifier::ClassifyFloat(Compiler* comp, var_types type, unsigned numElems) +{ + assert((type == TYP_FLOAT) || (type == TYP_DOUBLE)); + + unsigned numConsecutive = type == TYP_FLOAT ? numElems : (numElems * 2); + + // Find the first start index that has a consecutive run of + // 'numConsecutive' bits set. + unsigned startRegMask = m_floatRegs; + for (unsigned i = 1; i < numConsecutive; i++) + { + startRegMask &= m_floatRegs >> i; + } + + // Doubles can only start at even indices. + if (type == TYP_DOUBLE) + { + startRegMask &= 0b0101010101010101; + } + + if (startRegMask != 0) + { + unsigned startRegIndex = BitOperations::TrailingZeroCount(startRegMask); + unsigned usedRegsMask = ((1 << numConsecutive) - 1) << startRegIndex; + // First consecutive run of numConsecutive bits start at startRegIndex + assert((m_floatRegs & usedRegsMask) == usedRegsMask); + + m_floatRegs ^= usedRegsMask; + ABIPassingInformation info; + info.NumSegments = numElems; + info.Segments = new (comp, CMK_ABI) ABIPassingSegment[numElems]; + unsigned numRegsPerElem = type == TYP_FLOAT ? 1 : 2; + for (unsigned i = 0; i < numElems; i++) + { + regNumber reg = static_cast(static_cast(REG_F0) + startRegIndex + i * numRegsPerElem); + info.Segments[i] = ABIPassingSegment::InRegister(reg, i * genTypeSize(type), genTypeSize(type)); + } + + return info; + } + else + { + // As soon as any float arg goes on stack no other float arg can go in a register. + m_floatRegs = 0; + + m_stackArgSize = roundUp(m_stackArgSize, genTypeSize(type)); + ABIPassingInformation info = + ABIPassingInformation::FromSegment(comp, ABIPassingSegment::OnStack(m_stackArgSize, 0, + numElems * genTypeSize(type))); + m_stackArgSize += numElems * genTypeSize(type); + + return info; + } +} + #endif // TARGET_ARM From afd751e36449196957a404ab314a1324642582b5 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Apr 2024 19:52:50 +0200 Subject: [PATCH 2/3] Fix for armel --- src/coreclr/jit/lclvars.cpp | 6 ++++++ src/coreclr/jit/targetarm.cpp | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index d5307d7097cc1..50997980ca748 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1717,6 +1717,12 @@ void Compiler::lvaClassifyParameterABI() { numSegmentsToCompare = 1; } + + // And also for TYP_DOUBLE on soft FP + if (opts.compUseSoftFP && (dsc->TypeGet() == TYP_DOUBLE)) + { + numSegmentsToCompare = 1; + } #endif for (unsigned i = 0; i < numSegmentsToCompare; i++) diff --git a/src/coreclr/jit/targetarm.cpp b/src/coreclr/jit/targetarm.cpp index 9a8badd052f74..fa771c9f56e8b 100644 --- a/src/coreclr/jit/targetarm.cpp +++ b/src/coreclr/jit/targetarm.cpp @@ -76,8 +76,8 @@ ABIPassingInformation Arm32Classifier::Classify(Compiler* comp, } unsigned alignment = 4; - if ((type == TYP_LONG) || ((type == TYP_STRUCT) && (comp->info.compCompHnd->getClassAlignmentRequirement( - structLayout->GetClassHandle()) == 8))) + if ((type == TYP_LONG) || (type == TYP_DOUBLE) || + ((type == TYP_STRUCT) && (comp->info.compCompHnd->getClassAlignmentRequirement(structLayout->GetClassHandle()) == 8))) { alignment = 8; m_nextIntReg = roundUp(m_nextIntReg, 2); From 518f864dd4fea611a840d09d86c33c44b4a920f8 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 2 Apr 2024 19:53:18 +0200 Subject: [PATCH 3/3] Run jit-format --- src/coreclr/jit/targetarm.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/targetarm.cpp b/src/coreclr/jit/targetarm.cpp index fa771c9f56e8b..14cb85adbcc16 100644 --- a/src/coreclr/jit/targetarm.cpp +++ b/src/coreclr/jit/targetarm.cpp @@ -77,7 +77,8 @@ ABIPassingInformation Arm32Classifier::Classify(Compiler* comp, unsigned alignment = 4; if ((type == TYP_LONG) || (type == TYP_DOUBLE) || - ((type == TYP_STRUCT) && (comp->info.compCompHnd->getClassAlignmentRequirement(structLayout->GetClassHandle()) == 8))) + ((type == TYP_STRUCT) && + (comp->info.compCompHnd->getClassAlignmentRequirement(structLayout->GetClassHandle()) == 8))) { alignment = 8; m_nextIntReg = roundUp(m_nextIntReg, 2);