diff options
Diffstat (limited to 'backend/unittests/Target')
| -rw-r--r-- | backend/unittests/Target/CMakeLists.txt | 30 | ||||
| -rw-r--r-- | backend/unittests/Target/DescriptionGeneratorTest.cpp | 2 | ||||
| -rw-r--r-- | backend/unittests/Target/Inputs/RISCVStackFramePipeline.mir | 12 | ||||
| -rw-r--r-- | backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp | 411 | ||||
| -rw-r--r-- | backend/unittests/Target/RISCVMCTest.cpp | 264 | ||||
| -rw-r--r-- | backend/unittests/Target/RISCVObjectFileTest.cpp | 791 |
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> §ions() 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(); +} |
