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

[RISC-V] New ABI classifiers #101114

Merged
merged 5 commits into from
Apr 17, 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
5 changes: 1 addition & 4 deletions src/coreclr/jit/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,7 @@ bool ABIPassingInformation::IsSplitAcrossRegistersAndStack() const
//
ABIPassingInformation ABIPassingInformation::FromSegment(Compiler* comp, const ABIPassingSegment& segment)
{
ABIPassingInformation info;
info.NumSegments = 1;
info.Segments = new (comp, CMK_ABI) ABIPassingSegment(segment);
return info;
return {1, new (comp, CMK_ABI) ABIPassingSegment(segment)};
}

#ifdef DEBUG
Expand Down
27 changes: 26 additions & 1 deletion src/coreclr/jit/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,16 @@ struct ABIPassingInformation
// multiple register segments and a struct segment.
// - On Windows x64, all parameters always fit into one stack slot or
// register, and thus always have NumSegments == 1
// - On RISC-V, structs can be split out over 2 segments, each can be an integer/float register or a stack slot
unsigned NumSegments = 0;
tomeksowi marked this conversation as resolved.
Show resolved Hide resolved
ABIPassingSegment* Segments = nullptr;

ABIPassingInformation(unsigned numSegments = 0, ABIPassingSegment* segments = nullptr)
: NumSegments(numSegments)
, Segments(segments)
{
}

bool HasAnyRegisterSegment() const;
bool HasAnyStackSegment() const;
bool HasExactlyOneRegisterSegment() const;
Expand All @@ -77,7 +84,7 @@ class RegisterQueue
{
}

unsigned Count()
unsigned Count() const
{
return m_numRegs - m_index;
}
Expand Down Expand Up @@ -179,6 +186,22 @@ class Arm32Classifier
WellKnownArg wellKnownParam);
};

