aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/Target/RISCVTargetTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backend/unittests/Target/RISCVTargetTest.cpp')
-rw-r--r--backend/unittests/Target/RISCVTargetTest.cpp330
1 files changed, 330 insertions, 0 deletions
diff --git a/backend/unittests/Target/RISCVTargetTest.cpp b/backend/unittests/Target/RISCVTargetTest.cpp
new file mode 100644
index 0000000..c067168
--- /dev/null
+++ b/backend/unittests/Target/RISCVTargetTest.cpp
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include "rcc/CodeGen/GenericOpcodes.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#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 F31D = registerID(rcc::riscv::PhysicalRegisterID::F31_D);
+ const rcc::PhysicalRegister F31F = registerID(rcc::riscv::PhysicalRegisterID::F31_F);
+ const rcc::PhysicalRegister FFLAGS = registerID(rcc::riscv::PhysicalRegisterID::FFLAGS);
+ const rcc::PhysicalRegister FRM = registerID(rcc::riscv::PhysicalRegisterID::FRM);
+ const rcc::PhysicalRegister V8 = registerID(rcc::riscv::PhysicalRegisterID::V8);
+
+ Context.expect(
+ Registers->physicalRegisters().size() == 326 && Registers->registerUnits().size() == 98 &&
+ Registers->registerClasses().size() == 18 && Registers->registerBanks().size() == 4,
+ "RISC-V must expose scalar, vector, restricted, and status physical registers");
+ 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(F31D) != nullptr &&
+ Registers->physicalRegister(F31D)->Encoding == 31 &&
+ Registers->physicalRegister("f31_d") != nullptr &&
+ Registers->physicalRegister("f31_d")->Reg == F31D &&
+ Registers->physicalRegister("f31_f") != nullptr &&
+ Registers->physicalRegister("f31_f")->Reg == F31F &&
+ Registers->physicalRegisterMIRName(F31D) == "f31_d" &&
+ Registers->physicalRegisterMIRName(F31F) == "f31_f" &&
+ Registers->regsOverlap(F31D, F31F) &&
+ Registers->subRegister(F31D, rcc::SubRegIndex::fromValue(1)) == F31F,
+ "FPR32 and FPR64 MIR views must retain encoding, overlap, and subregister facts");
+ Context.expect(Registers->physicalRegister("v8") != nullptr &&
+ Registers->physicalRegister("v8")->Reg == V8 &&
+ Registers->physicalRegister("v8")->Encoding == 8 &&
+ Registers->physicalRegisterMIRName(V8) == "v8" &&
+ Registers->registerClass("vr") != nullptr &&
+ !Registers->registerClass("vr")->Allocatable &&
+ Registers->registerClassContains(Registers->registerClass("vr")->ID, V8),
+ "vector physical registers must retain LLVM MIR names and encodings");
+ const auto *V8V9 = Registers->physicalRegister("v8_v9");
+ const auto *V28V29V30 = Registers->physicalRegister("v28_v29_v30");
+ const auto *V0ThroughV7 = Registers->physicalRegister("v0_v1_v2_v3_v4_v5_v6_v7");
+ Context.expect(V8V9 != nullptr && V28V29V30 != nullptr && V0ThroughV7 != nullptr &&
+ Registers->regsOverlap(V8V9->Reg, V8) &&
+ !Registers->regsOverlap(V28V29V30->Reg, V8) &&
+ Registers->regsOverlap(V0ThroughV7->Reg,
+ registerID(rcc::riscv::PhysicalRegisterID::V7)) &&
+ Registers->registerClass("vrtuple2") != nullptr &&
+ !Registers->registerClass("vrtuple2")->Allocatable,
+ "vector tuple registers must expose canonical names and physical overlap");
+ Context.expect(Registers->physicalRegister("fflags") != nullptr &&
+ Registers->physicalRegister("fflags")->Reg == FFLAGS &&
+ Registers->isReservedRegister(FFLAGS) &&
+ Registers->registerClass("fflags") != nullptr &&
+ !Registers->registerClass("fflags")->Allocatable,
+ "FFLAGS must be a reserved nonallocatable status register");
+ Context.expect(Registers->physicalRegister("frm") != nullptr &&
+ Registers->physicalRegister("frm")->Reg == FRM &&
+ Registers->isReservedRegister(FRM) &&
+ Registers->registerClass("frm") != nullptr &&
+ !Registers->registerClass("frm")->Allocatable,
+ "FRM must be a reserved nonallocatable status register");
+
+ 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");
+ 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) {
+ const rcc::PhysicalRegister X1 = registerID(rcc::riscv::PhysicalRegisterID::X1);
+ const rcc::PhysicalRegister X10 = registerID(rcc::riscv::PhysicalRegisterID::X10);
+ const rcc::PhysicalRegister F8D = registerID(rcc::riscv::PhysicalRegisterID::F8_D);
+ const rcc::PhysicalRegister F8F = registerID(rcc::riscv::PhysicalRegisterID::F8_F);
+ const rcc::PhysicalRegister F10D = registerID(rcc::riscv::PhysicalRegisterID::F10_D);
+ const rcc::PhysicalRegister F10F = registerID(rcc::riscv::PhysicalRegisterID::F10_F);
+ const rcc::PhysicalRegister F31D = registerID(rcc::riscv::PhysicalRegisterID::F31_D);
+ const rcc::PhysicalRegister F31F = registerID(rcc::riscv::PhysicalRegisterID::F31_F);
+
+ const auto LP64Mask = rcc::riscv::createCallPreservedMask(rcc::riscv::CallingConvention::LP64);
+ const auto LP64DMask = rcc::riscv::createCallPreservedMask(rcc::riscv::CallingConvention::LP64D);
+ Context.expect(
+ LP64DMask != nullptr && LP64DMask->preserves(X1) && LP64DMask->preserves(F8D) &&
+ LP64DMask->preserves(F8F) && LP64DMask->clobbers(X10) && LP64DMask->clobbers(F10D) &&
+ LP64DMask->clobbers(F10F) && LP64Mask != nullptr && LP64Mask->preserves(X1) &&
+ LP64Mask->clobbers(F8D) && LP64Mask->clobbers(F8F) && LP64Mask->clobbers(X10),
+ "LP64 and LP64D masks must distinguish integer and floating callee-saved registers");
+
+ auto MIRText = rcc::riscv::createMIRTextInfo();
+ Context.expect(MIRText.has_value(), "RV64GC MIR text facts must be valid");
+ if (!MIRText)
+ return;
+ const auto TextLP64Mask = MIRText->registerMask("csr_ilp32_lp64");
+ const auto TextLP64DMask = MIRText->registerMask("csr_ilp32d_lp64d");
+ Context.expect(MIRText->registerMasks().size() == 2 && TextLP64Mask != nullptr &&
+ LP64Mask != nullptr && *TextLP64Mask == *LP64Mask &&
+ TextLP64DMask != nullptr && LP64DMask != nullptr &&
+ *TextLP64DMask == *LP64DMask,
+ "RV64GC must expose named LP64 and LP64D call-preserved masks");
+ Context.expect(MIRText->directOperandFlagMask() == 127 &&
+ MIRText->directOperandFlag("riscv-call") == 1 &&
+ MIRText->directOperandFlag("riscv-hi") == 32 &&
+ MIRText->directOperandFlag("riscv-lo") == 64,
+ "RV64GC MIR target flags must expose canonical call and address spellings");
+ Context.expect(MIRText->dwarfRegister(X1) == 1,
+ "the return-address register must retain its DWARF register number");
+ Context.expect(MIRText->dwarfRegister(F31D) == 63 && MIRText->dwarfRegister(F31F) == 63 &&
+ MIRText->physicalRegister(63) == F31D,
+ "typed floating views must share one canonical DWARF register mapping");
+
+ const auto Conventions = rcc::riscv::callingConventions();
+ const auto *LP64 = rcc::riscv::callingConvention(rcc::riscv::CallingConvention::LP64);
+ const auto *LP64D = rcc::riscv::callingConvention(rcc::riscv::CallingConvention::LP64D);
+ Context.expect(
+ Conventions.size() == 2 && LP64 != nullptr && LP64->Name == "lp64" &&
+ LP64->IntegerArgumentRegisters.size() == 8 && LP64->FloatingArgumentRegisters.empty() &&
+ LP64->IntegerReturnRegisters.size() == 2 && LP64->FloatingReturnRegisters.empty() &&
+ LP64->PreservedMaskName == "CSR_ILP32_LP64" && 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",
+ "LP64 and LP64D calling-convention tables must expose complete ABI facts");
+}
+
+void testInstructionFacts(TestContext &Context) {
+ constexpr std::size_t TargetInstructionCount =
+ static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::MAXU) -
+ static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::LUI) + 1U;
+ 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 *ShiftAdd = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::SH1ADD)));
+ const auto *ShiftLeftUnsignedWord = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::SLLI_UW)));
+ const auto *SignExtendByte = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::SEXT_B)));
+ const auto *SignedMinimum = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::MIN)));
+ 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 *PseudoTail = Target->instructionDescriptor(rcc::Opcode::fromValue(
+ static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::PseudoTAIL)));
+ 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)));
+
+ constexpr std::array SymbolicLowOpcodes = {
+ rcc::riscv::InstructionOpcode::LB, rcc::riscv::InstructionOpcode::LH,
+ rcc::riscv::InstructionOpcode::LW, rcc::riscv::InstructionOpcode::LBU,
+ rcc::riscv::InstructionOpcode::LHU, rcc::riscv::InstructionOpcode::LWU,
+ rcc::riscv::InstructionOpcode::SB, rcc::riscv::InstructionOpcode::SH,
+ rcc::riscv::InstructionOpcode::SW, rcc::riscv::InstructionOpcode::LD,
+ rcc::riscv::InstructionOpcode::SD, rcc::riscv::InstructionOpcode::FLW,
+ rcc::riscv::InstructionOpcode::FSW, rcc::riscv::InstructionOpcode::FLD,
+ rcc::riscv::InstructionOpcode::FSD,
+ };
+ const rcc::OperandKindSet SymbolicLowKinds = {
+ rcc::MachineOperand::Kind::Immediate, rcc::MachineOperand::Kind::ConstantPoolIndex,
+ rcc::MachineOperand::Kind::JumpTableIndex, rcc::MachineOperand::Kind::GlobalSymbol,
+ rcc::MachineOperand::Kind::ExternalSymbol, rcc::MachineOperand::Kind::BlockAddress,
+ rcc::MachineOperand::Kind::MCSymbol,
+ };
+ Context.expect(
+ Load != nullptr && Load->Operands.size() == 3 &&
+ std::ranges::all_of(SymbolicLowOpcodes,
+ [&](rcc::riscv::InstructionOpcode Opcode) {
+ const auto *Descriptor = Target->instructionDescriptor(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(Opcode)));
+ return Descriptor != nullptr && Descriptor->Operands.size() == 3 &&
+ Descriptor->Operands[2].Kinds == SymbolicLowKinds &&
+ Descriptor->Operands[2].ImmediatePredicate ==
+ Load->Operands[2].ImmediatePredicate &&
+ Descriptor->Operands[2].AllowedTargetFlags == 68;
+ }),
+ "RV64GC loads and stores must accept SIMM12 low-part symbolic operands");
+
+ Context.expect(
+ Target->instructions().descriptors().size() ==
+ TargetInstructionCount + rcc::genericInstructionDescriptors().size() &&
+ Addi != nullptr && Addi->Name == "ADDI" && Addi->NumExplicitDefs == 1 &&
+ Addi->Operands.size() == 3 &&
+ Addi->Operands[1].Kinds.contains(rcc::MachineOperand::Kind::FrameIndex) &&
+ 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 &&
+ Floating->AllowExtraImplicitOperands && 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->Operands.size() == 2 && PseudoCall->Operands[1].Optional &&
+ PseudoTail != nullptr && PseudoTail->Operands.size() == 2 &&
+ PseudoTail->Operands[1].Optional && 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");
+ Context.expect(
+ Load != nullptr &&
+ Target->instructions()
+ .validateMemOperands(Load->InstructionOpcode,
+ std::span<const rcc::MachineMemOperand *const>{})
+ .has_value() &&
+ !Target->instructions().validateMemOperands(
+ rcc::opcode(rcc::GenericOpcode::G_LOAD),
+ std::span<const rcc::MachineMemOperand *const>{}),
+ "target memory operands must be optional while generic memory operands remain required");
+ Context.expect(ShiftAdd != nullptr && ShiftAdd->Operands.size() == 3 &&
+ ShiftLeftUnsignedWord != nullptr &&
+ ShiftLeftUnsignedWord->Operands.size() == 3 &&
+ ShiftLeftUnsignedWord->Operands.back().ImmediatePredicate &&
+ SignExtendByte != nullptr && SignExtendByte->Operands.size() == 2 &&
+ SignedMinimum != nullptr && SignedMinimum->Operands.size() == 3 &&
+ SignedMinimum->Flags.has(rcc::InstructionFlag::Commutable),
+ "RISC-V Zba and Zbb instructions must expose complete generated constraints");
+
+ 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() == 35 &&
+ rcc::riscv::assemblyInstructions().size() == 211 && 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
+
+int main() {
+ TestContext Context;
+ testRegisterFacts(Context);
+ testABIAndDebugFacts(Context);
+ testInstructionFacts(Context);
+ return Context.result();
+}