aboutsummaryrefslogtreecommitdiff
path: root/backend/lib
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-28 14:07:11 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-28 14:07:11 +0800
commit6e8feb15a0d256ca19e88185f78c81f689ce7d34 (patch)
tree9a2d97b188095a334a61ad471fc1b123ce2b2429 /backend/lib
parent8c878ada4e1df8b37aea9ff44a5fb2b25d2b2a1f (diff)
downloadrcc-project-6e8feb15a0d256ca19e88185f78c81f689ce7d34.tar.gz
rcc-project-6e8feb15a0d256ca19e88185f78c81f689ce7d34.tar.xz
[RISCV] Separate calling convention assignment
Diffstat (limited to 'backend/lib')
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt4
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp475
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVCallLowering.h6
-rw-r--r--backend/lib/Target/RISCV/RISCVCallingConv.cpp507
-rw-r--r--backend/lib/Target/RISCV/RISCVCallingConv.h52
-rw-r--r--backend/lib/Target/RISCV/RISCVPipeline.cpp1
6 files changed, 566 insertions, 479 deletions
diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt
index 65d5f82..9ce09cf 100644
--- a/backend/lib/Target/RISCV/CMakeLists.txt
+++ b/backend/lib/Target/RISCV/CMakeLists.txt
@@ -119,6 +119,7 @@ add_library(
RISCVImmediateMaterialization.cpp
RISCVInstPrinter.cpp
RISCVAsmBackend.cpp
+ RISCVCallingConv.cpp
RISCVMCCodeEmitter.cpp
RISCVObjectFile.cpp
RISCVMCTargetLowering.cpp
@@ -193,6 +194,8 @@ install(
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVCallLowering.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallingConv.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallingConv.h"
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVInstructionSelector.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVGISelCombiner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVLegalizerInfo.cpp"
@@ -243,6 +246,7 @@ rcc_backend_register_format_files(
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVCallLowering.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallingConv.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVInstructionSelector.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVGISelCombiner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVLegalizerInfo.cpp"
diff --git a/backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp b/backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp
index 66b4d87..3909020 100644
--- a/backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp
+++ b/backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp
@@ -2,6 +2,7 @@
#include "RISCVCallLowering.h"
+#include "../RISCVCallingConv.h"
#include "rcc/CodeGen/GenericOpcodes.h"
#include "rcc/CodeGen/MachineBasicBlock.h"
#include "rcc/CodeGen/MachineFrameInfo.h"
@@ -30,8 +31,6 @@ namespace rcc::riscv {
namespace {
constexpr std::string_view PlanTarget = "riscv64-unknown-linux-gnu";
-constexpr std::uint64_t XLenBytes = 8;
-constexpr std::uint64_t ABIStackAlignmentBytes = 16;
constexpr std::uint32_t AllowedDirectCallFlags =
static_cast<std::uint32_t>(CallTargetFlag::DirectCall);
@@ -54,478 +53,6 @@ constexpr std::uint32_t AllowedDirectCallFlags =
return Value + Increment;
}
-[[nodiscard]] std::uint64_t valueSizeBytes(LowLevelType Type) noexcept {
- return (static_cast<std::uint64_t>(Type.scalarBitWidth()) + 7U) / 8U;
-}
-
-[[nodiscard]] bool isSupportedFloat(const ABIValue &Value) noexcept {
- return Value.Category == ABIValueCategory::FloatingPoint &&
- (Value.Type.scalarBitWidth() == 32 || Value.Type.scalarBitWidth() == 64);
-}
-
-[[nodiscard]] Expected<PhysicalRegister> floatingRegisterView(PhysicalRegister DoubleView,
- const ABIValue &Value) {
- if (!isSupportedFloat(Value)) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "RISC-V floating-register view requires f32 or f64");
- }
- if (Value.Type.scalarBitWidth() == 64)
- return DoubleView;
-
- constexpr std::uint32_t FirstDouble = static_cast<std::uint32_t>(PhysicalRegisterID::F0_D);
- constexpr std::uint32_t LastDouble = static_cast<std::uint32_t>(PhysicalRegisterID::F31_D);
- constexpr std::uint32_t FirstSingle = static_cast<std::uint32_t>(PhysicalRegisterID::F0_F);
- const std::uint32_t Number = DoubleView.number();
- if (Number < FirstDouble || Number > LastDouble) {
- return makeUnexpected(ErrorCode::InternalError,
- "RISC-V ABI floating register is not an FPR64 view");
- }
- return PhysicalRegister::create(FirstSingle + Number - FirstDouble);
-}
-
-[[nodiscard]] bool requiresIntegerTransport(const ABIValue &Value) noexcept {
- return !isSupportedFloat(Value) || Value.Flags.has(ABIValueFlag::ByVal) ||
- Value.Flags.has(ABIValueFlag::StructReturn) || Value.Flags.has(ABIValueFlag::Indirect);
-}
-
-[[nodiscard]] Expected<void> verifyABIValue(const ABIValue &Value, bool RequireRegister) {
- if (RequireRegister && Value.Value.isNoRegister()) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "RISC-V ABI value requires a source or destination register");
- }
- if (!Value.Type.isValid() || !Value.Type.isScalarValue() || Value.Type.scalarBitWidth() > 64) {
- return makeUnexpected(
- ErrorCode::Unsupported,
- "RISC-V ABI accepts explicitly classified integer, pointer, f32, and f64 scalar fragments");
- }
- if (Value.Category == ABIValueCategory::Invalid || Value.Category >= ABIValueCategory::Count) {
- return makeUnexpected(ErrorCode::InvalidArgument, "RISC-V ABI value category is invalid");
- }
- if (Value.Category == ABIValueCategory::FloatingPoint && !isSupportedFloat(Value)) {
- return makeUnexpected(ErrorCode::Unsupported,
- "RISC-V LP64 ABIs only support f32 and f64 floating fragments");
- }
- if ((Value.Category == ABIValueCategory::Pointer) != Value.Type.isPointer()) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "RISC-V ABI pointer category must match its pointer LowLevelType");
- }
- if (Value.Type.isPointer() && Value.Type.scalarBitWidth() != 64) {
- return makeUnexpected(ErrorCode::Unsupported, "RISC-V LP64 ABIs require 64-bit pointers");
- }
- if (Value.Category == ABIValueCategory::Integer && Value.Type.scalarBitWidth() < 64) {
- if (Value.Extension == ABIExtensionKind::None) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "narrow RISC-V ABI integers require an explicit extension kind");
- }
- } else if (Value.Extension != ABIExtensionKind::None) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "only narrow integer ABI fragments may carry an extension kind");
- }
- if (Value.Extension >= ABIExtensionKind::Count || Value.FragmentCount == 0 ||
- Value.FragmentIndex >= Value.FragmentCount || Value.FragmentCount > 2) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "RISC-V ABI fragment coordinates or extension are invalid");
- }
-
- const bool IsByVal = Value.Flags.has(ABIValueFlag::ByVal);
- const bool IsSRet = Value.Flags.has(ABIValueFlag::StructReturn);
- const bool IsIndirect = Value.Flags.has(ABIValueFlag::Indirect);
- const unsigned SpecialFlagCount = static_cast<unsigned>(IsByVal) + static_cast<unsigned>(IsSRet) +
- static_cast<unsigned>(IsIndirect);
- if (SpecialFlagCount > 1) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "byval, sret, and indirect ABI flags are mutually exclusive");
- }
- if ((IsByVal || IsSRet || IsIndirect) &&
- (!Value.Type.isPointer() || Value.FragmentCount != 1 || Value.FragmentIndex != 0 ||
- Value.FragmentOffsetBytes != 0)) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "byval, sret, and indirect values require one explicit pointer fragment");
- }
- if (IsByVal) {
- if (Value.ByValSizeBytes == 0) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "byval arguments require a nonzero copy size");
- }
- } else if (Value.ByValSizeBytes != 0) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "non-byval values cannot carry a byval copy size");
- }
- return {};
-}
-
-[[nodiscard]] Expected<std::size_t> verifyValueSequence(std::span<const ABIValue> Values,
- bool RequireRegisters) {
- std::size_t ExpectedOriginal = 0;
- std::size_t Index = 0;
- while (Index < Values.size()) {
- const ABIValue &First = Values[Index];
- auto ValueVerification = verifyABIValue(First, RequireRegisters);
- if (!ValueVerification)
- return std::unexpected(ValueVerification.error());
- if (First.OriginalValue != ExpectedOriginal || First.FragmentIndex != 0 ||
- Index + First.FragmentCount > Values.size()) {
- return makeUnexpected(
- ErrorCode::InvalidArgument,
- "RISC-V ABI values must form contiguous, zero-based original-value groups");
- }
-
- std::uint64_t PreviousEnd = 0;
- for (std::size_t Fragment = 0; Fragment < First.FragmentCount; ++Fragment) {
- const ABIValue &Value = Values[Index + Fragment];
- auto FragmentVerification = verifyABIValue(Value, RequireRegisters);
- if (!FragmentVerification)
- return std::unexpected(FragmentVerification.error());
- if (Value.OriginalValue != First.OriginalValue || Value.FragmentIndex != Fragment ||
- Value.FragmentCount != First.FragmentCount ||
- Value.OriginalAlignment != First.OriginalAlignment || Value.Flags != First.Flags ||
- Value.ByValSizeBytes != First.ByValSizeBytes ||
- Value.ByValAlignment != First.ByValAlignment || Value.FragmentOffsetBytes < PreviousEnd) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "RISC-V ABI fragments of one value have inconsistent metadata");
- }
- PreviousEnd =
- static_cast<std::uint64_t>(Value.FragmentOffsetBytes) + valueSizeBytes(Value.Type);
- }
- Index += First.FragmentCount;
- ++ExpectedOriginal;
- }
- return ExpectedOriginal;
-}
-
-[[nodiscard]] CCAssignmentFlags assignmentFlags(const ABIValue &Value, bool IsVariadic,
- bool IsBitcast) {
- CCAssignmentFlags Result;
- if (IsVariadic)
- Result.set(CCAssignmentFlag::Variadic);
- if (Value.Flags.has(ABIValueFlag::ByVal))
- Result.set(CCAssignmentFlag::ByVal);
- if (Value.Flags.has(ABIValueFlag::StructReturn))
- Result.set(CCAssignmentFlag::StructReturn);
- if (Value.Flags.has(ABIValueFlag::Indirect))
- Result.set(CCAssignmentFlag::Indirect);
- if (Value.Flags.has(ABIValueFlag::InRegister))
- Result.set(CCAssignmentFlag::InRegister);
- if (IsBitcast)
- Result.set(CCAssignmentFlag::Bitcast);
- return Result;
-}
-
-[[nodiscard]] CCLocationKind locationKind(const ABIValue &Value, bool IsRegister) noexcept {
- if (Value.Flags.has(ABIValueFlag::Indirect))
- return CCLocationKind::Indirect;
- return IsRegister ? CCLocationKind::Register : CCLocationKind::Stack;
-}
-
-struct ArgumentAssignmentResult final {
- std::vector<CCAssignment> Assignments;
- StackArgumentArea StackArea;
- std::size_t FirstUnallocatedGPR = 0;
-};
-
-struct AssignmentState final {
- std::size_t GPR = 0;
- std::size_t FPR = 0;
- std::uint64_t StackOffset = 0;
- bool VariadicStackOnly = false;
-};
-
-[[nodiscard]] Expected<CCAssignment>
-registerAssignment(const ABIValue &Value, std::size_t ValueIndex, PhysicalRegister Location,
- LowLevelType LocationType, bool IsVariadic, bool IsBitcast) {
- auto Align =
- alignment(std::min<std::uint64_t>(Value.OriginalAlignment.bytes(), ABIStackAlignmentBytes));
- if (!Align)
- return std::unexpected(Align.error());
- return CCAssignment{
- .ValueIndex = ValueIndex,
- .OriginalValue = Value.Value,
- .OriginalValueIndex = Value.OriginalValue,
- .FragmentIndex = Value.FragmentIndex,
- .FragmentCount = Value.FragmentCount,
- .ValueType = Value.Type,
- .LocationType = LocationType,
- .LocationKind = locationKind(Value, true),
- .LocationRegister = Location,
- .StackOffsetBytes = std::nullopt,
- .LocationSizeBytes = valueSizeBytes(LocationType),
- .StackSlotSizeBytes = XLenBytes,
- .LocationAlignment = *Align,
- .Extension = Value.Extension,
- .CustomFlags = assignmentFlags(Value, IsVariadic, IsBitcast),
- };
-}
-
-[[nodiscard]] Expected<CCAssignment> stackAssignment(const ABIValue &Value, std::size_t ValueIndex,
- std::uint64_t StackOffset,
- LowLevelType LocationType, bool IsVariadic,
- std::uint64_t StackAlignment) {
- auto Align = alignment(StackAlignment);
- if (!Align)
- return std::unexpected(Align.error());
- return CCAssignment{
- .ValueIndex = ValueIndex,
- .OriginalValue = Value.Value,
- .OriginalValueIndex = Value.OriginalValue,
- .FragmentIndex = Value.FragmentIndex,
- .FragmentCount = Value.FragmentCount,
- .ValueType = Value.Type,
- .LocationType = LocationType,
- .LocationKind = locationKind(Value, false),
- .LocationRegister = std::nullopt,
- .StackOffsetBytes = StackOffset,
- .LocationSizeBytes = valueSizeBytes(LocationType),
- .StackSlotSizeBytes = XLenBytes,
- .LocationAlignment = *Align,
- .Extension = Value.Extension,
- .CustomFlags = assignmentFlags(Value, IsVariadic, false),
- };
-}
-
-[[nodiscard]] Expected<ArgumentAssignmentResult>
-assignArguments(std::span<const ABIValue> Values, std::size_t FixedArgumentCount, bool IsVariadic,
- const CallingConventionInfo &Convention) {
- auto I64 = LowLevelType::makeCategoryNeutralScalar(64);
- auto StackAlign = alignment(Convention.StackArgumentAlignmentBytes);
- if (!I64 || !StackAlign)
- return makeUnexpected(ErrorCode::InternalError, "RISC-V ABI primitive type facts are invalid");
-
- ArgumentAssignmentResult Result;
- AssignmentState State;
- State.StackOffset = Convention.ShadowSpaceBytes;
- Result.Assignments.reserve(Values.size());
-
- std::size_t Index = 0;
- while (Index < Values.size()) {
- const ABIValue &First = Values[Index];
- const std::size_t GroupSize = First.FragmentCount;
- const bool VariadicArgument = IsVariadic && First.OriginalValue >= FixedArgumentCount;
- bool HasPreferredFloat = false;
- std::size_t RequiredGPRs = 0;
- std::size_t RequiredFPRs = 0;
- for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
- const ABIValue &Value = Values[Index + Fragment];
- if (!VariadicArgument && !requiresIntegerTransport(Value)) {
- HasPreferredFloat = true;
- ++RequiredFPRs;
- } else {
- ++RequiredGPRs;
- }
- }
-
- const bool CanUsePreferredAggregate =
- HasPreferredFloat &&
- State.GPR + RequiredGPRs <= Convention.IntegerArgumentRegisters.size() &&
- State.FPR + RequiredFPRs <= Convention.FloatingArgumentRegisters.size();
- if (CanUsePreferredAggregate) {
- for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
- const ABIValue &Value = Values[Index + Fragment];
- Expected<CCAssignment> Assignment = makeUnexpected(
- ErrorCode::InternalError, "RISC-V ABI argument assignment was not initialized");
- if (requiresIntegerTransport(Value)) {
- Assignment = registerAssignment(Value, Index + Fragment,
- Convention.IntegerArgumentRegisters[State.GPR++], *I64,
- false, false);
- } else {
- auto View =
- floatingRegisterView(Convention.FloatingArgumentRegisters[State.FPR++], Value);
- if (!View)
- return std::unexpected(View.error());
- Assignment = registerAssignment(Value, Index + Fragment, *View, Value.Type, false, false);
- }
- if (!Assignment)
- return std::unexpected(Assignment.error());
- Result.Assignments.push_back(*Assignment);
- }
- Index += GroupSize;
- continue;
- }
-
- const bool RequiresAlignedPair =
- VariadicArgument && GroupSize == 2 && First.OriginalAlignment.bytes() >= 16;
- if (RequiresAlignedPair && State.GPR % 2 != 0 &&
- State.GPR < Convention.IntegerArgumentRegisters.size()) {
- ++State.GPR;
- }
- bool ForceEntireGroupToStack = State.VariadicStackOnly;
- if (RequiresAlignedPair && State.GPR + GroupSize > Convention.IntegerArgumentRegisters.size()) {
- ForceEntireGroupToStack = true;
- State.GPR = Convention.IntegerArgumentRegisters.size();
- }
-
- bool FirstStackFragment = true;
- for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
- const ABIValue &Value = Values[Index + Fragment];
- const bool HasGPR =
- !ForceEntireGroupToStack && State.GPR < Convention.IntegerArgumentRegisters.size();
- if (HasGPR) {
- const bool Bitcast = isSupportedFloat(Value);
- auto Assignment = registerAssignment(Value, Index + Fragment,
- Convention.IntegerArgumentRegisters[State.GPR++], *I64,
- VariadicArgument, Bitcast);
- if (!Assignment)
- return std::unexpected(Assignment.error());
- Result.Assignments.push_back(*Assignment);
- continue;
- }
-
- if (Value.Flags.has(ABIValueFlag::InRegister)) {
- return makeUnexpected(ErrorCode::Unsupported,
- "RISC-V inreg argument cannot be assigned to the stack");
- }
- std::uint64_t RequiredAlignment = XLenBytes;
- if (FirstStackFragment && ForceEntireGroupToStack) {
- RequiredAlignment = std::min<std::uint64_t>(
- std::max<std::uint64_t>(XLenBytes, First.OriginalAlignment.bytes()),
- ABIStackAlignmentBytes);
- }
- auto AlignedOffset = alignTo(State.StackOffset, RequiredAlignment);
- if (!AlignedOffset)
- return std::unexpected(AlignedOffset.error());
- State.StackOffset = *AlignedOffset;
- const LowLevelType LocationType = isSupportedFloat(Value) ? Value.Type : *I64;
- auto Assignment = stackAssignment(Value, Index + Fragment, State.StackOffset, LocationType,
- VariadicArgument, RequiredAlignment);
- if (!Assignment)
- return std::unexpected(Assignment.error());
- Result.Assignments.push_back(*Assignment);
- if (State.StackOffset > std::numeric_limits<std::uint64_t>::max() - XLenBytes) {
- return makeUnexpected(ErrorCode::Unsupported, "RISC-V argument area overflows uint64");
- }
- State.StackOffset += XLenBytes;
- FirstStackFragment = false;
- if (VariadicArgument)
- State.VariadicStackOnly = true;
- }
- Index += GroupSize;
- }
-
- auto FinalSize = alignTo(State.StackOffset, Convention.StackArgumentAlignmentBytes);
- if (!FinalSize)
- return std::unexpected(FinalSize.error());
- Result.StackArea = StackArgumentArea{*FinalSize, Convention.ShadowSpaceBytes, *StackAlign};
- Result.FirstUnallocatedGPR = State.GPR;
- return Result;
-}
-
-[[nodiscard]] Expected<std::vector<CCAssignment>>
-assignReturns(std::span<const ABIValue> Values, bool RequireRegisters,
- const CallingConventionInfo &Convention) {
- auto Sequence = verifyValueSequence(Values, RequireRegisters);
- if (!Sequence)
- return std::unexpected(Sequence.error());
- if (Values.size() > 2) {
- return makeUnexpected(ErrorCode::Unsupported,
- "RISC-V direct returns are limited to two explicit fragments");
- }
- auto I64 = LowLevelType::makeCategoryNeutralScalar(64);
- if (!I64)
- return std::unexpected(I64.error());
-
- std::vector<CCAssignment> Result;
- Result.reserve(Values.size());
- std::size_t GPR = 0;
- std::size_t FPR = 0;
- for (std::size_t Index = 0; Index < Values.size(); ++Index) {
- const ABIValue &Value = Values[Index];
- if (Value.Flags.rawValue() != 0) {
- return makeUnexpected(ErrorCode::InvalidArgument,
- "return fragments cannot carry argument-only ABI flags");
- }
- const bool UseFloatingRegister =
- isSupportedFloat(Value) && FPR < Convention.FloatingReturnRegisters.size();
- Expected<CCAssignment> Assignment = makeUnexpected(
- ErrorCode::Unsupported, "RISC-V calling convention has no return register available");
- if (UseFloatingRegister) {
- auto View = floatingRegisterView(Convention.FloatingReturnRegisters[FPR++], Value);
- if (!View)
- return std::unexpected(View.error());
- Assignment = registerAssignment(Value, Index, *View, Value.Type, false, false);
- } else if (GPR < Convention.IntegerReturnRegisters.size()) {
- Assignment = registerAssignment(Value, Index, Convention.IntegerReturnRegisters[GPR++], *I64,
- false, isSupportedFloat(Value));
- }
- if (!Assignment)
- return std::unexpected(Assignment.error());
- Result.push_back(*Assignment);
- }
- return Result;
-}
-
-[[nodiscard]] bool hasStructReturn(std::span<const ABIValue> Arguments) noexcept {
- return std::ranges::any_of(
- Arguments, [](const ABIValue &Value) { return Value.Flags.has(ABIValueFlag::StructReturn); });
-}
-
-[[nodiscard]] bool equivalentReturnLocations(std::span<const CCAssignment> Left,
- std::span<const CCAssignment> Right) noexcept {
- if (Left.size() != Right.size())
- return false;
- for (std::size_t Index = 0; Index < Left.size(); ++Index) {
- if (Left[Index].ValueType != Right[Index].ValueType ||
- Left[Index].LocationType != Right[Index].LocationType ||
- Left[Index].LocationKind != Right[Index].LocationKind ||
- Left[Index].LocationRegister != Right[Index].LocationRegister ||
- Left[Index].Extension != Right[Index].Extension ||
- Left[Index].CustomFlags != Right[Index].CustomFlags) {
- return false;
- }
- }
- return true;
-}
-
-[[nodiscard]] Expected<std::string>
-tailCallRejection(const CallLoweringRequest &Request, const ArgumentAssignmentResult &Arguments,
- std::span<const CCAssignment> Results,
- std::span<const ByValCopyAssignment> ByValCopies,
- const CallingConventionInfo &Convention) {
- if (!Convention.SupportsTailCalls)
- return std::string("the calling convention does not support tail calls");
- if (!Request.TailContext)
- return std::string("tail-call context is missing");
- const TailCallContext &Context = *Request.TailContext;
- if (Context.CallerConvention != Request.Convention)
- return std::string("caller and callee calling conventions differ");
- if (Context.CallerIsVariadic != Request.IsVariadic ||
- (Request.IsVariadic && Context.CallerFixedArgumentCount != Request.FixedArgumentCount)) {
- return std::string("caller and callee variadic signatures differ");
- }
- if (Context.CallerHasDynamicStackAllocation)
- return std::string("the caller has a dynamic stack allocation");
- if (!ByValCopies.empty())
- return std::string("tail calls cannot create byval copies");
- if (Context.CallerUsesStructReturn != hasStructReturn(Request.Arguments))
- return std::string("caller and callee sret conventions differ");
- if (Arguments.StackArea.SizeBytes > Context.ReusableIncomingArgumentBytes)
- return std::string("outgoing stack arguments exceed the reusable incoming area");
- if (std::ranges::any_of(Request.Results,
- [](const ABIValue &Value) { return !Value.Value.isNoRegister(); })) {
- return std::string("tail-call result signatures must not request local result copies");
- }
- auto CallerReturns = assignReturns(Context.CallerReturns, false, Convention);
- if (!CallerReturns)
- return std::unexpected(CallerReturns.error());
- if (!equivalentReturnLocations(Results, *CallerReturns))
- return std::string("caller and callee return locations differ");
- return std::string{};
-}
-
-[[nodiscard]] Expected<const CallingConventionInfo *>
-riscvConvention(CallingConventionID Convention) {
- CallingConvention TargetConvention = CallingConvention::None;
- if (Convention == callingConventionID(CallingConvention::LP64D))
- TargetConvention = CallingConvention::LP64D;
- else if (Convention == callingConventionID(CallingConvention::LP64))
- TargetConvention = CallingConvention::LP64;
- else
- return makeUnexpected(ErrorCode::Unsupported, "RISC-V backend only supports LP64 and LP64D");
- const CallingConventionInfo *Info = callingConvention(TargetConvention);
- if (Info == nullptr)
- return makeUnexpected(ErrorCode::InternalError,
- "RISC-V calling-convention target facts are unavailable");
- return Info;
-}
-
} // namespace
Expected<CallLoweringPlan>
diff --git a/backend/lib/Target/RISCV/GISel/RISCVCallLowering.h b/backend/lib/Target/RISCV/GISel/RISCVCallLowering.h
index dad0875..bc03fe8 100644
--- a/backend/lib/Target/RISCV/GISel/RISCVCallLowering.h
+++ b/backend/lib/Target/RISCV/GISel/RISCVCallLowering.h
@@ -3,6 +3,7 @@
#ifndef RCC_LIB_TARGET_RISCV_GISEL_RISCVCALLLOWERING_H
#define RCC_LIB_TARGET_RISCV_GISEL_RISCVCALLLOWERING_H
+#include "../RISCVCallingConv.h"
#include "rcc/CodeGen/TargetCallLowering.h"
#include "rcc/Target/RISCV/RISCVInstructions.h"
@@ -31,11 +32,6 @@ enum class CallValueTransform : std::uint8_t {
Bitcast,
};
-[[nodiscard]] constexpr CallingConventionID
-callingConventionID(CallingConvention Convention) noexcept {
- return CallingConventionID::fromValue(static_cast<std::uint32_t>(Convention));
-}
-
class RISCVCallLowering final : public TargetCallLowering {
public:
[[nodiscard]] Expected<CallLoweringPlan>
diff --git a/backend/lib/Target/RISCV/RISCVCallingConv.cpp b/backend/lib/Target/RISCV/RISCVCallingConv.cpp
new file mode 100644
index 0000000..26243f1
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVCallingConv.cpp
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "RISCVCallingConv.h"
+
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <ranges>
+
+namespace rcc::riscv {
+namespace {
+
+[[nodiscard]] Expected<Alignment> alignment(std::uint64_t Bytes) {
+ return Alignment::create(Bytes);
+}
+
+[[nodiscard]] Expected<std::uint64_t> alignTo(std::uint64_t Value, std::uint64_t AlignmentBytes) {
+ const std::uint64_t Remainder = Value % AlignmentBytes;
+ if (Remainder == 0)
+ return Value;
+ const std::uint64_t Increment = AlignmentBytes - Remainder;
+ if (Value > std::numeric_limits<std::uint64_t>::max() - Increment) {
+ return makeUnexpected(ErrorCode::Unsupported, "RISC-V ABI stack-area size overflows uint64");
+ }
+ return Value + Increment;
+}
+
+[[nodiscard]] std::uint64_t valueSizeBytes(LowLevelType Type) noexcept {
+ return (static_cast<std::uint64_t>(Type.scalarBitWidth()) + 7U) / 8U;
+}
+
+[[nodiscard]] bool isSupportedFloat(const ABIValue &Value) noexcept {
+ return Value.Category == ABIValueCategory::FloatingPoint &&
+ (Value.Type.scalarBitWidth() == 32 || Value.Type.scalarBitWidth() == 64);
+}
+
+[[nodiscard]] Expected<PhysicalRegister> floatingRegisterView(PhysicalRegister DoubleView,
+ const ABIValue &Value) {
+ if (!isSupportedFloat(Value)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "RISC-V floating-register view requires f32 or f64");
+ }
+ if (Value.Type.scalarBitWidth() == 64)
+ return DoubleView;
+
+ constexpr std::uint32_t FirstDouble = static_cast<std::uint32_t>(PhysicalRegisterID::F0_D);
+ constexpr std::uint32_t LastDouble = static_cast<std::uint32_t>(PhysicalRegisterID::F31_D);
+ constexpr std::uint32_t FirstSingle = static_cast<std::uint32_t>(PhysicalRegisterID::F0_F);
+ const std::uint32_t Number = DoubleView.number();
+ if (Number < FirstDouble || Number > LastDouble) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "RISC-V ABI floating register is not an FPR64 view");
+ }
+ return PhysicalRegister::create(FirstSingle + Number - FirstDouble);
+}
+
+[[nodiscard]] bool requiresIntegerTransport(const ABIValue &Value) noexcept {
+ return !isSupportedFloat(Value) || Value.Flags.has(ABIValueFlag::ByVal) ||
+ Value.Flags.has(ABIValueFlag::StructReturn) || Value.Flags.has(ABIValueFlag::Indirect);
+}
+
+[[nodiscard]] Expected<void> verifyABIValue(const ABIValue &Value, bool RequireRegister) {
+ if (RequireRegister && Value.Value.isNoRegister()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "RISC-V ABI value requires a source or destination register");
+ }
+ if (!Value.Type.isValid() || !Value.Type.isScalarValue() || Value.Type.scalarBitWidth() > 64) {
+ return makeUnexpected(
+ ErrorCode::Unsupported,
+ "RISC-V ABI accepts explicitly classified integer, pointer, f32, and f64 scalar fragments");
+ }
+ if (Value.Category == ABIValueCategory::Invalid || Value.Category >= ABIValueCategory::Count) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "RISC-V ABI value category is invalid");
+ }
+ if (Value.Category == ABIValueCategory::FloatingPoint && !isSupportedFloat(Value)) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V LP64 ABIs only support f32 and f64 floating fragments");
+ }
+ if ((Value.Category == ABIValueCategory::Pointer) != Value.Type.isPointer()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "RISC-V ABI pointer category must match its pointer LowLevelType");
+ }
+ if (Value.Type.isPointer() && Value.Type.scalarBitWidth() != 64) {
+ return makeUnexpected(ErrorCode::Unsupported, "RISC-V LP64 ABIs require 64-bit pointers");
+ }
+ if (Value.Category == ABIValueCategory::Integer && Value.Type.scalarBitWidth() < 64) {
+ if (Value.Extension == ABIExtensionKind::None) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "narrow RISC-V ABI integers require an explicit extension kind");
+ }
+ } else if (Value.Extension != ABIExtensionKind::None) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "only narrow integer ABI fragments may carry an extension kind");
+ }
+ if (Value.Extension >= ABIExtensionKind::Count || Value.FragmentCount == 0 ||
+ Value.FragmentIndex >= Value.FragmentCount || Value.FragmentCount > 2) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "RISC-V ABI fragment coordinates or extension are invalid");
+ }
+
+ const bool IsByVal = Value.Flags.has(ABIValueFlag::ByVal);
+ const bool IsSRet = Value.Flags.has(ABIValueFlag::StructReturn);
+ const bool IsIndirect = Value.Flags.has(ABIValueFlag::Indirect);
+ const unsigned SpecialFlagCount = static_cast<unsigned>(IsByVal) + static_cast<unsigned>(IsSRet) +
+ static_cast<unsigned>(IsIndirect);
+ if (SpecialFlagCount > 1) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "byval, sret, and indirect ABI flags are mutually exclusive");
+ }
+ if ((IsByVal || IsSRet || IsIndirect) &&
+ (!Value.Type.isPointer() || Value.FragmentCount != 1 || Value.FragmentIndex != 0 ||
+ Value.FragmentOffsetBytes != 0)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "byval, sret, and indirect values require one explicit pointer fragment");
+ }
+ if (IsByVal) {
+ if (Value.ByValSizeBytes == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "byval arguments require a nonzero copy size");
+ }
+ } else if (Value.ByValSizeBytes != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "non-byval values cannot carry a byval copy size");
+ }
+ return {};
+}
+
+} // namespace
+
+[[nodiscard]] Expected<std::size_t> verifyValueSequence(std::span<const ABIValue> Values,
+ bool RequireRegisters) {
+ std::size_t ExpectedOriginal = 0;
+ std::size_t Index = 0;
+ while (Index < Values.size()) {
+ const ABIValue &First = Values[Index];
+ auto ValueVerification = verifyABIValue(First, RequireRegisters);
+ if (!ValueVerification)
+ return std::unexpected(ValueVerification.error());
+ if (First.OriginalValue != ExpectedOriginal || First.FragmentIndex != 0 ||
+ Index + First.FragmentCount > Values.size()) {
+ return makeUnexpected(
+ ErrorCode::InvalidArgument,
+ "RISC-V ABI values must form contiguous, zero-based original-value groups");
+ }
+
+ std::uint64_t PreviousEnd = 0;
+ for (std::size_t Fragment = 0; Fragment < First.FragmentCount; ++Fragment) {
+ const ABIValue &Value = Values[Index + Fragment];
+ auto FragmentVerification = verifyABIValue(Value, RequireRegisters);
+ if (!FragmentVerification)
+ return std::unexpected(FragmentVerification.error());
+ if (Value.OriginalValue != First.OriginalValue || Value.FragmentIndex != Fragment ||
+ Value.FragmentCount != First.FragmentCount ||
+ Value.OriginalAlignment != First.OriginalAlignment || Value.Flags != First.Flags ||
+ Value.ByValSizeBytes != First.ByValSizeBytes ||
+ Value.ByValAlignment != First.ByValAlignment || Value.FragmentOffsetBytes < PreviousEnd) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "RISC-V ABI fragments of one value have inconsistent metadata");
+ }
+ PreviousEnd =
+ static_cast<std::uint64_t>(Value.FragmentOffsetBytes) + valueSizeBytes(Value.Type);
+ }
+ Index += First.FragmentCount;
+ ++ExpectedOriginal;
+ }
+ return ExpectedOriginal;
+}
+
+namespace {
+
+[[nodiscard]] CCAssignmentFlags assignmentFlags(const ABIValue &Value, bool IsVariadic,
+ bool IsBitcast) {
+ CCAssignmentFlags Result;
+ if (IsVariadic)
+ Result.set(CCAssignmentFlag::Variadic);
+ if (Value.Flags.has(ABIValueFlag::ByVal))
+ Result.set(CCAssignmentFlag::ByVal);
+ if (Value.Flags.has(ABIValueFlag::StructReturn))
+ Result.set(CCAssignmentFlag::StructReturn);
+ if (Value.Flags.has(ABIValueFlag::Indirect))
+ Result.set(CCAssignmentFlag::Indirect);
+ if (Value.Flags.has(ABIValueFlag::InRegister))
+ Result.set(CCAssignmentFlag::InRegister);
+ if (IsBitcast)
+ Result.set(CCAssignmentFlag::Bitcast);
+ return Result;
+}
+
+[[nodiscard]] CCLocationKind locationKind(const ABIValue &Value, bool IsRegister) noexcept {
+ if (Value.Flags.has(ABIValueFlag::Indirect))
+ return CCLocationKind::Indirect;
+ return IsRegister ? CCLocationKind::Register : CCLocationKind::Stack;
+}
+
+struct AssignmentState final {
+ std::size_t GPR = 0;
+ std::size_t FPR = 0;
+ std::uint64_t StackOffset = 0;
+ bool VariadicStackOnly = false;
+};
+
+[[nodiscard]] Expected<CCAssignment>
+registerAssignment(const ABIValue &Value, std::size_t ValueIndex, PhysicalRegister Location,
+ LowLevelType LocationType, bool IsVariadic, bool IsBitcast) {
+ auto Align =
+ alignment(std::min<std::uint64_t>(Value.OriginalAlignment.bytes(), ABIStackAlignmentBytes));
+ if (!Align)
+ return std::unexpected(Align.error());
+ return CCAssignment{
+ .ValueIndex = ValueIndex,
+ .OriginalValue = Value.Value,
+ .OriginalValueIndex = Value.OriginalValue,
+ .FragmentIndex = Value.FragmentIndex,
+ .FragmentCount = Value.FragmentCount,
+ .ValueType = Value.Type,
+ .LocationType = LocationType,
+ .LocationKind = locationKind(Value, true),
+ .LocationRegister = Location,
+ .StackOffsetBytes = std::nullopt,
+ .LocationSizeBytes = valueSizeBytes(LocationType),
+ .StackSlotSizeBytes = XLenBytes,
+ .LocationAlignment = *Align,
+ .Extension = Value.Extension,
+ .CustomFlags = assignmentFlags(Value, IsVariadic, IsBitcast),
+ };
+}
+
+[[nodiscard]] Expected<CCAssignment> stackAssignment(const ABIValue &Value, std::size_t ValueIndex,
+ std::uint64_t StackOffset,
+ LowLevelType LocationType, bool IsVariadic,
+ std::uint64_t StackAlignment) {
+ auto Align = alignment(StackAlignment);
+ if (!Align)
+ return std::unexpected(Align.error());
+ return CCAssignment{
+ .ValueIndex = ValueIndex,
+ .OriginalValue = Value.Value,
+ .OriginalValueIndex = Value.OriginalValue,
+ .FragmentIndex = Value.FragmentIndex,
+ .FragmentCount = Value.FragmentCount,
+ .ValueType = Value.Type,
+ .LocationType = LocationType,
+ .LocationKind = locationKind(Value, false),
+ .LocationRegister = std::nullopt,
+ .StackOffsetBytes = StackOffset,
+ .LocationSizeBytes = valueSizeBytes(LocationType),
+ .StackSlotSizeBytes = XLenBytes,
+ .LocationAlignment = *Align,
+ .Extension = Value.Extension,
+ .CustomFlags = assignmentFlags(Value, IsVariadic, false),
+ };
+}
+
+} // namespace
+
+[[nodiscard]] Expected<ArgumentAssignmentResult>
+assignArguments(std::span<const ABIValue> Values, std::size_t FixedArgumentCount, bool IsVariadic,
+ const CallingConventionInfo &Convention) {
+ auto I64 = LowLevelType::makeCategoryNeutralScalar(64);
+ auto StackAlign = alignment(Convention.StackArgumentAlignmentBytes);
+ if (!I64 || !StackAlign)
+ return makeUnexpected(ErrorCode::InternalError, "RISC-V ABI primitive type facts are invalid");
+
+ ArgumentAssignmentResult Result;
+ AssignmentState State;
+ State.StackOffset = Convention.ShadowSpaceBytes;
+ Result.Assignments.reserve(Values.size());
+
+ std::size_t Index = 0;
+ while (Index < Values.size()) {
+ const ABIValue &First = Values[Index];
+ const std::size_t GroupSize = First.FragmentCount;
+ const bool VariadicArgument = IsVariadic && First.OriginalValue >= FixedArgumentCount;
+ bool HasPreferredFloat = false;
+ std::size_t RequiredGPRs = 0;
+ std::size_t RequiredFPRs = 0;
+ for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
+ const ABIValue &Value = Values[Index + Fragment];
+ if (!VariadicArgument && !requiresIntegerTransport(Value)) {
+ HasPreferredFloat = true;
+ ++RequiredFPRs;
+ } else {
+ ++RequiredGPRs;
+ }
+ }
+
+ const bool CanUsePreferredAggregate =
+ HasPreferredFloat &&
+ State.GPR + RequiredGPRs <= Convention.IntegerArgumentRegisters.size() &&
+ State.FPR + RequiredFPRs <= Convention.FloatingArgumentRegisters.size();
+ if (CanUsePreferredAggregate) {
+ for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
+ const ABIValue &Value = Values[Index + Fragment];
+ Expected<CCAssignment> Assignment = makeUnexpected(
+ ErrorCode::InternalError, "RISC-V ABI argument assignment was not initialized");
+ if (requiresIntegerTransport(Value)) {
+ Assignment = registerAssignment(Value, Index + Fragment,
+ Convention.IntegerArgumentRegisters[State.GPR++], *I64,
+ false, false);
+ } else {
+ auto View =
+ floatingRegisterView(Convention.FloatingArgumentRegisters[State.FPR++], Value);
+ if (!View)
+ return std::unexpected(View.error());
+ Assignment = registerAssignment(Value, Index + Fragment, *View, Value.Type, false, false);
+ }
+ if (!Assignment)
+ return std::unexpected(Assignment.error());
+ Result.Assignments.push_back(*Assignment);
+ }
+ Index += GroupSize;
+ continue;
+ }
+
+ const bool RequiresAlignedPair =
+ VariadicArgument && GroupSize == 2 && First.OriginalAlignment.bytes() >= 16;
+ if (RequiresAlignedPair && State.GPR % 2 != 0 &&
+ State.GPR < Convention.IntegerArgumentRegisters.size()) {
+ ++State.GPR;
+ }
+ bool ForceEntireGroupToStack = State.VariadicStackOnly;
+ if (RequiresAlignedPair && State.GPR + GroupSize > Convention.IntegerArgumentRegisters.size()) {
+ ForceEntireGroupToStack = true;
+ State.GPR = Convention.IntegerArgumentRegisters.size();
+ }
+
+ bool FirstStackFragment = true;
+ for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) {
+ const ABIValue &Value = Values[Index + Fragment];
+ const bool HasGPR =
+ !ForceEntireGroupToStack && State.GPR < Convention.IntegerArgumentRegisters.size();
+ if (HasGPR) {
+ const bool Bitcast = isSupportedFloat(Value);
+ auto Assignment = registerAssignment(Value, Index + Fragment,
+ Convention.IntegerArgumentRegisters[State.GPR++], *I64,
+ VariadicArgument, Bitcast);
+ if (!Assignment)
+ return std::unexpected(Assignment.error());
+ Result.Assignments.push_back(*Assignment);
+ continue;
+ }
+
+ if (Value.Flags.has(ABIValueFlag::InRegister)) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V inreg argument cannot be assigned to the stack");
+ }
+ std::uint64_t RequiredAlignment = XLenBytes;
+ if (FirstStackFragment && ForceEntireGroupToStack) {
+ RequiredAlignment = std::min<std::uint64_t>(
+ std::max<std::uint64_t>(XLenBytes, First.OriginalAlignment.bytes()),
+ ABIStackAlignmentBytes);
+ }
+ auto AlignedOffset = alignTo(State.StackOffset, RequiredAlignment);
+ if (!AlignedOffset)
+ return std::unexpected(AlignedOffset.error());
+ State.StackOffset = *AlignedOffset;
+ const LowLevelType LocationType = isSupportedFloat(Value) ? Value.Type : *I64;
+ auto Assignment = stackAssignment(Value, Index + Fragment, State.StackOffset, LocationType,
+ VariadicArgument, RequiredAlignment);
+ if (!Assignment)
+ return std::unexpected(Assignment.error());
+ Result.Assignments.push_back(*Assignment);
+ if (State.StackOffset > std::numeric_limits<std::uint64_t>::max() - XLenBytes) {
+ return makeUnexpected(ErrorCode::Unsupported, "RISC-V argument area overflows uint64");
+ }
+ State.StackOffset += XLenBytes;
+ FirstStackFragment = false;
+ if (VariadicArgument)
+ State.VariadicStackOnly = true;
+ }
+ Index += GroupSize;
+ }
+
+ auto FinalSize = alignTo(State.StackOffset, Convention.StackArgumentAlignmentBytes);
+ if (!FinalSize)
+ return std::unexpected(FinalSize.error());
+ Result.StackArea = StackArgumentArea{*FinalSize, Convention.ShadowSpaceBytes, *StackAlign};
+ Result.FirstUnallocatedGPR = State.GPR;
+ return Result;
+}
+
+[[nodiscard]] Expected<std::vector<CCAssignment>>
+assignReturns(std::span<const ABIValue> Values, bool RequireRegisters,
+ const CallingConventionInfo &Convention) {
+ auto Sequence = verifyValueSequence(Values, RequireRegisters);
+ if (!Sequence)
+ return std::unexpected(Sequence.error());
+ if (Values.size() > 2) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V direct returns are limited to two explicit fragments");
+ }
+ auto I64 = LowLevelType::makeCategoryNeutralScalar(64);
+ if (!I64)
+ return std::unexpected(I64.error());
+
+ std::vector<CCAssignment> Result;
+ Result.reserve(Values.size());
+ std::size_t GPR = 0;
+ std::size_t FPR = 0;
+ for (std::size_t Index = 0; Index < Values.size(); ++Index) {
+ const ABIValue &Value = Values[Index];
+ if (Value.Flags.rawValue() != 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "return fragments cannot carry argument-only ABI flags");
+ }
+ const bool UseFloatingRegister =
+ isSupportedFloat(Value) && FPR < Convention.FloatingReturnRegisters.size();
+ Expected<CCAssignment> Assignment = makeUnexpected(
+ ErrorCode::Unsupported, "RISC-V calling convention has no return register available");
+ if (UseFloatingRegister) {
+ auto View = floatingRegisterView(Convention.FloatingReturnRegisters[FPR++], Value);
+ if (!View)
+ return std::unexpected(View.error());
+ Assignment = registerAssignment(Value, Index, *View, Value.Type, false, false);
+ } else if (GPR < Convention.IntegerReturnRegisters.size()) {
+ Assignment = registerAssignment(Value, Index, Convention.IntegerReturnRegisters[GPR++], *I64,
+ false, isSupportedFloat(Value));
+ }
+ if (!Assignment)
+ return std::unexpected(Assignment.error());
+ Result.push_back(*Assignment);
+ }
+ return Result;
+}
+
+[[nodiscard]] bool hasStructReturn(std::span<const ABIValue> Arguments) noexcept {
+ return std::ranges::any_of(
+ Arguments, [](const ABIValue &Value) { return Value.Flags.has(ABIValueFlag::StructReturn); });
+}
+
+namespace {
+
+[[nodiscard]] bool equivalentReturnLocations(std::span<const CCAssignment> Left,
+ std::span<const CCAssignment> Right) noexcept {
+ if (Left.size() != Right.size())
+ return false;
+ for (std::size_t Index = 0; Index < Left.size(); ++Index) {
+ if (Left[Index].ValueType != Right[Index].ValueType ||
+ Left[Index].LocationType != Right[Index].LocationType ||
+ Left[Index].LocationKind != Right[Index].LocationKind ||
+ Left[Index].LocationRegister != Right[Index].LocationRegister ||
+ Left[Index].Extension != Right[Index].Extension ||
+ Left[Index].CustomFlags != Right[Index].CustomFlags) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+[[nodiscard]] Expected<std::string>
+tailCallRejection(const CallLoweringRequest &Request, const ArgumentAssignmentResult &Arguments,
+ std::span<const CCAssignment> Results,
+ std::span<const ByValCopyAssignment> ByValCopies,
+ const CallingConventionInfo &Convention) {
+ if (!Convention.SupportsTailCalls)
+ return std::string("the calling convention does not support tail calls");
+ if (!Request.TailContext)
+ return std::string("tail-call context is missing");
+ const TailCallContext &Context = *Request.TailContext;
+ if (Context.CallerConvention != Request.Convention)
+ return std::string("caller and callee calling conventions differ");
+ if (Context.CallerIsVariadic != Request.IsVariadic ||
+ (Request.IsVariadic && Context.CallerFixedArgumentCount != Request.FixedArgumentCount)) {
+ return std::string("caller and callee variadic signatures differ");
+ }
+ if (Context.CallerHasDynamicStackAllocation)
+ return std::string("the caller has a dynamic stack allocation");
+ if (!ByValCopies.empty())
+ return std::string("tail calls cannot create byval copies");
+ if (Context.CallerUsesStructReturn != hasStructReturn(Request.Arguments))
+ return std::string("caller and callee sret conventions differ");
+ if (Arguments.StackArea.SizeBytes > Context.ReusableIncomingArgumentBytes)
+ return std::string("outgoing stack arguments exceed the reusable incoming area");
+ if (std::ranges::any_of(Request.Results,
+ [](const ABIValue &Value) { return !Value.Value.isNoRegister(); })) {
+ return std::string("tail-call result signatures must not request local result copies");
+ }
+ auto CallerReturns = assignReturns(Context.CallerReturns, false, Convention);
+ if (!CallerReturns)
+ return std::unexpected(CallerReturns.error());
+ if (!equivalentReturnLocations(Results, *CallerReturns))
+ return std::string("caller and callee return locations differ");
+ return std::string{};
+}
+
+[[nodiscard]] Expected<const CallingConventionInfo *>
+riscvConvention(CallingConventionID Convention) {
+ CallingConvention TargetConvention = CallingConvention::None;
+ if (Convention == callingConventionID(CallingConvention::LP64D))
+ TargetConvention = CallingConvention::LP64D;
+ else if (Convention == callingConventionID(CallingConvention::LP64))
+ TargetConvention = CallingConvention::LP64;
+ else
+ return makeUnexpected(ErrorCode::Unsupported, "RISC-V backend only supports LP64 and LP64D");
+ const CallingConventionInfo *Info = callingConvention(TargetConvention);
+ if (Info == nullptr)
+ return makeUnexpected(ErrorCode::InternalError,
+ "RISC-V calling-convention target facts are unavailable");
+ return Info;
+}
+
+} // namespace rcc::riscv
diff --git a/backend/lib/Target/RISCV/RISCVCallingConv.h b/backend/lib/Target/RISCV/RISCVCallingConv.h
new file mode 100644
index 0000000..ba2dcf2
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVCallingConv.h
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_LIB_TARGET_RISCV_RISCVCALLINGCONV_H
+#define RCC_LIB_TARGET_RISCV_RISCVCALLINGCONV_H
+
+#include "rcc/CodeGen/TargetCallLowering.h"
+#include "rcc/Target/RISCV/RISCVInstructions.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string>
+#include <vector>
+
+namespace rcc::riscv {
+
+struct CallingConventionInfo;
+
+inline constexpr std::uint64_t XLenBytes = 8;
+inline constexpr std::uint64_t ABIStackAlignmentBytes = 16;
+
+[[nodiscard]] constexpr CallingConventionID
+callingConventionID(CallingConvention Convention) noexcept {
+ return CallingConventionID::fromValue(static_cast<std::uint32_t>(Convention));
+}
+
+struct ArgumentAssignmentResult final {
+ std::vector<CCAssignment> Assignments;
+ StackArgumentArea StackArea;
+ std::size_t FirstUnallocatedGPR = 0;
+};
+
+[[nodiscard]] Expected<std::size_t> verifyValueSequence(std::span<const ABIValue> Values,
+ bool RequireRegisters);
+[[nodiscard]] Expected<ArgumentAssignmentResult>
+assignArguments(std::span<const ABIValue> Values, std::size_t FixedArgumentCount, bool IsVariadic,
+ const CallingConventionInfo &Convention);
+[[nodiscard]] Expected<std::vector<CCAssignment>>
+assignReturns(std::span<const ABIValue> Values, bool RequireRegisters,
+ const CallingConventionInfo &Convention);
+[[nodiscard]] bool hasStructReturn(std::span<const ABIValue> Arguments) noexcept;
+[[nodiscard]] Expected<std::string>
+tailCallRejection(const CallLoweringRequest &Request, const ArgumentAssignmentResult &Arguments,
+ std::span<const CCAssignment> Results,
+ std::span<const ByValCopyAssignment> ByValCopies,
+ const CallingConventionInfo &Convention);
+[[nodiscard]] Expected<const CallingConventionInfo *>
+riscvConvention(CallingConventionID Convention);
+
+} // namespace rcc::riscv
+
+#endif // RCC_LIB_TARGET_RISCV_RISCVCALLINGCONV_H
diff --git a/backend/lib/Target/RISCV/RISCVPipeline.cpp b/backend/lib/Target/RISCV/RISCVPipeline.cpp
index 84fe012..61b3888 100644
--- a/backend/lib/Target/RISCV/RISCVPipeline.cpp
+++ b/backend/lib/Target/RISCV/RISCVPipeline.cpp
@@ -5,6 +5,7 @@
#include "GISel/RISCVCallLowering.h"
#include "GISel/RISCVRegisterBankInfo.h"
#include "RISCV.h"
+#include "RISCVCallingConv.h"
#include "RISCVInstrInfo.h"
#include "rcc/CodeGen/CFIInstrInserter.h"
#include "rcc/CodeGen/FinalizeISel.h"