aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-09 22:05:14 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-09 22:05:14 +0800
commit3a57627e59ba18b6f9f9aae7bb60c390c0d3575a (patch)
tree736ca062bfffbd68177a512dfe145b06c16d11b1 /backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp
parent379ff50dcc7ccf977379aee68fa9d10f69065d23 (diff)
downloadrcc-project-3a57627e59ba18b6f9f9aae7bb60c390c0d3575a.tar.gz
rcc-project-3a57627e59ba18b6f9f9aae7bb60c390c0d3575a.tar.xz
[MC] Implement RV64GC object emission
Diffstat (limited to 'backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp')
-rw-r--r--backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp411
1 files changed, 411 insertions, 0 deletions
diff --git a/backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp b/backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp
new file mode 100644
index 0000000..1e10d24
--- /dev/null
+++ b/backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp
@@ -0,0 +1,411 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/MC/MCInst.h"
+#include "rcc/Target/RISCV/RISCVMCCodeEmitter.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <cstdint>
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace {
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCInst> fixtureError(std::string_view Message) {
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, std::string(Message));
+}
+
+[[nodiscard]] std::int64_t signExtend(std::uint32_t Value, unsigned Width) {
+ const std::uint32_t Mask = (std::uint32_t{1} << Width) - 1U;
+ const std::uint32_t Sign = std::uint32_t{1} << (Width - 1U);
+ Value &= Mask;
+ if ((Value & Sign) == 0)
+ return Value;
+ return static_cast<std::int64_t>(Value) - (std::int64_t{1} << Width);
+}
+
+void addRegister(rcc::mc::MCInst &Instruction, std::uint32_t Encoding, bool Floating) {
+ const std::uint32_t ID = Encoding + (Floating ? 33U : 1U);
+ Instruction.addOperand(rcc::mc::MCOperand::registerOperand(rcc::mc::MCRegister::fromValue(ID)));
+}
+
+void addImmediate(rcc::mc::MCInst &Instruction, std::int64_t Value) {
+ Instruction.addOperand(rcc::mc::MCOperand::immediate(Value));
+}
+
+[[nodiscard]] std::int64_t decodeBranchImmediate(std::uint32_t Encoding) {
+ const std::uint32_t Value = ((Encoding >> 31U) & 1U) << 12U | ((Encoding >> 25U) & 0x3fU) << 5U |
+ ((Encoding >> 8U) & 0xfU) << 1U | ((Encoding >> 7U) & 1U) << 11U;
+ return signExtend(Value, 13);
+}
+
+[[nodiscard]] std::int64_t decodeJALImmediate(std::uint32_t Encoding) {
+ const std::uint32_t Value = ((Encoding >> 31U) & 1U) << 20U | ((Encoding >> 21U) & 0x3ffU) << 1U |
+ ((Encoding >> 20U) & 1U) << 11U | ((Encoding >> 12U) & 0xffU) << 12U;
+ return signExtend(Value, 21);
+}
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCInst>
+createStandardFixture(const rcc::InstructionDescriptor &Descriptor,
+ const rcc::riscv::AssemblyInstructionInfo &Assembly) {
+ using enum rcc::riscv::AssemblyFormat;
+ using enum rcc::riscv::InstructionOpcode;
+
+ const auto Category =
+ static_cast<rcc::riscv::EncodingCategory>(Descriptor.EncodingCategory.value());
+ const auto Format = static_cast<rcc::riscv::AssemblyFormat>(Assembly.Format.value());
+ const auto Opcode =
+ static_cast<rcc::riscv::InstructionOpcode>(Assembly.InstructionOpcode.value());
+ const std::uint32_t Encoding = Assembly.Encoding;
+ const std::uint32_t DestinationEncoding = (Encoding >> 7U) & 0x1fU;
+ const std::uint32_t Source1Encoding = (Encoding >> 15U) & 0x1fU;
+ const std::uint32_t Source2Encoding = (Encoding >> 20U) & 0x1fU;
+ rcc::mc::MCInst Result(rcc::mc::MCOpcode::fromValue(Assembly.InstructionOpcode.value()));
+
+ if (Category == rcc::riscv::EncodingCategory::R) {
+ switch (Format) {
+ case RD_RS1_RS2:
+ addRegister(Result, DestinationEncoding, false);
+ addRegister(Result, Source1Encoding, false);
+ addRegister(Result, Source2Encoding, false);
+ return Result;
+ case FRD_FRS1_FRS2_RM:
+ addRegister(Result, DestinationEncoding, true);
+ addRegister(Result, Source1Encoding, true);
+ addRegister(Result, Source2Encoding, true);
+ addImmediate(Result, (Encoding >> 12U) & 7U);
+ return Result;
+ case FRD_FRS1_FRS2:
+ addRegister(Result, DestinationEncoding, true);
+ addRegister(Result, Source1Encoding, true);
+ addRegister(Result, Source2Encoding, true);
+ return Result;
+ case RD_FRS1_FRS2:
+ addRegister(Result, DestinationEncoding, false);
+ addRegister(Result, Source1Encoding, true);
+ addRegister(Result, Source2Encoding, true);
+ return Result;
+ case FRD_FRS1_RM:
+ addRegister(Result, DestinationEncoding, true);
+ addRegister(Result, Source1Encoding, true);
+ addImmediate(Result, (Encoding >> 12U) & 7U);
+ return Result;
+ case RD_FRS1_RM:
+ addRegister(Result, DestinationEncoding, false);
+ addRegister(Result, Source1Encoding, true);
+ addImmediate(Result, (Encoding >> 12U) & 7U);
+ return Result;
+ case FRD_RS1_RM:
+ addRegister(Result, DestinationEncoding, true);
+ addRegister(Result, Source1Encoding, false);
+ addImmediate(Result, (Encoding >> 12U) & 7U);
+ return Result;
+ case RD_FRS1:
+ addRegister(Result, DestinationEncoding, false);
+ addRegister(Result, Source1Encoding, true);
+ return Result;
+ case FRD_RS1:
+ addRegister(Result, DestinationEncoding, true);
+ addRegister(Result, Source1Encoding, false);
+ return Result;
+ default:
+ return fixtureError("unsupported R-type reference fixture format");
+ }
+ }
+
+ if (Category == rcc::riscv::EncodingCategory::I) {
+ if (Format == NONE)
+ return Result;
+ if (Format == IMM_IMM) {
+ addImmediate(Result, (Encoding >> 24U) & 0xfU);
+ addImmediate(Result, (Encoding >> 20U) & 0xfU);
+ return Result;
+ }
+ const bool FloatingDestination = Format == FRD_OFFSET_RS1;
+ if (Format != RD_RS1_IMM && Format != RD_OFFSET_RS1 && !FloatingDestination)
+ return fixtureError("unsupported I-type reference fixture format");
+ addRegister(Result, DestinationEncoding, FloatingDestination);
+ addRegister(Result, Source1Encoding, false);
+ const bool Shift64 = Opcode == SLLI || Opcode == SRLI || Opcode == SRAI;
+ const bool Shift32 = Opcode == SLLIW || Opcode == SRLIW || Opcode == SRAIW;
+ if (Shift64)
+ addImmediate(Result, (Encoding >> 20U) & 0x3fU);
+ else if (Shift32)
+ addImmediate(Result, (Encoding >> 20U) & 0x1fU);
+ else
+ addImmediate(Result, signExtend(Encoding >> 20U, 12));
+ return Result;
+ }
+
+ if (Category == rcc::riscv::EncodingCategory::S) {
+ if (Format != RS2_OFFSET_RS1 && Format != FRS2_OFFSET_RS1)
+ return fixtureError("unsupported S-type reference fixture format");
+ addRegister(Result, Source2Encoding, Format == FRS2_OFFSET_RS1);
+ addRegister(Result, Source1Encoding, false);
+ const std::uint32_t Immediate = ((Encoding >> 25U) & 0x7fU) << 5U | ((Encoding >> 7U) & 0x1fU);
+ addImmediate(Result, signExtend(Immediate, 12));
+ return Result;
+ }
+
+ if (Category == rcc::riscv::EncodingCategory::B) {
+ if (Format != RS1_RS2_TARGET)
+ return fixtureError("unsupported B-type reference fixture format");
+ addRegister(Result, Source1Encoding, false);
+ addRegister(Result, Source2Encoding, false);
+ addImmediate(Result, decodeBranchImmediate(Encoding));
+ return Result;
+ }
+
+ if (Category == rcc::riscv::EncodingCategory::U) {
+ if (Format != RD_IMM)
+ return fixtureError("unsupported U-type reference fixture format");
+ addRegister(Result, DestinationEncoding, false);
+ addImmediate(Result, signExtend(Encoding >> 12U, 20));
+ return Result;
+ }
+
+ if (Category == rcc::riscv::EncodingCategory::J) {
+ if (Format != RD_TARGET)
+ return fixtureError("unsupported J-type reference fixture format");
+ addRegister(Result, DestinationEncoding, false);
+ addImmediate(Result, decodeJALImmediate(Encoding));
+ return Result;
+ }
+
+ if (Category == rcc::riscv::EncodingCategory::SYSTEM) {
+ if (Format == NONE)
+ return Result;
+ if (Format != RD_CSR_RS1 && Format != RD_CSR_IMM)
+ return fixtureError("unsupported system reference fixture format");
+ addRegister(Result, DestinationEncoding, false);
+ addImmediate(Result, Encoding >> 20U);
+ if (Format == RD_CSR_RS1)
+ addRegister(Result, Source1Encoding, false);
+ else
+ addImmediate(Result, Source1Encoding);
+ return Result;
+ }
+
+ if (Category == rcc::riscv::EncodingCategory::AMO) {
+ if (Format != RD_PAREN_RS1 && Format != RD_RS2_PAREN_RS1)
+ return fixtureError("unsupported atomic reference fixture format");
+ addRegister(Result, DestinationEncoding, false);
+ if (Format == RD_RS2_PAREN_RS1)
+ addRegister(Result, Source2Encoding, false);
+ addRegister(Result, Source1Encoding, false);
+ return Result;
+ }
+
+ if (Category == rcc::riscv::EncodingCategory::R4) {
+ if (Format != FRD_FRS1_FRS2_FRS3_RM)
+ return fixtureError("unsupported R4 reference fixture format");
+ addRegister(Result, DestinationEncoding, true);
+ addRegister(Result, Source1Encoding, true);
+ addRegister(Result, Source2Encoding, true);
+ addRegister(Result, (Encoding >> 27U) & 0x1fU, true);
+ addImmediate(Result, (Encoding >> 12U) & 7U);
+ return Result;
+ }
+
+ return fixtureError("unsupported standard reference fixture category");
+}
+
+[[nodiscard]] std::int64_t decodeRVCJumpImmediate(std::uint16_t Encoding) {
+ const std::uint32_t Value = ((Encoding >> 12U) & 1U) << 11U | ((Encoding >> 11U) & 1U) << 4U |
+ ((Encoding >> 9U) & 3U) << 8U | ((Encoding >> 8U) & 1U) << 10U |
+ ((Encoding >> 7U) & 1U) << 6U | ((Encoding >> 6U) & 1U) << 7U |
+ ((Encoding >> 3U) & 7U) << 1U | ((Encoding >> 2U) & 1U) << 5U;
+ return signExtend(Value, 12);
+}
+
+[[nodiscard]] std::int64_t decodeRVCBranchImmediate(std::uint16_t Encoding) {
+ const std::uint32_t Value = ((Encoding >> 12U) & 1U) << 8U | ((Encoding >> 10U) & 3U) << 3U |
+ ((Encoding >> 5U) & 3U) << 6U | ((Encoding >> 3U) & 3U) << 1U |
+ ((Encoding >> 2U) & 1U) << 5U;
+ return signExtend(Value, 9);
+}
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCInst>
+createCompressedFixture(const rcc::riscv::AssemblyInstructionInfo &Assembly) {
+ using enum rcc::riscv::InstructionOpcode;
+ const auto Opcode =
+ static_cast<rcc::riscv::InstructionOpcode>(Assembly.InstructionOpcode.value());
+ const auto Encoding = static_cast<std::uint16_t>(Assembly.Encoding);
+ rcc::mc::MCInst Result(rcc::mc::MCOpcode::fromValue(Assembly.InstructionOpcode.value()));
+
+ if (Opcode == C_ADDI4SPN) {
+ addRegister(Result, ((Encoding >> 2U) & 7U) + 8U, false);
+ const std::uint32_t Immediate = ((Encoding >> 11U) & 3U) << 4U |
+ ((Encoding >> 7U) & 0xfU) << 6U |
+ ((Encoding >> 6U) & 1U) << 2U | ((Encoding >> 5U) & 1U) << 3U;
+ addImmediate(Result, Immediate);
+ return Result;
+ }
+
+ const bool DoubleLoad = Opcode == C_FLD || Opcode == C_LD;
+ const bool WordLoad = Opcode == C_LW;
+ const bool DoubleStore = Opcode == C_FSD || Opcode == C_SD;
+ const bool WordStore = Opcode == C_SW;
+ if (DoubleLoad || WordLoad || DoubleStore || WordStore) {
+ addRegister(Result, ((Encoding >> 2U) & 7U) + 8U, Opcode == C_FLD || Opcode == C_FSD);
+ addRegister(Result, ((Encoding >> 7U) & 7U) + 8U, false);
+ const bool Double = DoubleLoad || DoubleStore;
+ std::uint32_t Immediate = ((Encoding >> 10U) & 7U) << 3U;
+ if (Double) {
+ Immediate |= ((Encoding >> 5U) & 3U) << 6U;
+ } else {
+ Immediate |= ((Encoding >> 6U) & 1U) << 2U | ((Encoding >> 5U) & 1U) << 6U;
+ }
+ addImmediate(Result, Immediate);
+ return Result;
+ }
+
+ const bool CI = Opcode == C_ADDI || Opcode == C_ADDIW || Opcode == C_LI || Opcode == C_LUI ||
+ Opcode == C_SLLI;
+ if (CI) {
+ addRegister(Result, (Encoding >> 7U) & 0x1fU, false);
+ const std::uint32_t Immediate = ((Encoding >> 12U) & 1U) << 5U | ((Encoding >> 2U) & 0x1fU);
+ addImmediate(Result, Opcode == C_SLLI ? Immediate : signExtend(Immediate, 6));
+ return Result;
+ }
+
+ if (Opcode == C_ADDI16SP) {
+ const std::uint32_t Immediate = ((Encoding >> 12U) & 1U) << 9U | ((Encoding >> 6U) & 1U) << 4U |
+ ((Encoding >> 5U) & 1U) << 6U | ((Encoding >> 3U) & 3U) << 7U |
+ ((Encoding >> 2U) & 1U) << 5U;
+ addImmediate(Result, signExtend(Immediate, 10));
+ return Result;
+ }
+
+ const bool CompactShift = Opcode == C_SRLI || Opcode == C_SRAI;
+ if (CompactShift || Opcode == C_ANDI) {
+ addRegister(Result, ((Encoding >> 7U) & 7U) + 8U, false);
+ const std::uint32_t Immediate = ((Encoding >> 12U) & 1U) << 5U | ((Encoding >> 2U) & 0x1fU);
+ addImmediate(Result, CompactShift ? Immediate : signExtend(Immediate, 6));
+ return Result;
+ }
+
+ const bool CompactALU = Opcode == C_SUB || Opcode == C_XOR || Opcode == C_OR || Opcode == C_AND ||
+ Opcode == C_SUBW || Opcode == C_ADDW;
+ if (CompactALU) {
+ addRegister(Result, ((Encoding >> 7U) & 7U) + 8U, false);
+ addRegister(Result, ((Encoding >> 2U) & 7U) + 8U, false);
+ return Result;
+ }
+
+ if (Opcode == C_J) {
+ addImmediate(Result, decodeRVCJumpImmediate(Encoding));
+ return Result;
+ }
+
+ if (Opcode == C_BEQZ || Opcode == C_BNEZ) {
+ addRegister(Result, ((Encoding >> 7U) & 7U) + 8U, false);
+ addImmediate(Result, decodeRVCBranchImmediate(Encoding));
+ return Result;
+ }
+
+ const bool DoubleStackLoad = Opcode == C_FLDSP || Opcode == C_LDSP;
+ if (DoubleStackLoad || Opcode == C_LWSP) {
+ addRegister(Result, (Encoding >> 7U) & 0x1fU, Opcode == C_FLDSP);
+ const std::uint32_t Immediate =
+ DoubleStackLoad ? ((Encoding >> 12U) & 1U) << 5U | ((Encoding >> 5U) & 3U) << 3U |
+ ((Encoding >> 2U) & 7U) << 6U
+ : ((Encoding >> 12U) & 1U) << 5U | ((Encoding >> 4U) & 7U) << 2U |
+ ((Encoding >> 2U) & 3U) << 6U;
+ addImmediate(Result, Immediate);
+ return Result;
+ }
+
+ const bool CRSingle = Opcode == C_JR || Opcode == C_JALR;
+ const bool CRDouble = Opcode == C_MV || Opcode == C_ADD;
+ if (CRSingle || CRDouble) {
+ addRegister(Result, (Encoding >> 7U) & 0x1fU, false);
+ if (CRDouble)
+ addRegister(Result, (Encoding >> 2U) & 0x1fU, false);
+ return Result;
+ }
+
+ if (Opcode == C_EBREAK)
+ return Result;
+
+ const bool DoubleStackStore = Opcode == C_FSDSP || Opcode == C_SDSP;
+ if (DoubleStackStore || Opcode == C_SWSP) {
+ addRegister(Result, (Encoding >> 2U) & 0x1fU, Opcode == C_FSDSP);
+ const std::uint32_t Immediate =
+ DoubleStackStore ? ((Encoding >> 10U) & 7U) << 3U | ((Encoding >> 7U) & 7U) << 6U
+ : ((Encoding >> 9U) & 0xfU) << 2U | ((Encoding >> 7U) & 3U) << 6U;
+ addImmediate(Result, Immediate);
+ return Result;
+ }
+
+ return fixtureError("unsupported compressed reference fixture opcode");
+}
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCInst>
+createReferenceFixture(const rcc::InstructionDescriptor &Descriptor,
+ const rcc::riscv::AssemblyInstructionInfo &Assembly) {
+ const auto Category =
+ static_cast<rcc::riscv::EncodingCategory>(Descriptor.EncodingCategory.value());
+ if (Category == rcc::riscv::EncodingCategory::C)
+ return createCompressedFixture(Assembly);
+ return createStandardFixture(Descriptor, Assembly);
+}
+
+[[nodiscard]] std::vector<std::uint8_t> referenceBytes(std::uint32_t Encoding, std::uint8_t Size) {
+ std::vector<std::uint8_t> Result;
+ Result.reserve(Size);
+ for (std::uint8_t Index = 0; Index < Size; ++Index)
+ Result.push_back(static_cast<std::uint8_t>(Encoding >> (Index * 8U)));
+ return Result;
+}
+
+} // namespace
+
+int main() {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target) {
+ std::cerr << Target.error().message() << '\n';
+ return 1;
+ }
+ const std::span<const rcc::riscv::AssemblyInstructionInfo> Instructions =
+ rcc::riscv::assemblyInstructions();
+ if (Instructions.size() != 195) {
+ std::cerr << "RV64GC encoding coverage does not contain all 195 instructions\n";
+ return 1;
+ }
+
+ rcc::riscv::RISCVMCCodeEmitter Emitter(*Target);
+ for (const rcc::riscv::AssemblyInstructionInfo &Assembly : Instructions) {
+ const rcc::InstructionDescriptor *Descriptor =
+ Target->instructionDescriptor(Assembly.InstructionOpcode);
+ if (Descriptor == nullptr || Assembly.Encoding == 0) {
+ std::cerr << Assembly.Mnemonic << ": missing encoding description\n";
+ return 1;
+ }
+ auto Fixture = createReferenceFixture(*Descriptor, Assembly);
+ if (!Fixture) {
+ std::cerr << Assembly.Mnemonic << ": " << Fixture.error().message() << '\n';
+ return 1;
+ }
+ auto Encoded = Emitter.encodeInstruction(*Fixture);
+ if (!Encoded) {
+ std::cerr << Assembly.Mnemonic << ": " << Encoded.error().message() << '\n';
+ return 1;
+ }
+ const auto Category =
+ static_cast<rcc::riscv::EncodingCategory>(Descriptor->EncodingCategory.value());
+ const std::uint8_t Size = Category == rcc::riscv::EncodingCategory::C ? 2 : 4;
+ if (!Encoded->Fixups.empty() || Encoded->Bytes != referenceBytes(Assembly.Encoding, Size)) {
+ std::uint32_t Actual = 0;
+ for (std::size_t Index = 0; Index < Encoded->Bytes.size(); ++Index)
+ Actual |= static_cast<std::uint32_t>(Encoded->Bytes[Index]) << (Index * 8U);
+ std::cerr << Assembly.Mnemonic << ": encoding differs from its RV64GC reference word ("
+ << Actual << " != " << Assembly.Encoding << ")\n";
+ return 1;
+ }
+ }
+ return 0;
+}