diff options
52 files changed, 7361 insertions, 58 deletions
diff --git a/backend/include/rcc/MC/ELFObjectWriter.h b/backend/include/rcc/MC/ELFObjectWriter.h new file mode 100644 index 0000000..2cd8879 --- /dev/null +++ b/backend/include/rcc/MC/ELFObjectWriter.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MC_ELFOBJECTWRITER_H +#define RCC_MC_ELFOBJECTWRITER_H + +#include "rcc/MC/MCObjectWriter.h" + +#include <cstdint> +#include <string> +#include <utility> +#include <vector> + +namespace rcc::mc { + +struct ELFExtraSection final { + std::string Name; + std::uint32_t Type = 1; + std::uint64_t Flags = 0; + std::uint64_t Alignment = 1; + std::uint64_t EntrySize = 0; + std::vector<std::uint8_t> Contents; +}; + +struct ELFObjectTargetInfo final { + std::uint16_t Machine = 0; + std::uint8_t OSABI = 0; + std::uint8_t ABIVersion = 0; + std::uint32_t Flags = 0; + std::vector<ELFExtraSection> ExtraSections; +}; + +class ELF64LEObjectWriter final : public MCObjectWriter { +public: + explicit ELF64LEObjectWriter(ELFObjectTargetInfo Target) : Target(std::move(Target)) {} + + [[nodiscard]] Expected<std::vector<std::uint8_t>> + writeObject(const MCAssembledObject &Object) const override; + +private: + ELFObjectTargetInfo Target; +}; + +} // namespace rcc::mc + +#endif // RCC_MC_ELFOBJECTWRITER_H diff --git a/backend/include/rcc/MC/MCAsmBackend.h b/backend/include/rcc/MC/MCAsmBackend.h new file mode 100644 index 0000000..76e6214 --- /dev/null +++ b/backend/include/rcc/MC/MCAsmBackend.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MC_MCASMBACKEND_H +#define RCC_MC_MCASMBACKEND_H + +#include "rcc/MC/MCFixup.h" +#include "rcc/MC/MCInst.h" +#include "rcc/Support/Error.h" + +#include <cstdint> +#include <span> +#include <vector> + +namespace rcc::mc { + +struct MCFixupKindInfo final { + std::uint8_t StorageSize = 0; + bool PCRelative = false; +}; + +struct MCFixupRelocation final { + std::uint32_t Type = 0; + MCSymbolRef Symbol; + std::int64_t Addend = 0; +}; + +class MCAsmBackend { +public: + virtual ~MCAsmBackend() = default; + + [[nodiscard]] virtual Expected<MCFixupKindInfo> fixupKindInfo(MCFixupKind Kind) const = 0; + [[nodiscard]] virtual Expected<void> applyFixup(MCFixupKind Kind, std::span<std::uint8_t> Data, + std::uint64_t Offset, + std::int64_t Value) const = 0; + [[nodiscard]] virtual Expected<std::uint32_t> relocationType(const MCFixup &Fixup, + const MCValue &Value) const = 0; + [[nodiscard]] virtual Expected<std::vector<MCFixupRelocation>> + relocationsForFixup(const MCFixup &Fixup, const MCValue &Value) const; + + [[nodiscard]] virtual bool mayNeedRelaxation(const MCInst &) const noexcept { + return false; + } + [[nodiscard]] virtual Expected<bool> fixupNeedsRelaxation(const MCFixup &, std::int64_t) const { + return false; + } + [[nodiscard]] virtual Expected<MCInst> relaxInstruction(const MCInst &) const; + + [[nodiscard]] virtual std::uint32_t objectFlags() const noexcept { + return 0; + } +}; + +} // namespace rcc::mc + +#endif // RCC_MC_MCASMBACKEND_H diff --git a/backend/include/rcc/MC/MCAssembler.h b/backend/include/rcc/MC/MCAssembler.h new file mode 100644 index 0000000..7a75498 --- /dev/null +++ b/backend/include/rcc/MC/MCAssembler.h @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MC_MCASSEMBLER_H +#define RCC_MC_MCASSEMBLER_H + +#include "rcc/MC/MCAsmBackend.h" +#include "rcc/MC/MCCodeEmitter.h" +#include "rcc/MC/MCDwarfFrameEmitter.h" +#include "rcc/MC/MCObjectWriter.h" +#include "rcc/MC/MCStreamer.h" +#include "rcc/Support/Error.h" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <span> +#include <unordered_map> +#include <variant> +#include <vector> + +namespace rcc::mc { + +class MCAssembler final { +public: + MCAssembler(const MCCodeEmitter &Emitter, const MCAsmBackend &Backend) noexcept + : Emitter(&Emitter), Backend(&Backend) {} + + [[nodiscard]] Expected<void> switchSection(MCSectionRef Section); + [[nodiscard]] Expected<void> emitLabel(MCSymbolRef Symbol); + [[nodiscard]] Expected<void> emitInstruction(const MCInst &Instruction); + [[nodiscard]] Expected<void> emitValue(MCExprRef Value, std::uint8_t Size); + [[nodiscard]] Expected<void> emitBytes(std::span<const std::uint8_t> Bytes); + [[nodiscard]] Expected<void> emitZeroFill(std::uint64_t Size); + [[nodiscard]] Expected<void> emitAlignment(std::uint64_t Alignment, std::uint8_t Fill, + std::uint64_t MaximumBytes); + [[nodiscard]] Expected<void> emitSymbolAttribute(MCSymbolRef Symbol, MCSymbolAttribute Attribute); + [[nodiscard]] Expected<void> emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size); + [[nodiscard]] Expected<void> emitCommonSymbol(MCSymbolRef Symbol, std::uint64_t Size, + std::uint64_t Alignment); + [[nodiscard]] Expected<void> emitCFI(const MCCFIInstruction &Instruction); + [[nodiscard]] Expected<void> configureDwarfFrame(MCSectionRef Section, MCDwarfFrameInfo Info); + + [[nodiscard]] Expected<MCAssembledObject> assemble(); + +private: + struct InstructionFragment final { + MCInst Instruction; + }; + struct DataFragment final { + std::vector<std::uint8_t> Bytes; + std::vector<MCFixup> Fixups; + }; + struct AlignmentFragment final { + std::uint64_t Alignment = 1; + std::uint8_t Fill = 0; + std::uint64_t MaximumBytes = 0; + }; + struct ZeroFillFragment final { + std::uint64_t Size = 0; + }; + using FragmentPayload = + std::variant<InstructionFragment, DataFragment, AlignmentFragment, ZeroFillFragment>; + + struct Fragment final { + FragmentPayload Payload; + std::uint64_t Offset = 0; + std::uint64_t Size = 0; + std::vector<std::uint8_t> EncodedBytes; + std::vector<MCFixup> Fixups; + }; + + struct SectionData final { + MCSectionRef Section; + std::vector<Fragment> Fragments; + std::uint64_t AddressSize = 0; + std::uint64_t Alignment = 1; + }; + + struct SymbolData final { + MCSymbolRef Symbol; + MCObjectSymbolBinding Binding = MCObjectSymbolBinding::Local; + bool BindingWasSet = false; + MCObjectSymbolType Type = MCObjectSymbolType::None; + MCObjectSymbolVisibility Visibility = MCObjectSymbolVisibility::Default; + std::optional<std::size_t> SectionIndex; + std::size_t FragmentIndex = 0; + MCExprRef Size; + std::uint64_t CommonSize = 0; + std::uint64_t CommonAlignment = 0; + }; + + struct CFIEvent final { + MCCFIInstruction Instruction; + std::size_t FragmentIndex = 0; + }; + + struct CFIProcedure final { + MCSymbolRef Function; + std::size_t SectionIndex = 0; + std::size_t BeginFragmentIndex = 0; + std::size_t EndFragmentIndex = 0; + std::vector<CFIEvent> Events; + bool IsClosed = false; + }; + + [[nodiscard]] Expected<std::size_t> currentSectionIndex() const; + [[nodiscard]] SymbolData &getOrCreateSymbol(MCSymbolRef Symbol); + [[nodiscard]] Expected<void> encodeFragments(); + [[nodiscard]] Expected<bool> layoutAndRelax(); + [[nodiscard]] Expected<MCAssembledObject> finalizeObject(); + [[nodiscard]] Expected<void> appendDwarfFrame(MCAssembledObject &Object) const; + [[nodiscard]] std::optional<std::uint64_t> symbolOffset(const SymbolData &Symbol) const; + + const MCCodeEmitter *Emitter; + const MCAsmBackend *Backend; + std::vector<SectionData> Sections; + std::unordered_map<const MCSection *, std::size_t> SectionIndices; + std::vector<SymbolData> Symbols; + std::unordered_map<const MCSymbol *, std::size_t> SymbolIndices; + std::optional<std::size_t> CurrentSection; + std::vector<CFIProcedure> CFIProcedures; + std::optional<std::size_t> ActiveCFIProcedure; + MCSectionRef EHFrameSection; + std::optional<MCDwarfFrameInfo> DwarfFrameInfo; + bool WasAssembled = false; +}; + +} // namespace rcc::mc + +#endif // RCC_MC_MCASSEMBLER_H diff --git a/backend/include/rcc/MC/MCCodeEmitter.h b/backend/include/rcc/MC/MCCodeEmitter.h new file mode 100644 index 0000000..7307a83 --- /dev/null +++ b/backend/include/rcc/MC/MCCodeEmitter.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MC_MCCODEEMITTER_H +#define RCC_MC_MCCODEEMITTER_H + +#include "rcc/MC/MCFixup.h" +#include "rcc/MC/MCInst.h" +#include "rcc/Support/Error.h" + +#include <cstdint> +#include <vector> + +namespace rcc::mc { + +struct MCEncodedInstruction final { + std::vector<std::uint8_t> Bytes; + std::vector<MCFixup> Fixups; +}; + +class MCCodeEmitter { +public: + virtual ~MCCodeEmitter() = default; + + [[nodiscard]] virtual Expected<MCEncodedInstruction> + encodeInstruction(const MCInst &Instruction) const = 0; +}; + +} // namespace rcc::mc + +#endif // RCC_MC_MCCODEEMITTER_H diff --git a/backend/include/rcc/MC/MCDwarfFrameEmitter.h b/backend/include/rcc/MC/MCDwarfFrameEmitter.h new file mode 100644 index 0000000..f338f36 --- /dev/null +++ b/backend/include/rcc/MC/MCDwarfFrameEmitter.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MC_MCDWARFFRAMEEMITTER_H +#define RCC_MC_MCDWARFFRAMEEMITTER_H + +#include "rcc/MC/MCAsmBackend.h" +#include "rcc/MC/MCCFI.h" +#include "rcc/MC/MCObjectWriter.h" +#include "rcc/Support/Error.h" + +#include <cstdint> +#include <span> +#include <vector> + +namespace rcc::mc { + +struct MCDwarfFrameInfo final { + std::uint64_t CodeAlignment = 1; + std::int64_t DataAlignment = -8; + std::uint32_t ReturnAddressRegister = 0; + std::uint32_t InitialCFARegister = 0; + std::uint64_t InitialCFAOffset = 0; + + [[nodiscard]] Expected<void> verify() const; +}; + +struct MCDwarfFrameInstruction final { + MCCFIInstruction Instruction; + std::uint64_t Offset = 0; +}; + +struct MCDwarfFrameProcedure final { + MCSymbolRef Function; + std::uint64_t AddressRange = 0; + std::vector<MCDwarfFrameInstruction> Instructions; +}; + +[[nodiscard]] Expected<MCObjectSection> +emitDwarfEHFrame(MCSectionRef Section, std::span<const MCDwarfFrameProcedure> Procedures, + const MCDwarfFrameInfo &Info, const MCAsmBackend &Backend); + +} // namespace rcc::mc + +#endif // RCC_MC_MCDWARFFRAMEEMITTER_H diff --git a/backend/include/rcc/MC/MCFixup.h b/backend/include/rcc/MC/MCFixup.h new file mode 100644 index 0000000..3329fab --- /dev/null +++ b/backend/include/rcc/MC/MCFixup.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MC_MCFIXUP_H +#define RCC_MC_MCFIXUP_H + +#include "rcc/MC/MCExpr.h" +#include "rcc/Support/Error.h" + +#include <cstdint> + +namespace rcc::mc { + +using MCFixupKind = std::uint16_t; + +namespace fixup_kind { + +inline constexpr MCFixupKind None = 0; +inline constexpr MCFixupKind Data1 = 1; +inline constexpr MCFixupKind Data2 = 2; +inline constexpr MCFixupKind Data4 = 3; +inline constexpr MCFixupKind Data8 = 4; +inline constexpr MCFixupKind PCRelative4 = 5; +inline constexpr MCFixupKind FirstTarget = 256; + +} // namespace fixup_kind + +struct MCFixup final { + std::uint64_t Offset = 0; + MCExprRef Value; + MCFixupKind Kind = fixup_kind::None; + bool PCRelative = false; + bool LinkerRelaxable = false; + + [[nodiscard]] Expected<void> verify() const; +}; + +struct MCValue final { + MCSymbolRef AddSymbol; + MCSymbolRef SubtractSymbol; + std::int64_t Addend = 0; + std::uint32_t TargetKind = 0; + + [[nodiscard]] bool isAbsolute() const noexcept { + return !AddSymbol && !SubtractSymbol; + } +}; + +[[nodiscard]] Expected<MCValue> evaluateAsRelocatable(const MCExpr &Expression); + +} // namespace rcc::mc + +#endif // RCC_MC_MCFIXUP_H diff --git a/backend/include/rcc/MC/MCObjectStreamer.h b/backend/include/rcc/MC/MCObjectStreamer.h new file mode 100644 index 0000000..4a6b56c --- /dev/null +++ b/backend/include/rcc/MC/MCObjectStreamer.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MC_MCOBJECTSTREAMER_H +#define RCC_MC_MCOBJECTSTREAMER_H + +#include "rcc/MC/MCAssembler.h" +#include "rcc/MC/MCObjectWriter.h" +#include "rcc/MC/MCStreamer.h" + +#include <cstdint> +#include <span> +#include <string_view> +#include <vector> + +namespace rcc::mc { + +class ELFObjectStreamer final : public MCStreamer { +public: + ELFObjectStreamer(const MCCodeEmitter &Emitter, const MCAsmBackend &Backend, + const MCObjectWriter &Writer) noexcept + : Assembler(Emitter, Backend), Writer(&Writer) {} + + [[nodiscard]] Expected<void> switchSection(MCSectionRef Section) override; + [[nodiscard]] Expected<void> emitLabel(MCSymbolRef Symbol) override; + [[nodiscard]] Expected<void> emitInstruction(const MCInst &Instruction) override; + [[nodiscard]] Expected<void> emitValue(MCExprRef Value, std::uint8_t Size) override; + [[nodiscard]] Expected<void> emitBytes(std::span<const std::uint8_t> Bytes) override; + [[nodiscard]] Expected<void> emitZeroFill(std::uint64_t Size) override; + [[nodiscard]] Expected<void> emitAlignment(std::uint64_t Alignment, std::uint8_t Fill = 0, + std::uint64_t MaximumBytes = 0) override; + [[nodiscard]] Expected<void> emitSymbolAttribute(MCSymbolRef Symbol, + MCSymbolAttribute Attribute) override; + [[nodiscard]] Expected<void> emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) override; + [[nodiscard]] Expected<void> emitCommonSymbol(MCSymbolRef Symbol, std::uint64_t Size, + std::uint64_t Alignment) override; + [[nodiscard]] Expected<void> emitDirective(std::string_view Name, + std::string_view Arguments = {}) override; + [[nodiscard]] Expected<void> emitCFI(const MCCFIInstruction &Instruction) override; + [[nodiscard]] Expected<void> emitInlineAssembly(std::string_view Text) override; + [[nodiscard]] Expected<void> emitComment(std::string_view Text) override; + [[nodiscard]] Expected<void> finish() override; + + [[nodiscard]] Expected<void> configureDwarfFrame(MCSectionRef Section, MCDwarfFrameInfo Info); + + [[nodiscard]] Expected<std::vector<std::uint8_t>> takeOutput(); + +private: + [[nodiscard]] Expected<void> ensureOpen() const; + + MCAssembler Assembler; + const MCObjectWriter *Writer; + std::vector<std::uint8_t> Output; + bool IsFinished = false; + bool OutputWasTaken = false; +}; + +} // namespace rcc::mc + +#endif // RCC_MC_MCOBJECTSTREAMER_H diff --git a/backend/include/rcc/MC/MCObjectWriter.h b/backend/include/rcc/MC/MCObjectWriter.h new file mode 100644 index 0000000..16c6a50 --- /dev/null +++ b/backend/include/rcc/MC/MCObjectWriter.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_MC_MCOBJECTWRITER_H +#define RCC_MC_MCOBJECTWRITER_H + +#include "rcc/MC/MCSection.h" +#include "rcc/MC/MCSymbol.h" +#include "rcc/Support/Error.h" + +#include <cstddef> +#include <cstdint> +#include <optional> +#include <vector> + +namespace rcc::mc { + +enum class MCObjectSymbolBinding : std::uint8_t { + Local, + Global, + Weak, +}; + +enum class MCObjectSymbolType : std::uint8_t { + None, + Function, + Object, + TLSObject, + Section, +}; + +enum class MCObjectSymbolVisibility : std::uint8_t { + Default, + Hidden, + Protected, +}; + +struct MCObjectRelocation final { + std::uint64_t Offset = 0; + std::uint32_t Type = 0; + MCSymbolRef Symbol; + std::int64_t Addend = 0; +}; + +struct MCObjectSection final { + MCSectionRef Section; + std::vector<std::uint8_t> Contents; + std::uint64_t AddressSize = 0; + std::uint64_t Alignment = 1; + std::vector<MCObjectRelocation> Relocations; +}; + +struct MCObjectSymbol final { + MCSymbolRef Symbol; + MCObjectSymbolBinding Binding = MCObjectSymbolBinding::Local; + MCObjectSymbolType Type = MCObjectSymbolType::None; + MCObjectSymbolVisibility Visibility = MCObjectSymbolVisibility::Default; + std::optional<std::size_t> SectionIndex; + std::uint64_t Value = 0; + std::uint64_t Size = 0; + std::uint64_t CommonAlignment = 0; +}; + +struct MCAssembledObject final { + std::vector<MCObjectSection> Sections; + std::vector<MCObjectSymbol> Symbols; + std::uint32_t Flags = 0; +}; + +class MCObjectWriter { +public: + virtual ~MCObjectWriter() = default; + + [[nodiscard]] virtual Expected<std::vector<std::uint8_t>> + writeObject(const MCAssembledObject &Object) const = 0; +}; + +} // namespace rcc::mc + +#endif // RCC_MC_MCOBJECTWRITER_H diff --git a/backend/include/rcc/MC/MCStreamer.h b/backend/include/rcc/MC/MCStreamer.h index 3add976..ea37178 100644 --- a/backend/include/rcc/MC/MCStreamer.h +++ b/backend/include/rcc/MC/MCStreamer.h @@ -44,6 +44,8 @@ public: [[nodiscard]] virtual Expected<void> emitSymbolAttribute(MCSymbolRef Symbol, MCSymbolAttribute Attribute) = 0; [[nodiscard]] virtual Expected<void> emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) = 0; + [[nodiscard]] virtual Expected<void> emitCommonSymbol(MCSymbolRef Symbol, std::uint64_t Size, + std::uint64_t Alignment) = 0; [[nodiscard]] virtual Expected<void> emitDirective(std::string_view Name, std::string_view Arguments = {}) = 0; [[nodiscard]] virtual Expected<void> emitCFI(const MCCFIInstruction &Instruction) = 0; @@ -67,6 +69,8 @@ public: [[nodiscard]] Expected<void> emitSymbolAttribute(MCSymbolRef Symbol, MCSymbolAttribute Attribute) override; [[nodiscard]] Expected<void> emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) override; + [[nodiscard]] Expected<void> emitCommonSymbol(MCSymbolRef Symbol, std::uint64_t Size, + std::uint64_t Alignment) override; [[nodiscard]] Expected<void> emitDirective(std::string_view Name, std::string_view Arguments = {}) override; [[nodiscard]] Expected<void> emitCFI(const MCCFIInstruction &Instruction) override; diff --git a/backend/include/rcc/Support/Error.h b/backend/include/rcc/Support/Error.h index 3bdab97..63c0b2f 100644 --- a/backend/include/rcc/Support/Error.h +++ b/backend/include/rcc/Support/Error.h @@ -26,6 +26,10 @@ enum class ErrorCode : std::uint8_t { FinalEncodabilityFailed, CFGOptimizationFailed, AssemblyLoweringFailed, + InstructionEncodingFailed, + FixupApplicationFailed, + RelocationFailed, + ObjectWritingFailed, }; [[nodiscard]] std::string_view getErrorCodeName(ErrorCode Code) noexcept; diff --git a/backend/include/rcc/Target/RISCV/RISCVAsmBackend.h b/backend/include/rcc/Target/RISCV/RISCVAsmBackend.h new file mode 100644 index 0000000..11dcb77 --- /dev/null +++ b/backend/include/rcc/Target/RISCV/RISCVAsmBackend.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVASMBACKEND_H +#define RCC_TARGET_RISCV_RISCVASMBACKEND_H + +#include "rcc/MC/MCAsmBackend.h" + +namespace rcc::riscv { + +class RISCVAsmBackend final : public mc::MCAsmBackend { +public: + [[nodiscard]] Expected<mc::MCFixupKindInfo> fixupKindInfo(mc::MCFixupKind Kind) const override; + [[nodiscard]] Expected<void> applyFixup(mc::MCFixupKind Kind, std::span<std::uint8_t> Data, + std::uint64_t Offset, std::int64_t Value) const override; + [[nodiscard]] Expected<std::uint32_t> relocationType(const mc::MCFixup &Fixup, + const mc::MCValue &Value) const override; + [[nodiscard]] Expected<std::vector<mc::MCFixupRelocation>> + relocationsForFixup(const mc::MCFixup &Fixup, const mc::MCValue &Value) const override; + + [[nodiscard]] bool mayNeedRelaxation(const mc::MCInst &Instruction) const noexcept override; + [[nodiscard]] Expected<bool> fixupNeedsRelaxation(const mc::MCFixup &Fixup, + std::int64_t Value) const override; + [[nodiscard]] Expected<mc::MCInst> relaxInstruction(const mc::MCInst &Instruction) const override; + + [[nodiscard]] std::uint32_t objectFlags() const noexcept override; +}; + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVASMBACKEND_H diff --git a/backend/include/rcc/Target/RISCV/RISCVFixupKinds.h b/backend/include/rcc/Target/RISCV/RISCVFixupKinds.h new file mode 100644 index 0000000..7e9c8a2 --- /dev/null +++ b/backend/include/rcc/Target/RISCV/RISCVFixupKinds.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVFIXUPKINDS_H +#define RCC_TARGET_RISCV_RISCVFIXUPKINDS_H + +#include "rcc/MC/MCFixup.h" + +#include <cstdint> + +namespace rcc::riscv { + +enum class RISCVFixupKind : mc::MCFixupKind { + High20 = mc::fixup_kind::FirstTarget, + Low12I, + Low12S, + PCRelativeHigh20, + PCRelativeLow12I, + PCRelativeLow12S, + JAL, + Branch, + RVCJump, + RVCBranch, + CallPLT, +}; + +[[nodiscard]] constexpr mc::MCFixupKind fixupKind(RISCVFixupKind Kind) noexcept { + return static_cast<mc::MCFixupKind>(Kind); +} + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVFIXUPKINDS_H diff --git a/backend/include/rcc/Target/RISCV/RISCVMCCodeEmitter.h b/backend/include/rcc/Target/RISCV/RISCVMCCodeEmitter.h new file mode 100644 index 0000000..700cdc2 --- /dev/null +++ b/backend/include/rcc/Target/RISCV/RISCVMCCodeEmitter.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVMCCODEEMITTER_H +#define RCC_TARGET_RISCV_RISCVMCCODEEMITTER_H + +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/MC/MCCodeEmitter.h" + +namespace rcc::riscv { + +class RISCVMCCodeEmitter final : public mc::MCCodeEmitter { +public: + explicit RISCVMCCodeEmitter(const TargetDescription &Target) noexcept : Target(&Target) {} + + [[nodiscard]] Expected<mc::MCEncodedInstruction> + encodeInstruction(const mc::MCInst &Instruction) const override; + +private: + const TargetDescription *Target; +}; + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVMCCODEEMITTER_H diff --git a/backend/include/rcc/Target/RISCV/RISCVObjectFile.h b/backend/include/rcc/Target/RISCV/RISCVObjectFile.h new file mode 100644 index 0000000..fd25012 --- /dev/null +++ b/backend/include/rcc/Target/RISCV/RISCVObjectFile.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVOBJECTFILE_H +#define RCC_TARGET_RISCV_RISCVOBJECTFILE_H + +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Support/Error.h" + +#include <cstdint> +#include <vector> + +namespace rcc { +class CompilationUnit; +class MachineFunction; +} // namespace rcc + +namespace rcc::riscv { + +struct ObjectEmissionOptions final { + bool EmitFunctionDirectives = true; + bool EmitCFI = true; + bool EmitBlockLabels = true; + bool VerifyInput = true; +}; + +[[nodiscard]] Expected<std::vector<std::uint8_t>> emitObject(const CompilationUnit &Unit, + const TargetDescription &Target, + ObjectEmissionOptions Options = {}); +[[nodiscard]] Expected<std::vector<std::uint8_t>> emitObject(const MachineFunction &Function, + const TargetDescription &Target, + ObjectEmissionOptions Options = {}); + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVOBJECTFILE_H diff --git a/backend/include/rcc/Target/RISCV/RISCVTarget.h b/backend/include/rcc/Target/RISCV/RISCVTarget.h index ca0849d..e005fb8 100644 --- a/backend/include/rcc/Target/RISCV/RISCVTarget.h +++ b/backend/include/rcc/Target/RISCV/RISCVTarget.h @@ -37,6 +37,7 @@ struct AssemblyInstructionInfo final { Opcode InstructionOpcode; std::string_view Mnemonic; AssemblyFormatID Format; + std::uint32_t Encoding = 0; }; struct CallingConventionInfo final { diff --git a/backend/include/rcc/Target/RISCV/RISCVTargetConstant.h b/backend/include/rcc/Target/RISCV/RISCVTargetConstant.h new file mode 100644 index 0000000..2244117 --- /dev/null +++ b/backend/include/rcc/Target/RISCV/RISCVTargetConstant.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVTARGETCONSTANT_H +#define RCC_TARGET_RISCV_RISCVTARGETCONSTANT_H + +#include "rcc/CodeGen/TargetConstant.h" + +#include <cstddef> +#include <cstdint> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace rcc::riscv { + +enum class RISCVSymbolConstantKind : std::uint8_t { + GlobalValue, + ExternalSymbol, +}; + +class RISCVSymbolConstant final { +public: + RISCVSymbolConstant(RISCVSymbolConstantKind Kind, std::string Symbol, std::int64_t Addend = 0) + : ConstantKind(Kind), SymbolName(std::move(Symbol)), SymbolAddend(Addend) {} + + [[nodiscard]] static constexpr TargetConstantTypeID stableTypeID() noexcept { + return TargetConstantTypeID::fromValue(0x5243435256435001ULL); + } + + [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept { + return 1; + } + + [[nodiscard]] Expected<void> verify() const; + [[nodiscard]] Expected<RISCVSymbolConstant> clone() const; + [[nodiscard]] std::uint64_t stableHash() const noexcept; + [[nodiscard]] std::string print() const; + [[nodiscard]] Expected<std::vector<std::byte>> encode() const; + [[nodiscard]] Expected<TargetConstantEmission> emit() const; + [[nodiscard]] Alignment requiredAlignment() const noexcept; + [[nodiscard]] std::optional<TargetRelocationKindID> relocationKind() const noexcept; + + [[nodiscard]] static Expected<RISCVSymbolConstant> parse(std::string_view Text); + [[nodiscard]] static Expected<RISCVSymbolConstant> decode(std::uint32_t Version, + std::span<const std::byte> Payload); + + [[nodiscard]] RISCVSymbolConstantKind kind() const noexcept { + return ConstantKind; + } + + [[nodiscard]] std::string_view symbol() const noexcept { + return SymbolName; + } + + [[nodiscard]] std::int64_t addend() const noexcept { + return SymbolAddend; + } + + friend bool operator==(const RISCVSymbolConstant &Left, + const RISCVSymbolConstant &Right) = default; + +private: + RISCVSymbolConstantKind ConstantKind; + std::string SymbolName; + std::int64_t SymbolAddend = 0; +}; + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVTARGETCONSTANT_H diff --git a/backend/include/rcc/Target/RISCV/RISCVTargetRelocationKinds.h b/backend/include/rcc/Target/RISCV/RISCVTargetRelocationKinds.h new file mode 100644 index 0000000..08bc73f --- /dev/null +++ b/backend/include/rcc/Target/RISCV/RISCVTargetRelocationKinds.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVTARGETRELOCATIONKINDS_H +#define RCC_TARGET_RISCV_RISCVTARGETRELOCATIONKINDS_H + +#include "rcc/CodeGen/MachineIRTypes.h" + +#include <cstdint> + +namespace rcc::riscv { + +enum class RISCVTargetRelocationKind : std::uint32_t { + Absolute32 = 1, + Absolute64, +}; + +[[nodiscard]] constexpr TargetRelocationKindID +targetRelocationKind(RISCVTargetRelocationKind Kind) noexcept { + return TargetRelocationKindID::fromValue(static_cast<std::uint32_t>(Kind)); +} + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVTARGETRELOCATIONKINDS_H diff --git a/backend/lib/AsmPrinter/AsmPrinter.cpp b/backend/lib/AsmPrinter/AsmPrinter.cpp index cebd6b8..49388e1 100644 --- a/backend/lib/AsmPrinter/AsmPrinter.cpp +++ b/backend/lib/AsmPrinter/AsmPrinter.cpp @@ -318,7 +318,7 @@ Expected<void> AsmPrinter::emitGlobalObjects(const CompilationUnit &Unit) { const GlobalObjectDesc *Object = Unit.globalObjectAt(Index); if (Object == nullptr) return makeUnexpected(ErrorCode::InternalError, "compilation unit contains a null global"); - if (Object->isDeclaration() || Object->Linkage == GlobalLinkage::AvailableExternally) + if (Object->Linkage == GlobalLinkage::AvailableExternally) continue; const mc::MCSymbolRef Symbol = Context->globalSymbol(*Object); @@ -327,14 +327,11 @@ Expected<void> AsmPrinter::emitGlobalObjects(const CompilationUnit &Unit) { auto Attributes = emitGlobalAttributes(*Object, Symbol, *Streamer); if (!Attributes) return std::unexpected(Attributes.error()); + if (Object->isDeclaration()) + continue; if (Object->Linkage == GlobalLinkage::Common) { - auto Name = mc::printMCSymbolName(Symbol->name()); - if (!Name) - return std::unexpected(Name.error()); - auto Common = - Streamer->emitDirective("comm", *Name + "," + std::to_string(Object->Size) + "," + - std::to_string(Object->Align.bytes())); + auto Common = Streamer->emitCommonSymbol(Symbol, Object->Size, Object->Align.bytes()); if (!Common) return std::unexpected(Common.error()); continue; diff --git a/backend/lib/CodeGen/MachineCombiner.cpp b/backend/lib/CodeGen/MachineCombiner.cpp index 02ea5e3..513d282 100644 --- a/backend/lib/CodeGen/MachineCombiner.cpp +++ b/backend/lib/CodeGen/MachineCombiner.cpp @@ -103,7 +103,9 @@ namespace { } [[nodiscard]] bool hasProtectedAttachment(const MachineInstr &Instruction) noexcept { - return Instruction.debugInstrNumber() != 0 || Instruction.preInstrSymbol().has_value() || + return Instruction.flags().has(MachineInstrFlag::FrameSetup) || + Instruction.flags().has(MachineInstrFlag::FrameDestroy) || + Instruction.debugInstrNumber() != 0 || Instruction.preInstrSymbol().has_value() || Instruction.postInstrSymbol().has_value() || Instruction.heapAllocMarker().has_value() || Instruction.pcSections().has_value() || Instruction.mmra().has_value() || Instruction.cfiType().has_value() || Instruction.deactivationSymbol().has_value(); diff --git a/backend/lib/CodeGen/MachineCompilationStatus.cpp b/backend/lib/CodeGen/MachineCompilationStatus.cpp index 93acf3a..3ebfa0f 100644 --- a/backend/lib/CodeGen/MachineCompilationStatus.cpp +++ b/backend/lib/CodeGen/MachineCompilationStatus.cpp @@ -51,6 +51,10 @@ isKnownDiagnosticSeverity(MachineCompilationDiagnosticSeverity Severity) noexcep case ErrorCode::FinalEncodabilityFailed: case ErrorCode::CFGOptimizationFailed: case ErrorCode::AssemblyLoweringFailed: + case ErrorCode::InstructionEncodingFailed: + case ErrorCode::FixupApplicationFailed: + case ErrorCode::RelocationFailed: + case ErrorCode::ObjectWritingFailed: case ErrorCode::IoError: case ErrorCode::InternalError: return true; diff --git a/backend/lib/CodeGen/MachineDCE.cpp b/backend/lib/CodeGen/MachineDCE.cpp index b413499..9ba9a1b 100644 --- a/backend/lib/CodeGen/MachineDCE.cpp +++ b/backend/lib/CodeGen/MachineDCE.cpp @@ -44,7 +44,9 @@ namespace { } [[nodiscard]] bool hasProtectedAttachment(const MachineInstr &Instruction) noexcept { - return Instruction.debugInstrNumber() != 0 || Instruction.preInstrSymbol().has_value() || + return Instruction.flags().has(MachineInstrFlag::FrameSetup) || + Instruction.flags().has(MachineInstrFlag::FrameDestroy) || + Instruction.debugInstrNumber() != 0 || Instruction.preInstrSymbol().has_value() || Instruction.postInstrSymbol().has_value() || Instruction.heapAllocMarker().has_value() || Instruction.pcSections().has_value() || Instruction.mmra().has_value() || Instruction.cfiType().has_value() || Instruction.deactivationSymbol().has_value(); @@ -156,8 +158,10 @@ Expected<MachinePassResult> MachineDCEPass::runImplementation(MachineFunction &F Instruction = Next; continue; } + std::vector<Register> DefinitionList = + std::move(*Definitions).value_or(std::vector<Register>{}); - for (const Register Reg : **Definitions) { + for (const Register Reg : DefinitionList) { auto Cleared = clearDebugUses(Registers, Reg); if (!Cleared) return std::unexpected(Cleared.error()); @@ -165,7 +169,7 @@ Expected<MachinePassResult> MachineDCEPass::runImplementation(MachineFunction &F auto Removed = Block->removeInstruction(*Instruction); if (!Removed) return std::unexpected(Removed.error()); - for (const Register Reg : **Definitions) { + for (const Register Reg : DefinitionList) { if (!Registers.useEmpty(Reg, true) || Registers.getUniqueVRegDef(Reg) != nullptr) { return makeUnexpected(ErrorCode::InternalError, "machine DCE left references to a removed definition"); diff --git a/backend/lib/MC/CMakeLists.txt b/backend/lib/MC/CMakeLists.txt index a55b468..330ede3 100644 --- a/backend/lib/MC/CMakeLists.txt +++ b/backend/lib/MC/CMakeLists.txt @@ -2,20 +2,34 @@ set( RCCBackendMCSources + ELFObjectWriter.cpp MCCFI.cpp + MCDwarfFrameEmitter.cpp + MCAsmBackend.cpp + MCAssembler.cpp MCContext.cpp MCExpr.cpp + MCFixup.cpp MCInstPrinter.cpp + MCObjectStreamer.cpp MCSection.cpp MCStreamer.cpp ) set( RCCBackendMCHeaders + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/ELFObjectWriter.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCCFI.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCDwarfFrameEmitter.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCAsmBackend.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCAssembler.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCCodeEmitter.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCContext.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCExpr.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCFixup.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCInst.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCInstPrinter.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCObjectStreamer.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCObjectWriter.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCSection.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCStreamer.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCSymbol.h" diff --git a/backend/lib/MC/ELFObjectWriter.cpp b/backend/lib/MC/ELFObjectWriter.cpp new file mode 100644 index 0000000..730ef72 --- /dev/null +++ b/backend/lib/MC/ELFObjectWriter.cpp @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MC/ELFObjectWriter.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <unordered_map> +#include <utility> +#include <vector> + +namespace rcc::mc { +namespace { + +constexpr std::uint32_t SHTNull = 0; +constexpr std::uint32_t SHTProgBits = 1; +constexpr std::uint32_t SHTSymTab = 2; +constexpr std::uint32_t SHTStrTab = 3; +constexpr std::uint32_t SHTRela = 4; +constexpr std::uint32_t SHTNoBits = 8; +constexpr std::uint32_t SHTRel = 9; +constexpr std::uint32_t SHTGroup = 17; +constexpr std::uint32_t SHTSymTabShndx = 18; +constexpr std::uint16_t SHNUndefined = 0; +constexpr std::uint32_t SHNLoReserve = 0xff00U; +constexpr std::uint16_t SHNCommon = 0xfff2U; +constexpr std::uint32_t GRPComdat = 1; + +constexpr std::uint64_t SHFWrite = 0x1; +constexpr std::uint64_t SHFAlloc = 0x2; +constexpr std::uint64_t SHFExecInstr = 0x4; +constexpr std::uint64_t SHFMerge = 0x10; +constexpr std::uint64_t SHFStrings = 0x20; +constexpr std::uint64_t SHFInfoLink = 0x40; +constexpr std::uint64_t SHFGroup = 0x200; +constexpr std::uint64_t SHFTLS = 0x400; +constexpr std::uint64_t SHFGNURetain = 0x200000; + +class StringTable final { +public: + StringTable() { + Bytes.push_back(0); + } + + [[nodiscard]] Expected<std::uint32_t> add(std::string_view Value) { + const auto Existing = Offsets.find(std::string(Value)); + if (Existing != Offsets.end()) + return Existing->second; + constexpr std::size_t Maximum = std::numeric_limits<std::uint32_t>::max(); + if (Value.size() > Maximum - 1U || Bytes.size() > Maximum - Value.size() - 1U) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "ELF string table exceeds 4 GiB"); + } + const auto Offset = static_cast<std::uint32_t>(Bytes.size()); + Bytes.insert(Bytes.end(), Value.begin(), Value.end()); + Bytes.push_back(0); + Offsets.emplace(std::string(Value), Offset); + return Offset; + } + + [[nodiscard]] const std::vector<std::uint8_t> &bytes() const noexcept { + return Bytes; + } + +private: + std::vector<std::uint8_t> Bytes; + std::unordered_map<std::string, std::uint32_t> Offsets; +}; + +struct OutputSymbol final { + std::uint32_t Name = 0; + std::uint8_t Info = 0; + std::uint8_t Other = 0; + std::uint16_t Section = SHNUndefined; + std::uint64_t Value = 0; + std::uint64_t Size = 0; + const MCSymbol *Identity = nullptr; +}; + +struct OutputSection final { + std::string Name; + std::uint32_t NameOffset = 0; + std::uint32_t Type = SHTProgBits; + std::uint64_t Flags = 0; + std::uint64_t Alignment = 1; + std::uint64_t EntrySize = 0; + std::uint32_t Link = 0; + std::uint32_t Info = 0; + std::vector<std::uint8_t> Contents; + std::uint64_t AddressSize = 0; + std::uint64_t FileOffset = 0; + const MCSection *Input = nullptr; + std::optional<std::size_t> RelocationTarget; + MCSymbolRef GroupSignature; +}; + +[[nodiscard]] bool isPowerOfTwo(std::uint64_t Value) noexcept { + return Value != 0 && (Value & (Value - 1U)) == 0; +} + +[[nodiscard]] bool isWriterManagedSectionType(std::uint32_t Type) noexcept { + switch (Type) { + case SHTNull: + case SHTSymTab: + case SHTStrTab: + case SHTRela: + case SHTNoBits: + case SHTRel: + case SHTGroup: + case SHTSymTabShndx: + return true; + default: + return false; + } +} + +[[nodiscard]] Expected<std::uint64_t> checkedAdd(std::uint64_t Left, std::uint64_t Right, + std::string_view Message) { + std::uint64_t Result = 0; + if (__builtin_add_overflow(Left, Right, &Result)) + return makeUnexpected(ErrorCode::ObjectWritingFailed, std::string(Message)); + return Result; +} + +[[nodiscard]] Expected<std::uint64_t> alignTo(std::uint64_t Value, std::uint64_t Alignment) { + if (!isPowerOfTwo(Alignment)) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "ELF alignment is not a power of two"); + auto Adjusted = checkedAdd(Value, Alignment - 1U, "ELF file offset overflows"); + if (!Adjusted) + return std::unexpected(Adjusted.error()); + return *Adjusted & ~(Alignment - 1U); +} + +void appendU16(std::vector<std::uint8_t> &Output, std::uint16_t Value) { + Output.push_back(static_cast<std::uint8_t>(Value)); + Output.push_back(static_cast<std::uint8_t>(Value >> 8U)); +} + +void appendU32(std::vector<std::uint8_t> &Output, std::uint32_t Value) { + for (std::uint32_t Shift = 0; Shift < 32; Shift += 8) + Output.push_back(static_cast<std::uint8_t>(Value >> Shift)); +} + +void appendU64(std::vector<std::uint8_t> &Output, std::uint64_t Value) { + for (std::uint32_t Shift = 0; Shift < 64; Shift += 8) + Output.push_back(static_cast<std::uint8_t>(Value >> Shift)); +} + +void writeU64(std::vector<std::uint8_t> &Output, std::size_t Offset, std::uint64_t Value) { + for (std::uint32_t Shift = 0; Shift < 64; Shift += 8) + Output[Offset + Shift / 8U] = static_cast<std::uint8_t>(Value >> Shift); +} + +[[nodiscard]] std::uint64_t sectionFlags(MCSectionFlags Flags) noexcept { + std::uint64_t Result = 0; + if (Flags.has(MCSectionFlag::Writable)) + Result |= SHFWrite; + if (Flags.has(MCSectionFlag::Alloc)) + Result |= SHFAlloc; + if (Flags.has(MCSectionFlag::Executable)) + Result |= SHFExecInstr; + if (Flags.has(MCSectionFlag::Merge)) + Result |= SHFMerge; + if (Flags.has(MCSectionFlag::Strings)) + Result |= SHFStrings; + if (Flags.has(MCSectionFlag::Group)) + Result |= SHFGroup; + if (Flags.has(MCSectionFlag::TLS)) + Result |= SHFTLS; + if (Flags.has(MCSectionFlag::Retain)) + Result |= SHFGNURetain; + return Result; +} + +[[nodiscard]] std::uint8_t symbolBinding(MCObjectSymbolBinding Binding) noexcept { + switch (Binding) { + case MCObjectSymbolBinding::Local: + return 0; + case MCObjectSymbolBinding::Global: + return 1; + case MCObjectSymbolBinding::Weak: + return 2; + } + return 0; +} + +[[nodiscard]] std::uint8_t symbolType(MCObjectSymbolType Type) noexcept { + switch (Type) { + case MCObjectSymbolType::None: + return 0; + case MCObjectSymbolType::Object: + return 1; + case MCObjectSymbolType::Function: + return 2; + case MCObjectSymbolType::Section: + return 3; + case MCObjectSymbolType::TLSObject: + return 6; + } + return 0; +} + +[[nodiscard]] std::uint8_t symbolVisibility(MCObjectSymbolVisibility Visibility) noexcept { + switch (Visibility) { + case MCObjectSymbolVisibility::Default: + return 0; + case MCObjectSymbolVisibility::Hidden: + return 2; + case MCObjectSymbolVisibility::Protected: + return 3; + } + return 0; +} + +[[nodiscard]] Expected<void> appendAtOffset(std::vector<std::uint8_t> &Output, std::uint64_t Offset, + std::span<const std::uint8_t> Contents) { + if (Offset > std::numeric_limits<std::size_t>::max() || + Contents.size() > + std::numeric_limits<std::size_t>::max() - static_cast<std::size_t>(Offset)) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "ELF output exceeds the host limit"); + } + const std::size_t Position = static_cast<std::size_t>(Offset); + if (Output.size() > Position) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF output sections overlap in file layout"); + } + if (Output.size() < Position) + Output.resize(Position); + Output.insert(Output.end(), Contents.begin(), Contents.end()); + return {}; +} + +} // namespace + +Expected<std::vector<std::uint8_t>> +ELF64LEObjectWriter::writeObject(const MCAssembledObject &Object) const { + if (Target.Machine == 0) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "ELF target machine is not configured"); + + constexpr std::size_t ReservedOutputSections = 4; + constexpr std::size_t MaximumInputAndExtraSections = SHNLoReserve - ReservedOutputSections - 1U; + if (Object.Sections.size() > MaximumInputAndExtraSections || + Target.ExtraSections.size() > MaximumInputAndExtraSections - Object.Sections.size()) { + return makeUnexpected(ErrorCode::Unsupported, + "ELF extended section numbering is not implemented"); + } + + std::vector<OutputSection> Sections; + Sections.push_back(OutputSection{.Name = {}, + .NameOffset = 0, + .Type = SHTNull, + .Flags = 0, + .Alignment = 0, + .EntrySize = 0, + .Link = 0, + .Info = 0, + .Contents = {}, + .AddressSize = 0, + .FileOffset = 0, + .Input = nullptr, + .RelocationTarget = std::nullopt, + .GroupSignature = {}}); + const auto RequireSectionCapacity = [&Sections](std::size_t Additional) -> Expected<void> { + if (Sections.size() >= SHNLoReserve || Additional >= SHNLoReserve - Sections.size()) { + return makeUnexpected(ErrorCode::Unsupported, + "ELF extended section numbering is not implemented"); + } + return {}; + }; + auto InputCapacity = RequireSectionCapacity(Object.Sections.size()); + if (!InputCapacity) + return std::unexpected(InputCapacity.error()); + std::vector<std::uint32_t> InputSectionIndices(Object.Sections.size()); + std::unordered_map<const MCSection *, bool> ExistingSections; + for (std::size_t Index = 0; Index < Object.Sections.size(); ++Index) { + const MCObjectSection &Input = Object.Sections[Index]; + if (!Input.Section) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "ELF input section is null"); + if (!ExistingSections.emplace(Input.Section.get(), true).second) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF input contains a duplicate section identity"); + } + const MCSectionDesc &Description = Input.Section->description(); + auto DescriptionVerification = Description.verify(); + if (!DescriptionVerification) + return std::unexpected(DescriptionVerification.error()); + const bool NoBits = + Description.Kind == MCSectionKind::BSS || Description.Kind == MCSectionKind::TLSBSS; + if (!isPowerOfTwo(Input.Alignment) || Input.Alignment < Description.Alignment) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF input section has an invalid effective alignment"); + } + if ((!NoBits && Input.AddressSize != Input.Contents.size()) || + (NoBits && !Input.Contents.empty())) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF input section contents disagree with its address size"); + } + if (NoBits && !Input.Relocations.empty()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF NOBITS section cannot contain relocations"); + } + const auto OutputIndex = static_cast<std::uint32_t>(Sections.size()); + InputSectionIndices[Index] = OutputIndex; + Sections.push_back( + OutputSection{.Name = Description.Name, + .NameOffset = 0, + .Type = NoBits ? SHTNoBits : SHTProgBits, + .Flags = sectionFlags(Description.Flags), + .Alignment = Input.Alignment, + .EntrySize = Description.EntrySize, + .Link = 0, + .Info = 0, + .Contents = NoBits ? std::vector<std::uint8_t>{} : Input.Contents, + .AddressSize = Input.AddressSize, + .FileOffset = 0, + .Input = Input.Section.get(), + .RelocationTarget = std::nullopt, + .GroupSignature = Description.Group}); + } + + for (const ELFExtraSection &Extra : Target.ExtraSections) { + if (Extra.Name.empty() || isWriterManagedSectionType(Extra.Type) || + !isPowerOfTwo(Extra.Alignment) || (Extra.Flags & (SHFInfoLink | SHFGroup)) != 0 || + (Extra.EntrySize != 0 && Extra.Contents.size() % Extra.EntrySize != 0)) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF extra section has an invalid description"); + } + auto ExtraCapacity = RequireSectionCapacity(1); + if (!ExtraCapacity) + return std::unexpected(ExtraCapacity.error()); + Sections.push_back(OutputSection{.Name = Extra.Name, + .NameOffset = 0, + .Type = Extra.Type, + .Flags = Extra.Flags, + .Alignment = Extra.Alignment, + .EntrySize = Extra.EntrySize, + .Link = 0, + .Info = 0, + .Contents = Extra.Contents, + .AddressSize = Extra.Contents.size(), + .FileOffset = 0, + .Input = nullptr, + .RelocationTarget = std::nullopt, + .GroupSignature = {}}); + } + + StringTable SymbolNames; + std::vector<OutputSymbol> LocalSymbols; + std::vector<OutputSymbol> GlobalSymbols; + std::unordered_map<const MCSymbol *, bool> ExistingSymbols; + for (const MCObjectSymbol &Input : Object.Symbols) { + if (!Input.Symbol) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "ELF input symbol is null"); + if (!ExistingSymbols.emplace(Input.Symbol.get(), true).second) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF input contains a duplicate symbol identity"); + } + auto Name = SymbolNames.add(Input.Symbol->name()); + if (!Name) + return std::unexpected(Name.error()); + std::uint16_t Section = SHNUndefined; + std::uint64_t Value = Input.Value; + if (Input.CommonAlignment != 0) { + if (!isPowerOfTwo(Input.CommonAlignment) || Input.Size == 0 || Input.SectionIndex || + Input.Binding == MCObjectSymbolBinding::Local || Input.Value != Input.CommonAlignment) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF common symbol has an invalid definition"); + } + Section = SHNCommon; + Value = Input.CommonAlignment; + } else if (Input.SectionIndex) { + if (*Input.SectionIndex >= InputSectionIndices.size()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF symbol references an invalid section"); + } + const std::uint32_t Mapped = InputSectionIndices[*Input.SectionIndex]; + if (Mapped >= SHNLoReserve) { + return makeUnexpected(ErrorCode::Unsupported, + "ELF extended section indices are not implemented"); + } + Section = static_cast<std::uint16_t>(Mapped); + const std::uint64_t SectionSize = Object.Sections[*Input.SectionIndex].AddressSize; + if (Input.Value > SectionSize || Input.Size > SectionSize - Input.Value) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF symbol range exceeds its section"); + } + } else if (Input.Value != 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF undefined symbol has a nonzero value"); + } + OutputSymbol Symbol{.Name = *Name, + .Info = static_cast<std::uint8_t>((symbolBinding(Input.Binding) << 4U) | + symbolType(Input.Type)), + .Other = symbolVisibility(Input.Visibility), + .Section = Section, + .Value = Value, + .Size = Input.Size, + .Identity = Input.Symbol.get()}; + if (Input.Binding == MCObjectSymbolBinding::Local) + LocalSymbols.push_back(Symbol); + else + GlobalSymbols.push_back(Symbol); + } + + for (const OutputSection &Section : Sections) { + if (!Section.GroupSignature || ExistingSymbols.contains(Section.GroupSignature.get())) + continue; + auto Name = SymbolNames.add(Section.GroupSignature->name()); + if (!Name) + return std::unexpected(Name.error()); + GlobalSymbols.push_back(OutputSymbol{.Name = *Name, + .Info = static_cast<std::uint8_t>(1U << 4U), + .Other = 0, + .Section = SHNUndefined, + .Value = 0, + .Size = 0, + .Identity = Section.GroupSignature.get()}); + ExistingSymbols.emplace(Section.GroupSignature.get(), true); + } + + std::vector<OutputSymbol> Symbols; + Symbols.push_back(OutputSymbol{}); + for (std::size_t Index = 1; Index < Sections.size(); ++Index) { + Symbols.push_back(OutputSymbol{.Name = 0, + .Info = static_cast<std::uint8_t>(3), + .Other = 0, + .Section = static_cast<std::uint16_t>(Index), + .Value = 0, + .Size = 0, + .Identity = nullptr}); + } + constexpr std::size_t MaximumSymbolCount = std::numeric_limits<std::uint32_t>::max(); + if (LocalSymbols.size() > MaximumSymbolCount - Symbols.size()) { + return makeUnexpected(ErrorCode::Unsupported, "ELF symbol index exceeds 32 bits"); + } + Symbols.insert(Symbols.end(), LocalSymbols.begin(), LocalSymbols.end()); + const std::uint32_t FirstGlobal = static_cast<std::uint32_t>(Symbols.size()); + if (GlobalSymbols.size() > MaximumSymbolCount - Symbols.size()) { + return makeUnexpected(ErrorCode::Unsupported, "ELF symbol index exceeds 32 bits"); + } + Symbols.insert(Symbols.end(), GlobalSymbols.begin(), GlobalSymbols.end()); + std::unordered_map<const MCSymbol *, std::uint32_t> SymbolIndices; + for (std::size_t Index = 0; Index < Symbols.size(); ++Index) { + if (Symbols[Index].Identity) + SymbolIndices.emplace(Symbols[Index].Identity, static_cast<std::uint32_t>(Index)); + } + + for (std::size_t InputIndex = 0; InputIndex < Object.Sections.size(); ++InputIndex) { + const MCObjectSection &Input = Object.Sections[InputIndex]; + if (Input.Relocations.empty()) + continue; + if (Input.Relocations.size() > std::numeric_limits<std::size_t>::max() / 24U) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF relocation section exceeds the host limit"); + } + std::vector<std::uint8_t> Contents; + Contents.reserve(Input.Relocations.size() * 24U); + for (const MCObjectRelocation &Relocation : Input.Relocations) { + const auto Symbol = SymbolIndices.find(Relocation.Symbol.get()); + if (!Relocation.Symbol || Symbol == SymbolIndices.end()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF relocation references an unknown symbol"); + } + if (Relocation.Type == 0 || Relocation.Offset >= Input.AddressSize) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF relocation has an invalid type or section offset"); + } + appendU64(Contents, Relocation.Offset); + appendU64(Contents, (static_cast<std::uint64_t>(Symbol->second) << 32U) | Relocation.Type); + appendU64(Contents, static_cast<std::uint64_t>(Relocation.Addend)); + } + const std::uint32_t TargetSection = InputSectionIndices[InputIndex]; + const MCSymbolRef GroupSignature = Sections[TargetSection].GroupSignature; + auto RelocationCapacity = RequireSectionCapacity(1); + if (!RelocationCapacity) + return std::unexpected(RelocationCapacity.error()); + Sections.push_back(OutputSection{.Name = ".rela" + std::string(Input.Section->name()), + .NameOffset = 0, + .Type = SHTRela, + .Flags = SHFInfoLink | (GroupSignature ? SHFGroup : 0), + .Alignment = 8, + .EntrySize = 24, + .Link = 0, + .Info = TargetSection, + .Contents = std::move(Contents), + .AddressSize = 0, + .FileOffset = 0, + .Input = nullptr, + .RelocationTarget = InputIndex, + .GroupSignature = GroupSignature}); + } + + struct PendingGroup final { + MCSymbolRef Signature; + std::vector<std::uint32_t> Members; + }; + std::vector<PendingGroup> Groups; + for (std::size_t SectionIndex = 1; SectionIndex < Sections.size(); ++SectionIndex) { + if (!Sections[SectionIndex].GroupSignature) + continue; + const auto Existing = std::ranges::find_if(Groups, [&](const PendingGroup &Group) { + return Group.Signature == Sections[SectionIndex].GroupSignature; + }); + if (Existing == Groups.end()) { + Groups.push_back(PendingGroup{ + .Signature = Sections[SectionIndex].GroupSignature, + .Members = {static_cast<std::uint32_t>(SectionIndex)}, + }); + } else { + Existing->Members.push_back(static_cast<std::uint32_t>(SectionIndex)); + } + } + for (const PendingGroup &Group : Groups) { + const auto Signature = SymbolIndices.find(Group.Signature.get()); + if (Signature == SymbolIndices.end() || (Symbols[Signature->second].Info >> 4U) == 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF COMDAT group requires a nonlocal signature symbol"); + } + std::vector<std::uint8_t> Contents; + appendU32(Contents, GRPComdat); + for (const std::uint32_t Member : Group.Members) + appendU32(Contents, Member); + auto GroupCapacity = RequireSectionCapacity(1); + if (!GroupCapacity) + return std::unexpected(GroupCapacity.error()); + Sections.push_back(OutputSection{.Name = ".group", + .NameOffset = 0, + .Type = SHTGroup, + .Flags = 0, + .Alignment = 4, + .EntrySize = 4, + .Link = 0, + .Info = Signature->second, + .Contents = std::move(Contents), + .AddressSize = 0, + .FileOffset = 0, + .Input = nullptr, + .RelocationTarget = std::nullopt, + .GroupSignature = {}}); + } + + auto MetadataCapacity = RequireSectionCapacity(3); + if (!MetadataCapacity) + return std::unexpected(MetadataCapacity.error()); + const std::uint32_t SymTabIndex = static_cast<std::uint32_t>(Sections.size()); + const std::uint32_t StrTabIndex = SymTabIndex + 1U; + for (OutputSection &Section : Sections) { + if (Section.Type == SHTGroup || Section.Type == SHTRela) + Section.Link = SymTabIndex; + } + + std::vector<std::uint8_t> SymbolContents; + if (Symbols.size() > std::numeric_limits<std::size_t>::max() / 24U) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF symbol table exceeds the host limit"); + } + SymbolContents.reserve(Symbols.size() * 24U); + for (const OutputSymbol &Symbol : Symbols) { + appendU32(SymbolContents, Symbol.Name); + SymbolContents.push_back(Symbol.Info); + SymbolContents.push_back(Symbol.Other); + appendU16(SymbolContents, Symbol.Section); + appendU64(SymbolContents, Symbol.Value); + appendU64(SymbolContents, Symbol.Size); + } + Sections.push_back(OutputSection{.Name = ".symtab", + .NameOffset = 0, + .Type = SHTSymTab, + .Flags = 0, + .Alignment = 8, + .EntrySize = 24, + .Link = StrTabIndex, + .Info = FirstGlobal, + .Contents = std::move(SymbolContents), + .AddressSize = 0, + .FileOffset = 0, + .Input = nullptr, + .RelocationTarget = std::nullopt, + .GroupSignature = {}}); + Sections.push_back(OutputSection{.Name = ".strtab", + .NameOffset = 0, + .Type = SHTStrTab, + .Flags = 0, + .Alignment = 1, + .EntrySize = 0, + .Link = 0, + .Info = 0, + .Contents = SymbolNames.bytes(), + .AddressSize = 0, + .FileOffset = 0, + .Input = nullptr, + .RelocationTarget = std::nullopt, + .GroupSignature = {}}); + + StringTable SectionNames; + for (OutputSection &Section : Sections) { + auto Name = SectionNames.add(Section.Name); + if (!Name) + return std::unexpected(Name.error()); + Section.NameOffset = *Name; + } + const std::uint32_t ShStrTabIndex = static_cast<std::uint32_t>(Sections.size()); + auto ShStrName = SectionNames.add(".shstrtab"); + if (!ShStrName) + return std::unexpected(ShStrName.error()); + Sections.push_back(OutputSection{.Name = ".shstrtab", + .NameOffset = *ShStrName, + .Type = SHTStrTab, + .Flags = 0, + .Alignment = 1, + .EntrySize = 0, + .Link = 0, + .Info = 0, + .Contents = SectionNames.bytes(), + .AddressSize = 0, + .FileOffset = 0, + .Input = nullptr, + .RelocationTarget = std::nullopt, + .GroupSignature = {}}); + + if (Sections.size() >= SHNLoReserve) { + return makeUnexpected(ErrorCode::Unsupported, + "ELF extended section numbering is not implemented"); + } + + std::uint64_t FileOffset = 64; + for (std::size_t Index = 1; Index < Sections.size(); ++Index) { + OutputSection &Section = Sections[Index]; + auto Aligned = alignTo(FileOffset, Section.Alignment); + if (!Aligned) + return std::unexpected(Aligned.error()); + Section.FileOffset = *Aligned; + if (Section.Type == SHTNoBits) + continue; + auto End = checkedAdd(*Aligned, Section.Contents.size(), "ELF section file range overflows"); + if (!End) + return std::unexpected(End.error()); + FileOffset = *End; + } + auto SectionHeaderOffset = alignTo(FileOffset, 8); + if (!SectionHeaderOffset) + return std::unexpected(SectionHeaderOffset.error()); + if (Sections.size() > std::numeric_limits<std::uint64_t>::max() / 64U) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "ELF section header table exceeds the host limit"); + } + auto End = checkedAdd(*SectionHeaderOffset, static_cast<std::uint64_t>(Sections.size()) * 64U, + "ELF section header table overflows"); + if (!End || *End > std::numeric_limits<std::size_t>::max()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "ELF output exceeds the host limit"); + } + + std::vector<std::uint8_t> Output(64, 0); + Output[0] = 0x7f; + Output[1] = 'E'; + Output[2] = 'L'; + Output[3] = 'F'; + Output[4] = 2; + Output[5] = 1; + Output[6] = 1; + Output[7] = Target.OSABI; + Output[8] = Target.ABIVersion; + Output[16] = 1; + Output[18] = static_cast<std::uint8_t>(Target.Machine); + Output[19] = static_cast<std::uint8_t>(Target.Machine >> 8U); + Output[20] = 1; + writeU64(Output, 40, *SectionHeaderOffset); + Output[48] = static_cast<std::uint8_t>(Target.Flags | Object.Flags); + Output[49] = static_cast<std::uint8_t>((Target.Flags | Object.Flags) >> 8U); + Output[50] = static_cast<std::uint8_t>((Target.Flags | Object.Flags) >> 16U); + Output[51] = static_cast<std::uint8_t>((Target.Flags | Object.Flags) >> 24U); + Output[52] = 64; + Output[58] = 64; + Output[60] = static_cast<std::uint8_t>(Sections.size()); + Output[61] = static_cast<std::uint8_t>(Sections.size() >> 8U); + Output[62] = static_cast<std::uint8_t>(ShStrTabIndex); + Output[63] = static_cast<std::uint8_t>(ShStrTabIndex >> 8U); + + for (std::size_t Index = 1; Index < Sections.size(); ++Index) { + if (Sections[Index].Type == SHTNoBits) + continue; + auto Appended = appendAtOffset(Output, Sections[Index].FileOffset, Sections[Index].Contents); + if (!Appended) + return std::unexpected(Appended.error()); + } + if (Output.size() < *SectionHeaderOffset) + Output.resize(static_cast<std::size_t>(*SectionHeaderOffset)); + for (const OutputSection &Section : Sections) { + appendU32(Output, Section.NameOffset); + appendU32(Output, Section.Type); + appendU64(Output, Section.Flags); + appendU64(Output, 0); + appendU64(Output, Section.FileOffset); + appendU64(Output, Section.Type == SHTNoBits ? Section.AddressSize : Section.Contents.size()); + appendU32(Output, Section.Link); + appendU32(Output, Section.Info); + appendU64(Output, Section.Alignment); + appendU64(Output, Section.EntrySize); + } + return Output; +} + +} // namespace rcc::mc diff --git a/backend/lib/MC/MCAsmBackend.cpp b/backend/lib/MC/MCAsmBackend.cpp new file mode 100644 index 0000000..bc350b7 --- /dev/null +++ b/backend/lib/MC/MCAsmBackend.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MC/MCAsmBackend.h" + +namespace rcc::mc { + +Expected<std::vector<MCFixupRelocation>> +MCAsmBackend::relocationsForFixup(const MCFixup &Fixup, const MCValue &Value) const { + if (!Value.AddSymbol || Value.SubtractSymbol) { + return makeUnexpected(ErrorCode::RelocationFailed, + "MC backend cannot represent this expression as one relocation"); + } + auto Type = relocationType(Fixup, Value); + if (!Type) + return std::unexpected(Type.error()); + return std::vector<MCFixupRelocation>{ + {.Type = *Type, .Symbol = Value.AddSymbol, .Addend = Value.Addend}}; +} + +Expected<MCInst> MCAsmBackend::relaxInstruction(const MCInst &) const { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "target does not support MC instruction relaxation"); +} + +} // namespace rcc::mc diff --git a/backend/lib/MC/MCAssembler.cpp b/backend/lib/MC/MCAssembler.cpp new file mode 100644 index 0000000..0f92edb --- /dev/null +++ b/backend/lib/MC/MCAssembler.cpp @@ -0,0 +1,849 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MC/MCAssembler.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <optional> +#include <utility> + +namespace rcc::mc { +namespace { + +[[nodiscard]] bool isPowerOfTwo(std::uint64_t Value) noexcept { + return Value != 0 && (Value & (Value - 1U)) == 0; +} + +[[nodiscard]] Expected<std::uint64_t> checkedAdd(std::uint64_t Left, std::uint64_t Right, + std::string_view Message) { + std::uint64_t Result = 0; + if (__builtin_add_overflow(Left, Right, &Result)) + return makeUnexpected(ErrorCode::ObjectWritingFailed, std::string(Message)); + return Result; +} + +[[nodiscard]] Expected<std::int64_t> checkedAdd(std::int64_t Left, std::int64_t Right, + std::string_view Message) { + std::int64_t Result = 0; + if (__builtin_add_overflow(Left, Right, &Result)) + return makeUnexpected(ErrorCode::RelocationFailed, std::string(Message)); + return Result; +} + +[[nodiscard]] Expected<std::int64_t> checkedSubtract(std::int64_t Left, std::int64_t Right, + std::string_view Message) { + std::int64_t Result = 0; + if (__builtin_sub_overflow(Left, Right, &Result)) + return makeUnexpected(ErrorCode::RelocationFailed, std::string(Message)); + return Result; +} + +[[nodiscard]] Expected<std::int64_t> signedOffset(std::uint64_t Value, std::string_view Message) { + if (Value > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) + return makeUnexpected(ErrorCode::RelocationFailed, std::string(Message)); + return static_cast<std::int64_t>(Value); +} + +[[nodiscard]] Expected<std::uint64_t> alignmentPadding(std::uint64_t Offset, + std::uint64_t Alignment) { + if (!isPowerOfTwo(Alignment)) + return makeUnexpected(ErrorCode::InvalidArgument, "MC alignment is not a power of two"); + const std::uint64_t Mask = Alignment - 1U; + auto Adjusted = checkedAdd(Offset, Mask, "MC section alignment overflows"); + if (!Adjusted) + return std::unexpected(Adjusted.error()); + return (*Adjusted & ~Mask) - Offset; +} + +[[nodiscard]] MCFixupKind dataFixupKind(std::uint8_t Size) noexcept { + switch (Size) { + case 1: + return fixup_kind::Data1; + case 2: + return fixup_kind::Data2; + case 4: + return fixup_kind::Data4; + case 8: + return fixup_kind::Data8; + default: + return fixup_kind::None; + } +} + +void appendLittleEndian(std::vector<std::uint8_t> &Bytes, std::uint64_t Value, std::uint8_t Size) { + for (std::uint8_t Index = 0; Index < Size; ++Index) + Bytes.push_back(static_cast<std::uint8_t>(Value >> (Index * 8U))); +} + +[[nodiscard]] bool isNoBits(const MCSection &Section) noexcept { + return Section.description().Kind == MCSectionKind::BSS || + Section.description().Kind == MCSectionKind::TLSBSS; +} + +} // namespace + +Expected<std::size_t> MCAssembler::currentSectionIndex() const { + if (WasAssembled) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC assembler is already finalized"); + if (!CurrentSection) + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "object emission requires an active MC section"); + return *CurrentSection; +} + +MCAssembler::SymbolData &MCAssembler::getOrCreateSymbol(MCSymbolRef Symbol) { + const auto Position = SymbolIndices.find(Symbol.get()); + if (Position != SymbolIndices.end()) + return Symbols[Position->second]; + const std::size_t Index = Symbols.size(); + Symbols.push_back(SymbolData{.Symbol = std::move(Symbol), + .Binding = MCObjectSymbolBinding::Local, + .BindingWasSet = false, + .Type = MCObjectSymbolType::None, + .Visibility = MCObjectSymbolVisibility::Default, + .SectionIndex = std::nullopt, + .FragmentIndex = 0, + .Size = {}, + .CommonSize = 0, + .CommonAlignment = 0}); + SymbolIndices.emplace(Symbols.back().Symbol.get(), Index); + return Symbols.back(); +} + +std::optional<std::uint64_t> MCAssembler::symbolOffset(const SymbolData &Symbol) const { + if (!Symbol.SectionIndex || *Symbol.SectionIndex >= Sections.size()) + return std::nullopt; + const SectionData &Section = Sections[*Symbol.SectionIndex]; + if (Symbol.FragmentIndex > Section.Fragments.size()) + return std::nullopt; + if (Symbol.FragmentIndex == Section.Fragments.size()) + return Section.AddressSize; + return Section.Fragments[Symbol.FragmentIndex].Offset; +} + +Expected<void> MCAssembler::switchSection(MCSectionRef Section) { + if (WasAssembled) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC assembler is already finalized"); + if (!Section) + return makeUnexpected(ErrorCode::InvalidArgument, "cannot switch to a null MC section"); + auto Verification = Section->description().verify(); + if (!Verification) + return std::unexpected(Verification.error()); + const auto Existing = SectionIndices.find(Section.get()); + if (Existing != SectionIndices.end()) { + CurrentSection = Existing->second; + return {}; + } + const std::size_t Index = Sections.size(); + const std::uint64_t Alignment = Section->description().Alignment; + Sections.push_back(SectionData{ + .Section = std::move(Section), .Fragments = {}, .AddressSize = 0, .Alignment = Alignment}); + SectionIndices.emplace(Sections.back().Section.get(), Index); + CurrentSection = Index; + return {}; +} + +Expected<void> MCAssembler::emitLabel(MCSymbolRef Symbol) { + auto SectionIndex = currentSectionIndex(); + if (!SectionIndex) + return std::unexpected(SectionIndex.error()); + if (!Symbol) + return makeUnexpected(ErrorCode::InvalidArgument, "cannot emit a null MC symbol label"); + SymbolData &Data = getOrCreateSymbol(std::move(Symbol)); + if (Data.SectionIndex || Data.CommonSize != 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC symbol is defined more than once"); + } + Data.SectionIndex = *SectionIndex; + Data.FragmentIndex = Sections[*SectionIndex].Fragments.size(); + return {}; +} + +Expected<void> MCAssembler::emitInstruction(const MCInst &Instruction) { + auto SectionIndex = currentSectionIndex(); + if (!SectionIndex) + return std::unexpected(SectionIndex.error()); + if (!Instruction.opcode().isValid()) + return makeUnexpected(ErrorCode::InvalidArgument, "cannot encode an invalid MC opcode"); + Sections[*SectionIndex].Fragments.push_back( + Fragment{.Payload = InstructionFragment{.Instruction = Instruction}, + .Offset = 0, + .Size = 0, + .EncodedBytes = {}, + .Fixups = {}}); + return {}; +} + +Expected<void> MCAssembler::emitValue(MCExprRef Value, std::uint8_t Size) { + auto SectionIndex = currentSectionIndex(); + if (!SectionIndex) + return std::unexpected(SectionIndex.error()); + const MCFixupKind Kind = dataFixupKind(Size); + if (!Value || Kind == fixup_kind::None) { + return makeUnexpected(ErrorCode::InvalidArgument, + "MC value requires an expression and a supported size"); + } + DataFragment Data; + if (const auto Absolute = Value->evaluateAbsolute()) { + appendLittleEndian(Data.Bytes, static_cast<std::uint64_t>(*Absolute), Size); + } else { + Data.Bytes.resize(Size); + Data.Fixups.push_back(MCFixup{.Offset = 0, .Value = std::move(Value), .Kind = Kind}); + } + Sections[*SectionIndex].Fragments.push_back(Fragment{ + .Payload = std::move(Data), .Offset = 0, .Size = 0, .EncodedBytes = {}, .Fixups = {}}); + return {}; +} + +Expected<void> MCAssembler::emitBytes(std::span<const std::uint8_t> Bytes) { + auto SectionIndex = currentSectionIndex(); + if (!SectionIndex) + return std::unexpected(SectionIndex.error()); + if (Bytes.empty()) + return {}; + Sections[*SectionIndex].Fragments.push_back( + Fragment{.Payload = DataFragment{.Bytes = {Bytes.begin(), Bytes.end()}, .Fixups = {}}, + .Offset = 0, + .Size = 0, + .EncodedBytes = {}, + .Fixups = {}}); + return {}; +} + +Expected<void> MCAssembler::emitZeroFill(std::uint64_t Size) { + auto SectionIndex = currentSectionIndex(); + if (!SectionIndex) + return std::unexpected(SectionIndex.error()); + if (Size == 0) + return {}; + Sections[*SectionIndex].Fragments.push_back(Fragment{.Payload = ZeroFillFragment{.Size = Size}, + .Offset = 0, + .Size = 0, + .EncodedBytes = {}, + .Fixups = {}}); + return {}; +} + +Expected<void> MCAssembler::emitAlignment(std::uint64_t Alignment, std::uint8_t Fill, + std::uint64_t MaximumBytes) { + auto SectionIndex = currentSectionIndex(); + if (!SectionIndex) + return std::unexpected(SectionIndex.error()); + if (!isPowerOfTwo(Alignment)) + return makeUnexpected(ErrorCode::InvalidArgument, "MC alignment is not a power of two"); + Sections[*SectionIndex].Fragments.push_back(Fragment{ + .Payload = + AlignmentFragment{.Alignment = Alignment, .Fill = Fill, .MaximumBytes = MaximumBytes}, + .Offset = 0, + .Size = 0, + .EncodedBytes = {}, + .Fixups = {}}); + return {}; +} + +Expected<void> MCAssembler::emitSymbolAttribute(MCSymbolRef Symbol, MCSymbolAttribute Attribute) { + if (WasAssembled) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC assembler is already finalized"); + if (!Symbol) + return makeUnexpected(ErrorCode::InvalidArgument, "MC symbol attribute has no symbol"); + SymbolData &Data = getOrCreateSymbol(std::move(Symbol)); + switch (Attribute) { + case MCSymbolAttribute::Global: + Data.Binding = MCObjectSymbolBinding::Global; + Data.BindingWasSet = true; + break; + case MCSymbolAttribute::Weak: + Data.Binding = MCObjectSymbolBinding::Weak; + Data.BindingWasSet = true; + break; + case MCSymbolAttribute::Local: + Data.Binding = MCObjectSymbolBinding::Local; + Data.BindingWasSet = true; + break; + case MCSymbolAttribute::Hidden: + Data.Visibility = MCObjectSymbolVisibility::Hidden; + break; + case MCSymbolAttribute::Protected: + Data.Visibility = MCObjectSymbolVisibility::Protected; + break; + case MCSymbolAttribute::Function: + Data.Type = MCObjectSymbolType::Function; + break; + case MCSymbolAttribute::Object: + Data.Type = MCObjectSymbolType::Object; + break; + case MCSymbolAttribute::TLSObject: + Data.Type = MCObjectSymbolType::TLSObject; + break; + } + return {}; +} + +Expected<void> MCAssembler::emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) { + if (WasAssembled) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC assembler is already finalized"); + if (!Symbol || !Size) + return makeUnexpected(ErrorCode::InvalidArgument, "MC symbol size is incomplete"); + SymbolData &Data = getOrCreateSymbol(std::move(Symbol)); + if (Data.Size) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC symbol size is defined twice"); + Data.Size = std::move(Size); + return {}; +} + +Expected<void> MCAssembler::emitCommonSymbol(MCSymbolRef Symbol, std::uint64_t Size, + std::uint64_t Alignment) { + if (WasAssembled) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC assembler is already finalized"); + if (!Symbol || Size == 0 || !isPowerOfTwo(Alignment)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "common symbol requires a size and power-of-two alignment"); + } + SymbolData &Data = getOrCreateSymbol(std::move(Symbol)); + if (Data.SectionIndex || Data.CommonSize != 0) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "common symbol is defined twice"); + Data.CommonSize = Size; + Data.CommonAlignment = Alignment; + if (Data.Binding == MCObjectSymbolBinding::Local) + Data.Binding = MCObjectSymbolBinding::Global; + if (Data.Type == MCObjectSymbolType::None) + Data.Type = MCObjectSymbolType::Object; + return {}; +} + +Expected<void> MCAssembler::configureDwarfFrame(MCSectionRef Section, MCDwarfFrameInfo Info) { + if (WasAssembled || !CFIProcedures.empty() || DwarfFrameInfo) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF frame emission is already configured or active"); + } + if (!Section || Section->description().Kind != MCSectionKind::EHFrame) { + return makeUnexpected(ErrorCode::InvalidArgument, + "DWARF frame emission requires an EHFrame section"); + } + auto SectionVerification = Section->description().verify(); + if (!SectionVerification) + return std::unexpected(SectionVerification.error()); + auto InfoVerification = Info.verify(); + if (!InfoVerification) + return std::unexpected(InfoVerification.error()); + EHFrameSection = std::move(Section); + DwarfFrameInfo = Info; + return {}; +} + +Expected<void> MCAssembler::emitCFI(const MCCFIInstruction &Instruction) { + if (WasAssembled) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC assembler is already finalized"); + auto Verification = Instruction.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + auto SectionIndex = currentSectionIndex(); + if (!SectionIndex) + return std::unexpected(SectionIndex.error()); + const std::size_t FragmentIndex = Sections[*SectionIndex].Fragments.size(); + + if (Instruction.Operation == MCCFIOperation::StartProc) { + if (ActiveCFIProcedure) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "nested CFI procedures are not permitted"); + } + MCSymbolRef Function; + for (auto Position = Symbols.rbegin(); Position != Symbols.rend(); ++Position) { + if (Position->SectionIndex == SectionIndex && Position->FragmentIndex == FragmentIndex && + Position->Symbol && Position->Symbol->kind() == MCSymbolKind::Function) { + Function = Position->Symbol; + break; + } + } + if (!Function) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "CFI procedure does not begin at a function symbol"); + } + CFIProcedures.push_back(CFIProcedure{.Function = std::move(Function), + .SectionIndex = *SectionIndex, + .BeginFragmentIndex = FragmentIndex, + .EndFragmentIndex = 0, + .Events = {}, + .IsClosed = false}); + ActiveCFIProcedure = CFIProcedures.size() - 1U; + return {}; + } + + if (!ActiveCFIProcedure) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "CFI event appears outside a procedure"); + } + CFIProcedure &Procedure = CFIProcedures[*ActiveCFIProcedure]; + if (Procedure.SectionIndex != *SectionIndex) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "CFI procedure changes sections before it ends"); + } + if (Instruction.Operation == MCCFIOperation::EndProc) { + Procedure.EndFragmentIndex = FragmentIndex; + Procedure.IsClosed = true; + ActiveCFIProcedure.reset(); + return {}; + } + if (Instruction.Operation == MCCFIOperation::Label) { + return emitLabel(Instruction.Symbol); + } + Procedure.Events.push_back(CFIEvent{.Instruction = Instruction, .FragmentIndex = FragmentIndex}); + return {}; +} + +Expected<void> MCAssembler::encodeFragments() { + for (SectionData &Section : Sections) { + for (Fragment &Fragment : Section.Fragments) { + Fragment.EncodedBytes.clear(); + Fragment.Fixups.clear(); + Fragment.Size = 0; + if (auto *Instruction = std::get_if<InstructionFragment>(&Fragment.Payload)) { + auto Encoded = Emitter->encodeInstruction(Instruction->Instruction); + if (!Encoded) + return std::unexpected(Encoded.error()); + Fragment.EncodedBytes = std::move(Encoded->Bytes); + Fragment.Fixups = std::move(Encoded->Fixups); + } else if (auto *Data = std::get_if<DataFragment>(&Fragment.Payload)) { + Fragment.EncodedBytes = Data->Bytes; + Fragment.Fixups = Data->Fixups; + } + Fragment.Size = Fragment.EncodedBytes.size(); + for (const MCFixup &Fixup : Fragment.Fixups) { + auto Verification = Fixup.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + auto KindInfo = Backend->fixupKindInfo(Fixup.Kind); + if (!KindInfo) + return std::unexpected(KindInfo.error()); + if (Fixup.PCRelative != KindInfo->PCRelative) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "MC fixup PC-relative state disagrees with its kind"); + } + if (Fixup.Offset > Fragment.EncodedBytes.size() || + KindInfo->StorageSize > + Fragment.EncodedBytes.size() - static_cast<std::size_t>(Fixup.Offset)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "MC fixup exceeds its encoded fragment"); + } + } + } + } + return {}; +} + +Expected<bool> MCAssembler::layoutAndRelax() { + std::size_t RelaxableInstructionCount = 0; + for (const SectionData &Section : Sections) { + for (const Fragment &Fragment : Section.Fragments) { + const auto *Instruction = std::get_if<InstructionFragment>(&Fragment.Payload); + if (Instruction != nullptr && Backend->mayNeedRelaxation(Instruction->Instruction)) { + if (RelaxableInstructionCount == std::numeric_limits<std::size_t>::max()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "MC relaxation budget exceeds the host limit"); + } + ++RelaxableInstructionCount; + } + } + } + const std::size_t MaximumIterations = RelaxableInstructionCount + 1U; + for (std::size_t Iteration = 0; Iteration < MaximumIterations; ++Iteration) { + auto Encoded = encodeFragments(); + if (!Encoded) + return std::unexpected(Encoded.error()); + + for (SectionData &Section : Sections) { + std::uint64_t Offset = 0; + Section.Alignment = Section.Section->description().Alignment; + const bool NoBits = isNoBits(*Section.Section); + for (Fragment &Fragment : Section.Fragments) { + Fragment.Offset = Offset; + std::uint64_t Size = Fragment.Size; + if (const auto *Alignment = std::get_if<AlignmentFragment>(&Fragment.Payload)) { + auto Padding = alignmentPadding(Offset, Alignment->Alignment); + if (!Padding) + return std::unexpected(Padding.error()); + Size = Alignment->MaximumBytes != 0 && *Padding > Alignment->MaximumBytes ? 0 : *Padding; + Section.Alignment = std::max(Section.Alignment, Alignment->Alignment); + if (NoBits) + Fragment.EncodedBytes.clear(); + else + Fragment.EncodedBytes.assign(static_cast<std::size_t>(Size), Alignment->Fill); + } else if (const auto *Zero = std::get_if<ZeroFillFragment>(&Fragment.Payload)) { + Size = Zero->Size; + if (!NoBits && Size > std::numeric_limits<std::size_t>::max()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "zero-fill fragment exceeds the host size limit"); + } + if (NoBits) + Fragment.EncodedBytes.clear(); + else + Fragment.EncodedBytes.assign(static_cast<std::size_t>(Size), 0); + } else if (NoBits) { + if (std::holds_alternative<InstructionFragment>(Fragment.Payload) || + !Fragment.Fixups.empty() || + std::ranges::any_of(Fragment.EncodedBytes, + [](std::uint8_t Byte) { return Byte != 0; })) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "MC NOBITS section contains initialized data"); + } + Fragment.EncodedBytes.clear(); + } + Fragment.Size = Size; + auto End = checkedAdd(Offset, Size, "MC section size overflows"); + if (!End) + return std::unexpected(End.error()); + Offset = *End; + } + Section.AddressSize = Offset; + } + + bool Changed = false; + for (SectionData &Section : Sections) { + for (Fragment &Fragment : Section.Fragments) { + auto *Instruction = std::get_if<InstructionFragment>(&Fragment.Payload); + if (Instruction == nullptr || !Backend->mayNeedRelaxation(Instruction->Instruction)) + continue; + for (const MCFixup &Fixup : Fragment.Fixups) { + auto Value = evaluateAsRelocatable(*Fixup.Value); + if (!Value) + return std::unexpected(Value.error()); + if (!Value->AddSymbol || Value->SubtractSymbol || Value->TargetKind != 0) + continue; + const auto SymbolPosition = SymbolIndices.find(Value->AddSymbol.get()); + if (SymbolPosition == SymbolIndices.end()) + continue; + const SymbolData &Target = Symbols[SymbolPosition->second]; + if (!Target.SectionIndex || *Target.SectionIndex != SectionIndices[Section.Section.get()]) + continue; + const auto TargetOffset = symbolOffset(Target); + if (!TargetOffset) + continue; + auto PlaceOffset = checkedAdd(Fragment.Offset, Fixup.Offset, "MC fixup place overflows"); + if (!PlaceOffset) + return std::unexpected(PlaceOffset.error()); + auto Place = signedOffset(*PlaceOffset, "MC fixup place exceeds the signed range"); + auto TargetPosition = + signedOffset(*TargetOffset, "MC relaxation target exceeds the signed range"); + if (!Place) + return std::unexpected(Place.error()); + if (!TargetPosition) + return std::unexpected(TargetPosition.error()); + auto WithAddend = + checkedAdd(*TargetPosition, Value->Addend, "MC relaxation target addend overflows"); + if (!WithAddend) + return std::unexpected(WithAddend.error()); + auto Displacement = + checkedSubtract(*WithAddend, *Place, "MC relaxation displacement overflows"); + if (!Displacement) + return std::unexpected(Displacement.error()); + auto NeedsRelaxation = Backend->fixupNeedsRelaxation(Fixup, *Displacement); + if (!NeedsRelaxation) + return std::unexpected(NeedsRelaxation.error()); + if (!*NeedsRelaxation) + continue; + auto Relaxed = Backend->relaxInstruction(Instruction->Instruction); + if (!Relaxed) + return std::unexpected(Relaxed.error()); + Instruction->Instruction = std::move(*Relaxed); + Changed = true; + break; + } + } + } + if (!Changed) + return false; + } + return makeUnexpected(ErrorCode::InternalError, "MC relaxation did not reach a fixed point"); +} + +Expected<MCAssembledObject> MCAssembler::finalizeObject() { + for (SectionData &Section : Sections) { + for (Fragment &Fragment : Section.Fragments) { + for (const MCFixup &Fixup : Fragment.Fixups) { + auto Value = evaluateAsRelocatable(*Fixup.Value); + if (!Value) + return std::unexpected(Value.error()); + if (Value->AddSymbol) + static_cast<void>(getOrCreateSymbol(Value->AddSymbol)); + if (Value->SubtractSymbol) + static_cast<void>(getOrCreateSymbol(Value->SubtractSymbol)); + } + } + } + + MCAssembledObject Result; + Result.Flags = Backend->objectFlags(); + Result.Sections.reserve(Sections.size()); + for (const SectionData &Section : Sections) { + MCObjectSection Output{.Section = Section.Section, + .Contents = {}, + .AddressSize = Section.AddressSize, + .Alignment = Section.Alignment, + .Relocations = {}}; + if (!isNoBits(*Section.Section) && + Section.AddressSize > std::numeric_limits<std::size_t>::max()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "MC section exceeds the host size limit"); + } + if (!isNoBits(*Section.Section)) { + Output.Contents.reserve(static_cast<std::size_t>(Section.AddressSize)); + for (const Fragment &Fragment : Section.Fragments) + Output.Contents.insert(Output.Contents.end(), Fragment.EncodedBytes.begin(), + Fragment.EncodedBytes.end()); + } + Result.Sections.push_back(std::move(Output)); + } + + Result.Symbols.reserve(Symbols.size()); + for (const SymbolData &Data : Symbols) { + if (!Data.SectionIndex && Data.CommonSize == 0 && Data.Symbol->isTemporary()) { + return makeUnexpected(ErrorCode::RelocationFailed, + "temporary MC symbol is referenced but not defined"); + } + const MCObjectSymbolBinding Binding = + !Data.BindingWasSet && !Data.SectionIndex && Data.CommonSize == 0 + ? MCObjectSymbolBinding::Global + : Data.Binding; + MCObjectSymbol Output{.Symbol = Data.Symbol, + .Binding = Binding, + .Type = Data.Type, + .Visibility = Data.Visibility, + .SectionIndex = Data.SectionIndex, + .CommonAlignment = Data.CommonAlignment}; + if (Data.CommonSize != 0) { + Output.Size = Data.CommonSize; + Output.Value = Data.CommonAlignment; + } else if (const auto Offset = symbolOffset(Data)) { + Output.Value = *Offset; + } + if (Data.Size) { + auto SizeValue = evaluateAsRelocatable(*Data.Size); + if (!SizeValue) + return std::unexpected(SizeValue.error()); + if (SizeValue->TargetKind != 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "symbol size cannot use a target expression"); + } + if (SizeValue->AddSymbol && SizeValue->SubtractSymbol) { + const auto AddPosition = SymbolIndices.find(SizeValue->AddSymbol.get()); + const auto SubtractPosition = SymbolIndices.find(SizeValue->SubtractSymbol.get()); + if (AddPosition == SymbolIndices.end() || SubtractPosition == SymbolIndices.end()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "symbol size is unresolved"); + } + const SymbolData &Add = Symbols[AddPosition->second]; + const SymbolData &Subtract = Symbols[SubtractPosition->second]; + if (!Add.SectionIndex || !Subtract.SectionIndex || + Add.SectionIndex != Subtract.SectionIndex) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "symbol size difference crosses sections"); + } + } + std::int64_t Resolved = SizeValue->Addend; + const auto Resolve = [&](const MCSymbolRef &Symbol) -> Expected<std::int64_t> { + const auto Position = SymbolIndices.find(Symbol.get()); + if (Position == SymbolIndices.end()) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "symbol size is unresolved"); + const auto Offset = symbolOffset(Symbols[Position->second]); + if (!Offset) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "symbol size is unresolved"); + return signedOffset(*Offset, "symbol size offset exceeds the signed range"); + }; + if (SizeValue->AddSymbol) { + auto Offset = Resolve(SizeValue->AddSymbol); + if (!Offset) + return std::unexpected(Offset.error()); + auto Updated = checkedAdd(Resolved, *Offset, "symbol size addition overflows"); + if (!Updated) + return std::unexpected(Updated.error()); + Resolved = *Updated; + } + if (SizeValue->SubtractSymbol) { + auto Offset = Resolve(SizeValue->SubtractSymbol); + if (!Offset) + return std::unexpected(Offset.error()); + auto Updated = checkedSubtract(Resolved, *Offset, "symbol size subtraction overflows"); + if (!Updated) + return std::unexpected(Updated.error()); + Resolved = *Updated; + } + if (Resolved < 0) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "symbol size is negative"); + Output.Size = static_cast<std::uint64_t>(Resolved); + } + if (Output.SectionIndex) { + const std::uint64_t SectionSize = Sections[*Output.SectionIndex].AddressSize; + if (Output.Value > SectionSize || Output.Size > SectionSize - Output.Value) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "symbol range exceeds its defining section"); + } + } + Result.Symbols.push_back(std::move(Output)); + } + + for (std::size_t SectionIndex = 0; SectionIndex < Sections.size(); ++SectionIndex) { + const SectionData &Section = Sections[SectionIndex]; + MCObjectSection &Output = Result.Sections[SectionIndex]; + for (const Fragment &Fragment : Section.Fragments) { + for (const MCFixup &Fixup : Fragment.Fixups) { + auto Value = evaluateAsRelocatable(*Fixup.Value); + if (!Value) + return std::unexpected(Value.error()); + auto PlaceValue = + checkedAdd(Fragment.Offset, Fixup.Offset, "MC relocation offset overflows"); + if (!PlaceValue) + return std::unexpected(PlaceValue.error()); + const std::uint64_t Place = *PlaceValue; + bool Resolved = Value->isAbsolute(); + std::int64_t FixedValue = Value->Addend; + const auto FindSymbol = [&](const MCSymbolRef &Symbol) -> const SymbolData * { + const auto Position = SymbolIndices.find(Symbol.get()); + return Position == SymbolIndices.end() ? nullptr : &Symbols[Position->second]; + }; + const SymbolData *Add = Value->AddSymbol ? FindSymbol(Value->AddSymbol) : nullptr; + const SymbolData *Subtract = + Value->SubtractSymbol ? FindSymbol(Value->SubtractSymbol) : nullptr; + if (Value->TargetKind == 0 && Add != nullptr && Subtract != nullptr && Add->SectionIndex && + Subtract->SectionIndex && Add->SectionIndex == Subtract->SectionIndex) { + const auto AddOffset = symbolOffset(*Add); + const auto SubtractOffset = symbolOffset(*Subtract); + if (AddOffset && SubtractOffset) { + auto SignedAdd = signedOffset(*AddOffset, "MC symbol offset exceeds the signed range"); + auto SignedSubtract = + signedOffset(*SubtractOffset, "MC symbol offset exceeds the signed range"); + if (!SignedAdd) + return std::unexpected(SignedAdd.error()); + if (!SignedSubtract) + return std::unexpected(SignedSubtract.error()); + auto Difference = + checkedSubtract(*SignedAdd, *SignedSubtract, "MC symbol difference overflows"); + if (!Difference) + return std::unexpected(Difference.error()); + auto Updated = checkedAdd(FixedValue, *Difference, "MC resolved fixup value overflows"); + if (!Updated) + return std::unexpected(Updated.error()); + FixedValue = *Updated; + Resolved = true; + } + } else if (Value->TargetKind == 0 && Fixup.PCRelative && Add != nullptr && + !Value->SubtractSymbol && Add->SectionIndex && + *Add->SectionIndex == SectionIndex) { + const auto AddOffset = symbolOffset(*Add); + if (AddOffset) { + auto SignedAdd = signedOffset(*AddOffset, "MC symbol offset exceeds the signed range"); + auto SignedPlace = signedOffset(Place, "MC fixup place exceeds the signed range"); + if (!SignedAdd) + return std::unexpected(SignedAdd.error()); + if (!SignedPlace) + return std::unexpected(SignedPlace.error()); + auto Difference = + checkedSubtract(*SignedAdd, *SignedPlace, "MC PC-relative displacement overflows"); + if (!Difference) + return std::unexpected(Difference.error()); + auto Updated = checkedAdd(FixedValue, *Difference, "MC resolved fixup value overflows"); + if (!Updated) + return std::unexpected(Updated.error()); + FixedValue = *Updated; + Resolved = true; + } + } + + if (Resolved) { + auto Applied = Backend->applyFixup(Fixup.Kind, Output.Contents, Place, FixedValue); + if (!Applied) + return std::unexpected(Applied.error()); + continue; + } + auto Relocations = Backend->relocationsForFixup(Fixup, *Value); + if (!Relocations) + return std::unexpected(Relocations.error()); + for (const MCFixupRelocation &Relocation : *Relocations) { + if (Relocation.Type == 0 || !Relocation.Symbol) { + return makeUnexpected(ErrorCode::RelocationFailed, + "MC backend produced an invalid relocation"); + } + Output.Relocations.push_back(MCObjectRelocation{.Offset = Place, + .Type = Relocation.Type, + .Symbol = Relocation.Symbol, + .Addend = Relocation.Addend}); + } + } + } + } + auto DwarfFrame = appendDwarfFrame(Result); + if (!DwarfFrame) + return std::unexpected(DwarfFrame.error()); + return Result; +} + +Expected<void> MCAssembler::appendDwarfFrame(MCAssembledObject &Object) const { + if (CFIProcedures.empty()) + return {}; + if (!DwarfFrameInfo || !EHFrameSection) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "CFI was emitted without DWARF frame configuration"); + } + if (ActiveCFIProcedure) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "CFI procedure was not terminated"); + } + + std::vector<MCDwarfFrameProcedure> Procedures; + Procedures.reserve(CFIProcedures.size()); + for (const CFIProcedure &Procedure : CFIProcedures) { + if (!Procedure.IsClosed || Procedure.SectionIndex >= Sections.size()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "CFI procedure is incomplete"); + } + const SectionData &Section = Sections[Procedure.SectionIndex]; + const auto FragmentOffset = [&Section](std::size_t Index) -> std::optional<std::uint64_t> { + if (Index > Section.Fragments.size()) + return std::nullopt; + if (Index == Section.Fragments.size()) + return Section.AddressSize; + return Section.Fragments[Index].Offset; + }; + const auto Begin = FragmentOffset(Procedure.BeginFragmentIndex); + const auto End = FragmentOffset(Procedure.EndFragmentIndex); + if (!Begin || !End || *End < *Begin) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "CFI procedure has an invalid address range"); + } + MCDwarfFrameProcedure Output{ + .Function = Procedure.Function, .AddressRange = *End - *Begin, .Instructions = {}}; + Output.Instructions.reserve(Procedure.Events.size()); + for (const CFIEvent &Event : Procedure.Events) { + const auto Offset = FragmentOffset(Event.FragmentIndex); + if (!Offset || *Offset < *Begin || *Offset > *End) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "CFI event lies outside its procedure"); + } + Output.Instructions.push_back(MCDwarfFrameInstruction{ + .Instruction = Event.Instruction, + .Offset = *Offset - *Begin, + }); + } + Procedures.push_back(std::move(Output)); + } + auto Frame = emitDwarfEHFrame(EHFrameSection, Procedures, *DwarfFrameInfo, *Backend); + if (!Frame) + return std::unexpected(Frame.error()); + Object.Sections.push_back(std::move(*Frame)); + return {}; +} + +Expected<MCAssembledObject> MCAssembler::assemble() { + if (WasAssembled) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "MC assembler was already finalized"); + if (ActiveCFIProcedure) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "CFI procedure was not terminated"); + } + MCAssembler Working = *this; + auto Relaxed = Working.layoutAndRelax(); + if (!Relaxed) + return std::unexpected(Relaxed.error()); + auto Result = Working.finalizeObject(); + if (!Result) + return std::unexpected(Result.error()); + WasAssembled = true; + return Result; +} + +} // namespace rcc::mc diff --git a/backend/lib/MC/MCDwarfFrameEmitter.cpp b/backend/lib/MC/MCDwarfFrameEmitter.cpp new file mode 100644 index 0000000..581c8cb --- /dev/null +++ b/backend/lib/MC/MCDwarfFrameEmitter.cpp @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MC/MCDwarfFrameEmitter.h" + +#include "rcc/MC/MCExpr.h" +#include "rcc/MC/MCFixup.h" + +#include <cstddef> +#include <cstdint> +#include <limits> +#include <string> +#include <utility> +#include <vector> + +namespace rcc::mc { +namespace { + +constexpr std::uint8_t DWCFAAdvanceLoc = 0x40; +constexpr std::uint8_t DWCFAOffset = 0x80; +constexpr std::uint8_t DWCFARestore = 0xc0; +constexpr std::uint8_t DWCFANop = 0x00; +constexpr std::uint8_t DWCFAAdvanceLoc1 = 0x02; +constexpr std::uint8_t DWCFAAdvanceLoc2 = 0x03; +constexpr std::uint8_t DWCFAAdvanceLoc4 = 0x04; +constexpr std::uint8_t DWCFAOffsetExtended = 0x05; +constexpr std::uint8_t DWCFARestoreExtended = 0x06; +constexpr std::uint8_t DWCFAUndefined = 0x07; +constexpr std::uint8_t DWCFASameValue = 0x08; +constexpr std::uint8_t DWCFARegister = 0x09; +constexpr std::uint8_t DWCFARememberState = 0x0a; +constexpr std::uint8_t DWCFARestoreState = 0x0b; +constexpr std::uint8_t DWCFADefCFA = 0x0c; +constexpr std::uint8_t DWCFADefCFARegister = 0x0d; +constexpr std::uint8_t DWCFADefCFAOffset = 0x0e; +constexpr std::uint8_t DWCFAValOffset = 0x14; +constexpr std::uint8_t DWCFAGNUWindowSave = 0x2d; +constexpr std::uint8_t DWCFAGNUArgsSize = 0x2e; +constexpr std::uint8_t DWEHPEPCRelativeSData4 = 0x1b; + +void appendU16(std::vector<std::uint8_t> &Bytes, std::uint16_t Value) { + Bytes.push_back(static_cast<std::uint8_t>(Value)); + Bytes.push_back(static_cast<std::uint8_t>(Value >> 8U)); +} + +void appendU32(std::vector<std::uint8_t> &Bytes, std::uint32_t Value) { + for (std::uint8_t Shift = 0; Shift < 32; Shift += 8) + Bytes.push_back(static_cast<std::uint8_t>(Value >> Shift)); +} + +void writeU32(std::vector<std::uint8_t> &Bytes, std::size_t Offset, std::uint32_t Value) { + for (std::uint8_t Shift = 0; Shift < 32; Shift += 8) + Bytes[Offset + Shift / 8U] = static_cast<std::uint8_t>(Value >> Shift); +} + +void appendULEB128(std::vector<std::uint8_t> &Bytes, std::uint64_t Value) { + do { + std::uint8_t Byte = static_cast<std::uint8_t>(Value & 0x7fU); + Value >>= 7U; + if (Value != 0) + Byte |= 0x80U; + Bytes.push_back(Byte); + } while (Value != 0); +} + +void appendSLEB128(std::vector<std::uint8_t> &Bytes, std::int64_t Value) { + bool More = true; + while (More) { + std::uint8_t Byte = static_cast<std::uint8_t>(Value & 0x7f); + const bool HasSign = (Byte & 0x40U) != 0; + Value >>= 7; + More = (Value != 0 || HasSign) && (Value != -1 || !HasSign); + if (More) + Byte |= 0x80U; + Bytes.push_back(Byte); + } +} + +[[nodiscard]] Expected<void> appendAdvance(std::vector<std::uint8_t> &Bytes, std::uint64_t Delta, + const MCDwarfFrameInfo &Info) { + if (Delta == 0) + return {}; + if (Delta % Info.CodeAlignment != 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "CFI location is not aligned to the DWARF code factor"); + } + const std::uint64_t Factored = Delta / Info.CodeAlignment; + if (Factored <= 0x3fU) { + Bytes.push_back(static_cast<std::uint8_t>(DWCFAAdvanceLoc | Factored)); + } else if (Factored <= std::numeric_limits<std::uint8_t>::max()) { + Bytes.push_back(DWCFAAdvanceLoc1); + Bytes.push_back(static_cast<std::uint8_t>(Factored)); + } else if (Factored <= std::numeric_limits<std::uint16_t>::max()) { + Bytes.push_back(DWCFAAdvanceLoc2); + appendU16(Bytes, static_cast<std::uint16_t>(Factored)); + } else if (Factored <= std::numeric_limits<std::uint32_t>::max()) { + Bytes.push_back(DWCFAAdvanceLoc4); + appendU32(Bytes, static_cast<std::uint32_t>(Factored)); + } else { + return makeUnexpected(ErrorCode::Unsupported, "DWARF CFI location delta exceeds 32 bits"); + } + return {}; +} + +[[nodiscard]] Expected<std::uint64_t> registerNumber(std::int64_t Value) { + if (Value < 0 || static_cast<std::uint64_t>(Value) > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF CFI register number is out of range"); + } + return static_cast<std::uint64_t>(Value); +} + +[[nodiscard]] Expected<std::uint64_t> factoredOffset(std::int64_t Offset, + std::int64_t DataAlignment) { + if (Offset == std::numeric_limits<std::int64_t>::min() && DataAlignment == -1) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, "DWARF CFI offset factor overflows"); + } + if (Offset % DataAlignment != 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF CFI offset is not divisible by its data factor"); + } + const std::int64_t Factored = Offset / DataAlignment; + if (Factored < 0) { + return makeUnexpected(ErrorCode::Unsupported, "DWARF CFI rule requires a signed offset opcode"); + } + return static_cast<std::uint64_t>(Factored); +} + +void appendOffsetRule(std::vector<std::uint8_t> &Bytes, std::uint64_t Register, + std::uint64_t Offset) { + if (Register <= 0x3fU) { + Bytes.push_back(static_cast<std::uint8_t>(DWCFAOffset | Register)); + } else { + Bytes.push_back(DWCFAOffsetExtended); + appendULEB128(Bytes, Register); + } + appendULEB128(Bytes, Offset); +} + +void appendRestoreRule(std::vector<std::uint8_t> &Bytes, std::uint64_t Register) { + if (Register <= 0x3fU) { + Bytes.push_back(static_cast<std::uint8_t>(DWCFARestore | Register)); + } else { + Bytes.push_back(DWCFARestoreExtended); + appendULEB128(Bytes, Register); + } +} + +struct CFAState final { + std::uint64_t Register = 0; + std::int64_t Offset = 0; +}; + +[[nodiscard]] Expected<void> +appendFrameInstructions(std::vector<std::uint8_t> &Bytes, + std::span<const MCDwarfFrameInstruction> Instructions, + const MCDwarfFrameInfo &Info) { + CFAState State{.Register = Info.InitialCFARegister, + .Offset = static_cast<std::int64_t>(Info.InitialCFAOffset)}; + std::vector<CFAState> StateStack; + std::uint64_t PreviousOffset = 0; + for (const MCDwarfFrameInstruction &Entry : Instructions) { + auto Verification = Entry.Instruction.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + if (Entry.Offset < PreviousOffset) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF CFI locations are not monotonic"); + } + if (Entry.Instruction.Operation == MCCFIOperation::Label) + continue; + auto Advance = appendAdvance(Bytes, Entry.Offset - PreviousOffset, Info); + if (!Advance) + return std::unexpected(Advance.error()); + PreviousOffset = Entry.Offset; + + const auto &Operands = Entry.Instruction.Operands; + switch (Entry.Instruction.Operation) { + case MCCFIOperation::SameValue: { + auto Register = registerNumber(Operands[0]); + if (!Register) + return std::unexpected(Register.error()); + Bytes.push_back(DWCFASameValue); + appendULEB128(Bytes, *Register); + break; + } + case MCCFIOperation::RememberState: + Bytes.push_back(DWCFARememberState); + StateStack.push_back(State); + break; + case MCCFIOperation::RestoreState: + if (StateStack.empty()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF CFI restore_state has no saved state"); + } + Bytes.push_back(DWCFARestoreState); + State = StateStack.back(); + StateStack.pop_back(); + break; + case MCCFIOperation::Offset: + case MCCFIOperation::RelativeOffset: { + auto Register = registerNumber(Operands[0]); + if (!Register) + return std::unexpected(Register.error()); + std::int64_t Offset = Operands[1]; + if (Entry.Instruction.Operation == MCCFIOperation::RelativeOffset && + __builtin_sub_overflow(Offset, State.Offset, &Offset)) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "relative DWARF CFI offset overflows"); + } + auto Factored = factoredOffset(Offset, Info.DataAlignment); + if (!Factored) + return std::unexpected(Factored.error()); + appendOffsetRule(Bytes, *Register, *Factored); + break; + } + case MCCFIOperation::DefCFARegister: { + auto Register = registerNumber(Operands[0]); + if (!Register) + return std::unexpected(Register.error()); + Bytes.push_back(DWCFADefCFARegister); + appendULEB128(Bytes, *Register); + State.Register = *Register; + break; + } + case MCCFIOperation::DefCFAOffset: + if (Operands[0] < 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF CFA offset cannot be negative"); + } + Bytes.push_back(DWCFADefCFAOffset); + appendULEB128(Bytes, static_cast<std::uint64_t>(Operands[0])); + State.Offset = Operands[0]; + break; + case MCCFIOperation::DefCFA: { + auto Register = registerNumber(Operands[0]); + if (!Register) + return std::unexpected(Register.error()); + if (Operands[1] < 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF CFA offset cannot be negative"); + } + Bytes.push_back(DWCFADefCFA); + appendULEB128(Bytes, *Register); + appendULEB128(Bytes, static_cast<std::uint64_t>(Operands[1])); + State = CFAState{.Register = *Register, .Offset = Operands[1]}; + break; + } + case MCCFIOperation::AdjustCFAOffset: { + std::int64_t Adjusted = 0; + if (__builtin_add_overflow(State.Offset, Operands[0], &Adjusted) || Adjusted < 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "adjusted DWARF CFA offset is invalid"); + } + Bytes.push_back(DWCFADefCFAOffset); + appendULEB128(Bytes, static_cast<std::uint64_t>(Adjusted)); + State.Offset = Adjusted; + break; + } + case MCCFIOperation::Escape: + Bytes.insert(Bytes.end(), Entry.Instruction.Bytes.begin(), Entry.Instruction.Bytes.end()); + break; + case MCCFIOperation::Restore: { + auto Register = registerNumber(Operands[0]); + if (!Register) + return std::unexpected(Register.error()); + appendRestoreRule(Bytes, *Register); + break; + } + case MCCFIOperation::Undefined: { + auto Register = registerNumber(Operands[0]); + if (!Register) + return std::unexpected(Register.error()); + Bytes.push_back(DWCFAUndefined); + appendULEB128(Bytes, *Register); + break; + } + case MCCFIOperation::Register: { + auto Register = registerNumber(Operands[0]); + auto Source = registerNumber(Operands[1]); + if (!Register) + return std::unexpected(Register.error()); + if (!Source) + return std::unexpected(Source.error()); + Bytes.push_back(DWCFARegister); + appendULEB128(Bytes, *Register); + appendULEB128(Bytes, *Source); + break; + } + case MCCFIOperation::WindowSave: + Bytes.push_back(DWCFAGNUWindowSave); + break; + case MCCFIOperation::GNUArgsSize: + if (Operands[0] < 0) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF GNU argument size cannot be negative"); + } + Bytes.push_back(DWCFAGNUArgsSize); + appendULEB128(Bytes, static_cast<std::uint64_t>(Operands[0])); + break; + case MCCFIOperation::ValueOffset: { + auto Register = registerNumber(Operands[0]); + if (!Register) + return std::unexpected(Register.error()); + auto Factored = factoredOffset(Operands[1], Info.DataAlignment); + if (!Factored) + return std::unexpected(Factored.error()); + Bytes.push_back(DWCFAValOffset); + appendULEB128(Bytes, *Register); + appendULEB128(Bytes, *Factored); + break; + } + case MCCFIOperation::StartProc: + case MCCFIOperation::EndProc: + case MCCFIOperation::Sections: + case MCCFIOperation::Personality: + case MCCFIOperation::LSDA: + case MCCFIOperation::SignalFrame: + case MCCFIOperation::ReturnColumn: + case MCCFIOperation::NegateReturnAddressState: + case MCCFIOperation::NegateReturnAddressStateWithPC: + case MCCFIOperation::Label: + return makeUnexpected(ErrorCode::Unsupported, + "this CFI operation cannot be encoded in an RV64GC FDE"); + } + } + if (!StateStack.empty()) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF CFI procedure has unbalanced remember_state operations"); + } + return {}; +} + +[[nodiscard]] Expected<void> finishEntry(std::vector<std::uint8_t> &Bytes, std::size_t EntryStart) { + while (Bytes.size() % 4U != 0) + Bytes.push_back(DWCFANop); + const std::size_t Length = Bytes.size() - EntryStart - 4U; + if (Length > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, "DWARF frame entry exceeds 32 bits"); + } + writeU32(Bytes, EntryStart, static_cast<std::uint32_t>(Length)); + return {}; +} + +} // namespace + +Expected<void> MCDwarfFrameInfo::verify() const { + if (CodeAlignment == 0 || DataAlignment == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "DWARF frame alignment factors must be nonzero"); + } + if (InitialCFAOffset > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::InvalidArgument, "initial DWARF CFA offset is too large"); + } + return {}; +} + +Expected<MCObjectSection> emitDwarfEHFrame(MCSectionRef Section, + std::span<const MCDwarfFrameProcedure> Procedures, + const MCDwarfFrameInfo &Info, + const MCAsmBackend &Backend) { + if (!Section || Section->description().Kind != MCSectionKind::EHFrame) { + return makeUnexpected(ErrorCode::InvalidArgument, + "DWARF EH frame emission requires an EHFrame section"); + } + auto Verification = Info.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + + MCObjectSection Result{.Section = std::move(Section), + .Contents = {}, + .AddressSize = 0, + .Alignment = 1, + .Relocations = {}}; + Result.Alignment = Result.Section->description().Alignment; + const std::size_t CIEStart = Result.Contents.size(); + appendU32(Result.Contents, 0); + appendU32(Result.Contents, 0); + Result.Contents.push_back(1); + Result.Contents.insert(Result.Contents.end(), {'z', 'R', 0}); + appendULEB128(Result.Contents, Info.CodeAlignment); + appendSLEB128(Result.Contents, Info.DataAlignment); + appendULEB128(Result.Contents, Info.ReturnAddressRegister); + appendULEB128(Result.Contents, 1); + Result.Contents.push_back(DWEHPEPCRelativeSData4); + Result.Contents.push_back(DWCFADefCFA); + appendULEB128(Result.Contents, Info.InitialCFARegister); + appendULEB128(Result.Contents, Info.InitialCFAOffset); + auto FinishedCIE = finishEntry(Result.Contents, CIEStart); + if (!FinishedCIE) + return std::unexpected(FinishedCIE.error()); + + for (const MCDwarfFrameProcedure &Procedure : Procedures) { + if (!Procedure.Function) { + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "DWARF frame procedure has no function symbol"); + } + if (Procedure.AddressRange > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, "DWARF FDE address range exceeds 32 bits"); + } + const std::size_t FDEStart = Result.Contents.size(); + appendU32(Result.Contents, 0); + const std::size_t CIEPointerOffset = Result.Contents.size(); + const std::size_t CIEDistance = CIEPointerOffset - CIEStart; + if (CIEDistance > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, "DWARF FDE CIE distance exceeds 32 bits"); + } + appendU32(Result.Contents, static_cast<std::uint32_t>(CIEDistance)); + const std::size_t InitialLocationOffset = Result.Contents.size(); + appendU32(Result.Contents, 0); + appendU32(Result.Contents, static_cast<std::uint32_t>(Procedure.AddressRange)); + appendULEB128(Result.Contents, 0); + auto Instructions = appendFrameInstructions(Result.Contents, Procedure.Instructions, Info); + if (!Instructions) + return std::unexpected(Instructions.error()); + auto FinishedFDE = finishEntry(Result.Contents, FDEStart); + if (!FinishedFDE) + return std::unexpected(FinishedFDE.error()); + + auto Expression = MCExpr::symbol(Procedure.Function); + if (!Expression) + return std::unexpected(Expression.error()); + MCFixup Fixup{.Offset = InitialLocationOffset, + .Value = *Expression, + .Kind = fixup_kind::PCRelative4, + .PCRelative = true, + .LinkerRelaxable = false}; + MCValue Value{ + .AddSymbol = Procedure.Function, .SubtractSymbol = {}, .Addend = 0, .TargetKind = 0}; + auto Relocation = Backend.relocationType(Fixup, Value); + if (!Relocation) + return std::unexpected(Relocation.error()); + Result.Relocations.push_back(MCObjectRelocation{ + .Offset = InitialLocationOffset, + .Type = *Relocation, + .Symbol = Procedure.Function, + .Addend = 0, + }); + } + Result.AddressSize = Result.Contents.size(); + return Result; +} + +} // namespace rcc::mc diff --git a/backend/lib/MC/MCFixup.cpp b/backend/lib/MC/MCFixup.cpp new file mode 100644 index 0000000..8819cd4 --- /dev/null +++ b/backend/lib/MC/MCFixup.cpp @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MC/MCFixup.h" + +#include <algorithm> +#include <array> +#include <cstdint> +#include <limits> +#include <utility> +#include <vector> + +namespace rcc::mc { +namespace { + +[[nodiscard]] Expected<std::int64_t> checkedAdd(std::int64_t Left, std::int64_t Right) { + std::int64_t Result = 0; + if (__builtin_add_overflow(Left, Right, &Result)) + return makeUnexpected(ErrorCode::RelocationFailed, "MC expression addition overflows"); + return Result; +} + +[[nodiscard]] Expected<std::int64_t> checkedSubtract(std::int64_t Left, std::int64_t Right) { + std::int64_t Result = 0; + if (__builtin_sub_overflow(Left, Right, &Result)) + return makeUnexpected(ErrorCode::RelocationFailed, "MC expression subtraction overflows"); + return Result; +} + +[[nodiscard]] Expected<MCValue> combineLinearValues(const MCValue &Left, const MCValue &Right, + bool SubtractRight) { + struct SymbolTerm final { + MCSymbolRef Symbol; + int Coefficient = 0; + }; + std::vector<SymbolTerm> Terms; + const auto AddTerm = [&Terms](const MCSymbolRef &Symbol, int Coefficient) -> Expected<void> { + if (!Symbol) + return {}; + const auto Position = std::ranges::find(Terms, Symbol, &SymbolTerm::Symbol); + if (Position == Terms.end()) { + Terms.push_back(SymbolTerm{.Symbol = Symbol, .Coefficient = Coefficient}); + return {}; + } + Position->Coefficient += Coefficient; + if (Position->Coefficient < -1 || Position->Coefficient > 1) { + return makeUnexpected(ErrorCode::RelocationFailed, + "MC expression has a symbol coefficient outside -1 to 1"); + } + if (Position->Coefficient == 0) + Terms.erase(Position); + return {}; + }; + + for (const auto &[Symbol, Coefficient] : std::array<std::pair<MCSymbolRef, int>, 4>{ + std::pair{Left.AddSymbol, 1}, + std::pair{Left.SubtractSymbol, -1}, + std::pair{Right.AddSymbol, SubtractRight ? -1 : 1}, + std::pair{Right.SubtractSymbol, SubtractRight ? 1 : -1}, + }) { + auto Added = AddTerm(Symbol, Coefficient); + if (!Added) + return std::unexpected(Added.error()); + } + + MCValue Result; + for (const SymbolTerm &Term : Terms) { + MCSymbolRef &Destination = Term.Coefficient > 0 ? Result.AddSymbol : Result.SubtractSymbol; + if (Destination) { + return makeUnexpected(ErrorCode::RelocationFailed, + "MC expression contains more than one symbol with the same sign"); + } + Destination = Term.Symbol; + } + auto Addend = SubtractRight ? checkedSubtract(Left.Addend, Right.Addend) + : checkedAdd(Left.Addend, Right.Addend); + if (!Addend) + return std::unexpected(Addend.error()); + Result.Addend = *Addend; + return Result; +} + +[[nodiscard]] Expected<MCValue> evaluate(const MCExpr &Expression, std::size_t Depth) { + if (Depth > 256) + return makeUnexpected(ErrorCode::RelocationFailed, "MC expression exceeds its depth limit"); + + switch (Expression.kind()) { + case MCExpr::Kind::Constant: + return MCValue{.AddSymbol = {}, + .SubtractSymbol = {}, + .Addend = *Expression.getIf<std::int64_t>(), + .TargetKind = 0}; + case MCExpr::Kind::SymbolRef: + return MCValue{.AddSymbol = Expression.getIf<MCSymbolRefExpr>()->Symbol, + .SubtractSymbol = {}, + .Addend = 0, + .TargetKind = 0}; + case MCExpr::Kind::Target: { + const auto &Target = *Expression.getIf<MCTargetExpr>(); + auto Result = evaluate(*Target.Operand, Depth + 1); + if (!Result) + return std::unexpected(Result.error()); + if (Result->TargetKind != 0) { + return makeUnexpected(ErrorCode::RelocationFailed, + "nested target MC expressions are not relocatable"); + } + Result->TargetKind = Target.Kind; + return Result; + } + case MCExpr::Kind::Unary: { + const auto &Unary = *Expression.getIf<MCUnaryExpr>(); + auto Operand = evaluate(*Unary.Operand, Depth + 1); + if (!Operand) + return std::unexpected(Operand.error()); + if (Unary.Operator == MCUnaryOperator::Plus) + return Operand; + if (Unary.Operator == MCUnaryOperator::Minus && Operand->isAbsolute()) { + if (Operand->Addend == std::numeric_limits<std::int64_t>::min()) { + return makeUnexpected(ErrorCode::RelocationFailed, "MC expression negation overflows"); + } + Operand->Addend = -Operand->Addend; + return Operand; + } + const auto Absolute = Expression.evaluateAbsolute(); + if (!Absolute) { + return makeUnexpected(ErrorCode::RelocationFailed, + "symbolic unary MC expression is not relocatable"); + } + return MCValue{.AddSymbol = {}, .SubtractSymbol = {}, .Addend = *Absolute, .TargetKind = 0}; + } + case MCExpr::Kind::Binary: { + const auto &Binary = *Expression.getIf<MCBinaryExpr>(); + if (Binary.Operator != MCBinaryOperator::Add && Binary.Operator != MCBinaryOperator::Subtract) { + const auto Absolute = Expression.evaluateAbsolute(); + if (!Absolute) { + return makeUnexpected(ErrorCode::RelocationFailed, + "symbolic binary MC expression is not relocatable"); + } + return MCValue{.AddSymbol = {}, .SubtractSymbol = {}, .Addend = *Absolute, .TargetKind = 0}; + } + auto Left = evaluate(*Binary.Left, Depth + 1); + auto Right = evaluate(*Binary.Right, Depth + 1); + if (!Left) + return std::unexpected(Left.error()); + if (!Right) + return std::unexpected(Right.error()); + if (Left->TargetKind != 0 || Right->TargetKind != 0) { + return makeUnexpected(ErrorCode::RelocationFailed, + "target MC expression cannot participate in outer arithmetic"); + } + + return combineLinearValues(*Left, *Right, Binary.Operator == MCBinaryOperator::Subtract); + } + } + return makeUnexpected(ErrorCode::InternalError, "MC expression has an invalid kind"); +} + +} // namespace + +Expected<void> MCFixup::verify() const { + if (Kind == fixup_kind::None) + return makeUnexpected(ErrorCode::InvalidArgument, "MC fixup has no kind"); + if (!Value) + return makeUnexpected(ErrorCode::InvalidArgument, "MC fixup has no value expression"); + auto Verification = Value->verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return {}; +} + +Expected<MCValue> evaluateAsRelocatable(const MCExpr &Expression) { + auto Verification = Expression.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return evaluate(Expression, 0); +} + +} // namespace rcc::mc diff --git a/backend/lib/MC/MCObjectStreamer.cpp b/backend/lib/MC/MCObjectStreamer.cpp new file mode 100644 index 0000000..634b713 --- /dev/null +++ b/backend/lib/MC/MCObjectStreamer.cpp @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MC/MCObjectStreamer.h" + +#include <utility> + +namespace rcc::mc { + +Expected<void> ELFObjectStreamer::ensureOpen() const { + if (IsFinished) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "object stream is already finalized"); + return {}; +} + +Expected<void> ELFObjectStreamer::switchSection(MCSectionRef Section) { + auto Open = ensureOpen(); + return Open ? Assembler.switchSection(std::move(Section)) : Open; +} + +Expected<void> ELFObjectStreamer::emitLabel(MCSymbolRef Symbol) { + auto Open = ensureOpen(); + return Open ? Assembler.emitLabel(std::move(Symbol)) : Open; +} + +Expected<void> ELFObjectStreamer::emitInstruction(const MCInst &Instruction) { + auto Open = ensureOpen(); + return Open ? Assembler.emitInstruction(Instruction) : Open; +} + +Expected<void> ELFObjectStreamer::emitValue(MCExprRef Value, std::uint8_t Size) { + auto Open = ensureOpen(); + return Open ? Assembler.emitValue(std::move(Value), Size) : Open; +} + +Expected<void> ELFObjectStreamer::emitBytes(std::span<const std::uint8_t> Bytes) { + auto Open = ensureOpen(); + return Open ? Assembler.emitBytes(Bytes) : Open; +} + +Expected<void> ELFObjectStreamer::emitZeroFill(std::uint64_t Size) { + auto Open = ensureOpen(); + return Open ? Assembler.emitZeroFill(Size) : Open; +} + +Expected<void> ELFObjectStreamer::emitAlignment(std::uint64_t Alignment, std::uint8_t Fill, + std::uint64_t MaximumBytes) { + auto Open = ensureOpen(); + return Open ? Assembler.emitAlignment(Alignment, Fill, MaximumBytes) : Open; +} + +Expected<void> ELFObjectStreamer::emitSymbolAttribute(MCSymbolRef Symbol, + MCSymbolAttribute Attribute) { + auto Open = ensureOpen(); + return Open ? Assembler.emitSymbolAttribute(std::move(Symbol), Attribute) : Open; +} + +Expected<void> ELFObjectStreamer::emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) { + auto Open = ensureOpen(); + return Open ? Assembler.emitSymbolSize(std::move(Symbol), std::move(Size)) : Open; +} + +Expected<void> ELFObjectStreamer::emitCommonSymbol(MCSymbolRef Symbol, std::uint64_t Size, + std::uint64_t Alignment) { + auto Open = ensureOpen(); + return Open ? Assembler.emitCommonSymbol(std::move(Symbol), Size, Alignment) : Open; +} + +Expected<void> ELFObjectStreamer::emitDirective(std::string_view Name, std::string_view) { + auto Open = ensureOpen(); + if (!Open) + return Open; + if (Name == "attribute") + return {}; + return makeUnexpected(ErrorCode::Unsupported, + "native object emission does not support the MC directive '" + + std::string(Name) + "'"); +} + +Expected<void> ELFObjectStreamer::emitCFI(const MCCFIInstruction &Instruction) { + auto Open = ensureOpen(); + if (!Open) + return Open; + return Assembler.emitCFI(Instruction); +} + +Expected<void> ELFObjectStreamer::emitInlineAssembly(std::string_view Text) { + auto Open = ensureOpen(); + if (!Open) + return Open; + if (Text.empty()) + return {}; + return makeUnexpected(ErrorCode::Unsupported, + "native object emission requires parsed inline assembly"); +} + +Expected<void> ELFObjectStreamer::emitComment(std::string_view) { + return ensureOpen(); +} + +Expected<void> ELFObjectStreamer::configureDwarfFrame(MCSectionRef Section, MCDwarfFrameInfo Info) { + auto Open = ensureOpen(); + if (!Open) + return Open; + return Assembler.configureDwarfFrame(std::move(Section), Info); +} + +Expected<void> ELFObjectStreamer::finish() { + auto Open = ensureOpen(); + if (!Open) + return Open; + auto Object = Assembler.assemble(); + if (!Object) + return std::unexpected(Object.error()); + auto Written = Writer->writeObject(*Object); + if (!Written) + return std::unexpected(Written.error()); + Output = std::move(*Written); + IsFinished = true; + return {}; +} + +Expected<std::vector<std::uint8_t>> ELFObjectStreamer::takeOutput() { + if (!IsFinished) + return makeUnexpected(ErrorCode::ObjectWritingFailed, + "object output cannot be taken before finalization"); + if (OutputWasTaken) + return makeUnexpected(ErrorCode::ObjectWritingFailed, "object output was already taken"); + OutputWasTaken = true; + return std::move(Output); +} + +} // namespace rcc::mc diff --git a/backend/lib/MC/MCStreamer.cpp b/backend/lib/MC/MCStreamer.cpp index 001fa28..56c3f96 100644 --- a/backend/lib/MC/MCStreamer.cpp +++ b/backend/lib/MC/MCStreamer.cpp @@ -342,6 +342,22 @@ Expected<void> AsmTextStreamer::emitSymbolSize(MCSymbolRef Symbol, MCExprRef Siz return appendLine({}, ".size " + *Name + ", " + *PrintedSize); } +Expected<void> AsmTextStreamer::emitCommonSymbol(MCSymbolRef Symbol, std::uint64_t Size, + std::uint64_t Alignment) { + if (IsFinished) + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "cannot emit a common symbol after assembly finalization"); + if (!Symbol || Size == 0 || Alignment == 0 || (Alignment & (Alignment - 1U)) != 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "common symbol requires a symbol, size, and power-of-two alignment"); + } + auto Name = printMCSymbolName(Symbol->name()); + if (!Name) + return std::unexpected(Name.error()); + return appendLine({}, ".comm " + *Name + "," + std::to_string(Size) + "," + + std::to_string(Alignment)); +} + Expected<void> AsmTextStreamer::emitDirective(std::string_view Name, std::string_view Arguments) { if (!isDirectiveName(Name) || containsInvalidLineCharacter(Arguments)) { return makeUnexpected(ErrorCode::InvalidArgument, diff --git a/backend/lib/Support/Error.cpp b/backend/lib/Support/Error.cpp index ed88d1a..da4299f 100644 --- a/backend/lib/Support/Error.cpp +++ b/backend/lib/Support/Error.cpp @@ -30,6 +30,14 @@ std::string_view getErrorCodeName(ErrorCode Code) noexcept { return "CFG optimization failed"; case ErrorCode::AssemblyLoweringFailed: return "assembly lowering failed"; + case ErrorCode::InstructionEncodingFailed: + return "instruction encoding failed"; + case ErrorCode::FixupApplicationFailed: + return "fixup application failed"; + case ErrorCode::RelocationFailed: + return "relocation failed"; + case ErrorCode::ObjectWritingFailed: + return "object writing failed"; case ErrorCode::IoError: return "I/O error"; case ErrorCode::InternalError: diff --git a/backend/lib/Target/Description/TargetDescriptionGenerator.cpp b/backend/lib/Target/Description/TargetDescriptionGenerator.cpp index cd5ba97..dd3545b 100644 --- a/backend/lib/Target/Description/TargetDescriptionGenerator.cpp +++ b/backend/lib/Target/Description/TargetDescriptionGenerator.cpp @@ -198,6 +198,7 @@ struct SourceInstruction final { std::string Symbol; std::string Name; std::string AssemblyMnemonic; + std::optional<std::uint32_t> BinaryEncoding; std::vector<SourceOperand> Operands; std::uint32_t NumExplicitDefs = 0; bool Variadic = false; @@ -360,6 +361,7 @@ struct NormalizedInstruction final { std::string Symbol; std::string Name; std::string AssemblyMnemonic; + std::uint32_t BinaryEncoding = 0; std::vector<NormalizedOperand> Operands; std::uint32_t NumExplicitDefs = 0; bool Variadic = false; @@ -1476,10 +1478,11 @@ private: if (!requireMapping(Element, "instruction")) return false; if (!checkKeys(*Element, - {"id", "symbol", "name", "assembly_mnemonic", "operands", "num_explicit_defs", - "variadic", "implicit_defs", "implicit_uses", "allow_extra_implicit_operands", - "flags", "scheduling_class", "encoding_category", "assembly_format", - "requires_memory_operands", "owner", "deadline"}, + {"id", "symbol", "name", "assembly_mnemonic", "encoding", "operands", + "num_explicit_defs", "variadic", "implicit_defs", "implicit_uses", + "allow_extra_implicit_operands", "flags", "scheduling_class", + "encoding_category", "assembly_format", "requires_memory_operands", "owner", + "deadline"}, "instruction")) { return false; } @@ -1513,6 +1516,12 @@ private: return false; Instruction.AssemblyMnemonic = *Value; } + if (const yaml::Node *Encoding = optionalNode(*Element, "encoding")) { + const auto Value = unsignedInteger32(*Encoding, "instruction encoding"); + if (!Value) + return false; + Instruction.BinaryEncoding = Value; + } if (const yaml::Node *SchedulingClass = optionalNode(*Element, "scheduling_class")) { const auto Value = scalar(*SchedulingClass, "instruction scheduling_class"); if (!Value) @@ -2386,13 +2395,14 @@ private: std::unordered_set<std::string> Names; for (const SourceInstruction &Instruction : Input.Instructions) { const bool HasMnemonic = !Instruction.AssemblyMnemonic.empty(); + const bool HasBinaryEncoding = Instruction.BinaryEncoding.has_value(); const bool HasSchedulingClass = !Instruction.SchedulingClassSymbol.empty(); const bool HasEncodingCategory = !Instruction.EncodingCategorySymbol.empty(); const bool HasAssemblyFormat = !Instruction.AssemblyFormatSymbol.empty(); - const bool HasAllMachineFacts = - HasMnemonic && HasSchedulingClass && HasEncodingCategory && HasAssemblyFormat; - const bool HasAnyMachineFact = - HasMnemonic || HasSchedulingClass || HasEncodingCategory || HasAssemblyFormat; + const bool HasAllMachineFacts = HasMnemonic && HasBinaryEncoding && HasSchedulingClass && + HasEncodingCategory && HasAssemblyFormat; + const bool HasAnyMachineFact = HasMnemonic || HasBinaryEncoding || HasSchedulingClass || + HasEncodingCategory || HasAssemblyFormat; const bool IsPseudoSource = std::ranges::find(Instruction.Flags, "pseudo") != Instruction.Flags.end(); const bool IsTerminalEmissionSource = @@ -2432,6 +2442,7 @@ private: .Symbol = Instruction.Symbol, .Name = Instruction.Name, .AssemblyMnemonic = Instruction.AssemblyMnemonic, + .BinaryEncoding = Instruction.BinaryEncoding.value_or(0), .Operands = {}, .NumExplicitDefs = Instruction.NumExplicitDefs, .Variadic = Instruction.Variadic, @@ -2970,7 +2981,8 @@ void emitImmediatePredicate(std::ostream &Output, const NormalizedImmediatePredi continue; Output << " {Opcode::fromValue(" << Instruction.ID << "), "; appendQuoted(Output, Instruction.AssemblyMnemonic); - Output << ", AssemblyFormatID::fromValue(" << Instruction.AssemblyFormat << ")},\n"; + Output << ", AssemblyFormatID::fromValue(" << Instruction.AssemblyFormat << "), " + << Instruction.BinaryEncoding << "},\n"; } Output << "}};\n\n"; diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt index 646e856..55578a2 100644 --- a/backend/lib/Target/RISCV/CMakeLists.txt +++ b/backend/lib/Target/RISCV/CMakeLists.txt @@ -53,6 +53,9 @@ add_library( RISCVAssemblyPrinter.cpp RISCVFrameLowering.cpp RISCVInstPrinter.cpp + RISCVAsmBackend.cpp + RISCVMCCodeEmitter.cpp + RISCVObjectFile.cpp RISCVMCTargetLowering.cpp RISCVMachineCombiner.cpp RISCVMachineMemoryFolding.cpp @@ -61,6 +64,7 @@ add_library( RISCVRegisterAllocation.cpp RISCVScheduling.cpp RISCVTarget.cpp + RISCVTargetConstant.cpp "${RCCBackendRISCVGeneratedRegisterHeader}" "${RCCBackendRISCVGeneratedInstructionHeader}" "${RCCBackendRISCVGeneratedTables}" @@ -114,6 +118,9 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLowering.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLowering.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstPrinter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAsmBackend.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCCodeEmitter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVObjectFile.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCTargetLowering.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp" @@ -122,12 +129,19 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocation.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVScheduling.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTarget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetConstant.cpp" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVCodeGen.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVAssemblyPrinter.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVCallLowering.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVInstPrinter.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVAsmBackend.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVFixupKinds.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVMCCodeEmitter.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVObjectFile.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVMCTargetLowering.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTarget.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTargetConstant.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTargetRelocationKinds.h" ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/RISCVBranchInfo.cpp" @@ -136,6 +150,9 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLowering.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLowering.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstPrinter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAsmBackend.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCCodeEmitter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVObjectFile.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCTargetLowering.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp" @@ -144,4 +161,5 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocation.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVScheduling.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTarget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetConstant.cpp" ) diff --git a/backend/lib/Target/RISCV/RISCV.rccdesc b/backend/lib/Target/RISCV/RISCV.rccdesc index dce984c..237e294 100644 --- a/backend/lib/Target/RISCV/RISCV.rccdesc +++ b/backend/lib/Target/RISCV/RISCV.rccdesc @@ -375,6 +375,7 @@ instructions: symbol: LUI name: LUI assembly_mnemonic: lui + encoding: 5175 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: SIMM20, name: IMM } @@ -388,6 +389,7 @@ instructions: symbol: AUIPC name: AUIPC assembly_mnemonic: auipc + encoding: 5143 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate, basic_block, global_symbol, external_symbol, mc_symbol], immediate_predicate: SIMM20, allowed_target_flags: 3, name: IMM } @@ -401,6 +403,7 @@ instructions: symbol: ADDI name: ADDI assembly_mnemonic: addi + encoding: 1344531 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -415,6 +418,7 @@ instructions: symbol: SRLI name: SRLI assembly_mnemonic: srli + encoding: 1365011 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -429,6 +433,7 @@ instructions: symbol: SRAI name: SRAI assembly_mnemonic: srai + encoding: 1075106835 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -443,6 +448,7 @@ instructions: symbol: ADDIW name: ADDIW assembly_mnemonic: addiw + encoding: 1344539 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -457,6 +463,7 @@ instructions: symbol: SLLIW name: SLLIW assembly_mnemonic: slliw + encoding: 1348635 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -471,6 +478,7 @@ instructions: symbol: SRLIW name: SRLIW assembly_mnemonic: srliw + encoding: 1365019 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -485,6 +493,7 @@ instructions: symbol: SRAIW name: SRAIW assembly_mnemonic: sraiw + encoding: 1075106843 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -499,6 +508,7 @@ instructions: symbol: SLL name: SLL assembly_mnemonic: sll + encoding: 10785843 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -513,6 +523,7 @@ instructions: symbol: SLT name: SLT assembly_mnemonic: slt + encoding: 10789939 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -527,6 +538,7 @@ instructions: symbol: SLTU name: SLTU assembly_mnemonic: sltu + encoding: 10794035 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -541,6 +553,7 @@ instructions: symbol: XOR name: XOR assembly_mnemonic: xor + encoding: 10798131 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -555,6 +568,7 @@ instructions: symbol: SRL name: SRL assembly_mnemonic: srl + encoding: 10802227 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -569,6 +583,7 @@ instructions: symbol: SRA name: SRA assembly_mnemonic: sra + encoding: 1084544051 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -583,6 +598,7 @@ instructions: symbol: OR name: OR assembly_mnemonic: or + encoding: 10806323 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -597,6 +613,7 @@ instructions: symbol: AND name: AND assembly_mnemonic: and + encoding: 10810419 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -611,6 +628,7 @@ instructions: symbol: ADDW name: ADDW assembly_mnemonic: addw + encoding: 10781755 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -625,6 +643,7 @@ instructions: symbol: SUBW name: SUBW assembly_mnemonic: subw + encoding: 1084523579 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -639,6 +658,7 @@ instructions: symbol: SLLW name: SLLW assembly_mnemonic: sllw + encoding: 10785851 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -653,6 +673,7 @@ instructions: symbol: SRLW name: SRLW assembly_mnemonic: srlw + encoding: 10802235 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -667,6 +688,7 @@ instructions: symbol: SRAW name: SRAW assembly_mnemonic: sraw + encoding: 1084544059 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -681,6 +703,7 @@ instructions: symbol: LB name: LB assembly_mnemonic: lb + encoding: 8684547 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -696,6 +719,7 @@ instructions: symbol: LH name: LH assembly_mnemonic: lh + encoding: 8688643 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -711,6 +735,7 @@ instructions: symbol: LW name: LW assembly_mnemonic: lw + encoding: 8692739 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -726,6 +751,7 @@ instructions: symbol: LBU name: LBU assembly_mnemonic: lbu + encoding: 8700931 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -741,6 +767,7 @@ instructions: symbol: LHU name: LHU assembly_mnemonic: lhu + encoding: 8705027 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -756,6 +783,7 @@ instructions: symbol: LWU name: LWU assembly_mnemonic: lwu + encoding: 8709123 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -771,6 +799,7 @@ instructions: symbol: SB name: SB assembly_mnemonic: sb + encoding: 10781731 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -786,6 +815,7 @@ instructions: symbol: SH name: SH assembly_mnemonic: sh + encoding: 10785827 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -801,6 +831,7 @@ instructions: symbol: SW name: SW assembly_mnemonic: sw + encoding: 10789923 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -816,6 +847,7 @@ instructions: symbol: BLT name: BLT assembly_mnemonic: blt + encoding: 9715811 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } @@ -830,6 +862,7 @@ instructions: symbol: BGE name: BGE assembly_mnemonic: bge + encoding: 9719907 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } @@ -844,6 +877,7 @@ instructions: symbol: BLTU name: BLTU assembly_mnemonic: bltu + encoding: 9724003 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } @@ -858,6 +892,7 @@ instructions: symbol: BGEU name: BGEU assembly_mnemonic: bgeu + encoding: 9728099 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } @@ -872,6 +907,7 @@ instructions: symbol: JALR name: JALR assembly_mnemonic: jalr + encoding: 1344615 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -886,6 +922,7 @@ instructions: symbol: FENCE name: FENCE assembly_mnemonic: fence + encoding: 15 operands: - { kinds: [immediate], immediate_predicate: UIMM4, name: PRED } - { kinds: [immediate], immediate_predicate: UIMM4, name: SUCC } @@ -899,6 +936,7 @@ instructions: symbol: FENCE_I name: FENCE_I assembly_mnemonic: fence.i + encoding: 4111 operands: [] num_explicit_defs: 0 flags: [has_side_effects, produces_code] @@ -910,6 +948,7 @@ instructions: symbol: CSRRW name: CSRRW assembly_mnemonic: csrrw + encoding: 1348723 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } @@ -924,6 +963,7 @@ instructions: symbol: CSRRS name: CSRRS assembly_mnemonic: csrrs + encoding: 1352819 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } @@ -938,6 +978,7 @@ instructions: symbol: CSRRC name: CSRRC assembly_mnemonic: csrrc + encoding: 1356915 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } @@ -952,6 +993,7 @@ instructions: symbol: CSRRWI name: CSRRWI assembly_mnemonic: csrrwi + encoding: 1102963 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } @@ -966,6 +1008,7 @@ instructions: symbol: MUL name: MUL assembly_mnemonic: mul + encoding: 44336179 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -980,6 +1023,7 @@ instructions: symbol: MULH name: MULH assembly_mnemonic: mulh + encoding: 44340275 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -994,6 +1038,7 @@ instructions: symbol: MULHSU name: MULHSU assembly_mnemonic: mulhsu + encoding: 44344371 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1008,6 +1053,7 @@ instructions: symbol: MULHU name: MULHU assembly_mnemonic: mulhu + encoding: 44348467 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1022,6 +1068,7 @@ instructions: symbol: DIV name: DIV assembly_mnemonic: div + encoding: 44352563 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1036,6 +1083,7 @@ instructions: symbol: DIVU name: DIVU assembly_mnemonic: divu + encoding: 44356659 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1050,6 +1098,7 @@ instructions: symbol: REM name: REM assembly_mnemonic: rem + encoding: 44360755 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1064,6 +1113,7 @@ instructions: symbol: REMU name: REMU assembly_mnemonic: remu + encoding: 44364851 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1078,6 +1128,7 @@ instructions: symbol: MULW name: MULW assembly_mnemonic: mulw + encoding: 44336187 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1092,6 +1143,7 @@ instructions: symbol: DIVW name: DIVW assembly_mnemonic: divw + encoding: 44352571 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1106,6 +1158,7 @@ instructions: symbol: DIVUW name: DIVUW assembly_mnemonic: divuw + encoding: 44356667 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1120,6 +1173,7 @@ instructions: symbol: REMW name: REMW assembly_mnemonic: remw + encoding: 44360763 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1134,6 +1188,7 @@ instructions: symbol: REMUW name: REMUW assembly_mnemonic: remuw + encoding: 44364859 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1148,6 +1203,7 @@ instructions: symbol: CSRRSI name: CSRRSI assembly_mnemonic: csrrsi + encoding: 1107059 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } @@ -1162,6 +1218,7 @@ instructions: symbol: CSRRCI name: CSRRCI assembly_mnemonic: csrrci + encoding: 1111155 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } @@ -1176,6 +1233,7 @@ instructions: symbol: ADD name: ADD assembly_mnemonic: add + encoding: 10781747 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1190,6 +1248,7 @@ instructions: symbol: SUB name: SUB assembly_mnemonic: sub + encoding: 1084523571 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1204,6 +1263,7 @@ instructions: symbol: LD name: LD assembly_mnemonic: ld + encoding: 8696835 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -1219,6 +1279,7 @@ instructions: symbol: SD name: SD assembly_mnemonic: sd + encoding: 10794019 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -1234,6 +1295,7 @@ instructions: symbol: BEQ name: BEQ assembly_mnemonic: beq + encoding: 9699427 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } @@ -1248,6 +1310,7 @@ instructions: symbol: BNE name: BNE assembly_mnemonic: bne + encoding: 9703523 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } @@ -1262,6 +1325,7 @@ instructions: symbol: JAL name: JAL assembly_mnemonic: jal + encoding: 1135 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [basic_block], name: TARGET } @@ -1275,6 +1339,7 @@ instructions: symbol: ECALL name: ECALL assembly_mnemonic: ecall + encoding: 115 operands: [] num_explicit_defs: 0 flags: [has_side_effects, produces_code] @@ -1286,6 +1351,7 @@ instructions: symbol: EBREAK name: EBREAK assembly_mnemonic: ebreak + encoding: 1048691 operands: [] num_explicit_defs: 0 flags: [has_side_effects, produces_code] @@ -1297,6 +1363,7 @@ instructions: symbol: SLTI name: SLTI assembly_mnemonic: slti + encoding: 1352723 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1311,6 +1378,7 @@ instructions: symbol: SLTIU name: SLTIU assembly_mnemonic: sltiu + encoding: 1356819 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1325,6 +1393,7 @@ instructions: symbol: XORI name: XORI assembly_mnemonic: xori + encoding: 1360915 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1339,6 +1408,7 @@ instructions: symbol: ORI name: ORI assembly_mnemonic: ori + encoding: 1369107 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1353,6 +1423,7 @@ instructions: symbol: ANDI name: ANDI assembly_mnemonic: andi + encoding: 1373203 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1367,6 +1438,7 @@ instructions: symbol: SLLI name: SLLI assembly_mnemonic: slli + encoding: 1348627 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1381,6 +1453,7 @@ instructions: symbol: LR_W name: LR_W assembly_mnemonic: lr.w + encoding: 268739631 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -1395,6 +1468,7 @@ instructions: symbol: SC_W name: SC_W assembly_mnemonic: sc.w + encoding: 413443119 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -1410,6 +1484,7 @@ instructions: symbol: AMOSWAP_W name: AMOSWAP_W assembly_mnemonic: amoswap.w + encoding: 145007663 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -1425,6 +1500,7 @@ instructions: symbol: FLW name: FLW assembly_mnemonic: flw + encoding: 8692743 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, memory_role: value, name: FRD } - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -1440,6 +1516,7 @@ instructions: symbol: FSW name: FSW assembly_mnemonic: fsw + encoding: 10789927 operands: - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, memory_role: value, name: FRS2 } - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -1455,6 +1532,7 @@ instructions: symbol: FMADD_S name: FMADD_S assembly_mnemonic: fmadd.s + encoding: 1487176771 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1471,6 +1549,7 @@ instructions: symbol: FMSUB_S name: FMSUB_S assembly_mnemonic: fmsub.s + encoding: 1487176775 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1487,6 +1566,7 @@ instructions: symbol: FNMSUB_S name: FNMSUB_S assembly_mnemonic: fnmsub.s + encoding: 1487176779 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1503,6 +1583,7 @@ instructions: symbol: FNMADD_S name: FNMADD_S assembly_mnemonic: fnmadd.s + encoding: 1487176783 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1519,6 +1600,7 @@ instructions: symbol: FADD_S name: FADD_S assembly_mnemonic: fadd.s + encoding: 10781779 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1534,6 +1616,7 @@ instructions: symbol: FSUB_S name: FSUB_S assembly_mnemonic: fsub.s + encoding: 144999507 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1549,6 +1632,7 @@ instructions: symbol: FMUL_S name: FMUL_S assembly_mnemonic: fmul.s + encoding: 279217235 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1564,6 +1648,7 @@ instructions: symbol: FDIV_S name: FDIV_S assembly_mnemonic: fdiv.s + encoding: 413434963 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1579,6 +1664,7 @@ instructions: symbol: FSQRT_S name: FSQRT_S assembly_mnemonic: fsqrt.s + encoding: 1476691027 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1593,6 +1679,7 @@ instructions: symbol: FSGNJ_S name: FSGNJ_S assembly_mnemonic: fsgnj.s + encoding: 547652691 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1607,6 +1694,7 @@ instructions: symbol: FSGNJN_S name: FSGNJN_S assembly_mnemonic: fsgnjn.s + encoding: 547656787 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1621,6 +1709,7 @@ instructions: symbol: FSGNJX_S name: FSGNJX_S assembly_mnemonic: fsgnjx.s + encoding: 547660883 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1635,6 +1724,7 @@ instructions: symbol: FMIN_S name: FMIN_S assembly_mnemonic: fmin.s + encoding: 681870419 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1649,6 +1739,7 @@ instructions: symbol: FMAX_S name: FMAX_S assembly_mnemonic: fmax.s + encoding: 681874515 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1663,6 +1754,7 @@ instructions: symbol: FCVT_W_S name: FCVT_W_S assembly_mnemonic: fcvt.w.s + encoding: 3221521491 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1677,6 +1769,7 @@ instructions: symbol: FCVT_WU_S name: FCVT_WU_S assembly_mnemonic: fcvt.wu.s + encoding: 3222570067 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1691,6 +1784,7 @@ instructions: symbol: FCVT_L_S name: FCVT_L_S assembly_mnemonic: fcvt.l.s + encoding: 3223618643 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1705,6 +1799,7 @@ instructions: symbol: FCVT_LU_S name: FCVT_LU_S assembly_mnemonic: fcvt.lu.s + encoding: 3224667219 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1719,6 +1814,7 @@ instructions: symbol: FMV_X_W name: FMV_X_W assembly_mnemonic: fmv.x.w + encoding: 3758392403 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1732,6 +1828,7 @@ instructions: symbol: FEQ_S name: FEQ_S assembly_mnemonic: feq.s + encoding: 2695144531 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1746,6 +1843,7 @@ instructions: symbol: FLT_S name: FLT_S assembly_mnemonic: flt.s + encoding: 2695140435 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1760,6 +1858,7 @@ instructions: symbol: FLE_S name: FLE_S assembly_mnemonic: fle.s + encoding: 2695136339 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1774,6 +1873,7 @@ instructions: symbol: FCLASS_S name: FCLASS_S assembly_mnemonic: fclass.s + encoding: 3758396499 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -1787,6 +1887,7 @@ instructions: symbol: FCVT_S_W name: FCVT_S_W assembly_mnemonic: fcvt.s.w + encoding: 3489956947 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1801,6 +1902,7 @@ instructions: symbol: FCVT_S_WU name: FCVT_S_WU assembly_mnemonic: fcvt.s.wu + encoding: 3491005523 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1815,6 +1917,7 @@ instructions: symbol: FCVT_S_L name: FCVT_S_L assembly_mnemonic: fcvt.s.l + encoding: 3492054099 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1829,6 +1932,7 @@ instructions: symbol: FCVT_S_LU name: FCVT_S_LU assembly_mnemonic: fcvt.s.lu + encoding: 3493102675 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1843,6 +1947,7 @@ instructions: symbol: FMV_W_X name: FMV_W_X assembly_mnemonic: fmv.w.x + encoding: 4026827859 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -1856,6 +1961,7 @@ instructions: symbol: FLD name: FLD assembly_mnemonic: fld + encoding: 8696839 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, memory_role: value, name: FRD } - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -1871,6 +1977,7 @@ instructions: symbol: FSD name: FSD assembly_mnemonic: fsd + encoding: 10794023 operands: - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, memory_role: value, name: FRS2 } - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -1886,6 +1993,7 @@ instructions: symbol: FMADD_D name: FMADD_D assembly_mnemonic: fmadd.d + encoding: 1520731203 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -1902,6 +2010,7 @@ instructions: symbol: FMSUB_D name: FMSUB_D assembly_mnemonic: fmsub.d + encoding: 1520731207 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -1918,6 +2027,7 @@ instructions: symbol: FNMSUB_D name: FNMSUB_D assembly_mnemonic: fnmsub.d + encoding: 1520731211 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -1934,6 +2044,7 @@ instructions: symbol: FNMADD_D name: FNMADD_D assembly_mnemonic: fnmadd.d + encoding: 1520731215 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -1950,6 +2061,7 @@ instructions: symbol: FADD_D name: FADD_D assembly_mnemonic: fadd.d + encoding: 44336211 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -1965,6 +2077,7 @@ instructions: symbol: FSUB_D name: FSUB_D assembly_mnemonic: fsub.d + encoding: 178553939 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -1980,6 +2093,7 @@ instructions: symbol: FMUL_D name: FMUL_D assembly_mnemonic: fmul.d + encoding: 312771667 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -1995,6 +2109,7 @@ instructions: symbol: FDIV_D name: FDIV_D assembly_mnemonic: fdiv.d + encoding: 446989395 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2010,6 +2125,7 @@ instructions: symbol: FSQRT_D name: FSQRT_D assembly_mnemonic: fsqrt.d + encoding: 1510245459 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2024,6 +2140,7 @@ instructions: symbol: FSGNJ_D name: FSGNJ_D assembly_mnemonic: fsgnj.d + encoding: 581207123 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2038,6 +2155,7 @@ instructions: symbol: FSGNJN_D name: FSGNJN_D assembly_mnemonic: fsgnjn.d + encoding: 581211219 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2052,6 +2170,7 @@ instructions: symbol: FSGNJX_D name: FSGNJX_D assembly_mnemonic: fsgnjx.d + encoding: 581215315 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2066,6 +2185,7 @@ instructions: symbol: FMIN_D name: FMIN_D assembly_mnemonic: fmin.d + encoding: 715424851 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2080,6 +2200,7 @@ instructions: symbol: FMAX_D name: FMAX_D assembly_mnemonic: fmax.d + encoding: 715428947 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2094,6 +2215,7 @@ instructions: symbol: FCVT_W_D name: FCVT_W_D assembly_mnemonic: fcvt.w.d + encoding: 3255075923 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2108,6 +2230,7 @@ instructions: symbol: FCVT_WU_D name: FCVT_WU_D assembly_mnemonic: fcvt.wu.d + encoding: 3256124499 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2122,6 +2245,7 @@ instructions: symbol: FCVT_L_D name: FCVT_L_D assembly_mnemonic: fcvt.l.d + encoding: 3257173075 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2136,6 +2260,7 @@ instructions: symbol: FCVT_LU_D name: FCVT_LU_D assembly_mnemonic: fcvt.lu.d + encoding: 3258221651 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2150,6 +2275,7 @@ instructions: symbol: FMV_X_D name: FMV_X_D assembly_mnemonic: fmv.x.d + encoding: 3791946835 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2163,6 +2289,7 @@ instructions: symbol: FEQ_D name: FEQ_D assembly_mnemonic: feq.d + encoding: 2728698963 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2177,6 +2304,7 @@ instructions: symbol: FLT_D name: FLT_D assembly_mnemonic: flt.d + encoding: 2728694867 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2191,6 +2319,7 @@ instructions: symbol: FLE_D name: FLE_D assembly_mnemonic: fle.d + encoding: 2728690771 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2205,6 +2334,7 @@ instructions: symbol: FCLASS_D name: FCLASS_D assembly_mnemonic: fclass.d + encoding: 3791950931 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2218,6 +2348,7 @@ instructions: symbol: FCVT_D_W name: FCVT_D_W assembly_mnemonic: fcvt.d.w + encoding: 3523511379 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -2232,6 +2363,7 @@ instructions: symbol: FCVT_D_WU name: FCVT_D_WU assembly_mnemonic: fcvt.d.wu + encoding: 3524559955 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -2246,6 +2378,7 @@ instructions: symbol: FCVT_D_L name: FCVT_D_L assembly_mnemonic: fcvt.d.l + encoding: 3525608531 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -2260,6 +2393,7 @@ instructions: symbol: FCVT_D_LU name: FCVT_D_LU assembly_mnemonic: fcvt.d.lu + encoding: 3526657107 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -2274,6 +2408,7 @@ instructions: symbol: FMV_D_X name: FMV_D_X assembly_mnemonic: fmv.d.x + encoding: 4060382291 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -2287,6 +2422,7 @@ instructions: symbol: FCVT_S_D name: FCVT_S_D assembly_mnemonic: fcvt.s.d + encoding: 1075086419 operands: - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } @@ -2301,6 +2437,7 @@ instructions: symbol: C_ADDI4SPN name: C_ADDI4SPN assembly_mnemonic: c.addi4spn + encoding: 2048 operands: - { kinds: [register], role: def, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: C_UIMM10_NZ_A4, name: IMM } @@ -2315,6 +2452,7 @@ instructions: symbol: C_FLD name: C_FLD assembly_mnemonic: c.fld + encoding: 9344 operands: - { kinds: [register], role: def, register_class: FPR64C, register_bank: FPR, memory_role: value, name: FRD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } @@ -2330,6 +2468,7 @@ instructions: symbol: C_LW name: C_LW assembly_mnemonic: c.lw + encoding: 17536 operands: - { kinds: [register], role: def, register_class: GPRC, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } @@ -2345,6 +2484,7 @@ instructions: symbol: C_LD name: C_LD assembly_mnemonic: c.ld + encoding: 25728 operands: - { kinds: [register], role: def, register_class: GPRC, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } @@ -2360,6 +2500,7 @@ instructions: symbol: C_FSD name: C_FSD assembly_mnemonic: c.fsd + encoding: 42120 operands: - { kinds: [register], role: use, register_class: FPR64C, register_bank: FPR, memory_role: value, name: FRS2 } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } @@ -2375,6 +2516,7 @@ instructions: symbol: C_SW name: C_SW assembly_mnemonic: c.sw + encoding: 50312 operands: - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: value, name: RS2 } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } @@ -2390,6 +2532,7 @@ instructions: symbol: C_SD name: C_SD assembly_mnemonic: c.sd + encoding: 58504 operands: - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: value, name: RS2 } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } @@ -2405,6 +2548,7 @@ instructions: symbol: C_ADDI name: C_ADDI assembly_mnemonic: c.addi + encoding: 1029 operands: - { kinds: [register], role: def_use, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: C_SIMM6, name: IMM } @@ -2418,6 +2562,7 @@ instructions: symbol: C_ADDIW name: C_ADDIW assembly_mnemonic: c.addiw + encoding: 9221 operands: - { kinds: [register], role: def_use, register_class: GPR_NX0, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: C_SIMM6, name: IMM } @@ -2431,6 +2576,7 @@ instructions: symbol: C_LI name: C_LI assembly_mnemonic: c.li + encoding: 17413 operands: - { kinds: [register], role: def, register_class: GPR_NX0, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: C_SIMM6, name: IMM } @@ -2444,6 +2590,7 @@ instructions: symbol: C_ADDI16SP name: C_ADDI16SP assembly_mnemonic: c.addi16sp + encoding: 24897 operands: - { kinds: [immediate], immediate_predicate: C_SIMM10_A16_NZ, name: IMM } num_explicit_defs: 0 @@ -2458,6 +2605,7 @@ instructions: symbol: C_LUI name: C_LUI assembly_mnemonic: c.lui + encoding: 25605 operands: - { kinds: [register], role: def, register_class: GPR_NX0_NX2, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: C_SIMM6_NZ, name: IMM } @@ -2471,6 +2619,7 @@ instructions: symbol: C_SRLI name: C_SRLI assembly_mnemonic: c.srli + encoding: 32773 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } @@ -2484,6 +2633,7 @@ instructions: symbol: C_SRAI name: C_SRAI assembly_mnemonic: c.srai + encoding: 33797 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } @@ -2497,6 +2647,7 @@ instructions: symbol: C_ANDI name: C_ANDI assembly_mnemonic: c.andi + encoding: 34821 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: C_SIMM6, name: IMM } @@ -2510,6 +2661,7 @@ instructions: symbol: C_SUB name: C_SUB assembly_mnemonic: c.sub + encoding: 35845 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } @@ -2523,6 +2675,7 @@ instructions: symbol: C_XOR name: C_XOR assembly_mnemonic: c.xor + encoding: 35877 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } @@ -2536,6 +2689,7 @@ instructions: symbol: C_OR name: C_OR assembly_mnemonic: c.or + encoding: 35909 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } @@ -2549,6 +2703,7 @@ instructions: symbol: C_AND name: C_AND assembly_mnemonic: c.and + encoding: 35941 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } @@ -2562,6 +2717,7 @@ instructions: symbol: C_SUBW name: C_SUBW assembly_mnemonic: c.subw + encoding: 39941 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } @@ -2575,6 +2731,7 @@ instructions: symbol: C_ADDW name: C_ADDW assembly_mnemonic: c.addw + encoding: 39973 operands: - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } @@ -2588,6 +2745,7 @@ instructions: symbol: C_J name: C_J assembly_mnemonic: c.j + encoding: 40961 operands: - { kinds: [basic_block], name: TARGET } num_explicit_defs: 0 @@ -2600,6 +2758,7 @@ instructions: symbol: C_BEQZ name: C_BEQZ assembly_mnemonic: c.beqz + encoding: 49153 operands: - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS1 } - { kinds: [basic_block], name: TARGET } @@ -2613,6 +2772,7 @@ instructions: symbol: C_BNEZ name: C_BNEZ assembly_mnemonic: c.bnez + encoding: 57345 operands: - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS1 } - { kinds: [basic_block], name: TARGET } @@ -2626,6 +2786,7 @@ instructions: symbol: C_SLLI name: C_SLLI assembly_mnemonic: c.slli + encoding: 1030 operands: - { kinds: [register], role: def_use, register_class: GPR_NX0, register_bank: GPR, name: RD } - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } @@ -2639,6 +2800,7 @@ instructions: symbol: C_FLDSP name: C_FLDSP assembly_mnemonic: c.fldsp + encoding: 9250 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, memory_role: value, name: FRD } - { kinds: [immediate], immediate_predicate: C_UIMM9_A8, name: IMM } @@ -2654,6 +2816,7 @@ instructions: symbol: C_LWSP name: C_LWSP assembly_mnemonic: c.lwsp + encoding: 17442 operands: - { kinds: [register], role: def, register_class: GPR_NX0, register_bank: GPR, memory_role: value, name: RD } - { kinds: [immediate], immediate_predicate: C_UIMM8_A4, name: IMM } @@ -2669,6 +2832,7 @@ instructions: symbol: C_LDSP name: C_LDSP assembly_mnemonic: c.ldsp + encoding: 25634 operands: - { kinds: [register], role: def, register_class: GPR_NX0, register_bank: GPR, memory_role: value, name: RD } - { kinds: [immediate], immediate_predicate: C_UIMM9_A8, name: IMM } @@ -2684,6 +2848,7 @@ instructions: symbol: C_JR name: C_JR assembly_mnemonic: c.jr + encoding: 33794 operands: - { kinds: [register], role: use, register_class: GPR_NX0, register_bank: GPR, name: RS1 } num_explicit_defs: 0 @@ -2696,6 +2861,7 @@ instructions: symbol: C_MV name: C_MV assembly_mnemonic: c.mv + encoding: 33830 operands: - { kinds: [register], role: def, register_class: GPR_NX0, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR_NX0, register_bank: GPR, name: RS2 } @@ -2709,6 +2875,7 @@ instructions: symbol: C_EBREAK name: C_EBREAK assembly_mnemonic: c.ebreak + encoding: 36866 operands: [] num_explicit_defs: 0 flags: [has_side_effects, produces_code] @@ -2720,6 +2887,7 @@ instructions: symbol: C_JALR name: C_JALR assembly_mnemonic: c.jalr + encoding: 37890 operands: - { kinds: [register], role: use, register_class: GPR_NX0, register_bank: GPR, name: RS1 } num_explicit_defs: 0 @@ -2733,6 +2901,7 @@ instructions: symbol: C_ADD name: C_ADD assembly_mnemonic: c.add + encoding: 37926 operands: - { kinds: [register], role: def_use, register_class: GPR_NX0, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR_NX0, register_bank: GPR, name: RS2 } @@ -2746,6 +2915,7 @@ instructions: symbol: C_FSDSP name: C_FSDSP assembly_mnemonic: c.fsdsp + encoding: 42018 operands: - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, memory_role: value, name: FRS2 } - { kinds: [immediate], immediate_predicate: C_UIMM9_A8, name: IMM } @@ -2761,6 +2931,7 @@ instructions: symbol: C_SWSP name: C_SWSP assembly_mnemonic: c.swsp + encoding: 50210 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } - { kinds: [immediate], immediate_predicate: C_UIMM8_A4, name: IMM } @@ -2776,6 +2947,7 @@ instructions: symbol: C_SDSP name: C_SDSP assembly_mnemonic: c.sdsp + encoding: 58402 operands: - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } - { kinds: [immediate], immediate_predicate: C_UIMM9_A8, name: IMM } @@ -2791,6 +2963,7 @@ instructions: symbol: FCVT_D_S name: FCVT_D_S assembly_mnemonic: fcvt.d.s + encoding: 1107592275 operands: - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } @@ -2805,6 +2978,7 @@ instructions: symbol: AMOADD_W name: AMOADD_W assembly_mnemonic: amoadd.w + encoding: 10789935 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2820,6 +2994,7 @@ instructions: symbol: AMOXOR_W name: AMOXOR_W assembly_mnemonic: amoxor.w + encoding: 547660847 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2835,6 +3010,7 @@ instructions: symbol: AMOAND_W name: AMOAND_W assembly_mnemonic: amoand.w + encoding: 1621402671 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2850,6 +3026,7 @@ instructions: symbol: AMOOR_W name: AMOOR_W assembly_mnemonic: amoor.w + encoding: 1084531759 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2865,6 +3042,7 @@ instructions: symbol: AMOMIN_W name: AMOMIN_W assembly_mnemonic: amomin.w + encoding: 2158273583 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2880,6 +3058,7 @@ instructions: symbol: AMOMAX_W name: AMOMAX_W assembly_mnemonic: amomax.w + encoding: 2695144495 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2895,6 +3074,7 @@ instructions: symbol: AMOMINU_W name: AMOMINU_W assembly_mnemonic: amominu.w + encoding: 3232015407 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2910,6 +3090,7 @@ instructions: symbol: AMOMAXU_W name: AMOMAXU_W assembly_mnemonic: amomaxu.w + encoding: 3768886319 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2925,6 +3106,7 @@ instructions: symbol: LR_D name: LR_D assembly_mnemonic: lr.d + encoding: 268743727 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } @@ -2939,6 +3121,7 @@ instructions: symbol: SC_D name: SC_D assembly_mnemonic: sc.d + encoding: 413447215 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2954,6 +3137,7 @@ instructions: symbol: AMOSWAP_D name: AMOSWAP_D assembly_mnemonic: amoswap.d + encoding: 145011759 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2969,6 +3153,7 @@ instructions: symbol: AMOADD_D name: AMOADD_D assembly_mnemonic: amoadd.d + encoding: 10794031 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2984,6 +3169,7 @@ instructions: symbol: AMOXOR_D name: AMOXOR_D assembly_mnemonic: amoxor.d + encoding: 547664943 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -2999,6 +3185,7 @@ instructions: symbol: AMOAND_D name: AMOAND_D assembly_mnemonic: amoand.d + encoding: 1621406767 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -3014,6 +3201,7 @@ instructions: symbol: AMOOR_D name: AMOOR_D assembly_mnemonic: amoor.d + encoding: 1084535855 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -3029,6 +3217,7 @@ instructions: symbol: AMOMIN_D name: AMOMIN_D assembly_mnemonic: amomin.d + encoding: 2158277679 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -3044,6 +3233,7 @@ instructions: symbol: AMOMAX_D name: AMOMAX_D assembly_mnemonic: amomax.d + encoding: 2695148591 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -3059,6 +3249,7 @@ instructions: symbol: AMOMINU_D name: AMOMINU_D assembly_mnemonic: amominu.d + encoding: 3232019503 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -3074,6 +3265,7 @@ instructions: symbol: AMOMAXU_D name: AMOMAXU_D assembly_mnemonic: amomaxu.d + encoding: 3768890415 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } @@ -3204,6 +3396,7 @@ instructions: symbol: JALR_CALL name: JALR_CALL assembly_mnemonic: jalr + encoding: 1344615 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -3221,6 +3414,7 @@ instructions: symbol: JALR_TAIL name: JALR_TAIL assembly_mnemonic: jalr + encoding: 1344615 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } @@ -3238,6 +3432,7 @@ instructions: symbol: JALR_RET name: JALR_RET assembly_mnemonic: jalr + encoding: 1344615 operands: - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } diff --git a/backend/lib/Target/RISCV/RISCVAsmBackend.cpp b/backend/lib/Target/RISCV/RISCVAsmBackend.cpp new file mode 100644 index 0000000..4e07933 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVAsmBackend.cpp @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVAsmBackend.h" + +#include "rcc/Target/RISCV/RISCVFixupKinds.h" +#include "rcc/Target/RISCV/RISCVInstPrinter.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" + +#include <cstddef> +#include <cstdint> +#include <limits> +#include <string> + +namespace rcc::riscv { +namespace { + +enum class RISCVRelocation : std::uint32_t { + Relocation32 = 1, + Relocation64 = 2, + Branch = 16, + JAL = 17, + CallPLT = 19, + PCRelativeHigh20 = 23, + PCRelativeLow12I = 24, + PCRelativeLow12S = 25, + High20 = 26, + Low12I = 27, + Low12S = 28, + Add8 = 33, + Add16 = 34, + Add32 = 35, + Add64 = 36, + Subtract8 = 37, + Subtract16 = 38, + Subtract32 = 39, + Subtract64 = 40, + RVCBranch = 44, + RVCJump = 45, + PCRelative32 = 57, +}; + +[[nodiscard]] bool isTargetKind(mc::MCFixupKind Kind, RISCVFixupKind TargetKind) noexcept { + return Kind == fixupKind(TargetKind); +} + +[[nodiscard]] Expected<void> requireStorage(std::span<std::uint8_t> Data, std::uint64_t Offset, + std::uint8_t Size) { + if (Offset > Data.size() || Size > Data.size() - static_cast<std::size_t>(Offset)) { + return makeUnexpected(ErrorCode::FixupApplicationFailed, + "RISC-V fixup exceeds its encoded fragment"); + } + return {}; +} + +void writeLittleEndian(std::span<std::uint8_t> Data, std::uint64_t Offset, std::uint64_t Value, + std::uint8_t Size) noexcept { + for (std::uint8_t Index = 0; Index < Size; ++Index) + Data[static_cast<std::size_t>(Offset) + Index] = + static_cast<std::uint8_t>(Value >> (Index * 8U)); +} + +[[nodiscard]] std::uint32_t readWord(std::span<const std::uint8_t> Data, + std::uint64_t Offset) noexcept { + std::uint32_t Result = 0; + for (std::uint8_t Index = 0; Index < 4; ++Index) + Result |= static_cast<std::uint32_t>(Data[static_cast<std::size_t>(Offset) + Index]) + << (Index * 8U); + return Result; +} + +[[nodiscard]] std::uint16_t readHalf(std::span<const std::uint8_t> Data, + std::uint64_t Offset) noexcept { + return static_cast<std::uint16_t>(Data[static_cast<std::size_t>(Offset)]) | + static_cast<std::uint16_t>(Data[static_cast<std::size_t>(Offset) + 1U] << 8U); +} + +[[nodiscard]] bool fitsSigned(std::int64_t Value, unsigned Bits) noexcept { + const std::int64_t Minimum = -(std::int64_t{1} << (Bits - 1U)); + const std::int64_t Maximum = (std::int64_t{1} << (Bits - 1U)) - 1; + return Value >= Minimum && Value <= Maximum; +} + +[[nodiscard]] Expected<void> requireDisplacement(std::int64_t Value, unsigned Bits, + std::string_view Name) { + if ((Value & 1) != 0 || !fitsSigned(Value, Bits)) { + return makeUnexpected(ErrorCode::FixupApplicationFailed, + std::string(Name) + " displacement is out of range or unaligned"); + } + return {}; +} + +[[nodiscard]] std::uint32_t encodeBranchImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint32_t>(Value); + return ((Bits >> 12U) & 0x1U) << 31U | ((Bits >> 5U) & 0x3fU) << 25U | + ((Bits >> 1U) & 0xfU) << 8U | ((Bits >> 11U) & 0x1U) << 7U; +} + +[[nodiscard]] std::uint32_t encodeJALImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint32_t>(Value); + return ((Bits >> 20U) & 0x1U) << 31U | ((Bits >> 1U) & 0x3ffU) << 21U | + ((Bits >> 11U) & 0x1U) << 20U | ((Bits >> 12U) & 0xffU) << 12U; +} + +[[nodiscard]] std::uint16_t encodeRVCJumpImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint16_t>(Value); + return static_cast<std::uint16_t>(((Bits >> 11U) & 1U) << 12U | ((Bits >> 4U) & 1U) << 11U | + ((Bits >> 8U) & 3U) << 9U | ((Bits >> 10U) & 1U) << 8U | + ((Bits >> 6U) & 1U) << 7U | ((Bits >> 7U) & 1U) << 6U | + ((Bits >> 1U) & 7U) << 3U | ((Bits >> 5U) & 1U) << 2U); +} + +[[nodiscard]] std::uint16_t encodeRVCBranchImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint16_t>(Value); + return static_cast<std::uint16_t>(((Bits >> 8U) & 1U) << 12U | ((Bits >> 3U) & 3U) << 10U | + ((Bits >> 6U) & 3U) << 5U | ((Bits >> 1U) & 3U) << 3U | + ((Bits >> 5U) & 1U) << 2U); +} + +[[nodiscard]] Expected<void> requireRelocationTargetKind(const mc::MCValue &Value, + std::uint32_t Expected, + std::string_view Name) { + if (Value.TargetKind != Expected) { + return makeUnexpected(ErrorCode::RelocationFailed, + std::string(Name) + " has an incompatible target expression"); + } + return {}; +} + +} // namespace + +Expected<mc::MCFixupKindInfo> RISCVAsmBackend::fixupKindInfo(mc::MCFixupKind Kind) const { + switch (Kind) { + case mc::fixup_kind::Data1: + return mc::MCFixupKindInfo{.StorageSize = 1, .PCRelative = false}; + case mc::fixup_kind::Data2: + return mc::MCFixupKindInfo{.StorageSize = 2, .PCRelative = false}; + case mc::fixup_kind::Data4: + return mc::MCFixupKindInfo{.StorageSize = 4, .PCRelative = false}; + case mc::fixup_kind::Data8: + return mc::MCFixupKindInfo{.StorageSize = 8, .PCRelative = false}; + case mc::fixup_kind::PCRelative4: + return mc::MCFixupKindInfo{.StorageSize = 4, .PCRelative = true}; + default: + break; + } + if (isTargetKind(Kind, RISCVFixupKind::RVCJump) || + isTargetKind(Kind, RISCVFixupKind::RVCBranch)) { + return mc::MCFixupKindInfo{.StorageSize = 2, .PCRelative = true}; + } + if (Kind >= fixupKind(RISCVFixupKind::High20) && Kind <= fixupKind(RISCVFixupKind::CallPLT)) { + const bool PCRelative = Kind >= fixupKind(RISCVFixupKind::PCRelativeHigh20); + return mc::MCFixupKindInfo{.StorageSize = 4, .PCRelative = PCRelative}; + } + return makeUnexpected(ErrorCode::InvalidArgument, "unknown RISC-V fixup kind"); +} + +Expected<void> RISCVAsmBackend::applyFixup(mc::MCFixupKind Kind, std::span<std::uint8_t> Data, + std::uint64_t Offset, std::int64_t Value) const { + auto Info = fixupKindInfo(Kind); + if (!Info) + return std::unexpected(Info.error()); + auto Storage = requireStorage(Data, Offset, Info->StorageSize); + if (!Storage) + return std::unexpected(Storage.error()); + + if (Kind == mc::fixup_kind::Data1 || Kind == mc::fixup_kind::Data2 || + Kind == mc::fixup_kind::Data4 || Kind == mc::fixup_kind::Data8 || + Kind == mc::fixup_kind::PCRelative4) { + writeLittleEndian(Data, Offset, static_cast<std::uint64_t>(Value), Info->StorageSize); + return {}; + } + + if (Info->StorageSize == 2) { + std::uint16_t Instruction = readHalf(Data, Offset); + if (isTargetKind(Kind, RISCVFixupKind::RVCJump)) { + auto Range = requireDisplacement(Value, 12, "RISC-V compressed jump"); + if (!Range) + return std::unexpected(Range.error()); + Instruction = static_cast<std::uint16_t>((Instruction & ~std::uint16_t{0x1ffc}) | + encodeRVCJumpImmediate(Value)); + } else { + auto Range = requireDisplacement(Value, 9, "RISC-V compressed branch"); + if (!Range) + return std::unexpected(Range.error()); + Instruction = static_cast<std::uint16_t>((Instruction & ~std::uint16_t{0x1c7c}) | + encodeRVCBranchImmediate(Value)); + } + writeLittleEndian(Data, Offset, Instruction, 2); + return {}; + } + + std::uint32_t Instruction = readWord(Data, Offset); + if (isTargetKind(Kind, RISCVFixupKind::High20) || + isTargetKind(Kind, RISCVFixupKind::PCRelativeHigh20) || + isTargetKind(Kind, RISCVFixupKind::CallPLT)) { + const std::uint64_t Adjusted = static_cast<std::uint64_t>(Value) + 0x800U; + const std::uint32_t High = static_cast<std::uint32_t>(Adjusted >> 12U); + Instruction = (Instruction & 0xfffU) | (High << 12U); + } else if (isTargetKind(Kind, RISCVFixupKind::Low12I) || + isTargetKind(Kind, RISCVFixupKind::PCRelativeLow12I)) { + Instruction = (Instruction & 0x000fffffU) | (static_cast<std::uint32_t>(Value) & 0xfffU) << 20U; + } else if (isTargetKind(Kind, RISCVFixupKind::Low12S) || + isTargetKind(Kind, RISCVFixupKind::PCRelativeLow12S)) { + const std::uint32_t Low = static_cast<std::uint32_t>(Value) & 0xfffU; + Instruction = (Instruction & ~std::uint32_t{0xfe000f80}) | ((Low >> 5U) & 0x7fU) << 25U | + (Low & 0x1fU) << 7U; + } else if (isTargetKind(Kind, RISCVFixupKind::JAL)) { + auto Range = requireDisplacement(Value, 21, "RISC-V JAL"); + if (!Range) + return std::unexpected(Range.error()); + Instruction = (Instruction & 0xfffU) | encodeJALImmediate(Value); + } else if (isTargetKind(Kind, RISCVFixupKind::Branch)) { + auto Range = requireDisplacement(Value, 13, "RISC-V branch"); + if (!Range) + return std::unexpected(Range.error()); + Instruction = (Instruction & ~std::uint32_t{0xfe000f80}) | encodeBranchImmediate(Value); + } else { + return makeUnexpected(ErrorCode::FixupApplicationFailed, + "RISC-V fixup cannot be applied to an instruction"); + } + writeLittleEndian(Data, Offset, Instruction, 4); + return {}; +} + +Expected<std::uint32_t> RISCVAsmBackend::relocationType(const mc::MCFixup &Fixup, + const mc::MCValue &Value) const { + const auto Relocation = [](RISCVRelocation Type) { return static_cast<std::uint32_t>(Type); }; + switch (Fixup.Kind) { + case mc::fixup_kind::Data4: + return Relocation(RISCVRelocation::Relocation32); + case mc::fixup_kind::Data8: + return Relocation(RISCVRelocation::Relocation64); + case mc::fixup_kind::PCRelative4: + return Relocation(RISCVRelocation::PCRelative32); + default: + break; + } + + if (isTargetKind(Fixup.Kind, RISCVFixupKind::PCRelativeHigh20)) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::PCRelativeHigh), "R_RISCV_PCREL_HI20"); + if (!Kind) + return std::unexpected(Kind.error()); + return Relocation(RISCVRelocation::PCRelativeHigh20); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::PCRelativeLow12I) || + isTargetKind(Fixup.Kind, RISCVFixupKind::PCRelativeLow12S)) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::PCRelativeLow), "R_RISCV_PCREL_LO12"); + if (!Kind) + return std::unexpected(Kind.error()); + return Relocation(isTargetKind(Fixup.Kind, RISCVFixupKind::PCRelativeLow12I) + ? RISCVRelocation::PCRelativeLow12I + : RISCVRelocation::PCRelativeLow12S); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::CallPLT)) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::CallPLT), "R_RISCV_CALL_PLT"); + if (!Kind) + return std::unexpected(Kind.error()); + return Relocation(RISCVRelocation::CallPLT); + } + if (Value.TargetKind != 0) { + return makeUnexpected(ErrorCode::RelocationFailed, + "RISC-V relocation has an unsupported target expression"); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::High20)) + return Relocation(RISCVRelocation::High20); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::Low12I)) + return Relocation(RISCVRelocation::Low12I); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::Low12S)) + return Relocation(RISCVRelocation::Low12S); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::JAL)) + return Relocation(RISCVRelocation::JAL); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::Branch)) + return Relocation(RISCVRelocation::Branch); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::RVCJump)) + return Relocation(RISCVRelocation::RVCJump); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::RVCBranch)) + return Relocation(RISCVRelocation::RVCBranch); + return makeUnexpected(ErrorCode::RelocationFailed, "RISC-V fixup has no ELF relocation type"); +} + +Expected<std::vector<mc::MCFixupRelocation>> +RISCVAsmBackend::relocationsForFixup(const mc::MCFixup &Fixup, const mc::MCValue &Value) const { + if (!Value.SubtractSymbol) + return mc::MCAsmBackend::relocationsForFixup(Fixup, Value); + if (!Value.AddSymbol || Value.TargetKind != 0) { + return makeUnexpected(ErrorCode::RelocationFailed, + "RISC-V symbol difference has an invalid relocation expression"); + } + + RISCVRelocation AddType; + RISCVRelocation SubtractType; + switch (Fixup.Kind) { + case mc::fixup_kind::Data1: + AddType = RISCVRelocation::Add8; + SubtractType = RISCVRelocation::Subtract8; + break; + case mc::fixup_kind::Data2: + AddType = RISCVRelocation::Add16; + SubtractType = RISCVRelocation::Subtract16; + break; + case mc::fixup_kind::Data4: + AddType = RISCVRelocation::Add32; + SubtractType = RISCVRelocation::Subtract32; + break; + case mc::fixup_kind::Data8: + AddType = RISCVRelocation::Add64; + SubtractType = RISCVRelocation::Subtract64; + break; + default: + return makeUnexpected(ErrorCode::RelocationFailed, + "RISC-V fixup cannot encode a symbol difference"); + } + return std::vector<mc::MCFixupRelocation>{ + {.Type = static_cast<std::uint32_t>(AddType), + .Symbol = Value.AddSymbol, + .Addend = Value.Addend}, + {.Type = static_cast<std::uint32_t>(SubtractType), + .Symbol = Value.SubtractSymbol, + .Addend = 0}, + }; +} + +bool RISCVAsmBackend::mayNeedRelaxation(const mc::MCInst &Instruction) const noexcept { + const auto Opcode = static_cast<InstructionOpcode>(Instruction.opcode().value()); + return Opcode == InstructionOpcode::C_J || Opcode == InstructionOpcode::C_BEQZ || + Opcode == InstructionOpcode::C_BNEZ; +} + +Expected<bool> RISCVAsmBackend::fixupNeedsRelaxation(const mc::MCFixup &Fixup, + std::int64_t Value) const { + if (isTargetKind(Fixup.Kind, RISCVFixupKind::RVCJump)) + return !fitsSigned(Value, 12) || (Value & 1) != 0; + if (isTargetKind(Fixup.Kind, RISCVFixupKind::RVCBranch)) + return !fitsSigned(Value, 9) || (Value & 1) != 0; + return false; +} + +Expected<mc::MCInst> RISCVAsmBackend::relaxInstruction(const mc::MCInst &Instruction) const { + const auto Opcode = static_cast<InstructionOpcode>(Instruction.opcode().value()); + const bool IsJump = Opcode == InstructionOpcode::C_J; + const bool IsBranch = Opcode == InstructionOpcode::C_BEQZ || Opcode == InstructionOpcode::C_BNEZ; + const std::size_t ExpectedOperands = IsJump ? 1U : 2U; + if ((!IsJump && !IsBranch) || Instruction.operands().size() != ExpectedOperands) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V MC relaxation received an unsupported instruction"); + } + + const mc::MCOperand &Target = Instruction.operands().back(); + if (Target.expressionValue() == nullptr) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V MC relaxation requires a symbolic target"); + } + const InstructionOpcode RelaxedOpcode = + IsJump + ? InstructionOpcode::JAL + : (Opcode == InstructionOpcode::C_BEQZ ? InstructionOpcode::BEQ : InstructionOpcode::BNE); + mc::MCInst Result(mc::MCOpcode::fromValue(static_cast<std::uint32_t>(RelaxedOpcode))); + Result.setFlags(Instruction.flags()); + Result.setSourceIdentity(Instruction.sourceIdentity()); + if (IsJump) { + Result.addOperand(mc::MCOperand::registerOperand( + mc::MCRegister::fromValue(static_cast<std::uint32_t>(PhysicalRegisterID::X0)))); + } else { + Result.addOperand(Instruction.operands().front()); + Result.addOperand(mc::MCOperand::registerOperand( + mc::MCRegister::fromValue(static_cast<std::uint32_t>(PhysicalRegisterID::X0)))); + } + Result.addOperand(Target); + return Result; +} + +std::uint32_t RISCVAsmBackend::objectFlags() const noexcept { + constexpr std::uint32_t RVC = 1; + constexpr std::uint32_t FloatABIDouble = 4; + return RVC | FloatABIDouble; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVMCCodeEmitter.cpp b/backend/lib/Target/RISCV/RISCVMCCodeEmitter.cpp new file mode 100644 index 0000000..5f98887 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMCCodeEmitter.cpp @@ -0,0 +1,984 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVMCCodeEmitter.h" + +#include "rcc/CodeGen/Register.h" +#include "rcc/MC/MCExpr.h" +#include "rcc/Target/RISCV/RISCVFixupKinds.h" +#include "rcc/Target/RISCV/RISCVInstPrinter.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <cstddef> +#include <cstdint> +#include <string> +#include <string_view> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] Expected<void> requireOperandCount(const mc::MCInst &Instruction, std::size_t Count) { + if (Instruction.operands().size() != Count) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction has an unexpected encoding operand count"); + } + return {}; +} + +[[nodiscard]] Expected<std::uint32_t> registerEncoding(const mc::MCInst &Instruction, + std::size_t Index, + const TargetDescription &Target) { + if (Index >= Instruction.operands().size()) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding references an absent register operand"); + } + const mc::MCRegister *Register = Instruction.operands()[Index].registerValue(); + if (Register == nullptr) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding requires a register operand"); + } + auto Physical = PhysicalRegister::create(Register->value()); + if (!Physical) + return std::unexpected(Physical.error()); + const PhysicalRegisterDesc *Description = Target.registers().physicalRegister(*Physical); + if (Description == nullptr || Description->Encoding >= 32) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding references an unknown physical register"); + } + return Description->Encoding; +} + +[[nodiscard]] Expected<void> validateRegisterOperands(const mc::MCInst &Instruction, + const InstructionDescriptor &Descriptor, + const TargetDescription &Target) { + for (std::size_t Index = 0; Index < Instruction.operands().size(); ++Index) { + const mc::MCRegister *Register = Instruction.operands()[Index].registerValue(); + if (Register == nullptr) + continue; + if (Index >= Descriptor.Operands.size()) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V register operand has no target descriptor"); + } + auto Physical = PhysicalRegister::create(Register->value()); + if (!Physical) + return std::unexpected(Physical.error()); + const OperandDescriptor &Operand = Descriptor.Operands[Index]; + if (Operand.RegClass && + !Target.registers().registerClassContains(*Operand.RegClass, *Physical)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V register is outside its required register class"); + } + if (Operand.RegBank && !Target.registers().bankContainsRegister(*Operand.RegBank, *Physical)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V register is outside its required register bank"); + } + } + return {}; +} + +[[nodiscard]] Expected<std::uint32_t> compactRegisterEncoding(const mc::MCInst &Instruction, + std::size_t Index, + const TargetDescription &Target) { + auto Encoding = registerEncoding(Instruction, Index, Target); + if (!Encoding) + return std::unexpected(Encoding.error()); + if (*Encoding < 8 || *Encoding > 15) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "compressed RISC-V instruction requires an x8-x15 register"); + } + return *Encoding - 8U; +} + +[[nodiscard]] bool fitsSigned(std::int64_t Value, unsigned Bits) noexcept { + const std::int64_t Minimum = -(std::int64_t{1} << (Bits - 1U)); + const std::int64_t Maximum = (std::int64_t{1} << (Bits - 1U)) - 1; + return Value >= Minimum && Value <= Maximum; +} + +[[nodiscard]] bool fitsUnsigned(std::int64_t Value, unsigned Bits) noexcept { + return Value >= 0 && static_cast<std::uint64_t>(Value) < (std::uint64_t{1} << Bits); +} + +[[nodiscard]] Expected<void> requireSigned(std::int64_t Value, unsigned Bits, + std::string_view Name) { + if (!fitsSigned(Value, Bits)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + std::string(Name) + " immediate is out of range"); + } + return {}; +} + +[[nodiscard]] Expected<void> requireUnsigned(std::int64_t Value, unsigned Bits, + std::string_view Name) { + if (!fitsUnsigned(Value, Bits)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + std::string(Name) + " immediate is out of range"); + } + return {}; +} + +[[nodiscard]] Expected<void> requireRoundingMode(std::int64_t Value) { + if ((Value >= 0 && Value <= 4) || Value == 7) + return {}; + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V rounding mode is not an ISA-defined value"); +} + +[[nodiscard]] Expected<void> requireAligned(std::int64_t Value, std::uint64_t Alignment, + std::string_view Name) { + if (Value % static_cast<std::int64_t>(Alignment) != 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + std::string(Name) + " immediate is not aligned"); + } + return {}; +} + +[[nodiscard]] Expected<std::uint32_t> expressionTargetKind(const mc::MCOperand &Operand) { + const mc::MCExprRef *Expression = Operand.expressionValue(); + if (Expression == nullptr) + return 0U; + if (!*Expression) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction has a null expression operand"); + } + auto Value = mc::evaluateAsRelocatable(**Expression); + if (!Value) + return std::unexpected(Value.error()); + return Value->TargetKind; +} + +[[nodiscard]] Expected<std::int64_t> immediateOrFixup(const mc::MCInst &Instruction, + std::size_t Index, mc::MCFixupKind Kind, + bool PCRelative, + mc::MCEncodedInstruction &Result) { + if (Index >= Instruction.operands().size()) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding references an absent immediate operand"); + } + const mc::MCOperand &Operand = Instruction.operands()[Index]; + if (const std::int64_t *Immediate = Operand.immediateValue(); Immediate != nullptr) + return *Immediate; + const mc::MCExprRef *Expression = Operand.expressionValue(); + if (Expression == nullptr || !*Expression) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding requires an immediate or expression operand"); + } + if (const auto Absolute = (*Expression)->evaluateAbsolute()) + return *Absolute; + if (Kind == mc::fixup_kind::None) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction does not permit a symbolic immediate here"); + } + Result.Fixups.push_back(mc::MCFixup{.Offset = 0, + .Value = *Expression, + .Kind = Kind, + .PCRelative = PCRelative, + .LinkerRelaxable = false}); + return 0; +} + +[[nodiscard]] Expected<mc::MCFixupKind> uFixupKind(const mc::MCOperand &Operand) { + auto Kind = expressionTargetKind(Operand); + if (!Kind) + return std::unexpected(Kind.error()); + if (*Kind == 0) + return fixupKind(RISCVFixupKind::High20); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::PCRelativeHigh)) + return fixupKind(RISCVFixupKind::PCRelativeHigh20); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::CallPLT)) + return fixupKind(RISCVFixupKind::CallPLT); + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V U-type operand has an incompatible target expression"); +} + +[[nodiscard]] Expected<mc::MCFixupKind> lowFixupKind(const mc::MCOperand &Operand, bool Store) { + auto Kind = expressionTargetKind(Operand); + if (!Kind) + return std::unexpected(Kind.error()); + if (*Kind == 0) + return fixupKind(Store ? RISCVFixupKind::Low12S : RISCVFixupKind::Low12I); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::PCRelativeLow)) { + return fixupKind(Store ? RISCVFixupKind::PCRelativeLow12S : RISCVFixupKind::PCRelativeLow12I); + } + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V low immediate has an incompatible target expression"); +} + +[[nodiscard]] Expected<void> requirePlainExpression(const mc::MCOperand &Operand, + std::string_view Name) { + auto Kind = expressionTargetKind(Operand); + if (!Kind) + return std::unexpected(Kind.error()); + if (*Kind != 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + std::string(Name) + " does not accept a target expression modifier"); + } + return {}; +} + +[[nodiscard]] std::uint32_t encodeBranchImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint32_t>(Value); + return ((Bits >> 12U) & 1U) << 31U | ((Bits >> 5U) & 0x3fU) << 25U | ((Bits >> 1U) & 0xfU) << 8U | + ((Bits >> 11U) & 1U) << 7U; +} + +[[nodiscard]] std::uint32_t encodeJALImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint32_t>(Value); + return ((Bits >> 20U) & 1U) << 31U | ((Bits >> 1U) & 0x3ffU) << 21U | + ((Bits >> 11U) & 1U) << 20U | ((Bits >> 12U) & 0xffU) << 12U; +} + +[[nodiscard]] std::uint16_t encodeRVCJumpImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint16_t>(Value); + return static_cast<std::uint16_t>(((Bits >> 11U) & 1U) << 12U | ((Bits >> 4U) & 1U) << 11U | + ((Bits >> 8U) & 3U) << 9U | ((Bits >> 10U) & 1U) << 8U | + ((Bits >> 6U) & 1U) << 7U | ((Bits >> 7U) & 1U) << 6U | + ((Bits >> 1U) & 7U) << 3U | ((Bits >> 5U) & 1U) << 2U); +} + +[[nodiscard]] std::uint16_t encodeRVCBranchImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint16_t>(Value); + return static_cast<std::uint16_t>(((Bits >> 8U) & 1U) << 12U | ((Bits >> 3U) & 3U) << 10U | + ((Bits >> 6U) & 3U) << 5U | ((Bits >> 1U) & 3U) << 3U | + ((Bits >> 5U) & 1U) << 2U); +} + +[[nodiscard]] Expected<std::uint32_t> encodeRType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly) { + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + std::size_t RegisterCount = 0; + bool HasRoundingMode = false; + switch (Format) { + case AssemblyFormat::RD_RS1_RS2: + case AssemblyFormat::FRD_FRS1_FRS2: + case AssemblyFormat::RD_FRS1_FRS2: + RegisterCount = 3; + break; + case AssemblyFormat::FRD_FRS1_FRS2_RM: + RegisterCount = 3; + HasRoundingMode = true; + break; + case AssemblyFormat::FRD_FRS1_RM: + case AssemblyFormat::RD_FRS1_RM: + case AssemblyFormat::FRD_RS1_RM: + RegisterCount = 2; + HasRoundingMode = true; + break; + case AssemblyFormat::RD_FRS1: + case AssemblyFormat::FRD_RS1: + RegisterCount = 2; + break; + default: + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported RISC-V R-type assembly format"); + } + auto Count = requireOperandCount(Instruction, RegisterCount + (HasRoundingMode ? 1U : 0U)); + if (!Count) + return std::unexpected(Count.error()); + + std::uint32_t Encoding = Assembly.Encoding & ~std::uint32_t{0x000f8f80}; + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source1 = registerEncoding(Instruction, 1, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source1) + return std::unexpected(Source1.error()); + Encoding |= *Destination << 7U | *Source1 << 15U; + if (RegisterCount == 3) { + auto Source2 = registerEncoding(Instruction, 2, Target); + if (!Source2) + return std::unexpected(Source2.error()); + Encoding = (Encoding & ~std::uint32_t{0x01f00000}) | *Source2 << 20U; + } + return Encoding; +} + +[[nodiscard]] Expected<std::uint32_t> encodeRTypeWithResult(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Encoding = encodeRType(Instruction, Target, Assembly); + if (!Encoding) + return std::unexpected(Encoding.error()); + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + std::size_t ModeIndex = 0; + switch (Format) { + case AssemblyFormat::FRD_FRS1_FRS2_RM: + ModeIndex = 3; + break; + case AssemblyFormat::FRD_FRS1_RM: + case AssemblyFormat::RD_FRS1_RM: + case AssemblyFormat::FRD_RS1_RM: + ModeIndex = 2; + break; + default: + return *Encoding; + } + auto Mode = immediateOrFixup(Instruction, ModeIndex, mc::fixup_kind::None, false, Result); + if (!Mode) + return std::unexpected(Mode.error()); + auto Range = requireRoundingMode(*Mode); + if (!Range) + return std::unexpected(Range.error()); + return (*Encoding & ~std::uint32_t{0x7000}) | static_cast<std::uint32_t>(*Mode) << 12U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeIType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + if (Format == AssemblyFormat::NONE) { + auto Count = requireOperandCount(Instruction, 0); + if (!Count) + return std::unexpected(Count.error()); + return Assembly.Encoding; + } + if (Format == AssemblyFormat::IMM_IMM) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Predecessor = immediateOrFixup(Instruction, 0, mc::fixup_kind::None, false, Result); + auto Successor = immediateOrFixup(Instruction, 1, mc::fixup_kind::None, false, Result); + if (!Predecessor) + return std::unexpected(Predecessor.error()); + if (!Successor) + return std::unexpected(Successor.error()); + auto PredRange = requireUnsigned(*Predecessor, 4, "RISC-V fence predecessor"); + auto SuccRange = requireUnsigned(*Successor, 4, "RISC-V fence successor"); + if (!PredRange) + return std::unexpected(PredRange.error()); + if (!SuccRange) + return std::unexpected(SuccRange.error()); + return (Assembly.Encoding & ~std::uint32_t{0x0ff00000}) | + static_cast<std::uint32_t>(*Predecessor) << 24U | + static_cast<std::uint32_t>(*Successor) << 20U; + } + if (Format != AssemblyFormat::RD_RS1_IMM && Format != AssemblyFormat::RD_OFFSET_RS1 && + Format != AssemblyFormat::FRD_OFFSET_RS1) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported RISC-V I-type assembly format"); + } + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source = registerEncoding(Instruction, 1, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source) + return std::unexpected(Source.error()); + std::uint32_t Encoding = + (Assembly.Encoding & ~std::uint32_t{0x000f8f80}) | *Destination << 7U | *Source << 15U; + + const auto Opcode = static_cast<InstructionOpcode>(Instruction.opcode().value()); + const bool Shift64 = Opcode == InstructionOpcode::SLLI || Opcode == InstructionOpcode::SRLI || + Opcode == InstructionOpcode::SRAI; + const bool Shift32 = Opcode == InstructionOpcode::SLLIW || Opcode == InstructionOpcode::SRLIW || + Opcode == InstructionOpcode::SRAIW; + if (Shift64 || Shift32) { + auto Immediate = immediateOrFixup(Instruction, 2, mc::fixup_kind::None, false, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + auto Range = requireUnsigned(*Immediate, Shift64 ? 6U : 5U, "RISC-V shift"); + if (!Range) + return std::unexpected(Range.error()); + const std::uint32_t Mask = Shift64 ? 0x03f00000U : 0x01f00000U; + return (Encoding & ~Mask) | static_cast<std::uint32_t>(*Immediate) << 20U; + } + + auto Kind = lowFixupKind(Instruction.operands()[2], false); + if (!Kind) + return std::unexpected(Kind.error()); + auto Immediate = immediateOrFixup(Instruction, 2, *Kind, + *Kind == fixupKind(RISCVFixupKind::PCRelativeLow12I), Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[2].expressionValue() == nullptr) { + auto Range = requireSigned(*Immediate, 12, "RISC-V I-type"); + if (!Range) + return std::unexpected(Range.error()); + } + return (Encoding & ~std::uint32_t{0xfff00000}) | (static_cast<std::uint32_t>(*Immediate) & 0xfffU) + << 20U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeSType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Source2 = registerEncoding(Instruction, 0, Target); + auto Source1 = registerEncoding(Instruction, 1, Target); + if (!Source2) + return std::unexpected(Source2.error()); + if (!Source1) + return std::unexpected(Source1.error()); + auto Kind = lowFixupKind(Instruction.operands()[2], true); + if (!Kind) + return std::unexpected(Kind.error()); + auto Immediate = immediateOrFixup(Instruction, 2, *Kind, + *Kind == fixupKind(RISCVFixupKind::PCRelativeLow12S), Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[2].expressionValue() == nullptr) { + auto Range = requireSigned(*Immediate, 12, "RISC-V S-type"); + if (!Range) + return std::unexpected(Range.error()); + } + const std::uint32_t Bits = static_cast<std::uint32_t>(*Immediate) & 0xfffU; + return (Assembly.Encoding & ~std::uint32_t{0xffff8f80}) | *Source1 << 15U | *Source2 << 20U | + ((Bits >> 5U) & 0x7fU) << 25U | (Bits & 0x1fU) << 7U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeBType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Source1 = registerEncoding(Instruction, 0, Target); + auto Source2 = registerEncoding(Instruction, 1, Target); + if (!Source1) + return std::unexpected(Source1.error()); + if (!Source2) + return std::unexpected(Source2.error()); + auto Plain = requirePlainExpression(Instruction.operands()[2], "RISC-V branch"); + if (!Plain) + return std::unexpected(Plain.error()); + auto Immediate = + immediateOrFixup(Instruction, 2, fixupKind(RISCVFixupKind::Branch), true, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[2].expressionValue() == nullptr && + (!fitsSigned(*Immediate, 13) || (*Immediate & 1) != 0)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V branch displacement is out of range or unaligned"); + } + return (Assembly.Encoding & ~std::uint32_t{0xffff8f80}) | *Source1 << 15U | *Source2 << 20U | + encodeBranchImmediate(*Immediate); +} + +[[nodiscard]] Expected<std::uint32_t> encodeUType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + if (!Destination) + return std::unexpected(Destination.error()); + auto Kind = uFixupKind(Instruction.operands()[1]); + if (!Kind) + return std::unexpected(Kind.error()); + const bool PCRelative = *Kind == fixupKind(RISCVFixupKind::PCRelativeHigh20) || + *Kind == fixupKind(RISCVFixupKind::CallPLT); + auto Immediate = immediateOrFixup(Instruction, 1, *Kind, PCRelative, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[1].expressionValue() == nullptr) { + auto Range = requireSigned(*Immediate, 20, "RISC-V U-type"); + if (!Range) + return std::unexpected(Range.error()); + } + return (Assembly.Encoding & 0x7fU) | *Destination << 7U | + (static_cast<std::uint32_t>(*Immediate) & 0xfffffU) << 12U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeJType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + if (!Destination) + return std::unexpected(Destination.error()); + auto Plain = requirePlainExpression(Instruction.operands()[1], "RISC-V JAL"); + if (!Plain) + return std::unexpected(Plain.error()); + auto Immediate = immediateOrFixup(Instruction, 1, fixupKind(RISCVFixupKind::JAL), true, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[1].expressionValue() == nullptr && + (!fitsSigned(*Immediate, 21) || (*Immediate & 1) != 0)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V JAL displacement is out of range or unaligned"); + } + return (Assembly.Encoding & 0x7fU) | *Destination << 7U | encodeJALImmediate(*Immediate); +} + +[[nodiscard]] Expected<std::uint32_t> encodeSystem(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + if (Format == AssemblyFormat::NONE) { + auto Count = requireOperandCount(Instruction, 0); + if (!Count) + return std::unexpected(Count.error()); + return Assembly.Encoding; + } + if (Format != AssemblyFormat::RD_CSR_RS1 && Format != AssemblyFormat::RD_CSR_IMM) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported RISC-V system assembly format"); + } + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + if (!Destination) + return std::unexpected(Destination.error()); + auto CSR = immediateOrFixup(Instruction, 1, mc::fixup_kind::None, false, Result); + if (!CSR) + return std::unexpected(CSR.error()); + auto CSRRange = requireUnsigned(*CSR, 12, "RISC-V CSR"); + if (!CSRRange) + return std::unexpected(CSRRange.error()); + std::uint32_t Source = 0; + if (Format == AssemblyFormat::RD_CSR_RS1) { + auto Register = registerEncoding(Instruction, 2, Target); + if (!Register) + return std::unexpected(Register.error()); + Source = *Register; + } else { + auto Immediate = immediateOrFixup(Instruction, 2, mc::fixup_kind::None, false, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + auto Range = requireUnsigned(*Immediate, 5, "RISC-V CSR immediate"); + if (!Range) + return std::unexpected(Range.error()); + Source = static_cast<std::uint32_t>(*Immediate); + } + return (Assembly.Encoding & ~std::uint32_t{0xffff8f80}) | *Destination << 7U | Source << 15U | + static_cast<std::uint32_t>(*CSR) << 20U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeAMO(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly) { + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + const bool IsLoadReserved = Format == AssemblyFormat::RD_PAREN_RS1; + if (!IsLoadReserved && Format != AssemblyFormat::RD_RS2_PAREN_RS1) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported RISC-V atomic assembly format"); + } + auto Count = requireOperandCount(Instruction, IsLoadReserved ? 2U : 3U); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source1 = registerEncoding(Instruction, IsLoadReserved ? 1U : 2U, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source1) + return std::unexpected(Source1.error()); + std::uint32_t Encoding = + (Assembly.Encoding & ~std::uint32_t{0x000f8f80}) | *Destination << 7U | *Source1 << 15U; + if (!IsLoadReserved) { + auto Source2 = registerEncoding(Instruction, 1, Target); + if (!Source2) + return std::unexpected(Source2.error()); + Encoding = (Encoding & ~std::uint32_t{0x01f00000}) | *Source2 << 20U; + } + return Encoding; +} + +[[nodiscard]] Expected<std::uint32_t> encodeR4(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 5); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source1 = registerEncoding(Instruction, 1, Target); + auto Source2 = registerEncoding(Instruction, 2, Target); + auto Source3 = registerEncoding(Instruction, 3, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source1) + return std::unexpected(Source1.error()); + if (!Source2) + return std::unexpected(Source2.error()); + if (!Source3) + return std::unexpected(Source3.error()); + auto Mode = immediateOrFixup(Instruction, 4, mc::fixup_kind::None, false, Result); + if (!Mode) + return std::unexpected(Mode.error()); + auto Range = requireRoundingMode(*Mode); + if (!Range) + return std::unexpected(Range.error()); + return (Assembly.Encoding & ~std::uint32_t{0xf9ffff80}) | *Destination << 7U | + static_cast<std::uint32_t>(*Mode) << 12U | *Source1 << 15U | *Source2 << 20U | + *Source3 << 27U; +} + +[[nodiscard]] Expected<std::uint16_t> encodeCompressed(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + const auto Opcode = static_cast<InstructionOpcode>(Instruction.opcode().value()); + std::uint16_t Encoding = static_cast<std::uint16_t>(Assembly.Encoding); + const auto Immediate = [&](std::size_t Index, mc::MCFixupKind Kind = mc::fixup_kind::None, + bool PCRelative = false) { + return immediateOrFixup(Instruction, Index, Kind, PCRelative, Result); + }; + + if (Opcode == InstructionOpcode::C_ADDI4SPN) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = compactRegisterEncoding(Instruction, 0, Target); + auto Value = Immediate(1); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Value) + return std::unexpected(Value.error()); + if (!fitsUnsigned(*Value, 10) || *Value == 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "C.ADDI4SPN immediate is out of range or zero"); + } + auto Alignment = requireAligned(*Value, 4, "C.ADDI4SPN"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Value); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | + ((Bits >> 4U) & 3U) << 11U | ((Bits >> 6U) & 0xfU) << 7U | + ((Bits >> 2U) & 1U) << 6U | ((Bits >> 3U) & 1U) << 5U | + *Destination << 2U); + } + + const bool CompactDoubleLoad = + Opcode == InstructionOpcode::C_FLD || Opcode == InstructionOpcode::C_LD; + const bool CompactWordLoad = Opcode == InstructionOpcode::C_LW; + const bool CompactDoubleStore = + Opcode == InstructionOpcode::C_FSD || Opcode == InstructionOpcode::C_SD; + const bool CompactWordStore = Opcode == InstructionOpcode::C_SW; + if (CompactDoubleLoad || CompactWordLoad || CompactDoubleStore || CompactWordStore) { + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto ValueRegister = compactRegisterEncoding(Instruction, 0, Target); + auto Base = compactRegisterEncoding(Instruction, 1, Target); + auto Offset = Immediate(2); + if (!ValueRegister) + return std::unexpected(ValueRegister.error()); + if (!Base) + return std::unexpected(Base.error()); + if (!Offset) + return std::unexpected(Offset.error()); + const bool IsDouble = CompactDoubleLoad || CompactDoubleStore; + auto Range = requireUnsigned(*Offset, IsDouble ? 8U : 7U, "compressed load/store"); + if (!Range) + return std::unexpected(Range.error()); + auto Alignment = requireAligned(*Offset, IsDouble ? 8U : 4U, "compressed load/store"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Offset); + std::uint16_t ImmediateBits = static_cast<std::uint16_t>(((Bits >> 3U) & 7U) << 10U); + ImmediateBits |= + IsDouble + ? static_cast<std::uint16_t>(((Bits >> 6U) & 3U) << 5U) + : static_cast<std::uint16_t>(((Bits >> 2U) & 1U) << 6U | ((Bits >> 6U) & 1U) << 5U); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | ImmediateBits | + *Base << 7U | *ValueRegister << 2U); + } + + const bool CI = Opcode == InstructionOpcode::C_ADDI || Opcode == InstructionOpcode::C_ADDIW || + Opcode == InstructionOpcode::C_LI || Opcode == InstructionOpcode::C_LUI || + Opcode == InstructionOpcode::C_SLLI; + if (CI) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Register = registerEncoding(Instruction, 0, Target); + auto Value = Immediate(1); + if (!Register) + return std::unexpected(Register.error()); + if (!Value) + return std::unexpected(Value.error()); + const bool IsShift = Opcode == InstructionOpcode::C_SLLI; + auto Range = + IsShift ? requireUnsigned(*Value, 6, "C.SLLI") : requireSigned(*Value, 6, "compressed CI"); + if (!Range) + return std::unexpected(Range.error()); + if (Opcode == InstructionOpcode::C_LUI && *Value == 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "C.LUI immediate must be nonzero"); + } + const auto Bits = static_cast<std::uint16_t>(*Value); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | + ((Bits >> 5U) & 1U) << 12U | *Register << 7U | + (Bits & 0x1fU) << 2U); + } + + if (Opcode == InstructionOpcode::C_ADDI16SP) { + auto Count = requireOperandCount(Instruction, 1); + if (!Count) + return std::unexpected(Count.error()); + auto Value = Immediate(0); + if (!Value) + return std::unexpected(Value.error()); + if (!fitsSigned(*Value, 10) || *Value == 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "C.ADDI16SP immediate is out of range or zero"); + } + auto Alignment = requireAligned(*Value, 16, "C.ADDI16SP"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Value); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x107c}) | + ((Bits >> 9U) & 1U) << 12U | ((Bits >> 4U) & 1U) << 6U | + ((Bits >> 6U) & 1U) << 5U | ((Bits >> 7U) & 3U) << 3U | + ((Bits >> 5U) & 1U) << 2U); + } + + const bool CompactShift = + Opcode == InstructionOpcode::C_SRLI || Opcode == InstructionOpcode::C_SRAI; + if (CompactShift || Opcode == InstructionOpcode::C_ANDI) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Register = compactRegisterEncoding(Instruction, 0, Target); + auto Value = Immediate(1); + if (!Register) + return std::unexpected(Register.error()); + if (!Value) + return std::unexpected(Value.error()); + auto Range = CompactShift ? requireUnsigned(*Value, 6, "compressed shift") + : requireSigned(*Value, 6, "C.ANDI"); + if (!Range) + return std::unexpected(Range.error()); + const auto Bits = static_cast<std::uint16_t>(*Value); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x13fc}) | + ((Bits >> 5U) & 1U) << 12U | *Register << 7U | + (Bits & 0x1fU) << 2U); + } + + const bool CompactALU = + Opcode == InstructionOpcode::C_SUB || Opcode == InstructionOpcode::C_XOR || + Opcode == InstructionOpcode::C_OR || Opcode == InstructionOpcode::C_AND || + Opcode == InstructionOpcode::C_SUBW || Opcode == InstructionOpcode::C_ADDW; + if (CompactALU) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = compactRegisterEncoding(Instruction, 0, Target); + auto Source = compactRegisterEncoding(Instruction, 1, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source) + return std::unexpected(Source.error()); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x039c}) | *Destination << 7U | + *Source << 2U); + } + + if (Opcode == InstructionOpcode::C_J) { + auto Count = requireOperandCount(Instruction, 1); + if (!Count) + return std::unexpected(Count.error()); + auto Plain = requirePlainExpression(Instruction.operands()[0], "C.J"); + if (!Plain) + return std::unexpected(Plain.error()); + auto Value = Immediate(0, fixupKind(RISCVFixupKind::RVCJump), true); + if (!Value) + return std::unexpected(Value.error()); + if (Instruction.operands()[0].expressionValue() == nullptr && + (!fitsSigned(*Value, 12) || (*Value & 1) != 0)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "C.J displacement is out of range or unaligned"); + } + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | + encodeRVCJumpImmediate(*Value)); + } + + if (Opcode == InstructionOpcode::C_BEQZ || Opcode == InstructionOpcode::C_BNEZ) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Source = compactRegisterEncoding(Instruction, 0, Target); + if (!Source) + return std::unexpected(Source.error()); + auto Plain = requirePlainExpression(Instruction.operands()[1], "compressed branch"); + if (!Plain) + return std::unexpected(Plain.error()); + auto Value = Immediate(1, fixupKind(RISCVFixupKind::RVCBranch), true); + if (!Value) + return std::unexpected(Value.error()); + if (Instruction.operands()[1].expressionValue() == nullptr && + (!fitsSigned(*Value, 9) || (*Value & 1) != 0)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "compressed branch displacement is out of range or unaligned"); + } + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | *Source << 7U | + encodeRVCBranchImmediate(*Value)); + } + + const bool StackDoubleLoad = + Opcode == InstructionOpcode::C_FLDSP || Opcode == InstructionOpcode::C_LDSP; + const bool StackWordLoad = Opcode == InstructionOpcode::C_LWSP; + if (StackDoubleLoad || StackWordLoad) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Offset = Immediate(1); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Offset) + return std::unexpected(Offset.error()); + auto Range = requireUnsigned(*Offset, StackDoubleLoad ? 9U : 8U, "stack load"); + if (!Range) + return std::unexpected(Range.error()); + auto Alignment = requireAligned(*Offset, StackDoubleLoad ? 8U : 4U, "stack load"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Offset); + const std::uint16_t ImmediateBits = + StackDoubleLoad + ? static_cast<std::uint16_t>(((Bits >> 5U) & 1U) << 12U | ((Bits >> 3U) & 3U) << 5U | + ((Bits >> 6U) & 7U) << 2U) + : static_cast<std::uint16_t>(((Bits >> 5U) & 1U) << 12U | ((Bits >> 2U) & 7U) << 4U | + ((Bits >> 6U) & 3U) << 2U); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | ImmediateBits | + *Destination << 7U); + } + + const bool CRSingle = Opcode == InstructionOpcode::C_JR || Opcode == InstructionOpcode::C_JALR; + const bool CRDouble = Opcode == InstructionOpcode::C_MV || Opcode == InstructionOpcode::C_ADD; + if (CRSingle || CRDouble) { + auto Count = requireOperandCount(Instruction, CRSingle ? 1U : 2U); + if (!Count) + return std::unexpected(Count.error()); + auto First = registerEncoding(Instruction, 0, Target); + if (!First) + return std::unexpected(First.error()); + Encoding = static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x0f80}) | *First << 7U); + if (CRDouble) { + auto Second = registerEncoding(Instruction, 1, Target); + if (!Second) + return std::unexpected(Second.error()); + Encoding = static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x007c}) | *Second << 2U); + } + return Encoding; + } + + if (Opcode == InstructionOpcode::C_EBREAK) { + auto Count = requireOperandCount(Instruction, 0); + if (!Count) + return std::unexpected(Count.error()); + return Encoding; + } + + const bool StackDoubleStore = + Opcode == InstructionOpcode::C_FSDSP || Opcode == InstructionOpcode::C_SDSP; + const bool StackWordStore = Opcode == InstructionOpcode::C_SWSP; + if (StackDoubleStore || StackWordStore) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Source = registerEncoding(Instruction, 0, Target); + auto Offset = Immediate(1); + if (!Source) + return std::unexpected(Source.error()); + if (!Offset) + return std::unexpected(Offset.error()); + auto Range = requireUnsigned(*Offset, StackDoubleStore ? 9U : 8U, "stack store"); + if (!Range) + return std::unexpected(Range.error()); + auto Alignment = requireAligned(*Offset, StackDoubleStore ? 8U : 4U, "stack store"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Offset); + const std::uint16_t ImmediateBits = + StackDoubleStore + ? static_cast<std::uint16_t>(((Bits >> 3U) & 7U) << 10U | ((Bits >> 6U) & 7U) << 7U) + : static_cast<std::uint16_t>(((Bits >> 2U) & 0xfU) << 9U | ((Bits >> 6U) & 3U) << 7U); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | ImmediateBits | + *Source << 2U); + } + + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported compressed RISC-V instruction"); +} + +void appendLittleEndian(mc::MCEncodedInstruction &Result, std::uint32_t Encoding, + std::uint8_t Size) { + Result.Bytes.reserve(Size); + for (std::uint8_t Index = 0; Index < Size; ++Index) + Result.Bytes.push_back(static_cast<std::uint8_t>(Encoding >> (Index * 8U))); +} + +} // namespace + +Expected<mc::MCEncodedInstruction> +RISCVMCCodeEmitter::encodeInstruction(const mc::MCInst &Instruction) const { + if (!Instruction.opcode().isValid()) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "cannot encode an invalid RISC-V MC opcode"); + } + const Opcode OpcodeValue = Opcode::fromValue(Instruction.opcode().value()); + const InstructionDescriptor *Descriptor = Target->instructionDescriptor(OpcodeValue); + const AssemblyInstructionInfo *Assembly = assemblyInstruction(OpcodeValue); + if (Descriptor == nullptr || Assembly == nullptr || Assembly->Encoding == 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction has no binary encoding description"); + } + auto Registers = validateRegisterOperands(Instruction, *Descriptor, *Target); + if (!Registers) + return std::unexpected(Registers.error()); + + mc::MCEncodedInstruction Result; + const auto Category = static_cast<EncodingCategory>(Descriptor->EncodingCategory.value()); + Expected<std::uint32_t> Encoding = makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction has no encoding category"); + switch (Category) { + case EncodingCategory::R: + Encoding = encodeRTypeWithResult(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::I: + Encoding = encodeIType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::S: + Encoding = encodeSType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::B: + Encoding = encodeBType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::U: + Encoding = encodeUType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::J: + Encoding = encodeJType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::SYSTEM: + Encoding = encodeSystem(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::AMO: + Encoding = encodeAMO(Instruction, *Target, *Assembly); + break; + case EncodingCategory::R4: + Encoding = encodeR4(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::C: { + auto Compressed = encodeCompressed(Instruction, *Target, *Assembly, Result); + if (!Compressed) + return std::unexpected(Compressed.error()); + appendLittleEndian(Result, *Compressed, 2); + return Result; + } + case EncodingCategory::None: + break; + } + if (!Encoding) + return std::unexpected(Encoding.error()); + appendLittleEndian(Result, *Encoding, 4); + return Result; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp b/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp index 4e8d5ec..19b1b11 100644 --- a/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp +++ b/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp @@ -22,6 +22,7 @@ #include "rcc/MC/MCStreamer.h" #include "rcc/MC/MCSymbol.h" #include "rcc/Target/RISCV/RISCVTarget.h" +#include "rcc/Target/RISCV/RISCVTargetRelocationKinds.h" #include <algorithm> #include <bit> @@ -53,6 +54,15 @@ struct TypeLayout final { std::uint64_t Alignment = 1; }; +[[nodiscard]] Expected<std::uint8_t> targetRelocationSize(TargetRelocationKindID Kind) { + if (Kind == targetRelocationKind(RISCVTargetRelocationKind::Absolute32)) + return 4; + if (Kind == targetRelocationKind(RISCVTargetRelocationKind::Absolute64)) + return 8; + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC target constant has an unknown relocation kind"); +} + [[nodiscard]] Expected<std::uint64_t> checkedAdd(std::uint64_t Left, std::uint64_t Right) { std::uint64_t Result = 0; if (__builtin_add_overflow(Left, Right, &Result)) @@ -612,6 +622,80 @@ struct TypeLayout final { return {}; } +[[nodiscard]] Expected<void> emitTargetBytes(std::span<const std::byte> Bytes, + mc::MCStreamer &Streamer) { + if (Bytes.empty()) + return {}; + std::vector<std::uint8_t> Storage; + Storage.reserve(Bytes.size()); + for (const std::byte Byte : Bytes) + Storage.push_back(std::to_integer<std::uint8_t>(Byte)); + return Streamer.emitBytes(Storage); +} + +[[nodiscard]] Expected<void> emitTargetConstantImpl(const TargetConstant &Constant, + MachineMCContext &Context, + mc::MCStreamer &Streamer) { + auto Emission = Constant.emit(); + if (!Emission) + return std::unexpected(Emission.error()); + auto DeclaredKind = Constant.relocationKind(); + if (!DeclaredKind) + return std::unexpected(DeclaredKind.error()); + if (Emission->Fixups.empty() != !DeclaredKind->has_value()) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RV64GC target constant relocation declaration is inconsistent"); + } + if (Emission->Fixups.empty()) + return emitTargetBytes(Emission->Bytes, Streamer); + + std::vector<const TargetConstantFixup *> Fixups; + Fixups.reserve(Emission->Fixups.size()); + for (const TargetConstantFixup &Fixup : Emission->Fixups) { + if (Fixup.Kind != **DeclaredKind) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RV64GC target constant contains mixed relocation kinds"); + } + Fixups.push_back(&Fixup); + } + std::ranges::sort(Fixups, {}, [](const TargetConstantFixup *Fixup) { return Fixup->Offset; }); + + std::uint64_t Cursor = 0; + for (const TargetConstantFixup *Fixup : Fixups) { + auto Size = targetRelocationSize(Fixup->Kind); + if (!Size) + return std::unexpected(Size.error()); + if (Fixup->Offset < Cursor || Fixup->Offset > Emission->Bytes.size() || + *Size > Emission->Bytes.size() - static_cast<std::size_t>(Fixup->Offset)) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RV64GC target constant relocation storage is invalid"); + } + auto RawBytes = emitTargetBytes(std::span<const std::byte>(Emission->Bytes) + .subspan(static_cast<std::size_t>(Cursor), + static_cast<std::size_t>(Fixup->Offset - Cursor)), + Streamer); + if (!RawBytes) + return std::unexpected(RawBytes.error()); + + auto Symbol = Context.symbol(Fixup->Symbol); + if (!Symbol) + return std::unexpected(Symbol.error()); + auto Expression = mc::MCExpr::symbol(std::move(*Symbol)); + if (!Expression) + return std::unexpected(Expression.error()); + auto WithAddend = addOffset(std::move(*Expression), Fixup->Addend); + if (!WithAddend) + return std::unexpected(WithAddend.error()); + auto Value = Streamer.emitValue(std::move(*WithAddend), *Size); + if (!Value) + return std::unexpected(Value.error()); + Cursor = Fixup->Offset + *Size; + } + return emitTargetBytes( + std::span<const std::byte>(Emission->Bytes).subspan(static_cast<std::size_t>(Cursor)), + Streamer); +} + [[nodiscard]] Expected<void> emitGlobalInitializerImpl(const GlobalInitializer &Initializer, MachineMCContext &Context, mc::MCStreamer &Streamer, @@ -722,18 +806,7 @@ struct TypeLayout final { const auto *Value = Initializer.getIf<TargetConstant>(); if (Value == nullptr) return makeUnexpected(ErrorCode::InternalError, "target initializer has no value"); - auto Emission = Value->emit(); - if (!Emission) - return std::unexpected(Emission.error()); - if (!Emission->Fixups.empty()) { - return makeUnexpected(ErrorCode::Unsupported, - "RV64GC target initializer fixups require native object emission"); - } - std::vector<std::uint8_t> Bytes; - Bytes.reserve(Emission->Bytes.size()); - for (const std::byte Byte : Emission->Bytes) - Bytes.push_back(std::to_integer<std::uint8_t>(Byte)); - return Streamer.emitBytes(Bytes); + return emitTargetConstantImpl(*Value, Context, Streamer); } case GlobalInitializer::Kind::AlignmentPadding: { const auto *Value = Initializer.getIf<GlobalAlignmentPadding>(); @@ -1090,20 +1163,9 @@ Expected<void> RISCVMCTargetLowering::emitGlobalInitializer(const GlobalInitiali } Expected<void> RISCVMCTargetLowering::emitTargetConstant(const TargetConstant &Constant, - MachineMCContext &, + MachineMCContext &Context, mc::MCStreamer &Streamer) const { - auto Emission = Constant.emit(); - if (!Emission) - return std::unexpected(Emission.error()); - if (!Emission->Fixups.empty()) { - return makeUnexpected(ErrorCode::Unsupported, - "RV64GC target-constant fixups require the native object stage"); - } - std::vector<std::uint8_t> Bytes; - Bytes.reserve(Emission->Bytes.size()); - for (const std::byte Byte : Emission->Bytes) - Bytes.push_back(std::to_integer<std::uint8_t>(Byte)); - return Streamer.emitBytes(Bytes); + return emitTargetConstantImpl(Constant, Context, Streamer); } Expected<void> RISCVMCTargetLowering::emitTargetJumpTable(const MachineFunction &Function, diff --git a/backend/lib/Target/RISCV/RISCVObjectFile.cpp b/backend/lib/Target/RISCV/RISCVObjectFile.cpp new file mode 100644 index 0000000..15d4980 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVObjectFile.cpp @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVObjectFile.h" + +#include "rcc/CodeGen/AsmPrinter.h" +#include "rcc/CodeGen/CompilationUnit.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineMCContext.h" +#include "rcc/MC/ELFObjectWriter.h" +#include "rcc/MC/MCContext.h" +#include "rcc/MC/MCObjectStreamer.h" +#include "rcc/Target/RISCV/RISCVAsmBackend.h" +#include "rcc/Target/RISCV/RISCVMCCodeEmitter.h" +#include "rcc/Target/RISCV/RISCVMCTargetLowering.h" + +#include <cstdint> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +constexpr std::uint16_t EMRISCV = 243; +constexpr std::uint32_t SHTRISCVAttributes = 0x70000003; + +[[nodiscard]] AsmPrinterOptions printerOptions(ObjectEmissionOptions Options) noexcept { + return AsmPrinterOptions{ + .EmitFunctionDirectives = Options.EmitFunctionDirectives, + .EmitCFI = Options.EmitCFI, + .EmitBlockLabels = Options.EmitBlockLabels, + .EmitInstructionSymbols = true, + .VerifyInput = Options.VerifyInput, + }; +} + +[[nodiscard]] std::vector<std::uint8_t> attributesSection() { + constexpr char Architecture[] = "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_" + "zmmul1p0_zaamo1p0_zalrsc1p0"; + const std::uint32_t FilePayloadSize = 1U + sizeof(Architecture) + 2U; + const std::uint32_t VendorSize = 4U + 6U + 1U + 4U + FilePayloadSize; + std::vector<std::uint8_t> Bytes; + Bytes.reserve(1U + VendorSize); + const auto AppendU32 = [&Bytes](std::uint32_t Value) { + for (std::uint8_t Shift = 0; Shift < 32; Shift += 8) + Bytes.push_back(static_cast<std::uint8_t>(Value >> Shift)); + }; + Bytes.push_back('A'); + AppendU32(VendorSize); + Bytes.insert(Bytes.end(), {'r', 'i', 's', 'c', 'v', 0}); + Bytes.push_back(1); + AppendU32(5U + FilePayloadSize); + Bytes.push_back(5); + Bytes.insert(Bytes.end(), Architecture, Architecture + sizeof(Architecture)); + Bytes.push_back(4); + Bytes.push_back(16); + return Bytes; +} + +[[nodiscard]] mc::ELFObjectTargetInfo objectTargetInfo() { + mc::ELFObjectTargetInfo Info; + Info.Machine = EMRISCV; + Info.ExtraSections.push_back(mc::ELFExtraSection{ + .Name = ".riscv.attributes", + .Type = SHTRISCVAttributes, + .Flags = 0, + .Alignment = 1, + .EntrySize = 0, + .Contents = attributesSection(), + }); + return Info; +} + +struct ObjectEmissionContext final { + explicit ObjectEmissionContext(const TargetDescription &Target) + : MachineContext(Context), Lowering(Target), Emitter(Target), Writer(objectTargetInfo()), + Streamer(Emitter, Backend, Writer) { + mc::MCSectionDesc EHFrameDescription{ + .Name = ".eh_frame", + .Kind = mc::MCSectionKind::EHFrame, + .Flags = {mc::MCSectionFlag::Alloc}, + .Alignment = 8, + .EntrySize = 0, + .Group = {}, + .UniqueID = std::nullopt, + }; + auto EHFrame = Context.getOrCreateSection(std::move(EHFrameDescription)); + if (!EHFrame) { + Initialization = std::unexpected(EHFrame.error()); + return; + } + Initialization = Streamer.configureDwarfFrame(std::move(*EHFrame), + mc::MCDwarfFrameInfo{.CodeAlignment = 1, + .DataAlignment = -8, + .ReturnAddressRegister = 1, + .InitialCFARegister = 2, + .InitialCFAOffset = 0}); + } + + mc::MCContext Context; + MachineMCContext MachineContext; + RISCVMCTargetLowering Lowering; + RISCVMCCodeEmitter Emitter; + RISCVAsmBackend Backend; + mc::ELF64LEObjectWriter Writer; + mc::ELFObjectStreamer Streamer; + Expected<void> Initialization; +}; + +} // namespace + +Expected<std::vector<std::uint8_t>> emitObject(const CompilationUnit &Unit, + const TargetDescription &Target, + ObjectEmissionOptions Options) { + ObjectEmissionContext Context(Target); + if (!Context.Initialization) + return std::unexpected(Context.Initialization.error()); + AsmPrinter Printer(Target, Context.Lowering, Context.MachineContext, Context.Streamer, + printerOptions(Options)); + auto Emitted = Printer.emitCompilationUnit(Unit); + if (!Emitted) + return std::unexpected(Emitted.error()); + return Context.Streamer.takeOutput(); +} + +Expected<std::vector<std::uint8_t>> emitObject(const MachineFunction &Function, + const TargetDescription &Target, + ObjectEmissionOptions Options) { + ObjectEmissionContext Context(Target); + if (!Context.Initialization) + return std::unexpected(Context.Initialization.error()); + AsmPrinter Printer(Target, Context.Lowering, Context.MachineContext, Context.Streamer, + printerOptions(Options)); + auto Header = Context.Lowering.emitFileHeader(Context.MachineContext, Context.Streamer); + if (!Header) + return std::unexpected(Header.error()); + auto Emitted = Printer.emitFunction(Function); + if (!Emitted) + return std::unexpected(Emitted.error()); + auto Trailer = Context.Lowering.emitFileTrailer(Context.MachineContext, Context.Streamer); + if (!Trailer) + return std::unexpected(Trailer.error()); + auto Finished = Context.Streamer.finish(); + if (!Finished) + return std::unexpected(Finished.error()); + return Context.Streamer.takeOutput(); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVTargetConstant.cpp b/backend/lib/Target/RISCV/RISCVTargetConstant.cpp new file mode 100644 index 0000000..6fda900 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVTargetConstant.cpp @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVTargetConstant.h" + +#include "rcc/Target/RISCV/RISCVTargetRelocationKinds.h" + +#include <bit> +#include <charconv> +#include <cstddef> +#include <cstdint> +#include <string> +#include <string_view> +#include <system_error> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] std::string_view kindSpelling(RISCVSymbolConstantKind Kind) noexcept { + switch (Kind) { + case RISCVSymbolConstantKind::GlobalValue: + return "global"; + case RISCVSymbolConstantKind::ExternalSymbol: + return "external"; + } + return {}; +} + +[[nodiscard]] Expected<RISCVSymbolConstantKind> parseKind(std::string_view Text) { + if (Text == "global") + return RISCVSymbolConstantKind::GlobalValue; + if (Text == "external") + return RISCVSymbolConstantKind::ExternalSymbol; + return makeUnexpected(ErrorCode::ParseError, "unknown RV64GC symbol constant kind"); +} + +void appendU64(std::vector<std::byte> &Bytes, std::uint64_t Value) { + for (std::uint8_t Shift = 0; Shift < 64; Shift += 8) + Bytes.push_back(static_cast<std::byte>(Value >> Shift)); +} + +[[nodiscard]] std::uint64_t readU64(std::span<const std::byte> Bytes) noexcept { + std::uint64_t Result = 0; + for (std::uint8_t Shift = 0; Shift < 64; Shift += 8) + Result |= std::to_integer<std::uint64_t>(Bytes[Shift / 8U]) << Shift; + return Result; +} + +} // namespace + +Expected<void> RISCVSymbolConstant::verify() const { + if (kindSpelling(ConstantKind).empty()) + return makeUnexpected(ErrorCode::VerificationError, "RV64GC symbol constant kind is invalid"); + if (SymbolName.empty() || SymbolName.find('\0') != std::string::npos) { + return makeUnexpected(ErrorCode::VerificationError, + "RV64GC symbol constant requires a valid symbol name"); + } + return {}; +} + +Expected<RISCVSymbolConstant> RISCVSymbolConstant::clone() const { + return RISCVSymbolConstant(ConstantKind, SymbolName, SymbolAddend); +} + +std::uint64_t RISCVSymbolConstant::stableHash() const noexcept { + constexpr std::uint64_t OffsetBasis = 14695981039346656037ULL; + constexpr std::uint64_t Prime = 1099511628211ULL; + std::uint64_t Hash = OffsetBasis; + const auto Mix = [&Hash](std::uint8_t Byte) { + Hash ^= Byte; + Hash *= Prime; + }; + Mix(static_cast<std::uint8_t>(ConstantKind)); + const std::uint64_t AddendBits = std::bit_cast<std::uint64_t>(SymbolAddend); + for (std::uint8_t Shift = 0; Shift < 64; Shift += 8) + Mix(static_cast<std::uint8_t>(AddendBits >> Shift)); + for (const char Character : SymbolName) + Mix(static_cast<unsigned char>(Character)); + return Hash; +} + +std::string RISCVSymbolConstant::print() const { + return std::string(kindSpelling(ConstantKind)) + "," + std::to_string(SymbolAddend) + "," + + SymbolName; +} + +Expected<std::vector<std::byte>> RISCVSymbolConstant::encode() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + std::vector<std::byte> Payload; + Payload.reserve(9U + SymbolName.size()); + Payload.push_back(static_cast<std::byte>(static_cast<std::uint8_t>(ConstantKind))); + appendU64(Payload, std::bit_cast<std::uint64_t>(SymbolAddend)); + for (const char Character : SymbolName) + Payload.push_back(static_cast<std::byte>(static_cast<unsigned char>(Character))); + return Payload; +} + +Expected<TargetConstantEmission> RISCVSymbolConstant::emit() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + TargetConstantEmission Emission; + Emission.Bytes.resize(8); + Emission.Fixups.push_back(TargetConstantFixup{ + .Offset = 0, + .Kind = targetRelocationKind(RISCVTargetRelocationKind::Absolute64), + .Symbol = SymbolName, + .Addend = SymbolAddend, + }); + return Emission; +} + +Alignment RISCVSymbolConstant::requiredAlignment() const noexcept { + return *Alignment::create(8); +} + +std::optional<TargetRelocationKindID> RISCVSymbolConstant::relocationKind() const noexcept { + return targetRelocationKind(RISCVTargetRelocationKind::Absolute64); +} + +Expected<RISCVSymbolConstant> RISCVSymbolConstant::parse(std::string_view Text) { + const std::size_t KindEnd = Text.find(','); + const std::size_t AddendEnd = + KindEnd == std::string_view::npos ? std::string_view::npos : Text.find(',', KindEnd + 1U); + if (KindEnd == std::string_view::npos || AddendEnd == std::string_view::npos) { + return makeUnexpected(ErrorCode::ParseError, + "RV64GC symbol constant must be 'kind,addend,symbol'"); + } + auto Kind = parseKind(Text.substr(0, KindEnd)); + if (!Kind) + return std::unexpected(Kind.error()); + std::int64_t Addend = 0; + const std::string_view AddendText = Text.substr(KindEnd + 1U, AddendEnd - KindEnd - 1U); + const auto [End, Error] = + std::from_chars(AddendText.data(), AddendText.data() + AddendText.size(), Addend); + if (Error != std::errc{} || End != AddendText.data() + AddendText.size()) + return makeUnexpected(ErrorCode::ParseError, "RV64GC symbol constant addend is invalid"); + RISCVSymbolConstant Result(*Kind, std::string(Text.substr(AddendEnd + 1U)), Addend); + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; +} + +Expected<RISCVSymbolConstant> RISCVSymbolConstant::decode(std::uint32_t Version, + std::span<const std::byte> Payload) { + if (Version != schemaVersion() || Payload.size() < 10) { + return makeUnexpected(ErrorCode::ParseError, + "encoded RV64GC symbol constant has an invalid version or size"); + } + const auto KindValue = std::to_integer<std::uint8_t>(Payload.front()); + if (KindValue > static_cast<std::uint8_t>(RISCVSymbolConstantKind::ExternalSymbol)) + return makeUnexpected(ErrorCode::ParseError, "encoded RV64GC symbol constant kind is invalid"); + std::string Symbol; + Symbol.reserve(Payload.size() - 9U); + for (const std::byte Character : Payload.subspan(9)) + Symbol.push_back(static_cast<char>(std::to_integer<unsigned char>(Character))); + RISCVSymbolConstant Result(static_cast<RISCVSymbolConstantKind>(KindValue), std::move(Symbol), + std::bit_cast<std::int64_t>(readU64(Payload.subspan(1, 8)))); + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; +} + +} // namespace rcc::riscv diff --git a/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp index ff339fa..8697754 100644 --- a/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp +++ b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp @@ -10,8 +10,10 @@ #include "rcc/Support/TextDiagnostic.h" #include "rcc/Target/RISCV/RISCVAssemblyPrinter.h" #include "rcc/Target/RISCV/RISCVCodeGen.h" +#include "rcc/Target/RISCV/RISCVObjectFile.h" #include "rcc/Target/RISCV/RISCVTarget.h" +#include <cstdint> #include <fstream> #include <iostream> #include <iterator> @@ -19,29 +21,86 @@ #include <string> #include <string_view> #include <utility> +#include <vector> namespace { +enum class OutputKind : std::uint8_t { + Assembly, + Object, +}; + struct CommandLine final { std::string InputPath; + std::string OutputPath = "-"; + OutputKind Kind = OutputKind::Assembly; }; +void printUsage() { + std::cerr << "usage: rcc-backend-asm --input <mir-file|-> " + "[--filetype asm|obj] [--output <file|->]\n"; +} + [[nodiscard]] bool parseCommandLine(int ArgumentCount, char *Arguments[], CommandLine &Result) { for (int Index = 1; Index < ArgumentCount; ++Index) { const std::string_view Argument(Arguments[Index]); - if (Argument != "--input" || Index + 1 >= ArgumentCount) { - std::cerr << "usage: rcc-backend-asm --input <mir-file|->\n"; + if (Argument == "--input" && Index + 1 < ArgumentCount) { + Result.InputPath = Arguments[++Index]; + continue; + } + if ((Argument == "--output" || Argument == "-o") && Index + 1 < ArgumentCount) { + Result.OutputPath = Arguments[++Index]; + continue; + } + if (Argument == "--filetype" && Index + 1 < ArgumentCount) { + const std::string_view Value(Arguments[++Index]); + if (Value == "asm" || Value == "assembly") { + Result.Kind = OutputKind::Assembly; + } else if (Value == "obj" || Value == "object") { + Result.Kind = OutputKind::Object; + } else { + printUsage(); + return false; + } + continue; + } + { + printUsage(); return false; } - Result.InputPath = Arguments[++Index]; } if (Result.InputPath.empty()) { - std::cerr << "usage: rcc-backend-asm --input <mir-file|->\n"; + printUsage(); + return false; + } + return true; +} + +[[nodiscard]] bool writeOutput(const CommandLine &Command, std::span<const std::uint8_t> Contents) { + if (Command.OutputPath == "-") { + std::cout.write(reinterpret_cast<const char *>(Contents.data()), + static_cast<std::streamsize>(Contents.size())); + return static_cast<bool>(std::cout); + } + std::ofstream Output(Command.OutputPath, std::ios::binary); + if (!Output) { + std::cerr << "rcc-backend-asm: cannot open output '" << Command.OutputPath << "'\n"; + return false; + } + Output.write(reinterpret_cast<const char *>(Contents.data()), + static_cast<std::streamsize>(Contents.size())); + if (!Output) { + std::cerr << "rcc-backend-asm: cannot write output '" << Command.OutputPath << "'\n"; return false; } return true; } +[[nodiscard]] bool writeOutput(const CommandLine &Command, std::string_view Contents) { + const auto *Data = reinterpret_cast<const std::uint8_t *>(Contents.data()); + return writeOutput(Command, std::span<const std::uint8_t>(Data, Contents.size())); +} + [[nodiscard]] bool readInput(const CommandLine &Command, std::string &Contents) { if (Command.InputPath == "-") { Contents.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>()); @@ -160,12 +219,19 @@ int main(int ArgumentCount, char *Arguments[]) { } } + if (Command.Kind == OutputKind::Object) { + auto Object = rcc::riscv::emitObject(Unit, *Target); + if (!Object) { + std::cerr << "rcc-backend-asm: object emission failed: " << Object.error().message() << '\n'; + return 1; + } + return writeOutput(Command, *Object) ? 0 : 1; + } auto Assembly = rcc::riscv::emitAssembly(Unit, *Target); if (!Assembly) { std::cerr << "rcc-backend-asm: assembly emission failed: " << Assembly.error().message() << '\n'; return 1; } - std::cout << *Assembly; - return 0; + return writeOutput(Command, *Assembly) ? 0 : 1; } diff --git a/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake b/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake index baeedcf..85977df 100644 --- a/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake +++ b/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake @@ -22,6 +22,11 @@ if(INPUT MATCHES "RISCVDataPipeline" AND NOT FirstAssembly MATCHES "\\.LJTI[0-9]+_0:")) message(FATAL_ERROR "assembly pipeline did not emit its constant pool and jump table") endif() +if(INPUT MATCHES "RISCVStackFramePipeline" AND + (NOT FirstAssembly MATCHES "addi x2, x2, -32" OR + NOT FirstAssembly MATCHES "addi x2, x2, 32")) + message(FATAL_ERROR "assembly pipeline did not balance its stack frame") +endif() execute_process( COMMAND "${EMITTER}" --input "${INPUT}" diff --git a/backend/tools/AssemblyEmitter/CMakeLists.txt b/backend/tools/AssemblyEmitter/CMakeLists.txt index a262983..01c99aa 100644 --- a/backend/tools/AssemblyEmitter/CMakeLists.txt +++ b/backend/tools/AssemblyEmitter/CMakeLists.txt @@ -22,7 +22,16 @@ install( ) find_program(RCCBackendLLVMMC NAMES llvm-mc-20 llvm-mc) -foreach(InputName IN ITEMS RISCVGenericPipeline RISCVWideConstantPipeline RISCVDataPipeline) +find_program(RCCBackendLLVMReadELF NAMES llvm-readelf-20 llvm-readelf) +find_program(RCCBackendLLVMObjdump NAMES llvm-objdump-20 llvm-objdump) +foreach( + InputName + IN ITEMS + RISCVGenericPipeline + RISCVWideConstantPipeline + RISCVDataPipeline + RISCVStackFramePipeline +) add_test( NAME RCCBackend.AssemblyEmitter.${InputName} COMMAND @@ -33,6 +42,17 @@ foreach(InputName IN ITEMS RISCVGenericPipeline RISCVWideConstantPipeline RISCVD "-DLLVM_MC=${RCCBackendLLVMMC}" -P "${CMAKE_CURRENT_SOURCE_DIR}/AssemblyPipelineTest.cmake" ) + add_test( + NAME RCCBackend.ObjectEmitter.${InputName} + COMMAND + "${CMAKE_COMMAND}" + "-DEMITTER=$<TARGET_FILE:RCCBackendAssemblyEmitter>" + "-DINPUT=${RCC_BACKEND_SOURCE_DIR}/unittests/Target/Inputs/${InputName}.mir" + "-DOUTPUT=${CMAKE_CURRENT_BINARY_DIR}/${InputName}.o" + "-DLLVM_READELF=${RCCBackendLLVMReadELF}" + "-DLLVM_OBJDUMP=${RCCBackendLLVMObjdump}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/ObjectPipelineTest.cmake" + ) endforeach() rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/AssemblyEmitterMain.cpp") diff --git a/backend/tools/AssemblyEmitter/ObjectPipelineTest.cmake b/backend/tools/AssemblyEmitter/ObjectPipelineTest.cmake new file mode 100644 index 0000000..5ba800c --- /dev/null +++ b/backend/tools/AssemblyEmitter/ObjectPipelineTest.cmake @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +if(NOT DEFINED EMITTER OR NOT DEFINED INPUT OR NOT DEFINED OUTPUT) + message(FATAL_ERROR "object pipeline test is missing a required path") +endif() + +set(RepeatedOutput "${OUTPUT}.repeat") +execute_process( + COMMAND "${EMITTER}" --input "${INPUT}" --filetype obj --output "${OUTPUT}" + RESULT_VARIABLE FirstResult + ERROR_VARIABLE FirstDiagnostics +) +if(NOT FirstResult EQUAL 0) + message(FATAL_ERROR "object pipeline failed: ${FirstDiagnostics}") +endif() + +execute_process( + COMMAND "${EMITTER}" --input "${INPUT}" --filetype obj --output "${RepeatedOutput}" + RESULT_VARIABLE SecondResult + ERROR_VARIABLE SecondDiagnostics +) +if(NOT SecondResult EQUAL 0) + message(FATAL_ERROR "repeated object pipeline failed: ${SecondDiagnostics}") +endif() + +execute_process( + COMMAND "${CMAKE_COMMAND}" -E compare_files "${OUTPUT}" "${RepeatedOutput}" + RESULT_VARIABLE ComparisonResult +) +if(NOT ComparisonResult EQUAL 0) + message(FATAL_ERROR "object pipeline output is not deterministic") +endif() + +file(READ "${OUTPUT}" ObjectMagic OFFSET 0 LIMIT 4 HEX) +if(NOT ObjectMagic STREQUAL "7f454c46") + message(FATAL_ERROR "object pipeline did not produce an ELF file") +endif() + +if(DEFINED LLVM_READELF AND NOT LLVM_READELF STREQUAL "LLVM_READELF-NOTFOUND" AND + NOT LLVM_READELF STREQUAL "") + execute_process( + COMMAND "${LLVM_READELF}" -h -S -s -r "${OUTPUT}" + RESULT_VARIABLE ReadELFResult + OUTPUT_VARIABLE ReadELFOutput + ERROR_VARIABLE ReadELFDiagnostics + ) + if(NOT ReadELFResult EQUAL 0) + message(FATAL_ERROR "LLVM readelf rejected generated object: ${ReadELFDiagnostics}") + endif() + if(NOT ReadELFOutput MATCHES "Type:[ \t]+REL" OR + NOT ReadELFOutput MATCHES "Machine:[ \t]+RISC-V" OR + NOT ReadELFOutput MATCHES "Flags:[ \t]+0x5" OR + NOT ReadELFOutput MATCHES "\\.text" OR + NOT ReadELFOutput MATCHES "\\.eh_frame" OR + NOT ReadELFOutput MATCHES "\\.riscv\\.attributes") + message(FATAL_ERROR "generated object is missing required RV64GC ELF metadata") + endif() + if(INPUT MATCHES "RISCVDataPipeline" AND + (NOT ReadELFOutput MATCHES "\\.rodata" OR + NOT ReadELFOutput MATCHES "\\.rela\\.text" OR + NOT ReadELFOutput MATCHES "R_RISCV_64")) + message(FATAL_ERROR "data pipeline object is missing data or relocation sections") + endif() +endif() + +if(DEFINED LLVM_OBJDUMP AND NOT LLVM_OBJDUMP STREQUAL "LLVM_OBJDUMP-NOTFOUND" AND + NOT LLVM_OBJDUMP STREQUAL "") + execute_process( + COMMAND "${LLVM_OBJDUMP}" -dr --no-show-raw-insn "${OUTPUT}" + RESULT_VARIABLE ObjdumpResult + OUTPUT_VARIABLE ObjdumpOutput + ERROR_VARIABLE ObjdumpDiagnostics + ) + if(NOT ObjdumpResult EQUAL 0) + message(FATAL_ERROR "LLVM objdump rejected generated object: ${ObjdumpDiagnostics}") + endif() + if(NOT ObjdumpOutput MATCHES "file format elf64-littleriscv" OR + NOT ObjdumpOutput MATCHES "ret") + message(FATAL_ERROR "generated object does not contain RV64GC function code") + endif() + if(INPUT MATCHES "RISCVStackFramePipeline" AND + (NOT ObjdumpOutput MATCHES "addi[ \t]+sp, sp, -0x20" OR + NOT ObjdumpOutput MATCHES "addi[ \t]+sp, sp, 0x20")) + message(FATAL_ERROR "stack-frame object does not balance its stack pointer") + endif() +endif() + +file(REMOVE "${OUTPUT}" "${RepeatedOutput}") diff --git a/backend/unittests/CodeGen/MachineOptimizationTest.cpp b/backend/unittests/CodeGen/MachineOptimizationTest.cpp index 8d01a65..d4a39fa 100644 --- a/backend/unittests/CodeGen/MachineOptimizationTest.cpp +++ b/backend/unittests/CodeGen/MachineOptimizationTest.cpp @@ -300,6 +300,25 @@ void testDCEFixedPointAndDebugUses(TestContext &Context, const rcc::TargetDescri if (Memory == nullptr) return; rcc::MachineInstr *Store = buildStore(Context, Builder, LiveValue, X2, *Memory); + rcc::MachineInstr *FrameSetup = buildADDI(Context, Builder, rcc::Register::fromPhysical(X2), + rcc::Register::fromPhysical(X2), -16); + rcc::MachineInstr *FrameDestroy = buildADDI(Context, Builder, rcc::Register::fromPhysical(X2), + rcc::Register::fromPhysical(X2), 16); + Context.expect(FrameSetup != nullptr && FrameDestroy != nullptr, + "DCE frame instruction construction must succeed"); + if (FrameSetup == nullptr || FrameDestroy == nullptr) + return; + FrameSetup->setFlags({rcc::MachineInstrFlag::FrameSetup}); + FrameDestroy->setFlags({rcc::MachineInstrFlag::FrameDestroy}); + for (rcc::MachineInstr *Instruction : {FrameSetup, FrameDestroy}) { + rcc::MachineOperand *Definition = Instruction->operandAt(0); + if (Definition == nullptr) + return; + rcc::RegisterOperandFlags Flags = Definition->registerFlags(); + Flags.set(rcc::RegisterOperandFlag::Dead); + Context.expect(Definition->setRegisterFlags(Flags).has_value(), + "DCE frame definition must accept a dead marker"); + } const std::size_t Before = Fixture.Entry->instructionCount(); rcc::MachineAnalysisManager Analyses; @@ -312,6 +331,8 @@ void testDCEFixedPointAndDebugUses(TestContext &Context, const rcc::TargetDescri Context.expect(Fixture.Entry->instructionCount() + 2 == Before, "machine DCE must reach a fixed point across the dead chain"); Context.expect(Store != nullptr && Store->isLinked(), "machine DCE must preserve a store"); + Context.expect(FrameSetup->isLinked() && FrameDestroy->isLinked(), + "machine DCE must preserve frame setup and destroy instructions"); Context.expect(!Fixture.Function->registerInfo().isKnownVirtualRegister(DeadRoot) && !Fixture.Function->registerInfo().isKnownVirtualRegister(DeadValue), "machine DCE must retire removed virtual registers"); @@ -379,6 +400,10 @@ void testPostRAMachineCombinerIdentity(TestContext &Context, const rcc::TargetDe const rcc::PhysicalRegister X10 = physical(Context, rcc::riscv::PhysicalRegisterID::X10); rcc::MachineInstr *Identity = buildADDI(Context, Builder, rcc::Register::fromPhysical(X10), rcc::Register::fromPhysical(X10), 0); + rcc::MachineInstr *FrameIdentity = buildADDI(Context, Builder, rcc::Register::fromPhysical(X10), + rcc::Register::fromPhysical(X10), 0); + if (FrameIdentity != nullptr) + FrameIdentity->setFlags({rcc::MachineInstrFlag::FrameDestroy}); rcc::MachineAnalysisManager Analyses; rcc::MachineCombinerPass Pass(rcc::riscv::machineCombinerInfo(), rcc::MachineCombinerStage::PostRegisterAllocation); @@ -386,6 +411,8 @@ void testPostRAMachineCombinerIdentity(TestContext &Context, const rcc::TargetDe Context.expect( Result.has_value() && Result->Changed && Identity != nullptr && !Identity->isLinked(), "post-RA combiner must erase a physical-register no-op only when identities match"); + Context.expect(FrameIdentity != nullptr && FrameIdentity->isLinked(), + "post-frame semantics must protect an otherwise removable identity"); } void testCSERegisterAndMemoryRules(TestContext &Context, const rcc::TargetDescription &Target) { @@ -610,8 +637,7 @@ void testPostRACodeMotion(TestContext &Context, const rcc::TargetDescription &Ta return; const rcc::Register X0 = rcc::Register::fromPhysical(physical(Context, rcc::riscv::PhysicalRegisterID::X0)); - const rcc::Register X2 = - rcc::Register::fromPhysical(physical(Context, rcc::riscv::PhysicalRegisterID::X2)); + const rcc::PhysicalRegister X2Physical = physical(Context, rcc::riscv::PhysicalRegisterID::X2); const rcc::Register X10 = rcc::Register::fromPhysical(physical(Context, rcc::riscv::PhysicalRegisterID::X10)); const rcc::Register X12 = @@ -634,7 +660,7 @@ void testPostRACodeMotion(TestContext &Context, const rcc::TargetDescription &Ta makeMemory(Context, *LICMFixture.Function, true, false); if (LICMMemory == nullptr) return; - (void)buildStore(Context, LICMBuilder, X12, *X2.physicalRegister(), *LICMMemory); + (void)buildStore(Context, LICMBuilder, X12, X2Physical, *LICMMemory); setPostRAProperties(*LICMFixture.Function); Context.expect(rcc::rebuildMachineLiveness(*LICMFixture.Function, Target).has_value(), "post-RA LICM fixture liveness must be built"); diff --git a/backend/unittests/MC/CMakeLists.txt b/backend/unittests/MC/CMakeLists.txt index d62bb1d..3c18b36 100644 --- a/backend/unittests/MC/CMakeLists.txt +++ b/backend/unittests/MC/CMakeLists.txt @@ -5,5 +5,16 @@ rcc_backend_configure_target(RCCBackendMCTests) target_link_libraries(RCCBackendMCTests PRIVATE RCCBackend::MC) add_test(NAME RCCBackend.MC.Core COMMAND RCCBackendMCTests) -rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/MCTest.cpp") -rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/MCTest.cpp") +add_executable(RCCBackendMCObjectTests MCObjectTest.cpp) +rcc_backend_configure_target(RCCBackendMCObjectTests) +target_link_libraries(RCCBackendMCObjectTests PRIVATE RCCBackend::MC) +add_test(NAME RCCBackend.MC.Object COMMAND RCCBackendMCObjectTests) + +rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/MCTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MCObjectTest.cpp" +) +rcc_backend_register_tidy_files( + "${CMAKE_CURRENT_SOURCE_DIR}/MCTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MCObjectTest.cpp" +) diff --git a/backend/unittests/MC/MCObjectTest.cpp b/backend/unittests/MC/MCObjectTest.cpp new file mode 100644 index 0000000..f221e49 --- /dev/null +++ b/backend/unittests/MC/MCObjectTest.cpp @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MC/ELFObjectWriter.h" +#include "rcc/MC/MCAsmBackend.h" +#include "rcc/MC/MCAssembler.h" +#include "rcc/MC/MCCodeEmitter.h" +#include "rcc/MC/MCContext.h" +#include "rcc/MC/MCExpr.h" +#include "rcc/MC/MCFixup.h" +#include "rcc/MC/MCInst.h" + +#include <algorithm> +#include <array> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <limits> +#include <span> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + +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; +}; + +class TestCodeEmitter final : public rcc::mc::MCCodeEmitter { +public: + [[nodiscard]] rcc::Expected<rcc::mc::MCEncodedInstruction> + encodeInstruction(const rcc::mc::MCInst &Instruction) const override { + if (FailNextEncoding) { + FailNextEncoding = false; + return rcc::makeUnexpected(rcc::ErrorCode::InstructionEncodingFailed, + "intentional test encoding failure"); + } + if (Instruction.opcode().value() != 1 || !Instruction.operands().empty()) { + return rcc::makeUnexpected(rcc::ErrorCode::InstructionEncodingFailed, + "unknown test instruction"); + } + return rcc::mc::MCEncodedInstruction{.Bytes = {0x11, 0x22, 0x33, 0x44}, .Fixups = {}}; + } + + mutable bool FailNextEncoding = false; +}; + +class TestAsmBackend final : public rcc::mc::MCAsmBackend { +public: + [[nodiscard]] rcc::Expected<rcc::mc::MCFixupKindInfo> + fixupKindInfo(rcc::mc::MCFixupKind Kind) const override { + switch (Kind) { + case rcc::mc::fixup_kind::Data1: + return rcc::mc::MCFixupKindInfo{.StorageSize = 1, .PCRelative = false}; + case rcc::mc::fixup_kind::Data2: + return rcc::mc::MCFixupKindInfo{.StorageSize = 2, .PCRelative = false}; + case rcc::mc::fixup_kind::Data4: + return rcc::mc::MCFixupKindInfo{.StorageSize = 4, .PCRelative = false}; + case rcc::mc::fixup_kind::Data8: + return rcc::mc::MCFixupKindInfo{.StorageSize = 8, .PCRelative = false}; + case rcc::mc::fixup_kind::PCRelative4: + return rcc::mc::MCFixupKindInfo{.StorageSize = 4, .PCRelative = true}; + default: + return rcc::makeUnexpected(rcc::ErrorCode::InvalidArgument, "unknown test fixup kind"); + } + } + + [[nodiscard]] rcc::Expected<void> applyFixup(rcc::mc::MCFixupKind Kind, + std::span<std::uint8_t> Data, std::uint64_t Offset, + std::int64_t Value) const override { + auto Info = fixupKindInfo(Kind); + if (!Info) + return std::unexpected(Info.error()); + if (Offset > Data.size() || + Info->StorageSize > Data.size() - static_cast<std::size_t>(Offset)) { + return rcc::makeUnexpected(rcc::ErrorCode::FixupApplicationFailed, + "test fixup exceeds its section"); + } + const std::uint64_t Bits = static_cast<std::uint64_t>(Value); + for (std::uint8_t Index = 0; Index < Info->StorageSize; ++Index) + Data[static_cast<std::size_t>(Offset) + Index] = + static_cast<std::uint8_t>(Bits >> (Index * 8U)); + return {}; + } + + [[nodiscard]] rcc::Expected<std::uint32_t> + relocationType(const rcc::mc::MCFixup &, const rcc::mc::MCValue &Value) const override { + if (!Value.AddSymbol || Value.SubtractSymbol) + return rcc::makeUnexpected(rcc::ErrorCode::RelocationFailed, + "test relocation requires one symbol"); + return 77; + } + + [[nodiscard]] std::uint32_t objectFlags() const noexcept override { + return 0x20; + } +}; + +[[nodiscard]] rcc::mc::MCInst testInstruction() { + return rcc::mc::MCInst(rcc::mc::MCOpcode::fromValue(1)); +} + +[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef> +symbolExpression(const rcc::mc::MCSymbolRef &Symbol) { + return rcc::mc::MCExpr::symbol(Symbol); +} + +[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef> addConstant(const rcc::mc::MCSymbolRef &Symbol, + std::int64_t Addend) { + auto Base = symbolExpression(Symbol); + if (!Base) + return std::unexpected(Base.error()); + return rcc::mc::MCExpr::binary(rcc::mc::MCBinaryOperator::Add, *Base, + rcc::mc::MCExpr::constant(Addend)); +} + +[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef> subtractSymbols(const rcc::mc::MCSymbolRef &Left, + const rcc::mc::MCSymbolRef &Right) { + auto LeftExpression = symbolExpression(Left); + auto RightExpression = symbolExpression(Right); + if (!LeftExpression) + return std::unexpected(LeftExpression.error()); + if (!RightExpression) + return std::unexpected(RightExpression.error()); + return rcc::mc::MCExpr::binary(rcc::mc::MCBinaryOperator::Subtract, *LeftExpression, + *RightExpression); +} + +[[nodiscard]] rcc::Expected<rcc::mc::MCSectionRef> +createSection(rcc::mc::MCContext &Context, std::string_view Name, rcc::mc::MCSectionKind Kind, + rcc::mc::MCSectionFlags Flags, std::uint64_t Alignment) { + return Context.getOrCreateSection(rcc::mc::MCSectionDesc{ + .Name = std::string(Name), + .Kind = Kind, + .Flags = Flags, + .Alignment = Alignment, + .EntrySize = 0, + .Group = {}, + .UniqueID = std::nullopt, + }); +} + +[[nodiscard]] const rcc::mc::MCObjectSymbol *findSymbol(const rcc::mc::MCAssembledObject &Object, + std::string_view Name) { + const auto Position = std::ranges::find_if(Object.Symbols, [&](const auto &Symbol) { + return Symbol.Symbol && Symbol.Symbol->name() == Name; + }); + return Position == Object.Symbols.end() ? nullptr : &*Position; +} + +[[nodiscard]] rcc::Expected<rcc::mc::MCAssembledObject> +assembleFixture(rcc::mc::MCContext &Context, const TestCodeEmitter &Emitter, + const TestAsmBackend &Backend) { + auto Text = createSection(Context, ".text", rcc::mc::MCSectionKind::Text, + {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Executable}, 4); + auto Data = createSection(Context, ".data", rcc::mc::MCSectionKind::Data, + {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Writable}, 1); + auto BSS = createSection(Context, ".bss", rcc::mc::MCSectionKind::BSS, + {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Writable}, 1); + auto Function = Context.getOrCreateSymbol("function", rcc::mc::MCSymbolKind::Function); + auto End = Context.createTemporarySymbol(".Lend"); + auto External = Context.getOrCreateSymbol("external", rcc::mc::MCSymbolKind::Generic); + if (!Text || !Data || !BSS || !Function || !End || !External) + return rcc::makeUnexpected(rcc::ErrorCode::InternalError, + "failed to create the MC object fixture"); + auto FunctionSize = subtractSymbols(*End, *Function); + auto ExternalValue = addConstant(*External, 7); + if (!FunctionSize || !ExternalValue) + return rcc::makeUnexpected(rcc::ErrorCode::InternalError, + "failed to create the MC object expressions"); + + rcc::mc::MCAssembler Assembler(Emitter, Backend); + constexpr std::uint64_t LargeBSS = (std::uint64_t{1} << 32U) + 17U; + if (!Assembler.switchSection(*Text) || + !Assembler.emitSymbolAttribute(*Function, rcc::mc::MCSymbolAttribute::Global) || + !Assembler.emitSymbolAttribute(*Function, rcc::mc::MCSymbolAttribute::Function) || + !Assembler.emitLabel(*Function) || !Assembler.emitInstruction(testInstruction()) || + !Assembler.emitAlignment(8, 0xaa, 0) || !Assembler.emitLabel(*End) || + !Assembler.emitSymbolSize(*Function, *FunctionSize) || !Assembler.switchSection(*Data) || + !Assembler.emitAlignment(16, 0, 0) || !Assembler.emitValue(*ExternalValue, 8) || + !Assembler.switchSection(*BSS) || !Assembler.emitAlignment(4096, 0, 0) || + !Assembler.emitZeroFill(LargeBSS)) { + return rcc::makeUnexpected(rcc::ErrorCode::InternalError, + "failed to emit the MC object fixture"); + } + return Assembler.assemble(); +} + +void testAssembler(TestContext &Test, const TestCodeEmitter &Emitter, + const TestAsmBackend &Backend) { + rcc::mc::MCContext Context; + auto Object = assembleFixture(Context, Emitter, Backend); + if (!Object) { + std::cerr << "MC object fixture assembly failed: " << Object.error().message() << '\n'; + Test.expect(false, "MC assembler fixture must assemble"); + return; + } + Test.expect(Object->Flags == 0x20 && Object->Sections.size() == 3, + "MC assembler must preserve target flags and section order"); + if (Object->Sections.size() != 3) + return; + Test.expect(Object->Sections[0].Alignment == 8 && Object->Sections[0].AddressSize == 8 && + Object->Sections[0].Contents == + std::vector<std::uint8_t>({0x11, 0x22, 0x33, 0x44, 0xaa, 0xaa, 0xaa, 0xaa}), + "MC assembler must materialize instructions and alignment padding"); + Test.expect(Object->Sections[1].Alignment == 16 && Object->Sections[1].AddressSize == 8 && + Object->Sections[1].Contents == std::vector<std::uint8_t>(8) && + Object->Sections[1].Relocations.size() == 1 && + Object->Sections[1].Relocations[0].Offset == 0 && + Object->Sections[1].Relocations[0].Type == 77 && + Object->Sections[1].Relocations[0].Addend == 7, + "MC assembler must preserve unresolved symbol relocations"); + constexpr std::uint64_t LargeBSS = (std::uint64_t{1} << 32U) + 17U; + Test.expect(Object->Sections[2].Alignment == 4096 && + Object->Sections[2].AddressSize == LargeBSS && + Object->Sections[2].Contents.empty(), + "MC assembler must not materialize NOBITS storage"); + + const rcc::mc::MCObjectSymbol *Function = findSymbol(*Object, "function"); + const rcc::mc::MCObjectSymbol *External = findSymbol(*Object, "external"); + Test.expect(Function != nullptr && Function->Binding == rcc::mc::MCObjectSymbolBinding::Global && + Function->Type == rcc::mc::MCObjectSymbolType::Function && + Function->SectionIndex == 0 && Function->Value == 0 && Function->Size == 8, + "MC assembler must resolve function binding, type, value, and size"); + Test.expect(External != nullptr && External->Binding == rcc::mc::MCObjectSymbolBinding::Global && + !External->SectionIndex && External->Value == 0, + "unqualified undefined symbols must become global ELF symbols"); +} + +void testAssemblerAtomicity(TestContext &Test, TestCodeEmitter &Emitter, + const TestAsmBackend &Backend) { + rcc::mc::MCContext Context; + auto Text = createSection(Context, ".text.atomic", rcc::mc::MCSectionKind::Text, + {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Executable}, 4); + auto Symbol = Context.getOrCreateSymbol("atomic_function", rcc::mc::MCSymbolKind::Function); + if (!Text || !Symbol) { + Test.expect(false, "MC assembler atomicity fixture must be constructible"); + return; + } + rcc::mc::MCAssembler Assembler(Emitter, Backend); + if (!Assembler.switchSection(*Text) || !Assembler.emitLabel(*Symbol) || + !Assembler.emitInstruction(testInstruction())) { + Test.expect(false, "MC assembler atomicity events must succeed"); + return; + } + + Emitter.FailNextEncoding = true; + auto Failed = Assembler.assemble(); + Test.expect(!Failed && Failed.error().code() == rcc::ErrorCode::InstructionEncodingFailed, + "MC assembler must propagate an encoding failure"); + Test.expect(Assembler.emitAlignment(8, 0, 0).has_value(), + "failed assembly must leave the input event stream mutable and unchanged"); + auto Succeeded = Assembler.assemble(); + Test.expect(Succeeded.has_value() && Succeeded->Sections.size() == 1 && + Succeeded->Sections[0].AddressSize == 8, + "MC assembler must support a clean retry after a failed transaction"); + if (!Succeeded) + return; + auto Size = rcc::mc::MCExpr::constant(4); + Test.expect(!Assembler.switchSection(*Text) && !Assembler.emitLabel(*Symbol) && + !Assembler.emitInstruction(testInstruction()) && !Assembler.emitValue(Size, 4) && + !Assembler.emitZeroFill(1) && !Assembler.emitAlignment(4, 0, 0) && + !Assembler.emitSymbolAttribute(*Symbol, rcc::mc::MCSymbolAttribute::Global) && + !Assembler.emitSymbolSize(*Symbol, Size) && + !Assembler.emitCommonSymbol(*Symbol, 4, 4) && !Assembler.assemble(), + "successful assembly must make every mutation and repeated finalization terminal"); +} + +void testSymbolSizeValidation(TestContext &Test, const TestCodeEmitter &Emitter, + const TestAsmBackend &Backend) { + rcc::mc::MCContext Context; + auto Text = createSection(Context, ".text.size", rcc::mc::MCSectionKind::Text, + {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Executable}, 4); + auto Data = createSection(Context, ".data.size", rcc::mc::MCSectionKind::Data, + {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Writable}, 4); + auto Function = Context.getOrCreateSymbol("sized_function", rcc::mc::MCSymbolKind::Function); + auto DataEnd = Context.createTemporarySymbol(".Ldata_end"); + if (!Text || !Data || !Function || !DataEnd) { + Test.expect(false, "symbol-size validation fixture must be constructible"); + return; + } + + auto CrossSectionSize = subtractSymbols(*DataEnd, *Function); + auto FunctionExpression = symbolExpression(*Function); + if (!CrossSectionSize || !FunctionExpression) { + Test.expect(false, "symbol-size validation expressions must be constructible"); + return; + } + auto TargetSize = rcc::mc::MCExpr::target(1, *FunctionExpression); + if (!TargetSize) { + Test.expect(false, "target symbol-size expression must be constructible"); + return; + } + + rcc::mc::MCAssembler CrossSectionAssembler(Emitter, Backend); + if (!CrossSectionAssembler.switchSection(*Text) || !CrossSectionAssembler.emitLabel(*Function) || + !CrossSectionAssembler.emitInstruction(testInstruction()) || + !CrossSectionAssembler.switchSection(*Data) || !CrossSectionAssembler.emitLabel(*DataEnd) || + !CrossSectionAssembler.emitSymbolSize(*Function, *CrossSectionSize)) { + Test.expect(false, "cross-section symbol-size events must succeed before finalization"); + return; + } + auto CrossSectionResult = CrossSectionAssembler.assemble(); + Test.expect(!CrossSectionResult && + CrossSectionResult.error().code() == rcc::ErrorCode::ObjectWritingFailed, + "MC assembler must reject a symbol-size difference across sections"); + + rcc::mc::MCAssembler TargetExpressionAssembler(Emitter, Backend); + if (!TargetExpressionAssembler.switchSection(*Text) || + !TargetExpressionAssembler.emitLabel(*Function) || + !TargetExpressionAssembler.emitInstruction(testInstruction()) || + !TargetExpressionAssembler.emitSymbolSize(*Function, *TargetSize)) { + Test.expect(false, "target symbol-size events must succeed before finalization"); + return; + } + auto TargetExpressionResult = TargetExpressionAssembler.assemble(); + Test.expect(!TargetExpressionResult && + TargetExpressionResult.error().code() == rcc::ErrorCode::ObjectWritingFailed, + "MC assembler must reject a target expression as a symbol size"); +} + +[[nodiscard]] std::uint16_t readU16(std::span<const std::uint8_t> Bytes, std::size_t Offset) { + return static_cast<std::uint16_t>(static_cast<std::uint16_t>(Bytes[Offset]) | + static_cast<std::uint16_t>(Bytes[Offset + 1U]) << 8U); +} + +[[nodiscard]] std::uint32_t readU32(std::span<const std::uint8_t> Bytes, std::size_t Offset) { + std::uint32_t Result = 0; + for (std::uint32_t Index = 0; Index < 4; ++Index) + Result |= static_cast<std::uint32_t>(Bytes[Offset + Index]) << (Index * 8U); + return Result; +} + +[[nodiscard]] bool contains(std::span<const std::uint8_t> Bytes, std::string_view Text) { + if (Text.size() > Bytes.size()) + return false; + const auto *Data = reinterpret_cast<const char *>(Bytes.data()); + return std::string_view(Data, Bytes.size()).find(Text) != std::string_view::npos; +} + +void testELFWriter(TestContext &Test, const TestCodeEmitter &Emitter, + const TestAsmBackend &Backend) { + rcc::mc::MCContext Context; + auto Object = assembleFixture(Context, Emitter, Backend); + if (!Object) { + Test.expect(false, "ELF writer fixture must assemble"); + return; + } + rcc::mc::ELFObjectTargetInfo Target{ + .Machine = 243, + .OSABI = 3, + .ABIVersion = 1, + .Flags = 1, + .ExtraSections = {{.Name = ".note.test", + .Type = 7, + .Flags = 0, + .Alignment = 4, + .EntrySize = 0, + .Contents = {1, 2, 3, 4}}}, + }; + rcc::mc::ELF64LEObjectWriter Writer(Target); + auto First = Writer.writeObject(*Object); + auto Second = Writer.writeObject(*Object); + Test.expect(First.has_value() && Second.has_value() && *First == *Second, + "ELF writer output must be deterministic"); + if (!First) + return; + Test.expect(First->size() < 4096 && First->size() >= 64 && + std::ranges::equal(std::span<const std::uint8_t>(*First).first(4), + std::array<std::uint8_t, 4>{0x7f, 'E', 'L', 'F'}) && + readU16(*First, 16) == 1 && readU16(*First, 18) == 243 && + readU32(*First, 48) == 0x21 && contains(*First, ".bss") && + contains(*First, ".rela.data") && contains(*First, ".note.test") && + contains(*First, "function") && contains(*First, "external"), + "ELF writer must encode its header, sparse BSS, sections, symbols, and relocation"); + + rcc::mc::ELFObjectTargetInfo MissingMachine = Target; + MissingMachine.Machine = 0; + Test.expect(!rcc::mc::ELF64LEObjectWriter(MissingMachine).writeObject(*Object), + "ELF writer must reject an unconfigured target machine"); + + auto DuplicateSection = *Object; + DuplicateSection.Sections.push_back(DuplicateSection.Sections.front()); + Test.expect(!Writer.writeObject(DuplicateSection), + "ELF writer must reject duplicate section identities"); + + auto BadAlignment = *Object; + BadAlignment.Sections[0].Alignment = 3; + Test.expect(!Writer.writeObject(BadAlignment), + "ELF writer must reject a non-power-of-two effective alignment"); + + auto BadContents = *Object; + ++BadContents.Sections[0].AddressSize; + Test.expect(!Writer.writeObject(BadContents), + "ELF writer must reject PROGBITS size/content disagreement"); + + auto BadNoBits = *Object; + BadNoBits.Sections[2].Relocations.push_back(rcc::mc::MCObjectRelocation{ + .Offset = 0, .Type = 1, .Symbol = Object->Symbols.back().Symbol, .Addend = 0}); + Test.expect(!Writer.writeObject(BadNoBits), + "ELF writer must reject relocations in a NOBITS section"); + + auto DuplicateSymbol = *Object; + DuplicateSymbol.Symbols.push_back(DuplicateSymbol.Symbols.front()); + Test.expect(!Writer.writeObject(DuplicateSymbol), + "ELF writer must reject duplicate symbol identities"); + + auto OutOfRangeSymbol = *Object; + auto Defined = std::ranges::find_if( + OutOfRangeSymbol.Symbols, [](const auto &Symbol) { return Symbol.SectionIndex.has_value(); }); + if (Defined != OutOfRangeSymbol.Symbols.end()) { + const std::size_t SectionIndex = + Defined->SectionIndex.value_or(OutOfRangeSymbol.Sections.size()); + Defined->Value = OutOfRangeSymbol.Sections[SectionIndex].AddressSize + 1U; + Test.expect(!Writer.writeObject(OutOfRangeSymbol), + "ELF writer must reject a symbol outside its defining section"); + } + + auto OutOfRangeSymbolSize = *Object; + auto Sized = std::ranges::find_if(OutOfRangeSymbolSize.Symbols, [](const auto &Symbol) { + return Symbol.SectionIndex.has_value() && Symbol.Size != 0; + }); + if (Sized != OutOfRangeSymbolSize.Symbols.end()) { + const std::size_t SectionIndex = + Sized->SectionIndex.value_or(OutOfRangeSymbolSize.Sections.size()); + Sized->Size = OutOfRangeSymbolSize.Sections[SectionIndex].AddressSize - Sized->Value + 1U; + Test.expect(!Writer.writeObject(OutOfRangeSymbolSize), + "ELF writer must reject a symbol range outside its defining section"); + } + + auto BadCommon = *Object; + auto Undefined = std::ranges::find_if(BadCommon.Symbols, + [](const auto &Symbol) { return !Symbol.SectionIndex; }); + if (Undefined != BadCommon.Symbols.end()) { + Undefined->Binding = rcc::mc::MCObjectSymbolBinding::Local; + Undefined->Value = 3; + Undefined->Size = 4; + Undefined->CommonAlignment = 3; + Test.expect(!Writer.writeObject(BadCommon), + "ELF writer must reject an invalid common symbol definition"); + } + + auto BadRelocationOffset = *Object; + BadRelocationOffset.Sections[1].Relocations[0].Offset = + BadRelocationOffset.Sections[1].AddressSize; + Test.expect(!Writer.writeObject(BadRelocationOffset), + "ELF writer must reject a relocation outside its section"); + + rcc::mc::MCContext OtherContext; + auto Unknown = OtherContext.getOrCreateSymbol("unknown", rcc::mc::MCSymbolKind::Generic); + if (Unknown) { + auto UnknownRelocation = *Object; + UnknownRelocation.Sections[1].Relocations[0].Symbol = *Unknown; + Test.expect(!Writer.writeObject(UnknownRelocation), + "ELF writer must reject a relocation to an unknown symbol"); + } + + auto BadExtraTarget = Target; + BadExtraTarget.ExtraSections[0].Alignment = 3; + Test.expect(!rcc::mc::ELF64LEObjectWriter(std::move(BadExtraTarget)).writeObject(*Object), + "ELF writer must reject an invalid target extra section"); + + auto ManagedExtraTarget = Target; + ManagedExtraTarget.ExtraSections[0].Type = 2; + Test.expect(!rcc::mc::ELF64LEObjectWriter(std::move(ManagedExtraTarget)).writeObject(*Object), + "ELF writer must reject a writer-managed target extra section type"); + + auto ExtendedNumberingTarget = Target; + ExtendedNumberingTarget.ExtraSections.resize(0xff00U); + auto ExtendedNumbering = + rcc::mc::ELF64LEObjectWriter(std::move(ExtendedNumberingTarget)).writeObject(*Object); + Test.expect(!ExtendedNumbering && ExtendedNumbering.error().code() == rcc::ErrorCode::Unsupported, + "ELF writer must reject section counts that require extended numbering"); +} + +} // namespace + +int main() { + TestContext Test; + TestCodeEmitter Emitter; + TestAsmBackend Backend; + testAssembler(Test, Emitter, Backend); + testAssemblerAtomicity(Test, Emitter, Backend); + testSymbolSizeValidation(Test, Emitter, Backend); + testELFWriter(Test, Emitter, Backend); + return Test.result(); +} diff --git a/backend/unittests/Support/ErrorTest.cpp b/backend/unittests/Support/ErrorTest.cpp index f111063..803ccee 100644 --- a/backend/unittests/Support/ErrorTest.cpp +++ b/backend/unittests/Support/ErrorTest.cpp @@ -42,6 +42,15 @@ int main() { return fail("CFG-optimization errors must have a stable name"); if (rcc::getErrorCodeName(rcc::ErrorCode::AssemblyLoweringFailed) != "assembly lowering failed") return fail("assembly-lowering errors must have a stable name"); + if (rcc::getErrorCodeName(rcc::ErrorCode::InstructionEncodingFailed) != + "instruction encoding failed") + return fail("instruction-encoding errors must have a stable name"); + if (rcc::getErrorCodeName(rcc::ErrorCode::FixupApplicationFailed) != "fixup application failed") + return fail("fixup-application errors must have a stable name"); + if (rcc::getErrorCodeName(rcc::ErrorCode::RelocationFailed) != "relocation failed") + return fail("relocation errors must have a stable name"); + if (rcc::getErrorCodeName(rcc::ErrorCode::ObjectWritingFailed) != "object writing failed") + return fail("object-writing errors must have a stable name"); return 0; } 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(); +} |
