Skip to content

Commit

Permalink
JIT: Add ABI classifier for arm32 (#100526)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobbotsch authored Apr 3, 2024
1 parent e0884ed commit 0b542b9
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 2 deletions.
33 changes: 33 additions & 0 deletions src/coreclr/jit/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions src/coreclr/jit/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
26 changes: 24 additions & 2 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -1698,11 +1706,25 @@ 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;
}

// 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++)
{
const ABIPassingSegment& expected = abiInfo.Segments[i];
Expand Down
181 changes: 181 additions & 0 deletions src/coreclr/jit/targetarm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,185 @@ 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_DOUBLE) ||
((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<regNumber>(static_cast<unsigned>(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<regNumber>(static_cast<unsigned>(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

0 comments on commit 0b542b9

Please sign in to comment.