From e16de95dce32f077361577377d5c52fc4ad78339 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Fri, 7 Aug 2026 02:15:09 +0800 Subject: [RISCV] Add generated RV64GC register description Add an RCC-owned build-time target description generator and the initial RV64GC register model. Keep ABI aliases separate from canonical MIR register spelling. --- backend/CMakeLists.txt | 2 + backend/include/rcc/CodeGen/TargetRegisterInfo.h | 7 + backend/include/rcc/Target/RISCV/RISCVTarget.h | 23 + backend/lib/CodeGen/TargetDescription.cpp | 17 +- backend/lib/CodeGen/TargetRegisterInfo.cpp | 30 +- backend/lib/Target/CMakeLists.txt | 3 + .../Description/TargetDescriptionGenerator.cpp | 1150 ++++++++++++++++++++ .../Description/TargetDescriptionGenerator.h | 34 + backend/lib/Target/RISCV/CMakeLists.txt | 79 ++ backend/lib/Target/RISCV/RISCV.rccdesc | 220 ++++ backend/lib/Target/RISCV/RISCVTarget.cpp | 103 ++ backend/tools/CMakeLists.txt | 3 + backend/tools/DescriptionGenerator/CMakeLists.txt | 39 + .../DescriptionGeneratorMain.cpp | 97 ++ .../unittests/CodeGen/TargetRegisterInfoTest.cpp | 13 + backend/unittests/Target/CMakeLists.txt | 21 +- .../unittests/Target/DescriptionGeneratorTest.cpp | 118 ++ backend/unittests/Target/RISCVTargetTest.cpp | 109 ++ backend/unittests/Target/TestTarget.cpp | 3 +- 19 files changed, 2062 insertions(+), 9 deletions(-) create mode 100644 backend/include/rcc/Target/RISCV/RISCVTarget.h create mode 100644 backend/lib/Target/CMakeLists.txt create mode 100644 backend/lib/Target/Description/TargetDescriptionGenerator.cpp create mode 100644 backend/lib/Target/Description/TargetDescriptionGenerator.h create mode 100644 backend/lib/Target/RISCV/CMakeLists.txt create mode 100644 backend/lib/Target/RISCV/RISCV.rccdesc create mode 100644 backend/lib/Target/RISCV/RISCVTarget.cpp create mode 100644 backend/tools/CMakeLists.txt create mode 100644 backend/tools/DescriptionGenerator/CMakeLists.txt create mode 100644 backend/tools/DescriptionGenerator/DescriptionGeneratorMain.cpp create mode 100644 backend/unittests/Target/DescriptionGeneratorTest.cpp create mode 100644 backend/unittests/Target/RISCVTargetTest.cpp diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt index 0f983d0..aa4afe6 100644 --- a/backend/CMakeLists.txt +++ b/backend/CMakeLists.txt @@ -54,6 +54,8 @@ endif() rcc_backend_initialize_toolchain() add_subdirectory(lib) +add_subdirectory(tools) +add_subdirectory(lib/Target) if(RCC_BACKEND_BUILD_TESTS) enable_testing() diff --git a/backend/include/rcc/CodeGen/TargetRegisterInfo.h b/backend/include/rcc/CodeGen/TargetRegisterInfo.h index 9357eb2..ed33263 100644 --- a/backend/include/rcc/CodeGen/TargetRegisterInfo.h +++ b/backend/include/rcc/CodeGen/TargetRegisterInfo.h @@ -38,6 +38,8 @@ struct PhysicalRegisterDesc final { std::vector Units; bool IsConstant = false; bool IsArtificial = false; + bool IsReserved = false; + std::vector AlternativeNames; }; struct SubRegIndexDesc final { @@ -128,6 +130,8 @@ public: [[nodiscard]] const RegisterUnitDesc *registerUnit(RegisterUnitID ID) const noexcept; [[nodiscard]] const PhysicalRegisterDesc *physicalRegister(PhysicalRegister Reg) const noexcept; [[nodiscard]] const PhysicalRegisterDesc *physicalRegister(std::string_view Name) const noexcept; + [[nodiscard]] const PhysicalRegisterDesc * + physicalRegisterByAlternativeName(std::string_view Name) const noexcept; [[nodiscard]] const SubRegIndexDesc *subRegIndex(SubRegIndex Index) const noexcept; [[nodiscard]] const SubRegIndexDesc *subRegIndex(std::string_view Name) const noexcept; [[nodiscard]] const RegisterClassDesc *registerClass(RegisterClassID ID) const noexcept; @@ -146,6 +150,8 @@ public: return physicalRegister(Reg) != nullptr; } + [[nodiscard]] bool isReservedRegister(PhysicalRegister Reg) const noexcept; + [[nodiscard]] LaneMask registerLaneMask(PhysicalRegister Reg) const noexcept; [[nodiscard]] bool isValidLaneMask(PhysicalRegister Reg, LaneMask Lanes) const noexcept; [[nodiscard]] bool regsOverlap(PhysicalRegister Left, PhysicalRegister Right) const noexcept; @@ -186,6 +192,7 @@ private: std::unordered_map UnitToIndex; std::unordered_map RegisterToIndex; std::unordered_map RegisterNameToIndex; + std::unordered_map AlternativeRegisterNameToIndex; std::unordered_map SubRegIndexToIndex; std::unordered_map SubRegNameToIndex; std::unordered_map ClassToIndex; diff --git a/backend/include/rcc/Target/RISCV/RISCVTarget.h b/backend/include/rcc/Target/RISCV/RISCVTarget.h new file mode 100644 index 0000000..948e22d --- /dev/null +++ b/backend/include/rcc/Target/RISCV/RISCVTarget.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVTARGET_H +#define RCC_TARGET_RISCV_RISCVTARGET_H + +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/Support/Error.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" + +#include +#include + +namespace rcc::riscv { + +[[nodiscard]] Expected physicalRegister(PhysicalRegisterID ID); +[[nodiscard]] Expected createRegisterInfo(); +[[nodiscard]] Expected createMIRTextInfo(); +[[nodiscard]] std::shared_ptr createCallPreservedMask(); + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVTARGET_H diff --git a/backend/lib/CodeGen/TargetDescription.cpp b/backend/lib/CodeGen/TargetDescription.cpp index b9f7606..e2bbe8a 100644 --- a/backend/lib/CodeGen/TargetDescription.cpp +++ b/backend/lib/CodeGen/TargetDescription.cpp @@ -175,10 +175,10 @@ Expected TargetDescription::validateVirtualRegister(Register Reg, return makeUnexpected(ErrorCode::VerificationError, "virtual register allocation hint names an unknown physical register"); } - if (HintDesc->IsConstant || HintDesc->IsArtificial) { + if (HintDesc->IsConstant || HintDesc->IsArtificial || HintDesc->IsReserved) { return makeUnexpected( ErrorCode::VerificationError, - "virtual register allocation hint names a constant or artificial register"); + "virtual register allocation hint names a constant, artificial, or reserved register"); } if (Info.RegClass && !Registers.registerClassContains(*Info.RegClass, Hint)) { return makeUnexpected(ErrorCode::VerificationError, @@ -283,7 +283,8 @@ Expected TargetDescription::validateTargetOperand(const OperandDescriptor return Registers.regsOverlap(Reserved, PhysRegValue); }); if (Operand.registerFlags().has(RegisterOperandFlag::Renamable) && - (IsReservedOrAlias || PhysRegDesc->IsConstant || PhysRegDesc->IsArtificial)) { + (IsReservedOrAlias || PhysRegDesc->IsConstant || PhysRegDesc->IsArtificial || + PhysRegDesc->IsReserved)) { return makeUnexpected(ErrorCode::VerificationError, "fixed physical register operand cannot be renamable"); } @@ -718,9 +719,15 @@ Expected TargetDescription::validateRegisterInfo(const MachineRegisterInfo if (!Validation) return std::unexpected(Validation.error()); for (const PhysicalRegister Hint : Info.AllocationHints) { - if (std::ranges::any_of(RegInfo.reservedRegisters(), [this, Hint](PhysicalRegister Reserved) { + const bool IsStaticReservedOrAlias = std::ranges::any_of( + Registers.physicalRegisters(), [this, Hint](const PhysicalRegisterDesc &Register) { + return Register.IsReserved && Registers.regsOverlap(Register.Reg, Hint); + }); + const bool IsFunctionReservedOrAlias = + std::ranges::any_of(RegInfo.reservedRegisters(), [this, Hint](PhysicalRegister Reserved) { return Registers.regsOverlap(Reserved, Hint); - })) { + }); + if (IsStaticReservedOrAlias || IsFunctionReservedOrAlias) { return makeUnexpected( ErrorCode::VerificationError, "virtual register allocation hint overlaps a reserved physical register"); diff --git a/backend/lib/CodeGen/TargetRegisterInfo.cpp b/backend/lib/CodeGen/TargetRegisterInfo.cpp index 3ece639..42ae935 100644 --- a/backend/lib/CodeGen/TargetRegisterInfo.cpp +++ b/backend/lib/CodeGen/TargetRegisterInfo.cpp @@ -75,10 +75,19 @@ Expected TargetRegisterInfo::create(TargetRegisterInfoDesc D } std::string MIRName = lowerASCII(Register.Name); if (!Result.RegisterToIndex.emplace(Register.Reg, Index).second || + Result.AlternativeRegisterNameToIndex.contains(MIRName) || !Result.RegisterNameToIndex.emplace(MIRName, Index).second) { return invalidDescription( "physical register numbers and names must be unique case-insensitively"); } + for (const std::string &AlternativeName : Register.AlternativeNames) { + const std::string NormalizedName = lowerASCII(AlternativeName); + if (AlternativeName.empty() || Result.RegisterNameToIndex.contains(NormalizedName) || + !Result.AlternativeRegisterNameToIndex.emplace(NormalizedName, Index).second) { + return invalidDescription( + "physical register alternative names must be nonempty and unique case-insensitively"); + } + } Result.RegisterMIRNames.push_back(std::move(MIRName)); } @@ -275,9 +284,13 @@ Expected TargetRegisterInfo::create(TargetRegisterInfoDesc D std::unordered_set AllocationRegisters; for (const PhysicalRegister Reg : Class.AllocationOrder) { - if (!Members.contains(Reg) || !AllocationRegisters.insert(Reg).second) { + const PhysicalRegisterDesc *Register = Result.physicalRegister(Reg); + const bool IsUnavailable = + Register != nullptr && + (Register->IsConstant || Register->IsArtificial || Register->IsReserved); + if (!Members.contains(Reg) || IsUnavailable || !AllocationRegisters.insert(Reg).second) { return invalidDescription( - "register class allocation order must contain unique class members"); + "register class allocation order must contain unique allocatable class members"); } } if ((Class.Allocatable && Class.AllocationOrder.empty()) || @@ -453,6 +466,19 @@ TargetRegisterInfo::physicalRegister(std::string_view Name) const noexcept { : &Description.PhysicalRegisters[Position->second]; } +const PhysicalRegisterDesc * +TargetRegisterInfo::physicalRegisterByAlternativeName(std::string_view Name) const noexcept { + const auto Position = AlternativeRegisterNameToIndex.find(lowerASCII(Name)); + return Position == AlternativeRegisterNameToIndex.end() + ? nullptr + : &Description.PhysicalRegisters[Position->second]; +} + +bool TargetRegisterInfo::isReservedRegister(PhysicalRegister Reg) const noexcept { + const PhysicalRegisterDesc *Register = physicalRegister(Reg); + return Register != nullptr && Register->IsReserved; +} + const SubRegIndexDesc *TargetRegisterInfo::subRegIndex(SubRegIndex Index) const noexcept { const auto Position = SubRegIndexToIndex.find(Index); return Position == SubRegIndexToIndex.end() ? nullptr diff --git a/backend/lib/Target/CMakeLists.txt b/backend/lib/Target/CMakeLists.txt new file mode 100644 index 0000000..341d98d --- /dev/null +++ b/backend/lib/Target/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +add_subdirectory(RISCV) diff --git a/backend/lib/Target/Description/TargetDescriptionGenerator.cpp b/backend/lib/Target/Description/TargetDescriptionGenerator.cpp new file mode 100644 index 0000000..259c9fe --- /dev/null +++ b/backend/lib/Target/Description/TargetDescriptionGenerator.cpp @@ -0,0 +1,1150 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TargetDescriptionGenerator.h" + +#include "rcc/Support/YAML.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::targetgen { +namespace { + +struct SourceTarget final { + std::string Namespace; + std::string Name; + std::string Triple; + std::string CPU; + std::string ABI; + std::string DataLayout; + std::vector Features; + TextRange Range; +}; + +struct SourceRegisterUnit final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + TextRange Range; +}; + +struct SourceRegisterUnitLane final { + std::string UnitSymbol; + std::uint64_t Lanes = 0; + TextRange Range; +}; + +struct SourceRegister final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t Encoding = 0; + std::uint32_t DwarfRegister = 0; + std::vector Units; + bool IsConstant = false; + bool IsArtificial = false; + bool IsReserved = false; + std::vector AlternativeNames; + TextRange Range; +}; + +struct SourceRegisterBank final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t MaximumSizeInBits = 0; + TextRange Range; +}; + +struct SourceRegisterClass final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::string BankSymbol; + std::uint32_t SizeInBits = 0; + std::uint32_t SpillSizeInBits = 0; + std::uint32_t SpillAlignmentInBits = 0; + std::uint64_t Lanes = 0; + std::vector RegisterSymbols; + std::vector AllocationOrderSymbols; + bool Allocatable = true; + std::uint32_t CopyCost = 1; + TextRange Range; +}; + +struct SourceRegisterMask final { + std::string Name; + std::vector PreservedRegisterSymbols; + TextRange Range; +}; + +struct DescriptionAST final { + SourceTarget Target; + std::vector RegisterUnits; + std::vector Registers; + std::vector RegisterBanks; + std::vector RegisterClasses; + std::vector RegisterMasks; +}; + +struct NormalizedRegisterUnitLane final { + std::uint32_t UnitID = 0; + std::uint64_t Lanes = 0; +}; + +struct NormalizedRegister final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t Encoding = 0; + std::uint32_t DwarfRegister = 0; + std::vector Units; + bool IsConstant = false; + bool IsArtificial = false; + bool IsReserved = false; + std::vector AlternativeNames; +}; + +struct NormalizedRegisterBank final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t MaximumSizeInBits = 0; + std::vector RegisterClasses; +}; + +struct NormalizedRegisterClass final { + std::uint32_t ID = 0; + std::string Symbol; + std::string Name; + std::uint32_t BankID = 0; + std::uint32_t SizeInBits = 0; + std::uint32_t SpillSizeInBits = 0; + std::uint32_t SpillAlignmentInBits = 0; + std::uint64_t Lanes = 0; + std::vector Registers; + std::vector AllocationOrder; + bool Allocatable = true; + std::uint32_t CopyCost = 1; +}; + +struct NormalizedRegisterMask final { + std::string Name; + std::vector PreservedRegisters; +}; + +struct NormalizedTargetModel final { + SourceTarget Target; + std::vector RegisterUnits; + std::vector Registers; + std::vector RegisterBanks; + std::vector RegisterClasses; + std::vector RegisterMasks; +}; + +[[nodiscard]] std::string lowerASCII(std::string_view Value) { + std::string Result(Value); + for (char &Character : Result) { + if (Character >= 'A' && Character <= 'Z') + Character = static_cast(Character - 'A' + 'a'); + } + return Result; +} + +[[nodiscard]] bool isIdentifier(std::string_view Value) noexcept { + if (Value.empty()) + return false; + const auto IsHead = [](unsigned char Character) { + return std::isalpha(Character) != 0 || Character == '_'; + }; + const auto IsTail = [](unsigned char Character) { + return std::isalnum(Character) != 0 || Character == '_'; + }; + if (!IsHead(static_cast(Value.front()))) + return false; + return std::ranges::all_of(Value.substr(1), [IsTail](char Character) { + return IsTail(static_cast(Character)); + }); +} + +[[nodiscard]] std::string upperIdentifier(std::string_view Value) { + std::string Result; + Result.reserve(Value.size()); + for (const char Character : Value) { + if (Character >= 'a' && Character <= 'z') + Result.push_back(static_cast(Character - 'a' + 'A')); + else if ((Character >= 'A' && Character <= 'Z') || (Character >= '0' && Character <= '9')) + Result.push_back(Character); + else + Result.push_back('_'); + } + return Result; +} + +void appendQuoted(std::ostream &Output, std::string_view Value) { + Output << '"'; + for (const char Character : Value) { + switch (Character) { + case '\\': + Output << "\\\\"; + break; + case '"': + Output << "\\\""; + break; + case '\n': + Output << "\\n"; + break; + case '\r': + Output << "\\r"; + break; + case '\t': + Output << "\\t"; + break; + default: + Output << Character; + break; + } + } + Output << '"'; +} + +class ASTParser final { +public: + explicit ASTParser(const yaml::Stream &Input) : Input(Input) {} + + [[nodiscard]] std::optional parse() { + const std::span Documents = Input.documents(); + if (Documents.size() != 1) { + fail(Input.source().range(0, Input.source().text().size()), + "target description must contain exactly one YAML document"); + return std::nullopt; + } + + const yaml::Node *Root = Input.node(Documents.front().Root); + if (!requireMapping(Root, "target description root")) + return std::nullopt; + if (!checkKeys(*Root, + {"schema", "target", "register_units", "registers", "register_banks", + "register_classes", "register_masks"}, + "target description root")) { + return std::nullopt; + } + + const yaml::Node *SchemaNode = requiredNode(*Root, "schema", "target description root"); + if (SchemaNode == nullptr) + return std::nullopt; + const auto Schema = scalar(*SchemaNode, "target description schema"); + if (!Schema || *Schema != "rcc-target-description-v1") { + if (Schema) + fail(SchemaNode->range(), "unsupported target description schema"); + return std::nullopt; + } + + DescriptionAST Result; + const yaml::Node *TargetNode = requiredNode(*Root, "target", "target description root"); + if (TargetNode == nullptr || !parseTarget(*TargetNode, Result.Target)) + return std::nullopt; + const yaml::Node *UnitsNode = requiredNode(*Root, "register_units", "target description root"); + const yaml::Node *RegistersNode = requiredNode(*Root, "registers", "target description root"); + const yaml::Node *BanksNode = requiredNode(*Root, "register_banks", "target description root"); + const yaml::Node *ClassesNode = + requiredNode(*Root, "register_classes", "target description root"); + const yaml::Node *MasksNode = requiredNode(*Root, "register_masks", "target description root"); + if (UnitsNode == nullptr || RegistersNode == nullptr || BanksNode == nullptr || + ClassesNode == nullptr || MasksNode == nullptr || + !parseRegisterUnits(*UnitsNode, Result.RegisterUnits) || + !parseRegisters(*RegistersNode, Result.Registers) || + !parseRegisterBanks(*BanksNode, Result.RegisterBanks) || + !parseRegisterClasses(*ClassesNode, Result.RegisterClasses) || + !parseRegisterMasks(*MasksNode, Result.RegisterMasks)) { + return std::nullopt; + } + return Result; + } + + [[nodiscard]] std::vector takeDiagnostics() { + return std::move(Diagnostics); + } + +private: + void fail(TextRange Range, std::string Message) { + Diagnostics.push_back({ + .Severity = DiagnosticSeverity::Error, + .Category = "target-description", + .Message = std::move(Message), + .SourceName = std::string(Input.source().name()), + .Range = Range, + }); + } + + [[nodiscard]] bool requireMapping(const yaml::Node *Node, std::string_view Context) { + if (Node != nullptr && Node->kind() == yaml::NodeKind::Mapping) + return true; + fail(Node == nullptr ? Input.source().range(0, 0) : Node->range(), + std::string(Context) + " must be a mapping"); + return false; + } + + [[nodiscard]] bool requireSequence(const yaml::Node *Node, std::string_view Context) { + if (Node != nullptr && Node->kind() == yaml::NodeKind::Sequence) + return true; + fail(Node == nullptr ? Input.source().range(0, 0) : Node->range(), + std::string(Context) + " must be a sequence"); + return false; + } + + [[nodiscard]] bool checkKeys(const yaml::Node &Mapping, + std::initializer_list Allowed, + std::string_view Context) { + for (const yaml::MappingEntry &Entry : Mapping.mapping()) { + const bool IsAllowed = + std::ranges::any_of(Allowed, [&Entry](std::string_view Key) { return Entry.Key == Key; }); + if (!IsAllowed) { + fail(Entry.KeyRange, std::string(Context) + " has unknown key '" + Entry.Key + "'"); + return false; + } + } + return true; + } + + [[nodiscard]] const yaml::Node *requiredNode(const yaml::Node &Mapping, std::string_view Key, + std::string_view Context) { + const std::optional Value = Mapping.mappingValue(Key); + if (Value) { + const yaml::Node *Node = Input.node(*Value); + if (Node != nullptr) + return Node; + } + fail(Mapping.range(), + std::string(Context) + " is missing required key '" + std::string(Key) + "'"); + return nullptr; + } + + [[nodiscard]] std::optional + requiredScalar(const yaml::Node &Mapping, std::string_view Key, std::string_view Context) { + const yaml::Node *Node = requiredNode(Mapping, Key, Context); + if (Node == nullptr) + return std::nullopt; + return scalar(*Node, std::string(Context) + " key '" + std::string(Key) + "'"); + } + + [[nodiscard]] std::optional scalar(const yaml::Node &Node, + std::string_view Context) { + if (Node.kind() != yaml::NodeKind::Scalar) { + fail(Node.range(), std::string(Context) + " must be a scalar"); + return std::nullopt; + } + return std::string(Node.scalar()); + } + + [[nodiscard]] std::optional unsignedInteger(const yaml::Node &Node, + std::string_view Context) { + const auto Text = scalar(Node, Context); + if (!Text) + return std::nullopt; + std::uint64_t Result = 0; + const auto [End, Error] = + std::from_chars(Text->data(), Text->data() + Text->size(), Result, 10); + if (Error != std::errc{} || End != Text->data() + Text->size()) { + fail(Node.range(), std::string(Context) + " must be an unsigned decimal integer"); + return std::nullopt; + } + return Result; + } + + [[nodiscard]] std::optional unsignedInteger32(const yaml::Node &Node, + std::string_view Context) { + const auto Value = unsignedInteger(Node, Context); + if (!Value) + return std::nullopt; + if (*Value > std::numeric_limits::max()) { + fail(Node.range(), std::string(Context) + " is outside the uint32 range"); + return std::nullopt; + } + return static_cast(*Value); + } + + [[nodiscard]] std::optional boolean(const yaml::Node &Node, std::string_view Context) { + const auto Text = scalar(Node, Context); + if (!Text) + return std::nullopt; + if (*Text == "true") + return true; + if (*Text == "false") + return false; + fail(Node.range(), std::string(Context) + " must be 'true' or 'false'"); + return std::nullopt; + } + + [[nodiscard]] std::optional> scalarSequence(const yaml::Node &Node, + std::string_view Context) { + if (!requireSequence(&Node, Context)) + return std::nullopt; + std::vector Result; + for (const yaml::NodeID Value : Node.sequence()) { + const yaml::Node *Element = Input.node(Value); + if (Element == nullptr) + return std::nullopt; + const auto Text = scalar(*Element, Context); + if (!Text) + return std::nullopt; + Result.push_back(*Text); + } + return Result; + } + + [[nodiscard]] bool parseTarget(const yaml::Node &Node, SourceTarget &Result) { + if (!requireMapping(&Node, "target")) + return false; + if (!checkKeys(Node, {"namespace", "name", "triple", "cpu", "abi", "data_layout", "features"}, + "target")) { + return false; + } + const auto Namespace = requiredScalar(Node, "namespace", "target"); + const auto Name = requiredScalar(Node, "name", "target"); + const auto Triple = requiredScalar(Node, "triple", "target"); + const auto CPU = requiredScalar(Node, "cpu", "target"); + const auto ABI = requiredScalar(Node, "abi", "target"); + const auto DataLayout = requiredScalar(Node, "data_layout", "target"); + const yaml::Node *Features = requiredNode(Node, "features", "target"); + if (!Namespace || !Name || !Triple || !CPU || !ABI || !DataLayout || Features == nullptr) + return false; + const auto ParsedFeatures = scalarSequence(*Features, "target features"); + if (!ParsedFeatures) + return false; + Result = { + .Namespace = *Namespace, + .Name = *Name, + .Triple = *Triple, + .CPU = *CPU, + .ABI = *ABI, + .DataLayout = *DataLayout, + .Features = *ParsedFeatures, + .Range = Node.range(), + }; + return true; + } + + [[nodiscard]] bool parseRegisterUnits(const yaml::Node &Node, + std::vector &Result) { + if (!requireSequence(&Node, "register_units")) + return false; + for (const yaml::NodeID ElementID : Node.sequence()) { + const yaml::Node *Element = Input.node(ElementID); + if (!requireMapping(Element, "register unit")) + return false; + if (!checkKeys(*Element, {"id", "symbol", "name"}, "register unit")) + return false; + const yaml::Node *IDNode = requiredNode(*Element, "id", "register unit"); + const auto Symbol = requiredScalar(*Element, "symbol", "register unit"); + const auto Name = requiredScalar(*Element, "name", "register unit"); + if (IDNode == nullptr || !Symbol || !Name) + return false; + const auto ID = unsignedInteger32(*IDNode, "register unit id"); + if (!ID) + return false; + Result.push_back({.ID = *ID, .Symbol = *Symbol, .Name = *Name, .Range = Element->range()}); + } + return true; + } + + [[nodiscard]] std::optional> + parseRegisterUnitLanes(const yaml::Node &Node) { + if (!requireSequence(&Node, "register units")) + return std::nullopt; + std::vector Result; + for (const yaml::NodeID ElementID : Node.sequence()) { + const yaml::Node *Element = Input.node(ElementID); + if (!requireMapping(Element, "register unit lane")) + return std::nullopt; + if (!checkKeys(*Element, {"unit", "lanes"}, "register unit lane")) + return std::nullopt; + const auto Unit = requiredScalar(*Element, "unit", "register unit lane"); + const yaml::Node *LanesNode = requiredNode(*Element, "lanes", "register unit lane"); + if (!Unit || LanesNode == nullptr) + return std::nullopt; + const auto Lanes = unsignedInteger(*LanesNode, "register unit lane lanes"); + if (!Lanes) + return std::nullopt; + Result.push_back({.UnitSymbol = *Unit, .Lanes = *Lanes, .Range = Element->range()}); + } + return Result; + } + + [[nodiscard]] bool parseRegisters(const yaml::Node &Node, std::vector &Result) { + if (!requireSequence(&Node, "registers")) + return false; + for (const yaml::NodeID ElementID : Node.sequence()) { + const yaml::Node *Element = Input.node(ElementID); + if (!requireMapping(Element, "register")) + return false; + if (!checkKeys(*Element, + {"id", "symbol", "name", "encoding", "dwarf_register", "units", "constant", + "artificial", "reserved", "aliases"}, + "register")) { + return false; + } + const yaml::Node *IDNode = requiredNode(*Element, "id", "register"); + const auto Symbol = requiredScalar(*Element, "symbol", "register"); + const auto Name = requiredScalar(*Element, "name", "register"); + const yaml::Node *EncodingNode = requiredNode(*Element, "encoding", "register"); + const yaml::Node *DwarfNode = requiredNode(*Element, "dwarf_register", "register"); + const yaml::Node *UnitsNode = requiredNode(*Element, "units", "register"); + if (IDNode == nullptr || !Symbol || !Name || EncodingNode == nullptr || + DwarfNode == nullptr || UnitsNode == nullptr) { + return false; + } + const auto ID = unsignedInteger32(*IDNode, "register id"); + const auto Encoding = unsignedInteger32(*EncodingNode, "register encoding"); + const auto DwarfRegister = unsignedInteger32(*DwarfNode, "register DWARF number"); + const auto Units = parseRegisterUnitLanes(*UnitsNode); + if (!ID || !Encoding || !DwarfRegister || !Units) + return false; + + SourceRegister Register{ + .ID = *ID, + .Symbol = *Symbol, + .Name = *Name, + .Encoding = *Encoding, + .DwarfRegister = *DwarfRegister, + .Units = *Units, + .IsConstant = false, + .IsArtificial = false, + .IsReserved = false, + .AlternativeNames = {}, + .Range = Element->range(), + }; + if (const yaml::Node *Constant = optionalNode(*Element, "constant")) { + const auto Value = boolean(*Constant, "register constant"); + if (!Value) + return false; + Register.IsConstant = *Value; + } + if (const yaml::Node *Artificial = optionalNode(*Element, "artificial")) { + const auto Value = boolean(*Artificial, "register artificial"); + if (!Value) + return false; + Register.IsArtificial = *Value; + } + if (const yaml::Node *Reserved = optionalNode(*Element, "reserved")) { + const auto Value = boolean(*Reserved, "register reserved"); + if (!Value) + return false; + Register.IsReserved = *Value; + } + if (const yaml::Node *Aliases = optionalNode(*Element, "aliases")) { + const auto Values = scalarSequence(*Aliases, "register aliases"); + if (!Values) + return false; + Register.AlternativeNames = *Values; + } + Result.push_back(std::move(Register)); + } + return true; + } + + [[nodiscard]] bool parseRegisterBanks(const yaml::Node &Node, + std::vector &Result) { + if (!requireSequence(&Node, "register_banks")) + return false; + for (const yaml::NodeID ElementID : Node.sequence()) { + const yaml::Node *Element = Input.node(ElementID); + if (!requireMapping(Element, "register bank")) + return false; + if (!checkKeys(*Element, {"id", "symbol", "name", "maximum_size_bits"}, "register bank")) + return false; + const yaml::Node *IDNode = requiredNode(*Element, "id", "register bank"); + const auto Symbol = requiredScalar(*Element, "symbol", "register bank"); + const auto Name = requiredScalar(*Element, "name", "register bank"); + const yaml::Node *SizeNode = requiredNode(*Element, "maximum_size_bits", "register bank"); + if (IDNode == nullptr || !Symbol || !Name || SizeNode == nullptr) + return false; + const auto ID = unsignedInteger32(*IDNode, "register bank id"); + const auto Size = unsignedInteger32(*SizeNode, "register bank maximum_size_bits"); + if (!ID || !Size) + return false; + Result.push_back({.ID = *ID, + .Symbol = *Symbol, + .Name = *Name, + .MaximumSizeInBits = *Size, + .Range = Element->range()}); + } + return true; + } + + [[nodiscard]] bool parseRegisterClasses(const yaml::Node &Node, + std::vector &Result) { + if (!requireSequence(&Node, "register_classes")) + return false; + for (const yaml::NodeID ElementID : Node.sequence()) { + const yaml::Node *Element = Input.node(ElementID); + if (!requireMapping(Element, "register class")) + return false; + if (!checkKeys(*Element, + {"id", "symbol", "name", "bank", "size_bits", "spill_size_bits", + "spill_alignment_bits", "lanes", "registers", "allocation_order", + "allocatable", "copy_cost"}, + "register class")) { + return false; + } + const yaml::Node *IDNode = requiredNode(*Element, "id", "register class"); + const auto Symbol = requiredScalar(*Element, "symbol", "register class"); + const auto Name = requiredScalar(*Element, "name", "register class"); + const auto Bank = requiredScalar(*Element, "bank", "register class"); + const yaml::Node *SizeNode = requiredNode(*Element, "size_bits", "register class"); + const yaml::Node *SpillSizeNode = requiredNode(*Element, "spill_size_bits", "register class"); + const yaml::Node *SpillAlignmentNode = + requiredNode(*Element, "spill_alignment_bits", "register class"); + const yaml::Node *LanesNode = requiredNode(*Element, "lanes", "register class"); + const yaml::Node *RegistersNode = requiredNode(*Element, "registers", "register class"); + const yaml::Node *AllocationNode = + requiredNode(*Element, "allocation_order", "register class"); + if (IDNode == nullptr || !Symbol || !Name || !Bank || SizeNode == nullptr || + SpillSizeNode == nullptr || SpillAlignmentNode == nullptr || LanesNode == nullptr || + RegistersNode == nullptr || AllocationNode == nullptr) { + return false; + } + const auto ID = unsignedInteger32(*IDNode, "register class id"); + const auto Size = unsignedInteger32(*SizeNode, "register class size_bits"); + const auto SpillSize = unsignedInteger32(*SpillSizeNode, "register class spill_size_bits"); + const auto SpillAlignment = + unsignedInteger32(*SpillAlignmentNode, "register class spill_alignment_bits"); + const auto Lanes = unsignedInteger(*LanesNode, "register class lanes"); + const auto Registers = scalarSequence(*RegistersNode, "register class registers"); + const auto AllocationOrder = + scalarSequence(*AllocationNode, "register class allocation_order"); + if (!ID || !Size || !SpillSize || !SpillAlignment || !Lanes || !Registers || + !AllocationOrder) { + return false; + } + SourceRegisterClass RegisterClass{ + .ID = *ID, + .Symbol = *Symbol, + .Name = *Name, + .BankSymbol = *Bank, + .SizeInBits = *Size, + .SpillSizeInBits = *SpillSize, + .SpillAlignmentInBits = *SpillAlignment, + .Lanes = *Lanes, + .RegisterSymbols = *Registers, + .AllocationOrderSymbols = *AllocationOrder, + .Range = Element->range(), + }; + if (const yaml::Node *Allocatable = optionalNode(*Element, "allocatable")) { + const auto Value = boolean(*Allocatable, "register class allocatable"); + if (!Value) + return false; + RegisterClass.Allocatable = *Value; + } + if (const yaml::Node *CopyCost = optionalNode(*Element, "copy_cost")) { + const auto Value = unsignedInteger32(*CopyCost, "register class copy_cost"); + if (!Value) + return false; + RegisterClass.CopyCost = *Value; + } + Result.push_back(std::move(RegisterClass)); + } + return true; + } + + [[nodiscard]] bool parseRegisterMasks(const yaml::Node &Node, + std::vector &Result) { + if (!requireSequence(&Node, "register_masks")) + return false; + for (const yaml::NodeID ElementID : Node.sequence()) { + const yaml::Node *Element = Input.node(ElementID); + if (!requireMapping(Element, "register mask")) + return false; + if (!checkKeys(*Element, {"name", "preserved"}, "register mask")) + return false; + const auto Name = requiredScalar(*Element, "name", "register mask"); + const yaml::Node *PreservedNode = requiredNode(*Element, "preserved", "register mask"); + if (!Name || PreservedNode == nullptr) + return false; + const auto Preserved = scalarSequence(*PreservedNode, "register mask preserved"); + if (!Preserved) + return false; + Result.push_back( + {.Name = *Name, .PreservedRegisterSymbols = *Preserved, .Range = Element->range()}); + } + return true; + } + + [[nodiscard]] const yaml::Node *optionalNode(const yaml::Node &Mapping, + std::string_view Key) const noexcept { + const std::optional Value = Mapping.mappingValue(Key); + return Value ? Input.node(*Value) : nullptr; + } + + const yaml::Stream &Input; + std::vector Diagnostics; +}; + +class ModelNormalizer final { +public: + ModelNormalizer(const DescriptionAST &Input, std::string SourceName) + : Input(Input), SourceName(std::move(SourceName)) {} + + [[nodiscard]] std::optional normalize() { + if (!validateTarget()) + return std::nullopt; + + NormalizedTargetModel Result; + Result.Target = Input.Target; + Result.RegisterUnits = Input.RegisterUnits; + if (!normalizeRegisterUnits() || !normalizeRegisters(Result) || + !normalizeRegisterBanks(Result) || !normalizeRegisterClasses(Result) || + !normalizeRegisterMasks(Result)) { + return std::nullopt; + } + return Result; + } + + [[nodiscard]] std::vector takeDiagnostics() { + return std::move(Diagnostics); + } + +private: + void fail(TextRange Range, std::string Message) { + Diagnostics.push_back({ + .Severity = DiagnosticSeverity::Error, + .Category = "target-description", + .Message = std::move(Message), + .SourceName = SourceName, + .Range = Range, + }); + } + + [[nodiscard]] bool validateTarget() { + if (!isIdentifier(Input.Target.Namespace)) { + fail(Input.Target.Range, "target namespace must be a C++ identifier"); + return false; + } + if (Input.Target.Name.empty() || Input.Target.Triple.empty() || Input.Target.CPU.empty() || + Input.Target.ABI.empty() || Input.Target.DataLayout.empty() || + Input.Target.Features.empty()) { + fail(Input.Target.Range, "target metadata fields and feature list must be nonempty"); + return false; + } + std::unordered_set Features; + for (const std::string &Feature : Input.Target.Features) { + if (Feature.empty() || !Features.insert(Feature).second) { + fail(Input.Target.Range, "target feature names must be nonempty and unique"); + return false; + } + } + return true; + } + + [[nodiscard]] bool normalizeRegisterUnits() { + if (Input.RegisterUnits.empty()) { + fail(Input.Target.Range, "target description requires at least one register unit"); + return false; + } + std::unordered_set IDs; + std::unordered_set Symbols; + std::unordered_set Names; + for (const SourceRegisterUnit &Unit : Input.RegisterUnits) { + if (Unit.ID == 0 || !isIdentifier(Unit.Symbol) || Unit.Name.empty() || + !IDs.insert(Unit.ID).second || !Symbols.insert(Unit.Symbol).second || + !Names.insert(lowerASCII(Unit.Name)).second) { + fail(Unit.Range, + "register units require nonzero unique IDs, unique symbols, and unique names"); + return false; + } + UnitBySymbol.emplace(Unit.Symbol, Unit.ID); + } + return true; + } + + [[nodiscard]] bool normalizeRegisters(NormalizedTargetModel &Result) { + if (Input.Registers.empty()) { + fail(Input.Target.Range, "target description requires at least one physical register"); + return false; + } + std::unordered_set IDs; + std::unordered_set Symbols; + std::unordered_set Names; + std::unordered_set DwarfRegisters; + for (const SourceRegister &Register : Input.Registers) { + if (Register.ID == 0 || !isIdentifier(Register.Symbol) || Register.Name.empty() || + !IDs.insert(Register.ID).second || !Symbols.insert(Register.Symbol).second || + !Names.insert(lowerASCII(Register.Name)).second || + !DwarfRegisters.insert(Register.DwarfRegister).second || Register.Units.empty()) { + fail(Register.Range, "registers require nonzero unique IDs, symbols, canonical names, " + "DWARF numbers, and units"); + return false; + } + RegisterBySymbol.emplace(Register.Symbol, Register.ID); + NormalizedRegister Normalized{ + .ID = Register.ID, + .Symbol = Register.Symbol, + .Name = Register.Name, + .Encoding = Register.Encoding, + .DwarfRegister = Register.DwarfRegister, + .Units = {}, + .IsConstant = Register.IsConstant, + .IsArtificial = Register.IsArtificial, + .IsReserved = Register.IsReserved, + .AlternativeNames = Register.AlternativeNames, + }; + std::unordered_set Units; + for (const SourceRegisterUnitLane &Unit : Register.Units) { + const auto Position = UnitBySymbol.find(Unit.UnitSymbol); + if (Position == UnitBySymbol.end() || Unit.Lanes == 0 || + !Units.insert(Position->second).second) { + fail(Unit.Range, + "register units must name distinct declared units with nonzero lane masks"); + return false; + } + Normalized.Units.push_back({.UnitID = Position->second, .Lanes = Unit.Lanes}); + } + for (const std::string &Alias : Register.AlternativeNames) { + if (Alias.empty() || !Names.insert(lowerASCII(Alias)).second) { + fail(Register.Range, + "register ABI aliases must be nonempty and unique across all register names"); + return false; + } + } + Result.Registers.push_back(std::move(Normalized)); + } + return true; + } + + [[nodiscard]] bool normalizeRegisterBanks(NormalizedTargetModel &Result) { + if (Input.RegisterBanks.empty()) { + fail(Input.Target.Range, "target description requires at least one register bank"); + return false; + } + std::unordered_set IDs; + std::unordered_set Symbols; + std::unordered_set Names; + for (const SourceRegisterBank &Bank : Input.RegisterBanks) { + if (Bank.ID == 0 || !isIdentifier(Bank.Symbol) || Bank.Name.empty() || + Bank.MaximumSizeInBits == 0 || !IDs.insert(Bank.ID).second || + !Symbols.insert(Bank.Symbol).second || !Names.insert(lowerASCII(Bank.Name)).second) { + fail(Bank.Range, + "register banks require nonzero unique IDs, symbols, names, and maximum sizes"); + return false; + } + BankBySymbol.emplace(Bank.Symbol, Bank.ID); + Result.RegisterBanks.push_back({ + .ID = Bank.ID, + .Symbol = Bank.Symbol, + .Name = Bank.Name, + .MaximumSizeInBits = Bank.MaximumSizeInBits, + .RegisterClasses = {}, + }); + } + return true; + } + + [[nodiscard]] bool normalizeRegisterClasses(NormalizedTargetModel &Result) { + if (Input.RegisterClasses.empty()) { + fail(Input.Target.Range, "target description requires at least one register class"); + return false; + } + std::unordered_set IDs; + std::unordered_set Symbols; + std::unordered_set Names; + for (const SourceRegisterClass &Class : Input.RegisterClasses) { + const auto Bank = BankBySymbol.find(Class.BankSymbol); + if (Class.ID == 0 || !isIdentifier(Class.Symbol) || Class.Name.empty() || + Bank == BankBySymbol.end() || Class.SizeInBits == 0 || Class.SpillSizeInBits == 0 || + Class.SpillAlignmentInBits == 0 || Class.Lanes == 0 || Class.RegisterSymbols.empty() || + !IDs.insert(Class.ID).second || !Symbols.insert(Class.Symbol).second || + !Names.insert(lowerASCII(Class.Name)).second) { + fail(Class.Range, "register classes require unique IDs, symbols, names, a declared bank, " + "sizes, lanes, and members"); + return false; + } + ClassBySymbol.emplace(Class.Symbol, Class.ID); + NormalizedRegisterClass Normalized{ + .ID = Class.ID, + .Symbol = Class.Symbol, + .Name = Class.Name, + .BankID = Bank->second, + .SizeInBits = Class.SizeInBits, + .SpillSizeInBits = Class.SpillSizeInBits, + .SpillAlignmentInBits = Class.SpillAlignmentInBits, + .Lanes = Class.Lanes, + .Registers = {}, + .AllocationOrder = {}, + .Allocatable = Class.Allocatable, + .CopyCost = Class.CopyCost, + }; + std::unordered_set Members; + for (const std::string &Symbol : Class.RegisterSymbols) { + const auto Register = RegisterBySymbol.find(Symbol); + if (Register == RegisterBySymbol.end() || !Members.insert(Register->second).second) { + fail(Class.Range, "register class members must reference distinct declared registers"); + return false; + } + Normalized.Registers.push_back(Register->second); + } + std::unordered_set Allocation; + for (const std::string &Symbol : Class.AllocationOrderSymbols) { + const auto Register = RegisterBySymbol.find(Symbol); + if (Register == RegisterBySymbol.end() || !Members.contains(Register->second) || + !Allocation.insert(Register->second).second) { + fail(Class.Range, + "register class allocation order must contain distinct members of the same class"); + return false; + } + Normalized.AllocationOrder.push_back(Register->second); + } + if ((Normalized.Allocatable && Normalized.AllocationOrder.empty()) || + (!Normalized.Allocatable && !Normalized.AllocationOrder.empty())) { + fail(Class.Range, "allocatable register classes require an allocation order and " + "nonallocatable classes forbid one"); + return false; + } + Result.RegisterClasses.push_back(std::move(Normalized)); + } + for (NormalizedRegisterBank &Bank : Result.RegisterBanks) { + for (const NormalizedRegisterClass &Class : Result.RegisterClasses) { + if (Class.BankID == Bank.ID) + Bank.RegisterClasses.push_back(Class.ID); + } + } + return true; + } + + [[nodiscard]] bool normalizeRegisterMasks(NormalizedTargetModel &Result) { + std::unordered_set Names; + for (const SourceRegisterMask &Mask : Input.RegisterMasks) { + if (Mask.Name.empty() || !Names.insert(Mask.Name).second) { + fail(Mask.Range, "register mask names must be nonempty and unique"); + return false; + } + NormalizedRegisterMask Normalized{.Name = Mask.Name, .PreservedRegisters = {}}; + std::unordered_set Preserved; + for (const std::string &Symbol : Mask.PreservedRegisterSymbols) { + const auto Register = RegisterBySymbol.find(Symbol); + if (Register == RegisterBySymbol.end() || !Preserved.insert(Register->second).second) { + fail(Mask.Range, "register mask entries must name distinct declared registers"); + return false; + } + Normalized.PreservedRegisters.push_back(Register->second); + } + Result.RegisterMasks.push_back(std::move(Normalized)); + } + return true; + } + + const DescriptionAST &Input; + std::string SourceName; + std::unordered_map UnitBySymbol; + std::unordered_map RegisterBySymbol; + std::unordered_map BankBySymbol; + std::unordered_map ClassBySymbol; + std::vector Diagnostics; +}; + +[[nodiscard]] const NormalizedRegister &findRegister(const NormalizedTargetModel &Model, + std::uint32_t ID) { + const auto Position = std::ranges::find(Model.Registers, ID, &NormalizedRegister::ID); + return *Position; +} + +[[nodiscard]] const SourceRegisterUnit &findRegisterUnit(const NormalizedTargetModel &Model, + std::uint32_t ID) { + const auto Position = std::ranges::find(Model.RegisterUnits, ID, &SourceRegisterUnit::ID); + return *Position; +} + +[[nodiscard]] const NormalizedRegisterBank &findRegisterBank(const NormalizedTargetModel &Model, + std::uint32_t ID) { + const auto Position = std::ranges::find(Model.RegisterBanks, ID, &NormalizedRegisterBank::ID); + return *Position; +} + +[[nodiscard]] std::string emitRegisterHeader(const NormalizedTargetModel &Model, + std::string_view SourceName) { + const std::string Guard = "RCC_TARGET_" + upperIdentifier(Model.Target.Namespace) + "_" + + upperIdentifier(Model.Target.Name) + "REGISTERS_H"; + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-backend-descgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "#ifndef " << Guard << "\n#define " << Guard << "\n\n"; + Output << "#include \n#include \n\n"; + Output << "namespace rcc::" << Model.Target.Namespace << " {\n\n"; + Output << "inline constexpr std::string_view TargetName = "; + appendQuoted(Output, Model.Target.Name); + Output << ";\ninline constexpr std::string_view TargetTriple = "; + appendQuoted(Output, Model.Target.Triple); + Output << ";\ninline constexpr std::string_view TargetCPU = "; + appendQuoted(Output, Model.Target.CPU); + Output << ";\ninline constexpr std::string_view TargetABI = "; + appendQuoted(Output, Model.Target.ABI); + Output << ";\ninline constexpr std::string_view TargetDataLayout = "; + appendQuoted(Output, Model.Target.DataLayout); + Output << ";\n\ninline constexpr std::string_view TargetFeatures[] = {\n"; + for (const std::string &Feature : Model.Target.Features) { + Output << " "; + appendQuoted(Output, Feature); + Output << ",\n"; + } + Output << "};\n\n"; + + Output << "enum class PhysicalRegisterID : std::uint32_t {\n NoRegister = 0,\n"; + for (const NormalizedRegister &Register : Model.Registers) + Output << " " << Register.Symbol << " = " << Register.ID << ",\n"; + Output << "};\n\n"; + Output << "enum class RegisterUnit : std::uint32_t {\n"; + for (const SourceRegisterUnit &Unit : Model.RegisterUnits) + Output << " " << Unit.Symbol << " = " << Unit.ID << ",\n"; + Output << "};\n\n"; + Output << "enum class RegisterBank : std::uint32_t {\n"; + for (const NormalizedRegisterBank &Bank : Model.RegisterBanks) + Output << " " << Bank.Symbol << " = " << Bank.ID << ",\n"; + Output << "};\n\n"; + Output << "enum class RegisterClass : std::uint32_t {\n"; + for (const NormalizedRegisterClass &Class : Model.RegisterClasses) + Output << " " << Class.Symbol << " = " << Class.ID << ",\n"; + Output << "};\n\n"; + Output << "} // namespace rcc::" << Model.Target.Namespace << "\n\n"; + Output << "#endif // " << Guard << "\n"; + return Output.str(); +} + +void emitRegisterIDList(std::ostream &Output, const NormalizedTargetModel &Model, + std::span IDs) { + for (const std::uint32_t ID : IDs) + Output << "PhysicalRegisterID::" << findRegister(Model, ID).Symbol << ", "; +} + +[[nodiscard]] std::string emitTargetTables(const NormalizedTargetModel &Model, + std::string_view SourceName) { + std::ostringstream Output; + Output << "// SPDX-License-Identifier: GPL-3.0-or-later\n\n"; + Output << "// This file is generated by rcc-backend-descgen from "; + appendQuoted(Output, SourceName); + Output << ". Do not edit it manually.\n\n"; + Output << "void appendGeneratedRegisterInfo(TargetRegisterInfoDesc &Description) {\n"; + Output << " Description.RegisterUnits = {\n"; + for (const SourceRegisterUnit &Unit : Model.RegisterUnits) { + Output << " {RegisterUnitID::fromValue(" << Unit.ID << "), "; + appendQuoted(Output, Unit.Name); + Output << "},\n"; + } + Output << " };\n\n Description.PhysicalRegisters = {\n"; + for (const NormalizedRegister &Register : Model.Registers) { + Output << " {physical(PhysicalRegisterID::" << Register.Symbol << "), "; + appendQuoted(Output, Register.Name); + Output << ", " << Register.Encoding << ", {"; + for (const NormalizedRegisterUnitLane &Unit : Register.Units) { + Output << "unitLane(RegisterUnit::" << findRegisterUnit(Model, Unit.UnitID).Symbol << ", " + << Unit.Lanes << "), "; + } + Output << "}, " << (Register.IsConstant ? "true" : "false") << ", " + << (Register.IsArtificial ? "true" : "false") << ", " + << (Register.IsReserved ? "true" : "false") << ", {"; + for (const std::string &Alias : Register.AlternativeNames) { + appendQuoted(Output, Alias); + Output << ", "; + } + Output << "}},\n"; + } + Output << " };\n\n Description.RegisterBanks = {\n"; + for (const NormalizedRegisterBank &Bank : Model.RegisterBanks) { + Output << " {RegisterBankID::fromValue(" << Bank.ID << "), "; + appendQuoted(Output, Bank.Name); + Output << ", " << Bank.MaximumSizeInBits << ", {"; + for (const std::uint32_t ClassID : Bank.RegisterClasses) { + const auto Position = + std::ranges::find(Model.RegisterClasses, ClassID, &NormalizedRegisterClass::ID); + Output << "RegisterClassID::fromValue(" << Position->ID << "), "; + } + Output << "}},\n"; + } + Output << " };\n\n Description.RegisterClasses = {\n"; + for (const NormalizedRegisterClass &Class : Model.RegisterClasses) { + Output << " registerClass(RegisterClass::" << Class.Symbol << ", "; + appendQuoted(Output, Class.Name); + Output << ", {"; + emitRegisterIDList(Output, Model, Class.Registers); + Output << "}, {"; + emitRegisterIDList(Output, Model, Class.AllocationOrder); + Output << "}, RegisterBank::" << findRegisterBank(Model, Class.BankID).Symbol << ", " + << Class.SizeInBits << ", " << Class.SpillSizeInBits << ", " + << Class.SpillAlignmentInBits << ", " << Class.Lanes << ", " + << (Class.Allocatable ? "true" : "false") << ", " << Class.CopyCost << "),\n"; + } + Output << " };\n}\n\n"; + Output << "void appendGeneratedMIRTextInfo(TargetMIRTextInfoDesc &Description) {\n"; + Output << " Description.RegisterMasks = {\n"; + for (const NormalizedRegisterMask &Mask : Model.RegisterMasks) { + Output << " {"; + appendQuoted(Output, Mask.Name); + Output << ", makePreservedMask({"; + emitRegisterIDList(Output, Model, Mask.PreservedRegisters); + Output << "})},\n"; + } + Output << " };\n Description.CFIRegisters = {\n"; + for (const NormalizedRegister &Register : Model.Registers) { + Output << " {physical(PhysicalRegisterID::" << Register.Symbol << "), " + << Register.DwarfRegister << ", true},\n"; + } + Output << " };\n}\n"; + return Output.str(); +} + +} // namespace + +DescriptionGenerationResult generateDescription(std::string SourceName, std::string SourceText) { + yaml::ParseResult YAMLResult = yaml::parse(SourceName, SourceText); + if (!YAMLResult.succeeded()) { + DescriptionGenerationResult Result; + for (const TextDiagnostic &Diagnostic : YAMLResult.diagnostics()) + Result.Diagnostics.push_back(Diagnostic); + return Result; + } + + ASTParser Parser(*YAMLResult.stream()); + const auto AST = Parser.parse(); + if (!AST) { + DescriptionGenerationResult Result; + Result.Diagnostics = Parser.takeDiagnostics(); + return Result; + } + + ModelNormalizer Normalizer(*AST, SourceName); + const auto Model = Normalizer.normalize(); + if (!Model) { + DescriptionGenerationResult Result; + Result.Diagnostics = Normalizer.takeDiagnostics(); + return Result; + } + + const std::size_t Separator = SourceName.find_last_of("/\\"); + const std::string_view DisplayName = Separator == std::string::npos + ? std::string_view(SourceName) + : std::string_view(SourceName).substr(Separator + 1); + return { + .Output = + GeneratedDescription{ + .RegisterHeader = emitRegisterHeader(*Model, DisplayName), + .TargetTables = emitTargetTables(*Model, DisplayName), + }, + .Diagnostics = {}, + }; +} + +} // namespace rcc::targetgen diff --git a/backend/lib/Target/Description/TargetDescriptionGenerator.h b/backend/lib/Target/Description/TargetDescriptionGenerator.h new file mode 100644 index 0000000..54cbf3c --- /dev/null +++ b/backend/lib/Target/Description/TargetDescriptionGenerator.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_DESCRIPTION_TARGETDESCRIPTIONGENERATOR_H +#define RCC_TARGET_DESCRIPTION_TARGETDESCRIPTIONGENERATOR_H + +#include "rcc/Support/TextDiagnostic.h" + +#include +#include +#include +#include + +namespace rcc::targetgen { + +struct GeneratedDescription final { + std::string RegisterHeader; + std::string TargetTables; +}; + +struct DescriptionGenerationResult final { + std::optional Output; + std::vector Diagnostics; + + [[nodiscard]] bool succeeded() const noexcept { + return Output.has_value() && Diagnostics.empty(); + } +}; + +[[nodiscard]] DescriptionGenerationResult generateDescription(std::string SourceName, + std::string SourceText); + +} // namespace rcc::targetgen + +#endif // RCC_TARGET_DESCRIPTION_TARGETDESCRIPTIONGENERATOR_H diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt new file mode 100644 index 0000000..2c744c0 --- /dev/null +++ b/backend/lib/Target/RISCV/CMakeLists.txt @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +set(RCCBackendRISCVDescription "${CMAKE_CURRENT_SOURCE_DIR}/RISCV.rccdesc") +set( + RCCBackendRISCVGeneratedHeader + "${RCC_BACKEND_BINARY_DIR}/generated/include/rcc/Target/RISCV/RISCVRegisters.h" +) +set( + RCCBackendRISCVGeneratedTables + "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV/RISCVGenTargetDescription.inc" +) + +add_custom_command( + OUTPUT "${RCCBackendRISCVGeneratedHeader}" "${RCCBackendRISCVGeneratedTables}" + COMMAND + "$" + --input "${RCCBackendRISCVDescription}" + --header "${RCCBackendRISCVGeneratedHeader}" + --tables "${RCCBackendRISCVGeneratedTables}" + DEPENDS RCCBackendDescriptionGenerator "${RCCBackendRISCVDescription}" + COMMENT "Generating RCC RV64GC target tables" + VERBATIM +) + +add_custom_target( + RCCBackendRISCVGeneratedTablesTarget + DEPENDS "${RCCBackendRISCVGeneratedHeader}" "${RCCBackendRISCVGeneratedTables}" +) + +set_source_files_properties( + "${RCCBackendRISCVGeneratedHeader}" "${RCCBackendRISCVGeneratedTables}" + PROPERTIES GENERATED TRUE HEADER_FILE_ONLY TRUE +) + +add_library( + RCCBackendRISCV + RISCVTarget.cpp + "${RCCBackendRISCVGeneratedHeader}" + "${RCCBackendRISCVGeneratedTables}" +) +add_library(RCCBackend::RISCV ALIAS RCCBackendRISCV) +add_dependencies(RCCBackendRISCV RCCBackendRISCVGeneratedTablesTarget) + +rcc_backend_configure_target(RCCBackendRISCV) + +target_include_directories( + RCCBackendRISCV + PUBLIC + "$" + "$" + "$" + PRIVATE "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV" +) +target_link_libraries(RCCBackendRISCV PUBLIC RCCBackend::CodeGen) + +set_target_properties( + RCCBackendRISCV + PROPERTIES + EXPORT_NAME RISCV + OUTPUT_NAME RCCBackendRISCV +) + +install( + TARGETS RCCBackendRISCV + EXPORT RCCBackendTargets + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) +install( + FILES "${RCCBackendRISCVGeneratedHeader}" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/rcc/Target/RISCV" +) + +rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTarget.cpp" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTarget.h" +) +rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/RISCVTarget.cpp") diff --git a/backend/lib/Target/RISCV/RISCV.rccdesc b/backend/lib/Target/RISCV/RISCV.rccdesc new file mode 100644 index 0000000..54093e6 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCV.rccdesc @@ -0,0 +1,220 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +schema: rcc-target-description-v1 +target: + namespace: riscv + name: RISCV + triple: riscv64-unknown-linux-gnu + cpu: generic-rv64gc + abi: lp64d + data_layout: e-m:e-p:64:64-i64:64-i128:128-n32:64-S128 + features: [i, m, a, f, d, c, zicsr, zifencei] + +register_units: + - { id: 1, symbol: U_X0, name: x0.unit } + - { id: 2, symbol: U_X1, name: x1.unit } + - { id: 3, symbol: U_X2, name: x2.unit } + - { id: 4, symbol: U_X3, name: x3.unit } + - { id: 5, symbol: U_X4, name: x4.unit } + - { id: 6, symbol: U_X5, name: x5.unit } + - { id: 7, symbol: U_X6, name: x6.unit } + - { id: 8, symbol: U_X7, name: x7.unit } + - { id: 9, symbol: U_X8, name: x8.unit } + - { id: 10, symbol: U_X9, name: x9.unit } + - { id: 11, symbol: U_X10, name: x10.unit } + - { id: 12, symbol: U_X11, name: x11.unit } + - { id: 13, symbol: U_X12, name: x12.unit } + - { id: 14, symbol: U_X13, name: x13.unit } + - { id: 15, symbol: U_X14, name: x14.unit } + - { id: 16, symbol: U_X15, name: x15.unit } + - { id: 17, symbol: U_X16, name: x16.unit } + - { id: 18, symbol: U_X17, name: x17.unit } + - { id: 19, symbol: U_X18, name: x18.unit } + - { id: 20, symbol: U_X19, name: x19.unit } + - { id: 21, symbol: U_X20, name: x20.unit } + - { id: 22, symbol: U_X21, name: x21.unit } + - { id: 23, symbol: U_X22, name: x22.unit } + - { id: 24, symbol: U_X23, name: x23.unit } + - { id: 25, symbol: U_X24, name: x24.unit } + - { id: 26, symbol: U_X25, name: x25.unit } + - { id: 27, symbol: U_X26, name: x26.unit } + - { id: 28, symbol: U_X27, name: x27.unit } + - { id: 29, symbol: U_X28, name: x28.unit } + - { id: 30, symbol: U_X29, name: x29.unit } + - { id: 31, symbol: U_X30, name: x30.unit } + - { id: 32, symbol: U_X31, name: x31.unit } + - { id: 33, symbol: U_F0, name: f0.unit } + - { id: 34, symbol: U_F1, name: f1.unit } + - { id: 35, symbol: U_F2, name: f2.unit } + - { id: 36, symbol: U_F3, name: f3.unit } + - { id: 37, symbol: U_F4, name: f4.unit } + - { id: 38, symbol: U_F5, name: f5.unit } + - { id: 39, symbol: U_F6, name: f6.unit } + - { id: 40, symbol: U_F7, name: f7.unit } + - { id: 41, symbol: U_F8, name: f8.unit } + - { id: 42, symbol: U_F9, name: f9.unit } + - { id: 43, symbol: U_F10, name: f10.unit } + - { id: 44, symbol: U_F11, name: f11.unit } + - { id: 45, symbol: U_F12, name: f12.unit } + - { id: 46, symbol: U_F13, name: f13.unit } + - { id: 47, symbol: U_F14, name: f14.unit } + - { id: 48, symbol: U_F15, name: f15.unit } + - { id: 49, symbol: U_F16, name: f16.unit } + - { id: 50, symbol: U_F17, name: f17.unit } + - { id: 51, symbol: U_F18, name: f18.unit } + - { id: 52, symbol: U_F19, name: f19.unit } + - { id: 53, symbol: U_F20, name: f20.unit } + - { id: 54, symbol: U_F21, name: f21.unit } + - { id: 55, symbol: U_F22, name: f22.unit } + - { id: 56, symbol: U_F23, name: f23.unit } + - { id: 57, symbol: U_F24, name: f24.unit } + - { id: 58, symbol: U_F25, name: f25.unit } + - { id: 59, symbol: U_F26, name: f26.unit } + - { id: 60, symbol: U_F27, name: f27.unit } + - { id: 61, symbol: U_F28, name: f28.unit } + - { id: 62, symbol: U_F29, name: f29.unit } + - { id: 63, symbol: U_F30, name: f30.unit } + - { id: 64, symbol: U_F31, name: f31.unit } + +registers: + - { id: 1, symbol: X0, name: x0, encoding: 0, dwarf_register: 0, units: [{ unit: U_X0, lanes: 1 }], constant: true, reserved: true, aliases: [zero] } + - { id: 2, symbol: X1, name: x1, encoding: 1, dwarf_register: 1, units: [{ unit: U_X1, lanes: 1 }], aliases: [ra] } + - { id: 3, symbol: X2, name: x2, encoding: 2, dwarf_register: 2, units: [{ unit: U_X2, lanes: 1 }], reserved: true, aliases: [sp] } + - { id: 4, symbol: X3, name: x3, encoding: 3, dwarf_register: 3, units: [{ unit: U_X3, lanes: 1 }], reserved: true, aliases: [gp] } + - { id: 5, symbol: X4, name: x4, encoding: 4, dwarf_register: 4, units: [{ unit: U_X4, lanes: 1 }], reserved: true, aliases: [tp] } + - { id: 6, symbol: X5, name: x5, encoding: 5, dwarf_register: 5, units: [{ unit: U_X5, lanes: 1 }], aliases: [t0] } + - { id: 7, symbol: X6, name: x6, encoding: 6, dwarf_register: 6, units: [{ unit: U_X6, lanes: 1 }], aliases: [t1] } + - { id: 8, symbol: X7, name: x7, encoding: 7, dwarf_register: 7, units: [{ unit: U_X7, lanes: 1 }], aliases: [t2] } + - { id: 9, symbol: X8, name: x8, encoding: 8, dwarf_register: 8, units: [{ unit: U_X8, lanes: 1 }], aliases: [s0, fp] } + - { id: 10, symbol: X9, name: x9, encoding: 9, dwarf_register: 9, units: [{ unit: U_X9, lanes: 1 }], aliases: [s1] } + - { id: 11, symbol: X10, name: x10, encoding: 10, dwarf_register: 10, units: [{ unit: U_X10, lanes: 1 }], aliases: [a0] } + - { id: 12, symbol: X11, name: x11, encoding: 11, dwarf_register: 11, units: [{ unit: U_X11, lanes: 1 }], aliases: [a1] } + - { id: 13, symbol: X12, name: x12, encoding: 12, dwarf_register: 12, units: [{ unit: U_X12, lanes: 1 }], aliases: [a2] } + - { id: 14, symbol: X13, name: x13, encoding: 13, dwarf_register: 13, units: [{ unit: U_X13, lanes: 1 }], aliases: [a3] } + - { id: 15, symbol: X14, name: x14, encoding: 14, dwarf_register: 14, units: [{ unit: U_X14, lanes: 1 }], aliases: [a4] } + - { id: 16, symbol: X15, name: x15, encoding: 15, dwarf_register: 15, units: [{ unit: U_X15, lanes: 1 }], aliases: [a5] } + - { id: 17, symbol: X16, name: x16, encoding: 16, dwarf_register: 16, units: [{ unit: U_X16, lanes: 1 }], aliases: [a6] } + - { id: 18, symbol: X17, name: x17, encoding: 17, dwarf_register: 17, units: [{ unit: U_X17, lanes: 1 }], aliases: [a7] } + - { id: 19, symbol: X18, name: x18, encoding: 18, dwarf_register: 18, units: [{ unit: U_X18, lanes: 1 }], aliases: [s2] } + - { id: 20, symbol: X19, name: x19, encoding: 19, dwarf_register: 19, units: [{ unit: U_X19, lanes: 1 }], aliases: [s3] } + - { id: 21, symbol: X20, name: x20, encoding: 20, dwarf_register: 20, units: [{ unit: U_X20, lanes: 1 }], aliases: [s4] } + - { id: 22, symbol: X21, name: x21, encoding: 21, dwarf_register: 21, units: [{ unit: U_X21, lanes: 1 }], aliases: [s5] } + - { id: 23, symbol: X22, name: x22, encoding: 22, dwarf_register: 22, units: [{ unit: U_X22, lanes: 1 }], aliases: [s6] } + - { id: 24, symbol: X23, name: x23, encoding: 23, dwarf_register: 23, units: [{ unit: U_X23, lanes: 1 }], aliases: [s7] } + - { id: 25, symbol: X24, name: x24, encoding: 24, dwarf_register: 24, units: [{ unit: U_X24, lanes: 1 }], aliases: [s8] } + - { id: 26, symbol: X25, name: x25, encoding: 25, dwarf_register: 25, units: [{ unit: U_X25, lanes: 1 }], aliases: [s9] } + - { id: 27, symbol: X26, name: x26, encoding: 26, dwarf_register: 26, units: [{ unit: U_X26, lanes: 1 }], aliases: [s10] } + - { id: 28, symbol: X27, name: x27, encoding: 27, dwarf_register: 27, units: [{ unit: U_X27, lanes: 1 }], aliases: [s11] } + - { id: 29, symbol: X28, name: x28, encoding: 28, dwarf_register: 28, units: [{ unit: U_X28, lanes: 1 }], aliases: [t3] } + - { id: 30, symbol: X29, name: x29, encoding: 29, dwarf_register: 29, units: [{ unit: U_X29, lanes: 1 }], aliases: [t4] } + - { id: 31, symbol: X30, name: x30, encoding: 30, dwarf_register: 30, units: [{ unit: U_X30, lanes: 1 }], aliases: [t5] } + - { id: 32, symbol: X31, name: x31, encoding: 31, dwarf_register: 31, units: [{ unit: U_X31, lanes: 1 }], aliases: [t6] } + - { id: 33, symbol: F0, name: f0, encoding: 0, dwarf_register: 32, units: [{ unit: U_F0, lanes: 1 }], aliases: [ft0] } + - { id: 34, symbol: F1, name: f1, encoding: 1, dwarf_register: 33, units: [{ unit: U_F1, lanes: 1 }], aliases: [ft1] } + - { id: 35, symbol: F2, name: f2, encoding: 2, dwarf_register: 34, units: [{ unit: U_F2, lanes: 1 }], aliases: [ft2] } + - { id: 36, symbol: F3, name: f3, encoding: 3, dwarf_register: 35, units: [{ unit: U_F3, lanes: 1 }], aliases: [ft3] } + - { id: 37, symbol: F4, name: f4, encoding: 4, dwarf_register: 36, units: [{ unit: U_F4, lanes: 1 }], aliases: [ft4] } + - { id: 38, symbol: F5, name: f5, encoding: 5, dwarf_register: 37, units: [{ unit: U_F5, lanes: 1 }], aliases: [ft5] } + - { id: 39, symbol: F6, name: f6, encoding: 6, dwarf_register: 38, units: [{ unit: U_F6, lanes: 1 }], aliases: [ft6] } + - { id: 40, symbol: F7, name: f7, encoding: 7, dwarf_register: 39, units: [{ unit: U_F7, lanes: 1 }], aliases: [ft7] } + - { id: 41, symbol: F8, name: f8, encoding: 8, dwarf_register: 40, units: [{ unit: U_F8, lanes: 1 }], aliases: [fs0] } + - { id: 42, symbol: F9, name: f9, encoding: 9, dwarf_register: 41, units: [{ unit: U_F9, lanes: 1 }], aliases: [fs1] } + - { id: 43, symbol: F10, name: f10, encoding: 10, dwarf_register: 42, units: [{ unit: U_F10, lanes: 1 }], aliases: [fa0] } + - { id: 44, symbol: F11, name: f11, encoding: 11, dwarf_register: 43, units: [{ unit: U_F11, lanes: 1 }], aliases: [fa1] } + - { id: 45, symbol: F12, name: f12, encoding: 12, dwarf_register: 44, units: [{ unit: U_F12, lanes: 1 }], aliases: [fa2] } + - { id: 46, symbol: F13, name: f13, encoding: 13, dwarf_register: 45, units: [{ unit: U_F13, lanes: 1 }], aliases: [fa3] } + - { id: 47, symbol: F14, name: f14, encoding: 14, dwarf_register: 46, units: [{ unit: U_F14, lanes: 1 }], aliases: [fa4] } + - { id: 48, symbol: F15, name: f15, encoding: 15, dwarf_register: 47, units: [{ unit: U_F15, lanes: 1 }], aliases: [fa5] } + - { id: 49, symbol: F16, name: f16, encoding: 16, dwarf_register: 48, units: [{ unit: U_F16, lanes: 1 }], aliases: [fa6] } + - { id: 50, symbol: F17, name: f17, encoding: 17, dwarf_register: 49, units: [{ unit: U_F17, lanes: 1 }], aliases: [fa7] } + - { id: 51, symbol: F18, name: f18, encoding: 18, dwarf_register: 50, units: [{ unit: U_F18, lanes: 1 }], aliases: [fs2] } + - { id: 52, symbol: F19, name: f19, encoding: 19, dwarf_register: 51, units: [{ unit: U_F19, lanes: 1 }], aliases: [fs3] } + - { id: 53, symbol: F20, name: f20, encoding: 20, dwarf_register: 52, units: [{ unit: U_F20, lanes: 1 }], aliases: [fs4] } + - { id: 54, symbol: F21, name: f21, encoding: 21, dwarf_register: 53, units: [{ unit: U_F21, lanes: 1 }], aliases: [fs5] } + - { id: 55, symbol: F22, name: f22, encoding: 22, dwarf_register: 54, units: [{ unit: U_F22, lanes: 1 }], aliases: [fs6] } + - { id: 56, symbol: F23, name: f23, encoding: 23, dwarf_register: 55, units: [{ unit: U_F23, lanes: 1 }], aliases: [fs7] } + - { id: 57, symbol: F24, name: f24, encoding: 24, dwarf_register: 56, units: [{ unit: U_F24, lanes: 1 }], aliases: [fs8] } + - { id: 58, symbol: F25, name: f25, encoding: 25, dwarf_register: 57, units: [{ unit: U_F25, lanes: 1 }], aliases: [fs9] } + - { id: 59, symbol: F26, name: f26, encoding: 26, dwarf_register: 58, units: [{ unit: U_F26, lanes: 1 }], aliases: [fs10] } + - { id: 60, symbol: F27, name: f27, encoding: 27, dwarf_register: 59, units: [{ unit: U_F27, lanes: 1 }], aliases: [fs11] } + - { id: 61, symbol: F28, name: f28, encoding: 28, dwarf_register: 60, units: [{ unit: U_F28, lanes: 1 }], aliases: [ft8] } + - { id: 62, symbol: F29, name: f29, encoding: 29, dwarf_register: 61, units: [{ unit: U_F29, lanes: 1 }], aliases: [ft9] } + - { id: 63, symbol: F30, name: f30, encoding: 30, dwarf_register: 62, units: [{ unit: U_F30, lanes: 1 }], aliases: [ft10] } + - { id: 64, symbol: F31, name: f31, encoding: 31, dwarf_register: 63, units: [{ unit: U_F31, lanes: 1 }], aliases: [ft11] } + +register_banks: + - { id: 1, symbol: GPR, name: GPR, maximum_size_bits: 64 } + - { id: 2, symbol: FPR, name: FPR, maximum_size_bits: 64 } + +register_classes: + - + id: 1 + symbol: GPR + name: GPR + bank: GPR + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30, X31] + allocation_order: [X5, X6, X7, X28, X29, X30, X31, X10, X11, X12, X13, X14, X15, X16, X17, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X1] + - + id: 2 + symbol: GPRC + name: GPRC + bank: GPR + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [X8, X9, X10, X11, X12, X13, X14, X15] + allocation_order: [X10, X11, X12, X13, X14, X15, X8, X9] + - + id: 3 + symbol: FPR64 + name: FPR64 + bank: FPR + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31] + allocation_order: [F10, F11, F12, F13, F14, F15, F16, F17, F0, F1, F2, F3, F4, F5, F6, F7, F28, F29, F30, F31, F8, F9, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27] + - + id: 4 + symbol: FPR64C + name: FPR64C + bank: FPR + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [F8, F9, F10, F11, F12, F13, F14, F15] + allocation_order: [F10, F11, F12, F13, F14, F15, F8, F9] + - + id: 5 + symbol: FPR32 + name: FPR32 + bank: FPR + size_bits: 32 + spill_size_bits: 32 + spill_alignment_bits: 32 + lanes: 1 + registers: [F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31] + allocation_order: [F10, F11, F12, F13, F14, F15, F16, F17, F0, F1, F2, F3, F4, F5, F6, F7, F28, F29, F30, F31, F8, F9, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27] + - + id: 6 + symbol: FPR32C + name: FPR32C + bank: FPR + size_bits: 32 + spill_size_bits: 32 + spill_alignment_bits: 32 + lanes: 1 + registers: [F8, F9, F10, F11, F12, F13, F14, F15] + allocation_order: [F10, F11, F12, F13, F14, F15, F8, F9] + +register_masks: + - + name: CSR_ILP32D_LP64D + preserved: [X1, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, F8, F9, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27] diff --git a/backend/lib/Target/RISCV/RISCVTarget.cpp b/backend/lib/Target/RISCV/RISCVTarget.cpp new file mode 100644 index 0000000..bbad827 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVTarget.cpp @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::riscv { +namespace { + +[[nodiscard]] PhysicalRegister physical(PhysicalRegisterID ID) { + const auto Register = physicalRegister(ID); + assert(Register.has_value()); + return *Register; +} + +[[nodiscard]] RegisterUnitLane unitLane(RegisterUnit Unit, std::uint64_t Lanes) { + return { + .Unit = RegisterUnitID::fromValue(static_cast(Unit)), + .Lanes = LaneMask::fromRaw(Lanes), + }; +} + +[[nodiscard]] std::vector +physicalRegisters(std::initializer_list IDs) { + std::vector Result; + Result.reserve(IDs.size()); + for (const PhysicalRegisterID ID : IDs) + Result.push_back(physical(ID)); + return Result; +} + +[[nodiscard]] RegisterClassDesc registerClass( + RegisterClass ID, const char *Name, std::initializer_list Registers, + std::initializer_list AllocationOrder, RegisterBank Bank, + std::uint32_t SizeInBits, std::uint32_t SpillSizeInBits, std::uint32_t SpillAlignmentInBits, + std::uint64_t Lanes, bool Allocatable, std::uint32_t CopyCost) { + return { + .ID = RegisterClassID::fromValue(static_cast(ID)), + .Name = Name, + .Registers = physicalRegisters(Registers), + .AllocationOrder = physicalRegisters(AllocationOrder), + .SizeInBits = SizeInBits, + .SpillSizeInBits = SpillSizeInBits, + .SpillAlignmentInBits = SpillAlignmentInBits, + .Lanes = LaneMask::fromRaw(Lanes), + .SupportedSubRegs = {}, + .SuperClasses = {}, + .DefaultBank = RegisterBankID::fromValue(static_cast(Bank)), + .Allocatable = Allocatable, + .CopyCost = CopyCost, + }; +} + +[[nodiscard]] std::shared_ptr +makePreservedMask(std::initializer_list Preserved) { + std::vector Words; + for (const PhysicalRegisterID ID : Preserved) { + const std::uint32_t Number = static_cast(ID); + const std::size_t Word = Number / 64U; + if (Words.size() <= Word) + Words.resize(Word + 1U); + Words[Word] |= std::uint64_t{1} << (Number % 64U); + } + return std::make_shared(std::move(Words)); +} + +#include "RISCVGenTargetDescription.inc" + +} // namespace + +Expected physicalRegister(PhysicalRegisterID ID) { + return PhysicalRegister::create(static_cast(ID)); +} + +Expected createRegisterInfo() { + TargetRegisterInfoDesc Description; + appendGeneratedRegisterInfo(Description); + return TargetRegisterInfo::create(std::move(Description)); +} + +Expected createMIRTextInfo() { + auto Registers = createRegisterInfo(); + if (!Registers) + return std::unexpected(Registers.error()); + TargetMIRTextInfoDesc Description; + appendGeneratedMIRTextInfo(Description); + return TargetMIRTextInfo::create(std::move(Description), *Registers); +} + +std::shared_ptr createCallPreservedMask() { + TargetMIRTextInfoDesc Description; + appendGeneratedMIRTextInfo(Description); + assert(!Description.RegisterMasks.empty()); + return Description.RegisterMasks.front().Mask; +} + +} // namespace rcc::riscv diff --git a/backend/tools/CMakeLists.txt b/backend/tools/CMakeLists.txt new file mode 100644 index 0000000..67d7273 --- /dev/null +++ b/backend/tools/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +add_subdirectory(DescriptionGenerator) diff --git a/backend/tools/DescriptionGenerator/CMakeLists.txt b/backend/tools/DescriptionGenerator/CMakeLists.txt new file mode 100644 index 0000000..9bc3f26 --- /dev/null +++ b/backend/tools/DescriptionGenerator/CMakeLists.txt @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +set( + RCCBackendDescriptionGeneratorCoreSources + "${RCC_BACKEND_SOURCE_DIR}/lib/Target/Description/TargetDescriptionGenerator.cpp" +) +set( + RCCBackendDescriptionGeneratorCoreHeaders + "${RCC_BACKEND_SOURCE_DIR}/lib/Target/Description/TargetDescriptionGenerator.h" +) + +add_library( + RCCBackendDescriptionGeneratorCore + STATIC + ${RCCBackendDescriptionGeneratorCoreSources} +) + +rcc_backend_configure_target(RCCBackendDescriptionGeneratorCore) + +target_include_directories( + RCCBackendDescriptionGeneratorCore + PUBLIC "$" +) +target_link_libraries(RCCBackendDescriptionGeneratorCore PUBLIC RCCBackend::Support) + +add_executable(RCCBackendDescriptionGenerator DescriptionGeneratorMain.cpp) +rcc_backend_configure_target(RCCBackendDescriptionGenerator) +target_link_libraries(RCCBackendDescriptionGenerator PRIVATE RCCBackendDescriptionGeneratorCore) +set_target_properties(RCCBackendDescriptionGenerator PROPERTIES OUTPUT_NAME rcc-backend-descgen) + +rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorMain.cpp" + ${RCCBackendDescriptionGeneratorCoreSources} + ${RCCBackendDescriptionGeneratorCoreHeaders} +) +rcc_backend_register_tidy_files( + "${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorMain.cpp" + ${RCCBackendDescriptionGeneratorCoreSources} +) diff --git a/backend/tools/DescriptionGenerator/DescriptionGeneratorMain.cpp b/backend/tools/DescriptionGenerator/DescriptionGeneratorMain.cpp new file mode 100644 index 0000000..11890f6 --- /dev/null +++ b/backend/tools/DescriptionGenerator/DescriptionGeneratorMain.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TargetDescriptionGenerator.h" +#include "rcc/Support/TextDiagnostic.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +struct CommandLine final { + std::string InputPath; + std::string HeaderPath; + std::string TablesPath; +}; + +[[nodiscard]] bool parseCommandLine(int ArgumentCount, char *Arguments[], CommandLine &Result) { + for (int Index = 1; Index < ArgumentCount; ++Index) { + const std::string_view Argument(Arguments[Index]); + if (Argument == "--input" || Argument == "--header" || Argument == "--tables") { + if (Index + 1 >= ArgumentCount) { + std::cerr << "rcc-backend-descgen: missing value for " << Argument << '\n'; + return false; + } + std::string *Destination = nullptr; + if (Argument == "--input") + Destination = &Result.InputPath; + else if (Argument == "--header") + Destination = &Result.HeaderPath; + else + Destination = &Result.TablesPath; + *Destination = Arguments[++Index]; + continue; + } + std::cerr << "rcc-backend-descgen: unknown argument '" << Argument << "'\n"; + return false; + } + if (!Result.InputPath.empty() && !Result.HeaderPath.empty() && !Result.TablesPath.empty()) + return true; + std::cerr << "usage: rcc-backend-descgen --input --header --tables \n"; + return false; +} + +[[nodiscard]] bool writeFile(const std::filesystem::path &Path, std::string_view Contents) { + std::error_code Error; + std::filesystem::create_directories(Path.parent_path(), Error); + if (Error) { + std::cerr << "rcc-backend-descgen: cannot create output directory for '" << Path.string() + << "': " << Error.message() << '\n'; + return false; + } + std::ofstream Output(Path, std::ios::binary | std::ios::trunc); + if (!Output) { + std::cerr << "rcc-backend-descgen: cannot open output '" << Path.string() << "'\n"; + return false; + } + Output.write(Contents.data(), static_cast(Contents.size())); + if (Output) { + Output.close(); + return true; + } + std::cerr << "rcc-backend-descgen: cannot write output '" << Path.string() << "'\n"; + return false; +} + +} // namespace + +int main(int ArgumentCount, char *Arguments[]) { + CommandLine Command; + if (!parseCommandLine(ArgumentCount, Arguments, Command)) + return 1; + + std::ifstream Input(Command.InputPath, std::ios::binary); + if (!Input) { + std::cerr << "rcc-backend-descgen: cannot open input '" << Command.InputPath << "'\n"; + return 1; + } + const std::string SourceText{std::istreambuf_iterator(Input), + std::istreambuf_iterator()}; + const rcc::targetgen::DescriptionGenerationResult Result = + rcc::targetgen::generateDescription(Command.InputPath, SourceText); + if (!Result.succeeded()) { + const rcc::TextSource Source(Command.InputPath, SourceText); + for (const rcc::TextDiagnostic &Diagnostic : Result.Diagnostics) + std::cerr << rcc::formatTextDiagnostic(Diagnostic, Source) << '\n'; + return 1; + } + + return writeFile(Command.HeaderPath, Result.Output->RegisterHeader) && + writeFile(Command.TablesPath, Result.Output->TargetTables) + ? 0 + : 1; +} diff --git a/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp b/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp index da13e19..49f23c0 100644 --- a/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp +++ b/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp @@ -166,6 +166,19 @@ void testDescriptionRejection(TestContext &Context) { Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), "duplicate register unit IDs must be rejected"); } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.PhysicalRegisters.front().AlternativeNames = {"later-name"}; + Description.PhysicalRegisters[1].Name = "later-name"; + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "canonical register names must not collide with earlier ABI aliases"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.PhysicalRegisters.front().IsReserved = true; + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "statically reserved registers must not appear in allocation orders"); + } { auto Description = rcc::test::createTestTargetRegisterInfoDescription(); Description.PhysicalSubRegisters.front().SubReg = phys(rcc::test::TestRegister::G1Low32); diff --git a/backend/unittests/Target/CMakeLists.txt b/backend/unittests/Target/CMakeLists.txt index 514cb97..1ceb1ef 100644 --- a/backend/unittests/Target/CMakeLists.txt +++ b/backend/unittests/Target/CMakeLists.txt @@ -10,8 +10,27 @@ target_include_directories( ) target_link_libraries(RCCBackendTestTarget PUBLIC RCCBackend::CodeGen) +add_executable(RCCBackendDescriptionGeneratorTests DescriptionGeneratorTest.cpp) +rcc_backend_configure_target(RCCBackendDescriptionGeneratorTests) +target_link_libraries( + RCCBackendDescriptionGeneratorTests + PRIVATE RCCBackendDescriptionGeneratorCore +) +add_test(NAME RCCBackendDescriptionGeneratorTests COMMAND RCCBackendDescriptionGeneratorTests) + +add_executable(RCCBackendRISCVTargetTests RISCVTargetTest.cpp) +rcc_backend_configure_target(RCCBackendRISCVTargetTests) +target_link_libraries(RCCBackendRISCVTargetTests PRIVATE RCCBackend::RISCV) +add_test(NAME RCCBackendRISCVTargetTests COMMAND RCCBackendRISCVTargetTests) + rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.h" ) -rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp") +rcc_backend_register_tidy_files( + "${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp" +) diff --git a/backend/unittests/Target/DescriptionGeneratorTest.cpp b/backend/unittests/Target/DescriptionGeneratorTest.cpp new file mode 100644 index 0000000..0ba52b2 --- /dev/null +++ b/backend/unittests/Target/DescriptionGeneratorTest.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "TargetDescriptionGenerator.h" + +#include +#include +#include + +namespace { + +class TestContext final { +public: + void expect(bool Condition, std::string_view Message) { + if (Condition) + return; + std::cerr << Message << '\n'; + ++FailureCount; + } + + [[nodiscard]] int result() const noexcept { + return FailureCount == 0 ? 0 : 1; + } + +private: + int FailureCount = 0; +}; + +[[nodiscard]] std::string validDescription(std::string_view Aliases = "[acc]") { + return std::string(R"(schema: rcc-target-description-v1 +target: + namespace: tiny + name: Tiny + triple: tiny-unknown-none + cpu: tiny-cpu + abi: tiny-abi + data_layout: e-p:64:64 + features: [base] +register_units: + - { id: 1, symbol: U_R1, name: r1.unit } +registers: + - { id: 1, symbol: R1, name: r1, encoding: 0, dwarf_register: 0, units: [{ unit: U_R1, lanes: 1 }], aliases: )") + + std::string(Aliases) + R"( } +register_banks: + - { id: 1, symbol: GPR, name: GPR, maximum_size_bits: 64 } +register_classes: + - { id: 1, symbol: GPR64, name: GPR64, bank: GPR, size_bits: 64, spill_size_bits: 64, spill_alignment_bits: 64, lanes: 1, registers: [R1], allocation_order: [R1] } +register_masks: + - { name: CSR_TINY, preserved: [R1] } +)"; +} + +void testGeneration(TestContext &Context) { + const auto First = rcc::targetgen::generateDescription("tiny.rccdesc", validDescription()); + const auto Second = rcc::targetgen::generateDescription("tiny.rccdesc", validDescription()); + if (!First.succeeded()) { + for (const rcc::TextDiagnostic &Diagnostic : First.Diagnostics) + std::cerr << Diagnostic.Message << '\n'; + } + Context.expect(First.succeeded() && Second.succeeded(), + "a complete restricted target description must generate successfully"); + if (!First.succeeded() || !Second.succeeded()) + return; + + Context.expect(First.Output->RegisterHeader == Second.Output->RegisterHeader && + First.Output->TargetTables == Second.Output->TargetTables, + "target description generation must be deterministic"); + Context.expect( + First.Output->RegisterHeader.find("enum class PhysicalRegisterID") != std::string::npos && + First.Output->RegisterHeader.find("R1 = 1") != std::string::npos && + First.Output->TargetTables.find("appendGeneratedRegisterInfo") != std::string::npos && + First.Output->TargetTables.find("makePreservedMask") != std::string::npos, + "generated output must contain typed register IDs and static target tables"); +} + +void testSemanticRejection(TestContext &Context) { + const auto CollidingAlias = + rcc::targetgen::generateDescription("bad-alias.rccdesc", validDescription("[r1]")); + if (CollidingAlias.succeeded()) + std::cerr << "colliding alias description unexpectedly generated\n"; + Context.expect(!CollidingAlias.succeeded() && !CollidingAlias.Diagnostics.empty() && + CollidingAlias.Diagnostics.front().Message.find("aliases") != + std::string::npos, + "an ABI alias that collides with a canonical name must be rejected"); + + const std::string UnknownUnit = R"(schema: rcc-target-description-v1 +target: + namespace: tiny + name: Tiny + triple: tiny-unknown-none + cpu: tiny-cpu + abi: tiny-abi + data_layout: e-p:64:64 + features: [base] +register_units: + - { id: 1, symbol: U_R1, name: r1.unit } +registers: + - { id: 1, symbol: R1, name: r1, encoding: 0, dwarf_register: 0, units: [{ unit: U_MISSING, lanes: 1 }] } +register_banks: + - { id: 1, symbol: GPR, name: GPR, maximum_size_bits: 64 } +register_classes: + - { id: 1, symbol: GPR64, name: GPR64, bank: GPR, size_bits: 64, spill_size_bits: 64, spill_alignment_bits: 64, lanes: 1, registers: [R1], allocation_order: [R1] } +register_masks: [] +)"; + const auto BadUnit = rcc::targetgen::generateDescription("bad-unit.rccdesc", UnknownUnit); + Context.expect(!BadUnit.succeeded() && !BadUnit.Diagnostics.empty() && + BadUnit.Diagnostics.front().Message.find("declared units") != + std::string::npos, + "a register unit reference must resolve during semantic validation"); +} + +} // namespace + +int main() { + TestContext Context; + testGeneration(Context); + testSemanticRejection(Context); + return Context.result(); +} diff --git a/backend/unittests/Target/RISCVTargetTest.cpp b/backend/unittests/Target/RISCVTargetTest.cpp new file mode 100644 index 0000000..27a862d --- /dev/null +++ b/backend/unittests/Target/RISCVTargetTest.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include +#include +#include +#include + +namespace { + +class TestContext final { +public: + void expect(bool Condition, std::string_view Message) { + if (Condition) + return; + std::cerr << Message << '\n'; + ++FailureCount; + } + + [[nodiscard]] int result() const noexcept { + return FailureCount == 0 ? 0 : 1; + } + +private: + int FailureCount = 0; +}; + +[[nodiscard]] rcc::PhysicalRegister registerID(rcc::riscv::PhysicalRegisterID ID) { + const auto Register = rcc::riscv::physicalRegister(ID); + if (!Register) + std::terminate(); + return *Register; +} + +void testRegisterFacts(TestContext &Context) { + auto Registers = rcc::riscv::createRegisterInfo(); + Context.expect(Registers.has_value(), "RV64GC register description must be valid"); + if (!Registers) + return; + + const rcc::PhysicalRegister X0 = registerID(rcc::riscv::PhysicalRegisterID::X0); + const rcc::PhysicalRegister X2 = registerID(rcc::riscv::PhysicalRegisterID::X2); + const rcc::PhysicalRegister X5 = registerID(rcc::riscv::PhysicalRegisterID::X5); + const rcc::PhysicalRegister X8 = registerID(rcc::riscv::PhysicalRegisterID::X8); + const rcc::PhysicalRegister F31 = registerID(rcc::riscv::PhysicalRegisterID::F31); + + Context.expect( + Registers->physicalRegisters().size() == 64 && Registers->registerUnits().size() == 64 && + Registers->registerClasses().size() == 6 && Registers->registerBanks().size() == 2, + "RV64GC must expose the complete scalar integer and floating register files"); + Context.expect(Registers->physicalRegister("x0") != nullptr && + Registers->physicalRegister("zero") == nullptr && + Registers->physicalRegisterByAlternativeName("ZERO") != nullptr && + Registers->physicalRegisterByAlternativeName("zero")->Reg == X0 && + Registers->physicalRegisterByAlternativeName("Sp") != nullptr && + Registers->physicalRegisterByAlternativeName("sp")->Reg == X2 && + Registers->physicalRegisterMIRName(X8) == "x8", + "canonical MIR names and case-insensitive ABI aliases must use separate lookups"); + Context.expect( + Registers->isReservedRegister(X0) && Registers->isReservedRegister(X2) && + !Registers->isReservedRegister(X5), + "zero, stack, global, and thread register constraints must be recorded statically"); + Context.expect(Registers->physicalRegister(F31) != nullptr && + Registers->physicalRegister(F31)->Encoding == 31, + "floating register physical IDs must remain distinct from their ISA encodings"); + + const auto *GPR = Registers->registerClass("gpr"); + Context.expect( + GPR != nullptr && std::ranges::find(GPR->AllocationOrder, X0) == GPR->AllocationOrder.end() && + std::ranges::find(GPR->AllocationOrder, X2) == GPR->AllocationOrder.end() && + std::ranges::find(GPR->AllocationOrder, X5) != GPR->AllocationOrder.end(), + "the general-register allocation order must exclude statically reserved registers"); +} + +void testABIAndDebugFacts(TestContext &Context) { + const rcc::PhysicalRegister X1 = registerID(rcc::riscv::PhysicalRegisterID::X1); + const rcc::PhysicalRegister X10 = registerID(rcc::riscv::PhysicalRegisterID::X10); + const rcc::PhysicalRegister F8 = registerID(rcc::riscv::PhysicalRegisterID::F8); + const rcc::PhysicalRegister F10 = registerID(rcc::riscv::PhysicalRegisterID::F10); + const rcc::PhysicalRegister F31 = registerID(rcc::riscv::PhysicalRegisterID::F31); + + const auto Mask = rcc::riscv::createCallPreservedMask(); + Context.expect( + Mask != nullptr && Mask->preserves(X1) && Mask->preserves(F8) && Mask->clobbers(X10) && + Mask->clobbers(F10), + "LP64D preserved-mask facts must distinguish saved and caller-clobbered registers"); + + auto MIRText = rcc::riscv::createMIRTextInfo(); + Context.expect(MIRText.has_value(), "RV64GC MIR text facts must be valid"); + if (!MIRText) + return; + Context.expect(MIRText->registerMasks().size() == 1 && + MIRText->registerMasks().front().Name == "csr_ilp32d_lp64d", + "RV64GC must expose its named LP64D call-preserved mask"); + Context.expect(MIRText->dwarfRegister(X1) == 1, + "the return-address register must retain its DWARF register number"); + Context.expect(MIRText->physicalRegister(63) == F31, + "the final floating register DWARF number must map to f31"); +} + +} // namespace + +int main() { + TestContext Context; + testRegisterFacts(Context); + testABIAndDebugFacts(Context); + return Context.result(); +} diff --git a/backend/unittests/Target/TestTarget.cpp b/backend/unittests/Target/TestTarget.cpp index 4d31fb9..a367454 100644 --- a/backend/unittests/Target/TestTarget.cpp +++ b/backend/unittests/Target/TestTarget.cpp @@ -25,7 +25,8 @@ namespace { [[nodiscard]] PhysicalRegisterDesc physicalRegister(std::uint32_t Number, const char *Name, std::uint32_t Encoding, std::vector Units) { - return PhysicalRegisterDesc{phys(Number), Name, Encoding, std::move(Units)}; + return PhysicalRegisterDesc{phys(Number), Name, Encoding, std::move(Units), + false, false, false, {}}; } [[nodiscard]] RegisterClassDesc -- cgit v1.2.3