class RiscV64Classifier
{
const ClassifierInfo& m_info;
RegisterQueue m_intRegs;
RegisterQueue m_floatRegs;
unsigned m_stackArgSize = 0;

public:
RiscV64Classifier(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 @@ -189,6 +212,8 @@ typedef SysVX64Classifier PlatformClassifier;
typedef Arm64Classifier PlatformClassifier;
#elif defined(TARGET_ARM)
typedef Arm32Classifier PlatformClassifier;
#elif defined(TARGET_RISCV64)
typedef RiscV64Classifier PlatformClassifier;
#endif

#ifdef SWIFT_SUPPORT
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1805,7 +1805,8 @@ void Compiler::lvaClassifyParameterABI()
}
else
#endif
#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_ARM)
#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_ARM) || \
defined(TARGET_RISCV64)
{
PlatformClassifier classifier(cInfo);
lvaClassifyParameterABI(classifier);
Expand Down
150 changes: 150 additions & 0 deletions src/coreclr/jit/targetriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,154 @@ const regNumber fltArgRegs [] = {REG_FLTARG_0, REG_FLTARG_1, REG_FLTARG_2, REG_F
const regMaskTP fltArgMasks[] = {RBM_FLTARG_0, RBM_FLTARG_1, RBM_FLTARG_2, RBM_FLTARG_3, RBM_FLTARG_4, RBM_FLTARG_5, RBM_FLTARG_6, RBM_FLTARG_7 };
// clang-format on

//-----------------------------------------------------------------------------
// RiscV64Classifier:
// Construct a new instance of the RISC-V 64 ABI classifier.
//
// Parameters:
// info - Info about the method being classified.
//
RiscV64Classifier::RiscV64Classifier(const ClassifierInfo& info)
: m_info(info)
, m_intRegs(intArgRegs, ArrLen(intArgRegs))
, m_floatRegs(fltArgRegs, ArrLen(fltArgRegs))
{
}

//-----------------------------------------------------------------------------
// Classify:
// Classify a parameter for the RISC-V 64 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 RiscV64Classifier::Classify(Compiler* comp,
var_types type,
ClassLayout* structLayout,
WellKnownArg /*wellKnownParam*/)
{
assert(!m_info.IsVarArgs); // TODO: varargs currently not supported on RISC-V

StructFloatFieldInfoFlags flags = STRUCT_NO_FLOAT_FIELD;
unsigned intFields = 0, floatFields = 0;
unsigned passedSize;

if (varTypeIsStruct(type))
{
passedSize = structLayout->GetSize();
if (passedSize > MAX_PASS_MULTIREG_BYTES)
{
passedSize = TARGET_POINTER_SIZE; // pass by reference
}
else if (!structLayout->IsBlockLayout())
{
flags = (StructFloatFieldInfoFlags)comp->info.compCompHnd->getRISCV64PassStructInRegisterFlags(
structLayout->GetClassHandle());

if ((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0)
{
floatFields = 1;
}
else if ((flags & STRUCT_FLOAT_FIELD_ONLY_TWO) != 0)
{
floatFields = 2;
}
else if (flags != STRUCT_NO_FLOAT_FIELD)
{
assert((flags & (STRUCT_FLOAT_FIELD_FIRST | STRUCT_FLOAT_FIELD_SECOND)) != 0);
floatFields = 1;
intFields = 1;
}
}
}
else
{
assert(genTypeSize(type) <= TARGET_POINTER_SIZE);

if (varTypeIsFloating(type))
floatFields = 1;

passedSize = genTypeSize(type);
}

assert((floatFields > 0) || (intFields == 0));

auto PassSlot = [this](bool inFloatReg, unsigned offset, unsigned size) -> ABIPassingSegment {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Most places within the JIT that use lambdas use camelCase naming convention.

No need to address this here.

assert(size > 0);
assert(size <= TARGET_POINTER_SIZE);
if (inFloatReg)
{
return ABIPassingSegment::InRegister(m_floatRegs.Dequeue(), offset, size);
}
else if (m_intRegs.Count() > 0)
{
return ABIPassingSegment::InRegister(m_intRegs.Dequeue(), offset, size);
}
else
{
assert((m_stackArgSize % TARGET_POINTER_SIZE) == 0);
ABIPassingSegment seg = ABIPassingSegment::OnStack(m_stackArgSize, offset, size);
m_stackArgSize += TARGET_POINTER_SIZE;
return seg;
}
};

if ((floatFields > 0) && (m_floatRegs.Count() >= floatFields) && (m_intRegs.Count() >= intFields))
{
// Hardware floating-point calling convention
if ((floatFields == 1) && (intFields == 0))
{
if (flags == STRUCT_NO_FLOAT_FIELD)
assert(varTypeIsFloating(type)); // standalone floating-point real
else
assert((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0); // struct containing just one FP real

return ABIPassingInformation::FromSegment(comp, ABIPassingSegment::InRegister(m_floatRegs.Dequeue(), 0,
passedSize));
}
else
{
assert(varTypeIsStruct(type));
assert((floatFields + intFields) == 2);
assert(flags != STRUCT_NO_FLOAT_FIELD);
assert((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) == 0);

unsigned firstSize = ((flags & STRUCT_FIRST_FIELD_SIZE_IS8) != 0) ? 8 : 4;
unsigned secondSize = ((flags & STRUCT_SECOND_FIELD_SIZE_IS8) != 0) ? 8 : 4;
unsigned offset = max(firstSize, secondSize); // TODO: cover empty fields and custom offsets / alignments

bool isFirstFloat = (flags & (STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FLOAT_FIELD_FIRST)) != 0;
bool isSecondFloat = (flags & (STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FLOAT_FIELD_SECOND)) != 0;
assert(isFirstFloat || isSecondFloat);

return {2, new (comp, CMK_ABI) ABIPassingSegment[]{PassSlot(isFirstFloat, 0, firstSize),
PassSlot(isSecondFloat, offset, secondSize)}};
}
}
else
{
// Integer calling convention
if (passedSize <= TARGET_POINTER_SIZE)
{
return ABIPassingInformation::FromSegment(comp, PassSlot(false, 0, passedSize));
}
else
{
assert(varTypeIsStruct(type));
return {2, new (comp, CMK_ABI)
ABIPassingSegment[]{PassSlot(false, 0, TARGET_POINTER_SIZE),
PassSlot(false, TARGET_POINTER_SIZE, passedSize - TARGET_POINTER_SIZE)}};
}
}

unreached();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this do anything? Won't we have a compiler error if this becomes accidentally reachable?

Feel free to remove this separately.

}

#endif // TARGET_RISCV64
Loading