aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/include/rcc/MC/ELFObjectWriter.h45
-rw-r--r--backend/include/rcc/MC/MCAsmBackend.h55
-rw-r--r--backend/include/rcc/MC/MCAssembler.h131
-rw-r--r--backend/include/rcc/MC/MCCodeEmitter.h30
-rw-r--r--backend/include/rcc/MC/MCDwarfFrameEmitter.h44
-rw-r--r--backend/include/rcc/MC/MCFixup.h52
-rw-r--r--backend/include/rcc/MC/MCObjectStreamer.h59
-rw-r--r--backend/include/rcc/MC/MCObjectWriter.h79
-rw-r--r--backend/include/rcc/MC/MCStreamer.h4
-rw-r--r--backend/include/rcc/Support/Error.h4
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVAsmBackend.h30
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVFixupKinds.h32
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVMCCodeEmitter.h24
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVObjectFile.h35
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVTarget.h1
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVTargetConstant.h73
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVTargetRelocationKinds.h24
-rw-r--r--backend/lib/AsmPrinter/AsmPrinter.cpp11
-rw-r--r--backend/lib/CodeGen/MachineCombiner.cpp4
-rw-r--r--backend/lib/CodeGen/MachineCompilationStatus.cpp4
-rw-r--r--backend/lib/CodeGen/MachineDCE.cpp10
-rw-r--r--backend/lib/MC/CMakeLists.txt14
-rw-r--r--backend/lib/MC/ELFObjectWriter.cpp707
-rw-r--r--backend/lib/MC/MCAsmBackend.cpp25
-rw-r--r--backend/lib/MC/MCAssembler.cpp849
-rw-r--r--backend/lib/MC/MCDwarfFrameEmitter.cpp443
-rw-r--r--backend/lib/MC/MCFixup.cpp177
-rw-r--r--backend/lib/MC/MCObjectStreamer.cpp132
-rw-r--r--backend/lib/MC/MCStreamer.cpp16
-rw-r--r--backend/lib/Support/Error.cpp8
-rw-r--r--backend/lib/Target/Description/TargetDescriptionGenerator.cpp30
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt18
-rw-r--r--backend/lib/Target/RISCV/RISCV.rccdesc195
-rw-r--r--backend/lib/Target/RISCV/RISCVAsmBackend.cpp382
-rw-r--r--backend/lib/Target/RISCV/RISCVMCCodeEmitter.cpp984
-rw-r--r--backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp112
-rw-r--r--backend/lib/Target/RISCV/RISCVObjectFile.cpp148
-rw-r--r--backend/lib/Target/RISCV/RISCVTargetConstant.cpp169
-rw-r--r--backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp78
-rw-r--r--backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake5
-rw-r--r--backend/tools/AssemblyEmitter/CMakeLists.txt22
-rw-r--r--backend/tools/AssemblyEmitter/ObjectPipelineTest.cmake88
-rw-r--r--backend/unittests/CodeGen/MachineOptimizationTest.cpp32
-rw-r--r--backend/unittests/MC/CMakeLists.txt15
-rw-r--r--backend/unittests/MC/MCObjectTest.cpp500
-rw-r--r--backend/unittests/Support/ErrorTest.cpp9
-rw-r--r--backend/unittests/Target/CMakeLists.txt30
-rw-r--r--backend/unittests/Target/DescriptionGeneratorTest.cpp2
-rw-r--r--backend/unittests/Target/Inputs/RISCVStackFramePipeline.mir12
-rw-r--r--backend/unittests/Target/RISCVMCEncodingCoverageTest.cpp411
-rw-r--r--backend/unittests/Target/RISCVMCTest.cpp264
-rw-r--r--backend/unittests/Target/RISCVObjectFileTest.cpp791
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> &sections() const noexcept {
+ return Sections;
+ }
+
+private:
+ explicit ELFView(std::span<const std::uint8_t> Bytes) : Bytes(Bytes) {}
+
+ std::span<const std::uint8_t> Bytes;
+ std::vector<ELFSection> Sections;
+};
+
+[[nodiscard]] const ELFSymbol *findSymbol(const std::vector<ELFSymbol> &Symbols,
+ std::string_view Name) {
+ const auto Position =
+ std::ranges::find(Symbols, Name, [](const ELFSymbol &Symbol) { return Symbol.Name; });
+ return Position == Symbols.end() ? nullptr : &*Position;
+}
+
+[[nodiscard]] rcc::Expected<rcc::Alignment> alignment(std::uint64_t Value) {
+ return rcc::Alignment::create(Value);
+}
+
+[[nodiscard]] rcc::Expected<rcc::GlobalInitializer> integer(std::uint32_t Bits,
+ std::uint64_t Value) {
+ auto Immediate = rcc::IntegerImmediate::fromUnsigned(Bits, Value);
+ if (!Immediate)
+ return std::unexpected(Immediate.error());
+ return rcc::GlobalInitializer::integer(std::move(*Immediate));
+}
+
+[[nodiscard]] bool addObject(rcc::CompilationUnit &Unit, std::string Name, std::uint64_t Size,
+ std::uint64_t Alignment, rcc::GlobalLinkage Linkage,
+ std::optional<rcc::GlobalInitializer> Initializer,
+ const auto &Configure) {
+ auto Align = alignment(Alignment);
+ if (!Align)
+ return false;
+ rcc::GlobalObjectDesc Object;
+ Object.Symbol = std::move(Name);
+ Object.ObjectType = rcc::GenericConstantType::byte();
+ Object.Linkage = Linkage;
+ Object.Align = *Align;
+ Object.Size = Size;
+ Object.Initializer = std::move(Initializer);
+ Configure(Object);
+ return Unit.addGlobalObject(std::move(Object)).has_value();
+}
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef> symbolDifference(rcc::mc::MCContext &Context,
+ std::string_view Left,
+ std::string_view Right,
+ std::int64_t Addend) {
+ auto LeftSymbol = Context.getOrCreateSymbol(std::string(Left), rcc::mc::MCSymbolKind::Generic);
+ auto RightSymbol = Context.getOrCreateSymbol(std::string(Right), rcc::mc::MCSymbolKind::Generic);
+ if (!LeftSymbol || !RightSymbol)
+ return std::unexpected(!LeftSymbol ? LeftSymbol.error() : RightSymbol.error());
+ auto LeftExpression = rcc::mc::MCExpr::symbol(*LeftSymbol);
+ auto RightExpression = rcc::mc::MCExpr::symbol(*RightSymbol);
+ if (!LeftExpression || !RightExpression)
+ return std::unexpected(!LeftExpression ? LeftExpression.error() : RightExpression.error());
+ auto Difference = rcc::mc::MCExpr::binary(
+ rcc::mc::MCBinaryOperator::Subtract, std::move(*LeftExpression), std::move(*RightExpression));
+ if (!Difference)
+ return std::unexpected(Difference.error());
+ return rcc::mc::MCExpr::binary(rcc::mc::MCBinaryOperator::Add, std::move(*Difference),
+ rcc::mc::MCExpr::constant(Addend));
+}
+
+[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef>
+symbolExpression(rcc::mc::MCContext &Context, std::string_view Name,
+ rcc::mc::MCSymbolKind Kind = rcc::mc::MCSymbolKind::Generic) {
+ auto Symbol = Context.getOrCreateSymbol(std::string(Name), Kind);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return rcc::mc::MCExpr::symbol(std::move(*Symbol));
+}
+
+[[nodiscard]] rcc::mc::MCInst instruction(rcc::riscv::InstructionOpcode Opcode) {
+ return rcc::mc::MCInst(rcc::mc::MCOpcode::fromValue(static_cast<std::uint32_t>(Opcode)));
+}
+
+void testMCRelaxation(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::riscv::RISCVMCCodeEmitter Emitter(Target);
+ rcc::riscv::RISCVAsmBackend Backend;
+ rcc::mc::MCContext MCContext;
+ auto Text = MCContext.getOrCreateSection(rcc::mc::MCSectionDesc{
+ .Name = ".text.relax",
+ .Kind = rcc::mc::MCSectionKind::Text,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Executable},
+ .Alignment = 2,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ });
+ auto JumpTarget = MCContext.createTemporarySymbol(".Ljump_target");
+ auto BranchTarget = MCContext.createTemporarySymbol(".Lbranch_target");
+ if (!Text || !JumpTarget || !BranchTarget) {
+ Context.expect(false, "RISC-V MC relaxation fixture construction must succeed");
+ return;
+ }
+ auto JumpExpression = rcc::mc::MCExpr::symbol(*JumpTarget);
+ auto BranchExpression = rcc::mc::MCExpr::symbol(*BranchTarget);
+ if (!JumpExpression || !BranchExpression) {
+ Context.expect(false, "RISC-V MC relaxation expressions must be constructible");
+ return;
+ }
+
+ rcc::mc::MCInst Jump = instruction(rcc::riscv::InstructionOpcode::C_J);
+ Jump.addOperand(rcc::mc::MCOperand::expression(*JumpExpression));
+ rcc::mc::MCInst Branch = instruction(rcc::riscv::InstructionOpcode::C_BEQZ);
+ Branch.addOperand(rcc::mc::MCOperand::registerOperand(rcc::mc::MCRegister::fromValue(
+ static_cast<std::uint32_t>(rcc::riscv::PhysicalRegisterID::X8))));
+ Branch.addOperand(rcc::mc::MCOperand::expression(*BranchExpression));
+
+ rcc::mc::MCAssembler Assembler(Emitter, Backend);
+ if (!Assembler.switchSection(*Text) || !Assembler.emitInstruction(Jump) ||
+ !Assembler.emitZeroFill(3000) || !Assembler.emitLabel(*JumpTarget) ||
+ !Assembler.emitInstruction(Branch) || !Assembler.emitZeroFill(600) ||
+ !Assembler.emitLabel(*BranchTarget)) {
+ Context.expect(false, "RISC-V MC relaxation events must succeed");
+ return;
+ }
+ auto Object = Assembler.assemble();
+ if (!Object)
+ std::cerr << "RISC-V MC relaxation failure: " << Object.error().message() << '\n';
+ Context.expect(Object.has_value(), "RISC-V MC relaxation must reach a fixed point");
+ if (!Object || Object->Sections.size() != 1)
+ return;
+ const auto JumpWord = readU32(Object->Sections[0].Contents, 0);
+ const auto BranchWord = readU32(Object->Sections[0].Contents, 3004);
+ Context.expect(JumpWord && (*JumpWord & 0xfffU) == 0x6fU,
+ "out-of-range C.J must monotonically relax to JAL x0");
+ Context.expect(BranchWord && (*BranchWord & 0x01fff07fU) == 0x00040063U,
+ "out-of-range C.BEQZ must monotonically relax to BEQ x8, x0");
+ Context.expect(Object->Sections[0].AddressSize == 3608 && Object->Sections[0].Relocations.empty(),
+ "resolved MC relaxation must update layout without linker relocations");
+
+ auto ExternalExpression = symbolExpression(MCContext, "external_jump");
+ if (!ExternalExpression) {
+ Context.expect(false, "external MC relaxation expression must be constructible");
+ return;
+ }
+ rcc::mc::MCInst ExternalJump = instruction(rcc::riscv::InstructionOpcode::C_J);
+ ExternalJump.addOperand(rcc::mc::MCOperand::expression(*ExternalExpression));
+ rcc::mc::MCAssembler Relocatable(Emitter, Backend);
+ auto Switched = Relocatable.switchSection(*Text);
+ auto Emitted = Relocatable.emitInstruction(ExternalJump);
+ auto ExternalObject = Relocatable.assemble();
+ Context.expect(
+ Switched && Emitted && ExternalObject &&
+ ExternalObject->Sections[0].Relocations.size() == 1 &&
+ ExternalObject->Sections[0].Relocations[0].Type == 45,
+ "unresolved C.J must retain one mandatory relocation without implicit linker relax");
+}
+
+void testAssemblerLayout(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::riscv::RISCVMCCodeEmitter Emitter(Target);
+ rcc::riscv::RISCVAsmBackend Backend;
+ rcc::mc::MCContext MCContext;
+ auto Data = MCContext.getOrCreateSection(rcc::mc::MCSectionDesc{
+ .Name = ".data",
+ .Kind = rcc::mc::MCSectionKind::Data,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Writable},
+ .Alignment = 1,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ });
+ auto BSS = MCContext.getOrCreateSection(rcc::mc::MCSectionDesc{
+ .Name = ".bss",
+ .Kind = rcc::mc::MCSectionKind::BSS,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Writable},
+ .Alignment = 1,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ });
+ auto Difference = symbolDifference(MCContext, "left", "right", 5);
+ if (!Data || !BSS || !Difference) {
+ Context.expect(false, "MC assembler layout fixture construction must succeed");
+ return;
+ }
+
+ rcc::mc::MCAssembler Assembler(Emitter, Backend);
+ constexpr std::uint64_t LargeBSS = (std::uint64_t{1} << 32U) + 32U;
+ if (!Assembler.switchSection(*Data) || !Assembler.emitAlignment(128, 0, 0) ||
+ !Assembler.emitValue(*Difference, 8) || !Assembler.switchSection(*BSS) ||
+ !Assembler.emitAlignment(4096, 0, 0) || !Assembler.emitZeroFill(LargeBSS)) {
+ Context.expect(false, "MC assembler layout events must succeed");
+ return;
+ }
+ auto Object = Assembler.assemble();
+ if (!Object)
+ std::cerr << "MC assembler layout failure: " << Object.error().message() << '\n';
+ Context.expect(Object.has_value(), "MC assembler layout must complete");
+ if (!Object || Object->Sections.size() != 2)
+ return;
+ Context.expect(Object->Sections[0].Alignment == 128 &&
+ Object->Sections[0].Relocations.size() == 2 &&
+ Object->Sections[0].Relocations[0].Type == 36 &&
+ Object->Sections[0].Relocations[0].Addend == 5 &&
+ Object->Sections[0].Relocations[1].Type == 40 &&
+ Object->Sections[0].Relocations[1].Addend == 0,
+ "MC assembler must preserve effective alignment and paired symbol relocations");
+ Context.expect(Object->Sections[1].Alignment == 4096 &&
+ Object->Sections[1].AddressSize == LargeBSS &&
+ Object->Sections[1].Contents.empty(),
+ "MC assembler must keep NOBITS address size without materializing zero bytes");
+
+ rcc::mc::MCAssembler BadAssembler(Emitter, Backend);
+ const std::array<std::uint8_t, 1> Nonzero{1};
+ auto Switched = BadAssembler.switchSection(*BSS);
+ auto Emitted = BadAssembler.emitBytes(Nonzero);
+ auto Failure = BadAssembler.assemble();
+ Context.expect(Switched && Emitted && !Failure &&
+ Failure.error().code() == rcc::ErrorCode::ObjectWritingFailed,
+ "MC assembler must reject initialized bytes in a NOBITS section");
+}
+
+void testNativeObjectBoundaries(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::riscv::RISCVMCCodeEmitter Emitter(Target);
+ rcc::riscv::RISCVAsmBackend Backend;
+ rcc::mc::ELF64LEObjectWriter Writer(rcc::mc::ELFObjectTargetInfo{
+ .Machine = 243, .OSABI = 0, .ABIVersion = 0, .Flags = 0, .ExtraSections = {}});
+ rcc::mc::ELFObjectStreamer Streamer(Emitter, Backend, Writer);
+ auto EmptyAssembly = Streamer.emitInlineAssembly({});
+ auto RawAssembly = Streamer.emitInlineAssembly("addi a0, a0, 1");
+ Context.expect(EmptyAssembly && !RawAssembly &&
+ RawAssembly.error().code() == rcc::ErrorCode::Unsupported,
+ "native object emission must reject unparsed inline assembly without dropping it");
+
+ rcc::mc::MCContext MCContext;
+ auto EHFrame = MCContext.getOrCreateSection(rcc::mc::MCSectionDesc{
+ .Name = ".eh_frame.boundary",
+ .Kind = rcc::mc::MCSectionKind::EHFrame,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc},
+ .Alignment = 8,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ });
+ auto Function = MCContext.getOrCreateSymbol("cfi_boundary", rcc::mc::MCSymbolKind::Function);
+ auto Personality =
+ MCContext.getOrCreateSymbol("cfi_personality", rcc::mc::MCSymbolKind::Function);
+ if (!EHFrame || !Function || !Personality) {
+ Context.expect(false, "native CFI boundary fixture must be constructible");
+ return;
+ }
+ const rcc::mc::MCDwarfFrameInfo Info{.CodeAlignment = 1,
+ .DataAlignment = -8,
+ .ReturnAddressRegister = 1,
+ .InitialCFARegister = 2,
+ .InitialCFAOffset = 0};
+ const std::array UnsupportedEvents{
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::Personality,
+ .Operands = {0},
+ .Bytes = {},
+ .Symbol = *Personality},
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::LSDA,
+ .Operands = {0},
+ .Bytes = {},
+ .Symbol = *Personality},
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::SignalFrame,
+ .Operands = {},
+ .Bytes = {},
+ .Symbol = {}},
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::ReturnColumn,
+ .Operands = {1},
+ .Bytes = {},
+ .Symbol = {}},
+ rcc::mc::MCCFIInstruction{.Operation = rcc::mc::MCCFIOperation::NegateReturnAddressState,
+ .Operands = {},
+ .Bytes = {},
+ .Symbol = {}},
+ rcc::mc::MCCFIInstruction{.Operation =
+ rcc::mc::MCCFIOperation::NegateReturnAddressStateWithPC,
+ .Operands = {},
+ .Bytes = {},
+ .Symbol = {}},
+ };
+ for (const rcc::mc::MCCFIInstruction &Event : UnsupportedEvents) {
+ rcc::mc::MCDwarfFrameProcedure Procedure{
+ .Function = *Function,
+ .AddressRange = 4,
+ .Instructions = {{.Instruction = Event, .Offset = 0}},
+ };
+ auto Frame = rcc::mc::emitDwarfEHFrame(*EHFrame, std::span(&Procedure, 1), Info, Backend);
+ Context.expect(!Frame && Frame.error().code() == rcc::ErrorCode::Unsupported,
+ "unsupported RV64GC CFI extensions must fail structurally");
+ }
+}
+
+[[nodiscard]] bool buildGlobalFixture(rcc::CompilationUnit &Unit) {
+ constexpr auto NoConfiguration = [](rcc::GlobalObjectDesc &) {};
+ if (!addObject(Unit, "external_target", 0, 1, rcc::GlobalLinkage::External, std::nullopt,
+ NoConfiguration) ||
+ !addObject(Unit, "weak_target", 0, 1, rcc::GlobalLinkage::ExternalWeak, std::nullopt,
+ NoConfiguration)) {
+ return false;
+ }
+
+ constexpr std::uint64_t LargeBSS = (std::uint64_t{1} << 32U) + 32U;
+ auto LargeZero = rcc::GlobalInitializer::zeroFill(LargeBSS);
+ auto TLSValue = integer(64, 0x1122334455667788ULL);
+ auto TLSZero = rcc::GlobalInitializer::zeroFill(32);
+ auto Relocation = rcc::GlobalInitializer::symbolAddress(
+ {.Symbol = "external_target", .Addend = 7, .Size = 8, .TargetFlags = 0});
+ auto COMDATRelocation = rcc::GlobalInitializer::symbolAddress(
+ {.Symbol = "external_target", .Addend = 3, .Size = 8, .TargetFlags = 0});
+ auto HiddenValue = integer(32, 0x12345678U);
+ auto ProtectedValue = integer(64, 0x8877665544332211ULL);
+ auto ReadOnlyValue = integer(32, 0xaabbccddU);
+ auto TargetConstant = rcc::TargetConstant::create(rcc::riscv::RISCVSymbolConstant(
+ rcc::riscv::RISCVSymbolConstantKind::ExternalSymbol, "external_target", 13));
+ auto TargetInitializer =
+ TargetConstant
+ ? rcc::GlobalInitializer::target(std::move(*TargetConstant))
+ : rcc::Expected<rcc::GlobalInitializer>(std::unexpected(TargetConstant.error()));
+ if (!LargeZero || !TLSValue || !TLSZero || !Relocation || !COMDATRelocation || !HiddenValue ||
+ !ProtectedValue || !ReadOnlyValue || !TargetInitializer) {
+ return false;
+ }
+
+ std::vector<rcc::GlobalInitializer> Differences;
+ for (const auto [Size, Addend] :
+ std::array<std::pair<std::uint8_t, std::int64_t>, 4>{{{1, 11}, {2, 9}, {4, 7}, {8, 5}}}) {
+ auto Difference = rcc::GlobalInitializer::symbolDifference({.Minuend = "external_target",
+ .Subtrahend = "weak_target",
+ .Addend = Addend,
+ .Size = Size});
+ if (!Difference)
+ return false;
+ Differences.push_back(std::move(*Difference));
+ }
+ auto DifferenceAggregate = rcc::GlobalInitializer::aggregate(std::move(Differences));
+ if (!DifferenceAggregate)
+ return false;
+
+ return addObject(Unit, "large_bss", LargeBSS, 4096, rcc::GlobalLinkage::Internal,
+ std::move(*LargeZero), NoConfiguration) &&
+ addObject(Unit, "tls_data", 8, 8, rcc::GlobalLinkage::External, std::move(*TLSValue),
+ [](rcc::GlobalObjectDesc &Object) {
+ Object.TLSModel = rcc::GlobalTLSModel::LocalExec;
+ }) &&
+ addObject(Unit, "tls_bss", 32, 32, rcc::GlobalLinkage::External, std::move(*TLSZero),
+ [](rcc::GlobalObjectDesc &Object) {
+ Object.TLSModel = rcc::GlobalTLSModel::GeneralDynamic;
+ }) &&
+ addObject(Unit, "common_value", 128, 64, rcc::GlobalLinkage::Common, std::nullopt,
+ NoConfiguration) &&
+ addObject(Unit, "relro_pointer", 8, 8, rcc::GlobalLinkage::External,
+ std::move(*Relocation),
+ [](rcc::GlobalObjectDesc &Object) { Object.IsConstant = true; }) &&
+ addObject(Unit, "difference_value", 15, 1, rcc::GlobalLinkage::External,
+ std::move(*DifferenceAggregate), NoConfiguration) &&
+ addObject(Unit, "comdat_value", 8, 8, rcc::GlobalLinkage::LinkOnceODR,
+ std::move(*COMDATRelocation),
+ [](rcc::GlobalObjectDesc &Object) { Object.COMDAT = Object.Symbol; }) &&
+ addObject(Unit, "hidden_value", 4, 16, rcc::GlobalLinkage::External,
+ std::move(*HiddenValue),
+ [](rcc::GlobalObjectDesc &Object) {
+ Object.Visibility = rcc::GlobalVisibility::Hidden;
+ Object.Section = ".custom.data";
+ }) &&
+ addObject(Unit, "protected_value", 8, 128, rcc::GlobalLinkage::External,
+ std::move(*ProtectedValue),
+ [](rcc::GlobalObjectDesc &Object) {
+ Object.Visibility = rcc::GlobalVisibility::Protected;
+ }) &&
+ addObject(Unit, "readonly_value", 4, 4, rcc::GlobalLinkage::External,
+ std::move(*ReadOnlyValue),
+ [](rcc::GlobalObjectDesc &Object) { Object.IsConstant = true; }) &&
+ addObject(Unit, "target_constant_value", 8, 8, rcc::GlobalLinkage::Internal,
+ std::move(*TargetInitializer), [](rcc::GlobalObjectDesc &Object) {
+ Object.IsConstant = true;
+ Object.Section = ".data.rel.ro.target-constant";
+ });
+}
+
+void testGlobalObjectEmission(TestContext &Context, const rcc::TargetDescription &Target) {
+ rcc::CompilationUnit Unit;
+ Context.expect(buildGlobalFixture(Unit), "global object fixture construction must succeed");
+ auto First = rcc::riscv::emitObject(Unit, Target);
+ auto Second = rcc::riscv::emitObject(Unit, Target);
+ if (!First)
+ std::cerr << "first global object emission failure: " << First.error().message() << '\n';
+ if (!Second)
+ std::cerr << "second global object emission failure: " << Second.error().message() << '\n';
+ Context.expect(First && Second && *First == *Second,
+ "global object emission must be successful and deterministic");
+ if (!First)
+ return;
+ Context.expect(First->size() < std::size_t{64} * 1024U,
+ "large NOBITS globals must not inflate the ELF file");
+ auto View = ELFView::create(*First);
+ Context.expect(View.has_value(), "emitted global object must be a valid ELF64LE container");
+ if (!View)
+ return;
+
+ const ELFSection *BSS = View->section(".bss");
+ const ELFSection *Data = View->section(".data");
+ const ELFSection *TLSData = View->section(".tdata");
+ const ELFSection *TLSBSS = View->section(".tbss");
+ const ELFSection *RelRO = View->section(".data.rel.ro");
+ const ELFSection *COMDAT = View->section(".data.comdat_value");
+ const ELFSection *COMDATRelocations = View->section(".rela.data.comdat_value");
+ const ELFSection *Group = View->section(".group");
+ constexpr std::uint64_t LargeBSS = (std::uint64_t{1} << 32U) + 32U;
+ Context.expect(BSS != nullptr && BSS->Type == SHTNoBits && BSS->Size == LargeBSS &&
+ BSS->Alignment == 4096 &&
+ (BSS->Flags & (SHFWrite | SHFAlloc)) == (SHFWrite | SHFAlloc),
+ "ELF BSS section must preserve its address size, flags, and alignment");
+ Context.expect(Data != nullptr && Data->Alignment == 128,
+ "ELF data section alignment must include later fragment requirements");
+ Context.expect(TLSData != nullptr && TLSData->Type != SHTNoBits &&
+ (TLSData->Flags & SHFTLS) != 0 && TLSBSS != nullptr &&
+ TLSBSS->Type == SHTNoBits && (TLSBSS->Flags & SHFTLS) != 0,
+ "ELF TLS data and BSS sections must carry their TLS semantics");
+ Context.expect(RelRO != nullptr && COMDAT != nullptr && COMDATRelocations != nullptr &&
+ Group != nullptr && Group->Type == SHTGroup &&
+ (COMDAT->Flags & SHFGroup) != 0 && (COMDATRelocations->Flags & SHFGroup) != 0,
+ "ELF COMDAT data and relocation sections must share a group");
+ Context.expect(View->section(".custom.data") != nullptr && View->section(".rodata") != nullptr &&
+ View->section(".riscv.attributes") != nullptr,
+ "ELF custom, read-only, and RISC-V attribute sections must be present");
+ const ELFSection *TargetConstantSection = View->section(".data.rel.ro.target-constant");
+ Context.expect(TargetConstantSection != nullptr && TargetConstantSection->Size == 8 &&
+ std::ranges::all_of(View->contents(*TargetConstantSection),
+ [](std::uint8_t Byte) { return Byte == 0; }),
+ "RV64GC target constants must reserve their relocation storage");
+
+ auto Symbols = View->symbols();
+ Context.expect(Symbols.has_value(), "ELF symbol table must be parseable");
+ if (!Symbols)
+ return;
+ const ELFSymbol *External = findSymbol(*Symbols, "external_target");
+ const ELFSymbol *Weak = findSymbol(*Symbols, "weak_target");
+ const ELFSymbol *Large = findSymbol(*Symbols, "large_bss");
+ const ELFSymbol *TLS = findSymbol(*Symbols, "tls_data");
+ const ELFSymbol *Common = findSymbol(*Symbols, "common_value");
+ const ELFSymbol *Hidden = findSymbol(*Symbols, "hidden_value");
+ const ELFSymbol *Protected = findSymbol(*Symbols, "protected_value");
+ const ELFSymbol *COMDATSymbol = findSymbol(*Symbols, "comdat_value");
+ Context.expect(External != nullptr && External->Binding == 1 &&
+ External->Section == SHNUndefined && External->Type == 1 && Weak != nullptr &&
+ Weak->Binding == 2 && Weak->Section == SHNUndefined,
+ "undefined object declarations must retain global and weak bindings");
+ Context.expect(Large != nullptr && Large->Binding == 0 && Large->Size == LargeBSS &&
+ TLS != nullptr && TLS->Binding == 1 && TLS->Type == 6,
+ "local BSS and global TLS symbols must have correct ELF types and sizes");
+ Context.expect(Common != nullptr && Common->Binding == 1 && Common->Section == SHNCommon &&
+ Common->Value == 64 && Common->Size == 128,
+ "ELF common symbol alignment and size must be preserved");
+ Context.expect(Hidden != nullptr && Hidden->Visibility == 2 && Protected != nullptr &&
+ Protected->Visibility == 3 && COMDATSymbol != nullptr &&
+ COMDATSymbol->Binding == 2,
+ "ELF visibility and COMDAT weak binding must be preserved");
+
+ auto DifferenceRelocations = View->relocations(".rela.data");
+ Context.expect(DifferenceRelocations && DifferenceRelocations->size() == 8,
+ "symbol differences must emit eight paired ELF relocations");
+ if (DifferenceRelocations) {
+ const std::array<std::uint32_t, 8> ExpectedTypes{33, 37, 34, 38, 35, 39, 36, 40};
+ for (std::size_t Index = 0; Index < ExpectedTypes.size(); ++Index) {
+ Context.expect((*DifferenceRelocations)[Index].Type == ExpectedTypes[Index],
+ "symbol difference relocation type order is incorrect");
+ }
+ const std::array<std::int64_t, 8> ExpectedAddends{11, 0, 9, 0, 7, 0, 5, 0};
+ for (std::size_t Index = 0; Index < ExpectedAddends.size(); ++Index) {
+ Context.expect((*DifferenceRelocations)[Index].Addend == ExpectedAddends[Index],
+ "symbol difference relocation addend is incorrect");
+ }
+ }
+ auto Relocations = View->relocations(".rela.data.rel.ro");
+ Context.expect(Relocations && Relocations->size() == 1 && (*Relocations)[0].Type == 2 &&
+ (*Relocations)[0].Addend == 7,
+ "read-only pointer data must emit an R_RISCV_64 relocation");
+ auto COMDATFixups = View->relocations(".rela.data.comdat_value");
+ Context.expect(COMDATFixups && COMDATFixups->size() == 1 && (*COMDATFixups)[0].Type == 2 &&
+ (*COMDATFixups)[0].Addend == 3,
+ "COMDAT pointer data must retain its relocation");
+ auto TargetConstantFixups = View->relocations(".rela.data.rel.ro.target-constant");
+ Context.expect(TargetConstantFixups && TargetConstantFixups->size() == 1 &&
+ (*TargetConstantFixups)[0].Type == 2 &&
+ (*TargetConstantFixups)[0].Addend == 13,
+ "RV64GC target constants must emit their declared R_RISCV_64 relocation");
+
+ if (Group != nullptr && COMDATSymbol != nullptr) {
+ const std::span<const std::uint8_t> GroupBytes = View->contents(*Group);
+ const auto GroupFlags = readU32(GroupBytes, 0);
+ const auto COMDATIndex = View->sectionIndex(".data.comdat_value");
+ const auto RelocationIndex = View->sectionIndex(".rela.data.comdat_value");
+ bool ContainsCOMDAT = false;
+ bool ContainsRelocations = false;
+ for (std::size_t Offset = 4; Offset + 4 <= GroupBytes.size(); Offset += 4) {
+ const auto Member = readU32(GroupBytes, Offset);
+ ContainsCOMDAT |= Member && COMDATIndex && *Member == *COMDATIndex;
+ ContainsRelocations |= Member && RelocationIndex && *Member == *RelocationIndex;
+ }
+ Context.expect(GroupFlags == 1 && Group->Info < Symbols->size() &&
+ (*Symbols)[Group->Info].Name == "comdat_value" && ContainsCOMDAT &&
+ ContainsRelocations,
+ "ELF COMDAT group must name its signature and both member sections");
+ }
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ auto Target = rcc::riscv::createTargetDescription();
+ Context.expect(Target.has_value(), "RISC-V object test target creation must succeed");
+ if (!Target)
+ return Context.result();
+ testAssemblerLayout(Context, *Target);
+ testMCRelaxation(Context, *Target);
+ testNativeObjectBoundaries(Context, *Target);
+ testGlobalObjectEmission(Context, *Target);
+ return Context.result();
+}