aboutsummaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-07 02:15:09 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-07 02:15:09 +0800
commite16de95dce32f077361577377d5c52fc4ad78339 (patch)
treeea563652a95e6e32eb86ce68845a2005d83a85d5 /backend
parent76d89700953ddd81cf31ab8f260eddf67e93aa85 (diff)
downloadrcc-project-e16de95dce32f077361577377d5c52fc4ad78339.tar.gz
rcc-project-e16de95dce32f077361577377d5c52fc4ad78339.tar.xz
[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.
Diffstat (limited to 'backend')
-rw-r--r--backend/CMakeLists.txt2
-rw-r--r--backend/include/rcc/CodeGen/TargetRegisterInfo.h7
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVTarget.h23
-rw-r--r--backend/lib/CodeGen/TargetDescription.cpp17
-rw-r--r--backend/lib/CodeGen/TargetRegisterInfo.cpp30
-rw-r--r--backend/lib/Target/CMakeLists.txt3
-rw-r--r--backend/lib/Target/Description/TargetDescriptionGenerator.cpp1150
-rw-r--r--backend/lib/Target/Description/TargetDescriptionGenerator.h34
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt79
-rw-r--r--backend/lib/Target/RISCV/RISCV.rccdesc220
-rw-r--r--backend/lib/Target/RISCV/RISCVTarget.cpp103
-rw-r--r--backend/tools/CMakeLists.txt3
-rw-r--r--backend/tools/DescriptionGenerator/CMakeLists.txt39
-rw-r--r--backend/tools/DescriptionGenerator/DescriptionGeneratorMain.cpp97
-rw-r--r--backend/unittests/CodeGen/TargetRegisterInfoTest.cpp13
-rw-r--r--backend/unittests/Target/CMakeLists.txt21
-rw-r--r--backend/unittests/Target/DescriptionGeneratorTest.cpp118
-rw-r--r--backend/unittests/Target/RISCVTargetTest.cpp109
-rw-r--r--backend/unittests/Target/TestTarget.cpp3
19 files changed, 2062 insertions, 9 deletions
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<RegisterUnitLane> Units;
bool IsConstant = false;
bool IsArtificial = false;
+ bool IsReserved = false;
+ std::vector<std::string> 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<RegisterUnitID, std::size_t> UnitToIndex;
std::unordered_map<PhysicalRegister, std::size_t> RegisterToIndex;
std::unordered_map<std::string, std::size_t> RegisterNameToIndex;
+ std::unordered_map<std::string, std::size_t> AlternativeRegisterNameToIndex;
std::unordered_map<SubRegIndex, std::size_t> SubRegIndexToIndex;
std::unordered_map<std::string, std::size_t> SubRegNameToIndex;
std::unordered_map<RegisterClassID, std::size_t> 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 <cstdint>
+#include <memory>
+
+namespace rcc::riscv {
+
+[[nodiscard]] Expected<PhysicalRegister> physicalRegister(PhysicalRegisterID ID);
+[[nodiscard]] Expected<TargetRegisterInfo> createRegisterInfo();
+[[nodiscard]] Expected<TargetMIRTextInfo> createMIRTextInfo();
+[[nodiscard]] std::shared_ptr<const RegisterMask> 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<void> 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<void> 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<void> 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> 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> TargetRegisterInfo::create(TargetRegisterInfoDesc D
std::unordered_set<PhysicalRegister> 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 <algorithm>
+#include <array>
+#include <cctype>
+#include <charconv>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+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<std::string> 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<SourceRegisterUnitLane> Units;
+ bool IsConstant = false;
+ bool IsArtificial = false;
+ bool IsReserved = false;
+ std::vector<std::string> 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<std::string> RegisterSymbols;
+ std::vector<std::string> AllocationOrderSymbols;
+ bool Allocatable = true;
+ std::uint32_t CopyCost = 1;
+ TextRange Range;
+};
+
+struct SourceRegisterMask final {
+ std::string Name;
+ std::vector<std::string> PreservedRegisterSymbols;
+ TextRange Range;
+};
+
+struct DescriptionAST final {
+ SourceTarget Target;
+ std::vector<SourceRegisterUnit> RegisterUnits;
+ std::vector<SourceRegister> Registers;
+ std::vector<SourceRegisterBank> RegisterBanks;
+ std::vector<SourceRegisterClass> RegisterClasses;
+ std::vector<SourceRegisterMask> 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<NormalizedRegisterUnitLane> Units;
+ bool IsConstant = false;
+ bool IsArtificial = false;
+ bool IsReserved = false;
+ std::vector<std::string> AlternativeNames;
+};
+
+struct NormalizedRegisterBank final {
+ std::uint32_t ID = 0;
+ std::string Symbol;
+ std::string Name;
+ std::uint32_t MaximumSizeInBits = 0;
+ std::vector<std::uint32_t> 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<std::uint32_t> Registers;
+ std::vector<std::uint32_t> AllocationOrder;
+ bool Allocatable = true;
+ std::uint32_t CopyCost = 1;
+};
+
+struct NormalizedRegisterMask final {
+ std::string Name;
+ std::vector<std::uint32_t> PreservedRegisters;
+};
+
+struct NormalizedTargetModel final {
+ SourceTarget Target;
+ std::vector<SourceRegisterUnit> RegisterUnits;
+ std::vector<NormalizedRegister> Registers;
+ std::vector<NormalizedRegisterBank> RegisterBanks;
+ std::vector<NormalizedRegisterClass> RegisterClasses;
+ std::vector<NormalizedRegisterMask> 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<char>(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<unsigned char>(Value.front())))
+ return false;
+ return std::ranges::all_of(Value.substr(1), [IsTail](char Character) {
+ return IsTail(static_cast<unsigned char>(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<char>(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<DescriptionAST> parse() {
+ const std::span<const yaml::Document> 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<TextDiagnostic> 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<std::string_view> 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<yaml::NodeID> 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<std::string>
+ 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<std::string> 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<std::uint64_t> 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<std::uint32_t> 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<std::uint32_t>::max()) {
+ fail(Node.range(), std::string(Context) + " is outside the uint32 range");
+ return std::nullopt;
+ }
+ return static_cast<std::uint32_t>(*Value);
+ }
+
+ [[nodiscard]] std::optional<bool> 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<std::vector<std::string>> scalarSequence(const yaml::Node &Node,
+ std::string_view Context) {
+ if (!requireSequence(&Node, Context))
+ return std::nullopt;
+ std::vector<std::string> 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<SourceRegisterUnit> &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<std::vector<SourceRegisterUnitLane>>
+ parseRegisterUnitLanes(const yaml::Node &Node) {
+ if (!requireSequence(&Node, "register units"))
+ return std::nullopt;
+ std::vector<SourceRegisterUnitLane> 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<SourceRegister> &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<SourceRegisterBank> &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<SourceRegisterClass> &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<SourceRegisterMask> &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<yaml::NodeID> Value = Mapping.mappingValue(Key);
+ return Value ? Input.node(*Value) : nullptr;
+ }
+
+ const yaml::Stream &Input;
+ std::vector<TextDiagnostic> Diagnostics;
+};
+
+class ModelNormalizer final {
+public:
+ ModelNormalizer(const DescriptionAST &Input, std::string SourceName)
+ : Input(Input), SourceName(std::move(SourceName)) {}
+
+ [[nodiscard]] std::optional<NormalizedTargetModel> 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<TextDiagnostic> 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<std::string> 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<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> 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<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> Names;
+ std::unordered_set<std::uint32_t> 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<std::uint32_t> 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<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> 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<std::uint32_t> IDs;
+ std::unordered_set<std::string> Symbols;
+ std::unordered_set<std::string> 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<std::uint32_t> 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<std::uint32_t> 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<std::string> 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<std::uint32_t> 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<std::string, std::uint32_t> UnitBySymbol;
+ std::unordered_map<std::string, std::uint32_t> RegisterBySymbol;
+ std::unordered_map<std::string, std::uint32_t> BankBySymbol;
+ std::unordered_map<std::string, std::uint32_t> ClassBySymbol;
+ std::vector<TextDiagnostic> 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 <cstdint>\n#include <string_view>\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<const std::uint32_t> 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 <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc::targetgen {
+
+struct GeneratedDescription final {
+ std::string RegisterHeader;
+ std::string TargetTables;
+};
+
+struct DescriptionGenerationResult final {
+ std::optional<GeneratedDescription> Output;
+ std::vector<TextDiagnostic> 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
+ "$<TARGET_FILE:RCCBackendDescriptionGenerator>"
+ --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
+ "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/include>"
+ "$<BUILD_INTERFACE:${RCC_BACKEND_BINARY_DIR}/generated/include>"
+ "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
+ 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 <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <memory>
+#include <utility>
+#include <vector>
+
+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<std::uint32_t>(Unit)),
+ .Lanes = LaneMask::fromRaw(Lanes),
+ };
+}
+
+[[nodiscard]] std::vector<PhysicalRegister>
+physicalRegisters(std::initializer_list<PhysicalRegisterID> IDs) {
+ std::vector<PhysicalRegister> 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<PhysicalRegisterID> Registers,
+ std::initializer_list<PhysicalRegisterID> 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<std::uint32_t>(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<std::uint32_t>(Bank)),
+ .Allocatable = Allocatable,
+ .CopyCost = CopyCost,
+ };
+}
+
+[[nodiscard]] std::shared_ptr<const RegisterMask>
+makePreservedMask(std::initializer_list<PhysicalRegisterID> Preserved) {
+ std::vector<std::uint64_t> Words;
+ for (const PhysicalRegisterID ID : Preserved) {
+ const std::uint32_t Number = static_cast<std::uint32_t>(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<const RegisterMask>(std::move(Words));
+}
+
+#include "RISCVGenTargetDescription.inc"
+
+} // namespace
+
+Expected<PhysicalRegister> physicalRegister(PhysicalRegisterID ID) {
+ return PhysicalRegister::create(static_cast<std::uint32_t>(ID));
+}
+
+Expected<TargetRegisterInfo> createRegisterInfo() {
+ TargetRegisterInfoDesc Description;
+ appendGeneratedRegisterInfo(Description);
+ return TargetRegisterInfo::create(std::move(Description));
+}
+
+Expected<TargetMIRTextInfo> 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<const RegisterMask> 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 "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/lib/Target/Description>"
+)
+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 <filesystem>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <string_view>
+
+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 <file> --header <file> --tables <file>\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<std::streamsize>(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<char>(Input),
+ std::istreambuf_iterator<char>()};
+ 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
@@ -168,6 +168,19 @@ void testDescriptionRejection(TestContext &Context) {
}
{
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);
Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)),
"subregister mappings with unrelated units must be rejected");
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 <iostream>
+#include <string>
+#include <string_view>
+
+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 <cstdint>
+#include <iostream>
+#include <ranges>
+#include <string_view>
+
+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<RegisterUnitLane> Units) {
- return PhysicalRegisterDesc{phys(Number), Name, Encoding, std::move(Units)};
+ return PhysicalRegisterDesc{phys(Number), Name, Encoding, std::move(Units),
+ false, false, false, {}};
}
[[nodiscard]] RegisterClassDesc