aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/Target
diff options
context:
space:
mode:
Diffstat (limited to 'backend/unittests/Target')
-rw-r--r--backend/unittests/Target/DescriptionGeneratorTest.cpp125
-rw-r--r--backend/unittests/Target/RISCVTargetTest.cpp97
2 files changed, 199 insertions, 23 deletions
diff --git a/backend/unittests/Target/DescriptionGeneratorTest.cpp b/backend/unittests/Target/DescriptionGeneratorTest.cpp
index 0ba52b2..d41d2d4 100644
--- a/backend/unittests/Target/DescriptionGeneratorTest.cpp
+++ b/backend/unittests/Target/DescriptionGeneratorTest.cpp
@@ -2,6 +2,7 @@
#include "TargetDescriptionGenerator.h"
+#include <cstddef>
#include <iostream>
#include <string>
#include <string_view>
@@ -26,7 +27,7 @@ private:
};
[[nodiscard]] std::string validDescription(std::string_view Aliases = "[acc]") {
- return std::string(R"(schema: rcc-target-description-v1
+ return std::string(R"(schema: rcc-target-description-v2
target:
namespace: tiny
name: Tiny
@@ -46,6 +47,25 @@ 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] }
+mir_operand_flags:
+ direct_mask: 31
+ direct: [{ name: tiny-call, value: 1 }]
+ bitmask: [{ name: tiny-relax, value: 32 }]
+calling_conventions:
+ - { id: 1, symbol: TINY, name: tiny, integer_register_class: GPR64, floating_register_class: GPR64, integer_argument_registers: [R1], floating_argument_registers: [R1], integer_return_registers: [R1], floating_return_registers: [R1], preserved_mask: CSR_TINY, stack_alignment_bytes: 8, stack_argument_alignment_bytes: 8, shadow_space_bytes: 0, supports_varargs: true, supports_tail_calls: true }
+named_operands:
+ - { id: 1, symbol: RD, name: rd }
+ - { id: 2, symbol: IMM, name: imm }
+immediate_predicates:
+ - { id: 1, symbol: SIMM8, name: simm8, kind: signed, bits: 8 }
+scheduling_classes:
+ - { id: 1, symbol: ALU, name: alu, micro_ops: 1, latency: 1 }
+encoding_categories:
+ - { id: 1, symbol: I, name: i, size_bytes: 4 }
+assembly_formats:
+ - { id: 1, symbol: RD_IMM, name: rd-imm }
+instructions:
+ - { id: 1024, symbol: LOAD_IMM, name: LOAD_IMM, assembly_mnemonic: load.imm, operands: [{ kinds: [register], role: def, register_class: GPR64, register_bank: GPR, name: RD }, { kinds: [immediate], immediate_predicate: SIMM8, name: IMM, allowed_target_flags: 33 }], num_explicit_defs: 1, flags: [produces_code], scheduling_class: ALU, encoding_category: I, assembly_format: RD_IMM }
)";
}
@@ -62,14 +82,25 @@ void testGeneration(TestContext &Context) {
return;
Context.expect(First.Output->RegisterHeader == Second.Output->RegisterHeader &&
+ First.Output->InstructionHeader == Second.Output->InstructionHeader &&
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->InstructionHeader.find("enum class InstructionOpcode") !=
+ std::string::npos &&
+ First.Output->InstructionHeader.find("LOAD_IMM = 1024") != std::string::npos &&
First.Output->TargetTables.find("appendGeneratedRegisterInfo") != std::string::npos &&
+ First.Output->TargetTables.find("appendGeneratedInstructionInfo") != std::string::npos &&
+ First.Output->TargetTables.find("GeneratedAssemblyInstructions") != std::string::npos &&
+ First.Output->TargetTables.find("Description.DirectOperandFlagMask = 31") !=
+ std::string::npos &&
+ First.Output->TargetTables.find("\"tiny-call\", 1") != std::string::npos &&
+ First.Output->TargetTables.find("\"load.imm\"") != std::string::npos &&
First.Output->TargetTables.find("makePreservedMask") != std::string::npos,
- "generated output must contain typed register IDs and static target tables");
+ "generated output must contain typed target IDs, assembly mnemonics, and static target "
+ "tables");
}
void testSemanticRejection(TestContext &Context) {
@@ -82,30 +113,82 @@ void testSemanticRejection(TestContext &Context) {
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: []
-)";
+ std::string UnknownUnit = validDescription();
+ constexpr std::string_view UnitReference = "unit: U_R1";
+ const std::size_t UnitPosition = UnknownUnit.find(UnitReference);
+ if (UnitPosition != std::string::npos)
+ UnknownUnit.replace(UnitPosition, UnitReference.size(), "unit: U_MISSING");
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");
+
+ std::string UnknownClass = validDescription();
+ constexpr std::string_view ClassReference = "register_class: GPR64";
+ const std::size_t ClassPosition = UnknownClass.find(ClassReference);
+ if (ClassPosition != std::string::npos)
+ UnknownClass.replace(ClassPosition, ClassReference.size(), "register_class: MISSING");
+ const auto BadClass =
+ rcc::targetgen::generateDescription("bad-instruction-class.rccdesc", UnknownClass);
+ Context.expect(!BadClass.succeeded() && !BadClass.Diagnostics.empty() &&
+ BadClass.Diagnostics.front().Message.find("register class") !=
+ std::string::npos,
+ "instruction register classes must resolve during semantic validation");
+
+ std::string BadAllowedValue = validDescription();
+ constexpr std::string_view Predicate = "kind: signed, bits: 8";
+ const std::size_t PredicatePosition = BadAllowedValue.find(Predicate);
+ if (PredicatePosition != std::string::npos) {
+ BadAllowedValue.replace(PredicatePosition, Predicate.size(),
+ "kind: signed, bits: 8, allowed_values: [128]");
+ }
+ const auto InvalidAllowedValue =
+ rcc::targetgen::generateDescription("bad-allowed-value.rccdesc", BadAllowedValue);
+ Context.expect(!InvalidAllowedValue.succeeded() && !InvalidAllowedValue.Diagnostics.empty() &&
+ InvalidAllowedValue.Diagnostics.front().Message.find("allowed_values") !=
+ std::string::npos,
+ "immediate predicates must reject allowed values outside their signed range");
+
+ std::string ContradictoryNonZero = validDescription();
+ if (PredicatePosition != std::string::npos) {
+ const std::size_t Position = ContradictoryNonZero.find(Predicate);
+ ContradictoryNonZero.replace(Position, Predicate.size(),
+ "kind: signed, bits: 8, nonzero: true, allowed_values: [0]");
+ }
+ const auto InvalidNonZero =
+ rcc::targetgen::generateDescription("bad-nonzero.rccdesc", ContradictoryNonZero);
+ Context.expect(!InvalidNonZero.succeeded() && !InvalidNonZero.Diagnostics.empty() &&
+ InvalidNonZero.Diagnostics.front().Message.find("allowed_values") !=
+ std::string::npos,
+ "nonzero immediate predicates must reject an explicit zero value");
+
+ std::string BadStackAlignment = validDescription();
+ constexpr std::string_view StackAlignment =
+ "stack_alignment_bytes: 8, stack_argument_alignment_bytes: 8";
+ const std::size_t StackPosition = BadStackAlignment.find(StackAlignment);
+ if (StackPosition != std::string::npos) {
+ BadStackAlignment.replace(StackPosition, StackAlignment.size(),
+ "stack_alignment_bytes: 8, stack_argument_alignment_bytes: 16");
+ }
+ const auto InvalidStackAlignment =
+ rcc::targetgen::generateDescription("bad-stack-alignment.rccdesc", BadStackAlignment);
+ Context.expect(!InvalidStackAlignment.succeeded() && !InvalidStackAlignment.Diagnostics.empty() &&
+ InvalidStackAlignment.Diagnostics.front().Message.find("stack alignments") !=
+ std::string::npos,
+ "calling conventions must reject incompatible stack alignments");
+
+ std::string BadMnemonic = validDescription();
+ constexpr std::string_view Mnemonic = "assembly_mnemonic: load.imm";
+ const std::size_t MnemonicPosition = BadMnemonic.find(Mnemonic);
+ if (MnemonicPosition != std::string::npos)
+ BadMnemonic.replace(MnemonicPosition, Mnemonic.size(), "assembly_mnemonic: .load");
+ const auto InvalidMnemonic =
+ rcc::targetgen::generateDescription("bad-mnemonic.rccdesc", BadMnemonic);
+ Context.expect(!InvalidMnemonic.succeeded() && !InvalidMnemonic.Diagnostics.empty() &&
+ InvalidMnemonic.Diagnostics.front().Message.find("assembly mnemonics") !=
+ std::string::npos,
+ "assembly mnemonics must use a canonical lowercase spelling");
}
} // namespace
diff --git a/backend/unittests/Target/RISCVTargetTest.cpp b/backend/unittests/Target/RISCVTargetTest.cpp
index 27a862d..98525e9 100644
--- a/backend/unittests/Target/RISCVTargetTest.cpp
+++ b/backend/unittests/Target/RISCVTargetTest.cpp
@@ -2,6 +2,9 @@
#include "rcc/Target/RISCV/RISCVTarget.h"
+#include "rcc/CodeGen/GenericOpcodes.h"
+
+#include <algorithm>
#include <cstdint>
#include <iostream>
#include <ranges>
@@ -47,8 +50,8 @@ void testRegisterFacts(TestContext &Context) {
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");
+ Registers->registerClasses().size() == 8 && Registers->registerBanks().size() == 2,
+ "RV64GC must expose the complete scalar, compressed, and restricted register classes");
Context.expect(Registers->physicalRegister("x0") != nullptr &&
Registers->physicalRegister("zero") == nullptr &&
Registers->physicalRegisterByAlternativeName("ZERO") != nullptr &&
@@ -71,6 +74,9 @@ void testRegisterFacts(TestContext &Context) {
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");
+ Context.expect(Registers->registerClass("gpr_nx0") != nullptr &&
+ Registers->registerClass("gpr_nx0_nx2") != nullptr,
+ "compressed instructions must have explicit nonzero register classes");
}
void testABIAndDebugFacts(TestContext &Context) {
@@ -93,10 +99,96 @@ void testABIAndDebugFacts(TestContext &Context) {
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->directOperandFlagMask() == 31 &&
+ MIRText->directOperandFlag("riscv-call") == 1,
+ "RV64GC MIR target flags must expose the canonical direct-call spelling");
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");
+
+ const auto Conventions = rcc::riscv::callingConventions();
+ const auto *LP64D = rcc::riscv::callingConvention(rcc::riscv::CallingConvention::LP64D);
+ Context.expect(
+ Conventions.size() == 1 && LP64D != nullptr && LP64D->Name == "lp64d" &&
+ LP64D->IntegerArgumentRegisters.size() == 8 &&
+ LP64D->FloatingArgumentRegisters.size() == 8 &&
+ LP64D->IntegerReturnRegisters.size() == 2 && LP64D->FloatingReturnRegisters.size() == 2 &&
+ LP64D->StackAlignmentBytes == 16 && LP64D->StackArgumentAlignmentBytes == 16 &&
+ LP64D->SupportsVarArgs && LP64D->SupportsTailCalls &&
+ LP64D->PreservedMaskName == "CSR_ILP32D_LP64D",
+ "LP64D calling-convention tables must expose register, stack, and tail-call facts");
+}
+
+void testInstructionFacts(TestContext &Context) {
+ auto Target = rcc::riscv::createTargetDescription();
+ Context.expect(Target.has_value(), "RV64GC target description must be structurally valid");
+ if (!Target) {
+ std::cerr << "target construction error: " << Target.error().message() << '\n';
+ return;
+ }
+
+ const auto *Addi = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::ADDI)));
+ const auto *Load = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::LD)));
+ const auto *Branch = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::BEQ)));
+ const auto *Floating = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::FADD_S)));
+ const auto *Atomic = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::LR_W)));
+ const auto *Compressed = Target->instructionDescriptor(rcc::Opcode::fromValue(
+ static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::C_ADDI16SP)));
+ const auto *GenericAdd = Target->instructionDescriptor(rcc::opcode(rcc::GenericOpcode::G_ADD));
+ const auto *PseudoCall = Target->instructionDescriptor(rcc::Opcode::fromValue(
+ static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::PseudoCALL)));
+ const auto *Assembly = rcc::riscv::assemblyInstruction(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::ADDI)));
+ const auto *FenceAssembly = rcc::riscv::assemblyInstruction(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::FENCE_I)));
+ Context.expect(
+ Target->instructions().descriptors().size() ==
+ 201 + rcc::genericInstructionDescriptors().size() &&
+ Addi != nullptr && Addi->Name == "ADDI" && Addi->NumExplicitDefs == 1 &&
+ Addi->Operands.size() == 3 && Addi->Operands.back().ImmediatePredicate &&
+ Load != nullptr && Load->Flags.has(rcc::InstructionFlag::MayLoad) &&
+ Load->RequiresMemoryOperands && Branch != nullptr &&
+ Branch->Flags.has(rcc::InstructionFlag::Terminator) &&
+ Branch->Flags.has(rcc::InstructionFlag::Branch) && Floating != nullptr &&
+ Floating->Operands.size() == 4 && Floating->Operands.back().ImmediatePredicate &&
+ Atomic != nullptr && Atomic->Flags.has(rcc::InstructionFlag::MayLoad) &&
+ Atomic->RequiresMemoryOperands && Compressed != nullptr &&
+ Compressed->ImplicitDefs.size() == 1 && Compressed->ImplicitUses.size() == 1 &&
+ GenericAdd != nullptr && GenericAdd->Flags.has(rcc::InstructionFlag::Generic) &&
+ PseudoCall != nullptr && PseudoCall->Flags.has(rcc::InstructionFlag::Pseudo) &&
+ PseudoCall->Flags.has(rcc::InstructionFlag::Call) &&
+ rcc::riscv::assemblyInstruction(PseudoCall->InstructionOpcode) == nullptr &&
+ Assembly != nullptr && Assembly->Mnemonic == "addi" &&
+ Assembly->Format == rcc::AssemblyFormatID::fromValue(static_cast<std::uint32_t>(
+ rcc::riscv::AssemblyFormat::RD_RS1_IMM)),
+ "generated instruction and assembly descriptors must drive the formal RV64GC target");
+
+ const auto Schedules = rcc::riscv::schedulingClasses();
+ const auto Encodings = rcc::riscv::encodingCategories();
+ const auto Formats = rcc::riscv::assemblyFormats();
+ Context.expect(Schedules.size() == 15 && Encodings.size() == 10 && Formats.size() == 34 &&
+ rcc::riscv::assemblyInstructions().size() == 192 && FenceAssembly != nullptr &&
+ FenceAssembly->Mnemonic == "fence.i" &&
+ std::ranges::any_of(Schedules,
+ [](const rcc::riscv::SchedulingClassInfo &Class) {
+ return Class.Name == "load" && Class.MicroOps == 1 &&
+ Class.Latency == 3;
+ }) &&
+ std::ranges::any_of(Encodings,
+ [](const rcc::riscv::EncodingCategoryInfo &Category) {
+ return Category.Name == "i" && Category.SizeInBytes == 4;
+ }) &&
+ std::ranges::any_of(Formats,
+ [](const rcc::riscv::AssemblyFormatInfo &Format) {
+ return Format.Name == "rd-rs1-imm";
+ }),
+ "generated scheduling, encoding, and assembly tables must preserve target facts");
}
} // namespace
@@ -105,5 +197,6 @@ int main() {
TestContext Context;
testRegisterFacts(Context);
testABIAndDebugFacts(Context);
+ testInstructionFacts(Context);
return Context.result();
}