Skip to content

Commit 0b542b9

Browse files
authored
JIT: Add ABI classifier for arm32 (#100526)
1 parent e0884ed commit 0b542b9

File tree

4 files changed

+268
-2
lines changed

4 files changed

+268
-2
lines changed

src/coreclr/jit/abi.cpp

+33
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,39 @@ ABIPassingInformation ABIPassingInformation::FromSegment(Compiler* comp, const A
140140
return info;
141141
}
142142

143+
#ifdef DEBUG
144+
//-----------------------------------------------------------------------------
145+
// Dump:
146+
// Dump the ABIPassingInformation to stdout.
147+
//
148+
void ABIPassingInformation::Dump() const
149+
{
150+
if (NumSegments != 1)
151+
{
152+
printf("%u segments\n", NumSegments);
153+
}
154+
155+
for (unsigned i = 0; i < NumSegments; i++)
156+
{
157+
if (NumSegments > 1)
158+
{
159+
printf(" [%u] ", i);
160+
}
161+
162+
const ABIPassingSegment& seg = Segments[i];
163+
164+
if (Segments[i].IsPassedInRegister())
165+
{
166+
printf("[%02u..%02u) reg %s\n", seg.Offset, seg.Offset + seg.Size, getRegName(seg.GetRegister()));
167+
}
168+
else
169+
{
170+
printf("[%02u..%02u) stack @ +%02u\n", seg.Offset, seg.Offset + seg.Size, seg.GetStackOffset());
171+
}
172+
}
173+
}
174+
#endif
175+
143176
//-----------------------------------------------------------------------------
144177
// RegisterQueue::Dequeue:
145178
// Dequeue a register from the queue.

src/coreclr/jit/abi.h

+30
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ struct ABIPassingInformation
5050
bool IsSplitAcrossRegistersAndStack() const;
5151

5252
static ABIPassingInformation FromSegment(Compiler* comp, const ABIPassingSegment& segment);
53+
54+
#ifdef DEBUG
55+
void Dump() const;
56+
#endif
5357
};
5458

5559
class RegisterQueue
@@ -141,6 +145,30 @@ class Arm64Classifier
141145
WellKnownArg wellKnownParam);
142146
};
143147

148+
class Arm32Classifier
149+
{
150+
const ClassifierInfo& m_info;
151+
// 4 int regs are available for parameters. This gives the index of the
152+
// next one.
153+
// A.k.a. "NCRN": Next Core Register Number
154+
unsigned m_nextIntReg = 0;
155+
// 16 float regs are available for parameters. We keep them as a mask as
156+
// they can be backfilled.
157+
unsigned m_floatRegs = 0xFFFF;
158+
// A.k.a. "NSAA": Next Stack Argument Address
159+
unsigned m_stackArgSize = 0;
160+
161+
ABIPassingInformation ClassifyFloat(Compiler* comp, var_types type, unsigned elems);
162+
163+
public:
164+
Arm32Classifier(const ClassifierInfo& info);
165+
166+
ABIPassingInformation Classify(Compiler* comp,
167+
var_types type,
168+
ClassLayout* structLayout,
169+
WellKnownArg wellKnownParam);
170+
};
171+
144172
#if defined(TARGET_X86)
145173
typedef X86Classifier PlatformClassifier;
146174
#elif defined(WINDOWS_AMD64_ABI)
@@ -149,6 +177,8 @@ typedef WinX64Classifier PlatformClassifier;
149177
typedef SysVX64Classifier PlatformClassifier;
150178
#elif defined(TARGET_ARM64)
151179
typedef Arm64Classifier PlatformClassifier;
180+
#elif defined(TARGET_ARM)
181+
typedef Arm32Classifier PlatformClassifier;
152182
#endif
153183

154184
#ifdef SWIFT_SUPPORT

src/coreclr/jit/lclvars.cpp

+24-2
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,14 @@ void Compiler::lvaClassifyParameterABI(Classifier& classifier)
16471647
#endif
16481648

16491649
lvaParameterPassingInfo[i] = classifier.Classify(this, dsc->TypeGet(), structLayout, wellKnownArg);
1650+
1651+
#ifdef DEBUG
1652+
if (verbose)
1653+
{
1654+
printf("Parameter #%u ABI info: ", i);
1655+
lvaParameterPassingInfo[i].Dump();
1656+
}
1657+
#endif
16501658
}
16511659
}
16521660

