aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/Target
diff options
context:
space:
mode:
Diffstat (limited to 'backend/unittests/Target')
-rw-r--r--backend/unittests/Target/CMakeLists.txt30
-rw-r--r--backend/unittests/Target/DescriptionGeneratorTest.cpp2
-rw-r--r--backend/unittests/Target/Inputs/RISCVStackFramePipeline.mir12
-rw-r--r--backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp411
-rw-r--r--backend/unittests/Target/RISCVMCTest.cpp264
-rw-r--r--backend/unittests/Target/RISCVObjectFileTest.cpp791
6 files changed, 1509 insertions, 1 deletions
diff --git a/backend/unittests/Target/CMakeLists.txt b/backend/unittests/Target/CMakeLists.txt
index 1b1f344..e68c525 100644
--- a/backend/unittests/Target/CMakeLists.txt
+++ b/backend/unittests/Target/CMakeLists.txt
@@ -82,6 +82,30 @@ add_test(
COMMAND RCCBackendRISCVAssemblyPrinterTests
)
+add_executable(RCCBackendRISCVMCTests RISCVMCTest.cpp)
+rcc_backend_configure_target(RCCBackendRISCVMCTests)
+target_link_libraries(RCCBackendRISCVMCTests PRIVATE RCCBackend::RISCV RCCBackend::MC)
+add_test(NAME RCCBackendRISCVMCTests COMMAND RCCBackendRISCVMCTests)
+
+add_executable(RCCBackendRISCVMCEncodingCoverageTests RISCVMCEncodingCoverageTest.cpp)
+rcc_backend_configure_target(RCCBackendRISCVMCEncodingCoverageTests)
+target_link_libraries(
+ RCCBackendRISCVMCEncodingCoverageTests
+ PRIVATE RCCBackend::RISCV RCCBackend::MC
+)
+add_test(
+ NAME RCCBackendRISCVMCEncodingCoverageTests
+ COMMAND RCCBackendRISCVMCEncodingCoverageTests
+)
+
+add_executable(RCCBackendRISCVObjectFileTests RISCVObjectFileTest.cpp)
+rcc_backend_configure_target(RCCBackendRISCVObjectFileTests)
+target_link_libraries(
+ RCCBackendRISCVObjectFileTests
+ PRIVATE RCCBackend::RISCV RCCBackend::MC RCCBackend::CodeGen
+)
+add_test(NAME RCCBackendRISCVObjectFileTests COMMAND RCCBackendRISCVObjectFileTests)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLoweringTest.cpp"
@@ -92,6 +116,9 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVLateCodeGenTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinterTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCEncodingCoverageTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVObjectFileTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.h"
)
@@ -105,5 +132,8 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVLateCodeGenTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinterTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCEncodingCoverageTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVObjectFileTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
)
diff --git a/backend/unittests/Target/DescriptionGeneratorTest.cpp b/backend/unittests/Target/DescriptionGeneratorTest.cpp
index ea073bf..e6679e8 100644
--- a/backend/unittests/Target/DescriptionGeneratorTest.cpp
+++ b/backend/unittests/Target/DescriptionGeneratorTest.cpp
@@ -65,7 +65,7 @@ encoding_categories:
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, global_symbol], 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 }
+ - { id: 1024, symbol: LOAD_IMM, name: LOAD_IMM, assembly_mnemonic: load.imm, encoding: 19, operands: [{ kinds: [register], role: def, register_class: GPR64, register_bank: GPR, name: RD }, { kinds: [immediate, global_symbol], 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 }
)";
}
diff --git a/backend/unittests/Target/Inputs/RISCVStackFramePipeline.mir b/backend/unittests/Target/Inputs/RISCVStackFramePipeline.mir
new file mode 100644
index 0000000..3105fe4
--- /dev/null
+++ b/backend/unittests/Target/Inputs/RISCVStackFramePipeline.mir
@@ -0,0 +1,12 @@
+---
+name: stack_frame_pipeline
+tracksRegLiveness: true
+isSSA: true
+stack:
+ - { id: 0, size: 32, alignment: 16 }
+body: |
+ bb.0:
+ liveins: $x1
+ %0:gpr(i64) = G_CONSTANT 9
+ $x10 = COPY %0
+ PseudoRET implicit $x1, implicit $x10
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;
+}
diff --git a/backend/unittests/Target/RISCVMCTest.cpp b/backend/unittests/Target/RISCVMCTest.cpp
new file mode 100644
index 0000000..1d44c73
--- /dev/null
+++ b/backend/unittests/Target/RISCVMCTest.cpp
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/GlobalObject.h"
+#include "rcc/MC/MCContext.h"
+#include "rcc/MC/MCExpr.h"
+#include "rcc/MC/MCInst.h"
+#include "rcc/Target/RISCV/RISCVAsmBackend.h"
+#include "rcc/Target/RISCV/RISCVFixupKinds.h"
+#include "rcc/Target/RISCV/RISCVInstPrinter.h"
+#include "rcc/Target/RISCV/RISCVMCCodeEmitter.h"
+#include "rcc/Target/RISCV/RISCVObjectFile.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <cstdint>
+#include <cstring>
+#include <initializer_list>
+#include <iostream>
+#include <span>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+int fail(std::string_view Message) {
+ std::cerr << Message << '\n';
+ return 1;
+}
+
+[[nodiscard]] rcc::mc::MCOperand reg(rcc::riscv::PhysicalRegisterID ID) {
+ return rcc::mc::MCOperand::registerOperand(
+ rcc::mc::MCRegister::fromValue(static_cast<std::uint32_t>(ID)));
+}
+
+[[nodiscard]] rcc::mc::MCOperand imm(std::int64_t Value) {
+ return rcc::mc::MCOperand::immediate(Value);
+}
+
+[[nodiscard]] rcc::mc::MCInst instruction(rcc::riscv::InstructionOpcode Opcode,
+ std::initializer_list<rcc::mc::MCOperand> Operands) {
+ rcc::mc::MCInst Result(rcc::mc::MCOpcode::fromValue(static_cast<std::uint32_t>(Opcode)));
+ Result.reserveOperands(Operands.size());
+ for (const rcc::mc::MCOperand &Operand : Operands)
+ Result.addOperand(Operand);
+ return Result;
+}
+
+[[nodiscard]] bool bytesEqual(std::span<const std::uint8_t> Left,
+ std::initializer_list<std::uint8_t> Right) {
+ return Left.size() == Right.size() &&
+ std::equal(Left.begin(), Left.end(), Right.begin(), Right.end());
+}
+
+[[nodiscard]] bool expectEncoding(const rcc::riscv::RISCVMCCodeEmitter &Emitter,
+ const rcc::mc::MCInst &Instruction,
+ std::initializer_list<std::uint8_t> Expected,
+ std::string_view Name) {
+ auto Encoded = Emitter.encodeInstruction(Instruction);
+ if (!Encoded) {
+ std::cerr << Name << ": " << Encoded.error().message() << '\n';
+ return false;
+ }
+ if (!Encoded->Fixups.empty() || !bytesEqual(Encoded->Bytes, Expected)) {
+ std::cerr << Name << ": encoded bytes differ\n";
+ return false;
+ }
+ return true;
+}
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef>
+symbolExpression(rcc::mc::MCContext &Context, std::string_view Name, std::uint32_t TargetKind = 0) {
+ auto Symbol = Context.getOrCreateSymbol(std::string(Name), rcc::mc::MCSymbolKind::Generic);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ auto Expression = rcc::mc::MCExpr::symbol(*Symbol);
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ if (TargetKind == 0)
+ return *Expression;
+ return rcc::mc::MCExpr::target(TargetKind, *Expression);
+}
+
+[[nodiscard]] bool testEncodings(const rcc::riscv::RISCVMCCodeEmitter &Emitter) {
+ using enum rcc::riscv::InstructionOpcode;
+ using enum rcc::riscv::PhysicalRegisterID;
+ return expectEncoding(Emitter, instruction(AUIPC, {reg(X11), imm(7)}), {0x97, 0x75, 0x00, 0x00},
+ "AUIPC") &&
+ expectEncoding(Emitter, instruction(ADDI, {reg(X12), reg(X13), imm(-17)}),
+ {0x13, 0x86, 0xf6, 0xfe}, "ADDI") &&
+ expectEncoding(Emitter, instruction(SRAI, {reg(X14), reg(X15), imm(63)}),
+ {0x13, 0xd7, 0xf7, 0x43}, "SRAI") &&
+ expectEncoding(Emitter, instruction(SD, {reg(X16), reg(X17), imm(-32)}),
+ {0x23, 0xb0, 0x08, 0xff}, "SD") &&
+ expectEncoding(Emitter, instruction(BGE, {reg(X18), reg(X19), imm(18)}),
+ {0x63, 0x59, 0x39, 0x01}, "BGE") &&
+ expectEncoding(Emitter, instruction(JAL, {reg(X20), imm(-20)}), {0x6f, 0xfa, 0xdf, 0xfe},
+ "JAL") &&
+ expectEncoding(Emitter, instruction(FENCE, {imm(10), imm(5)}), {0x0f, 0x00, 0x50, 0x0a},
+ "FENCE") &&
+ expectEncoding(Emitter, instruction(CSRRWI, {reg(X21), imm(0x305), imm(17)}),
+ {0xf3, 0xda, 0x58, 0x30}, "CSRRWI") &&
+ expectEncoding(Emitter, instruction(AMOADD_D, {reg(X22), reg(X23), reg(X24)}),
+ {0x2f, 0x3b, 0x7c, 0x01}, "AMOADD.D") &&
+ expectEncoding(Emitter, instruction(FMADD_D, {reg(F1), reg(F2), reg(F3), reg(F4), imm(2)}),
+ {0xc3, 0x20, 0x31, 0x22}, "FMADD.D") &&
+ expectEncoding(Emitter, instruction(FADD_S, {reg(F5), reg(F6), reg(F7), imm(3)}),
+ {0xd3, 0x32, 0x73, 0x00}, "FADD.S") &&
+ expectEncoding(Emitter, instruction(C_ADDI4SPN, {reg(X8), imm(48)}), {0x00, 0x18},
+ "C.ADDI4SPN") &&
+ expectEncoding(Emitter, instruction(C_LD, {reg(X9), reg(X8), imm(56)}), {0x04, 0x7c},
+ "C.LD") &&
+ expectEncoding(Emitter, instruction(C_SW, {reg(X10), reg(X11), imm(28)}), {0xc8, 0xcd},
+ "C.SW") &&
+ expectEncoding(Emitter, instruction(C_ADDI, {reg(X12), imm(-9)}), {0x5d, 0x16},
+ "C.ADDI") &&
+ expectEncoding(Emitter, instruction(C_ADDI16SP, {imm(-128)}), {0x19, 0x71},
+ "C.ADDI16SP") &&
+ expectEncoding(Emitter, instruction(C_SRAI, {reg(X13), imm(33)}), {0x85, 0x96},
+ "C.SRAI") &&
+ expectEncoding(Emitter, instruction(C_SUBW, {reg(X14), reg(X15)}), {0x1d, 0x9f},
+ "C.SUBW") &&
+ expectEncoding(Emitter, instruction(C_J, {imm(42)}), {0x2d, 0xa0}, "C.J") &&
+ expectEncoding(Emitter, instruction(C_BNEZ, {reg(X8), imm(-30)}), {0x6d, 0xf0},
+ "C.BNEZ") &&
+ expectEncoding(Emitter, instruction(C_LDSP, {reg(X10), imm(248)}), {0x6e, 0x75},
+ "C.LDSP") &&
+ expectEncoding(Emitter, instruction(C_SWSP, {reg(X11), imm(124)}), {0xae, 0xde},
+ "C.SWSP") &&
+ expectEncoding(Emitter, instruction(C_JALR, {reg(X12)}), {0x02, 0x96}, "C.JALR");
+}
+
+[[nodiscard]] bool testFixups(const rcc::riscv::RISCVMCCodeEmitter &Emitter,
+ const rcc::riscv::RISCVAsmBackend &Backend) {
+ using enum rcc::riscv::InstructionOpcode;
+ using enum rcc::riscv::PhysicalRegisterID;
+ rcc::mc::MCContext Context;
+
+ auto HighExpression =
+ symbolExpression(Context, "destination",
+ static_cast<std::uint32_t>(rcc::riscv::RISCVMCExprKind::PCRelativeHigh));
+ if (!HighExpression)
+ return false;
+ auto High = instruction(AUIPC, {reg(X5), rcc::mc::MCOperand::expression(*HighExpression)});
+ auto HighEncoding = Emitter.encodeInstruction(High);
+ if (!HighEncoding || HighEncoding->Fixups.size() != 1 ||
+ HighEncoding->Fixups[0].Kind !=
+ rcc::riscv::fixupKind(rcc::riscv::RISCVFixupKind::PCRelativeHigh20) ||
+ !HighEncoding->Fixups[0].PCRelative ||
+ !bytesEqual(HighEncoding->Bytes, {0x97, 0x02, 0x00, 0x00})) {
+ return false;
+ }
+ auto HighValue = rcc::mc::evaluateAsRelocatable(**HighExpression);
+ if (!HighValue)
+ return false;
+ auto HighRelocation = Backend.relocationType(HighEncoding->Fixups[0], *HighValue);
+ if (!HighRelocation || *HighRelocation != 23)
+ return false;
+
+ auto LowExpression = symbolExpression(
+ Context, "anchor", static_cast<std::uint32_t>(rcc::riscv::RISCVMCExprKind::PCRelativeLow));
+ if (!LowExpression)
+ return false;
+ auto Low = instruction(JALR, {reg(X1), reg(X5), rcc::mc::MCOperand::expression(*LowExpression)});
+ auto LowEncoding = Emitter.encodeInstruction(Low);
+ if (!LowEncoding || LowEncoding->Fixups.size() != 1 ||
+ LowEncoding->Fixups[0].Kind !=
+ rcc::riscv::fixupKind(rcc::riscv::RISCVFixupKind::PCRelativeLow12I)) {
+ return false;
+ }
+
+ auto BranchExpression = symbolExpression(Context, "block");
+ if (!BranchExpression)
+ return false;
+ auto Branch =
+ instruction(BEQ, {reg(X10), reg(X11), rcc::mc::MCOperand::expression(*BranchExpression)});
+ auto BranchEncoding = Emitter.encodeInstruction(Branch);
+ if (!BranchEncoding || BranchEncoding->Fixups.size() != 1 ||
+ BranchEncoding->Fixups[0].Kind != rcc::riscv::fixupKind(rcc::riscv::RISCVFixupKind::Branch)) {
+ return false;
+ }
+ auto Applied = Backend.applyFixup(BranchEncoding->Fixups[0].Kind, BranchEncoding->Bytes, 0, 18);
+ if (!Applied || !bytesEqual(BranchEncoding->Bytes, {0x63, 0x09, 0xb5, 0x00}))
+ return false;
+
+ std::vector<std::uint8_t> Word(4);
+ auto OutOfRange =
+ Backend.applyFixup(rcc::riscv::fixupKind(rcc::riscv::RISCVFixupKind::Branch), Word, 0, 4096);
+ return !OutOfRange && OutOfRange.error().code() == rcc::ErrorCode::FixupApplicationFailed;
+}
+
+[[nodiscard]] bool testFailures(const rcc::riscv::RISCVMCCodeEmitter &Emitter) {
+ using enum rcc::riscv::InstructionOpcode;
+ using enum rcc::riscv::PhysicalRegisterID;
+ auto BadShift = Emitter.encodeInstruction(instruction(SLLI, {reg(X1), reg(X2), imm(64)}));
+ auto BadCompactRegister =
+ Emitter.encodeInstruction(instruction(C_LD, {reg(X1), reg(X8), imm(8)}));
+ auto BadAlignment = Emitter.encodeInstruction(instruction(C_SDSP, {reg(X10), imm(6)}));
+ auto Pseudo = Emitter.encodeInstruction(instruction(PseudoCALL, {}));
+ return !BadShift && BadShift.error().code() == rcc::ErrorCode::InstructionEncodingFailed &&
+ !BadCompactRegister &&
+ BadCompactRegister.error().code() == rcc::ErrorCode::InstructionEncodingFailed &&
+ !BadAlignment &&
+ BadAlignment.error().code() == rcc::ErrorCode::InstructionEncodingFailed && !Pseudo &&
+ Pseudo.error().code() == rcc::ErrorCode::InstructionEncodingFailed;
+}
+
+[[nodiscard]] bool containsBytes(std::span<const std::uint8_t> Bytes, std::string_view Text) {
+ if (Text.size() > Bytes.size())
+ return false;
+ const auto *Begin = reinterpret_cast<const char *>(Bytes.data());
+ return std::string_view(Begin, Bytes.size()).find(Text) != std::string_view::npos;
+}
+
+[[nodiscard]] bool testObjectEmission(const rcc::TargetDescription &Target) {
+ auto Alignment = rcc::Alignment::create(8);
+ auto Value = rcc::IntegerImmediate::fromUnsigned(64, 0x1122334455667788ULL);
+ if (!Alignment || !Value)
+ return false;
+ rcc::GlobalObjectDesc Global;
+ Global.Symbol = "object_value";
+ Global.ObjectType = rcc::GenericConstantType::byte();
+ Global.Align = *Alignment;
+ Global.Size = 8;
+ Global.Initializer.emplace(rcc::GlobalInitializer::integer(std::move(*Value)));
+ rcc::CompilationUnit Unit;
+ if (!Unit.addGlobalObject(std::move(Global)))
+ return false;
+ auto Object = rcc::riscv::emitObject(Unit, Target);
+ if (!Object || Object->size() < 64 || (*Object)[0] != 0x7f || (*Object)[1] != 'E' ||
+ (*Object)[2] != 'L' || (*Object)[3] != 'F') {
+ return false;
+ }
+ const std::uint16_t Machine =
+ static_cast<std::uint16_t>((*Object)[18]) | static_cast<std::uint16_t>((*Object)[19] << 8U);
+ const std::uint32_t Flags = static_cast<std::uint32_t>((*Object)[48]) |
+ static_cast<std::uint32_t>((*Object)[49]) << 8U |
+ static_cast<std::uint32_t>((*Object)[50]) << 16U |
+ static_cast<std::uint32_t>((*Object)[51]) << 24U;
+ return Machine == 243 && Flags == 5 && containsBytes(*Object, ".data") &&
+ containsBytes(*Object, ".riscv.attributes") && containsBytes(*Object, "object_value") &&
+ containsBytes(*Object, std::string_view("\x88\x77\x66\x55\x44\x33\x22\x11", 8));
+}
+
+} // namespace
+
+int main() {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return fail(Target.error().message());
+ rcc::riscv::RISCVMCCodeEmitter Emitter(*Target);
+ rcc::riscv::RISCVAsmBackend Backend;
+ if (!testEncodings(Emitter))
+ return fail("RISC-V MC encodings differ from the RV64GC reference encodings");
+ if (!testFixups(Emitter, Backend))
+ return fail("RISC-V MC fixup or relocation behavior is incorrect");
+ if (!testFailures(Emitter))
+ return fail("RISC-V MC encoding failures are not diagnosed");
+ if (!testObjectEmission(*Target))
+ return fail("RISC-V ELF object emission is incomplete");
+ if (Backend.objectFlags() != 5)
+ return fail("RISC-V ELF flags do not describe RV64GC LP64D");
+ return 0;
+}
diff --git a/backend/unittests/Target/RISCVObjectFileTest.cpp b/backend/unittests/Target/RISCVObjectFileTest.cpp
new file mode 100644
index 0000000..1b6d1f7
--- /dev/null
+++ b/backend/unittests/Target/RISCVObjectFileTest.cpp
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVObjectFile.h"
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/GlobalObject.h"
+#include "rcc/MC/ELFObjectWriter.h"
+#include "rcc/MC/MCAssembler.h"
+#include "rcc/MC/MCContext.h"
+#include "rcc/MC/MCDwarfFrameEmitter.h"
+#include "rcc/MC/MCExpr.h"
+#include "rcc/MC/MCObjectStreamer.h"
+#include "rcc/Target/RISCV/RISCVAsmBackend.h"
+#include "rcc/Target/RISCV/RISCVMCCodeEmitter.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+#include "rcc/Target/RISCV/RISCVTargetConstant.h"
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+constexpr std::uint32_t SHTRela = 4;
+constexpr std::uint32_t SHTNoBits = 8;
+constexpr std::uint32_t SHTGroup = 17;
+constexpr std::uint16_t SHNUndefined = 0;
+constexpr std::uint16_t SHNCommon = 0xfff2U;
+constexpr std::uint64_t SHFWrite = 0x1;
+constexpr std::uint64_t SHFAlloc = 0x2;
+constexpr std::uint64_t SHFGroup = 0x200;
+constexpr std::uint64_t SHFTLS = 0x400;
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++Failures;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return Failures == 0 ? 0 : 1;
+ }
+
+private:
+ int Failures = 0;
+};
+
+[[nodiscard]] std::optional<std::uint16_t> readU16(std::span<const std::uint8_t> Bytes,
+ std::uint64_t Offset) {
+ if (Offset > Bytes.size() || 2U > Bytes.size() - static_cast<std::size_t>(Offset))
+ return std::nullopt;
+ const std::size_t Position = static_cast<std::size_t>(Offset);
+ return static_cast<std::uint16_t>(Bytes[Position]) |
+ static_cast<std::uint16_t>(Bytes[Position + 1U]) << 8U;
+}
+
+[[nodiscard]] std::optional<std::uint32_t> readU32(std::span<const std::uint8_t> Bytes,
+ std::uint64_t Offset) {
+ if (Offset > Bytes.size() || 4U > Bytes.size() - static_cast<std::size_t>(Offset))
+ return std::nullopt;
+ std::uint32_t Result = 0;
+ for (std::uint32_t Index = 0; Index < 4; ++Index)
+ Result |= static_cast<std::uint32_t>(Bytes[static_cast<std::size_t>(Offset) + Index])
+ << (Index * 8U);
+ return Result;
+}
+
+[[nodiscard]] std::optional<std::uint64_t> readU64(std::span<const std::uint8_t> Bytes,
+ std::uint64_t Offset) {
+ if (Offset > Bytes.size() || 8U > Bytes.size() - static_cast<std::size_t>(Offset))
+ return std::nullopt;
+ std::uint64_t Result = 0;
+ for (std::uint32_t Index = 0; Index < 8; ++Index)
+ Result |= static_cast<std::uint64_t>(Bytes[static_cast<std::size_t>(Offset) + Index])
+ << (Index * 8U);
+ return Result;
+}
+
+struct ELFSection final {
+ std::string Name;
+ std::uint32_t Type = 0;
+ std::uint64_t Flags = 0;
+ std::uint64_t Offset = 0;
+ std::uint64_t Size = 0;
+ std::uint32_t Link = 0;
+ std::uint32_t Info = 0;
+ std::uint64_t Alignment = 0;
+ std::uint64_t EntrySize = 0;
+};
+
+struct ELFSymbol final {
+ std::string Name;
+ std::uint8_t Binding = 0;
+ std::uint8_t Type = 0;
+ std::uint8_t Visibility = 0;
+ std::uint16_t Section = 0;
+ std::uint64_t Value = 0;
+ std::uint64_t Size = 0;
+};
+
+struct ELFRelocation final {
+ std::uint64_t Offset = 0;
+ std::uint32_t Symbol = 0;
+ std::uint32_t Type = 0;
+ std::int64_t Addend = 0;
+};
+
+class ELFView final {
+public:
+ [[nodiscard]] static std::optional<ELFView> create(std::span<const std::uint8_t> Bytes) {
+ if (Bytes.size() < 64 || Bytes[0] != 0x7f || Bytes[1] != 'E' || Bytes[2] != 'L' ||
+ Bytes[3] != 'F' || Bytes[4] != 2 || Bytes[5] != 1) {
+ return std::nullopt;
+ }
+ const auto HeaderOffset = readU64(Bytes, 40);
+ const auto EntrySize = readU16(Bytes, 58);
+ const auto Count = readU16(Bytes, 60);
+ const auto NamesIndex = readU16(Bytes, 62);
+ if (!HeaderOffset || !EntrySize || !Count || !NamesIndex || *EntrySize != 64 || *Count == 0 ||
+ *NamesIndex >= *Count) {
+ return std::nullopt;
+ }
+ if (*HeaderOffset > Bytes.size() ||
+ static_cast<std::uint64_t>(*Count) * *EntrySize > Bytes.size() - *HeaderOffset) {
+ return std::nullopt;
+ }
+
+ const std::uint64_t NamesHeader = *HeaderOffset + static_cast<std::uint64_t>(*NamesIndex) * 64U;
+ const auto NamesOffset = readU64(Bytes, NamesHeader + 24U);
+ const auto NamesSize = readU64(Bytes, NamesHeader + 32U);
+ if (!NamesOffset || !NamesSize || *NamesOffset > Bytes.size() ||
+ *NamesSize > Bytes.size() - *NamesOffset) {
+ return std::nullopt;
+ }
+ const std::span<const std::uint8_t> Names =
+ Bytes.subspan(static_cast<std::size_t>(*NamesOffset), static_cast<std::size_t>(*NamesSize));
+
+ ELFView Result(Bytes);
+ Result.Sections.reserve(*Count);
+ for (std::uint16_t Index = 0; Index < *Count; ++Index) {
+ const std::uint64_t Position = *HeaderOffset + static_cast<std::uint64_t>(Index) * 64U;
+ const auto NameOffset = readU32(Bytes, Position);
+ const auto Type = readU32(Bytes, Position + 4U);
+ const auto Flags = readU64(Bytes, Position + 8U);
+ const auto Offset = readU64(Bytes, Position + 24U);
+ const auto Size = readU64(Bytes, Position + 32U);
+ const auto Link = readU32(Bytes, Position + 40U);
+ const auto Info = readU32(Bytes, Position + 44U);
+ const auto Alignment = readU64(Bytes, Position + 48U);
+ const auto SectionEntrySize = readU64(Bytes, Position + 56U);
+ if (!NameOffset || !Type || !Flags || !Offset || !Size || !Link || !Info || !Alignment ||
+ !SectionEntrySize || *NameOffset >= Names.size()) {
+ return std::nullopt;
+ }
+ std::size_t End = *NameOffset;
+ while (End < Names.size() && Names[End] != 0)
+ ++End;
+ if (End == Names.size())
+ return std::nullopt;
+ Result.Sections.push_back(ELFSection{
+ .Name = std::string(reinterpret_cast<const char *>(Names.data() + *NameOffset),
+ End - *NameOffset),
+ .Type = *Type,
+ .Flags = *Flags,
+ .Offset = *Offset,
+ .Size = *Size,
+ .Link = *Link,
+ .Info = *Info,
+ .Alignment = *Alignment,
+ .EntrySize = *SectionEntrySize,
+ });
+ }
+ return Result;
+ }
+
+ [[nodiscard]] const ELFSection *section(std::string_view Name) const noexcept {
+ const auto Position =
+ std::ranges::find(Sections, Name, [](const ELFSection &Value) { return Value.Name; });
+ return Position == Sections.end() ? nullptr : &*Position;
+ }
+
+ [[nodiscard]] std::optional<std::size_t> sectionIndex(std::string_view Name) const noexcept {
+ for (std::size_t Index = 0; Index < Sections.size(); ++Index) {
+ if (Sections[Index].Name == Name)
+ return Index;
+ }
+ return std::nullopt;
+ }
+
+ [[nodiscard]] std::span<const std::uint8_t> contents(const ELFSection &Section) const noexcept {
+ if (Section.Type == SHTNoBits || Section.Offset > Bytes.size() ||
+ Section.Size > Bytes.size() - Section.Offset) {
+ return {};
+ }
+ return Bytes.subspan(static_cast<std::size_t>(Section.Offset),
+ static_cast<std::size_t>(Section.Size));
+ }
+
+ [[nodiscard]] std::optional<std::vector<ELFSymbol>> symbols() const {
+ const ELFSection *Table = section(".symtab");
+ if (Table == nullptr || Table->EntrySize != 24 || Table->Link >= Sections.size())
+ return std::nullopt;
+ const std::span<const std::uint8_t> TableBytes = contents(*Table);
+ const std::span<const std::uint8_t> Names = contents(Sections[Table->Link]);
+ if (TableBytes.size() % 24U != 0 || Names.empty())
+ return std::nullopt;
+ std::vector<ELFSymbol> Result;
+ Result.reserve(TableBytes.size() / 24U);
+ for (std::size_t Offset = 0; Offset < TableBytes.size(); Offset += 24U) {
+ const auto NameOffset = readU32(TableBytes, Offset);
+ const auto Section = readU16(TableBytes, Offset + 6U);
+ const auto Value = readU64(TableBytes, Offset + 8U);
+ const auto Size = readU64(TableBytes, Offset + 16U);
+ if (!NameOffset || !Section || !Value || !Size || *NameOffset >= Names.size())
+ return std::nullopt;
+ std::size_t End = *NameOffset;
+ while (End < Names.size() && Names[End] != 0)
+ ++End;
+ if (End == Names.size())
+ return std::nullopt;
+ Result.push_back(ELFSymbol{
+ .Name = std::string(reinterpret_cast<const char *>(Names.data() + *NameOffset),
+ End - *NameOffset),
+ .Binding = static_cast<std::uint8_t>(TableBytes[Offset + 4U] >> 4U),
+ .Type = static_cast<std::uint8_t>(TableBytes[Offset + 4U] & 0xfU),
+ .Visibility = static_cast<std::uint8_t>(TableBytes[Offset + 5U] & 0x3U),
+ .Section = *Section,
+ .Value = *Value,
+ .Size = *Size,
+ });
+ }
+ return Result;
+ }
+
+ [[nodiscard]] std::optional<std::vector<ELFRelocation>> relocations(std::string_view Name) const {
+ const ELFSection *Table = section(Name);
+ if (Table == nullptr || Table->Type != SHTRela || Table->EntrySize != 24)
+ return std::nullopt;
+ const std::span<const std::uint8_t> TableBytes = contents(*Table);
+ if (TableBytes.size() % 24U != 0)
+ return std::nullopt;
+ std::vector<ELFRelocation> Result;
+ Result.reserve(TableBytes.size() / 24U);
+ for (std::size_t Offset = 0; Offset < TableBytes.size(); Offset += 24U) {
+ const auto RelocationOffset = readU64(TableBytes, Offset);
+ const auto Info = readU64(TableBytes, Offset + 8U);
+ const auto Addend = readU64(TableBytes, Offset + 16U);
+ if (!RelocationOffset || !Info || !Addend)
+ return std::nullopt;
+ Result.push_back(ELFRelocation{
+ .Offset = *RelocationOffset,
+ .Symbol = static_cast<std::uint32_t>(*Info >> 32U),
+ .Type = static_cast<std::uint32_t>(*Info),
+ .Addend = std::bit_cast<std::int64_t>(*Addend),
+ });
+ }
+ return Result;
+ }
+
+ [[nodiscard]] const std::vector<ELFSection> &sections() const noexcept {
+ return Sections;
+ }
+
+private:
+ explicit ELFView(std::span<const std::uint8_t> Bytes) : Bytes(Bytes) {}
+
+ std::span<const std::uint8_t> Bytes;
+ std::vector<ELFSection> Sections;
+};
+
+[[nodiscard]] const ELFSymbol *findSymbol(const std::vector<ELFSymbol> &Symbols,
+ std::string_view Name) {
+ const auto Position =
+ std::ranges::find(Symbols, Name, [](const ELFSymbol &Symbol) { return Symbol.Name; });
+ return Position == Symbols.end() ? nullptr : &*Position;
+}
+
+[[nodiscard]] rcc::Expected<rcc::Alignment> alignment(std::uint64_t Value) {
+ return rcc::Alignment::create(Value);
+}
+
+[[nodiscard]] rcc::Expected<rcc::GlobalInitializer> integer(std::uint32_t Bits,
+ std::uint64_t Value) {
+ auto Immediate = rcc::IntegerImmediate::fromUnsigned(Bits, Value);
+ if (!Immediate)
+ return std::unexpected(Immediate.error());
+ return rcc::GlobalInitializer::integer(std::move(*Immediate));
+}
+
+[[nodiscard]] bool addObject(rcc::CompilationUnit &Unit, std::string Name, std::uint64_t Size,
+ std::uint64_t Alignment, rcc::GlobalLinkage Linkage,
+ std::optional<rcc::GlobalInitializer> Initializer,
+ const auto &Configure) {
+ auto Align = alignment(Alignment);
+ if (!Align)
+ return false;
+ rcc::GlobalObjectDesc Object;
+ Object.Symbol = std::move(Name);
+ Object.ObjectType = rcc::GenericConstantType::byte();
+ Object.Linkage = Linkage;
+ Object.Align = *Align;
+ Object.Size = Size;
+ Object.Initializer = std::move(Initializer);
+ Configure(Object);
+ return Unit.addGlobalObject(std::move(Object)).has_value();
+}
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef> symbolDifference(rcc::mc::MCContext &Context,
+ std::string_view Left,
+ std::string_view Right,
+ std::int64_t Addend) {
+ auto LeftSymbol = Context.getOrCreateSymbol(std::string(Left), rcc::mc::MCSymbolKind::Generic);
+ auto RightSymbol = Context.getOrCreateSymbol(std::string(Right), rcc::mc::MCSymbolKind::Generic);
+ if (!LeftSymbol || !RightSymbol)
+ return std::unexpected(!LeftSymbol ? LeftSymbol.error() : RightSymbol.error());
+ auto LeftExpression = rcc::mc::MCExpr::symbol(*LeftSymbol);
+ auto RightExpression = rcc::mc::MCExpr::symbol(*RightSymbol);
+ if (!LeftExpression || !RightExpression)
+ return std::unexpected(!LeftExpression ? LeftExpression.error() : RightExpression.error());
+ auto Difference = rcc::mc::MCExpr::binary(
+ rcc::mc::MCBinaryOperator::Subtract, std::move(*LeftExpression), std::move(*RightExpression));
+ if (!Difference)
+ return std::unexpected(Difference.error());
+ return rcc::mc::MCExpr::binary(rcc::mc::MCBinaryOperator::Add, std::move(*Difference),
+ rcc::mc::MCExpr::constant(Addend));
+}
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef>
+symbolExpression(rcc::mc::MCContext &Context, std::string_view Name,
+ rcc::mc::MCSymbolKind Kind = rcc::mc::MCSymbolKind::Generic) {
+ auto Symbol = Context.getOrCreateSymbol(std::string(Name), Kind);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return rcc::mc::MCExpr::symbol(std::move(*Symbol));
+}
+
+[[nodiscard]] rcc::mc::MCInst instruction(rcc::riscv::InstructionOpcode Opcode) {
+ return rcc::mc::MCInst(rcc::mc::MCOpcode::fromValue(static_cast<std::uint32_t>(Opcode)));
+}
+
+void testMCRelaxation(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::riscv::RISCVMCCodeEmitter Emitter(Target);
+ rcc::riscv::RISCVAsmBackend Backend;
+ rcc::mc::MCContext MCContext;
+ auto Text = MCContext.getOrCreateSection(rcc::mc::MCSectionDesc{
+ .Name = ".text.relax",
+ .Kind = rcc::mc::MCSectionKind::Text,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Executable},
+ .Alignment = 2,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ });
+ auto JumpTarget = MCContext.createTemporarySymbol(".Ljump_target");
+ auto BranchTarget = MCContext.createTemporarySymbol(".Lbranch_target");
+ if (!Text || !JumpTarget || !BranchTarget) {
+ Context.expect(false, "RISC-V MC relaxation fixture construction must succeed");
+ return;
+ }
+ auto JumpExpression = rcc::mc::MCExpr::symbol(*JumpTarget);
+ auto BranchExpression = rcc::mc::MCExpr::symbol(*BranchTarget);
+ if (!JumpExpression || !BranchExpression) {
+ Context.expect(false, "RISC-V MC relaxation expressions must be constructible");
+ return;
+ }
+
+ rcc::mc::MCInst Jump = instruction(rcc::riscv::InstructionOpcode::C_J);
+ Jump.addOperand(rcc::mc::MCOperand::expression(*JumpExpression));
+ rcc::mc::MCInst Branch = instruction(rcc::riscv::InstructionOpcode::C_BEQZ);
+ Branch.addOperand(rcc::mc::MCOperand::registerOperand(rcc::mc::MCRegister::fromValue(
+ static_cast<std::uint32_t>(rcc::riscv::PhysicalRegisterID::X8))));
+ Branch.addOperand(rcc::mc::MCOperand::expression(*BranchExpression));
+
+ rcc::mc::MCAssembler Assembler(Emitter, Backend);
+ if (!Assembler.switchSection(*Text) || !Assembler.emitInstruction(Jump) ||
+ !Assembler.emitZeroFill(3000) || !Assembler.emitLabel(*JumpTarget) ||
+ !Assembler.emitInstruction(Branch) || !Assembler.emitZeroFill(600) ||
+ !Assembler.emitLabel(*BranchTarget)) {
+ Context.expect(false, "RISC-V MC relaxation events must succeed");
+ return;
+ }
+ auto Object = Assembler.assemble();
+ if (!Object)
+ std::cerr << "RISC-V MC relaxation failure: " << Object.error().message() << '\n';
+ Context.expect(Object.has_value(), "RISC-V MC relaxation must reach a fixed point");
+ if (!Object || Object->Sections.size() != 1)
+ return;
+ const auto JumpWord = readU32(Object->Sections[0].Contents, 0);
+ const auto BranchWord = readU32(Object->Sections[0].Contents, 3004);
+ Context.expect(JumpWord && (*JumpWord & 0xfffU) == 0x6fU,
+ "out-of-range C.J must monotonically relax to JAL x0");
+ Context.expect(BranchWord && (*BranchWord & 0x01fff07fU) == 0x00040063U,
+ "out-of-range C.BEQZ must monotonically relax to BEQ x8, x0");
+ Context.expect(Object->Sections[0].AddressSize == 3608 && Object->Sections[0].Relocations.empty(),
+ "resolved MC relaxation must update layout without linker relocations");
+
+ auto ExternalExpression = symbolExpression(MCContext, "external_jump");
+ if (!ExternalExpression) {
+ Context.expect(false, "external MC relaxation expression must be constructible");
+ return;
+ }
+ rcc::mc::MCInst ExternalJump = instruction(rcc::riscv::InstructionOpcode::C_J);
+ ExternalJump.addOperand(rcc::mc::MCOperand::expression(*ExternalExpression));
+ rcc::mc::MCAssembler Relocatable(Emitter, Backend);
+ auto Switched = Relocatable.switchSection(*Text);
+ auto Emitted = Relocatable.emitInstruction(ExternalJump);
+ auto ExternalObject = Relocatable.assemble();
+ Context.expect(
+ Switched && Emitted && ExternalObject &&
+ ExternalObject->Sections[0].Relocations.size() == 1 &&
+ ExternalObject->Sections[0].Relocations[0].Type == 45,
+ "unresolved C.J must retain one mandatory relocation without implicit linker relax");
+}
+
+void testAssemblerLayout(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::riscv::RISCVMCCodeEmitter Emitter(Target);
+ rcc::riscv::RISCVAsmBackend Backend;
+ rcc::mc::MCContext MCContext;
+ auto Data = MCContext.getOrCreateSection(rcc::mc::MCSectionDesc{
+ .Name = ".data",
+ .Kind = rcc::mc::MCSectionKind::Data,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Writable},
+ .Alignment = 1,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ });
+ auto BSS = MCContext.getOrCreateSection(rcc::mc::MCSectionDesc{
+ .Name = ".bss",
+ .Kind = rcc::mc::MCSectionKind::BSS,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Writable},
+ .Alignment = 1,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ });
+ auto Difference = symbolDifference(MCContext, "left", "right", 5);
+ if (!Data || !BSS || !Difference) {
+ Context.expect(false, "MC assembler layout fixture construction must succeed");
+ return;
+ }
+
+ rcc::mc::MCAssembler Assembler(Emitter, Backend);
+ constexpr std::uint64_t LargeBSS = (std::uint64_t{1} << 32U) + 32U;
+ if (!Assembler.switchSection(*Data) || !Assembler.emitAlignment(128, 0, 0) ||
+ !Assembler.emitValue(*Difference, 8) || !Assembler.switchSection(*BSS) ||
+ !Assembler.emitAlignment(4096, 0, 0) || !Assembler.emitZeroFill(LargeBSS)) {
+ Context.expect(false, "MC assembler layout events must succeed");
+ return;
+ }
+ auto Object = Assembler.assemble();
+ if (!Object)
+ std::cerr << "MC assembler layout failure: " << Object.error().message() << '\n';
+ Context.expect(Object.has_value(), "MC assembler layout must complete");
+ if (!Object || Object->Sections.size() != 2)
+ return;
+ Context.expect(Object->Sections[0].Alignment == 128 &&
+ Object->Sections[0].Relocations.size() == 2 &&
+ Object->Sections[0].Relocations[0].Type == 36 &&
+ Object->Sections[0].Relocations[0].Addend == 5 &&
+ Object->Sections[0].Relocations[1].Type == 40 &&
+ Object->Sections[0].Relocations[1].Addend == 0,
+ "MC assembler must preserve effective alignment and paired symbol relocations");
+ Context.expect(Object->Sections[1].Alignment == 4096 &&
+ Object->Sections[1].AddressSize == LargeBSS &&
+ Object->Sections[1].Contents.empty(),
+ "MC assembler must keep NOBITS address size without materializing zero bytes");
+
+ rcc::mc::MCAssembler BadAssembler(Emitter, Backend);
+ const std::array<std::uint8_t, 1> Nonzero{1};
+ auto Switched = BadAssembler.switchSection(*BSS);
+ auto Emitted = BadAssembler.emitBytes(Nonzero);
+ auto Failure = BadAssembler.assemble();
+ Context.expect(Switched && Emitted && !Failure &&
+ Failure.error().code() == rcc::ErrorCode::ObjectWritingFailed,
+ "MC assembler must reject initialized bytes in a NOBITS section");
+}
+
+void testNativeObjectBoundaries(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::riscv::RISCVMCCodeEmitter Emitter(Target);
+ rcc::riscv::RISCVAsmBackend Backend;
+ rcc::mc::ELF64LEObjectWriter Writer(rcc::mc::ELFObjectTargetInfo{
+ .Machine = 243, .OSABI = 0, .ABIVersion = 0, .Flags = 0, .ExtraSections = {}});
+ rcc::mc::ELFObjectStreamer Streamer(Emitter, Backend, Writer);
+ auto EmptyAssembly = Streamer.emitInlineAssembly({});
+ auto RawAssembly = Streamer.emitInlineAssembly("addi a0, a0, 1");
+ Context.expect(EmptyAssembly && !RawAssembly &&
+ RawAssembly.error().code() == rcc::ErrorCode::Unsupported,
+ "native object emission must reject unparsed inline assembly without dropping it");
+
+ rcc::mc::MCContext MCContext;
+ auto EHFrame = MCContext.getOrCreateSection(rcc::mc::MCSectionDesc{
+ .Name = ".eh_frame.boundary",
+ .Kind = rcc::mc::MCSectionKind::EHFrame,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc},
+ .Alignment = 8,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ });
+ auto Function = MCContext.getOrCreateSymbol("cfi_boundary", rcc::mc::MCSymbolKind::Function);
+ auto Personality =
+ MCContext.getOrCreateSymbol("cfi_personality", rcc::mc::MCSymbolKind::Function);
+ if (!EHFrame || !Function || !Personality) {
+ Context.expect(false, "native CFI boundary fixture must be constructible");
+ return;
+ }
+ const rcc::mc::MCDwarfFrameInfo Info{.CodeAlignment = 1,
+ .DataAlignment = -8,
+ .ReturnAddressRegister = 1,
+ .InitialCFARegister = 2,
+ .InitialCFAOffset = 0};
+ const std::array UnsupportedEvents{
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::Personality,
+ .Operands = {0},
+ .Bytes = {},
+ .Symbol = *Personality},
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::LSDA,
+ .Operands = {0},
+ .Bytes = {},
+ .Symbol = *Personality},
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::SignalFrame,
+ .Operands = {},
+ .Bytes = {},
+ .Symbol = {}},
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::ReturnColumn,
+ .Operands = {1},
+ .Bytes = {},
+ .Symbol = {}},
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::NegateReturnAddressState,
+ .Operands = {},
+ .Bytes = {},
+ .Symbol = {}},
+ rcc::mc::MCCFIInstruction{.Operation =
+ rcc::mc::MCCFIOperation::NegateReturnAddressStateWithPC,
+ .Operands = {},
+ .Bytes = {},
+ .Symbol = {}},
+ };
+ for (const rcc::mc::MCCFIInstruction &Event : UnsupportedEvents) {
+ rcc::mc::MCDwarfFrameProcedure Procedure{
+ .Function = *Function,
+ .AddressRange = 4,
+ .Instructions = {{.Instruction = Event, .Offset = 0}},
+ };
+ auto Frame = rcc::mc::emitDwarfEHFrame(*EHFrame, std::span(&Procedure, 1), Info, Backend);
+ Context.expect(!Frame && Frame.error().code() == rcc::ErrorCode::Unsupported,
+ "unsupported RV64GC CFI extensions must fail structurally");
+ }
+}
+
+[[nodiscard]] bool buildGlobalFixture(rcc::CompilationUnit &Unit) {
+ constexpr auto NoConfiguration = [](rcc::GlobalObjectDesc &) {};
+ if (!addObject(Unit, "external_target", 0, 1, rcc::GlobalLinkage::External, std::nullopt,
+ NoConfiguration) ||
+ !addObject(Unit, "weak_target", 0, 1, rcc::GlobalLinkage::ExternalWeak, std::nullopt,
+ NoConfiguration)) {
+ return false;
+ }
+
+ constexpr std::uint64_t LargeBSS = (std::uint64_t{1} << 32U) + 32U;
+ auto LargeZero = rcc::GlobalInitializer::zeroFill(LargeBSS);
+ auto TLSValue = integer(64, 0x1122334455667788ULL);
+ auto TLSZero = rcc::GlobalInitializer::zeroFill(32);
+ auto Relocation = rcc::GlobalInitializer::symbolAddress(
+ {.Symbol = "external_target", .Addend = 7, .Size = 8, .TargetFlags = 0});
+ auto COMDATRelocation = rcc::GlobalInitializer::symbolAddress(
+ {.Symbol = "external_target", .Addend = 3, .Size = 8, .TargetFlags = 0});
+ auto HiddenValue = integer(32, 0x12345678U);
+ auto ProtectedValue = integer(64, 0x8877665544332211ULL);
+ auto ReadOnlyValue = integer(32, 0xaabbccddU);
+ auto TargetConstant = rcc::TargetConstant::create(rcc::riscv::RISCVSymbolConstant(
+ rcc::riscv::RISCVSymbolConstantKind::ExternalSymbol, "external_target", 13));
+ auto TargetInitializer =
+ TargetConstant
+ ? rcc::GlobalInitializer::target(std::move(*TargetConstant))
+ : rcc::Expected<rcc::GlobalInitializer>(std::unexpected(TargetConstant.error()));
+ if (!LargeZero || !TLSValue || !TLSZero || !Relocation || !COMDATRelocation || !HiddenValue ||
+ !ProtectedValue || !ReadOnlyValue || !TargetInitializer) {
+ return false;
+ }
+
+ std::vector<rcc::GlobalInitializer> Differences;
+ for (const auto [Size, Addend] :
+ std::array<std::pair<std::uint8_t, std::int64_t>, 4>{{{1, 11}, {2, 9}, {4, 7}, {8, 5}}}) {
+ auto Difference = rcc::GlobalInitializer::symbolDifference({.Minuend = "external_target",
+ .Subtrahend = "weak_target",
+ .Addend = Addend,
+ .Size = Size});
+ if (!Difference)
+ return false;
+ Differences.push_back(std::move(*Difference));
+ }
+ auto DifferenceAggregate = rcc::GlobalInitializer::aggregate(std::move(Differences));
+ if (!DifferenceAggregate)
+ return false;
+
+ return addObject(Unit, "large_bss", LargeBSS, 4096, rcc::GlobalLinkage::Internal,
+ std::move(*LargeZero), NoConfiguration) &&
+ addObject(Unit, "tls_data", 8, 8, rcc::GlobalLinkage::External, std::move(*TLSValue),
+ [](rcc::GlobalObjectDesc &Object) {
+ Object.TLSModel = rcc::GlobalTLSModel::LocalExec;
+ }) &&
+ addObject(Unit, "tls_bss", 32, 32, rcc::GlobalLinkage::External, std::move(*TLSZero),
+ [](rcc::GlobalObjectDesc &Object) {
+ Object.TLSModel = rcc::GlobalTLSModel::GeneralDynamic;
+ }) &&
+ addObject(Unit, "common_value", 128, 64, rcc::GlobalLinkage::Common, std::nullopt,
+ NoConfiguration) &&
+ addObject(Unit, "relro_pointer", 8, 8, rcc::GlobalLinkage::External,
+ std::move(*Relocation),
+ [](rcc::GlobalObjectDesc &Object) { Object.IsConstant = true; }) &&
+ addObject(Unit, "difference_value", 15, 1, rcc::GlobalLinkage::External,
+ std::move(*DifferenceAggregate), NoConfiguration) &&
+ addObject(Unit, "comdat_value", 8, 8, rcc::GlobalLinkage::LinkOnceODR,
+ std::move(*COMDATRelocation),
+ [](rcc::GlobalObjectDesc &Object) { Object.COMDAT = Object.Symbol; }) &&
+ addObject(Unit, "hidden_value", 4, 16, rcc::GlobalLinkage::External,
+ std::move(*HiddenValue),
+ [](rcc::GlobalObjectDesc &Object) {
+ Object.Visibility = rcc::GlobalVisibility::Hidden;
+ Object.Section = ".custom.data";
+ }) &&
+ addObject(Unit, "protected_value", 8, 128, rcc::GlobalLinkage::External,
+ std::move(*ProtectedValue),
+ [](rcc::GlobalObjectDesc &Object) {
+ Object.Visibility = rcc::GlobalVisibility::Protected;
+ }) &&
+ addObject(Unit, "readonly_value", 4, 4, rcc::GlobalLinkage::External,
+ std::move(*ReadOnlyValue),
+ [](rcc::GlobalObjectDesc &Object) { Object.IsConstant = true; }) &&
+ addObject(Unit, "target_constant_value", 8, 8, rcc::GlobalLinkage::Internal,
+ std::move(*TargetInitializer), [](rcc::GlobalObjectDesc &Object) {
+ Object.IsConstant = true;
+ Object.Section = ".data.rel.ro.target-constant";
+ });
+}
+
+void testGlobalObjectEmission(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::CompilationUnit Unit;
+ Context.expect(buildGlobalFixture(Unit), "global object fixture construction must succeed");
+ auto First = rcc::riscv::emitObject(Unit, Target);
+ auto Second = rcc::riscv::emitObject(Unit, Target);
+ if (!First)
+ std::cerr << "first global object emission failure: " << First.error().message() << '\n';
+ if (!Second)
+ std::cerr << "second global object emission failure: " << Second.error().message() << '\n';
+ Context.expect(First && Second && *First == *Second,
+ "global object emission must be successful and deterministic");
+ if (!First)
+ return;
+ Context.expect(First->size() < std::size_t{64} * 1024U,
+ "large NOBITS globals must not inflate the ELF file");
+ auto View = ELFView::create(*First);
+ Context.expect(View.has_value(), "emitted global object must be a valid ELF64LE container");
+ if (!View)
+ return;
+
+ const ELFSection *BSS = View->section(".bss");
+ const ELFSection *Data = View->section(".data");
+ const ELFSection *TLSData = View->section(".tdata");
+ const ELFSection *TLSBSS = View->section(".tbss");
+ const ELFSection *RelRO = View->section(".data.rel.ro");
+ const ELFSection *COMDAT = View->section(".data.comdat_value");
+ const ELFSection *COMDATRelocations = View->section(".rela.data.comdat_value");
+ const ELFSection *Group = View->section(".group");
+ constexpr std::uint64_t LargeBSS = (std::uint64_t{1} << 32U) + 32U;
+ Context.expect(BSS != nullptr && BSS->Type == SHTNoBits && BSS->Size == LargeBSS &&
+ BSS->Alignment == 4096 &&
+ (BSS->Flags & (SHFWrite | SHFAlloc)) == (SHFWrite | SHFAlloc),
+ "ELF BSS section must preserve its address size, flags, and alignment");
+ Context.expect(Data != nullptr && Data->Alignment == 128,
+ "ELF data section alignment must include later fragment requirements");
+ Context.expect(TLSData != nullptr && TLSData->Type != SHTNoBits &&
+ (TLSData->Flags & SHFTLS) != 0 && TLSBSS != nullptr &&
+ TLSBSS->Type == SHTNoBits && (TLSBSS->Flags & SHFTLS) != 0,
+ "ELF TLS data and BSS sections must carry their TLS semantics");
+ Context.expect(RelRO != nullptr && COMDAT != nullptr && COMDATRelocations != nullptr &&
+ Group != nullptr && Group->Type == SHTGroup &&
+ (COMDAT->Flags & SHFGroup) != 0 && (COMDATRelocations->Flags & SHFGroup) != 0,
+ "ELF COMDAT data and relocation sections must share a group");
+ Context.expect(View->section(".custom.data") != nullptr && View->section(".rodata") != nullptr &&
+ View->section(".riscv.attributes") != nullptr,
+ "ELF custom, read-only, and RISC-V attribute sections must be present");
+ const ELFSection *TargetConstantSection = View->section(".data.rel.ro.target-constant");
+ Context.expect(TargetConstantSection != nullptr && TargetConstantSection->Size == 8 &&
+ std::ranges::all_of(View->contents(*TargetConstantSection),
+ [](std::uint8_t Byte) { return Byte == 0; }),
+ "RV64GC target constants must reserve their relocation storage");
+
+ auto Symbols = View->symbols();
+ Context.expect(Symbols.has_value(), "ELF symbol table must be parseable");
+ if (!Symbols)
+ return;
+ const ELFSymbol *External = findSymbol(*Symbols, "external_target");
+ const ELFSymbol *Weak = findSymbol(*Symbols, "weak_target");
+ const ELFSymbol *Large = findSymbol(*Symbols, "large_bss");
+ const ELFSymbol *TLS = findSymbol(*Symbols, "tls_data");
+ const ELFSymbol *Common = findSymbol(*Symbols, "common_value");
+ const ELFSymbol *Hidden = findSymbol(*Symbols, "hidden_value");
+ const ELFSymbol *Protected = findSymbol(*Symbols, "protected_value");
+ const ELFSymbol *COMDATSymbol = findSymbol(*Symbols, "comdat_value");
+ Context.expect(External != nullptr && External->Binding == 1 &&
+ External->Section == SHNUndefined && External->Type == 1 && Weak != nullptr &&
+ Weak->Binding == 2 && Weak->Section == SHNUndefined,
+ "undefined object declarations must retain global and weak bindings");
+ Context.expect(Large != nullptr && Large->Binding == 0 && Large->Size == LargeBSS &&
+ TLS != nullptr && TLS->Binding == 1 && TLS->Type == 6,
+ "local BSS and global TLS symbols must have correct ELF types and sizes");
+ Context.expect(Common != nullptr && Common->Binding == 1 && Common->Section == SHNCommon &&
+ Common->Value == 64 && Common->Size == 128,
+ "ELF common symbol alignment and size must be preserved");
+ Context.expect(Hidden != nullptr && Hidden->Visibility == 2 && Protected != nullptr &&
+ Protected->Visibility == 3 && COMDATSymbol != nullptr &&
+ COMDATSymbol->Binding == 2,
+ "ELF visibility and COMDAT weak binding must be preserved");
+
+ auto DifferenceRelocations = View->relocations(".rela.data");
+ Context.expect(DifferenceRelocations && DifferenceRelocations->size() == 8,
+ "symbol differences must emit eight paired ELF relocations");
+ if (DifferenceRelocations) {
+ const std::array<std::uint32_t, 8> ExpectedTypes{33, 37, 34, 38, 35, 39, 36, 40};
+ for (std::size_t Index = 0; Index < ExpectedTypes.size(); ++Index) {
+ Context.expect((*DifferenceRelocations)[Index].Type == ExpectedTypes[Index],
+ "symbol difference relocation type order is incorrect");
+ }
+ const std::array<std::int64_t, 8> ExpectedAddends{11, 0, 9, 0, 7, 0, 5, 0};
+ for (std::size_t Index = 0; Index < ExpectedAddends.size(); ++Index) {
+ Context.expect((*DifferenceRelocations)[Index].Addend == ExpectedAddends[Index],
+ "symbol difference relocation addend is incorrect");
+ }
+ }
+ auto Relocations = View->relocations(".rela.data.rel.ro");
+ Context.expect(Relocations && Relocations->size() == 1 && (*Relocations)[0].Type == 2 &&
+ (*Relocations)[0].Addend == 7,
+ "read-only pointer data must emit an R_RISCV_64 relocation");
+ auto COMDATFixups = View->relocations(".rela.data.comdat_value");
+ Context.expect(COMDATFixups && COMDATFixups->size() == 1 && (*COMDATFixups)[0].Type == 2 &&
+ (*COMDATFixups)[0].Addend == 3,
+ "COMDAT pointer data must retain its relocation");
+ auto TargetConstantFixups = View->relocations(".rela.data.rel.ro.target-constant");
+ Context.expect(TargetConstantFixups && TargetConstantFixups->size() == 1 &&
+ (*TargetConstantFixups)[0].Type == 2 &&
+ (*TargetConstantFixups)[0].Addend == 13,
+ "RV64GC target constants must emit their declared R_RISCV_64 relocation");
+
+ if (Group != nullptr && COMDATSymbol != nullptr) {
+ const std::span<const std::uint8_t> GroupBytes = View->contents(*Group);
+ const auto GroupFlags = readU32(GroupBytes, 0);
+ const auto COMDATIndex = View->sectionIndex(".data.comdat_value");
+ const auto RelocationIndex = View->sectionIndex(".rela.data.comdat_value");
+ bool ContainsCOMDAT = false;
+ bool ContainsRelocations = false;
+ for (std::size_t Offset = 4; Offset + 4 <= GroupBytes.size(); Offset += 4) {
+ const auto Member = readU32(GroupBytes, Offset);
+ ContainsCOMDAT |= Member && COMDATIndex && *Member == *COMDATIndex;
+ ContainsRelocations |= Member && RelocationIndex && *Member == *RelocationIndex;
+ }
+ Context.expect(GroupFlags == 1 && Group->Info < Symbols->size() &&
+ (*Symbols)[Group->Info].Name == "comdat_value" && ContainsCOMDAT &&
+ ContainsRelocations,
+ "ELF COMDAT group must name its signature and both member sections");
+ }
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ auto Target = rcc::riscv::createTargetDescription();
+ Context.expect(Target.has_value(), "RISC-V object test target creation must succeed");
+ if (!Target)
+ return Context.result();
+ testAssemblerLayout(Context, *Target);
+ testMCRelaxation(Context, *Target);
+ testNativeObjectBoundaries(Context, *Target);
+ testGlobalObjectEmission(Context, *Target);
+ return Context.result();
+}