Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: Add ABI classifier for arm32 #100526

Merged
merged 3 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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