aboutsummaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-09 18:31:49 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-09 18:31:49 +0800
commit379ff50dcc7ccf977379aee68fa9d10f69065d23 (patch)
treee0fac3952d46d0bbcaea32bcfc6087c95fff2992 /backend
parent495c675771f43cfa12ab7a426a538be72fec1805 (diff)
downloadrcc-project-379ff50dcc7ccf977379aee68fa9d10f69065d23.tar.gz
rcc-project-379ff50dcc7ccf977379aee68fa9d10f69065d23.tar.xz
[MC] Implement RV64GC assembly emission
Diffstat (limited to 'backend')
-rw-r--r--backend/include/rcc/CodeGen/AsmPrinter.h57
-rw-r--r--backend/include/rcc/CodeGen/CompilationUnit.h13
-rw-r--r--backend/include/rcc/CodeGen/GlobalObject.h196
-rw-r--r--backend/include/rcc/CodeGen/MachineMCContext.h68
-rw-r--r--backend/include/rcc/CodeGen/TargetMCInstLowering.h69
-rw-r--r--backend/include/rcc/MC/MCCFI.h54
-rw-r--r--backend/include/rcc/MC/MCContext.h43
-rw-r--r--backend/include/rcc/MC/MCExpr.h109
-rw-r--r--backend/include/rcc/MC/MCInst.h174
-rw-r--r--backend/include/rcc/MC/MCInstPrinter.h31
-rw-r--r--backend/include/rcc/MC/MCSection.h108
-rw-r--r--backend/include/rcc/MC/MCStreamer.h93
-rw-r--r--backend/include/rcc/MC/MCSymbol.h56
-rw-r--r--backend/include/rcc/Support/Error.h1
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVAssemblyPrinter.h35
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVInstPrinter.h36
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVMCTargetLowering.h62
-rw-r--r--backend/lib/AsmPrinter/AsmPrinter.cpp679
-rw-r--r--backend/lib/AsmPrinter/CMakeLists.txt58
-rw-r--r--backend/lib/AsmPrinter/MachineMCContext.cpp153
-rw-r--r--backend/lib/AsmPrinter/TargetMCInstLowering.cpp49
-rw-r--r--backend/lib/CMakeLists.txt2
-rw-r--r--backend/lib/CodeGen/CMakeLists.txt4
-rw-r--r--backend/lib/CodeGen/CompilationUnit.cpp71
-rw-r--r--backend/lib/CodeGen/GlobalObject.cpp295
-rw-r--r--backend/lib/CodeGen/MachineCompilationStatus.cpp1
-rw-r--r--backend/lib/MC/CMakeLists.txt58
-rw-r--r--backend/lib/MC/MCCFI.cpp70
-rw-r--r--backend/lib/MC/MCContext.cpp95
-rw-r--r--backend/lib/MC/MCExpr.cpp205
-rw-r--r--backend/lib/MC/MCInstPrinter.cpp170
-rw-r--r--backend/lib/MC/MCSection.cpp60
-rw-r--r--backend/lib/MC/MCStreamer.cpp488
-rw-r--r--backend/lib/Support/Error.cpp2
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt20
-rw-r--r--backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp66
-rw-r--r--backend/lib/Target/RISCV/RISCVInstPrinter.cpp279
-rw-r--r--backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp1158
-rw-r--r--backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp171
-rw-r--r--backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake52
-rw-r--r--backend/tools/AssemblyEmitter/CMakeLists.txt39
-rw-r--r--backend/tools/CMakeLists.txt1
-rw-r--r--backend/unittests/CMakeLists.txt1
-rw-r--r--backend/unittests/CodeGen/CompilationUnitTest.cpp44
-rw-r--r--backend/unittests/MC/CMakeLists.txt9
-rw-r--r--backend/unittests/MC/MCTest.cpp147
-rw-r--r--backend/unittests/Support/ErrorTest.cpp2
-rw-r--r--backend/unittests/Target/CMakeLists.txt13
-rw-r--r--backend/unittests/Target/Inputs/RISCVDataPipeline.mir18
-rw-r--r--backend/unittests/Target/Inputs/RISCVGenericPipeline.mir12
-rw-r--r--backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir10
-rw-r--r--backend/unittests/Target/RISCVAssemblyPrinterTest.cpp182
52 files changed, 5886 insertions, 3 deletions
diff --git a/backend/include/rcc/CodeGen/AsmPrinter.h b/backend/include/rcc/CodeGen/AsmPrinter.h
new file mode 100644
index 0000000..82bad7d
--- /dev/null
+++ b/backend/include/rcc/CodeGen/AsmPrinter.h
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_ASMPRINTER_H
+#define RCC_CODEGEN_ASMPRINTER_H
+
+#include "rcc/Support/Error.h"
+
+namespace rcc {
+
+class CompilationUnit;
+class MachineFunction;
+class MachineMCContext;
+class TargetDescription;
+class TargetMCInstLowering;
+
+namespace mc {
+class MCStreamer;
+} // namespace mc
+
+struct AsmPrinterOptions final {
+ bool EmitFunctionDirectives = true;
+ bool EmitCFI = true;
+ bool EmitBlockLabels = true;
+ bool EmitInstructionSymbols = true;
+ bool VerifyInput = true;
+};
+
+class AsmPrinter final {
+public:
+ AsmPrinter(const TargetDescription &Target, const TargetMCInstLowering &Lowering,
+ MachineMCContext &Context, mc::MCStreamer &Streamer,
+ AsmPrinterOptions Options = {}) noexcept
+ : Target(&Target), Lowering(&Lowering), Context(&Context), Streamer(&Streamer),
+ Options(Options) {}
+
+ [[nodiscard]] Expected<void> emitCompilationUnit(const CompilationUnit &Unit);
+ [[nodiscard]] Expected<void> emitFunction(const MachineFunction &Function);
+
+private:
+ [[nodiscard]] Expected<void> verifyFunction(const MachineFunction &Function) const;
+ [[nodiscard]] Expected<void> emitGlobalObjects(const CompilationUnit &Unit);
+ [[nodiscard]] Expected<void> emitFunctionBody(const MachineFunction &Function);
+ [[nodiscard]] Expected<void> emitConstantPool(const MachineFunction &Function);
+ [[nodiscard]] Expected<void> emitJumpTables(const MachineFunction &Function);
+
+ const TargetDescription *Target;
+ const TargetMCInstLowering *Lowering;
+ MachineMCContext *Context;
+ mc::MCStreamer *Streamer;
+ AsmPrinterOptions Options;
+ bool UnitEmissionStarted = false;
+ bool UnitEmissionFinished = false;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_ASMPRINTER_H
diff --git a/backend/include/rcc/CodeGen/CompilationUnit.h b/backend/include/rcc/CodeGen/CompilationUnit.h
index d798438..14fd86f 100644
--- a/backend/include/rcc/CodeGen/CompilationUnit.h
+++ b/backend/include/rcc/CodeGen/CompilationUnit.h
@@ -3,6 +3,7 @@
#ifndef RCC_CODEGEN_COMPILATIONUNIT_H
#define RCC_CODEGEN_COMPILATIONUNIT_H
+#include "rcc/CodeGen/GlobalObject.h"
#include "rcc/CodeGen/StableID.h"
#include "rcc/Support/Error.h"
@@ -28,6 +29,7 @@ public:
~CompilationUnit();
[[nodiscard]] Expected<MachineFunction *> createMachineFunction(std::string Name);
+ [[nodiscard]] Expected<GlobalObjectDesc *> addGlobalObject(GlobalObjectDesc Object);
[[nodiscard]] std::size_t functionCount() const noexcept {
return Functions.size();
@@ -40,6 +42,15 @@ public:
[[nodiscard]] MachineFunction *function(MachineFunctionStableID ID) noexcept;
[[nodiscard]] const MachineFunction *function(MachineFunctionStableID ID) const noexcept;
+ [[nodiscard]] std::size_t globalObjectCount() const noexcept {
+ return GlobalObjects.size();
+ }
+
+ [[nodiscard]] GlobalObjectDesc *globalObjectAt(std::size_t Index) noexcept;
+ [[nodiscard]] const GlobalObjectDesc *globalObjectAt(std::size_t Index) const noexcept;
+ [[nodiscard]] GlobalObjectDesc *globalObject(std::string_view Name) noexcept;
+ [[nodiscard]] const GlobalObjectDesc *globalObject(std::string_view Name) const noexcept;
+
[[nodiscard]] Expected<void> verify() const;
private:
@@ -53,6 +64,8 @@ private:
std::vector<std::unique_ptr<MachineFunction>> Functions;
std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> FunctionNameToIndex;
+ std::vector<std::unique_ptr<GlobalObjectDesc>> GlobalObjects;
+ std::unordered_map<std::string, std::size_t, StringHash, std::equal_to<>> GlobalNameToIndex;
StableIDGenerator<MachineFunctionStableID> FunctionIDGenerator;
};
diff --git a/backend/include/rcc/CodeGen/GlobalObject.h b/backend/include/rcc/CodeGen/GlobalObject.h
new file mode 100644
index 0000000..49f1f42
--- /dev/null
+++ b/backend/include/rcc/CodeGen/GlobalObject.h
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_GLOBALOBJECT_H
+#define RCC_CODEGEN_GLOBALOBJECT_H
+
+#include "rcc/CodeGen/GenericConstant.h"
+#include "rcc/CodeGen/MachineIRTypes.h"
+#include "rcc/CodeGen/TargetConstant.h"
+#include "rcc/Support/Error.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace rcc {
+
+class GlobalInitializer;
+using GlobalInitializerRef = std::shared_ptr<const GlobalInitializer>;
+
+struct GlobalZeroFill final {
+ std::uint64_t Size = 0;
+};
+
+struct GlobalRepeatedInitializer final {
+ std::uint64_t Count = 0;
+ GlobalInitializerRef Value;
+};
+
+struct GlobalAggregateInitializer final {
+ std::vector<GlobalInitializerRef> Elements;
+};
+
+struct GlobalSymbolAddress final {
+ std::string Symbol;
+ std::int64_t Addend = 0;
+ std::uint8_t Size = 8;
+ std::uint32_t TargetFlags = 0;
+};
+
+struct GlobalSymbolDifference final {
+ std::string Minuend;
+ std::string Subtrahend;
+ std::int64_t Addend = 0;
+ std::uint8_t Size = 8;
+};
+
+struct GlobalAlignmentPadding final {
+ std::uint64_t Size = 0;
+ std::uint8_t Fill = 0;
+};
+
+struct GlobalUndefinedStorage final {
+ std::uint64_t Size = 0;
+};
+
+class GlobalInitializer final {
+public:
+ enum class Kind : std::uint8_t {
+ Invalid,
+ Integer,
+ Floating,
+ Bytes,
+ ZeroFill,
+ Repeated,
+ Aggregate,
+ SymbolAddress,
+ SymbolDifference,
+ Target,
+ AlignmentPadding,
+ UndefinedStorage,
+ };
+
+ GlobalInitializer() = default;
+ GlobalInitializer(const GlobalInitializer &) = delete;
+ GlobalInitializer &operator=(const GlobalInitializer &) = delete;
+ GlobalInitializer(GlobalInitializer &&) noexcept = default;
+ GlobalInitializer &operator=(GlobalInitializer &&) noexcept = default;
+ ~GlobalInitializer() = default;
+
+ [[nodiscard]] static GlobalInitializer integer(IntegerImmediate Value);
+ [[nodiscard]] static GlobalInitializer floating(FloatingPointBits Value);
+ [[nodiscard]] static Expected<GlobalInitializer> bytes(std::vector<std::byte> Value);
+ [[nodiscard]] static Expected<GlobalInitializer> zeroFill(std::uint64_t Size);
+ [[nodiscard]] static Expected<GlobalInitializer> repeated(std::uint64_t Count,
+ GlobalInitializer Value);
+ [[nodiscard]] static Expected<GlobalInitializer>
+ aggregate(std::vector<GlobalInitializer> Elements);
+ [[nodiscard]] static Expected<GlobalInitializer> symbolAddress(GlobalSymbolAddress Value);
+ [[nodiscard]] static Expected<GlobalInitializer> symbolDifference(GlobalSymbolDifference Value);
+ [[nodiscard]] static Expected<GlobalInitializer> target(TargetConstant Value);
+ [[nodiscard]] static Expected<GlobalInitializer> alignmentPadding(std::uint64_t Size,
+ std::uint8_t Fill = 0);
+ [[nodiscard]] static Expected<GlobalInitializer> undefinedStorage(std::uint64_t Size);
+
+ [[nodiscard]] Kind kind() const noexcept;
+
+ template <typename ValueT> [[nodiscard]] const ValueT *getIf() const noexcept {
+ return std::get_if<ValueT>(&Storage);
+ }
+
+ [[nodiscard]] Expected<void> verify(std::size_t MaximumDepth = 1024) const;
+ [[nodiscard]] Expected<std::uint64_t> storageSize(std::size_t MaximumDepth = 1024) const;
+
+private:
+ using StorageT =
+ std::variant<std::monostate, IntegerImmediate, FloatingPointBits, std::vector<std::byte>,
+ GlobalZeroFill, GlobalRepeatedInitializer, GlobalAggregateInitializer,
+ GlobalSymbolAddress, GlobalSymbolDifference, TargetConstant,
+ GlobalAlignmentPadding, GlobalUndefinedStorage>;
+
+ explicit GlobalInitializer(StorageT Storage) : Storage(std::move(Storage)) {}
+
+ StorageT Storage;
+};
+
+enum class GlobalLinkage : std::uint8_t {
+ External,
+ AvailableExternally,
+ LinkOnceAny,
+ LinkOnceODR,
+ WeakAny,
+ WeakODR,
+ Appending,
+ Internal,
+ Private,
+ ExternalWeak,
+ Common,
+ Count,
+};
+
+enum class GlobalVisibility : std::uint8_t {
+ Default,
+ Hidden,
+ Protected,
+ Count,
+};
+
+enum class GlobalTLSModel : std::uint8_t {
+ None,
+ GeneralDynamic,
+ LocalDynamic,
+ InitialExec,
+ LocalExec,
+ Count,
+};
+
+enum class GlobalUnnamedAddress : std::uint8_t {
+ None,
+ Local,
+ Global,
+ Count,
+};
+
+struct GlobalObjectDesc final {
+ std::string Symbol;
+ GenericConstantType ObjectType;
+ GlobalLinkage Linkage = GlobalLinkage::External;
+ GlobalVisibility Visibility = GlobalVisibility::Default;
+ bool DSOLocal = false;
+ std::optional<std::string> Section;
+ Alignment Align;
+ std::uint64_t Size = 0;
+ std::uint32_t AddressSpace = 0;
+ GlobalTLSModel TLSModel = GlobalTLSModel::None;
+ bool ExternallyInitialized = false;
+ std::optional<std::string> COMDAT;
+ bool IsConstant = false;
+ GlobalUnnamedAddress UnnamedAddress = GlobalUnnamedAddress::None;
+ std::optional<GlobalInitializer> Initializer;
+ std::vector<std::string> DebugExpressions;
+ std::optional<OriginID> StableOrigin;
+ std::uint64_t TargetFlags = 0;
+ std::vector<std::pair<std::string, std::string>> TargetAttributes;
+
+ GlobalObjectDesc() = default;
+ GlobalObjectDesc(const GlobalObjectDesc &) = delete;
+ GlobalObjectDesc &operator=(const GlobalObjectDesc &) = delete;
+ GlobalObjectDesc(GlobalObjectDesc &&) noexcept = default;
+ GlobalObjectDesc &operator=(GlobalObjectDesc &&) noexcept = default;
+ ~GlobalObjectDesc() = default;
+
+ [[nodiscard]] bool isDeclaration() const noexcept {
+ return !Initializer.has_value() && Linkage != GlobalLinkage::Common;
+ }
+
+ [[nodiscard]] Expected<void> verify() const;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_GLOBALOBJECT_H
diff --git a/backend/include/rcc/CodeGen/MachineMCContext.h b/backend/include/rcc/CodeGen/MachineMCContext.h
new file mode 100644
index 0000000..2464dc5
--- /dev/null
+++ b/backend/include/rcc/CodeGen/MachineMCContext.h
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_MACHINEMCCONTEXT_H
+#define RCC_CODEGEN_MACHINEMCCONTEXT_H
+
+#include "rcc/CodeGen/MachineIRTypes.h"
+#include "rcc/MC/MCContext.h"
+#include "rcc/MC/MCSymbol.h"
+#include "rcc/Support/Error.h"
+
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+namespace rcc {
+
+class CompilationUnit;
+struct GlobalObjectDesc;
+class MachineBasicBlock;
+class MachineFunction;
+
+class MachineMCContext final {
+public:
+ explicit MachineMCContext(mc::MCContext &Context) noexcept : Context(&Context) {}
+
+ [[nodiscard]] Expected<void> initialize(const CompilationUnit &Unit);
+ [[nodiscard]] Expected<void> initializeFunction(const MachineFunction &Function);
+
+ [[nodiscard]] mc::MCSymbolRef functionSymbol(const MachineFunction &Function) const noexcept;
+ [[nodiscard]] mc::MCSymbolRef globalSymbol(const GlobalObjectDesc &Object) const noexcept;
+ [[nodiscard]] mc::MCSymbolRef blockSymbol(const MachineBasicBlock &Block) const noexcept;
+ [[nodiscard]] mc::MCSymbolRef constantPoolSymbol(const MachineFunction &Function,
+ ConstantPoolIndex Index) const noexcept;
+ [[nodiscard]] mc::MCSymbolRef jumpTableSymbol(const MachineFunction &Function,
+ JumpTableIndex Index) const noexcept;
+
+ [[nodiscard]] Expected<mc::MCSymbolRef> symbol(std::string Name,
+ mc::MCSymbolKind Kind = mc::MCSymbolKind::Generic);
+ [[nodiscard]] Expected<mc::MCSymbolRef> blockAddressSymbol(std::string_view FunctionName,
+ std::string_view BlockName) const;
+
+ [[nodiscard]] mc::MCContext &context() const noexcept {
+ return *Context;
+ }
+
+private:
+ struct FunctionIndexKey final {
+ const MachineFunction *Function = nullptr;
+ std::uint32_t Index = 0;
+
+ friend bool operator==(FunctionIndexKey Left, FunctionIndexKey Right) noexcept = default;
+ };
+
+ struct FunctionIndexHash final {
+ [[nodiscard]] std::size_t operator()(FunctionIndexKey Key) const noexcept;
+ };
+
+ mc::MCContext *Context;
+ std::unordered_map<const MachineFunction *, mc::MCSymbolRef> FunctionSymbols;
+ std::unordered_map<const GlobalObjectDesc *, mc::MCSymbolRef> GlobalSymbols;
+ std::unordered_map<const MachineBasicBlock *, mc::MCSymbolRef> BlockSymbols;
+ std::unordered_map<FunctionIndexKey, mc::MCSymbolRef, FunctionIndexHash> ConstantPoolSymbols;
+ std::unordered_map<FunctionIndexKey, mc::MCSymbolRef, FunctionIndexHash> JumpTableSymbols;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_MACHINEMCCONTEXT_H
diff --git a/backend/include/rcc/CodeGen/TargetMCInstLowering.h b/backend/include/rcc/CodeGen/TargetMCInstLowering.h
new file mode 100644
index 0000000..875d2dd
--- /dev/null
+++ b/backend/include/rcc/CodeGen/TargetMCInstLowering.h
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_CODEGEN_TARGETMCINSTLOWERING_H
+#define RCC_CODEGEN_TARGETMCINSTLOWERING_H
+
+#include "rcc/MC/MCInst.h"
+#include "rcc/MC/MCInstPrinter.h"
+#include "rcc/MC/MCSection.h"
+#include "rcc/MC/MCStreamer.h"
+#include "rcc/Support/Error.h"
+
+namespace rcc {
+
+class MachineConstantPoolEntry;
+class GlobalInitializer;
+struct GlobalObjectDesc;
+class MachineFunction;
+class MachineInstr;
+class MachineJumpTableEntry;
+class MachineMCContext;
+struct GenericConstantData;
+class TargetConstant;
+
+class TargetMCInstLowering {
+public:
+ virtual ~TargetMCInstLowering() = default;
+
+ [[nodiscard]] virtual const mc::MCInstPrinter &instructionPrinter() const noexcept = 0;
+
+ [[nodiscard]] virtual Expected<mc::MCInst> lowerInstruction(const MachineInstr &Instruction,
+ MachineMCContext &Context) const = 0;
+
+ [[nodiscard]] virtual Expected<void> emitTerminalInstruction(const MachineInstr &Instruction,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const;
+
+ [[nodiscard]] virtual Expected<mc::MCSectionDesc>
+ textSection(const MachineFunction &Function) const = 0;
+ [[nodiscard]] virtual Expected<mc::MCSectionDesc>
+ globalSection(const GlobalObjectDesc &Object, MachineMCContext &Context) const = 0;
+ [[nodiscard]] virtual Expected<mc::MCSectionDesc>
+ constantPoolSection(const MachineFunction &Function,
+ const MachineConstantPoolEntry &Entry) const = 0;
+ [[nodiscard]] virtual Expected<mc::MCSectionDesc>
+ jumpTableSection(const MachineFunction &Function, const MachineJumpTableEntry &Entry) const = 0;
+
+ [[nodiscard]] virtual Expected<void> emitFileHeader(MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const;
+ [[nodiscard]] virtual Expected<void> emitFileTrailer(MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const;
+ [[nodiscard]] virtual Expected<void> emitGenericConstant(const GenericConstantData &Constant,
+ std::uint64_t Size,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const;
+ [[nodiscard]] virtual Expected<void> emitGlobalInitializer(const GlobalInitializer &Initializer,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const;
+ [[nodiscard]] virtual Expected<void> emitTargetConstant(const TargetConstant &Constant,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const;
+ [[nodiscard]] virtual Expected<void> emitTargetJumpTable(const MachineFunction &Function,
+ const MachineJumpTableEntry &Entry,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_TARGETMCINSTLOWERING_H
diff --git a/backend/include/rcc/MC/MCCFI.h b/backend/include/rcc/MC/MCCFI.h
new file mode 100644
index 0000000..e6ac396
--- /dev/null
+++ b/backend/include/rcc/MC/MCCFI.h
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MC_MCCFI_H
+#define RCC_MC_MCCFI_H
+
+#include "rcc/MC/MCSymbol.h"
+#include "rcc/Support/Error.h"
+
+#include <cstdint>
+#include <vector>
+
+namespace rcc::mc {
+
+enum class MCCFIOperation : std::uint8_t {
+ StartProc,
+ EndProc,
+ Sections,
+ Personality,
+ LSDA,
+ SignalFrame,
+ ReturnColumn,
+ SameValue,
+ RememberState,
+ RestoreState,
+ Offset,
+ DefCFARegister,
+ DefCFAOffset,
+ DefCFA,
+ RelativeOffset,
+ AdjustCFAOffset,
+ Escape,
+ Restore,
+ Undefined,
+ Register,
+ WindowSave,
+ NegateReturnAddressState,
+ NegateReturnAddressStateWithPC,
+ GNUArgsSize,
+ Label,
+ ValueOffset,
+};
+
+struct MCCFIInstruction final {
+ MCCFIOperation Operation = MCCFIOperation::StartProc;
+ std::vector<std::int64_t> Operands;
+ std::vector<std::uint8_t> Bytes;
+ MCSymbolRef Symbol;
+
+ [[nodiscard]] Expected<void> verify() const;
+};
+
+} // namespace rcc::mc
+
+#endif // RCC_MC_MCCFI_H
diff --git a/backend/include/rcc/MC/MCContext.h b/backend/include/rcc/MC/MCContext.h
new file mode 100644
index 0000000..e9b2d90
--- /dev/null
+++ b/backend/include/rcc/MC/MCContext.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MC_MCCONTEXT_H
+#define RCC_MC_MCCONTEXT_H
+
+#include "rcc/MC/MCSection.h"
+#include "rcc/MC/MCSymbol.h"
+#include "rcc/Support/Error.h"
+
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+namespace rcc::mc {
+
+class MCContext final {
+public:
+ [[nodiscard]] Expected<MCSymbolRef> getOrCreateSymbol(std::string Name, MCSymbolKind Kind);
+ [[nodiscard]] Expected<MCSymbolRef> createTemporarySymbol(std::string_view Prefix);
+ [[nodiscard]] MCSymbolRef symbol(std::string_view Name) const noexcept;
+
+ [[nodiscard]] Expected<MCSectionRef> getOrCreateSection(MCSectionDesc Description);
+ [[nodiscard]] MCSectionRef section(std::string_view Name) const noexcept;
+
+private:
+ struct StringHash final {
+ using is_transparent = void; // NOLINT(readability-identifier-naming)
+
+ [[nodiscard]] std::size_t operator()(std::string_view Value) const noexcept {
+ return std::hash<std::string_view>{}(Value);
+ }
+ };
+
+ std::unordered_map<std::string, MCSymbolRef, StringHash, std::equal_to<>> Symbols;
+ std::unordered_map<std::string, MCSectionRef, StringHash, std::equal_to<>> Sections;
+ std::uint64_t NextTemporarySymbol = 0;
+};
+
+} // namespace rcc::mc
+
+#endif // RCC_MC_MCCONTEXT_H
diff --git a/backend/include/rcc/MC/MCExpr.h b/backend/include/rcc/MC/MCExpr.h
new file mode 100644
index 0000000..15ff6fd
--- /dev/null
+++ b/backend/include/rcc/MC/MCExpr.h
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MC_MCEXPR_H
+#define RCC_MC_MCEXPR_H
+
+#include "rcc/MC/MCSymbol.h"
+#include "rcc/Support/Error.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <variant>
+
+namespace rcc::mc {
+
+class MCExpr;
+using MCExprRef = std::shared_ptr<const MCExpr>;
+
+enum class MCUnaryOperator : std::uint8_t {
+ Plus,
+ Minus,
+ BitwiseNot,
+ LogicalNot,
+};
+
+enum class MCBinaryOperator : std::uint8_t {
+ Add,
+ Subtract,
+ Multiply,
+ Divide,
+ Modulo,
+ ShiftLeft,
+ ShiftRight,
+ BitwiseAnd,
+ BitwiseOr,
+ BitwiseXor,
+ LogicalAnd,
+ LogicalOr,
+ Equal,
+ NotEqual,
+ Less,
+ LessEqual,
+ Greater,
+ GreaterEqual,
+};
+
+struct MCSymbolRefExpr final {
+ MCSymbolRef Symbol;
+};
+
+struct MCUnaryExpr final {
+ MCUnaryOperator Operator = MCUnaryOperator::Plus;
+ MCExprRef Operand;
+};
+
+struct MCBinaryExpr final {
+ MCBinaryOperator Operator = MCBinaryOperator::Add;
+ MCExprRef Left;
+ MCExprRef Right;
+};
+
+struct MCTargetExpr final {
+ std::uint32_t Kind = 0;
+ MCExprRef Operand;
+};
+
+class MCExpr final {
+public:
+ enum class Kind : std::uint8_t {
+ Constant,
+ SymbolRef,
+ Unary,
+ Binary,
+ Target,
+ };
+
+ [[nodiscard]] static MCExprRef constant(std::int64_t Value);
+ [[nodiscard]] static Expected<MCExprRef> symbol(MCSymbolRef Symbol);
+ [[nodiscard]] static Expected<MCExprRef> unary(MCUnaryOperator Operator, MCExprRef Operand);
+ [[nodiscard]] static Expected<MCExprRef> binary(MCBinaryOperator Operator, MCExprRef Left,
+ MCExprRef Right);
+ [[nodiscard]] static Expected<MCExprRef> target(std::uint32_t Kind, MCExprRef Operand);
+
+ [[nodiscard]] Kind kind() const noexcept {
+ return ExpressionKind;
+ }
+
+ template <typename ValueT> [[nodiscard]] const ValueT *getIf() const noexcept {
+ return std::get_if<ValueT>(&Storage);
+ }
+
+ [[nodiscard]] Expected<void> verify(std::size_t MaximumDepth = 256) const;
+ [[nodiscard]] std::optional<std::int64_t> evaluateAbsolute() const noexcept;
+
+private:
+ using StorageT =
+ std::variant<std::int64_t, MCSymbolRefExpr, MCUnaryExpr, MCBinaryExpr, MCTargetExpr>;
+
+ MCExpr(Kind ExpressionKind, StorageT Storage)
+ : ExpressionKind(ExpressionKind), Storage(std::move(Storage)) {}
+
+ Kind ExpressionKind;
+ StorageT Storage;
+};
+
+} // namespace rcc::mc
+
+#endif // RCC_MC_MCEXPR_H
diff --git a/backend/include/rcc/MC/MCInst.h b/backend/include/rcc/MC/MCInst.h
new file mode 100644
index 0000000..c6da93b
--- /dev/null
+++ b/backend/include/rcc/MC/MCInst.h
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MC_MCINST_H
+#define RCC_MC_MCINST_H
+
+#include "rcc/MC/MCExpr.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace rcc::mc {
+
+class MCRegister final {
+public:
+ constexpr MCRegister() noexcept = default;
+
+ [[nodiscard]] static constexpr MCRegister fromValue(std::uint32_t Value) noexcept {
+ return MCRegister(Value);
+ }
+
+ [[nodiscard]] constexpr std::uint32_t value() const noexcept {
+ return Value;
+ }
+
+ [[nodiscard]] constexpr bool isValid() const noexcept {
+ return Value != 0;
+ }
+
+ friend constexpr bool operator==(MCRegister Left, MCRegister Right) noexcept = default;
+
+private:
+ explicit constexpr MCRegister(std::uint32_t Value) noexcept : Value(Value) {}
+
+ std::uint32_t Value = 0;
+};
+
+class MCOpcode final {
+public:
+ constexpr MCOpcode() noexcept = default;
+
+ [[nodiscard]] static constexpr MCOpcode fromValue(std::uint32_t Value) noexcept {
+ return MCOpcode(Value);
+ }
+
+ [[nodiscard]] constexpr std::uint32_t value() const noexcept {
+ return Value;
+ }
+
+ [[nodiscard]] constexpr bool isValid() const noexcept {
+ return Value != 0;
+ }
+
+ friend constexpr bool operator==(MCOpcode Left, MCOpcode Right) noexcept = default;
+
+private:
+ explicit constexpr MCOpcode(std::uint32_t Value) noexcept : Value(Value) {}
+
+ std::uint32_t Value = 0;
+};
+
+class MCOperand final {
+public:
+ enum class Kind : std::uint8_t {
+ Register,
+ Immediate,
+ Float32Bits,
+ Float64Bits,
+ Expression,
+ };
+
+ [[nodiscard]] static MCOperand registerOperand(MCRegister Register) noexcept {
+ return MCOperand(Kind::Register, Register);
+ }
+
+ [[nodiscard]] static MCOperand immediate(std::int64_t Value) noexcept {
+ return MCOperand(Kind::Immediate, Value);
+ }
+
+ [[nodiscard]] static MCOperand float32Bits(std::uint32_t Value) noexcept {
+ return MCOperand(Kind::Float32Bits, Value);
+ }
+
+ [[nodiscard]] static MCOperand float64Bits(std::uint64_t Value) noexcept {
+ return MCOperand(Kind::Float64Bits, Value);
+ }
+
+ [[nodiscard]] static MCOperand expression(MCExprRef Value) {
+ return MCOperand(Kind::Expression, std::move(Value));
+ }
+
+ [[nodiscard]] Kind kind() const noexcept {
+ return OperandKind;
+ }
+
+ [[nodiscard]] const MCRegister *registerValue() const noexcept {
+ return std::get_if<MCRegister>(&Storage);
+ }
+
+ [[nodiscard]] const std::int64_t *immediateValue() const noexcept {
+ return std::get_if<std::int64_t>(&Storage);
+ }
+
+ [[nodiscard]] const std::uint32_t *float32BitsValue() const noexcept {
+ return std::get_if<std::uint32_t>(&Storage);
+ }
+
+ [[nodiscard]] const std::uint64_t *float64BitsValue() const noexcept {
+ return std::get_if<std::uint64_t>(&Storage);
+ }
+
+ [[nodiscard]] const MCExprRef *expressionValue() const noexcept {
+ return std::get_if<MCExprRef>(&Storage);
+ }
+
+private:
+ using StorageT = std::variant<MCRegister, std::int64_t, std::uint32_t, std::uint64_t, MCExprRef>;
+
+ MCOperand(Kind OperandKind, StorageT Storage)
+ : OperandKind(OperandKind), Storage(std::move(Storage)) {}
+
+ Kind OperandKind;
+ StorageT Storage;
+};
+
+class MCInst final {
+public:
+ explicit MCInst(MCOpcode InstructionOpcode) : InstructionOpcode(InstructionOpcode) {}
+
+ [[nodiscard]] MCOpcode opcode() const noexcept {
+ return InstructionOpcode;
+ }
+
+ void setFlags(std::uint32_t NewFlags) noexcept {
+ Flags = NewFlags;
+ }
+
+ [[nodiscard]] std::uint32_t flags() const noexcept {
+ return Flags;
+ }
+
+ void setSourceIdentity(std::uint64_t Identity) noexcept {
+ SourceIdentity = Identity;
+ }
+
+ [[nodiscard]] std::uint64_t sourceIdentity() const noexcept {
+ return SourceIdentity;
+ }
+
+ void reserveOperands(std::size_t Count) {
+ Operands.reserve(Count);
+ }
+
+ void addOperand(MCOperand Operand) {
+ Operands.push_back(std::move(Operand));
+ }
+
+ [[nodiscard]] std::span<const MCOperand> operands() const noexcept {
+ return Operands;
+ }
+
+private:
+ MCOpcode InstructionOpcode;
+ std::uint32_t Flags = 0;
+ std::uint64_t SourceIdentity = 0;
+ std::vector<MCOperand> Operands;
+};
+
+} // namespace rcc::mc
+
+#endif // RCC_MC_MCINST_H
diff --git a/backend/include/rcc/MC/MCInstPrinter.h b/backend/include/rcc/MC/MCInstPrinter.h
new file mode 100644
index 0000000..6024ced
--- /dev/null
+++ b/backend/include/rcc/MC/MCInstPrinter.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MC_MCINSTPRINTER_H
+#define RCC_MC_MCINSTPRINTER_H
+
+#include "rcc/MC/MCInst.h"
+#include "rcc/Support/Error.h"
+
+#include <cstdint>
+#include <string>
+#include <string_view>
+
+namespace rcc::mc {
+
+class MCInstPrinter {
+public:
+ virtual ~MCInstPrinter() = default;
+
+ [[nodiscard]] virtual Expected<std::string> printInstruction(const MCInst &Instruction) const = 0;
+
+ [[nodiscard]] virtual Expected<std::string>
+ printTargetExpression(std::uint32_t Kind, std::string_view Operand) const = 0;
+};
+
+[[nodiscard]] Expected<std::string> printMCExpression(const MCExpr &Expression,
+ const MCInstPrinter &Printer);
+[[nodiscard]] Expected<std::string> printMCSymbolName(std::string_view Name);
+
+} // namespace rcc::mc
+
+#endif // RCC_MC_MCINSTPRINTER_H
diff --git a/backend/include/rcc/MC/MCSection.h b/backend/include/rcc/MC/MCSection.h
new file mode 100644
index 0000000..4f76427
--- /dev/null
+++ b/backend/include/rcc/MC/MCSection.h
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MC_MCSECTION_H
+#define RCC_MC_MCSECTION_H
+
+#include "rcc/MC/MCSymbol.h"
+#include "rcc/Support/Error.h"
+
+#include <cstdint>
+#include <initializer_list>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace rcc::mc {
+
+enum class MCSectionKind : std::uint8_t {
+ Text,
+ ReadOnly,
+ Data,
+ BSS,
+ TLSData,
+ TLSBSS,
+ EHFrame,
+ Custom,
+};
+
+enum class MCSectionFlag : std::uint8_t {
+ Alloc,
+ Executable,
+ Writable,
+ Merge,
+ Strings,
+ TLS,
+ Group,
+ Retain,
+ Count,
+};
+
+class MCSectionFlags final {
+public:
+ constexpr MCSectionFlags() noexcept = default;
+
+ constexpr MCSectionFlags(std::initializer_list<MCSectionFlag> Flags) noexcept {
+ for (const MCSectionFlag Flag : Flags)
+ set(Flag);
+ }
+
+ constexpr void set(MCSectionFlag Flag) noexcept {
+ const auto Index = static_cast<std::uint8_t>(Flag);
+ if (Index < static_cast<std::uint8_t>(MCSectionFlag::Count))
+ Mask |= std::uint16_t{1} << Index;
+ }
+
+ [[nodiscard]] constexpr bool has(MCSectionFlag Flag) const noexcept {
+ const auto Index = static_cast<std::uint8_t>(Flag);
+ return Index < static_cast<std::uint8_t>(MCSectionFlag::Count) &&
+ (Mask & (std::uint16_t{1} << Index)) != 0;
+ }
+
+ [[nodiscard]] constexpr std::uint16_t rawValue() const noexcept {
+ return Mask;
+ }
+
+ friend constexpr bool operator==(MCSectionFlags Left, MCSectionFlags Right) noexcept = default;
+
+private:
+ std::uint16_t Mask = 0;
+};
+
+struct MCSectionDesc final {
+ std::string Name;
+ MCSectionKind Kind = MCSectionKind::Custom;
+ MCSectionFlags Flags;
+ std::uint64_t Alignment = 1;
+ std::uint64_t EntrySize = 0;
+ MCSymbolRef Group;
+ std::optional<std::uint64_t> UniqueID;
+
+ [[nodiscard]] Expected<void> verify() const;
+
+ friend bool operator==(const MCSectionDesc &Left, const MCSectionDesc &Right) = default;
+};
+
+class MCSection final {
+public:
+ [[nodiscard]] std::string_view name() const noexcept {
+ return Description.Name;
+ }
+
+ [[nodiscard]] const MCSectionDesc &description() const noexcept {
+ return Description;
+ }
+
+private:
+ explicit MCSection(MCSectionDesc Description) : Description(std::move(Description)) {}
+
+ MCSectionDesc Description;
+
+ friend class MCContext;
+};
+
+using MCSectionRef = std::shared_ptr<const MCSection>;
+
+} // namespace rcc::mc
+
+#endif // RCC_MC_MCSECTION_H
diff --git a/backend/include/rcc/MC/MCStreamer.h b/backend/include/rcc/MC/MCStreamer.h
new file mode 100644
index 0000000..3add976
--- /dev/null
+++ b/backend/include/rcc/MC/MCStreamer.h
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MC_MCSTREAMER_H
+#define RCC_MC_MCSTREAMER_H
+
+#include "rcc/MC/MCCFI.h"
+#include "rcc/MC/MCInst.h"
+#include "rcc/MC/MCInstPrinter.h"
+#include "rcc/MC/MCSection.h"
+#include "rcc/MC/MCSymbol.h"
+#include "rcc/Support/Error.h"
+
+#include <cstdint>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+
+namespace rcc::mc {
+
+enum class MCSymbolAttribute : std::uint8_t {
+ Global,
+ Weak,
+ Local,
+ Hidden,
+ Protected,
+ Function,
+ Object,
+ TLSObject,
+};
+
+class MCStreamer {
+public:
+ virtual ~MCStreamer() = default;
+
+ [[nodiscard]] virtual Expected<void> switchSection(MCSectionRef Section) = 0;
+ [[nodiscard]] virtual Expected<void> emitLabel(MCSymbolRef Symbol) = 0;
+ [[nodiscard]] virtual Expected<void> emitInstruction(const MCInst &Instruction) = 0;
+ [[nodiscard]] virtual Expected<void> emitValue(MCExprRef Value, std::uint8_t Size) = 0;
+ [[nodiscard]] virtual Expected<void> emitBytes(std::span<const std::uint8_t> Bytes) = 0;
+ [[nodiscard]] virtual Expected<void> emitZeroFill(std::uint64_t Size) = 0;
+ [[nodiscard]] virtual Expected<void> emitAlignment(std::uint64_t Alignment, std::uint8_t Fill = 0,
+ std::uint64_t MaximumBytes = 0) = 0;
+ [[nodiscard]] virtual Expected<void> emitSymbolAttribute(MCSymbolRef Symbol,
+ MCSymbolAttribute Attribute) = 0;
+ [[nodiscard]] virtual Expected<void> emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) = 0;
+ [[nodiscard]] virtual Expected<void> emitDirective(std::string_view Name,
+ std::string_view Arguments = {}) = 0;
+ [[nodiscard]] virtual Expected<void> emitCFI(const MCCFIInstruction &Instruction) = 0;
+ [[nodiscard]] virtual Expected<void> emitInlineAssembly(std::string_view Text) = 0;
+ [[nodiscard]] virtual Expected<void> emitComment(std::string_view Text) = 0;
+ [[nodiscard]] virtual Expected<void> finish() = 0;
+};
+
+class AsmTextStreamer final : public MCStreamer {
+public:
+ explicit AsmTextStreamer(const MCInstPrinter &Printer) noexcept : Printer(&Printer) {}
+
+ [[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> 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<std::string> takeOutput();
+
+private:
+ [[nodiscard]] Expected<void> appendLine(std::string_view Prefix, std::string_view Text);
+ [[nodiscard]] Expected<void> requireActiveSection() const;
+
+ const MCInstPrinter *Printer;
+ MCSectionRef CurrentSection;
+ std::unordered_set<std::string> DefinedSymbols;
+ std::string Output;
+ bool IsFinished = false;
+ bool OutputWasTaken = false;
+};
+
+} // namespace rcc::mc
+
+#endif // RCC_MC_MCSTREAMER_H
diff --git a/backend/include/rcc/MC/MCSymbol.h b/backend/include/rcc/MC/MCSymbol.h
new file mode 100644
index 0000000..1b7b878
--- /dev/null
+++ b/backend/include/rcc/MC/MCSymbol.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_MC_MCSYMBOL_H
+#define RCC_MC_MCSYMBOL_H
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace rcc::mc {
+
+class MCContext;
+
+enum class MCSymbolKind : std::uint8_t {
+ Generic,
+ Function,
+ Object,
+ BasicBlock,
+ ConstantPool,
+ JumpTable,
+ LiteralIsland,
+ Section,
+ Temporary,
+};
+
+class MCSymbol final {
+public:
+ [[nodiscard]] std::string_view name() const noexcept {
+ return Name;
+ }
+
+ [[nodiscard]] MCSymbolKind kind() const noexcept {
+ return Kind;
+ }
+
+ [[nodiscard]] bool isTemporary() const noexcept {
+ return Kind == MCSymbolKind::Temporary || Kind == MCSymbolKind::BasicBlock ||
+ Kind == MCSymbolKind::ConstantPool || Kind == MCSymbolKind::JumpTable ||
+ Kind == MCSymbolKind::LiteralIsland;
+ }
+
+private:
+ MCSymbol(std::string Name, MCSymbolKind Kind) : Name(std::move(Name)), Kind(Kind) {}
+
+ std::string Name;
+ MCSymbolKind Kind;
+
+ friend class MCContext;
+};
+
+using MCSymbolRef = std::shared_ptr<const MCSymbol>;
+
+} // namespace rcc::mc
+
+#endif // RCC_MC_MCSYMBOL_H
diff --git a/backend/include/rcc/Support/Error.h b/backend/include/rcc/Support/Error.h
index d125383..3bdab97 100644
--- a/backend/include/rcc/Support/Error.h
+++ b/backend/include/rcc/Support/Error.h
@@ -25,6 +25,7 @@ enum class ErrorCode : std::uint8_t {
PseudoExpansionFailed,
FinalEncodabilityFailed,
CFGOptimizationFailed,
+ AssemblyLoweringFailed,
};
[[nodiscard]] std::string_view getErrorCodeName(ErrorCode Code) noexcept;
diff --git a/backend/include/rcc/Target/RISCV/RISCVAssemblyPrinter.h b/backend/include/rcc/Target/RISCV/RISCVAssemblyPrinter.h
new file mode 100644
index 0000000..5c8dca5
--- /dev/null
+++ b/backend/include/rcc/Target/RISCV/RISCVAssemblyPrinter.h
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TARGET_RISCV_RISCVASSEMBLYPRINTER_H
+#define RCC_TARGET_RISCV_RISCVASSEMBLYPRINTER_H
+
+#include "rcc/CodeGen/AsmPrinter.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/Support/Error.h"
+
+#include <string>
+
+namespace rcc {
+class CompilationUnit;
+class MachineFunction;
+} // namespace rcc
+
+namespace rcc::riscv {
+
+struct AssemblyEmissionOptions final {
+ bool EmitFunctionDirectives = true;
+ bool EmitCFI = true;
+ bool EmitBlockLabels = true;
+ bool VerifyInput = true;
+};
+
+[[nodiscard]] Expected<std::string> emitAssembly(const CompilationUnit &Unit,
+ const TargetDescription &Target,
+ AssemblyEmissionOptions Options = {});
+[[nodiscard]] Expected<std::string> emitAssembly(const MachineFunction &Function,
+ const TargetDescription &Target,
+ AssemblyEmissionOptions Options = {});
+
+} // namespace rcc::riscv
+
+#endif // RCC_TARGET_RISCV_RISCVASSEMBLYPRINTER_H
diff --git a/backend/include/rcc/Target/RISCV/RISCVInstPrinter.h b/backend/include/rcc/Target/RISCV/RISCVInstPrinter.h
new file mode 100644
index 0000000..564ca2f
--- /dev/null
+++ b/backend/include/rcc/Target/RISCV/RISCVInstPrinter.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TARGET_RISCV_RISCVINSTPRINTER_H
+#define RCC_TARGET_RISCV_RISCVINSTPRINTER_H
+
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/MC/MCInstPrinter.h"
+
+#include <cstdint>
+#include <string>
+#include <string_view>
+
+namespace rcc::riscv {
+
+enum class RISCVMCExprKind : std::uint32_t {
+ CallPLT = 1,
+ PCRelativeHigh = 2,
+ PCRelativeLow = 3,
+};
+
+class RISCVInstPrinter final : public mc::MCInstPrinter {
+public:
+ explicit RISCVInstPrinter(const TargetDescription &Target) noexcept : Target(&Target) {}
+
+ [[nodiscard]] Expected<std::string>
+ printInstruction(const mc::MCInst &Instruction) const override;
+ [[nodiscard]] Expected<std::string>
+ printTargetExpression(std::uint32_t Kind, std::string_view Operand) const override;
+
+private:
+ const TargetDescription *Target;
+};
+
+} // namespace rcc::riscv
+
+#endif // RCC_TARGET_RISCV_RISCVINSTPRINTER_H
diff --git a/backend/include/rcc/Target/RISCV/RISCVMCTargetLowering.h b/backend/include/rcc/Target/RISCV/RISCVMCTargetLowering.h
new file mode 100644
index 0000000..fd620ad
--- /dev/null
+++ b/backend/include/rcc/Target/RISCV/RISCVMCTargetLowering.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TARGET_RISCV_RISCVMCTARGETLOWERING_H
+#define RCC_TARGET_RISCV_RISCVMCTARGETLOWERING_H
+
+#include "rcc/CodeGen/TargetMCInstLowering.h"
+#include "rcc/Target/RISCV/RISCVInstPrinter.h"
+
+namespace rcc::riscv {
+
+class RISCVMCTargetLowering final : public TargetMCInstLowering {
+public:
+ explicit RISCVMCTargetLowering(const TargetDescription &Target) noexcept
+ : Target(&Target), Printer(Target) {}
+
+ [[nodiscard]] const mc::MCInstPrinter &instructionPrinter() const noexcept override {
+ return Printer;
+ }
+
+ [[nodiscard]] Expected<mc::MCInst> lowerInstruction(const MachineInstr &Instruction,
+ MachineMCContext &Context) const override;
+ [[nodiscard]] Expected<void> emitTerminalInstruction(const MachineInstr &Instruction,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const override;
+
+ [[nodiscard]] Expected<mc::MCSectionDesc>
+ textSection(const MachineFunction &Function) const override;
+ [[nodiscard]] Expected<mc::MCSectionDesc> globalSection(const GlobalObjectDesc &Object,
+ MachineMCContext &Context) const override;
+ [[nodiscard]] Expected<mc::MCSectionDesc>
+ constantPoolSection(const MachineFunction &Function,
+ const MachineConstantPoolEntry &Entry) const override;
+ [[nodiscard]] Expected<mc::MCSectionDesc>
+ jumpTableSection(const MachineFunction &Function,
+ const MachineJumpTableEntry &Entry) const override;
+
+ [[nodiscard]] Expected<void> emitFileHeader(MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const override;
+ [[nodiscard]] Expected<void> emitFileTrailer(MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const override;
+ [[nodiscard]] Expected<void> emitGenericConstant(const GenericConstantData &Constant,
+ std::uint64_t Size, MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const override;
+ [[nodiscard]] Expected<void> emitGlobalInitializer(const GlobalInitializer &Initializer,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const override;
+ [[nodiscard]] Expected<void> emitTargetConstant(const TargetConstant &Constant,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const override;
+ [[nodiscard]] Expected<void> emitTargetJumpTable(const MachineFunction &Function,
+ const MachineJumpTableEntry &Entry,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const override;
+
+private:
+ const TargetDescription *Target;
+ RISCVInstPrinter Printer;
+};
+
+} // namespace rcc::riscv
+
+#endif // RCC_TARGET_RISCV_RISCVMCTARGETLOWERING_H
diff --git a/backend/lib/AsmPrinter/AsmPrinter.cpp b/backend/lib/AsmPrinter/AsmPrinter.cpp
new file mode 100644
index 0000000..cebd6b8
--- /dev/null
+++ b/backend/lib/AsmPrinter/AsmPrinter.cpp
@@ -0,0 +1,679 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/AsmPrinter.h"
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/GenericConstant.h"
+#include "rcc/CodeGen/GenericOpcodes.h"
+#include "rcc/CodeGen/GlobalObject.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineCFIInfo.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMCContext.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetConstant.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetMCInstLowering.h"
+#include "rcc/MC/MCCFI.h"
+#include "rcc/MC/MCContext.h"
+#include "rcc/MC/MCExpr.h"
+#include "rcc/MC/MCSection.h"
+#include "rcc/MC/MCStreamer.h"
+#include "rcc/MC/MCSymbol.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <variant>
+
+namespace rcc {
+namespace {
+
+[[nodiscard]] Expected<mc::MCExprRef> symbolExpression(mc::MCSymbolRef Symbol) {
+ auto Expression = mc::MCExpr::symbol(std::move(Symbol));
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ return *Expression;
+}
+
+[[nodiscard]] Expected<mc::MCExprRef> addConstant(mc::MCExprRef Expression, std::int64_t Addend) {
+ if (Addend == 0)
+ return Expression;
+ auto Result = mc::MCExpr::binary(mc::MCBinaryOperator::Add, std::move(Expression),
+ mc::MCExpr::constant(Addend));
+ if (!Result)
+ return std::unexpected(Result.error());
+ return *Result;
+}
+
+[[nodiscard]] Expected<mc::MCExprRef>
+symbolDifference(mc::MCSymbolRef Minuend, mc::MCSymbolRef Subtrahend, std::int64_t Addend) {
+ auto Left = symbolExpression(std::move(Minuend));
+ if (!Left)
+ return std::unexpected(Left.error());
+ auto Right = symbolExpression(std::move(Subtrahend));
+ if (!Right)
+ return std::unexpected(Right.error());
+ auto Difference =
+ mc::MCExpr::binary(mc::MCBinaryOperator::Subtract, std::move(*Left), std::move(*Right));
+ if (!Difference)
+ return std::unexpected(Difference.error());
+ return addConstant(std::move(*Difference), Addend);
+}
+
+[[nodiscard]] Expected<mc::MCCFIInstruction>
+lowerCFIInstruction(const MachineCFIInstruction &Instruction, MachineMCContext &Context) {
+ mc::MCCFIInstruction Result;
+ const auto Register = [&Result](const MachineCFIRegisterRule &Rule) {
+ Result.Operands.push_back(static_cast<std::int64_t>(Rule.Register.value()));
+ };
+ const auto Offset = [&Result](const MachineCFIOffsetRule &Rule) {
+ Result.Operands.push_back(Rule.Offset);
+ };
+ const auto RegisterOffset = [&Result](const MachineCFIRegisterOffsetRule &Rule) {
+ Result.Operands.push_back(static_cast<std::int64_t>(Rule.Register.value()));
+ Result.Operands.push_back(Rule.Offset);
+ };
+
+ switch (Instruction.operation()) {
+ case MachineCFIOperation::SameValue:
+ Result.Operation = mc::MCCFIOperation::SameValue;
+ Register(*Instruction.getIf<MachineCFIRegisterRule>());
+ break;
+ case MachineCFIOperation::RememberState:
+ Result.Operation = mc::MCCFIOperation::RememberState;
+ break;
+ case MachineCFIOperation::RestoreState:
+ Result.Operation = mc::MCCFIOperation::RestoreState;
+ break;
+ case MachineCFIOperation::Offset:
+ Result.Operation = mc::MCCFIOperation::Offset;
+ RegisterOffset(*Instruction.getIf<MachineCFIRegisterOffsetRule>());
+ break;
+ case MachineCFIOperation::DefCFARegister:
+ Result.Operation = mc::MCCFIOperation::DefCFARegister;
+ Register(*Instruction.getIf<MachineCFIRegisterRule>());
+ break;
+ case MachineCFIOperation::DefCFAOffset:
+ Result.Operation = mc::MCCFIOperation::DefCFAOffset;
+ Offset(*Instruction.getIf<MachineCFIOffsetRule>());
+ break;
+ case MachineCFIOperation::DefCFA:
+ Result.Operation = mc::MCCFIOperation::DefCFA;
+ RegisterOffset(*Instruction.getIf<MachineCFIRegisterOffsetRule>());
+ break;
+ case MachineCFIOperation::RelativeOffset:
+ Result.Operation = mc::MCCFIOperation::RelativeOffset;
+ RegisterOffset(*Instruction.getIf<MachineCFIRegisterOffsetRule>());
+ break;
+ case MachineCFIOperation::AdjustCFAOffset:
+ Result.Operation = mc::MCCFIOperation::AdjustCFAOffset;
+ Offset(*Instruction.getIf<MachineCFIOffsetRule>());
+ break;
+ case MachineCFIOperation::Escape:
+ Result.Operation = mc::MCCFIOperation::Escape;
+ Result.Bytes = Instruction.getIf<MachineCFIEscapeRule>()->Bytes;
+ break;
+ case MachineCFIOperation::Restore:
+ Result.Operation = mc::MCCFIOperation::Restore;
+ Register(*Instruction.getIf<MachineCFIRegisterRule>());
+ break;
+ case MachineCFIOperation::Undefined:
+ Result.Operation = mc::MCCFIOperation::Undefined;
+ Register(*Instruction.getIf<MachineCFIRegisterRule>());
+ break;
+ case MachineCFIOperation::Register: {
+ Result.Operation = mc::MCCFIOperation::Register;
+ const auto &Rule = *Instruction.getIf<MachineCFIRegisterRegisterRule>();
+ Result.Operands.push_back(static_cast<std::int64_t>(Rule.Register.value()));
+ Result.Operands.push_back(static_cast<std::int64_t>(Rule.SourceRegister.value()));
+ break;
+ }
+ case MachineCFIOperation::WindowSave:
+ Result.Operation = mc::MCCFIOperation::WindowSave;
+ break;
+ case MachineCFIOperation::NegateReturnAddressState:
+ Result.Operation = mc::MCCFIOperation::NegateReturnAddressState;
+ break;
+ case MachineCFIOperation::NegateReturnAddressStateWithPC:
+ Result.Operation = mc::MCCFIOperation::NegateReturnAddressStateWithPC;
+ break;
+ case MachineCFIOperation::GNUArgsSize:
+ Result.Operation = mc::MCCFIOperation::GNUArgsSize;
+ Offset(*Instruction.getIf<MachineCFIOffsetRule>());
+ break;
+ case MachineCFIOperation::Label: {
+ Result.Operation = mc::MCCFIOperation::Label;
+ auto Symbol = Context.symbol(Instruction.getIf<MachineCFILabelRule>()->Symbol,
+ mc::MCSymbolKind::Temporary);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ Result.Symbol = std::move(*Symbol);
+ break;
+ }
+ case MachineCFIOperation::ValueOffset:
+ Result.Operation = mc::MCCFIOperation::ValueOffset;
+ RegisterOffset(*Instruction.getIf<MachineCFIRegisterOffsetRule>());
+ break;
+ case MachineCFIOperation::LLVMDefAddressSpaceCFA:
+ case MachineCFIOperation::LLVMRegisterPair:
+ case MachineCFIOperation::LLVMVectorRegisters:
+ case MachineCFIOperation::LLVMVectorOffset:
+ case MachineCFIOperation::LLVMVectorRegisterMask:
+ return makeUnexpected(ErrorCode::Unsupported,
+ "assembly emission does not support this LLVM-specific CFI operation");
+ case MachineCFIOperation::Count:
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "machine CFI instruction has an invalid operation");
+ }
+
+ auto Verification = Result.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Result;
+}
+
+[[nodiscard]] Expected<void> emitNamedLabel(const MachineOperand &Operand,
+ MachineMCContext &Context, mc::MCStreamer &Streamer) {
+ const auto *SymbolOperand = Operand.getIf<MCSymbolOperand>();
+ if (SymbolOperand == nullptr || SymbolOperand->Offset != 0 || Operand.targetFlags() != 0) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "label pseudo requires an unmodified MC symbol operand");
+ }
+ auto Symbol = Context.symbol(SymbolOperand->Name, mc::MCSymbolKind::Temporary);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return Streamer.emitLabel(std::move(*Symbol));
+}
+
+[[nodiscard]] bool validValueSize(std::uint64_t Size) noexcept {
+ return Size == 1 || Size == 2 || Size == 4 || Size == 8;
+}
+
+[[nodiscard]] Expected<void> emitGlobalAttributes(const GlobalObjectDesc &Object,
+ const mc::MCSymbolRef &Symbol,
+ mc::MCStreamer &Streamer) {
+ switch (Object.Linkage) {
+ case GlobalLinkage::External:
+ case GlobalLinkage::Appending:
+ case GlobalLinkage::Common: {
+ auto Emitted = Streamer.emitSymbolAttribute(Symbol, mc::MCSymbolAttribute::Global);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ break;
+ }
+ case GlobalLinkage::LinkOnceAny:
+ case GlobalLinkage::LinkOnceODR:
+ case GlobalLinkage::WeakAny:
+ case GlobalLinkage::WeakODR:
+ case GlobalLinkage::ExternalWeak: {
+ auto Emitted = Streamer.emitSymbolAttribute(Symbol, mc::MCSymbolAttribute::Weak);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ break;
+ }
+ case GlobalLinkage::Internal:
+ case GlobalLinkage::Private: {
+ auto Emitted = Streamer.emitSymbolAttribute(Symbol, mc::MCSymbolAttribute::Local);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ break;
+ }
+ case GlobalLinkage::AvailableExternally:
+ break;
+ case GlobalLinkage::Count:
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "global object has invalid linkage during assembly emission");
+ }
+
+ if (Object.Visibility == GlobalVisibility::Hidden) {
+ auto Emitted = Streamer.emitSymbolAttribute(Symbol, mc::MCSymbolAttribute::Hidden);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ } else if (Object.Visibility == GlobalVisibility::Protected) {
+ auto Emitted = Streamer.emitSymbolAttribute(Symbol, mc::MCSymbolAttribute::Protected);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ }
+ return Streamer.emitSymbolAttribute(Symbol, Object.TLSModel == GlobalTLSModel::None
+ ? mc::MCSymbolAttribute::Object
+ : mc::MCSymbolAttribute::TLSObject);
+}
+
+} // namespace
+
+Expected<void> AsmPrinter::verifyFunction(const MachineFunction &Function) const {
+ const MachineFunctionProperties Required = {
+ MachineFunctionProperty::NoPHIs,
+ MachineFunctionProperty::Legalized,
+ MachineFunctionProperty::RegBankSelected,
+ MachineFunctionProperty::Selected,
+ MachineFunctionProperty::TiedOpsRewritten,
+ MachineFunctionProperty::NoVRegs,
+ MachineFunctionProperty::FrameFinalized,
+ MachineFunctionProperty::NoFrameIndices,
+ MachineFunctionProperty::NoExecutablePseudos,
+ };
+ if (!Function.properties().containsAll(Required)) {
+ const MachineFunctionProperties Missing = Required.missingFrom(Function.properties());
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "assembly emission requires finalized MIR properties: " +
+ Missing.toString());
+ }
+ if (!Options.VerifyInput)
+ return {};
+ return MachineVerifier::verify(Function, *Target,
+ MachineVerifierOptions{.Mode = MachineVerifierMode::Generic,
+ .Stage = PipelineStage::Emission,
+ .VerifyProperties = true});
+}
+
+Expected<void> AsmPrinter::emitCompilationUnit(const CompilationUnit &Unit) {
+ if (UnitEmissionStarted) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "a compilation unit can only be emitted once by an AsmPrinter");
+ }
+ UnitEmissionStarted = true;
+
+ auto Initialized = Context->initialize(Unit);
+ if (!Initialized)
+ return std::unexpected(Initialized.error());
+ auto Header = Lowering->emitFileHeader(*Context, *Streamer);
+ if (!Header)
+ return std::unexpected(Header.error());
+
+ auto Globals = emitGlobalObjects(Unit);
+ if (!Globals)
+ return std::unexpected(Globals.error());
+
+ for (std::size_t Index = 0; Index < Unit.functionCount(); ++Index) {
+ const MachineFunction *Function = Unit.functionAt(Index);
+ if (Function == nullptr) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "compilation unit contains a null function during assembly emission");
+ }
+ auto Emitted = emitFunction(*Function);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ }
+
+ auto Trailer = Lowering->emitFileTrailer(*Context, *Streamer);
+ if (!Trailer)
+ return std::unexpected(Trailer.error());
+ auto Finished = Streamer->finish();
+ if (!Finished)
+ return std::unexpected(Finished.error());
+ UnitEmissionFinished = true;
+ return {};
+}
+
+Expected<void> AsmPrinter::emitGlobalObjects(const CompilationUnit &Unit) {
+ for (std::size_t Index = 0; Index < Unit.globalObjectCount(); ++Index) {
+ 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)
+ continue;
+
+ const mc::MCSymbolRef Symbol = Context->globalSymbol(*Object);
+ if (!Symbol)
+ return makeUnexpected(ErrorCode::InternalError, "global object has no MC symbol");
+ auto Attributes = emitGlobalAttributes(*Object, Symbol, *Streamer);
+ if (!Attributes)
+ return std::unexpected(Attributes.error());
+
+ 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()));
+ if (!Common)
+ return std::unexpected(Common.error());
+ continue;
+ }
+
+ auto Description = Lowering->globalSection(*Object, *Context);
+ if (!Description)
+ return std::unexpected(Description.error());
+ auto Section = Context->context().getOrCreateSection(std::move(*Description));
+ if (!Section)
+ return std::unexpected(Section.error());
+ auto Switched = Streamer->switchSection(std::move(*Section));
+ if (!Switched)
+ return std::unexpected(Switched.error());
+ auto Aligned = Streamer->emitAlignment(Object->Align.bytes());
+ if (!Aligned)
+ return std::unexpected(Aligned.error());
+ auto Label = Streamer->emitLabel(Symbol);
+ if (!Label)
+ return std::unexpected(Label.error());
+ if (!Object->Initializer) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "defined global object has no initializer");
+ }
+ auto Initializer = Lowering->emitGlobalInitializer(*Object->Initializer, *Context, *Streamer);
+ if (!Initializer)
+ return std::unexpected(Initializer.error());
+ auto Size = Streamer->emitSymbolSize(
+ Symbol, mc::MCExpr::constant(static_cast<std::int64_t>(Object->Size)));
+ if (!Size)
+ return std::unexpected(Size.error());
+ }
+ return {};
+}
+
+Expected<void> AsmPrinter::emitFunction(const MachineFunction &Function) {
+ if (UnitEmissionFinished) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "cannot emit a function after the assembly stream is finalized");
+ }
+ auto Verification = verifyFunction(Function);
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ auto Initialized = Context->initializeFunction(Function);
+ if (!Initialized)
+ return std::unexpected(Initialized.error());
+
+ auto SectionDescription = Lowering->textSection(Function);
+ if (!SectionDescription)
+ return std::unexpected(SectionDescription.error());
+ auto Section = Context->context().getOrCreateSection(std::move(*SectionDescription));
+ if (!Section)
+ return std::unexpected(Section.error());
+ auto Switched = Streamer->switchSection(std::move(*Section));
+ if (!Switched)
+ return std::unexpected(Switched.error());
+
+ const mc::MCSymbolRef FunctionSymbol = Context->functionSymbol(Function);
+ if (!FunctionSymbol) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "function has no MC symbol after context initialization");
+ }
+ if (Options.EmitFunctionDirectives) {
+ auto Global = Streamer->emitSymbolAttribute(FunctionSymbol, mc::MCSymbolAttribute::Global);
+ if (!Global)
+ return std::unexpected(Global.error());
+ auto Type = Streamer->emitSymbolAttribute(FunctionSymbol, mc::MCSymbolAttribute::Function);
+ if (!Type)
+ return std::unexpected(Type.error());
+ }
+ if (const auto Alignment = Function.attributes().alignment()) {
+ auto Aligned = Streamer->emitAlignment(Alignment->bytes());
+ if (!Aligned)
+ return std::unexpected(Aligned.error());
+ }
+ auto FunctionLabel = Streamer->emitLabel(FunctionSymbol);
+ if (!FunctionLabel)
+ return std::unexpected(FunctionLabel.error());
+ if (Options.EmitCFI) {
+ mc::MCCFIInstruction StartProc;
+ StartProc.Operation = mc::MCCFIOperation::StartProc;
+ auto CFIStart = Streamer->emitCFI(StartProc);
+ if (!CFIStart)
+ return std::unexpected(CFIStart.error());
+ }
+
+ auto Body = emitFunctionBody(Function);
+ if (!Body)
+ return std::unexpected(Body.error());
+ if (Options.EmitCFI) {
+ mc::MCCFIInstruction EndProc;
+ EndProc.Operation = mc::MCCFIOperation::EndProc;
+ auto CFIEnd = Streamer->emitCFI(EndProc);
+ if (!CFIEnd)
+ return std::unexpected(CFIEnd.error());
+ }
+
+ auto EndSymbol = Context->symbol(".Lfunc_end" + std::to_string(Function.stableID().value()),
+ mc::MCSymbolKind::Temporary);
+ if (!EndSymbol)
+ return std::unexpected(EndSymbol.error());
+ auto EndLabel = Streamer->emitLabel(*EndSymbol);
+ if (!EndLabel)
+ return std::unexpected(EndLabel.error());
+ if (Options.EmitFunctionDirectives) {
+ auto Size = symbolDifference(*EndSymbol, FunctionSymbol, 0);
+ if (!Size)
+ return std::unexpected(Size.error());
+ auto EmittedSize = Streamer->emitSymbolSize(FunctionSymbol, std::move(*Size));
+ if (!EmittedSize)
+ return std::unexpected(EmittedSize.error());
+ }
+
+ auto Constants = emitConstantPool(Function);
+ if (!Constants)
+ return std::unexpected(Constants.error());
+ return emitJumpTables(Function);
+}
+
+Expected<void> AsmPrinter::emitFunctionBody(const MachineFunction &Function) {
+ for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
+ Block = Block->next()) {
+ if (Block->attributes().alignment().bytes() > 1) {
+ auto Aligned = Streamer->emitAlignment(Block->attributes().alignment().bytes());
+ if (!Aligned)
+ return std::unexpected(Aligned.error());
+ }
+ if (Options.EmitBlockLabels) {
+ const mc::MCSymbolRef Symbol = Context->blockSymbol(*Block);
+ if (!Symbol)
+ return makeUnexpected(ErrorCode::InternalError, "machine block has no MC symbol");
+ auto Label = Streamer->emitLabel(Symbol);
+ if (!Label)
+ return std::unexpected(Label.error());
+ }
+
+ for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;
+ Instruction = Instruction->next()) {
+ const InstructionDescriptor *Descriptor =
+ Target->instructionDescriptor(Instruction->opcode());
+ if (Descriptor == nullptr) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "assembly emission encountered an unknown opcode");
+ }
+ if (Options.EmitInstructionSymbols) {
+ if (const auto SymbolName = Instruction->preInstrSymbol()) {
+ auto Symbol = Context->symbol(std::string(*SymbolName), mc::MCSymbolKind::Temporary);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ auto Label = Streamer->emitLabel(std::move(*Symbol));
+ if (!Label)
+ return std::unexpected(Label.error());
+ }
+ }
+
+ Expected<void> Emitted;
+ if (Instruction->opcode() == opcode(GenericOpcode::CFI_INSTRUCTION)) {
+ const auto *Index = Instruction->operandAt(0) == nullptr
+ ? nullptr
+ : Instruction->operandAt(0)->getIf<CFIIndexOperand>();
+ const MachineCFIInstruction *CFI =
+ Index == nullptr ? nullptr : Function.cfiInfo().instruction(Index->Index);
+ if (CFI == nullptr) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "CFI pseudo references an invalid CFI instruction");
+ }
+ if (Options.EmitCFI) {
+ auto Lowered = lowerCFIInstruction(*CFI, *Context);
+ if (!Lowered)
+ return std::unexpected(Lowered.error());
+ Emitted = Streamer->emitCFI(*Lowered);
+ }
+ } else if (Instruction->opcode() == opcode(GenericOpcode::EH_LABEL) ||
+ Instruction->opcode() == opcode(GenericOpcode::GC_LABEL) ||
+ Instruction->opcode() == opcode(GenericOpcode::ANNOTATION_LABEL)) {
+ if (Instruction->operandAt(0) == nullptr) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "label pseudo has no symbol operand");
+ }
+ Emitted = emitNamedLabel(*Instruction->operandAt(0), *Context, *Streamer);
+ } else if (Descriptor->Flags.has(InstructionFlag::TerminalEmission)) {
+ Emitted = Lowering->emitTerminalInstruction(*Instruction, *Context, *Streamer);
+ } else if (Descriptor->Flags.has(InstructionFlag::Meta)) {
+ Emitted = {};
+ } else if (Descriptor->Flags.has(InstructionFlag::ProducesCode)) {
+ auto Lowered = Lowering->lowerInstruction(*Instruction, *Context);
+ if (!Lowered)
+ return std::unexpected(Lowered.error());
+ Emitted = Streamer->emitInstruction(*Lowered);
+ } else {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "non-meta instruction has no assembly emission behavior");
+ }
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+
+ if (Options.EmitInstructionSymbols) {
+ if (const auto SymbolName = Instruction->postInstrSymbol()) {
+ auto Symbol = Context->symbol(std::string(*SymbolName), mc::MCSymbolKind::Temporary);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ auto Label = Streamer->emitLabel(std::move(*Symbol));
+ if (!Label)
+ return std::unexpected(Label.error());
+ }
+ }
+ }
+ }
+ return {};
+}
+
+Expected<void> AsmPrinter::emitConstantPool(const MachineFunction &Function) {
+ for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) {
+ const MachineConstantPoolEntryDesc *Description = Entry.description();
+ if (Description == nullptr)
+ continue;
+ auto SectionDescription = Lowering->constantPoolSection(Function, Entry);
+ if (!SectionDescription)
+ return std::unexpected(SectionDescription.error());
+ auto Section = Context->context().getOrCreateSection(std::move(*SectionDescription));
+ if (!Section)
+ return std::unexpected(Section.error());
+ auto Switched = Streamer->switchSection(std::move(*Section));
+ if (!Switched)
+ return std::unexpected(Switched.error());
+ auto Aligned = Streamer->emitAlignment(Description->Align.bytes());
+ if (!Aligned)
+ return std::unexpected(Aligned.error());
+
+ const mc::MCSymbolRef Symbol = Context->constantPoolSymbol(Function, Entry.index());
+ if (!Symbol)
+ return makeUnexpected(ErrorCode::InternalError, "constant-pool entry has no MC symbol");
+ auto Label = Streamer->emitLabel(Symbol);
+ if (!Label)
+ return std::unexpected(Label.error());
+
+ Expected<void> Emitted;
+ if (const auto *Generic = std::get_if<GenericConstantData>(&Description->Value)) {
+ Emitted = Lowering->emitGenericConstant(*Generic, Description->Size, *Context, *Streamer);
+ } else if (const auto *TargetValue = std::get_if<TargetConstant>(&Description->Value)) {
+ Emitted = Lowering->emitTargetConstant(*TargetValue, *Context, *Streamer);
+ } else if (const auto *Difference =
+ std::get_if<SymbolDifferenceConstant>(&Description->Value)) {
+ if (!validValueSize(Description->Size)) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "symbol-difference constant has an unsupported storage size");
+ }
+ auto Minuend = Context->symbol(Difference->Minuend);
+ if (!Minuend)
+ return std::unexpected(Minuend.error());
+ auto Subtrahend = Context->symbol(Difference->Subtrahend);
+ if (!Subtrahend)
+ return std::unexpected(Subtrahend.error());
+ auto Expression = symbolDifference(*Minuend, *Subtrahend, Difference->Addend);
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ Emitted =
+ Streamer->emitValue(std::move(*Expression), static_cast<std::uint8_t>(Description->Size));
+ } else if (const auto *Address = std::get_if<BlockAddressConstant>(&Description->Value)) {
+ if (!validValueSize(Description->Size)) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "block-address constant has an unsupported storage size");
+ }
+ auto AddressSymbol = Context->blockAddressSymbol(Address->FunctionName, Address->BlockName);
+ if (!AddressSymbol)
+ return std::unexpected(AddressSymbol.error());
+ auto Expression = symbolExpression(std::move(*AddressSymbol));
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ Expression = addConstant(std::move(*Expression), Address->Addend);
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ Emitted =
+ Streamer->emitValue(std::move(*Expression), static_cast<std::uint8_t>(Description->Size));
+ } else if (const auto *TargetExpression =
+ std::get_if<TargetExpressionConstant>(&Description->Value)) {
+ Emitted = Lowering->emitTargetConstant(TargetExpression->Expression, *Context, *Streamer);
+ } else {
+ return makeUnexpected(ErrorCode::InternalError, "constant-pool value has an invalid kind");
+ }
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ }
+ return {};
+}
+
+Expected<void> AsmPrinter::emitJumpTables(const MachineFunction &Function) {
+ for (const MachineJumpTableEntry &Entry : Function.jumpTableInfo().tables()) {
+ const MachineJumpTableDesc *Description = Entry.description();
+ if (Description == nullptr)
+ continue;
+ auto SectionDescription = Lowering->jumpTableSection(Function, Entry);
+ if (!SectionDescription)
+ return std::unexpected(SectionDescription.error());
+ auto Section = Context->context().getOrCreateSection(std::move(*SectionDescription));
+ if (!Section)
+ return std::unexpected(Section.error());
+ auto Switched = Streamer->switchSection(std::move(*Section));
+ if (!Switched)
+ return std::unexpected(Switched.error());
+ auto Aligned = Streamer->emitAlignment(Description->Align.bytes());
+ if (!Aligned)
+ return std::unexpected(Aligned.error());
+
+ const mc::MCSymbolRef TableSymbol = Context->jumpTableSymbol(Function, Entry.index());
+ if (!TableSymbol)
+ return makeUnexpected(ErrorCode::InternalError, "jump-table entry has no MC symbol");
+ auto Label = Streamer->emitLabel(TableSymbol);
+ if (!Label)
+ return std::unexpected(Label.error());
+
+ if (Description->Encoding != JumpTableEntryEncoding::BlockAddress &&
+ Description->Encoding != JumpTableEntryEncoding::LabelDifference32 &&
+ Description->Encoding != JumpTableEntryEncoding::LabelDifference64) {
+ auto TargetTable = Lowering->emitTargetJumpTable(Function, Entry, *Context, *Streamer);
+ if (!TargetTable)
+ return std::unexpected(TargetTable.error());
+ continue;
+ }
+
+ for (const MachineBasicBlock *TargetBlock : Description->Targets) {
+ if (TargetBlock == nullptr)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "jump table has a null target");
+ const mc::MCSymbolRef TargetSymbol = Context->blockSymbol(*TargetBlock);
+ if (!TargetSymbol)
+ return makeUnexpected(ErrorCode::InternalError, "jump-table target has no MC symbol");
+
+ Expected<mc::MCExprRef> Expression = symbolExpression(TargetSymbol);
+ if (Description->Encoding == JumpTableEntryEncoding::LabelDifference32 ||
+ Description->Encoding == JumpTableEntryEncoding::LabelDifference64)
+ Expression = symbolDifference(TargetSymbol, TableSymbol, 0);
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ auto Value = Streamer->emitValue(std::move(*Expression), Description->EntrySize);
+ if (!Value)
+ return std::unexpected(Value.error());
+ }
+ }
+ return {};
+}
+
+} // namespace rcc
diff --git a/backend/lib/AsmPrinter/CMakeLists.txt b/backend/lib/AsmPrinter/CMakeLists.txt
new file mode 100644
index 0000000..6979a71
--- /dev/null
+++ b/backend/lib/AsmPrinter/CMakeLists.txt
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+set(
+ RCCBackendAsmPrinterSources
+ AsmPrinter.cpp
+ MachineMCContext.cpp
+ TargetMCInstLowering.cpp
+)
+set(
+ RCCBackendAsmPrinterHeaders
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/AsmPrinter.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineMCContext.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetMCInstLowering.h"
+)
+
+add_library(RCCBackendAsmPrinter ${RCCBackendAsmPrinterSources})
+add_library(RCCBackend::AsmPrinter ALIAS RCCBackendAsmPrinter)
+
+rcc_backend_configure_target(RCCBackendAsmPrinter)
+
+target_include_directories(
+ RCCBackendAsmPrinter
+ PUBLIC
+ "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/include>"
+ "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
+)
+target_link_libraries(
+ RCCBackendAsmPrinter
+ PUBLIC
+ RCCBackend::CodeGen
+ RCCBackend::MC
+)
+
+set_target_properties(
+ RCCBackendAsmPrinter
+ PROPERTIES
+ EXPORT_NAME AsmPrinter
+ OUTPUT_NAME RCCBackendAsmPrinter
+)
+
+install(
+ TARGETS RCCBackendAsmPrinter
+ EXPORT RCCBackendTargets
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+)
+
+set(RCCBackendAsmPrinterSourcePaths)
+foreach(Source IN LISTS RCCBackendAsmPrinterSources)
+ list(APPEND RCCBackendAsmPrinterSourcePaths "${CMAKE_CURRENT_SOURCE_DIR}/${Source}")
+endforeach()
+
+rcc_backend_register_format_files(
+ ${RCCBackendAsmPrinterSourcePaths}
+ ${RCCBackendAsmPrinterHeaders}
+)
+rcc_backend_register_tidy_files(${RCCBackendAsmPrinterSourcePaths})
diff --git a/backend/lib/AsmPrinter/MachineMCContext.cpp b/backend/lib/AsmPrinter/MachineMCContext.cpp
new file mode 100644
index 0000000..163e228
--- /dev/null
+++ b/backend/lib/AsmPrinter/MachineMCContext.cpp
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachineMCContext.h"
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/GlobalObject.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace rcc {
+
+std::size_t MachineMCContext::FunctionIndexHash::operator()(FunctionIndexKey Key) const noexcept {
+ const std::size_t FunctionHash = std::hash<const MachineFunction *>{}(Key.Function);
+ const std::size_t IndexHash = std::hash<std::uint32_t>{}(Key.Index);
+ return FunctionHash ^ (IndexHash + 0x9e3779b9U + (FunctionHash << 6U) + (FunctionHash >> 2U));
+}
+
+Expected<void> MachineMCContext::initialize(const CompilationUnit &Unit) {
+ auto Verification = Unit.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ for (std::size_t Index = 0; Index < Unit.globalObjectCount(); ++Index) {
+ const GlobalObjectDesc *Object = Unit.globalObjectAt(Index);
+ if (Object == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "compilation unit contains a null global");
+ auto Symbol = Context->getOrCreateSymbol(Object->Symbol, mc::MCSymbolKind::Object);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ GlobalSymbols.emplace(Object, std::move(*Symbol));
+ }
+ for (std::size_t Index = 0; Index < Unit.functionCount(); ++Index) {
+ const MachineFunction *Function = Unit.functionAt(Index);
+ if (Function == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "compilation unit contains a null function");
+ auto Initialized = initializeFunction(*Function);
+ if (!Initialized)
+ return std::unexpected(Initialized.error());
+ }
+ return {};
+}
+
+Expected<void> MachineMCContext::initializeFunction(const MachineFunction &Function) {
+ if (FunctionSymbols.contains(&Function))
+ return {};
+
+ auto FunctionSymbol =
+ Context->getOrCreateSymbol(std::string(Function.name()), mc::MCSymbolKind::Function);
+ if (!FunctionSymbol)
+ return std::unexpected(FunctionSymbol.error());
+ FunctionSymbols.emplace(&Function, *FunctionSymbol);
+
+ for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
+ Block = Block->next()) {
+ const std::optional<std::size_t> DenseNumber = Block->denseNumber();
+ if (!Block->isLinked() || !DenseNumber) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "MC symbol initialization requires linked and numbered blocks");
+ }
+ const std::string Name =
+ ".LBB" + std::to_string(Function.stableID().value()) + "_" + std::to_string(*DenseNumber);
+ auto Symbol = Context->getOrCreateSymbol(Name, mc::MCSymbolKind::BasicBlock);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ BlockSymbols.emplace(Block, *Symbol);
+ }
+
+ for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) {
+ if (Entry.isTombstone())
+ continue;
+ const std::string Name = ".LCPI" + std::to_string(Function.stableID().value()) + "_" +
+ std::to_string(Entry.index().value());
+ auto Symbol = Context->getOrCreateSymbol(Name, mc::MCSymbolKind::ConstantPool);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ ConstantPoolSymbols.emplace(
+ FunctionIndexKey{.Function = &Function, .Index = Entry.index().value()}, *Symbol);
+ }
+
+ for (const MachineJumpTableEntry &Entry : Function.jumpTableInfo().tables()) {
+ if (Entry.isTombstone() || Entry.isPending())
+ continue;
+ const std::string Name = ".LJTI" + std::to_string(Function.stableID().value()) + "_" +
+ std::to_string(Entry.index().value());
+ auto Symbol = Context->getOrCreateSymbol(Name, mc::MCSymbolKind::JumpTable);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ JumpTableSymbols.emplace(
+ FunctionIndexKey{.Function = &Function, .Index = Entry.index().value()}, *Symbol);
+ }
+ return {};
+}
+
+mc::MCSymbolRef MachineMCContext::functionSymbol(const MachineFunction &Function) const noexcept {
+ const auto Position = FunctionSymbols.find(&Function);
+ return Position == FunctionSymbols.end() ? mc::MCSymbolRef{} : Position->second;
+}
+
+mc::MCSymbolRef MachineMCContext::globalSymbol(const GlobalObjectDesc &Object) const noexcept {
+ const auto Position = GlobalSymbols.find(&Object);
+ return Position == GlobalSymbols.end() ? mc::MCSymbolRef{} : Position->second;
+}
+
+mc::MCSymbolRef MachineMCContext::blockSymbol(const MachineBasicBlock &Block) const noexcept {
+ const auto Position = BlockSymbols.find(&Block);
+ return Position == BlockSymbols.end() ? mc::MCSymbolRef{} : Position->second;
+}
+
+mc::MCSymbolRef MachineMCContext::constantPoolSymbol(const MachineFunction &Function,
+ ConstantPoolIndex Index) const noexcept {
+ const auto Position =
+ ConstantPoolSymbols.find(FunctionIndexKey{.Function = &Function, .Index = Index.value()});
+ return Position == ConstantPoolSymbols.end() ? mc::MCSymbolRef{} : Position->second;
+}
+
+mc::MCSymbolRef MachineMCContext::jumpTableSymbol(const MachineFunction &Function,
+ JumpTableIndex Index) const noexcept {
+ const auto Position =
+ JumpTableSymbols.find(FunctionIndexKey{.Function = &Function, .Index = Index.value()});
+ return Position == JumpTableSymbols.end() ? mc::MCSymbolRef{} : Position->second;
+}
+
+Expected<mc::MCSymbolRef> MachineMCContext::symbol(std::string Name, mc::MCSymbolKind Kind) {
+ return Context->getOrCreateSymbol(std::move(Name), Kind);
+}
+
+Expected<mc::MCSymbolRef> MachineMCContext::blockAddressSymbol(std::string_view FunctionName,
+ std::string_view BlockName) const {
+ for (const auto &[Block, Symbol] : BlockSymbols) {
+ if (Block == nullptr || Block->function().name() != FunctionName)
+ continue;
+ const auto IRName = Block->attributes().irBlockName();
+ const auto MachineName = Block->attributes().name();
+ const bool NumericMatch =
+ Block->denseNumber() && std::to_string(*Block->denseNumber()) == BlockName;
+ if ((IRName && *IRName == BlockName) || (MachineName && *MachineName == BlockName) ||
+ NumericMatch)
+ return Symbol;
+ }
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "block-address operand does not resolve to an emitted block symbol");
+}
+
+} // namespace rcc
diff --git a/backend/lib/AsmPrinter/TargetMCInstLowering.cpp b/backend/lib/AsmPrinter/TargetMCInstLowering.cpp
new file mode 100644
index 0000000..c171034
--- /dev/null
+++ b/backend/lib/AsmPrinter/TargetMCInstLowering.cpp
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/TargetMCInstLowering.h"
+
+namespace rcc {
+
+Expected<void> TargetMCInstLowering::emitTerminalInstruction(const MachineInstr &,
+ MachineMCContext &,
+ mc::MCStreamer &) const {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "target does not support this terminal emission instruction");
+}
+
+Expected<void> TargetMCInstLowering::emitFileHeader(MachineMCContext &, mc::MCStreamer &) const {
+ return {};
+}
+
+Expected<void> TargetMCInstLowering::emitFileTrailer(MachineMCContext &, mc::MCStreamer &) const {
+ return {};
+}
+
+Expected<void> TargetMCInstLowering::emitGenericConstant(const GenericConstantData &, std::uint64_t,
+ MachineMCContext &,
+ mc::MCStreamer &) const {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "target does not support assembly emission for generic constants");
+}
+
+Expected<void> TargetMCInstLowering::emitGlobalInitializer(const GlobalInitializer &,
+ MachineMCContext &,
+ mc::MCStreamer &) const {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "target does not support assembly emission for global initializers");
+}
+
+Expected<void> TargetMCInstLowering::emitTargetConstant(const TargetConstant &, MachineMCContext &,
+ mc::MCStreamer &) const {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "target does not support assembly emission for this target constant");
+}
+
+Expected<void> TargetMCInstLowering::emitTargetJumpTable(const MachineFunction &,
+ const MachineJumpTableEntry &,
+ MachineMCContext &,
+ mc::MCStreamer &) const {
+ return makeUnexpected(ErrorCode::Unsupported, "target does not support this jump-table encoding");
+}
+
+} // namespace rcc
diff --git a/backend/lib/CMakeLists.txt b/backend/lib/CMakeLists.txt
index f6460e5..0160f1b 100644
--- a/backend/lib/CMakeLists.txt
+++ b/backend/lib/CMakeLists.txt
@@ -3,3 +3,5 @@
add_subdirectory(Support)
add_subdirectory(CodeGen)
add_subdirectory(MIR)
+add_subdirectory(MC)
+add_subdirectory(AsmPrinter)
diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt
index 341452a..15f1772 100644
--- a/backend/lib/CodeGen/CMakeLists.txt
+++ b/backend/lib/CodeGen/CMakeLists.txt
@@ -3,6 +3,7 @@
set(
RCCBackendCodeGenSources
CompilationUnit.cpp
+ GlobalObject.cpp
GenericOpcodes.cpp
GenericConstant.cpp
FrameIndexElimination.cpp
@@ -82,6 +83,7 @@ set(
set(
RCCBackendCodeGenHeaders
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/CompilationUnit.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/GlobalObject.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/GenericOpcodes.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/GenericConstant.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/FrameIndexElimination.h"
@@ -201,6 +203,7 @@ install(
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnit.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/GlobalObject.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GenericOpcodes.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GenericConstant.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp"
@@ -281,6 +284,7 @@ rcc_backend_register_format_files(
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CompilationUnit.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/GlobalObject.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GenericOpcodes.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/GenericConstant.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp"
diff --git a/backend/lib/CodeGen/CompilationUnit.cpp b/backend/lib/CodeGen/CompilationUnit.cpp
index 1de6487..5ec54bc 100644
--- a/backend/lib/CodeGen/CompilationUnit.cpp
+++ b/backend/lib/CodeGen/CompilationUnit.cpp
@@ -28,6 +28,10 @@ Expected<MachineFunction *> CompilationUnit::createMachineFunction(std::string N
return makeUnexpected(ErrorCode::InvalidArgument,
"machine function name is already present in the compilation unit");
}
+ if (GlobalNameToIndex.contains(Name)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine function name conflicts with a global object");
+ }
auto StableID = FunctionIDGenerator.allocate();
if (!StableID)
@@ -43,6 +47,27 @@ Expected<MachineFunction *> CompilationUnit::createMachineFunction(std::string N
return Result;
}
+Expected<GlobalObjectDesc *> CompilationUnit::addGlobalObject(GlobalObjectDesc Object) {
+ auto Verification = Object.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ if (FunctionNameToIndex.contains(Object.Symbol)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "global object symbol conflicts with a machine function");
+ }
+ if (GlobalNameToIndex.contains(Object.Symbol)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "global object symbol is already present in the compilation unit");
+ }
+
+ const std::size_t Index = GlobalObjects.size();
+ auto Storage = std::make_unique<GlobalObjectDesc>(std::move(Object));
+ GlobalObjectDesc *Result = Storage.get();
+ GlobalNameToIndex.emplace(Result->Symbol, Index);
+ GlobalObjects.push_back(std::move(Storage));
+ return Result;
+}
+
MachineFunction *CompilationUnit::functionAt(std::size_t Index) noexcept {
if (Index >= Functions.size())
return nullptr;
@@ -85,11 +110,33 @@ const MachineFunction *CompilationUnit::function(MachineFunctionStableID ID) con
return nullptr;
}
+GlobalObjectDesc *CompilationUnit::globalObjectAt(std::size_t Index) noexcept {
+ return Index < GlobalObjects.size() ? GlobalObjects[Index].get() : nullptr;
+}
+
+const GlobalObjectDesc *CompilationUnit::globalObjectAt(std::size_t Index) const noexcept {
+ return Index < GlobalObjects.size() ? GlobalObjects[Index].get() : nullptr;
+}
+
+GlobalObjectDesc *CompilationUnit::globalObject(std::string_view Name) noexcept {
+ const auto Position = GlobalNameToIndex.find(Name);
+ return Position == GlobalNameToIndex.end() ? nullptr : GlobalObjects[Position->second].get();
+}
+
+const GlobalObjectDesc *CompilationUnit::globalObject(std::string_view Name) const noexcept {
+ const auto Position = GlobalNameToIndex.find(Name);
+ return Position == GlobalNameToIndex.end() ? nullptr : GlobalObjects[Position->second].get();
+}
+
Expected<void> CompilationUnit::verify() const {
if (FunctionNameToIndex.size() != Functions.size()) {
return makeUnexpected(ErrorCode::VerificationError,
"compilation unit function name index has an inconsistent size");
}
+ if (GlobalNameToIndex.size() != GlobalObjects.size()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "compilation unit global object name index has an inconsistent size");
+ }
std::unordered_set<MachineFunctionStableID> StableIDs;
for (std::size_t Index = 0; Index < Functions.size(); ++Index) {
@@ -112,6 +159,30 @@ Expected<void> CompilationUnit::verify() const {
return makeUnexpected(ErrorCode::VerificationError,
"compilation unit function name index is inconsistent");
}
+ if (GlobalNameToIndex.contains(Function->name())) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "function name conflicts with a global object symbol");
+ }
+ }
+
+ for (std::size_t Index = 0; Index < GlobalObjects.size(); ++Index) {
+ const std::unique_ptr<GlobalObjectDesc> &Object = GlobalObjects[Index];
+ if (Object == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "compilation unit contains a null global object");
+ }
+ auto ObjectVerification = Object->verify();
+ if (!ObjectVerification)
+ return std::unexpected(ObjectVerification.error());
+ const auto Position = GlobalNameToIndex.find(Object->Symbol);
+ if (Position == GlobalNameToIndex.end() || Position->second != Index) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "compilation unit global object name index is inconsistent");
+ }
+ if (FunctionNameToIndex.contains(Object->Symbol)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "global object symbol conflicts with a function name");
+ }
}
return {};
}
diff --git a/backend/lib/CodeGen/GlobalObject.cpp b/backend/lib/CodeGen/GlobalObject.cpp
new file mode 100644
index 0000000..cda28bd
--- /dev/null
+++ b/backend/lib/CodeGen/GlobalObject.cpp
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/GlobalObject.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+namespace {
+
+[[nodiscard]] bool validText(const std::string &Text) noexcept {
+ return !Text.empty() && Text.find('\0') == std::string::npos &&
+ Text.find('\n') == std::string::npos && Text.find('\r') == std::string::npos;
+}
+
+[[nodiscard]] bool validValueSize(std::uint8_t Size) noexcept {
+ return Size == 1 || Size == 2 || Size == 4 || Size == 8;
+}
+
+[[nodiscard]] Expected<std::uint64_t> addSize(std::uint64_t Left, std::uint64_t Right) {
+ std::uint64_t Result = 0;
+ if (__builtin_add_overflow(Left, Right, &Result))
+ return makeUnexpected(ErrorCode::VerificationError, "global initializer size overflows");
+ return Result;
+}
+
+[[nodiscard]] Expected<std::uint64_t> multiplySize(std::uint64_t Left, std::uint64_t Right) {
+ std::uint64_t Result = 0;
+ if (__builtin_mul_overflow(Left, Right, &Result))
+ return makeUnexpected(ErrorCode::VerificationError, "global initializer size overflows");
+ return Result;
+}
+
+[[nodiscard]] Expected<std::uint64_t> initializerSize(const GlobalInitializer &Initializer,
+ std::size_t Depth, std::size_t MaximumDepth) {
+ if (Depth > MaximumDepth)
+ return makeUnexpected(ErrorCode::VerificationError, "global initializer is too deeply nested");
+ switch (Initializer.kind()) {
+ case GlobalInitializer::Kind::Invalid:
+ return makeUnexpected(ErrorCode::VerificationError, "global initializer is invalid");
+ case GlobalInitializer::Kind::Integer: {
+ const auto *Value = Initializer.getIf<IntegerImmediate>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "integer initializer storage is invalid");
+ auto Verified = Value->verify();
+ if (!Verified)
+ return std::unexpected(Verified.error());
+ return (static_cast<std::uint64_t>(Value->bitWidth()) + 7U) / 8U;
+ }
+ case GlobalInitializer::Kind::Floating: {
+ const auto *Value = Initializer.getIf<FloatingPointBits>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "floating initializer storage is invalid");
+ return (static_cast<std::uint64_t>(Value->bitWidth()) + 7U) / 8U;
+ }
+ case GlobalInitializer::Kind::Bytes: {
+ const auto *Value = Initializer.getIf<std::vector<std::byte>>();
+ if (Value == nullptr || Value->empty())
+ return makeUnexpected(ErrorCode::VerificationError, "byte initializer cannot be empty");
+ return static_cast<std::uint64_t>(Value->size());
+ }
+ case GlobalInitializer::Kind::ZeroFill: {
+ const auto *Value = Initializer.getIf<GlobalZeroFill>();
+ if (Value == nullptr || Value->Size == 0)
+ return makeUnexpected(ErrorCode::VerificationError, "zero-fill initializer has zero size");
+ return Value->Size;
+ }
+ case GlobalInitializer::Kind::Repeated: {
+ const auto *Value = Initializer.getIf<GlobalRepeatedInitializer>();
+ if (Value == nullptr || Value->Count == 0 || !Value->Value)
+ return makeUnexpected(ErrorCode::VerificationError, "repeated initializer is invalid");
+ auto ElementSize = initializerSize(*Value->Value, Depth + 1, MaximumDepth);
+ if (!ElementSize)
+ return std::unexpected(ElementSize.error());
+ return multiplySize(*ElementSize, Value->Count);
+ }
+ case GlobalInitializer::Kind::Aggregate: {
+ const auto *Value = Initializer.getIf<GlobalAggregateInitializer>();
+ if (Value == nullptr || Value->Elements.empty())
+ return makeUnexpected(ErrorCode::VerificationError, "aggregate initializer cannot be empty");
+ std::uint64_t Size = 0;
+ for (const GlobalInitializerRef &Element : Value->Elements) {
+ if (!Element)
+ return makeUnexpected(ErrorCode::VerificationError, "aggregate initializer is null");
+ auto ElementSize = initializerSize(*Element, Depth + 1, MaximumDepth);
+ if (!ElementSize)
+ return std::unexpected(ElementSize.error());
+ auto Updated = addSize(Size, *ElementSize);
+ if (!Updated)
+ return std::unexpected(Updated.error());
+ Size = *Updated;
+ }
+ return Size;
+ }
+ case GlobalInitializer::Kind::SymbolAddress: {
+ const auto *Value = Initializer.getIf<GlobalSymbolAddress>();
+ if (Value == nullptr || !validText(Value->Symbol) || !validValueSize(Value->Size)) {
+ return makeUnexpected(ErrorCode::VerificationError, "symbol-address initializer is invalid");
+ }
+ return Value->Size;
+ }
+ case GlobalInitializer::Kind::SymbolDifference: {
+ const auto *Value = Initializer.getIf<GlobalSymbolDifference>();
+ if (Value == nullptr || !validText(Value->Minuend) || !validText(Value->Subtrahend) ||
+ !validValueSize(Value->Size)) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "symbol-difference initializer is invalid");
+ }
+ return Value->Size;
+ }
+ case GlobalInitializer::Kind::Target: {
+ const auto *Value = Initializer.getIf<TargetConstant>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "target initializer storage is invalid");
+ auto Emission = Value->emit();
+ if (!Emission)
+ return std::unexpected(Emission.error());
+ return static_cast<std::uint64_t>(Emission->Bytes.size());
+ }
+ case GlobalInitializer::Kind::AlignmentPadding: {
+ const auto *Value = Initializer.getIf<GlobalAlignmentPadding>();
+ if (Value == nullptr || Value->Size == 0)
+ return makeUnexpected(ErrorCode::VerificationError, "alignment padding has zero size");
+ return Value->Size;
+ }
+ case GlobalInitializer::Kind::UndefinedStorage: {
+ const auto *Value = Initializer.getIf<GlobalUndefinedStorage>();
+ if (Value == nullptr || Value->Size == 0)
+ return makeUnexpected(ErrorCode::VerificationError, "undefined storage has zero size");
+ return Value->Size;
+ }
+ }
+ return makeUnexpected(ErrorCode::InternalError, "global initializer kind is invalid");
+}
+
+} // namespace
+
+GlobalInitializer GlobalInitializer::integer(IntegerImmediate Value) {
+ return GlobalInitializer(std::move(Value));
+}
+
+GlobalInitializer GlobalInitializer::floating(FloatingPointBits Value) {
+ return GlobalInitializer(Value);
+}
+
+Expected<GlobalInitializer> GlobalInitializer::bytes(std::vector<std::byte> Value) {
+ if (Value.empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "byte initializer cannot be empty");
+ return GlobalInitializer(std::move(Value));
+}
+
+Expected<GlobalInitializer> GlobalInitializer::zeroFill(std::uint64_t Size) {
+ if (Size == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "zero-fill initializer size must be nonzero");
+ return GlobalInitializer(GlobalZeroFill{.Size = Size});
+}
+
+Expected<GlobalInitializer> GlobalInitializer::repeated(std::uint64_t Count,
+ GlobalInitializer Value) {
+ if (Count == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "initializer repeat count must be nonzero");
+ auto Reference = std::make_shared<GlobalInitializer>(std::move(Value));
+ auto Verification = Reference->verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return GlobalInitializer(
+ GlobalRepeatedInitializer{.Count = Count, .Value = std::move(Reference)});
+}
+
+Expected<GlobalInitializer> GlobalInitializer::aggregate(std::vector<GlobalInitializer> Elements) {
+ if (Elements.empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "aggregate initializer cannot be empty");
+ GlobalAggregateInitializer Result;
+ Result.Elements.reserve(Elements.size());
+ for (GlobalInitializer &Element : Elements) {
+ auto Reference = std::make_shared<GlobalInitializer>(std::move(Element));
+ auto Verification = Reference->verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ Result.Elements.push_back(std::move(Reference));
+ }
+ return GlobalInitializer(std::move(Result));
+}
+
+Expected<GlobalInitializer> GlobalInitializer::symbolAddress(GlobalSymbolAddress Value) {
+ GlobalInitializer Result(std::move(Value));
+ auto Verification = Result.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Result;
+}
+
+Expected<GlobalInitializer> GlobalInitializer::symbolDifference(GlobalSymbolDifference Value) {
+ GlobalInitializer Result(std::move(Value));
+ auto Verification = Result.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Result;
+}
+
+Expected<GlobalInitializer> GlobalInitializer::target(TargetConstant Value) {
+ GlobalInitializer Result(std::move(Value));
+ auto Verification = Result.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Result;
+}
+
+Expected<GlobalInitializer> GlobalInitializer::alignmentPadding(std::uint64_t Size,
+ std::uint8_t Fill) {
+ if (Size == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "alignment padding size must be nonzero");
+ return GlobalInitializer(GlobalAlignmentPadding{.Size = Size, .Fill = Fill});
+}
+
+Expected<GlobalInitializer> GlobalInitializer::undefinedStorage(std::uint64_t Size) {
+ if (Size == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "undefined storage size must be nonzero");
+ return GlobalInitializer(GlobalUndefinedStorage{.Size = Size});
+}
+
+GlobalInitializer::Kind GlobalInitializer::kind() const noexcept {
+ return static_cast<Kind>(Storage.index());
+}
+
+Expected<void> GlobalInitializer::verify(std::size_t MaximumDepth) const {
+ if (MaximumDepth == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "initializer depth limit cannot be zero");
+ auto Size = initializerSize(*this, 1, MaximumDepth);
+ if (!Size)
+ return std::unexpected(Size.error());
+ return {};
+}
+
+Expected<std::uint64_t> GlobalInitializer::storageSize(std::size_t MaximumDepth) const {
+ if (MaximumDepth == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "initializer depth limit cannot be zero");
+ return initializerSize(*this, 1, MaximumDepth);
+}
+
+Expected<void> GlobalObjectDesc::verify() const {
+ if (!validText(Symbol))
+ return makeUnexpected(ErrorCode::VerificationError, "global object symbol is invalid");
+ auto TypeVerification = ObjectType.verify();
+ if (!TypeVerification)
+ return std::unexpected(TypeVerification.error());
+ if (Linkage >= GlobalLinkage::Count || Visibility >= GlobalVisibility::Count ||
+ TLSModel >= GlobalTLSModel::Count || UnnamedAddress >= GlobalUnnamedAddress::Count) {
+ return makeUnexpected(ErrorCode::VerificationError, "global object enum field is invalid");
+ }
+ if (Section && !validText(*Section))
+ return makeUnexpected(ErrorCode::VerificationError, "global object section is invalid");
+ if (COMDAT && !validText(*COMDAT))
+ return makeUnexpected(ErrorCode::VerificationError, "global object COMDAT is invalid");
+ if (Size == 0 && !isDeclaration())
+ return makeUnexpected(ErrorCode::VerificationError, "defined global object has zero size");
+ if (!isDeclaration() &&
+ Size > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "global object size exceeds the supported assembly range");
+ }
+ if (Linkage == GlobalLinkage::Common && Initializer)
+ return makeUnexpected(ErrorCode::VerificationError, "common global cannot have an initializer");
+ if (Initializer) {
+ auto InitializerSize = Initializer->storageSize();
+ if (!InitializerSize)
+ return std::unexpected(InitializerSize.error());
+ if (*InitializerSize != Size) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "global initializer size does not match its object size");
+ }
+ }
+ std::unordered_set<std::string> AttributeNames;
+ for (const auto &[Name, Value] : TargetAttributes) {
+ if (!validText(Name) || Value.find('\0') != std::string::npos ||
+ Value.find('\n') != std::string::npos || Value.find('\r') != std::string::npos ||
+ !AttributeNames.insert(Name).second) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "global target attribute is invalid or duplicated");
+ }
+ }
+ for (const std::string &Expression : DebugExpressions) {
+ if (!validText(Expression))
+ return makeUnexpected(ErrorCode::VerificationError, "global debug expression is invalid");
+ }
+ return {};
+}
+
+} // namespace rcc
diff --git a/backend/lib/CodeGen/MachineCompilationStatus.cpp b/backend/lib/CodeGen/MachineCompilationStatus.cpp
index e541d39..93acf3a 100644
--- a/backend/lib/CodeGen/MachineCompilationStatus.cpp
+++ b/backend/lib/CodeGen/MachineCompilationStatus.cpp
@@ -50,6 +50,7 @@ isKnownDiagnosticSeverity(MachineCompilationDiagnosticSeverity Severity) noexcep
case ErrorCode::PseudoExpansionFailed:
case ErrorCode::FinalEncodabilityFailed:
case ErrorCode::CFGOptimizationFailed:
+ case ErrorCode::AssemblyLoweringFailed:
case ErrorCode::IoError:
case ErrorCode::InternalError:
return true;
diff --git a/backend/lib/MC/CMakeLists.txt b/backend/lib/MC/CMakeLists.txt
new file mode 100644
index 0000000..a55b468
--- /dev/null
+++ b/backend/lib/MC/CMakeLists.txt
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+set(
+ RCCBackendMCSources
+ MCCFI.cpp
+ MCContext.cpp
+ MCExpr.cpp
+ MCInstPrinter.cpp
+ MCSection.cpp
+ MCStreamer.cpp
+)
+set(
+ RCCBackendMCHeaders
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCCFI.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/MCInst.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/MC/MCInstPrinter.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"
+)
+
+add_library(RCCBackendMC ${RCCBackendMCSources})
+add_library(RCCBackend::MC ALIAS RCCBackendMC)
+
+rcc_backend_configure_target(RCCBackendMC)
+
+target_include_directories(
+ RCCBackendMC
+ PUBLIC
+ "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/include>"
+ "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
+)
+target_link_libraries(RCCBackendMC PUBLIC RCCBackend::Support)
+
+set_target_properties(
+ RCCBackendMC
+ PROPERTIES
+ EXPORT_NAME MC
+ OUTPUT_NAME RCCBackendMC
+)
+
+install(
+ TARGETS RCCBackendMC
+ EXPORT RCCBackendTargets
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+)
+
+set(RCCBackendMCSourcePaths)
+foreach(Source IN LISTS RCCBackendMCSources)
+ list(APPEND RCCBackendMCSourcePaths "${CMAKE_CURRENT_SOURCE_DIR}/${Source}")
+endforeach()
+
+rcc_backend_register_format_files(${RCCBackendMCSourcePaths} ${RCCBackendMCHeaders})
+rcc_backend_register_tidy_files(${RCCBackendMCSourcePaths})
diff --git a/backend/lib/MC/MCCFI.cpp b/backend/lib/MC/MCCFI.cpp
new file mode 100644
index 0000000..aa66e0f
--- /dev/null
+++ b/backend/lib/MC/MCCFI.cpp
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MC/MCCFI.h"
+
+#include <cstddef>
+
+namespace rcc::mc {
+
+Expected<void> MCCFIInstruction::verify() const {
+ std::size_t ExpectedOperands = 0;
+ bool RequiresBytes = false;
+ bool RequiresSymbol = false;
+
+ switch (Operation) {
+ case MCCFIOperation::StartProc:
+ case MCCFIOperation::EndProc:
+ case MCCFIOperation::Sections:
+ case MCCFIOperation::SignalFrame:
+ case MCCFIOperation::RememberState:
+ case MCCFIOperation::RestoreState:
+ case MCCFIOperation::WindowSave:
+ case MCCFIOperation::NegateReturnAddressState:
+ case MCCFIOperation::NegateReturnAddressStateWithPC:
+ break;
+ case MCCFIOperation::Personality:
+ case MCCFIOperation::LSDA:
+ ExpectedOperands = 1;
+ RequiresSymbol = true;
+ break;
+ case MCCFIOperation::ReturnColumn:
+ case MCCFIOperation::SameValue:
+ case MCCFIOperation::DefCFARegister:
+ case MCCFIOperation::DefCFAOffset:
+ case MCCFIOperation::AdjustCFAOffset:
+ case MCCFIOperation::Restore:
+ case MCCFIOperation::Undefined:
+ case MCCFIOperation::GNUArgsSize:
+ ExpectedOperands = 1;
+ break;
+ case MCCFIOperation::Offset:
+ case MCCFIOperation::DefCFA:
+ case MCCFIOperation::RelativeOffset:
+ case MCCFIOperation::Register:
+ case MCCFIOperation::ValueOffset:
+ ExpectedOperands = 2;
+ break;
+ case MCCFIOperation::Escape:
+ RequiresBytes = true;
+ break;
+ case MCCFIOperation::Label:
+ RequiresSymbol = true;
+ break;
+ }
+
+ if (Operands.size() != ExpectedOperands)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC CFI event has an invalid operand count");
+ if (RequiresBytes != !Bytes.empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ RequiresBytes ? "MC CFI escape event requires bytes"
+ : "non-escape MC CFI event cannot contain bytes");
+ }
+ if (RequiresSymbol != static_cast<bool>(Symbol)) {
+ return makeUnexpected(ErrorCode::InvalidArgument, RequiresSymbol
+ ? "MC CFI event requires a symbol"
+ : "MC CFI event cannot contain a symbol");
+ }
+ return {};
+}
+
+} // namespace rcc::mc
diff --git a/backend/lib/MC/MCContext.cpp b/backend/lib/MC/MCContext.cpp
new file mode 100644
index 0000000..2aa4c0e
--- /dev/null
+++ b/backend/lib/MC/MCContext.cpp
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MC/MCContext.h"
+
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace rcc::mc {
+namespace {
+
+[[nodiscard]] bool isValidName(std::string_view Name) noexcept {
+ if (Name.empty())
+ return false;
+ for (const char RawCharacter : Name) {
+ const auto Character = static_cast<unsigned char>(RawCharacter);
+ if (Character == 0 || Character == '\n' || Character == '\r' || Character == 0x7fU)
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+Expected<MCSymbolRef> MCContext::getOrCreateSymbol(std::string Name, MCSymbolKind Kind) {
+ if (!isValidName(Name))
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC symbol has an invalid name");
+
+ const auto Position = Symbols.find(Name);
+ if (Position != Symbols.end()) {
+ const MCSymbolKind ExistingKind = Position->second->kind();
+ if (Kind != MCSymbolKind::Generic && ExistingKind != MCSymbolKind::Generic &&
+ ExistingKind != Kind) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "MC symbol name is already used by a different symbol kind");
+ }
+ return Position->second;
+ }
+
+ MCSymbolRef Result(new MCSymbol(std::move(Name), Kind));
+ Symbols.emplace(std::string(Result->name()), Result);
+ return Result;
+}
+
+Expected<MCSymbolRef> MCContext::createTemporarySymbol(std::string_view Prefix) {
+ if (!isValidName(Prefix))
+ return makeUnexpected(ErrorCode::InvalidArgument, "temporary MC symbol prefix is invalid");
+ if (NextTemporarySymbol == std::numeric_limits<std::uint64_t>::max()) {
+ return makeUnexpected(ErrorCode::InternalError, "temporary MC symbol sequence is exhausted");
+ }
+
+ while (true) {
+ std::string Name(Prefix);
+ Name += std::to_string(NextTemporarySymbol++);
+ if (Symbols.contains(Name))
+ continue;
+ return getOrCreateSymbol(std::move(Name), MCSymbolKind::Temporary);
+ }
+}
+
+MCSymbolRef MCContext::symbol(std::string_view Name) const noexcept {
+ const auto Position = Symbols.find(Name);
+ return Position == Symbols.end() ? MCSymbolRef{} : Position->second;
+}
+
+Expected<MCSectionRef> MCContext::getOrCreateSection(MCSectionDesc Description) {
+ auto Verification = Description.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+
+ const auto Position = Sections.find(Description.Name);
+ if (Position != Sections.end()) {
+ MCSectionDesc ExistingDescription = Position->second->description();
+ ExistingDescription.Alignment = Description.Alignment;
+ if (ExistingDescription != Description) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "MC section name is already used by a different description");
+ }
+ return Position->second;
+ }
+
+ MCSectionRef Result(new MCSection(std::move(Description)));
+ Sections.emplace(std::string(Result->name()), Result);
+ return Result;
+}
+
+MCSectionRef MCContext::section(std::string_view Name) const noexcept {
+ const auto Position = Sections.find(Name);
+ return Position == Sections.end() ? MCSectionRef{} : Position->second;
+}
+
+} // namespace rcc::mc
diff --git a/backend/lib/MC/MCExpr.cpp b/backend/lib/MC/MCExpr.cpp
new file mode 100644
index 0000000..1f0bdb6
--- /dev/null
+++ b/backend/lib/MC/MCExpr.cpp
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MC/MCExpr.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace rcc::mc {
+namespace {
+
+[[nodiscard]] std::optional<std::int64_t> checkedUnary(MCUnaryOperator Operator,
+ std::int64_t Value) noexcept {
+ switch (Operator) {
+ case MCUnaryOperator::Plus:
+ return Value;
+ case MCUnaryOperator::Minus:
+ if (Value == std::numeric_limits<std::int64_t>::min())
+ return std::nullopt;
+ return -Value;
+ case MCUnaryOperator::BitwiseNot:
+ return ~Value;
+ case MCUnaryOperator::LogicalNot:
+ return Value == 0 ? 1 : 0;
+ }
+ return std::nullopt;
+}
+
+[[nodiscard]] std::optional<std::int64_t>
+checkedBinary(MCBinaryOperator Operator, std::int64_t Left, std::int64_t Right) noexcept {
+ std::int64_t Result = 0;
+ switch (Operator) {
+ case MCBinaryOperator::Add:
+ return __builtin_add_overflow(Left, Right, &Result) ? std::nullopt : std::optional(Result);
+ case MCBinaryOperator::Subtract:
+ return __builtin_sub_overflow(Left, Right, &Result) ? std::nullopt : std::optional(Result);
+ case MCBinaryOperator::Multiply:
+ return __builtin_mul_overflow(Left, Right, &Result) ? std::nullopt : std::optional(Result);
+ case MCBinaryOperator::Divide:
+ if (Right == 0 || (Left == std::numeric_limits<std::int64_t>::min() && Right == -1))
+ return std::nullopt;
+ return Left / Right;
+ case MCBinaryOperator::Modulo:
+ if (Right == 0 || (Left == std::numeric_limits<std::int64_t>::min() && Right == -1))
+ return std::nullopt;
+ return Left % Right;
+ case MCBinaryOperator::ShiftLeft:
+ if (Right < 0 || Right >= 64)
+ return std::nullopt;
+ if (Right == 63) {
+ if (Left == 0)
+ return 0;
+ return Left == -1 ? std::optional(std::numeric_limits<std::int64_t>::min()) : std::nullopt;
+ }
+ if (__builtin_mul_overflow(Left, std::int64_t{1} << Right, &Result))
+ return std::nullopt;
+ return Result;
+ case MCBinaryOperator::ShiftRight:
+ if (Right < 0 || Right >= 64)
+ return std::nullopt;
+ return Left >> Right;
+ case MCBinaryOperator::BitwiseAnd:
+ return Left & Right;
+ case MCBinaryOperator::BitwiseOr:
+ return Left | Right;
+ case MCBinaryOperator::BitwiseXor:
+ return Left ^ Right;
+ case MCBinaryOperator::LogicalAnd:
+ return Left != 0 && Right != 0 ? 1 : 0;
+ case MCBinaryOperator::LogicalOr:
+ return Left != 0 || Right != 0 ? 1 : 0;
+ case MCBinaryOperator::Equal:
+ return Left == Right ? 1 : 0;
+ case MCBinaryOperator::NotEqual:
+ return Left != Right ? 1 : 0;
+ case MCBinaryOperator::Less:
+ return Left < Right ? 1 : 0;
+ case MCBinaryOperator::LessEqual:
+ return Left <= Right ? 1 : 0;
+ case MCBinaryOperator::Greater:
+ return Left > Right ? 1 : 0;
+ case MCBinaryOperator::GreaterEqual:
+ return Left >= Right ? 1 : 0;
+ }
+ return std::nullopt;
+}
+
+[[nodiscard]] Expected<void> verifyExpression(const MCExpr &Expression, std::size_t Depth,
+ std::size_t MaximumDepth) {
+ if (Depth > MaximumDepth)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC expression exceeds its depth limit");
+
+ switch (Expression.kind()) {
+ case MCExpr::Kind::Constant:
+ if (Expression.getIf<std::int64_t>() == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "MC constant expression storage is invalid");
+ return {};
+ case MCExpr::Kind::SymbolRef: {
+ const auto *Reference = Expression.getIf<MCSymbolRefExpr>();
+ if (Reference == nullptr || !Reference->Symbol || Reference->Symbol->name().empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "MC symbol expression requires a valid symbol");
+ }
+ return {};
+ }
+ case MCExpr::Kind::Unary: {
+ const auto *Unary = Expression.getIf<MCUnaryExpr>();
+ if (Unary == nullptr || !Unary->Operand)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC unary expression has no operand");
+ return verifyExpression(*Unary->Operand, Depth + 1, MaximumDepth);
+ }
+ case MCExpr::Kind::Binary: {
+ const auto *Binary = Expression.getIf<MCBinaryExpr>();
+ if (Binary == nullptr || !Binary->Left || !Binary->Right)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC binary expression has a null operand");
+ auto Left = verifyExpression(*Binary->Left, Depth + 1, MaximumDepth);
+ if (!Left)
+ return std::unexpected(Left.error());
+ return verifyExpression(*Binary->Right, Depth + 1, MaximumDepth);
+ }
+ case MCExpr::Kind::Target: {
+ const auto *Target = Expression.getIf<MCTargetExpr>();
+ if (Target == nullptr || Target->Kind == 0 || !Target->Operand) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "target MC expression requires a nonzero kind and an operand");
+ }
+ return verifyExpression(*Target->Operand, Depth + 1, MaximumDepth);
+ }
+ }
+ return makeUnexpected(ErrorCode::InternalError, "MC expression kind is invalid");
+}
+
+} // namespace
+
+MCExprRef MCExpr::constant(std::int64_t Value) {
+ return std::shared_ptr<const MCExpr>(new MCExpr(Kind::Constant, Value));
+}
+
+Expected<MCExprRef> MCExpr::symbol(MCSymbolRef Symbol) {
+ if (!Symbol || Symbol->name().empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC symbol expression requires a symbol");
+ return std::shared_ptr<const MCExpr>(
+ new MCExpr(Kind::SymbolRef, MCSymbolRefExpr{.Symbol = std::move(Symbol)}));
+}
+
+Expected<MCExprRef> MCExpr::unary(MCUnaryOperator Operator, MCExprRef Operand) {
+ if (!Operand)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC unary expression requires an operand");
+ return std::shared_ptr<const MCExpr>(
+ new MCExpr(Kind::Unary, MCUnaryExpr{.Operator = Operator, .Operand = std::move(Operand)}));
+}
+
+Expected<MCExprRef> MCExpr::binary(MCBinaryOperator Operator, MCExprRef Left, MCExprRef Right) {
+ if (!Left || !Right)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC binary expression requires two operands");
+ return std::shared_ptr<const MCExpr>(new MCExpr(
+ Kind::Binary,
+ MCBinaryExpr{.Operator = Operator, .Left = std::move(Left), .Right = std::move(Right)}));
+}
+
+Expected<MCExprRef> MCExpr::target(std::uint32_t Kind, MCExprRef Operand) {
+ if (Kind == 0 || !Operand) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "target MC expression requires a nonzero kind and an operand");
+ }
+ return std::shared_ptr<const MCExpr>(
+ new MCExpr(MCExpr::Kind::Target, MCTargetExpr{.Kind = Kind, .Operand = std::move(Operand)}));
+}
+
+Expected<void> MCExpr::verify(std::size_t MaximumDepth) const {
+ if (MaximumDepth == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC expression depth limit cannot be zero");
+ return verifyExpression(*this, 1, MaximumDepth);
+}
+
+std::optional<std::int64_t> MCExpr::evaluateAbsolute() const noexcept {
+ switch (ExpressionKind) {
+ case Kind::Constant:
+ return *getIf<std::int64_t>();
+ case Kind::SymbolRef:
+ case Kind::Target:
+ return std::nullopt;
+ case Kind::Unary: {
+ const auto *Unary = getIf<MCUnaryExpr>();
+ if (Unary == nullptr || !Unary->Operand)
+ return std::nullopt;
+ const auto Value = Unary->Operand->evaluateAbsolute();
+ return Value ? checkedUnary(Unary->Operator, *Value) : std::nullopt;
+ }
+ case Kind::Binary: {
+ const auto *Binary = getIf<MCBinaryExpr>();
+ if (Binary == nullptr || !Binary->Left || !Binary->Right)
+ return std::nullopt;
+ const auto Left = Binary->Left->evaluateAbsolute();
+ const auto Right = Binary->Right->evaluateAbsolute();
+ return Left && Right ? checkedBinary(Binary->Operator, *Left, *Right) : std::nullopt;
+ }
+ }
+ return std::nullopt;
+}
+
+} // namespace rcc::mc
diff --git a/backend/lib/MC/MCInstPrinter.cpp b/backend/lib/MC/MCInstPrinter.cpp
new file mode 100644
index 0000000..1af6e4e
--- /dev/null
+++ b/backend/lib/MC/MCInstPrinter.cpp
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MC/MCInstPrinter.h"
+
+#include "rcc/MC/MCExpr.h"
+
+#include <cctype>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <string_view>
+
+namespace rcc::mc {
+namespace {
+
+[[nodiscard]] bool isBareSymbolName(std::string_view Name) noexcept {
+ if (Name.empty())
+ return false;
+ const auto Initial = [](unsigned char Character) {
+ return std::isalpha(Character) != 0 || Character == '_' || Character == '.' || Character == '$';
+ };
+ const auto Following = [](unsigned char Character) {
+ return std::isalnum(Character) != 0 || Character == '_' || Character == '.' || Character == '$';
+ };
+ if (!Initial(static_cast<unsigned char>(Name.front())))
+ return false;
+ for (const char Character : Name.substr(1)) {
+ if (!Following(static_cast<unsigned char>(Character)))
+ return false;
+ }
+ return true;
+}
+
+[[nodiscard]] std::string_view unarySpelling(MCUnaryOperator Operator) noexcept {
+ switch (Operator) {
+ case MCUnaryOperator::Plus:
+ return "+";
+ case MCUnaryOperator::Minus:
+ return "-";
+ case MCUnaryOperator::BitwiseNot:
+ return "~";
+ case MCUnaryOperator::LogicalNot:
+ return "!";
+ }
+ return {};
+}
+
+[[nodiscard]] std::string_view binarySpelling(MCBinaryOperator Operator) noexcept {
+ switch (Operator) {
+ case MCBinaryOperator::Add:
+ return "+";
+ case MCBinaryOperator::Subtract:
+ return "-";
+ case MCBinaryOperator::Multiply:
+ return "*";
+ case MCBinaryOperator::Divide:
+ return "/";
+ case MCBinaryOperator::Modulo:
+ return "%";
+ case MCBinaryOperator::ShiftLeft:
+ return "<<";
+ case MCBinaryOperator::ShiftRight:
+ return ">>";
+ case MCBinaryOperator::BitwiseAnd:
+ return "&";
+ case MCBinaryOperator::BitwiseOr:
+ return "|";
+ case MCBinaryOperator::BitwiseXor:
+ return "^";
+ case MCBinaryOperator::LogicalAnd:
+ return "&&";
+ case MCBinaryOperator::LogicalOr:
+ return "||";
+ case MCBinaryOperator::Equal:
+ return "==";
+ case MCBinaryOperator::NotEqual:
+ return "!=";
+ case MCBinaryOperator::Less:
+ return "<";
+ case MCBinaryOperator::LessEqual:
+ return "<=";
+ case MCBinaryOperator::Greater:
+ return ">";
+ case MCBinaryOperator::GreaterEqual:
+ return ">=";
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<std::string>
+printExpressionImpl(const MCExpr &Expression, const MCInstPrinter &Printer, std::size_t Depth) {
+ if (Depth > 256)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "MC expression exceeds the assembly printing depth limit");
+
+ switch (Expression.kind()) {
+ case MCExpr::Kind::Constant:
+ return std::to_string(*Expression.getIf<std::int64_t>());
+ case MCExpr::Kind::SymbolRef: {
+ const auto *Reference = Expression.getIf<MCSymbolRefExpr>();
+ if (Reference == nullptr || !Reference->Symbol)
+ return makeUnexpected(ErrorCode::InternalError, "MC symbol expression storage is invalid");
+ return printMCSymbolName(Reference->Symbol->name());
+ }
+ case MCExpr::Kind::Unary: {
+ const auto *Unary = Expression.getIf<MCUnaryExpr>();
+ if (Unary == nullptr || !Unary->Operand)
+ return makeUnexpected(ErrorCode::InternalError, "MC unary expression storage is invalid");
+ auto Operand = printExpressionImpl(*Unary->Operand, Printer, Depth + 1);
+ if (!Operand)
+ return std::unexpected(Operand.error());
+ return std::string(unarySpelling(Unary->Operator)) + "(" + *Operand + ")";
+ }
+ case MCExpr::Kind::Binary: {
+ const auto *Binary = Expression.getIf<MCBinaryExpr>();
+ if (Binary == nullptr || !Binary->Left || !Binary->Right)
+ return makeUnexpected(ErrorCode::InternalError, "MC binary expression storage is invalid");
+ auto Left = printExpressionImpl(*Binary->Left, Printer, Depth + 1);
+ if (!Left)
+ return std::unexpected(Left.error());
+ auto Right = printExpressionImpl(*Binary->Right, Printer, Depth + 1);
+ if (!Right)
+ return std::unexpected(Right.error());
+ return "(" + *Left + std::string(binarySpelling(Binary->Operator)) + *Right + ")";
+ }
+ case MCExpr::Kind::Target: {
+ const auto *Target = Expression.getIf<MCTargetExpr>();
+ if (Target == nullptr || !Target->Operand)
+ return makeUnexpected(ErrorCode::InternalError, "target MC expression storage is invalid");
+ auto Operand = printExpressionImpl(*Target->Operand, Printer, Depth + 1);
+ if (!Operand)
+ return std::unexpected(Operand.error());
+ return Printer.printTargetExpression(Target->Kind, *Operand);
+ }
+ }
+ return makeUnexpected(ErrorCode::InternalError, "MC expression kind is invalid");
+}
+
+} // namespace
+
+Expected<std::string> printMCSymbolName(std::string_view Name) {
+ if (Name.empty())
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "MC symbol name cannot be empty");
+ if (isBareSymbolName(Name))
+ return std::string(Name);
+
+ std::string Result;
+ Result.push_back('"');
+ for (const char RawCharacter : Name) {
+ const auto Character = static_cast<unsigned char>(RawCharacter);
+ if (Character == 0 || Character == '\n' || Character == '\r' || Character == 0x7fU) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "MC symbol name contains a non-printable character");
+ }
+ if (Character == '"' || Character == '\\')
+ Result.push_back('\\');
+ Result.push_back(static_cast<char>(Character));
+ }
+ Result.push_back('"');
+ return Result;
+}
+
+Expected<std::string> printMCExpression(const MCExpr &Expression, const MCInstPrinter &Printer) {
+ auto Verification = Expression.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return printExpressionImpl(Expression, Printer, 1);
+}
+
+} // namespace rcc::mc
diff --git a/backend/lib/MC/MCSection.cpp b/backend/lib/MC/MCSection.cpp
new file mode 100644
index 0000000..21ba410
--- /dev/null
+++ b/backend/lib/MC/MCSection.cpp
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MC/MCSection.h"
+
+#include <cstdint>
+#include <string_view>
+
+namespace rcc::mc {
+namespace {
+
+[[nodiscard]] bool isValidAssemblyName(std::string_view Name) noexcept {
+ if (Name.empty())
+ return false;
+ for (const char RawCharacter : Name) {
+ const auto Character = static_cast<unsigned char>(RawCharacter);
+ if (Character == 0 || Character == '\n' || Character == '\r' || Character == 0x7fU)
+ return false;
+ }
+ return true;
+}
+
+[[nodiscard]] bool isPowerOfTwo(std::uint64_t Value) noexcept {
+ return Value != 0 && (Value & (Value - 1U)) == 0;
+}
+
+} // namespace
+
+Expected<void> MCSectionDesc::verify() const {
+ if (!isValidAssemblyName(Name))
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC section has an invalid name");
+ if (!isPowerOfTwo(Alignment)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "MC section alignment must be a nonzero power of two");
+ }
+ if (Flags.has(MCSectionFlag::Strings) && !Flags.has(MCSectionFlag::Merge)) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "string MC sections must also be mergeable");
+ }
+ if (Flags.has(MCSectionFlag::Merge) && EntrySize == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "mergeable MC sections require a nonzero entry size");
+ }
+ if ((Kind == MCSectionKind::TLSData || Kind == MCSectionKind::TLSBSS) &&
+ !Flags.has(MCSectionFlag::TLS)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "TLS MC sections must carry the TLS section flag");
+ }
+ if (Flags.has(MCSectionFlag::Group) != static_cast<bool>(Group)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "MC section group flag and group symbol must agree");
+ }
+ if (Group && Group->name().empty()) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "MC section group symbol must have a name");
+ }
+ if (UniqueID && *UniqueID == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "unique MC section identity must be nonzero");
+ }
+ return {};
+}
+
+} // namespace rcc::mc
diff --git a/backend/lib/MC/MCStreamer.cpp b/backend/lib/MC/MCStreamer.cpp
new file mode 100644
index 0000000..001fa28
--- /dev/null
+++ b/backend/lib/MC/MCStreamer.cpp
@@ -0,0 +1,488 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MC/MCStreamer.h"
+
+#include <algorithm>
+#include <array>
+#include <cctype>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace rcc::mc {
+namespace {
+
+[[nodiscard]] bool containsInvalidLineCharacter(std::string_view Text) noexcept {
+ return Text.find('\0') != std::string_view::npos || Text.find('\n') != std::string_view::npos ||
+ Text.find('\r') != std::string_view::npos;
+}
+
+[[nodiscard]] bool isPowerOfTwo(std::uint64_t Value) noexcept {
+ return Value != 0 && (Value & (Value - 1U)) == 0;
+}
+
+[[nodiscard]] unsigned log2Exact(std::uint64_t Value) noexcept {
+ unsigned Result = 0;
+ while (Value > 1) {
+ Value >>= 1U;
+ ++Result;
+ }
+ return Result;
+}
+
+[[nodiscard]] std::string byteSpelling(std::uint8_t Byte) {
+ constexpr std::string_view Hex = "0123456789abcdef";
+ std::string Result = "0x00";
+ Result[2] = Hex[(Byte >> 4U) & 0xfU];
+ Result[3] = Hex[Byte & 0xfU];
+ return Result;
+}
+
+[[nodiscard]] std::string sectionFlagSpelling(MCSectionFlags Flags) {
+ std::string Result;
+ const auto Add = [&](MCSectionFlag Flag, char Spelling) {
+ if (Flags.has(Flag))
+ Result.push_back(Spelling);
+ };
+ Add(MCSectionFlag::Alloc, 'a');
+ Add(MCSectionFlag::Writable, 'w');
+ Add(MCSectionFlag::Executable, 'x');
+ Add(MCSectionFlag::Merge, 'M');
+ Add(MCSectionFlag::Strings, 'S');
+ Add(MCSectionFlag::TLS, 'T');
+ Add(MCSectionFlag::Group, 'G');
+ Add(MCSectionFlag::Retain, 'R');
+ return Result;
+}
+
+[[nodiscard]] std::string_view sectionTypeSpelling(MCSectionKind Kind) noexcept {
+ switch (Kind) {
+ case MCSectionKind::BSS:
+ case MCSectionKind::TLSBSS:
+ return "nobits";
+ case MCSectionKind::Text:
+ case MCSectionKind::ReadOnly:
+ case MCSectionKind::Data:
+ case MCSectionKind::TLSData:
+ case MCSectionKind::EHFrame:
+ case MCSectionKind::Custom:
+ return "progbits";
+ }
+ return {};
+}
+
+[[nodiscard]] bool isDirectiveName(std::string_view Name) noexcept {
+ if (Name.empty() || Name.front() == '.')
+ return false;
+ const auto Initial = [](unsigned char Character) {
+ return std::isalpha(Character) != 0 || Character == '_';
+ };
+ const auto Following = [](unsigned char Character) {
+ return std::isalnum(Character) != 0 || Character == '_' || Character == '.';
+ };
+ if (!Initial(static_cast<unsigned char>(Name.front())))
+ return false;
+ return std::ranges::all_of(Name.substr(1), [Following](char Character) {
+ return Following(static_cast<unsigned char>(Character));
+ });
+}
+
+[[nodiscard]] std::string joinIntegerOperands(std::span<const std::int64_t> Operands) {
+ std::string Result;
+ for (const std::int64_t Operand : Operands) {
+ if (!Result.empty())
+ Result += ", ";
+ Result += std::to_string(Operand);
+ }
+ return Result;
+}
+
+[[nodiscard]] std::vector<std::uint8_t> encodeULEB128(std::uint64_t Value) {
+ std::vector<std::uint8_t> Bytes;
+ 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);
+ return Bytes;
+}
+
+} // namespace
+
+Expected<void> AsmTextStreamer::appendLine(std::string_view Prefix, std::string_view Text) {
+ if (IsFinished)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "cannot emit after the assembly stream is finalized");
+ if (Text.empty() || containsInvalidLineCharacter(Text)) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "assembly stream line must be nonempty and contain no line break");
+ }
+ Output.append(Prefix);
+ Output.append(Text);
+ Output.push_back('\n');
+ return {};
+}
+
+Expected<void> AsmTextStreamer::requireActiveSection() const {
+ if (!CurrentSection) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "assembly emission requires an active section");
+ }
+ if (IsFinished) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "cannot emit after the assembly stream is finalized");
+ }
+ return {};
+}
+
+Expected<void> AsmTextStreamer::switchSection(MCSectionRef Section) {
+ if (IsFinished)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "cannot switch section after assembly finalization");
+ 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());
+ if (CurrentSection == Section)
+ return {};
+
+ auto Name = printMCSymbolName(Section->name());
+ if (!Name)
+ return std::unexpected(Name.error());
+ const MCSectionDesc &Description = Section->description();
+ std::string Directive = ".section " + *Name + ",\"" + sectionFlagSpelling(Description.Flags) +
+ "\",@" + std::string(sectionTypeSpelling(Description.Kind));
+ if (Description.EntrySize != 0)
+ Directive += "," + std::to_string(Description.EntrySize);
+ if (Description.Group) {
+ auto GroupName = printMCSymbolName(Description.Group->name());
+ if (!GroupName)
+ return std::unexpected(GroupName.error());
+ Directive += "," + *GroupName + ",comdat";
+ }
+ if (Description.UniqueID)
+ Directive += ",unique," + std::to_string(*Description.UniqueID);
+
+ auto Emitted = appendLine({}, Directive);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ CurrentSection = std::move(Section);
+ return {};
+}
+
+Expected<void> AsmTextStreamer::emitLabel(MCSymbolRef Symbol) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ if (!Symbol)
+ return makeUnexpected(ErrorCode::InvalidArgument, "cannot emit a null MC symbol label");
+ const std::string Name(Symbol->name());
+ if (!DefinedSymbols.insert(Name).second) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "MC symbol label is defined more than once");
+ }
+ auto Printed = printMCSymbolName(Name);
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ return appendLine({}, *Printed + ":");
+}
+
+Expected<void> AsmTextStreamer::emitInstruction(const MCInst &Instruction) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ if (!Instruction.opcode().isValid())
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "MC instruction has an invalid opcode");
+ for (const MCOperand &Operand : Instruction.operands()) {
+ if (const MCExprRef *Expression = Operand.expressionValue(); Expression != nullptr) {
+ if (!*Expression)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "MC instruction contains a null expression operand");
+ auto Verification = (*Expression)->verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ }
+ }
+ auto Text = Printer->printInstruction(Instruction);
+ if (!Text)
+ return std::unexpected(Text.error());
+ return appendLine(" ", *Text);
+}
+
+Expected<void> AsmTextStreamer::emitValue(MCExprRef Value, std::uint8_t Size) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ if (!Value)
+ return makeUnexpected(ErrorCode::InvalidArgument, "cannot emit a null MC value expression");
+ std::string_view Directive;
+ switch (Size) {
+ case 1:
+ Directive = ".byte ";
+ break;
+ case 2:
+ Directive = ".2byte ";
+ break;
+ case 4:
+ Directive = ".4byte ";
+ break;
+ case 8:
+ Directive = ".8byte ";
+ break;
+ default:
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "assembly value size must be 1, 2, 4, or 8 bytes");
+ }
+ auto Printed = printMCExpression(*Value, *Printer);
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ return appendLine(" ", std::string(Directive) + *Printed);
+}
+
+Expected<void> AsmTextStreamer::emitBytes(std::span<const std::uint8_t> Bytes) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ for (std::size_t Begin = 0; Begin < Bytes.size(); Begin += 16) {
+ const std::size_t End = std::min(Bytes.size(), Begin + 16);
+ std::string Directive = ".byte ";
+ for (std::size_t Index = Begin; Index < End; ++Index) {
+ if (Index != Begin)
+ Directive += ", ";
+ Directive += byteSpelling(Bytes[Index]);
+ }
+ auto Emitted = appendLine(" ", Directive);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ }
+ return {};
+}
+
+Expected<void> AsmTextStreamer::emitZeroFill(std::uint64_t Size) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ if (Size == 0)
+ return {};
+ return appendLine(" ", ".zero " + std::to_string(Size));
+}
+
+Expected<void> AsmTextStreamer::emitAlignment(std::uint64_t Alignment, std::uint8_t Fill,
+ std::uint64_t MaximumBytes) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ if (!isPowerOfTwo(Alignment)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "assembly alignment must be a nonzero power of two");
+ }
+ std::string Directive = ".p2align " + std::to_string(log2Exact(Alignment));
+ if (Fill != 0 || MaximumBytes != 0)
+ Directive += ", " + std::to_string(Fill);
+ if (MaximumBytes != 0)
+ Directive += ", " + std::to_string(MaximumBytes);
+ return appendLine(" ", Directive);
+}
+
+Expected<void> AsmTextStreamer::emitSymbolAttribute(MCSymbolRef Symbol,
+ MCSymbolAttribute Attribute) {
+ if (IsFinished)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "cannot emit a symbol attribute after assembly finalization");
+ if (!Symbol)
+ return makeUnexpected(ErrorCode::InvalidArgument, "symbol attribute requires an MC symbol");
+ auto Name = printMCSymbolName(Symbol->name());
+ if (!Name)
+ return std::unexpected(Name.error());
+
+ switch (Attribute) {
+ case MCSymbolAttribute::Global:
+ return appendLine({}, ".globl " + *Name);
+ case MCSymbolAttribute::Weak:
+ return appendLine({}, ".weak " + *Name);
+ case MCSymbolAttribute::Local:
+ return appendLine({}, ".local " + *Name);
+ case MCSymbolAttribute::Hidden:
+ return appendLine({}, ".hidden " + *Name);
+ case MCSymbolAttribute::Protected:
+ return appendLine({}, ".protected " + *Name);
+ case MCSymbolAttribute::Function:
+ return appendLine({}, ".type " + *Name + ", @function");
+ case MCSymbolAttribute::Object:
+ return appendLine({}, ".type " + *Name + ", @object");
+ case MCSymbolAttribute::TLSObject:
+ return appendLine({}, ".type " + *Name + ", @tls_object");
+ }
+ return makeUnexpected(ErrorCode::InternalError, "MC symbol attribute is invalid");
+}
+
+Expected<void> AsmTextStreamer::emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) {
+ if (IsFinished)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "cannot emit a symbol size after assembly finalization");
+ if (!Symbol || !Size)
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "symbol size event requires a symbol and an expression");
+ auto Name = printMCSymbolName(Symbol->name());
+ if (!Name)
+ return std::unexpected(Name.error());
+ auto PrintedSize = printMCExpression(*Size, *Printer);
+ if (!PrintedSize)
+ return std::unexpected(PrintedSize.error());
+ return appendLine({}, ".size " + *Name + ", " + *PrintedSize);
+}
+
+Expected<void> AsmTextStreamer::emitDirective(std::string_view Name, std::string_view Arguments) {
+ if (!isDirectiveName(Name) || containsInvalidLineCharacter(Arguments)) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "assembly directive name or arguments are invalid");
+ }
+ std::string Text = "." + std::string(Name);
+ if (!Arguments.empty())
+ Text += " " + std::string(Arguments);
+ return appendLine({}, Text);
+}
+
+Expected<void> AsmTextStreamer::emitCFI(const MCCFIInstruction &Instruction) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ auto Verification = Instruction.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+
+ const std::string Operands = joinIntegerOperands(Instruction.Operands);
+ switch (Instruction.Operation) {
+ case MCCFIOperation::StartProc:
+ return emitDirective("cfi_startproc");
+ case MCCFIOperation::EndProc:
+ return emitDirective("cfi_endproc");
+ case MCCFIOperation::Sections:
+ return emitDirective("cfi_sections", ".eh_frame");
+ case MCCFIOperation::Personality:
+ case MCCFIOperation::LSDA: {
+ auto Symbol = printMCSymbolName(Instruction.Symbol->name());
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return emitDirective(Instruction.Operation == MCCFIOperation::Personality ? "cfi_personality"
+ : "cfi_lsda",
+ Operands + ", " + *Symbol);
+ }
+ case MCCFIOperation::SignalFrame:
+ return emitDirective("cfi_signal_frame");
+ case MCCFIOperation::ReturnColumn:
+ return emitDirective("cfi_return_column", Operands);
+ case MCCFIOperation::SameValue:
+ return emitDirective("cfi_same_value", Operands);
+ case MCCFIOperation::RememberState:
+ return emitDirective("cfi_remember_state");
+ case MCCFIOperation::RestoreState:
+ return emitDirective("cfi_restore_state");
+ case MCCFIOperation::Offset:
+ return emitDirective("cfi_offset", Operands);
+ case MCCFIOperation::DefCFARegister:
+ return emitDirective("cfi_def_cfa_register", Operands);
+ case MCCFIOperation::DefCFAOffset:
+ return emitDirective("cfi_def_cfa_offset", Operands);
+ case MCCFIOperation::DefCFA:
+ return emitDirective("cfi_def_cfa", Operands);
+ case MCCFIOperation::RelativeOffset:
+ return emitDirective("cfi_rel_offset", Operands);
+ case MCCFIOperation::AdjustCFAOffset:
+ return emitDirective("cfi_adjust_cfa_offset", Operands);
+ case MCCFIOperation::Escape: {
+ std::string Bytes;
+ for (const std::uint8_t Byte : Instruction.Bytes) {
+ if (!Bytes.empty())
+ Bytes += ", ";
+ Bytes += byteSpelling(Byte);
+ }
+ return emitDirective("cfi_escape", Bytes);
+ }
+ case MCCFIOperation::Restore:
+ return emitDirective("cfi_restore", Operands);
+ case MCCFIOperation::Undefined:
+ return emitDirective("cfi_undefined", Operands);
+ case MCCFIOperation::Register:
+ return emitDirective("cfi_register", Operands);
+ case MCCFIOperation::WindowSave:
+ return emitDirective("cfi_window_save");
+ case MCCFIOperation::NegateReturnAddressState:
+ return emitDirective("cfi_negate_ra_state");
+ case MCCFIOperation::NegateReturnAddressStateWithPC:
+ return emitDirective("cfi_negate_ra_state_with_pc");
+ case MCCFIOperation::GNUArgsSize: {
+ if (Instruction.Operands.front() < 0) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "GNU CFI argument size cannot be negative");
+ }
+ std::vector<std::uint8_t> Bytes{0x2eU};
+ const auto Encoded = encodeULEB128(static_cast<std::uint64_t>(Instruction.Operands.front()));
+ Bytes.insert(Bytes.end(), Encoded.begin(), Encoded.end());
+ return emitCFI(MCCFIInstruction{.Operation = MCCFIOperation::Escape,
+ .Operands = {},
+ .Bytes = std::move(Bytes),
+ .Symbol = {}});
+ }
+ case MCCFIOperation::Label:
+ return emitLabel(Instruction.Symbol);
+ case MCCFIOperation::ValueOffset:
+ return emitDirective("cfi_val_offset", Operands);
+ }
+ return makeUnexpected(ErrorCode::InternalError, "MC CFI operation is invalid");
+}
+
+Expected<void> AsmTextStreamer::emitInlineAssembly(std::string_view Text) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ if (Text.find('\0') != std::string_view::npos || Text.find('\r') != std::string_view::npos) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "inline assembly contains an invalid character");
+ }
+ if (Text.empty())
+ return {};
+ Output.append(Text);
+ if (Text.back() != '\n')
+ Output.push_back('\n');
+ return {};
+}
+
+Expected<void> AsmTextStreamer::emitComment(std::string_view Text) {
+ auto Active = requireActiveSection();
+ if (!Active)
+ return std::unexpected(Active.error());
+ if (Text.empty() || containsInvalidLineCharacter(Text))
+ return makeUnexpected(ErrorCode::InvalidArgument, "assembly comment is invalid");
+ return appendLine(" # ", Text);
+}
+
+Expected<void> AsmTextStreamer::finish() {
+ if (IsFinished)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "assembly stream was already finalized");
+ IsFinished = true;
+ return {};
+}
+
+Expected<std::string> AsmTextStreamer::takeOutput() {
+ if (!IsFinished)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "assembly output cannot be taken before finalization");
+ if (OutputWasTaken)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "assembly output was already taken");
+ OutputWasTaken = true;
+ return std::move(Output);
+}
+
+} // namespace rcc::mc
diff --git a/backend/lib/Support/Error.cpp b/backend/lib/Support/Error.cpp
index 88e16c9..ed88d1a 100644
--- a/backend/lib/Support/Error.cpp
+++ b/backend/lib/Support/Error.cpp
@@ -28,6 +28,8 @@ std::string_view getErrorCodeName(ErrorCode Code) noexcept {
return "final encodability failed";
case ErrorCode::CFGOptimizationFailed:
return "CFG optimization failed";
+ case ErrorCode::AssemblyLoweringFailed:
+ return "assembly lowering failed";
case ErrorCode::IoError:
return "I/O error";
case ErrorCode::InternalError:
diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt
index a29315a..646e856 100644
--- a/backend/lib/Target/RISCV/CMakeLists.txt
+++ b/backend/lib/Target/RISCV/CMakeLists.txt
@@ -50,7 +50,10 @@ add_library(
RISCVBranchInfo.cpp
RISCVCodeGen.cpp
RISCVCallLowering.cpp
+ RISCVAssemblyPrinter.cpp
RISCVFrameLowering.cpp
+ RISCVInstPrinter.cpp
+ RISCVMCTargetLowering.cpp
RISCVMachineCombiner.cpp
RISCVMachineMemoryFolding.cpp
RISCVOptimizationPipeline.cpp
@@ -75,7 +78,13 @@ target_include_directories(
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
PRIVATE "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV"
)
-target_link_libraries(RCCBackendRISCV PUBLIC RCCBackend::CodeGen)
+target_link_libraries(
+ RCCBackendRISCV
+ PUBLIC
+ RCCBackend::AsmPrinter
+ RCCBackend::CodeGen
+ RCCBackend::MC
+)
set_target_properties(
RCCBackendRISCV
@@ -100,9 +109,12 @@ install(
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVBranchInfo.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinter.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCodeGen.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLowering.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstPrinter.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCTargetLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVOptimizationPipeline.cpp"
@@ -111,14 +123,20 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVScheduling.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVTarget.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/RISCVMCTargetLowering.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTarget.h"
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVBranchInfo.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinter.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCodeGen.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLowering.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstPrinter.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCTargetLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVOptimizationPipeline.cpp"
diff --git a/backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp b/backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp
new file mode 100644
index 0000000..7661c61
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h"
+
+#include "rcc/CodeGen/AsmPrinter.h"
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineMCContext.h"
+#include "rcc/MC/MCContext.h"
+#include "rcc/MC/MCStreamer.h"
+#include "rcc/Target/RISCV/RISCVMCTargetLowering.h"
+
+#include <string>
+
+namespace rcc::riscv {
+namespace {
+
+[[nodiscard]] AsmPrinterOptions printerOptions(AssemblyEmissionOptions Options) noexcept {
+ return AsmPrinterOptions{
+ .EmitFunctionDirectives = Options.EmitFunctionDirectives,
+ .EmitCFI = Options.EmitCFI,
+ .EmitBlockLabels = Options.EmitBlockLabels,
+ .EmitInstructionSymbols = true,
+ .VerifyInput = Options.VerifyInput,
+ };
+}
+
+} // namespace
+
+Expected<std::string> emitAssembly(const CompilationUnit &Unit, const TargetDescription &Target,
+ AssemblyEmissionOptions Options) {
+ mc::MCContext MCContext;
+ MachineMCContext MachineContext(MCContext);
+ RISCVMCTargetLowering Lowering(Target);
+ mc::AsmTextStreamer Streamer(Lowering.instructionPrinter());
+ AsmPrinter Printer(Target, Lowering, MachineContext, Streamer, printerOptions(Options));
+ auto Emitted = Printer.emitCompilationUnit(Unit);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ return Streamer.takeOutput();
+}
+
+Expected<std::string> emitAssembly(const MachineFunction &Function, const TargetDescription &Target,
+ AssemblyEmissionOptions Options) {
+ mc::MCContext MCContext;
+ MachineMCContext MachineContext(MCContext);
+ RISCVMCTargetLowering Lowering(Target);
+ mc::AsmTextStreamer Streamer(Lowering.instructionPrinter());
+ AsmPrinter Printer(Target, Lowering, MachineContext, Streamer, printerOptions(Options));
+
+ auto Header = Lowering.emitFileHeader(MachineContext, Streamer);
+ if (!Header)
+ return std::unexpected(Header.error());
+ auto Emitted = Printer.emitFunction(Function);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ auto Trailer = Lowering.emitFileTrailer(MachineContext, Streamer);
+ if (!Trailer)
+ return std::unexpected(Trailer.error());
+ auto Finished = Streamer.finish();
+ if (!Finished)
+ return std::unexpected(Finished.error());
+ return Streamer.takeOutput();
+}
+
+} // namespace rcc::riscv
diff --git a/backend/lib/Target/RISCV/RISCVInstPrinter.cpp b/backend/lib/Target/RISCV/RISCVInstPrinter.cpp
new file mode 100644
index 0000000..ba3e509
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVInstPrinter.cpp
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVInstPrinter.h"
+
+#include "rcc/CodeGen/Register.h"
+#include "rcc/MC/MCExpr.h"
+#include "rcc/MC/MCInst.h"
+#include "rcc/MC/MCInstPrinter.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <array>
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <ranges>
+#include <string>
+#include <string_view>
+
+namespace rcc::riscv {
+namespace {
+
+struct LinearFormat final {
+ std::string_view Name;
+ std::size_t OperandCount = 0;
+};
+
+constexpr std::array LinearFormats{
+ LinearFormat{"none", 0},
+ LinearFormat{"rd-imm", 2},
+ LinearFormat{"rd-rs1-imm", 3},
+ LinearFormat{"rd-rs1-rs2", 3},
+ LinearFormat{"rs1-rs2-target", 3},
+ LinearFormat{"rd-target", 2},
+ LinearFormat{"imm-imm", 2},
+ LinearFormat{"rd-csr-rs1", 3},
+ LinearFormat{"rd-csr-imm", 3},
+ LinearFormat{"frd-frs1-frs2-rm", 4},
+ LinearFormat{"frd-frs1-frs2-frs3-rm", 5},
+ LinearFormat{"frd-frs1-rm", 3},
+ LinearFormat{"frd-frs1-frs2", 3},
+ LinearFormat{"rd-frs1-rm", 3},
+ LinearFormat{"rd-frs1", 2},
+ LinearFormat{"rd-frs1-frs2", 3},
+ LinearFormat{"frd-rs1-rm", 3},
+ LinearFormat{"frd-rs1", 2},
+ LinearFormat{"target", 1},
+ LinearFormat{"rs1-target", 2},
+ LinearFormat{"rs1", 1},
+ LinearFormat{"rd-rs2", 2},
+};
+
+[[nodiscard]] const AssemblyFormatInfo *assemblyFormat(AssemblyFormatID ID) noexcept {
+ const auto Formats = assemblyFormats();
+ const auto Position = std::ranges::find(Formats, ID, &AssemblyFormatInfo::ID);
+ return Position == Formats.end() ? nullptr : &*Position;
+}
+
+[[nodiscard]] std::optional<std::size_t> linearOperandCount(std::string_view Name) noexcept {
+ const auto *const Position = std::ranges::find(LinearFormats, Name, &LinearFormat::Name);
+ return Position == LinearFormats.end() ? std::nullopt : std::optional(Position->OperandCount);
+}
+
+[[nodiscard]] Expected<void> requireOperandCount(const mc::MCInst &Instruction,
+ std::size_t ExpectedCount) {
+ if (Instruction.operands().size() != ExpectedCount) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V assembly format has an unexpected operand count");
+ }
+ return {};
+}
+
+[[nodiscard]] std::string hexValue(std::uint64_t Value) {
+ std::array<char, 16> Digits{};
+ const auto [End, Error] = std::to_chars(Digits.data(), Digits.data() + Digits.size(), Value, 16);
+ if (Error != std::errc{})
+ return {};
+ return "0x" + std::string(Digits.data(), End);
+}
+
+[[nodiscard]] Expected<std::string> formatOperand(const mc::MCOperand &Operand,
+ const TargetDescription &Target,
+ const mc::MCInstPrinter &Printer) {
+ if (const mc::MCRegister *Register = Operand.registerValue(); Register != nullptr) {
+ auto Physical = PhysicalRegister::create(Register->value());
+ if (!Physical)
+ return std::unexpected(Physical.error());
+ const auto Name = Target.registers().physicalRegisterMIRName(*Physical);
+ if (!Name) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V MC instruction references an unknown register");
+ }
+ return std::string(*Name);
+ }
+ if (const std::int64_t *Immediate = Operand.immediateValue(); Immediate != nullptr)
+ return std::to_string(*Immediate);
+ if (const std::uint32_t *Bits = Operand.float32BitsValue(); Bits != nullptr)
+ return hexValue(*Bits);
+ if (const std::uint64_t *Bits = Operand.float64BitsValue(); Bits != nullptr)
+ return hexValue(*Bits);
+ if (const mc::MCExprRef *Expression = Operand.expressionValue(); Expression != nullptr) {
+ if (!*Expression)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V MC instruction has a null expression operand");
+ return mc::printMCExpression(**Expression, Printer);
+ }
+ return makeUnexpected(ErrorCode::InternalError, "RISC-V MC operand storage is invalid");
+}
+
+[[nodiscard]] Expected<std::string> formatRegisterOperand(const mc::MCInst &Instruction,
+ std::size_t Index,
+ const TargetDescription &Target,
+ const mc::MCInstPrinter &Printer) {
+ if (Index >= Instruction.operands().size() ||
+ Instruction.operands()[Index].registerValue() == nullptr) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V assembly format requires a register operand");
+ }
+ return formatOperand(Instruction.operands()[Index], Target, Printer);
+}
+
+[[nodiscard]] Expected<std::string> formatAnyOperand(const mc::MCInst &Instruction,
+ std::size_t Index,
+ const TargetDescription &Target,
+ const mc::MCInstPrinter &Printer) {
+ if (Index >= Instruction.operands().size()) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V assembly format references an absent operand");
+ }
+ return formatOperand(Instruction.operands()[Index], Target, Printer);
+}
+
+[[nodiscard]] Expected<std::string> formatAddress(const mc::MCInst &Instruction,
+ std::size_t OffsetIndex, std::size_t BaseIndex,
+ const TargetDescription &Target,
+ const mc::MCInstPrinter &Printer) {
+ auto Offset = formatAnyOperand(Instruction, OffsetIndex, Target, Printer);
+ if (!Offset)
+ return std::unexpected(Offset.error());
+ auto Base = formatRegisterOperand(Instruction, BaseIndex, Target, Printer);
+ if (!Base)
+ return std::unexpected(Base.error());
+ return *Offset + "(" + *Base + ")";
+}
+
+[[nodiscard]] Expected<std::string> joinOperands(const mc::MCInst &Instruction, std::size_t Count,
+ const TargetDescription &Target,
+ const mc::MCInstPrinter &Printer) {
+ auto CountResult = requireOperandCount(Instruction, Count);
+ if (!CountResult)
+ return std::unexpected(CountResult.error());
+ std::string Result;
+ for (std::size_t Index = 0; Index < Count; ++Index) {
+ auto Operand = formatAnyOperand(Instruction, Index, Target, Printer);
+ if (!Operand)
+ return std::unexpected(Operand.error());
+ if (!Result.empty())
+ Result += ", ";
+ Result += *Operand;
+ }
+ return Result;
+}
+
+} // namespace
+
+Expected<std::string> RISCVInstPrinter::printInstruction(const mc::MCInst &Instruction) const {
+ const AssemblyInstructionInfo *Assembly =
+ assemblyInstruction(Opcode::fromValue(Instruction.opcode().value()));
+ if (Assembly == nullptr || Assembly->Mnemonic.empty()) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V instruction has no generated assembly spelling");
+ }
+ const AssemblyFormatInfo *Format = assemblyFormat(Assembly->Format);
+ if (Format == nullptr) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "RISC-V instruction references an unknown assembly format");
+ }
+
+ std::string Operands;
+ if (const auto LinearCount = linearOperandCount(Format->Name)) {
+ auto Joined = joinOperands(Instruction, *LinearCount, *Target, *this);
+ if (!Joined)
+ return std::unexpected(Joined.error());
+ Operands = std::move(*Joined);
+ } else if (Format->Name == "rd-offset-rs1" || Format->Name == "frd-offset-rs1" ||
+ Format->Name == "rs2-offset-rs1" || Format->Name == "frs2-offset-rs1") {
+ auto Count = requireOperandCount(Instruction, 3);
+ if (!Count)
+ return std::unexpected(Count.error());
+ auto First = formatRegisterOperand(Instruction, 0, *Target, *this);
+ if (!First)
+ return std::unexpected(First.error());
+ auto Address = formatAddress(Instruction, 2, 1, *Target, *this);
+ if (!Address)
+ return std::unexpected(Address.error());
+ Operands = *First + ", " + *Address;
+ } else if (Format->Name == "rd-paren-rs1") {
+ auto Count = requireOperandCount(Instruction, 2);
+ if (!Count)
+ return std::unexpected(Count.error());
+ auto Destination = formatRegisterOperand(Instruction, 0, *Target, *this);
+ if (!Destination)
+ return std::unexpected(Destination.error());
+ auto Base = formatRegisterOperand(Instruction, 1, *Target, *this);
+ if (!Base)
+ return std::unexpected(Base.error());
+ Operands = *Destination + ", (" + *Base + ")";
+ } else if (Format->Name == "rd-rs2-paren-rs1") {
+ auto Count = requireOperandCount(Instruction, 3);
+ if (!Count)
+ return std::unexpected(Count.error());
+ auto Destination = formatRegisterOperand(Instruction, 0, *Target, *this);
+ if (!Destination)
+ return std::unexpected(Destination.error());
+ auto Value = formatRegisterOperand(Instruction, 1, *Target, *this);
+ if (!Value)
+ return std::unexpected(Value.error());
+ auto Base = formatRegisterOperand(Instruction, 2, *Target, *this);
+ if (!Base)
+ return std::unexpected(Base.error());
+ Operands = *Destination + ", " + *Value + ", (" + *Base + ")";
+ } else if (Format->Name == "imm-sp") {
+ auto Count = requireOperandCount(Instruction, 1);
+ if (!Count)
+ return std::unexpected(Count.error());
+ auto Immediate = formatAnyOperand(Instruction, 0, *Target, *this);
+ if (!Immediate)
+ return std::unexpected(Immediate.error());
+ Operands = "sp, " + *Immediate;
+ } else if (Format->Name == "rd-imm-sp") {
+ auto Count = requireOperandCount(Instruction, 2);
+ if (!Count)
+ return std::unexpected(Count.error());
+ auto Destination = formatRegisterOperand(Instruction, 0, *Target, *this);
+ if (!Destination)
+ return std::unexpected(Destination.error());
+ auto Immediate = formatAnyOperand(Instruction, 1, *Target, *this);
+ if (!Immediate)
+ return std::unexpected(Immediate.error());
+ Operands = *Destination + ", sp, " + *Immediate;
+ } else if (Format->Name == "rd-offset-sp" || Format->Name == "frd-offset-sp" ||
+ Format->Name == "rs2-offset-sp" || Format->Name == "frs2-offset-sp") {
+ auto Count = requireOperandCount(Instruction, 2);
+ if (!Count)
+ return std::unexpected(Count.error());
+ auto First = formatRegisterOperand(Instruction, 0, *Target, *this);
+ if (!First)
+ return std::unexpected(First.error());
+ auto Offset = formatAnyOperand(Instruction, 1, *Target, *this);
+ if (!Offset)
+ return std::unexpected(Offset.error());
+ Operands = *First + ", " + *Offset + "(sp)";
+ } else {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V assembly printer does not support generated format '" +
+ std::string(Format->Name) + "'");
+ }
+
+ std::string Result(Assembly->Mnemonic);
+ if (!Operands.empty())
+ Result += " " + Operands;
+ return Result;
+}
+
+Expected<std::string> RISCVInstPrinter::printTargetExpression(std::uint32_t Kind,
+ std::string_view Operand) const {
+ switch (static_cast<RISCVMCExprKind>(Kind)) {
+ case RISCVMCExprKind::CallPLT:
+ return std::string(Operand);
+ case RISCVMCExprKind::PCRelativeHigh:
+ return "%pcrel_hi(" + std::string(Operand) + ")";
+ case RISCVMCExprKind::PCRelativeLow:
+ return "%pcrel_lo(" + std::string(Operand) + ")";
+ }
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V MC expression has an unknown target kind");
+}
+
+} // namespace rcc::riscv
diff --git a/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp b/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp
new file mode 100644
index 0000000..4e8d5ec
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp
@@ -0,0 +1,1158 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVMCTargetLowering.h"
+
+#include "rcc/CodeGen/GenericConstant.h"
+#include "rcc/CodeGen/GenericOpcodes.h"
+#include "rcc/CodeGen/GlobalObject.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMCContext.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/Register.h"
+#include "rcc/CodeGen/TargetConstant.h"
+#include "rcc/MC/MCContext.h"
+#include "rcc/MC/MCExpr.h"
+#include "rcc/MC/MCInst.h"
+#include "rcc/MC/MCSection.h"
+#include "rcc/MC/MCStreamer.h"
+#include "rcc/MC/MCSymbol.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <algorithm>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace rcc::riscv {
+namespace {
+
+constexpr std::uint32_t CallTargetFlag = 1;
+constexpr std::uint32_t PCRelativeHighTargetFlag = 2;
+constexpr std::uint32_t PCRelativeLowTargetFlag = 4;
+constexpr std::uint32_t CopyF32TargetFlag = 8;
+constexpr std::uint32_t CopyF64TargetFlag = 16;
+constexpr std::uint32_t KnownTargetFlags = CallTargetFlag | PCRelativeHighTargetFlag |
+ PCRelativeLowTargetFlag | CopyF32TargetFlag |
+ CopyF64TargetFlag;
+
+struct TypeLayout final {
+ std::uint64_t Size = 0;
+ std::uint64_t Alignment = 1;
+};
+
+[[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))
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "constant layout size overflows");
+ return Result;
+}
+
+[[nodiscard]] Expected<std::uint64_t> checkedMultiply(std::uint64_t Left, std::uint64_t Right) {
+ std::uint64_t Result = 0;
+ if (__builtin_mul_overflow(Left, Right, &Result))
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "constant layout size overflows");
+ return Result;
+}
+
+[[nodiscard]] Expected<std::uint64_t> alignTo(std::uint64_t Value, std::uint64_t Alignment) {
+ if (Alignment == 0 || (Alignment & (Alignment - 1U)) != 0) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "constant layout requires power-of-two alignment");
+ }
+ auto Adjusted = checkedAdd(Value, Alignment - 1U);
+ if (!Adjusted)
+ return std::unexpected(Adjusted.error());
+ return *Adjusted & ~(Alignment - 1U);
+}
+
+[[nodiscard]] std::uint64_t naturalAlignment(std::uint64_t Size) noexcept {
+ if (Size <= 1)
+ return 1;
+ std::uint64_t Result = 1;
+ while (Result < Size && Result < 16)
+ Result <<= 1U;
+ return Result;
+}
+
+[[nodiscard]] const GenericConstantType &resolvedType(const GenericConstantType &Type) noexcept {
+ const GenericConstantType *Result = &Type;
+ std::size_t Depth = 0;
+ while (Result->Kind == GenericConstantTypeKind::Named && Result->Elements.size() == 1 &&
+ Depth++ < 1024) {
+ Result = &Result->Elements.front();
+ }
+ return *Result;
+}
+
+[[nodiscard]] Expected<TypeLayout> layoutType(const GenericConstantType &InputType,
+ std::size_t Depth = 0) {
+ if (Depth > 1024) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "constant layout exceeds its nesting limit");
+ }
+ const GenericConstantType &Type = resolvedType(InputType);
+ switch (Type.Kind) {
+ case GenericConstantTypeKind::Integer: {
+ const std::uint64_t Size = (static_cast<std::uint64_t>(Type.BitWidth) + 7U) / 8U;
+ return TypeLayout{.Size = Size, .Alignment = naturalAlignment(Size)};
+ }
+ case GenericConstantTypeKind::Byte:
+ return TypeLayout{.Size = 1, .Alignment = 1};
+ case GenericConstantTypeKind::Floating: {
+ const auto BitWidth = getFloatSemanticsBitWidth(Type.Semantics);
+ if (!BitWidth) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "floating constant has unknown semantics");
+ }
+ const std::uint64_t Size = (static_cast<std::uint64_t>(*BitWidth) + 7U) / 8U;
+ return TypeLayout{.Size = Size, .Alignment = naturalAlignment(Size)};
+ }
+ case GenericConstantTypeKind::Pointer:
+ return TypeLayout{.Size = 8, .Alignment = 8};
+ case GenericConstantTypeKind::Array:
+ case GenericConstantTypeKind::Vector: {
+ if (Type.Elements.size() != 1 ||
+ (Type.Kind == GenericConstantTypeKind::Vector && Type.Scalable)) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC assembly cannot lay out this sequential constant type");
+ }
+ auto Element = layoutType(Type.Elements.front(), Depth + 1);
+ if (!Element)
+ return std::unexpected(Element.error());
+ auto Stride = alignTo(Element->Size, Element->Alignment);
+ if (!Stride)
+ return std::unexpected(Stride.error());
+ auto Size = checkedMultiply(*Stride, Type.ElementCount);
+ if (!Size)
+ return std::unexpected(Size.error());
+ std::uint64_t Alignment = Element->Alignment;
+ if (Type.Kind == GenericConstantTypeKind::Vector)
+ Alignment = naturalAlignment(*Size);
+ return TypeLayout{.Size = *Size, .Alignment = Alignment};
+ }
+ case GenericConstantTypeKind::Struct: {
+ std::uint64_t Offset = 0;
+ std::uint64_t StructAlignment = 1;
+ for (const GenericConstantType &ElementType : Type.Elements) {
+ auto Element = layoutType(ElementType, Depth + 1);
+ if (!Element)
+ return std::unexpected(Element.error());
+ const std::uint64_t ElementAlignment = Type.Packed ? 1 : Element->Alignment;
+ auto Aligned = alignTo(Offset, ElementAlignment);
+ if (!Aligned)
+ return std::unexpected(Aligned.error());
+ auto End = checkedAdd(*Aligned, Element->Size);
+ if (!End)
+ return std::unexpected(End.error());
+ Offset = *End;
+ StructAlignment = std::max(StructAlignment, ElementAlignment);
+ }
+ auto Size = alignTo(Offset, StructAlignment);
+ if (!Size)
+ return std::unexpected(Size.error());
+ return TypeLayout{.Size = *Size, .Alignment = StructAlignment};
+ }
+ case GenericConstantTypeKind::Named:
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC assembly cannot lay out an unresolved named constant type");
+ case GenericConstantTypeKind::Invalid:
+ case GenericConstantTypeKind::Void:
+ case GenericConstantTypeKind::Label:
+ case GenericConstantTypeKind::Token:
+ case GenericConstantTypeKind::Metadata:
+ case GenericConstantTypeKind::Function:
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "constant has a non-storage type");
+ }
+ return makeUnexpected(ErrorCode::InternalError, "generic constant type kind is invalid");
+}
+
+[[nodiscard]] Expected<mc::MCExprRef> addOffset(mc::MCExprRef Expression, std::int64_t Offset) {
+ if (Offset == 0)
+ return Expression;
+ auto Result = mc::MCExpr::binary(mc::MCBinaryOperator::Add, std::move(Expression),
+ mc::MCExpr::constant(Offset));
+ if (!Result)
+ return std::unexpected(Result.error());
+ return *Result;
+}
+
+[[nodiscard]] Expected<mc::MCExprRef> applyTargetFlags(mc::MCExprRef Expression,
+ std::uint32_t Flags) {
+ if ((Flags & ~KnownTargetFlags) != 0 || (Flags & (CopyF32TargetFlag | CopyF64TargetFlag)) != 0) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V operand has target flags that cannot survive MC lowering");
+ }
+ if (Flags == 0)
+ return Expression;
+
+ RISCVMCExprKind Kind;
+ switch (Flags) {
+ case CallTargetFlag:
+ Kind = RISCVMCExprKind::CallPLT;
+ break;
+ case PCRelativeHighTargetFlag:
+ Kind = RISCVMCExprKind::PCRelativeHigh;
+ break;
+ case PCRelativeLowTargetFlag:
+ Kind = RISCVMCExprKind::PCRelativeLow;
+ break;
+ default:
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V operand combines incompatible direct target flags");
+ }
+ auto TargetExpression =
+ mc::MCExpr::target(static_cast<std::uint32_t>(Kind), std::move(Expression));
+ if (!TargetExpression)
+ return std::unexpected(TargetExpression.error());
+ return *TargetExpression;
+}
+
+[[nodiscard]] Expected<mc::MCOperand> symbolOperand(mc::MCSymbolRef Symbol, std::int64_t Offset,
+ std::uint32_t TargetFlags) {
+ auto Expression = mc::MCExpr::symbol(std::move(Symbol));
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ auto WithOffset = addOffset(std::move(*Expression), Offset);
+ if (!WithOffset)
+ return std::unexpected(WithOffset.error());
+ auto TargetExpression = applyTargetFlags(std::move(*WithOffset), TargetFlags);
+ if (!TargetExpression)
+ return std::unexpected(TargetExpression.error());
+ return mc::MCOperand::expression(std::move(*TargetExpression));
+}
+
+[[nodiscard]] Expected<std::int64_t> lowerIntegerImmediate(const IntegerImmediate &Value) {
+ if (Value.bitWidth() > 64 || Value.words().size() != 1) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V instruction has an integer immediate wider than 64 bits");
+ }
+ const std::uint64_t Raw = Value.words().front();
+ if (Value.bitWidth() == 64)
+ return std::bit_cast<std::int64_t>(Raw);
+ if (!Value.isNegative())
+ return static_cast<std::int64_t>(Raw);
+ const std::uint64_t Mask = (std::uint64_t{1} << Value.bitWidth()) - 1U;
+ return std::bit_cast<std::int64_t>(Raw | ~Mask);
+}
+
+[[nodiscard]] Expected<mc::MCOperand> lowerOperand(const MachineOperand &Operand,
+ const MachineFunction &Function,
+ MachineMCContext &Context) {
+ if (const auto *RegisterValue = Operand.getIf<Register>()) {
+ const auto Physical = RegisterValue->physicalRegister();
+ if (!Physical || Operand.subReg().value() != 0) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V MC lowering requires a resolved physical register");
+ }
+ if (Operand.targetFlags() != 0) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V register operand retains target flags at MC lowering");
+ }
+ return mc::MCOperand::registerOperand(mc::MCRegister::fromValue(Physical->number()));
+ }
+ if (const auto *Immediate = Operand.getIf<std::int64_t>()) {
+ if (Operand.targetFlags() != 0) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V immediate operand has symbol-only target flags");
+ }
+ return mc::MCOperand::immediate(*Immediate);
+ }
+ if (const auto *Immediate = Operand.getIf<IntegerImmediateOperand>()) {
+ if (Operand.targetFlags() != 0) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V integer immediate has symbol-only target flags");
+ }
+ auto Lowered = lowerIntegerImmediate(Immediate->Value);
+ if (!Lowered)
+ return std::unexpected(Lowered.error());
+ return mc::MCOperand::immediate(*Lowered);
+ }
+ if (const auto *Immediate = Operand.getIf<FloatingImmediateOperand>()) {
+ if (Operand.targetFlags() != 0 || Immediate->Bits.words().size() != 1) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V floating immediate cannot be represented in MCInst");
+ }
+ if (Immediate->Bits.bitWidth() == 32)
+ return mc::MCOperand::float32Bits(static_cast<std::uint32_t>(Immediate->Bits.words()[0]));
+ if (Immediate->Bits.bitWidth() == 64)
+ return mc::MCOperand::float64Bits(Immediate->Bits.words()[0]);
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V MC lowering supports only 32-bit and 64-bit FP immediates");
+ }
+ if (const auto *Block = Operand.getIf<BasicBlockOperand>()) {
+ if (Block->Block == nullptr)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "RISC-V block operand is null");
+ const mc::MCSymbolRef Symbol = Context.blockSymbol(*Block->Block);
+ if (!Symbol)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V block operand has no MC symbol");
+ return symbolOperand(Symbol, 0, Operand.targetFlags());
+ }
+ if (const auto *Pool = Operand.getIf<ConstantPoolOperand>()) {
+ const mc::MCSymbolRef Symbol = Context.constantPoolSymbol(Function, Pool->Index);
+ if (!Symbol)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V constant-pool operand has no MC symbol");
+ return symbolOperand(Symbol, Pool->Offset, Operand.targetFlags());
+ }
+ if (const auto *Table = Operand.getIf<JumpTableOperand>()) {
+ const mc::MCSymbolRef Symbol = Context.jumpTableSymbol(Function, Table->Index);
+ if (!Symbol)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V jump-table operand has no MC symbol");
+ return symbolOperand(Symbol, 0, Operand.targetFlags());
+ }
+ if (const auto *Global = Operand.getIf<GlobalSymbolOperand>()) {
+ auto Symbol = Context.symbol(Global->Name, mc::MCSymbolKind::Object);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return symbolOperand(std::move(*Symbol), Global->Offset, Operand.targetFlags());
+ }
+ if (const auto *External = Operand.getIf<ExternalSymbolOperand>()) {
+ auto Symbol = Context.symbol(External->Name);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return symbolOperand(std::move(*Symbol), External->Offset, Operand.targetFlags());
+ }
+ if (const auto *Address = Operand.getIf<BlockAddressOperand>()) {
+ auto Symbol = Context.blockAddressSymbol(Address->FunctionName, Address->BlockName);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return symbolOperand(std::move(*Symbol), Address->Offset, Operand.targetFlags());
+ }
+ if (const auto *SymbolValue = Operand.getIf<MCSymbolOperand>()) {
+ auto Symbol = Context.symbol(SymbolValue->Name);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return symbolOperand(std::move(*Symbol), SymbolValue->Offset, Operand.targetFlags());
+ }
+ if (Operand.kind() == MachineOperand::Kind::FrameIndex) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V MC lowering encountered an unresolved frame index");
+ }
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V MC lowering encountered a non-encoding operand");
+}
+
+[[nodiscard]] const AssemblyFormatInfo *assemblyFormat(AssemblyFormatID ID) noexcept {
+ const auto Formats = assemblyFormats();
+ const auto Position = std::ranges::find(Formats, ID, &AssemblyFormatInfo::ID);
+ return Position == Formats.end() ? nullptr : &*Position;
+}
+
+[[nodiscard]] std::optional<std::size_t> formatOperandCount(std::string_view Name) noexcept {
+ if (Name == "none")
+ return 0;
+ if (Name == "rs1" || Name == "target" || Name == "imm-sp")
+ return 1;
+ if (Name == "rd-imm" || Name == "rd-target" || Name == "rd-frs1" || Name == "frd-rs1" ||
+ Name == "rs1-target" || Name == "rd-rs2" || Name == "rd-paren-rs1" || Name == "rd-imm-sp" ||
+ Name == "rd-offset-sp" || Name == "rs2-offset-sp" || Name == "frd-offset-sp" ||
+ Name == "frs2-offset-sp") {
+ return 2;
+ }
+ if (Name == "rd-rs1-imm" || Name == "rd-rs1-rs2" || Name == "rd-offset-rs1" ||
+ Name == "rs2-offset-rs1" || Name == "rs1-rs2-target" || Name == "imm-imm" ||
+ Name == "rd-csr-rs1" || Name == "rd-csr-imm" || Name == "rd-rs2-paren-rs1" ||
+ Name == "frd-offset-rs1" || Name == "frs2-offset-rs1" || Name == "frd-frs1-rm" ||
+ Name == "frd-frs1-frs2" || Name == "rd-frs1-rm" || Name == "rd-frs1-frs2" ||
+ Name == "frd-rs1-rm") {
+ return 3;
+ }
+ if (Name == "frd-frs1-frs2-rm")
+ return 4;
+ if (Name == "frd-frs1-frs2-frs3-rm")
+ return 5;
+ return std::nullopt;
+}
+
+[[nodiscard]] std::vector<std::uint8_t> bytesFromWords(std::span<const std::uint64_t> Words,
+ std::uint64_t Size) {
+ std::vector<std::uint8_t> Bytes(static_cast<std::size_t>(Size), 0);
+ for (std::uint64_t ByteIndex = 0; ByteIndex < Size; ++ByteIndex) {
+ const std::size_t WordIndex = static_cast<std::size_t>(ByteIndex / 8U);
+ if (WordIndex >= Words.size())
+ break;
+ const unsigned Shift = static_cast<unsigned>((ByteIndex % 8U) * 8U);
+ Bytes[static_cast<std::size_t>(ByteIndex)] =
+ static_cast<std::uint8_t>((Words[WordIndex] >> Shift) & 0xffU);
+ }
+ return Bytes;
+}
+
+[[nodiscard]] Expected<mc::MCExprRef> lowerConstantExpression(const GenericConstantData &Constant,
+ MachineMCContext &Context,
+ std::size_t Depth = 0) {
+ if (Depth > 1024) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "constant expression exceeds its nesting limit");
+ }
+ switch (Constant.Kind) {
+ case GenericConstantKind::Integer: {
+ if (!Constant.IntegerValue || Constant.IntegerValue->bitWidth() > 64 ||
+ Constant.IntegerValue->words().size() != 1) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC MC expression requires an integer no wider than 64 bits");
+ }
+ const std::uint64_t Raw = Constant.IntegerValue->words().front();
+ const std::int64_t Value = Constant.IntegerValue->bitWidth() == 64
+ ? std::bit_cast<std::int64_t>(Raw)
+ : static_cast<std::int64_t>(Raw);
+ return mc::MCExpr::constant(Value);
+ }
+ case GenericConstantKind::NullPointer:
+ case GenericConstantKind::ZeroInitializer:
+ case GenericConstantKind::Undef:
+ case GenericConstantKind::Poison:
+ return mc::MCExpr::constant(0);
+ case GenericConstantKind::GlobalAddress:
+ case GenericConstantKind::DSOLocalEquivalent:
+ case GenericConstantKind::NoCFI: {
+ auto Symbol = Context.symbol(Constant.Symbol, mc::MCSymbolKind::Object);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return mc::MCExpr::symbol(std::move(*Symbol));
+ }
+ case GenericConstantKind::BlockAddress: {
+ auto Symbol = Context.blockAddressSymbol(Constant.Symbol, Constant.Block);
+ if (!Symbol)
+ return std::unexpected(Symbol.error());
+ return mc::MCExpr::symbol(std::move(*Symbol));
+ }
+ case GenericConstantKind::Expression: {
+ if (Constant.Elements.empty()) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "generic constant expression has no operands");
+ }
+ auto Left = lowerConstantExpression(Constant.Elements.front(), Context, Depth + 1);
+ if (!Left)
+ return std::unexpected(Left.error());
+ switch (Constant.ExpressionOpcode) {
+ case GenericConstantExpressionOpcode::Trunc:
+ case GenericConstantExpressionOpcode::BitCast:
+ case GenericConstantExpressionOpcode::AddressSpaceCast:
+ case GenericConstantExpressionOpcode::IntToPtr:
+ case GenericConstantExpressionOpcode::PtrToAddr:
+ case GenericConstantExpressionOpcode::PtrToInt:
+ return *Left;
+ case GenericConstantExpressionOpcode::Add:
+ case GenericConstantExpressionOpcode::Sub:
+ case GenericConstantExpressionOpcode::Xor: {
+ if (Constant.Elements.size() != 2) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "binary constant expression has the wrong operand count");
+ }
+ auto Right = lowerConstantExpression(Constant.Elements[1], Context, Depth + 1);
+ if (!Right)
+ return std::unexpected(Right.error());
+ const mc::MCBinaryOperator Operator =
+ Constant.ExpressionOpcode == GenericConstantExpressionOpcode::Add
+ ? mc::MCBinaryOperator::Add
+ : Constant.ExpressionOpcode == GenericConstantExpressionOpcode::Sub
+ ? mc::MCBinaryOperator::Subtract
+ : mc::MCBinaryOperator::BitwiseXor;
+ return mc::MCExpr::binary(Operator, std::move(*Left), std::move(*Right));
+ }
+ case GenericConstantExpressionOpcode::Invalid:
+ case GenericConstantExpressionOpcode::GetElementPtr:
+ case GenericConstantExpressionOpcode::ShuffleVector:
+ case GenericConstantExpressionOpcode::InsertElement:
+ case GenericConstantExpressionOpcode::ExtractElement:
+ case GenericConstantExpressionOpcode::Splat:
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC assembly cannot lower this generic constant expression");
+ }
+ }
+ case GenericConstantKind::Invalid:
+ case GenericConstantKind::Floating:
+ case GenericConstantKind::Aggregate:
+ case GenericConstantKind::ByteString:
+ case GenericConstantKind::PointerAuth:
+ return makeUnexpected(ErrorCode::Unsupported,
+ "generic constant cannot be represented as an MC expression");
+ }
+ return makeUnexpected(ErrorCode::InternalError, "generic constant kind is invalid");
+}
+
+[[nodiscard]] Expected<void> emitPadding(std::uint64_t Size, mc::MCStreamer &Streamer) {
+ return Size == 0 ? Expected<void>{} : Streamer.emitZeroFill(Size);
+}
+
+[[nodiscard]] Expected<void> emitGenericConstantImpl(const GenericConstantData &Constant,
+ const TypeLayout &Layout,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer,
+ std::size_t Depth = 0) {
+ if (Depth > 1024) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "generic constant exceeds its emission nesting limit");
+ }
+ switch (Constant.Kind) {
+ case GenericConstantKind::Integer: {
+ if (!Constant.IntegerValue)
+ return makeUnexpected(ErrorCode::InternalError, "integer constant has no value");
+ const auto Bytes = bytesFromWords(Constant.IntegerValue->words(), Layout.Size);
+ return Streamer.emitBytes(Bytes);
+ }
+ case GenericConstantKind::Floating: {
+ if (!Constant.FloatingValue)
+ return makeUnexpected(ErrorCode::InternalError, "floating constant has no value");
+ const auto Bytes = bytesFromWords(Constant.FloatingValue->words(), Layout.Size);
+ return Streamer.emitBytes(Bytes);
+ }
+ case GenericConstantKind::ByteString: {
+ if (Constant.ByteValue.size() != Layout.Size) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "byte-string constant size does not match its RV64GC layout");
+ }
+ std::vector<std::uint8_t> Bytes;
+ Bytes.reserve(Constant.ByteValue.size());
+ for (const std::byte Byte : Constant.ByteValue)
+ Bytes.push_back(std::to_integer<std::uint8_t>(Byte));
+ return Streamer.emitBytes(Bytes);
+ }
+ case GenericConstantKind::NullPointer:
+ case GenericConstantKind::ZeroInitializer:
+ case GenericConstantKind::Undef:
+ case GenericConstantKind::Poison:
+ return Streamer.emitZeroFill(Layout.Size);
+ case GenericConstantKind::Aggregate: {
+ const GenericConstantType &Type = resolvedType(Constant.Type);
+ std::uint64_t Offset = 0;
+ for (std::size_t Index = 0; Index < Constant.Elements.size(); ++Index) {
+ auto ElementLayout = layoutType(Constant.Elements[Index].Type, Depth + 1);
+ if (!ElementLayout)
+ return std::unexpected(ElementLayout.error());
+ const std::uint64_t Alignment = Type.Kind == GenericConstantTypeKind::Struct && Type.Packed
+ ? 1
+ : ElementLayout->Alignment;
+ auto ElementOffset = alignTo(Offset, Alignment);
+ if (!ElementOffset)
+ return std::unexpected(ElementOffset.error());
+ auto Padding = emitPadding(*ElementOffset - Offset, Streamer);
+ if (!Padding)
+ return std::unexpected(Padding.error());
+ auto Element = emitGenericConstantImpl(Constant.Elements[Index], *ElementLayout, Context,
+ Streamer, Depth + 1);
+ if (!Element)
+ return std::unexpected(Element.error());
+ auto End = checkedAdd(*ElementOffset, ElementLayout->Size);
+ if (!End)
+ return std::unexpected(End.error());
+ Offset = *End;
+ if (Type.Kind == GenericConstantTypeKind::Array ||
+ Type.Kind == GenericConstantTypeKind::Vector) {
+ auto StrideEnd = alignTo(Offset, ElementLayout->Alignment);
+ if (!StrideEnd)
+ return std::unexpected(StrideEnd.error());
+ auto ElementPadding = emitPadding(*StrideEnd - Offset, Streamer);
+ if (!ElementPadding)
+ return std::unexpected(ElementPadding.error());
+ Offset = *StrideEnd;
+ }
+ }
+ if (Offset > Layout.Size) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "aggregate constant exceeds its RV64GC layout");
+ }
+ return emitPadding(Layout.Size - Offset, Streamer);
+ }
+ case GenericConstantKind::GlobalAddress:
+ case GenericConstantKind::BlockAddress:
+ case GenericConstantKind::DSOLocalEquivalent:
+ case GenericConstantKind::NoCFI:
+ case GenericConstantKind::Expression: {
+ if (Layout.Size != 1 && Layout.Size != 2 && Layout.Size != 4 && Layout.Size != 8) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "symbolic RV64GC constant has an unsupported storage size");
+ }
+ auto Expression = lowerConstantExpression(Constant, Context);
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ return Streamer.emitValue(std::move(*Expression), static_cast<std::uint8_t>(Layout.Size));
+ }
+ case GenericConstantKind::PointerAuth:
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC assembly does not support pointer-auth constants");
+ case GenericConstantKind::Invalid:
+ return makeUnexpected(ErrorCode::InternalError, "generic constant kind is invalid");
+ }
+ return makeUnexpected(ErrorCode::InternalError, "generic constant kind is invalid");
+}
+
+[[nodiscard]] Expected<void> emitRepeatedByte(std::uint8_t Byte, std::uint64_t Size,
+ mc::MCStreamer &Streamer) {
+ if (Size == 0)
+ return {};
+ if (Byte == 0)
+ return Streamer.emitZeroFill(Size);
+ constexpr std::uint64_t ChunkSize = 4096;
+ const std::size_t InitialSize = static_cast<std::size_t>(std::min(Size, ChunkSize));
+ const std::vector<std::uint8_t> Chunk(InitialSize, Byte);
+ while (Size != 0) {
+ const std::size_t EmittedSize = static_cast<std::size_t>(std::min(Size, ChunkSize));
+ auto Emitted = Streamer.emitBytes(std::span<const std::uint8_t>(Chunk).first(EmittedSize));
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ Size -= static_cast<std::uint64_t>(EmittedSize);
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> emitGlobalInitializerImpl(const GlobalInitializer &Initializer,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer,
+ std::size_t Depth = 0) {
+ if (Depth > 1024) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "global initializer exceeds its emission nesting limit");
+ }
+ switch (Initializer.kind()) {
+ case GlobalInitializer::Kind::Integer: {
+ const auto *Value = Initializer.getIf<IntegerImmediate>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "integer initializer has no value");
+ const std::uint64_t Size = (static_cast<std::uint64_t>(Value->bitWidth()) + 7U) / 8U;
+ return Streamer.emitBytes(bytesFromWords(Value->words(), Size));
+ }
+ case GlobalInitializer::Kind::Floating: {
+ const auto *Value = Initializer.getIf<FloatingPointBits>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "floating initializer has no value");
+ const std::uint64_t Size = (static_cast<std::uint64_t>(Value->bitWidth()) + 7U) / 8U;
+ return Streamer.emitBytes(bytesFromWords(Value->words(), Size));
+ }
+ case GlobalInitializer::Kind::Bytes: {
+ const auto *Value = Initializer.getIf<std::vector<std::byte>>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "byte initializer has no value");
+ std::vector<std::uint8_t> Bytes;
+ Bytes.reserve(Value->size());
+ for (const std::byte Byte : *Value)
+ Bytes.push_back(std::to_integer<std::uint8_t>(Byte));
+ return Streamer.emitBytes(Bytes);
+ }
+ case GlobalInitializer::Kind::ZeroFill: {
+ const auto *Value = Initializer.getIf<GlobalZeroFill>();
+ return Value == nullptr
+ ? makeUnexpected(ErrorCode::InternalError, "zero-fill initializer has no value")
+ : Streamer.emitZeroFill(Value->Size);
+ }
+ case GlobalInitializer::Kind::Repeated: {
+ const auto *Value = Initializer.getIf<GlobalRepeatedInitializer>();
+ if (Value == nullptr || !Value->Value)
+ return makeUnexpected(ErrorCode::InternalError, "repeated initializer has no value");
+ for (std::uint64_t Index = 0; Index < Value->Count; ++Index) {
+ auto Emitted = emitGlobalInitializerImpl(*Value->Value, Context, Streamer, Depth + 1);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ }
+ return {};
+ }
+ case GlobalInitializer::Kind::Aggregate: {
+ const auto *Value = Initializer.getIf<GlobalAggregateInitializer>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "aggregate initializer has no value");
+ for (const GlobalInitializerRef &Element : Value->Elements) {
+ if (!Element)
+ return makeUnexpected(ErrorCode::InternalError, "aggregate initializer is null");
+ auto Emitted = emitGlobalInitializerImpl(*Element, Context, Streamer, Depth + 1);
+ if (!Emitted)
+ return std::unexpected(Emitted.error());
+ }
+ return {};
+ }
+ case GlobalInitializer::Kind::SymbolAddress: {
+ const auto *Value = Initializer.getIf<GlobalSymbolAddress>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "symbol initializer has no value");
+ auto Symbol = Context.symbol(Value->Symbol, mc::MCSymbolKind::Object);
+ 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), Value->Addend);
+ if (!WithAddend)
+ return std::unexpected(WithAddend.error());
+ auto Lowered = applyTargetFlags(std::move(*WithAddend), Value->TargetFlags);
+ if (!Lowered)
+ return std::unexpected(Lowered.error());
+ return Streamer.emitValue(std::move(*Lowered), Value->Size);
+ }
+ case GlobalInitializer::Kind::SymbolDifference: {
+ const auto *Value = Initializer.getIf<GlobalSymbolDifference>();
+ if (Value == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "symbol difference has no value");
+ auto MinuendSymbol = Context.symbol(Value->Minuend);
+ if (!MinuendSymbol)
+ return std::unexpected(MinuendSymbol.error());
+ auto SubtrahendSymbol = Context.symbol(Value->Subtrahend);
+ if (!SubtrahendSymbol)
+ return std::unexpected(SubtrahendSymbol.error());
+ auto Minuend = mc::MCExpr::symbol(std::move(*MinuendSymbol));
+ if (!Minuend)
+ return std::unexpected(Minuend.error());
+ auto Subtrahend = mc::MCExpr::symbol(std::move(*SubtrahendSymbol));
+ if (!Subtrahend)
+ return std::unexpected(Subtrahend.error());
+ auto Difference = mc::MCExpr::binary(mc::MCBinaryOperator::Subtract, std::move(*Minuend),
+ std::move(*Subtrahend));
+ if (!Difference)
+ return std::unexpected(Difference.error());
+ auto WithAddend = addOffset(std::move(*Difference), Value->Addend);
+ if (!WithAddend)
+ return std::unexpected(WithAddend.error());
+ return Streamer.emitValue(std::move(*WithAddend), Value->Size);
+ }
+ case GlobalInitializer::Kind::Target: {
+ 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);
+ }
+ case GlobalInitializer::Kind::AlignmentPadding: {
+ const auto *Value = Initializer.getIf<GlobalAlignmentPadding>();
+ return Value == nullptr
+ ? makeUnexpected(ErrorCode::InternalError, "padding initializer has no value")
+ : emitRepeatedByte(Value->Fill, Value->Size, Streamer);
+ }
+ case GlobalInitializer::Kind::UndefinedStorage: {
+ const auto *Value = Initializer.getIf<GlobalUndefinedStorage>();
+ return Value == nullptr
+ ? makeUnexpected(ErrorCode::InternalError, "undefined initializer has no value")
+ : Streamer.emitZeroFill(Value->Size);
+ }
+ case GlobalInitializer::Kind::Invalid:
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "global initializer is invalid");
+ }
+ return makeUnexpected(ErrorCode::InternalError, "global initializer kind is invalid");
+}
+
+[[nodiscard]] bool initializerIsZero(const GlobalInitializer &Initializer, std::size_t Depth = 0) {
+ if (Depth > 1024)
+ return false;
+ switch (Initializer.kind()) {
+ case GlobalInitializer::Kind::Integer: {
+ const auto *Value = Initializer.getIf<IntegerImmediate>();
+ return Value != nullptr &&
+ std::ranges::all_of(Value->words(), [](std::uint64_t Word) { return Word == 0; });
+ }
+ case GlobalInitializer::Kind::Floating: {
+ const auto *Value = Initializer.getIf<FloatingPointBits>();
+ return Value != nullptr &&
+ std::ranges::all_of(Value->words(), [](std::uint64_t Word) { return Word == 0; });
+ }
+ case GlobalInitializer::Kind::Bytes: {
+ const auto *Value = Initializer.getIf<std::vector<std::byte>>();
+ return Value != nullptr &&
+ std::ranges::all_of(*Value, [](std::byte Byte) { return Byte == std::byte{0}; });
+ }
+ case GlobalInitializer::Kind::ZeroFill:
+ case GlobalInitializer::Kind::UndefinedStorage:
+ return true;
+ case GlobalInitializer::Kind::Repeated: {
+ const auto *Value = Initializer.getIf<GlobalRepeatedInitializer>();
+ return Value != nullptr && Value->Value && initializerIsZero(*Value->Value, Depth + 1);
+ }
+ case GlobalInitializer::Kind::Aggregate: {
+ const auto *Value = Initializer.getIf<GlobalAggregateInitializer>();
+ return Value != nullptr && std::ranges::all_of(Value->Elements, [Depth](const auto &Element) {
+ return Element && initializerIsZero(*Element, Depth + 1);
+ });
+ }
+ case GlobalInitializer::Kind::AlignmentPadding: {
+ const auto *Value = Initializer.getIf<GlobalAlignmentPadding>();
+ return Value != nullptr && Value->Fill == 0;
+ }
+ case GlobalInitializer::Kind::Target: {
+ const auto *Value = Initializer.getIf<TargetConstant>();
+ if (Value == nullptr)
+ return false;
+ auto Emission = Value->emit();
+ return Emission && Emission->Fixups.empty() &&
+ std::ranges::all_of(Emission->Bytes,
+ [](std::byte Byte) { return Byte == std::byte{0}; });
+ }
+ case GlobalInitializer::Kind::Invalid:
+ case GlobalInitializer::Kind::SymbolAddress:
+ case GlobalInitializer::Kind::SymbolDifference:
+ return false;
+ }
+ return false;
+}
+
+[[nodiscard]] bool initializerHasRelocation(const GlobalInitializer &Initializer,
+ std::size_t Depth = 0) {
+ if (Depth > 1024)
+ return true;
+ switch (Initializer.kind()) {
+ case GlobalInitializer::Kind::SymbolAddress:
+ case GlobalInitializer::Kind::SymbolDifference:
+ return true;
+ case GlobalInitializer::Kind::Repeated: {
+ const auto *Value = Initializer.getIf<GlobalRepeatedInitializer>();
+ return Value == nullptr || !Value->Value || initializerHasRelocation(*Value->Value, Depth + 1);
+ }
+ case GlobalInitializer::Kind::Aggregate: {
+ const auto *Value = Initializer.getIf<GlobalAggregateInitializer>();
+ return Value == nullptr || std::ranges::any_of(Value->Elements, [Depth](const auto &Element) {
+ return !Element || initializerHasRelocation(*Element, Depth + 1);
+ });
+ }
+ case GlobalInitializer::Kind::Target: {
+ const auto *Value = Initializer.getIf<TargetConstant>();
+ if (Value == nullptr)
+ return true;
+ auto Emission = Value->emit();
+ return !Emission || !Emission->Fixups.empty();
+ }
+ case GlobalInitializer::Kind::Invalid:
+ return true;
+ case GlobalInitializer::Kind::Integer:
+ case GlobalInitializer::Kind::Floating:
+ case GlobalInitializer::Kind::Bytes:
+ case GlobalInitializer::Kind::ZeroFill:
+ case GlobalInitializer::Kind::AlignmentPadding:
+ case GlobalInitializer::Kind::UndefinedStorage:
+ return false;
+ }
+ return true;
+}
+
+[[nodiscard]] mc::MCSectionDesc textSectionDescription() {
+ return mc::MCSectionDesc{
+ .Name = ".text",
+ .Kind = mc::MCSectionKind::Text,
+ .Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Executable},
+ .Alignment = 4,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ };
+}
+
+[[nodiscard]] mc::MCSectionDesc readOnlySectionDescription() {
+ return mc::MCSectionDesc{
+ .Name = ".rodata",
+ .Kind = mc::MCSectionKind::ReadOnly,
+ .Flags = {mc::MCSectionFlag::Alloc},
+ .Alignment = 1,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ };
+}
+
+} // namespace
+
+Expected<mc::MCInst> RISCVMCTargetLowering::lowerInstruction(const MachineInstr &Instruction,
+ MachineMCContext &Context) const {
+ const InstructionDescriptor *Descriptor = Target->instructionDescriptor(Instruction.opcode());
+ const AssemblyInstructionInfo *Assembly = assemblyInstruction(Instruction.opcode());
+ if (Descriptor == nullptr || Assembly == nullptr || Assembly->Mnemonic.empty()) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V instruction has no MC lowering description");
+ }
+ const AssemblyFormatInfo *Format = assemblyFormat(Assembly->Format);
+ if (Format == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "RISC-V assembly format is missing");
+ const auto RequiredOperands = formatOperandCount(Format->Name);
+ if (!RequiredOperands) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V instruction uses an unsupported assembly format");
+ }
+
+ mc::MCInst Result(mc::MCOpcode::fromValue(Instruction.opcode().value()));
+ Result.setFlags(Instruction.flags().rawValue());
+ Result.setSourceIdentity(Instruction.stableID().value());
+ Result.reserveOperands(*RequiredOperands);
+ for (const MachineOperand &Operand : Instruction.operands()) {
+ if (Result.operands().size() == *RequiredOperands)
+ break;
+ if (Operand.isRegister() && Operand.isImplicit())
+ continue;
+ if (Operand.kind() == MachineOperand::Kind::RegisterMask ||
+ Operand.kind() == MachineOperand::Kind::LiveOutRegisterMask) {
+ continue;
+ }
+ auto Lowered = lowerOperand(Operand, Instruction.function(), Context);
+ if (!Lowered)
+ return std::unexpected(Lowered.error());
+ Result.addOperand(std::move(*Lowered));
+ }
+ if (Result.operands().size() != *RequiredOperands) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "RISC-V instruction has too few encoding operands");
+ }
+ return Result;
+}
+
+Expected<void> RISCVMCTargetLowering::emitTerminalInstruction(const MachineInstr &Instruction,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const {
+ if (Instruction.opcode() != opcode(GenericOpcode::INLINEASM) &&
+ Instruction.opcode() != opcode(GenericOpcode::INLINEASM_BR)) {
+ return TargetMCInstLowering::emitTerminalInstruction(Instruction, Context, Streamer);
+ }
+ for (const MachineOperand &Operand : Instruction.operands()) {
+ if (Operand.kind() == MachineOperand::Kind::RegisterMask ||
+ Operand.kind() == MachineOperand::Kind::LiveOutRegisterMask ||
+ (Operand.isRegister() && Operand.isImplicit())) {
+ continue;
+ }
+ const auto *Text = Operand.getIf<MCSymbolOperand>();
+ if (Text == nullptr || Text->Offset != 0 || Operand.targetFlags() != 0) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V inline assembly requires a literal MC-symbol text operand");
+ }
+ return Streamer.emitInlineAssembly(Text->Name);
+ }
+ return {};
+}
+
+Expected<mc::MCSectionDesc> RISCVMCTargetLowering::textSection(const MachineFunction &) const {
+ return textSectionDescription();
+}
+
+Expected<mc::MCSectionDesc> RISCVMCTargetLowering::globalSection(const GlobalObjectDesc &Object,
+ MachineMCContext &Context) const {
+ if (Object.AddressSpace != 0) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC assembly does not support nonzero global address spaces");
+ }
+ if (Object.TargetFlags != 0 || !Object.TargetAttributes.empty()) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC assembly does not recognize this global target state");
+ }
+ const bool IsTLS = Object.TLSModel != GlobalTLSModel::None;
+ const bool IsZero = Object.Initializer && initializerIsZero(*Object.Initializer);
+ const bool HasRelocation = Object.Initializer && initializerHasRelocation(*Object.Initializer);
+
+ mc::MCSectionDesc Result;
+ Result.Alignment = Object.Align.bytes();
+ if (IsTLS) {
+ Result.Name = IsZero ? ".tbss" : ".tdata";
+ Result.Kind = IsZero ? mc::MCSectionKind::TLSBSS : mc::MCSectionKind::TLSData;
+ Result.Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Writable, mc::MCSectionFlag::TLS};
+ } else if (Object.IsConstant && HasRelocation) {
+ Result.Name = ".data.rel.ro";
+ Result.Kind = mc::MCSectionKind::Data;
+ Result.Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Writable};
+ } else if (Object.IsConstant) {
+ Result = readOnlySectionDescription();
+ Result.Alignment = Object.Align.bytes();
+ } else if (IsZero) {
+ Result.Name = ".bss";
+ Result.Kind = mc::MCSectionKind::BSS;
+ Result.Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Writable};
+ } else {
+ Result.Name = ".data";
+ Result.Kind = mc::MCSectionKind::Data;
+ Result.Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Writable};
+ }
+
+ if (Object.Section)
+ Result.Name = *Object.Section;
+ if (Object.COMDAT) {
+ auto Group = Context.symbol(*Object.COMDAT);
+ if (!Group)
+ return std::unexpected(Group.error());
+ Result.Flags.set(mc::MCSectionFlag::Group);
+ Result.Group = std::move(*Group);
+ if (!Object.Section)
+ Result.Name += "." + Object.Symbol;
+ }
+ return Result;
+}
+
+Expected<mc::MCSectionDesc>
+RISCVMCTargetLowering::constantPoolSection(const MachineFunction &Function,
+ const MachineConstantPoolEntry &Entry) const {
+ const MachineConstantPoolEntryDesc *Description = Entry.description();
+ if (Description == nullptr)
+ return makeUnexpected(ErrorCode::InvalidArgument, "constant-pool entry is not live");
+ if (Description->Placement.Section == ConstantPoolSectionKind::TextAdjacent)
+ return textSectionDescription();
+
+ mc::MCSectionDesc Result = readOnlySectionDescription();
+ if (Description->Placement.Section == ConstantPoolSectionKind::Data) {
+ Result.Name = ".data";
+ Result.Kind = mc::MCSectionKind::Data;
+ Result.Flags.set(mc::MCSectionFlag::Writable);
+ } else if (Description->Placement.Section == ConstantPoolSectionKind::Target) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC constant pool has an unknown target section");
+ } else if (Description->Placement.Section == ConstantPoolSectionKind::MergeableReadOnly) {
+ Result.Name = ".rodata.cst" + std::to_string(Description->Size);
+ Result.Flags.set(mc::MCSectionFlag::Merge);
+ Result.EntrySize = Description->Size;
+ }
+ if (Description->Placement.UniqueSection) {
+ Result.Name += "." + std::to_string(Function.stableID().value()) + "." +
+ std::to_string(Entry.index().value());
+ Result.UniqueID = static_cast<std::uint64_t>(Entry.index().value()) + 1U;
+ }
+ Result.Alignment = Description->Align.bytes();
+ return Result;
+}
+
+Expected<mc::MCSectionDesc>
+RISCVMCTargetLowering::jumpTableSection(const MachineFunction &Function,
+ const MachineJumpTableEntry &Entry) const {
+ const MachineJumpTableDesc *Description = Entry.description();
+ if (Description == nullptr)
+ return makeUnexpected(ErrorCode::InvalidArgument, "jump-table entry is not live");
+ mc::MCSectionDesc Result = Description->Placement.InTextSection ? textSectionDescription()
+ : readOnlySectionDescription();
+ if (Description->Placement.UniqueSection) {
+ Result.Name += "." + std::to_string(Function.stableID().value()) + ".jt." +
+ std::to_string(Entry.index().value());
+ Result.UniqueID = static_cast<std::uint64_t>(Entry.index().value()) + 1U;
+ }
+ Result.Alignment = Description->Align.bytes();
+ return Result;
+}
+
+Expected<void> RISCVMCTargetLowering::emitFileHeader(MachineMCContext &,
+ mc::MCStreamer &Streamer) const {
+ auto Attribute = Streamer.emitDirective("attribute", "arch, \"rv64gc\"");
+ if (!Attribute)
+ return std::unexpected(Attribute.error());
+ return Streamer.emitDirective("attribute", "stack_align, 16");
+}
+
+Expected<void> RISCVMCTargetLowering::emitFileTrailer(MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const {
+ mc::MCSectionDesc Note{
+ .Name = ".note.GNU-stack",
+ .Kind = mc::MCSectionKind::Custom,
+ .Flags = {},
+ .Alignment = 1,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ };
+ auto Section = Context.context().getOrCreateSection(std::move(Note));
+ if (!Section)
+ return std::unexpected(Section.error());
+ return Streamer.switchSection(std::move(*Section));
+}
+
+Expected<void> RISCVMCTargetLowering::emitGenericConstant(const GenericConstantData &Constant,
+ std::uint64_t Size,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const {
+ auto Verification = Constant.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ auto Layout = layoutType(Constant.Type);
+ if (!Layout)
+ return std::unexpected(Layout.error());
+ if (Layout->Size != Size) {
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "generic constant size does not match the RV64GC data layout");
+ }
+ return emitGenericConstantImpl(Constant, *Layout, Context, Streamer);
+}
+
+Expected<void> RISCVMCTargetLowering::emitGlobalInitializer(const GlobalInitializer &Initializer,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const {
+ auto Verification = Initializer.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return emitGlobalInitializerImpl(Initializer, Context, Streamer);
+}
+
+Expected<void> RISCVMCTargetLowering::emitTargetConstant(const TargetConstant &Constant,
+ MachineMCContext &,
+ 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);
+}
+
+Expected<void> RISCVMCTargetLowering::emitTargetJumpTable(const MachineFunction &Function,
+ const MachineJumpTableEntry &Entry,
+ MachineMCContext &Context,
+ mc::MCStreamer &Streamer) const {
+ const MachineJumpTableDesc *Description = Entry.description();
+ if (Description == nullptr)
+ return makeUnexpected(ErrorCode::InvalidArgument, "jump-table entry is not live");
+ if (Description->Encoding == JumpTableEntryEncoding::Target) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RV64GC jump table has an unknown target encoding");
+ }
+
+ mc::MCSymbolRef BaseSymbol;
+ if (Description->Encoding == JumpTableEntryEncoding::GPRelative32BlockAddress ||
+ Description->Encoding == JumpTableEntryEncoding::GPRelative64BlockAddress) {
+ auto GlobalPointer = Context.symbol("__global_pointer$");
+ if (!GlobalPointer)
+ return std::unexpected(GlobalPointer.error());
+ BaseSymbol = std::move(*GlobalPointer);
+ } else if (Description->Encoding == JumpTableEntryEncoding::Custom32) {
+ BaseSymbol = Context.jumpTableSymbol(Function, Entry.index());
+ }
+
+ for (const MachineBasicBlock *TargetBlock : Description->Targets) {
+ if (TargetBlock == nullptr)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "jump table has a null target");
+ const mc::MCSymbolRef TargetSymbol = Context.blockSymbol(*TargetBlock);
+ if (!TargetSymbol)
+ return makeUnexpected(ErrorCode::AssemblyLoweringFailed,
+ "jump-table target has no MC symbol");
+ auto Expression = mc::MCExpr::symbol(TargetSymbol);
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ if (BaseSymbol) {
+ auto Base = mc::MCExpr::symbol(BaseSymbol);
+ if (!Base)
+ return std::unexpected(Base.error());
+ Expression = mc::MCExpr::binary(mc::MCBinaryOperator::Subtract, std::move(*Expression),
+ std::move(*Base));
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ }
+ auto Value = Streamer.emitValue(std::move(*Expression), Description->EntrySize);
+ if (!Value)
+ return std::unexpected(Value.error());
+ }
+ return {};
+}
+
+} // namespace rcc::riscv
diff --git a/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp
new file mode 100644
index 0000000..ff339fa
--- /dev/null
+++ b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineOptimizationPipeline.h"
+#include "rcc/CodeGen/MachinePass.h"
+#include "rcc/CodeGen/MachinePipelinePasses.h"
+#include "rcc/CodeGen/MachineSSAExit.h"
+#include "rcc/CodeGen/RegAllocPBQP.h"
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Support/TextDiagnostic.h"
+#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h"
+#include "rcc/Target/RISCV/RISCVCodeGen.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace {
+
+struct CommandLine final {
+ std::string InputPath;
+};
+
+[[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";
+ return false;
+ }
+ Result.InputPath = Arguments[++Index];
+ }
+ if (Result.InputPath.empty()) {
+ std::cerr << "usage: rcc-backend-asm --input <mir-file|->\n";
+ return false;
+ }
+ return true;
+}
+
+[[nodiscard]] bool readInput(const CommandLine &Command, std::string &Contents) {
+ if (Command.InputPath == "-") {
+ Contents.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
+ return true;
+ }
+ std::ifstream Input(Command.InputPath, std::ios::binary);
+ if (!Input) {
+ std::cerr << "rcc-backend-asm: cannot open input '" << Command.InputPath << "'\n";
+ return false;
+ }
+ Contents.assign(std::istreambuf_iterator<char>(Input), std::istreambuf_iterator<char>());
+ return true;
+}
+
+template <typename PassT, typename... ArgumentT>
+[[nodiscard]] rcc::Expected<void> addPass(rcc::MachinePassManager &Pipeline,
+ ArgumentT &&...Arguments) {
+ return Pipeline.addPass(std::make_unique<PassT>(std::forward<ArgumentT>(Arguments)...));
+}
+
+[[nodiscard]] rcc::Expected<void> runBackendPipeline(rcc::MachineFunction &Function,
+ const rcc::TargetDescription &Target) {
+ rcc::MachinePassManager Pipeline;
+ const rcc::MachineOptimizationPipelineTargetInfo TargetInfo =
+ rcc::riscv::optimizationPipelineInfo();
+
+ if (!Function.properties().has(rcc::MachineFunctionProperty::Legalized)) {
+ auto Added = addPass<rcc::LegalizerPass>(Pipeline, rcc::riscv::legalizerInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::RegBankSelected)) {
+ auto Added = addPass<rcc::RegisterBankSelectPass>(Pipeline, rcc::riscv::registerBankInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::Selected)) {
+ auto Added = addPass<rcc::InstructionSelectPass>(Pipeline, rcc::riscv::instructionSelector());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::NoPHIs)) {
+ auto Added = rcc::addSelectedSSAOptimizationPipeline(Pipeline, TargetInfo);
+ if (!Added)
+ return std::unexpected(Added.error());
+ Added = addPass<rcc::PHIEliminationPass>(Pipeline, rcc::riscv::ssaInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::TiedOpsRewritten)) {
+ auto Added = addPass<rcc::TwoAddressRewritePass>(Pipeline, rcc::riscv::ssaInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::NoVRegs)) {
+ auto Added = rcc::addPreRegisterAllocationOptimizationPipeline(Pipeline, TargetInfo);
+ if (!Added)
+ return std::unexpected(Added.error());
+ Added = addPass<rcc::PBQPAllocatorPass>(Pipeline, rcc::riscv::registerAllocationInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::NoExecutablePseudos)) {
+ auto Added = rcc::addPostRegisterAllocationPipeline(Pipeline, TargetInfo);
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+
+ rcc::MachineAnalysisManager Analyses;
+ auto Registered = rcc::registerMachineOptimizationAnalyses(Analyses);
+ if (!Registered)
+ return std::unexpected(Registered.error());
+ return Pipeline.run(Function, Target, Analyses);
+}
+
+} // namespace
+
+int main(int ArgumentCount, char *Arguments[]) {
+ CommandLine Command;
+ if (!parseCommandLine(ArgumentCount, Arguments, Command))
+ return 1;
+
+ std::string SourceText;
+ if (!readInput(Command, SourceText))
+ return 1;
+
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target) {
+ std::cerr << "rcc-backend-asm: cannot create RV64GC target: " << Target.error().message()
+ << '\n';
+ return 1;
+ }
+ rcc::mir::MIRDocumentParserOptions Options;
+ Options.DataLayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128";
+ Options.DefaultPointerSizeInBits = 64;
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, Command.InputPath, std::move(SourceText), Options);
+ if (!Parsed.succeeded()) {
+ for (const rcc::TextDiagnostic &Diagnostic : Parsed.diagnostics())
+ std::cerr << rcc::formatTextDiagnostic(Diagnostic, Parsed.source()) << '\n';
+ return 1;
+ }
+
+ rcc::CompilationUnit &Unit = Parsed.document()->unit();
+ for (std::size_t Index = 0; Index < Unit.functionCount(); ++Index) {
+ rcc::MachineFunction *Function = Unit.functionAt(Index);
+ if (Function == nullptr) {
+ std::cerr << "rcc-backend-asm: parsed document contains a null function\n";
+ return 1;
+ }
+ auto Run = runBackendPipeline(*Function, *Target);
+ if (!Run) {
+ std::cerr << "rcc-backend-asm: backend pipeline failed for '" << Function->name()
+ << "': " << Run.error().message() << '\n';
+ return 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;
+}
diff --git a/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake b/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake
new file mode 100644
index 0000000..baeedcf
--- /dev/null
+++ b/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+if(NOT DEFINED EMITTER OR NOT DEFINED INPUT OR NOT DEFINED OUTPUT)
+ message(FATAL_ERROR "assembly pipeline test is missing a required path")
+endif()
+
+execute_process(
+ COMMAND "${EMITTER}" --input "${INPUT}"
+ RESULT_VARIABLE FirstResult
+ OUTPUT_VARIABLE FirstAssembly
+ ERROR_VARIABLE FirstDiagnostics
+)
+if(NOT FirstResult EQUAL 0)
+ message(FATAL_ERROR "assembly pipeline failed: ${FirstDiagnostics}")
+endif()
+if(NOT FirstAssembly MATCHES "\\.attribute arch, \"rv64gc\"" OR
+ NOT FirstAssembly MATCHES "jalr x0, x1, 0")
+ message(FATAL_ERROR "assembly pipeline output is missing required RV64GC content")
+endif()
+if(INPUT MATCHES "RISCVDataPipeline" AND
+ (NOT FirstAssembly MATCHES "\\.LCPI[0-9]+_0:" OR
+ NOT FirstAssembly MATCHES "\\.LJTI[0-9]+_0:"))
+ message(FATAL_ERROR "assembly pipeline did not emit its constant pool and jump table")
+endif()
+
+execute_process(
+ COMMAND "${EMITTER}" --input "${INPUT}"
+ RESULT_VARIABLE SecondResult
+ OUTPUT_VARIABLE SecondAssembly
+ ERROR_VARIABLE SecondDiagnostics
+)
+if(NOT SecondResult EQUAL 0)
+ message(FATAL_ERROR "repeated assembly pipeline failed: ${SecondDiagnostics}")
+endif()
+if(NOT FirstAssembly STREQUAL SecondAssembly)
+ message(FATAL_ERROR "assembly pipeline output is not deterministic")
+endif()
+
+if(DEFINED LLVM_MC AND NOT LLVM_MC STREQUAL "LLVM_MC-NOTFOUND" AND
+ NOT LLVM_MC STREQUAL "")
+ file(WRITE "${OUTPUT}" "${FirstAssembly}")
+ execute_process(
+ COMMAND "${LLVM_MC}" -triple=riscv64-unknown-linux-gnu -filetype=null "${OUTPUT}"
+ RESULT_VARIABLE AssemblerResult
+ OUTPUT_VARIABLE AssemblerOutput
+ ERROR_VARIABLE AssemblerDiagnostics
+ )
+ file(REMOVE "${OUTPUT}")
+ if(NOT AssemblerResult EQUAL 0)
+ message(FATAL_ERROR "LLVM MC rejected generated assembly: ${AssemblerDiagnostics}")
+ endif()
+endif()
diff --git a/backend/tools/AssemblyEmitter/CMakeLists.txt b/backend/tools/AssemblyEmitter/CMakeLists.txt
new file mode 100644
index 0000000..a262983
--- /dev/null
+++ b/backend/tools/AssemblyEmitter/CMakeLists.txt
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+add_executable(RCCBackendAssemblyEmitter AssemblyEmitterMain.cpp)
+rcc_backend_configure_target(RCCBackendAssemblyEmitter)
+target_link_libraries(
+ RCCBackendAssemblyEmitter
+ PRIVATE
+ RCCBackend::MIR
+ RCCBackend::RISCV
+)
+
+set_target_properties(
+ RCCBackendAssemblyEmitter
+ PROPERTIES
+ OUTPUT_NAME rcc-backend-asm
+)
+
+install(
+ TARGETS RCCBackendAssemblyEmitter
+ EXPORT RCCBackendTargets
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+)
+
+find_program(RCCBackendLLVMMC NAMES llvm-mc-20 llvm-mc)
+foreach(InputName IN ITEMS RISCVGenericPipeline RISCVWideConstantPipeline RISCVDataPipeline)
+ add_test(
+ NAME RCCBackend.AssemblyEmitter.${InputName}
+ COMMAND
+ "${CMAKE_COMMAND}"
+ "-DEMITTER=$<TARGET_FILE:RCCBackendAssemblyEmitter>"
+ "-DINPUT=${RCC_BACKEND_SOURCE_DIR}/unittests/Target/Inputs/${InputName}.mir"
+ "-DOUTPUT=${CMAKE_CURRENT_BINARY_DIR}/${InputName}.s"
+ "-DLLVM_MC=${RCCBackendLLVMMC}"
+ -P "${CMAKE_CURRENT_SOURCE_DIR}/AssemblyPipelineTest.cmake"
+ )
+endforeach()
+
+rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/AssemblyEmitterMain.cpp")
+rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/AssemblyEmitterMain.cpp")
diff --git a/backend/tools/CMakeLists.txt b/backend/tools/CMakeLists.txt
index 67d7273..403f4a9 100644
--- a/backend/tools/CMakeLists.txt
+++ b/backend/tools/CMakeLists.txt
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-3.0-or-later
add_subdirectory(DescriptionGenerator)
+add_subdirectory(AssemblyEmitter)
diff --git a/backend/unittests/CMakeLists.txt b/backend/unittests/CMakeLists.txt
index b93373d..d1ef6fa 100644
--- a/backend/unittests/CMakeLists.txt
+++ b/backend/unittests/CMakeLists.txt
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
add_subdirectory(Support)
+add_subdirectory(MC)
add_subdirectory(Target)
add_subdirectory(CodeGen)
add_subdirectory(MIR)
diff --git a/backend/unittests/CodeGen/CompilationUnitTest.cpp b/backend/unittests/CodeGen/CompilationUnitTest.cpp
index 39cd175..a31c9a8 100644
--- a/backend/unittests/CodeGen/CompilationUnitTest.cpp
+++ b/backend/unittests/CodeGen/CompilationUnitTest.cpp
@@ -4,6 +4,7 @@
#include "rcc/CodeGen/MachineFunction.h"
+#include <cstddef>
#include <iostream>
#include <utility>
@@ -43,14 +44,53 @@ int main() {
if (Unit.functionAt(2) != nullptr)
return fail("CompilationUnit must reject an out-of-range ordered lookup");
+ auto Zero = rcc::GlobalInitializer::zeroFill(4);
+ if (!Zero)
+ return fail("Global initializer creation must succeed");
+ rcc::GlobalObjectDesc Object;
+ Object.Symbol = "global";
+ Object.ObjectType = rcc::GenericConstantType::byte();
+ Object.Size = 4;
+ Object.Initializer.emplace(std::move(*Zero));
+ auto Global = Unit.addGlobalObject(std::move(Object));
+ if (!Global || Unit.globalObjectCount() != 1 || Unit.globalObject("global") != *Global ||
+ Unit.globalObjectAt(0) != *Global || Unit.globalObjectAt(1) != nullptr)
+ return fail("CompilationUnit must own and index global objects");
+
+ auto ConflictingFunction = Unit.createMachineFunction("global");
+ if (ConflictingFunction || Unit.functionCount() != 2)
+ return fail("CompilationUnit must reject function/global symbol conflicts atomically");
+
+ rcc::GlobalObjectDesc ConflictingGlobal;
+ ConflictingGlobal.Symbol = "first";
+ ConflictingGlobal.ObjectType = rcc::GenericConstantType::byte();
+ ConflictingGlobal.Size = 1;
+ auto Byte = rcc::GlobalInitializer::bytes({std::byte{1}});
+ if (!Byte)
+ return fail("Byte initializer creation must succeed");
+ ConflictingGlobal.Initializer.emplace(std::move(*Byte));
+ if (Unit.addGlobalObject(std::move(ConflictingGlobal)) || Unit.globalObjectCount() != 1)
+ return fail("CompilationUnit must reject global/function symbol conflicts atomically");
+
+ auto MismatchedBytes = rcc::GlobalInitializer::bytes({std::byte{1}, std::byte{2}});
+ if (!MismatchedBytes)
+ return fail("Mismatched initializer construction must succeed before object verification");
+ rcc::GlobalObjectDesc MismatchedObject;
+ MismatchedObject.Symbol = "mismatched";
+ MismatchedObject.ObjectType = rcc::GenericConstantType::byte();
+ MismatchedObject.Size = 1;
+ MismatchedObject.Initializer.emplace(std::move(*MismatchedBytes));
+ if (Unit.addGlobalObject(std::move(MismatchedObject)) || Unit.globalObjectCount() != 1)
+ return fail("CompilationUnit must reject mismatched global initializer sizes atomically");
+
auto Verification = Unit.verify();
if (!Verification)
return fail("A valid CompilationUnit must pass verification");
rcc::CompilationUnit Moved = std::move(Unit);
if (Moved.functionCount() != 2 || Moved.function("first") != *First ||
- Moved.function("second") != *Second)
- return fail("Moving a CompilationUnit must preserve owned function addresses and indices");
+ Moved.function("second") != *Second || Moved.globalObject("global") != *Global)
+ return fail("Moving a CompilationUnit must preserve owned object addresses and indices");
Verification = Moved.verify();
if (!Verification)
return fail("A moved CompilationUnit must pass verification");
diff --git a/backend/unittests/MC/CMakeLists.txt b/backend/unittests/MC/CMakeLists.txt
new file mode 100644
index 0000000..d62bb1d
--- /dev/null
+++ b/backend/unittests/MC/CMakeLists.txt
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+add_executable(RCCBackendMCTests MCTest.cpp)
+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")
diff --git a/backend/unittests/MC/MCTest.cpp b/backend/unittests/MC/MCTest.cpp
new file mode 100644
index 0000000..1876fdb
--- /dev/null
+++ b/backend/unittests/MC/MCTest.cpp
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MC/MCCFI.h"
+#include "rcc/MC/MCContext.h"
+#include "rcc/MC/MCExpr.h"
+#include "rcc/MC/MCInst.h"
+#include "rcc/MC/MCInstPrinter.h"
+#include "rcc/MC/MCSection.h"
+#include "rcc/MC/MCStreamer.h"
+
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace {
+
+class TestPrinter final : public rcc::mc::MCInstPrinter {
+public:
+ [[nodiscard]] rcc::Expected<std::string>
+ printInstruction(const rcc::mc::MCInst &Instruction) const override {
+ if (Instruction.opcode().value() != 7 || Instruction.operands().size() != 2)
+ return rcc::makeUnexpected(rcc::ErrorCode::AssemblyLoweringFailed,
+ "unexpected test instruction");
+ const auto *Register = Instruction.operands()[0].registerValue();
+ const auto *Immediate = Instruction.operands()[1].immediateValue();
+ if (Register == nullptr || Immediate == nullptr)
+ return rcc::makeUnexpected(rcc::ErrorCode::AssemblyLoweringFailed,
+ "unexpected test operands");
+ return "probe r" + std::to_string(Register->value()) + ", " + std::to_string(*Immediate);
+ }
+
+ [[nodiscard]] rcc::Expected<std::string>
+ printTargetExpression(std::uint32_t Kind, std::string_view Operand) const override {
+ if (Kind != 3)
+ return rcc::makeUnexpected(rcc::ErrorCode::AssemblyLoweringFailed, "unknown test expression");
+ return "%test(" + std::string(Operand) + ")";
+ }
+};
+
+int fail(std::string_view Message) {
+ std::cerr << Message << '\n';
+ return 1;
+}
+
+[[nodiscard]] bool contains(std::string_view Text, std::string_view Needle) {
+ return Text.find(Needle) != std::string_view::npos;
+}
+
+} // namespace
+
+int main() {
+ rcc::mc::MCContext Context;
+ auto Symbol = Context.getOrCreateSymbol("object", rcc::mc::MCSymbolKind::Object);
+ auto SameSymbol = Context.getOrCreateSymbol("object", rcc::mc::MCSymbolKind::Generic);
+ if (!Symbol || !SameSymbol || *Symbol != *SameSymbol || Context.symbol("object") != *Symbol)
+ return fail("MCContext must uniquify compatible symbols");
+ if (Context.getOrCreateSymbol("object", rcc::mc::MCSymbolKind::Function) ||
+ Context.getOrCreateSymbol("bad\nname", rcc::mc::MCSymbolKind::Generic)) {
+ return fail("MCContext must reject conflicting or invalid symbols");
+ }
+ auto Temporary0 = Context.createTemporarySymbol(".Ltmp");
+ auto Temporary1 = Context.createTemporarySymbol(".Ltmp");
+ if (!Temporary0 || !Temporary1 || (*Temporary0)->name() != ".Ltmp0" ||
+ (*Temporary1)->name() != ".Ltmp1") {
+ return fail("temporary MC symbols must be deterministic");
+ }
+
+ auto SymbolExpr = rcc::mc::MCExpr::symbol(*Symbol);
+ if (!SymbolExpr)
+ return fail("symbol MC expression creation failed");
+ auto Add = rcc::mc::MCExpr::binary(rcc::mc::MCBinaryOperator::Add, *SymbolExpr,
+ rcc::mc::MCExpr::constant(4));
+ if (!Add)
+ return fail("binary MC expression creation failed");
+ auto TargetExpr = rcc::mc::MCExpr::target(3, *Add);
+ if (!TargetExpr || !(*TargetExpr)->verify())
+ return fail("structured MC expression creation failed");
+ auto Folded =
+ rcc::mc::MCExpr::binary(rcc::mc::MCBinaryOperator::ShiftLeft, rcc::mc::MCExpr::constant(-1),
+ rcc::mc::MCExpr::constant(63));
+ if (!Folded || (*Folded)->evaluateAbsolute() != std::int64_t(-9223372036854775807LL - 1))
+ return fail("absolute MC expression evaluation must use defined 64-bit semantics");
+
+ rcc::mc::MCSectionDesc TextDescription{
+ .Name = ".text",
+ .Kind = rcc::mc::MCSectionKind::Text,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Executable},
+ .Alignment = 4,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ };
+ auto Text = Context.getOrCreateSection(TextDescription);
+ auto SameText = Context.getOrCreateSection(TextDescription);
+ if (!Text || !SameText || *Text != *SameText || Context.section(".text") != *Text)
+ return fail("MCContext must uniquify identical sections");
+ TextDescription.Alignment = 8;
+ auto DifferentlyAlignedText = Context.getOrCreateSection(TextDescription);
+ if (!DifferentlyAlignedText || *DifferentlyAlignedText != *Text)
+ return fail("MCContext must merge per-fragment section alignment requirements");
+ TextDescription.Flags.set(rcc::mc::MCSectionFlag::Writable);
+ if (Context.getOrCreateSection(std::move(TextDescription)))
+ return fail("MCContext must reject conflicting section descriptions");
+
+ TestPrinter Printer;
+ rcc::mc::AsmTextStreamer Streamer(Printer);
+ rcc::mc::MCInst Instruction(rcc::mc::MCOpcode::fromValue(7));
+ Instruction.setFlags(11);
+ Instruction.setSourceIdentity(42);
+ Instruction.addOperand(rcc::mc::MCOperand::registerOperand(rcc::mc::MCRegister::fromValue(5)));
+ Instruction.addOperand(rcc::mc::MCOperand::immediate(-9));
+ if (Streamer.emitInstruction(Instruction) || !Streamer.switchSection(*Text) ||
+ !Streamer.emitSymbolAttribute(*Symbol, rcc::mc::MCSymbolAttribute::Global) ||
+ !Streamer.emitLabel(*Symbol) || Streamer.emitLabel(*Symbol) ||
+ !Streamer.emitInstruction(Instruction) || !Streamer.emitValue(*TargetExpr, 8)) {
+ return fail("assembly streamer event validation failed");
+ }
+ const std::array<std::uint8_t, 3> Bytes{0x01, 0x02, 0xff};
+ if (!Streamer.emitBytes(Bytes) || !Streamer.emitZeroFill(6) ||
+ !Streamer.emitAlignment(16, 0xaa, 31) ||
+ !Streamer.emitSymbolSize(*Symbol, rcc::mc::MCExpr::constant(17))) {
+ return fail("assembly streamer data events failed");
+ }
+ rcc::mc::MCCFIInstruction CFI;
+ CFI.Operation = rcc::mc::MCCFIOperation::DefCFA;
+ CFI.Operands = {2, 16};
+ if (!Streamer.emitCFI(CFI) || !Streamer.emitInlineAssembly(" nop\n") ||
+ !Streamer.emitComment("finished") || !Streamer.finish() || Streamer.finish()) {
+ return fail("assembly streamer terminal events failed");
+ }
+ auto Output = Streamer.takeOutput();
+ if (!Output || Streamer.takeOutput())
+ return fail("assembly output ownership must transfer exactly once");
+ if (!contains(*Output, ".section .text,\"ax\",@progbits\n") ||
+ !contains(*Output, ".globl object\nobject:\n") || !contains(*Output, " probe r5, -9\n") ||
+ !contains(*Output, " .8byte %test((object+4))\n") ||
+ !contains(*Output, " .byte 0x01, 0x02, 0xff\n") || !contains(*Output, " .zero 6\n") ||
+ !contains(*Output, " .p2align 4, 170, 31\n") || !contains(*Output, ".size object, 17\n") ||
+ !contains(*Output, ".cfi_def_cfa 2, 16\n") || !contains(*Output, " nop\n # finished\n")) {
+ return fail("assembly streamer output is incomplete or unstable");
+ }
+ return 0;
+}
diff --git a/backend/unittests/Support/ErrorTest.cpp b/backend/unittests/Support/ErrorTest.cpp
index de75aa3..f111063 100644
--- a/backend/unittests/Support/ErrorTest.cpp
+++ b/backend/unittests/Support/ErrorTest.cpp
@@ -40,6 +40,8 @@ int main() {
return fail("final-encodability errors must have a stable name");
if (rcc::getErrorCodeName(rcc::ErrorCode::CFGOptimizationFailed) != "CFG optimization failed")
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");
return 0;
}
diff --git a/backend/unittests/Target/CMakeLists.txt b/backend/unittests/Target/CMakeLists.txt
index c1bef1d..1b1f344 100644
--- a/backend/unittests/Target/CMakeLists.txt
+++ b/backend/unittests/Target/CMakeLists.txt
@@ -71,6 +71,17 @@ add_test(
COMMAND RCCBackendRISCVLateCodeGenTests
)
+add_executable(RCCBackendRISCVAssemblyPrinterTests RISCVAssemblyPrinterTest.cpp)
+rcc_backend_configure_target(RCCBackendRISCVAssemblyPrinterTests)
+target_link_libraries(
+ RCCBackendRISCVAssemblyPrinterTests
+ PRIVATE RCCBackend::RISCV RCCBackend::CodeGen
+)
+add_test(
+ NAME RCCBackendRISCVAssemblyPrinterTests
+ COMMAND RCCBackendRISCVAssemblyPrinterTests
+)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLoweringTest.cpp"
@@ -80,6 +91,7 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocationTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVLateCodeGenTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinterTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.h"
)
@@ -92,5 +104,6 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocationTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVLateCodeGenTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinterTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
)
diff --git a/backend/unittests/Target/Inputs/RISCVDataPipeline.mir b/backend/unittests/Target/Inputs/RISCVDataPipeline.mir
new file mode 100644
index 0000000..f847640
--- /dev/null
+++ b/backend/unittests/Target/Inputs/RISCVDataPipeline.mir
@@ -0,0 +1,18 @@
+---
+name: data_pipeline
+tracksRegLiveness: true
+isSSA: true
+constants:
+ - id: 0
+ value: 'i64 1234605616436508552'
+ alignment: 8
+jumpTable:
+ kind: block-address
+ entries:
+ - { id: 0, blocks: [ '%bb.0' ] }
+body: |
+ bb.0:
+ liveins: $x1
+ %0:gpr(i64) = G_CONSTANT 7
+ $x10 = COPY %0
+ PseudoRET implicit $x1, implicit $x10
diff --git a/backend/unittests/Target/Inputs/RISCVGenericPipeline.mir b/backend/unittests/Target/Inputs/RISCVGenericPipeline.mir
new file mode 100644
index 0000000..d68cd72
--- /dev/null
+++ b/backend/unittests/Target/Inputs/RISCVGenericPipeline.mir
@@ -0,0 +1,12 @@
+---
+name: generic_pipeline
+tracksRegLiveness: true
+isSSA: true
+body: |
+ bb.0:
+ liveins: $x1
+ %0:gpr(i64) = G_CONSTANT 42
+ %1:gpr(i64) = G_CONSTANT 1
+ %2:gpr(i64) = G_ADD %0, %1
+ $x10 = COPY %2
+ PseudoRET implicit $x1, implicit $x10
diff --git a/backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir b/backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir
new file mode 100644
index 0000000..9695691
--- /dev/null
+++ b/backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir
@@ -0,0 +1,10 @@
+---
+name: wide_constant_pipeline
+tracksRegLiveness: true
+isSSA: true
+body: |
+ bb.0:
+ liveins: $x1
+ %0:gpr(i64) = G_CONSTANT 9223372036854775807
+ $x10 = COPY %0
+ PseudoRET implicit $x1, implicit $x10
diff --git a/backend/unittests/Target/RISCVAssemblyPrinterTest.cpp b/backend/unittests/Target/RISCVAssemblyPrinterTest.cpp
new file mode 100644
index 0000000..e40ce8a
--- /dev/null
+++ b/backend/unittests/Target/RISCVAssemblyPrinterTest.cpp
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h"
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/GlobalObject.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+int fail(std::string_view Message) {
+ std::cerr << Message << '\n';
+ return 1;
+}
+
+[[nodiscard]] bool contains(std::string_view Text, std::string_view Needle) {
+ return Text.find(Needle) != std::string_view::npos;
+}
+
+[[nodiscard]] rcc::Expected<rcc::GlobalObjectDesc>
+makeObject(std::string Symbol, std::uint64_t Size, std::uint64_t Alignment,
+ rcc::GlobalInitializer Initializer, bool IsConstant = false) {
+ auto Align = rcc::Alignment::create(Alignment);
+ if (!Align)
+ return std::unexpected(Align.error());
+ rcc::GlobalObjectDesc Object;
+ Object.Symbol = std::move(Symbol);
+ Object.ObjectType = rcc::GenericConstantType::byte();
+ Object.Align = *Align;
+ Object.Size = Size;
+ Object.IsConstant = IsConstant;
+ Object.Initializer.emplace(std::move(Initializer));
+ auto Verification = Object.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Object;
+}
+
+[[nodiscard]] rcc::Expected<void> addObjects(rcc::CompilationUnit &Unit) {
+ auto AnswerValue = rcc::IntegerImmediate::fromUnsigned(32, 42);
+ if (!AnswerValue)
+ return std::unexpected(AnswerValue.error());
+ auto Answer =
+ makeObject("answer", 4, 4, rcc::GlobalInitializer::integer(std::move(*AnswerValue)), true);
+ if (!Answer)
+ return std::unexpected(Answer.error());
+ Answer->Visibility = rcc::GlobalVisibility::Hidden;
+ if (!Unit.addGlobalObject(std::move(*Answer)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add answer");
+
+ auto Zero = rcc::GlobalInitializer::zeroFill(16);
+ if (!Zero)
+ return std::unexpected(Zero.error());
+ auto Buffer = makeObject("zero_buffer", 16, 16, std::move(*Zero));
+ if (!Buffer)
+ return std::unexpected(Buffer.error());
+ Buffer->Linkage = rcc::GlobalLinkage::Internal;
+ if (!Unit.addGlobalObject(std::move(*Buffer)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add buffer");
+
+ auto TLSZero = rcc::GlobalInitializer::zeroFill(8);
+ if (!TLSZero)
+ return std::unexpected(TLSZero.error());
+ auto TLS = makeObject("tls_counter", 8, 8, std::move(*TLSZero));
+ if (!TLS)
+ return std::unexpected(TLS.error());
+ TLS->TLSModel = rcc::GlobalTLSModel::LocalExec;
+ if (!Unit.addGlobalObject(std::move(*TLS)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add TLS object");
+
+ auto Address = rcc::GlobalInitializer::symbolAddress(
+ rcc::GlobalSymbolAddress{.Symbol = "answer", .Addend = 8, .Size = 8, .TargetFlags = 0});
+ if (!Address)
+ return std::unexpected(Address.error());
+ auto Pointer = makeObject("answer_pointer", 8, 8, std::move(*Address), true);
+ if (!Pointer)
+ return std::unexpected(Pointer.error());
+ Pointer->Linkage = rcc::GlobalLinkage::WeakAny;
+ if (!Unit.addGlobalObject(std::move(*Pointer)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add pointer");
+
+ auto Bytes = rcc::GlobalInitializer::bytes(
+ {std::byte{0x52}, std::byte{0x43}, std::byte{0x43}, std::byte{0x00}});
+ if (!Bytes)
+ return std::unexpected(Bytes.error());
+ auto Grouped = makeObject("grouped_data", 4, 4, std::move(*Bytes));
+ if (!Grouped)
+ return std::unexpected(Grouped.error());
+ Grouped->Linkage = rcc::GlobalLinkage::LinkOnceODR;
+ Grouped->COMDAT = "grouped_data";
+ if (!Unit.addGlobalObject(std::move(*Grouped)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add COMDAT object");
+
+ auto CommonAlign = rcc::Alignment::create(16);
+ if (!CommonAlign)
+ return std::unexpected(CommonAlign.error());
+ rcc::GlobalObjectDesc Common;
+ Common.Symbol = "tentative";
+ Common.ObjectType = rcc::GenericConstantType::byte();
+ Common.Linkage = rcc::GlobalLinkage::Common;
+ Common.Align = *CommonAlign;
+ Common.Size = 32;
+ if (!Unit.addGlobalObject(std::move(Common)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add common object");
+ return {};
+}
+
+} // namespace
+
+int main(int ArgumentCount, char **Arguments) {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return fail(Target.error().message());
+
+ rcc::CompilationUnit Unit;
+ auto Added = addObjects(Unit);
+ if (!Added)
+ return fail(Added.error().message());
+ auto Assembly = rcc::riscv::emitAssembly(Unit, *Target);
+ if (!Assembly)
+ return fail(Assembly.error().message());
+
+ if (ArgumentCount == 2 && std::string_view(Arguments[1]) == "--print") {
+ std::cout << *Assembly;
+ return 0;
+ }
+
+ const std::string_view Text = *Assembly;
+ if (!contains(Text, ".section .rodata,\"a\",@progbits\n") || !contains(Text, ".globl answer\n") ||
+ !contains(Text, ".hidden answer\n") || !contains(Text, ".type answer, @object\n") ||
+ !contains(Text, "answer:\n") || !contains(Text, ".byte 0x2a, 0x00, 0x00, 0x00\n") ||
+ !contains(Text, ".size answer, 4\n")) {
+ return fail("read-only global assembly is incomplete");
+ }
+ if (!contains(Text, ".section .bss,\"aw\",@nobits\n") ||
+ !contains(Text, ".local zero_buffer\n") || !contains(Text, "zero_buffer:\n") ||
+ !contains(Text, ".zero 16\n")) {
+ return fail("BSS global assembly is incomplete");
+ }
+ if (!contains(Text, ".section .tbss,\"awT\",@nobits\n") ||
+ !contains(Text, ".type tls_counter, @tls_object\n")) {
+ return fail("TLS global assembly is incomplete");
+ }
+ if (!contains(Text, ".section .data.rel.ro,\"aw\",@progbits\n") ||
+ !contains(Text, ".weak answer_pointer\n") || !contains(Text, ".8byte (answer+8)\n")) {
+ return fail("relocatable constant global assembly is incomplete");
+ }
+ if (!contains(Text, ".section .data.grouped_data,\"awG\",@progbits,grouped_data,comdat\n") ||
+ !contains(Text, ".weak grouped_data\n")) {
+ return fail("COMDAT global assembly is incomplete");
+ }
+ if (!contains(Text, ".comm tentative,32,16\n"))
+ return fail("common global assembly is incomplete");
+ if (!contains(Text, ".attribute arch, \"rv64gc\"\n") ||
+ !contains(Text, ".section \".note.GNU-stack\",\"\",@progbits\n")) {
+ std::cerr << Text;
+ return fail("RV64GC file directives are incomplete");
+ }
+
+ rcc::CompilationUnit UnsupportedUnit;
+ auto UnsupportedZero = rcc::GlobalInitializer::zeroFill(1);
+ if (!UnsupportedZero)
+ return fail("unsupported global test initializer creation failed");
+ auto UnsupportedObject = makeObject("unsupported_global", 1, 1, std::move(*UnsupportedZero));
+ if (!UnsupportedObject)
+ return fail("unsupported global test object creation failed");
+ UnsupportedObject->TargetFlags = 1;
+ if (!UnsupportedUnit.addGlobalObject(std::move(*UnsupportedObject)))
+ return fail("unsupported global test insertion failed");
+ auto UnsupportedAssembly = rcc::riscv::emitAssembly(UnsupportedUnit, *Target);
+ if (UnsupportedAssembly || UnsupportedAssembly.error().code() != rcc::ErrorCode::Unsupported)
+ return fail("RV64GC assembly must reject unknown global target state");
+ return 0;
+}