@@ -1675,7 +1683,7 @@ void Compiler::lvaClassifyParameterABI()
16751683
}
16761684
else
16771685
#endif
1678-
#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64)
1686+
#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_ARM)
16791687
{
16801688
PlatformClassifier classifier(cInfo);
16811689
lvaClassifyParameterABI(classifier);
@@ -1698,11 +1706,25 @@ void Compiler::lvaClassifyParameterABI()
16981706
unsigned numSegmentsToCompare = abiInfo.NumSegments;
16991707
if (dsc->lvIsHfa())
17001708
{
1701-
assert(abiInfo.NumSegments >= 1);
17021709
// LclVarDsc only has one register set for HFAs
17031710
numSegmentsToCompare = 1;
17041711
}
17051712

1713+
#ifdef TARGET_ARM
1714+
// On arm the old representation only represents the start register for
1715+
// struct multireg args.
1716+
if (varTypeIsStruct(dsc))
1717+
{
1718+
numSegmentsToCompare = 1;
1719+
}
1720+
1721+
// And also for TYP_DOUBLE on soft FP
1722+
if (opts.compUseSoftFP && (dsc->TypeGet() == TYP_DOUBLE))
1723+
{
1724+
numSegmentsToCompare = 1;
1725+
}
1726+
#endif
1727+
17061728
for (unsigned i = 0; i < numSegmentsToCompare; i++)
17071729
{
17081730
const ABIPassingSegment& expected = abiInfo.Segments[i];

src/coreclr/jit/targetarm.cpp

+181
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,185 @@ const regMaskTP fltArgMasks[] = {RBM_F0, RBM_F1, RBM_F2, RBM_F3, RBM_F4, RBM_F5,
2626

2727
static_assert_no_msg(RBM_ALLDOUBLE == (RBM_ALLDOUBLE_HIGH >> 1));
2828

29+
//-----------------------------------------------------------------------------
30+
// Arm32Classifier:
31+
// Construct a new instance of the arm32 ABI classifier.
32+
//
33+
// Parameters:
34+
// info - Info about the method being classified.
35+
//
36+
Arm32Classifier::Arm32Classifier(const ClassifierInfo& info) : m_info(info)
37+
{
38+
}
39+
40+
//-----------------------------------------------------------------------------
41+
// Classify:
42+
// Classify a parameter for the arm32 ABI.
43+
//
44+
// Parameters:
45+
// comp - Compiler instance
46+
// type - The type of the parameter
47+
// structLayout - The layout of the struct. Expected to be non-null if
48+
// varTypeIsStruct(type) is true.
49+
// wellKnownParam - Well known type of the parameter (if it may affect its ABI classification)
50+
//
51+
// Returns:
52+
// Classification information for the parameter.
53+
//
54+
ABIPassingInformation Arm32Classifier::Classify(Compiler* comp,
55+
var_types type,
56+
ClassLayout* structLayout,
57+
WellKnownArg wellKnownParam)
58+
{
59+
if (!comp->opts.compUseSoftFP)
60+
{
61+
if (varTypeIsStruct(type))
62+
{
63+
var_types hfaType = comp->GetHfaType(structLayout->GetClassHandle());
64+
65+
if (hfaType != TYP_UNDEF)
66+
{
67+
unsigned slots = structLayout->GetSize() / genTypeSize(hfaType);
68+
return ClassifyFloat(comp, hfaType, slots);
69+
}
70+
}
71+
72+
if (varTypeIsFloating(type))
73+
{
74+
return ClassifyFloat(comp, type, 1);
75+
}
76+
}
77+
78+
unsigned alignment = 4;
79+
if ((type == TYP_LONG) || (type == TYP_DOUBLE) ||
80+
((type == TYP_STRUCT) &&
81+
(comp->info.compCompHnd->getClassAlignmentRequirement(structLayout->GetClassHandle()) == 8)))
82+
{
83+
alignment = 8;
84+
m_nextIntReg = roundUp(m_nextIntReg, 2);
85+
}
86+
87+
unsigned size = type == TYP_STRUCT ? structLayout->GetSize() : genTypeSize(type);
88+
unsigned alignedSize = roundUp(size, alignment);
89+
90+
unsigned numInRegs = min(alignedSize / 4, 4 - m_nextIntReg);
91+
bool anyOnStack = numInRegs < (alignedSize / 4);
92+
93+
// If we already passed anything on stack (due to float args) then we
94+
// cannot split an arg.
95+
if ((numInRegs > 0) && anyOnStack && (m_stackArgSize != 0))
96+
{
97+
numInRegs = 0;
98+
}
99+
100+
ABIPassingInformation info;
101+
info.NumSegments = numInRegs + (anyOnStack ? 1 : 0);
102+
info.Segments = new (comp, CMK_ABI) ABIPassingSegment[info.NumSegments];
103+
104+
for (unsigned i = 0; i < numInRegs; i++)
105+
{
106+
unsigned endOffs = min((i + 1) * 4, size);
107+
info.Segments[i] =
108+
ABIPassingSegment::InRegister(static_cast<regNumber>(static_cast<unsigned>(REG_R0) + m_nextIntReg + i),
109+
i * 4, endOffs - (i * 4));
110+
}
111+
112+
m_nextIntReg += numInRegs;
113+
114+
if (anyOnStack)
115+
{
116+
m_stackArgSize = roundUp(m_stackArgSize, alignment);
117+
unsigned stackSize = size - (numInRegs * 4);
118+
info.Segments[numInRegs] = ABIPassingSegment::OnStack(m_stackArgSize, 0, stackSize);
119+
m_stackArgSize += roundUp(stackSize, 4);
120+
121+
// As soon as any int arg goes on stack we cannot put anything else in
122+
// int registers. This situation can happen if an arg would normally be
123+
// split but wasn't because a float arg was already passed on stack.
124+
m_nextIntReg = 4;
125+
}
126+
127+
return info;
128+
}
129+
130+
//-----------------------------------------------------------------------------
131+
// ClassifyFloat:
132+
// Classify a parameter that uses float registers.
133+
//
134+
// Parameters:
135+
// comp - Compiler instance
136+
// type - The type of the parameter
137+
// numElems - Number of elements for the parameter.
138+
//
139+
// Returns:
140+
// Classification information for the parameter.
141+
//
142+
// Remarks:
143+
// Float parameters can require multiple registers; the double registers are
144+
// overlaid on top of the float registers so that d0 = s0, s1, d1 = s2, s3
145+
// etc. This means that allocating a double register automatically makes the
146+
// two corresponding float registers unavailable.
147+
//
148+
// The ABI also supports HFAs that similarly require multiple registers for
149+
// passing. When multiple registers are required for a single argument they
150+
// must always be allocated into consecutive float registers. However,
151+
// backfilling is allowed. For example, a signature like
152+
// Foo(float x, double y, float z) allocates x in REG_F0 = s0, y in REG_F2 =
153+
// d1, z in REG_F1 = s1.
154+
//
155+
ABIPassingInformation Arm32Classifier::ClassifyFloat(Compiler* comp, var_types type, unsigned numElems)
156+
{
157+
assert((type == TYP_FLOAT) || (type == TYP_DOUBLE));
158+
159+
unsigned numConsecutive = type == TYP_FLOAT ? numElems : (numElems * 2);
160+
161+
// Find the first start index that has a consecutive run of
162+
// 'numConsecutive' bits set.
163+
unsigned startRegMask = m_floatRegs;
164+
for (unsigned i = 1; i < numConsecutive; i++)
165+
{
166+
startRegMask &= m_floatRegs >> i;
167+
}
168+
169+
// Doubles can only start at even indices.
170+
if (type == TYP_DOUBLE)
171+
{
172+
startRegMask &= 0b0101010101010101;
173+
}
174+
175+
if (startRegMask != 0)
176+
{
177+
unsigned startRegIndex = BitOperations::TrailingZeroCount(startRegMask);
178+
unsigned usedRegsMask = ((1 << numConsecutive) - 1) << startRegIndex;
179+
// First consecutive run of numConsecutive bits start at startRegIndex
180+
assert((m_floatRegs & usedRegsMask) == usedRegsMask);
181+
182+
m_floatRegs ^= usedRegsMask;
183+
ABIPassingInformation info;
184+
info.NumSegments = numElems;
185+
info.Segments = new (comp, CMK_ABI) ABIPassingSegment[numElems];
186+
unsigned numRegsPerElem = type == TYP_FLOAT ? 1 : 2;
187+
for (unsigned i = 0; i < numElems; i++)
188+
{
189+
regNumber reg = static_cast<regNumber>(static_cast<unsigned>(REG_F0) + startRegIndex + i * numRegsPerElem);
190+
info.Segments[i] = ABIPassingSegment::InRegister(reg, i * genTypeSize(type), genTypeSize(type));
191+
}
192+
193+
return info;
194+
}
195+
else
196+
{
197+
// As soon as any float arg goes on stack no other float arg can go in a register.
198+
m_floatRegs = 0;
199+
200+
m_stackArgSize = roundUp(m_stackArgSize, genTypeSize(type));
201+
ABIPassingInformation info =
202+
ABIPassingInformation::FromSegment(comp, ABIPassingSegment::OnStack(m_stackArgSize, 0,
203+
numElems * genTypeSize(type)));
204+
m_stackArgSize += numElems * genTypeSize(type);
205+
206+
return info;
207+
}
208+
}
209+
29210
#endif // TARGET_ARM

0 commit comments

Comments
 (0)