From 379ff50dcc7ccf977379aee68fa9d10f69065d23 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Sun, 9 Aug 2026 18:31:49 +0800 Subject: [MC] Implement RV64GC assembly emission --- backend/include/rcc/CodeGen/AsmPrinter.h | 57 + backend/include/rcc/CodeGen/CompilationUnit.h | 13 + backend/include/rcc/CodeGen/GlobalObject.h | 196 ++++ backend/include/rcc/CodeGen/MachineMCContext.h | 68 ++ backend/include/rcc/CodeGen/TargetMCInstLowering.h | 69 ++ backend/include/rcc/MC/MCCFI.h | 54 + backend/include/rcc/MC/MCContext.h | 43 + backend/include/rcc/MC/MCExpr.h | 109 ++ backend/include/rcc/MC/MCInst.h | 174 +++ backend/include/rcc/MC/MCInstPrinter.h | 31 + backend/include/rcc/MC/MCSection.h | 108 ++ backend/include/rcc/MC/MCStreamer.h | 93 ++ backend/include/rcc/MC/MCSymbol.h | 56 + backend/include/rcc/Support/Error.h | 1 + .../rcc/Target/RISCV/RISCVAssemblyPrinter.h | 35 + .../include/rcc/Target/RISCV/RISCVInstPrinter.h | 36 + .../rcc/Target/RISCV/RISCVMCTargetLowering.h | 62 ++ backend/lib/AsmPrinter/AsmPrinter.cpp | 679 ++++++++++++ backend/lib/AsmPrinter/CMakeLists.txt | 58 + backend/lib/AsmPrinter/MachineMCContext.cpp | 153 +++ backend/lib/AsmPrinter/TargetMCInstLowering.cpp | 49 + backend/lib/CMakeLists.txt | 2 + backend/lib/CodeGen/CMakeLists.txt | 4 + backend/lib/CodeGen/CompilationUnit.cpp | 71 ++ backend/lib/CodeGen/GlobalObject.cpp | 295 +++++ backend/lib/CodeGen/MachineCompilationStatus.cpp | 1 + backend/lib/MC/CMakeLists.txt | 58 + backend/lib/MC/MCCFI.cpp | 70 ++ backend/lib/MC/MCContext.cpp | 95 ++ backend/lib/MC/MCExpr.cpp | 205 ++++ backend/lib/MC/MCInstPrinter.cpp | 170 +++ backend/lib/MC/MCSection.cpp | 60 + backend/lib/MC/MCStreamer.cpp | 488 +++++++++ backend/lib/Support/Error.cpp | 2 + backend/lib/Target/RISCV/CMakeLists.txt | 20 +- backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp | 66 ++ backend/lib/Target/RISCV/RISCVInstPrinter.cpp | 279 +++++ backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp | 1158 ++++++++++++++++++++ .../tools/AssemblyEmitter/AssemblyEmitterMain.cpp | 171 +++ .../AssemblyEmitter/AssemblyPipelineTest.cmake | 52 + backend/tools/AssemblyEmitter/CMakeLists.txt | 39 + backend/tools/CMakeLists.txt | 1 + backend/unittests/CMakeLists.txt | 1 + backend/unittests/CodeGen/CompilationUnitTest.cpp | 44 +- backend/unittests/MC/CMakeLists.txt | 9 + backend/unittests/MC/MCTest.cpp | 147 +++ backend/unittests/Support/ErrorTest.cpp | 2 + backend/unittests/Target/CMakeLists.txt | 13 + .../unittests/Target/Inputs/RISCVDataPipeline.mir | 18 + .../Target/Inputs/RISCVGenericPipeline.mir | 12 + .../Target/Inputs/RISCVWideConstantPipeline.mir | 10 + .../unittests/Target/RISCVAssemblyPrinterTest.cpp | 182 +++ 52 files changed, 5886 insertions(+), 3 deletions(-) create mode 100644 backend/include/rcc/CodeGen/AsmPrinter.h create mode 100644 backend/include/rcc/CodeGen/GlobalObject.h create mode 100644 backend/include/rcc/CodeGen/MachineMCContext.h create mode 100644 backend/include/rcc/CodeGen/TargetMCInstLowering.h create mode 100644 backend/include/rcc/MC/MCCFI.h create mode 100644 backend/include/rcc/MC/MCContext.h create mode 100644 backend/include/rcc/MC/MCExpr.h create mode 100644 backend/include/rcc/MC/MCInst.h create mode 100644 backend/include/rcc/MC/MCInstPrinter.h create mode 100644 backend/include/rcc/MC/MCSection.h create mode 100644 backend/include/rcc/MC/MCStreamer.h create mode 100644 backend/include/rcc/MC/MCSymbol.h create mode 100644 backend/include/rcc/Target/RISCV/RISCVAssemblyPrinter.h create mode 100644 backend/include/rcc/Target/RISCV/RISCVInstPrinter.h create mode 100644 backend/include/rcc/Target/RISCV/RISCVMCTargetLowering.h create mode 100644 backend/lib/AsmPrinter/AsmPrinter.cpp create mode 100644 backend/lib/AsmPrinter/CMakeLists.txt create mode 100644 backend/lib/AsmPrinter/MachineMCContext.cpp create mode 100644 backend/lib/AsmPrinter/TargetMCInstLowering.cpp create mode 100644 backend/lib/CodeGen/GlobalObject.cpp create mode 100644 backend/lib/MC/CMakeLists.txt create mode 100644 backend/lib/MC/MCCFI.cpp create mode 100644 backend/lib/MC/MCContext.cpp create mode 100644 backend/lib/MC/MCExpr.cpp create mode 100644 backend/lib/MC/MCInstPrinter.cpp create mode 100644 backend/lib/MC/MCSection.cpp create mode 100644 backend/lib/MC/MCStreamer.cpp create mode 100644 backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp create mode 100644 backend/lib/Target/RISCV/RISCVInstPrinter.cpp create mode 100644 backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp create mode 100644 backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp create mode 100644 backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake create mode 100644 backend/tools/AssemblyEmitter/CMakeLists.txt create mode 100644 backend/unittests/MC/CMakeLists.txt create mode 100644 backend/unittests/MC/MCTest.cpp create mode 100644 backend/unittests/Target/Inputs/RISCVDataPipeline.mir create mode 100644 backend/unittests/Target/Inputs/RISCVGenericPipeline.mir create mode 100644 backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir create mode 100644 backend/unittests/Target/RISCVAssemblyPrinterTest.cpp 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 emitCompilationUnit(const CompilationUnit &Unit); + [[nodiscard]] Expected emitFunction(const MachineFunction &Function); + +private: + [[nodiscard]] Expected verifyFunction(const MachineFunction &Function) const; + [[nodiscard]] Expected emitGlobalObjects(const CompilationUnit &Unit); + [[nodiscard]] Expected emitFunctionBody(const MachineFunction &Function); + [[nodiscard]] Expected emitConstantPool(const MachineFunction &Function); + [[nodiscard]] Expected 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 createMachineFunction(std::string Name); + [[nodiscard]] Expected 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 verify() const; private: @@ -53,6 +64,8 @@ private: std::vector> Functions; std::unordered_map> FunctionNameToIndex; + std::vector> GlobalObjects; + std::unordered_map> GlobalNameToIndex; StableIDGenerator 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 +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +class GlobalInitializer; +using GlobalInitializerRef = std::shared_ptr; + +struct GlobalZeroFill final { + std::uint64_t Size = 0; +}; + +struct GlobalRepeatedInitializer final { + std::uint64_t Count = 0; + GlobalInitializerRef Value; +}; + +struct GlobalAggregateInitializer final { + std::vector 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 bytes(std::vector Value); + [[nodiscard]] static Expected zeroFill(std::uint64_t Size); + [[nodiscard]] static Expected repeated(std::uint64_t Count, + GlobalInitializer Value); + [[nodiscard]] static Expected + aggregate(std::vector Elements); + [[nodiscard]] static Expected symbolAddress(GlobalSymbolAddress Value); + [[nodiscard]] static Expected symbolDifference(GlobalSymbolDifference Value); + [[nodiscard]] static Expected target(TargetConstant Value); + [[nodiscard]] static Expected alignmentPadding(std::uint64_t Size, + std::uint8_t Fill = 0); + [[nodiscard]] static Expected undefinedStorage(std::uint64_t Size); + + [[nodiscard]] Kind kind() const noexcept; + + template [[nodiscard]] const ValueT *getIf() const noexcept { + return std::get_if(&Storage); + } + + [[nodiscard]] Expected verify(std::size_t MaximumDepth = 1024) const; + [[nodiscard]] Expected storageSize(std::size_t MaximumDepth = 1024) const; + +private: + using StorageT = + std::variant, + 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 Section; + Alignment Align; + std::uint64_t Size = 0; + std::uint32_t AddressSpace = 0; + GlobalTLSModel TLSModel = GlobalTLSModel::None; + bool ExternallyInitialized = false; + std::optional COMDAT; + bool IsConstant = false; + GlobalUnnamedAddress UnnamedAddress = GlobalUnnamedAddress::None; + std::optional Initializer; + std::vector DebugExpressions; + std::optional StableOrigin; + std::uint64_t TargetFlags = 0; + std::vector> 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 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 +#include +#include + +namespace rcc { + +class CompilationUnit; +struct GlobalObjectDesc; +class MachineBasicBlock; +class MachineFunction; + +class MachineMCContext final { +public: + explicit MachineMCContext(mc::MCContext &Context) noexcept : Context(&Context) {} + + [[nodiscard]] Expected initialize(const CompilationUnit &Unit); + [[nodiscard]] Expected 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 symbol(std::string Name, + mc::MCSymbolKind Kind = mc::MCSymbolKind::Generic); + [[nodiscard]] Expected 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 FunctionSymbols; + std::unordered_map GlobalSymbols; + std::unordered_map BlockSymbols; + std::unordered_map ConstantPoolSymbols; + std::unordered_map 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 lowerInstruction(const MachineInstr &Instruction, + MachineMCContext &Context) const = 0; + + [[nodiscard]] virtual Expected emitTerminalInstruction(const MachineInstr &Instruction, + MachineMCContext &Context, + mc::MCStreamer &Streamer) const; + + [[nodiscard]] virtual Expected + textSection(const MachineFunction &Function) const = 0; + [[nodiscard]] virtual Expected + globalSection(const GlobalObjectDesc &Object, MachineMCContext &Context) const = 0; + [[nodiscard]] virtual Expected + constantPoolSection(const MachineFunction &Function, + const MachineConstantPoolEntry &Entry) const = 0; + [[nodiscard]] virtual Expected + jumpTableSection(const MachineFunction &Function, const MachineJumpTableEntry &Entry) const = 0; + + [[nodiscard]] virtual Expected emitFileHeader(MachineMCContext &Context, + mc::MCStreamer &Streamer) const; + [[nodiscard]] virtual Expected emitFileTrailer(MachineMCContext &Context, + mc::MCStreamer &Streamer) const; + [[nodiscard]] virtual Expected emitGenericConstant(const GenericConstantData &Constant, + std::uint64_t Size, + MachineMCContext &Context, + mc::MCStreamer &Streamer) const; + [[nodiscard]] virtual Expected emitGlobalInitializer(const GlobalInitializer &Initializer, + MachineMCContext &Context, + mc::MCStreamer &Streamer) const; + [[nodiscard]] virtual Expected emitTargetConstant(const TargetConstant &Constant, + MachineMCContext &Context, + mc::MCStreamer &Streamer) const; + [[nodiscard]] virtual Expected 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 +#include + +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 Operands; + std::vector Bytes; + MCSymbolRef Symbol; + + [[nodiscard]] Expected 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 +#include +#include +#include +#include + +namespace rcc::mc { + +class MCContext final { +public: + [[nodiscard]] Expected getOrCreateSymbol(std::string Name, MCSymbolKind Kind); + [[nodiscard]] Expected createTemporarySymbol(std::string_view Prefix); + [[nodiscard]] MCSymbolRef symbol(std::string_view Name) const noexcept; + + [[nodiscard]] Expected 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{}(Value); + } + }; + + std::unordered_map> Symbols; + std::unordered_map> 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 +#include +#include +#include +#include + +namespace rcc::mc { + +class MCExpr; +using MCExprRef = std::shared_ptr; + +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 symbol(MCSymbolRef Symbol); + [[nodiscard]] static Expected unary(MCUnaryOperator Operator, MCExprRef Operand); + [[nodiscard]] static Expected binary(MCBinaryOperator Operator, MCExprRef Left, + MCExprRef Right); + [[nodiscard]] static Expected target(std::uint32_t Kind, MCExprRef Operand); + + [[nodiscard]] Kind kind() const noexcept { + return ExpressionKind; + } + + template [[nodiscard]] const ValueT *getIf() const noexcept { + return std::get_if(&Storage); + } + + [[nodiscard]] Expected verify(std::size_t MaximumDepth = 256) const; + [[nodiscard]] std::optional evaluateAbsolute() const noexcept; + +private: + using StorageT = + std::variant; + + 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 +#include +#include +#include +#include +#include + +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(&Storage); + } + + [[nodiscard]] const std::int64_t *immediateValue() const noexcept { + return std::get_if(&Storage); + } + + [[nodiscard]] const std::uint32_t *float32BitsValue() const noexcept { + return std::get_if(&Storage); + } + + [[nodiscard]] const std::uint64_t *float64BitsValue() const noexcept { + return std::get_if(&Storage); + } + + [[nodiscard]] const MCExprRef *expressionValue() const noexcept { + return std::get_if(&Storage); + } + +private: + using StorageT = std::variant; + + 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 operands() const noexcept { + return Operands; + } + +private: + MCOpcode InstructionOpcode; + std::uint32_t Flags = 0; + std::uint64_t SourceIdentity = 0; + std::vector 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 +#include +#include + +namespace rcc::mc { + +class MCInstPrinter { +public: + virtual ~MCInstPrinter() = default; + + [[nodiscard]] virtual Expected printInstruction(const MCInst &Instruction) const = 0; + + [[nodiscard]] virtual Expected + printTargetExpression(std::uint32_t Kind, std::string_view Operand) const = 0; +}; + +[[nodiscard]] Expected printMCExpression(const MCExpr &Expression, + const MCInstPrinter &Printer); +[[nodiscard]] Expected 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 +#include +#include +#include +#include +#include + +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 Flags) noexcept { + for (const MCSectionFlag Flag : Flags) + set(Flag); + } + + constexpr void set(MCSectionFlag Flag) noexcept { + const auto Index = static_cast(Flag); + if (Index < static_cast(MCSectionFlag::Count)) + Mask |= std::uint16_t{1} << Index; + } + + [[nodiscard]] constexpr bool has(MCSectionFlag Flag) const noexcept { + const auto Index = static_cast(Flag); + return Index < static_cast(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 UniqueID; + + [[nodiscard]] Expected 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; + +} // 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 +#include +#include +#include +#include + +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 switchSection(MCSectionRef Section) = 0; + [[nodiscard]] virtual Expected emitLabel(MCSymbolRef Symbol) = 0; + [[nodiscard]] virtual Expected emitInstruction(const MCInst &Instruction) = 0; + [[nodiscard]] virtual Expected emitValue(MCExprRef Value, std::uint8_t Size) = 0; + [[nodiscard]] virtual Expected emitBytes(std::span Bytes) = 0; + [[nodiscard]] virtual Expected emitZeroFill(std::uint64_t Size) = 0; + [[nodiscard]] virtual Expected emitAlignment(std::uint64_t Alignment, std::uint8_t Fill = 0, + std::uint64_t MaximumBytes = 0) = 0; + [[nodiscard]] virtual Expected emitSymbolAttribute(MCSymbolRef Symbol, + MCSymbolAttribute Attribute) = 0; + [[nodiscard]] virtual Expected emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) = 0; + [[nodiscard]] virtual Expected emitDirective(std::string_view Name, + std::string_view Arguments = {}) = 0; + [[nodiscard]] virtual Expected emitCFI(const MCCFIInstruction &Instruction) = 0; + [[nodiscard]] virtual Expected emitInlineAssembly(std::string_view Text) = 0; + [[nodiscard]] virtual Expected emitComment(std::string_view Text) = 0; + [[nodiscard]] virtual Expected finish() = 0; +}; + +class AsmTextStreamer final : public MCStreamer { +public: + explicit AsmTextStreamer(const MCInstPrinter &Printer) noexcept : Printer(&Printer) {} + + [[nodiscard]] Expected switchSection(MCSectionRef Section) override; + [[nodiscard]] Expected emitLabel(MCSymbolRef Symbol) override; + [[nodiscard]] Expected emitInstruction(const MCInst &Instruction) override; + [[nodiscard]] Expected emitValue(MCExprRef Value, std::uint8_t Size) override; + [[nodiscard]] Expected emitBytes(std::span Bytes) override; + [[nodiscard]] Expected emitZeroFill(std::uint64_t Size) override; + [[nodiscard]] Expected emitAlignment(std::uint64_t Alignment, std::uint8_t Fill = 0, + std::uint64_t MaximumBytes = 0) override; + [[nodiscard]] Expected emitSymbolAttribute(MCSymbolRef Symbol, + MCSymbolAttribute Attribute) override; + [[nodiscard]] Expected emitSymbolSize(MCSymbolRef Symbol, MCExprRef Size) override; + [[nodiscard]] Expected emitDirective(std::string_view Name, + std::string_view Arguments = {}) override; + [[nodiscard]] Expected emitCFI(const MCCFIInstruction &Instruction) override; + [[nodiscard]] Expected emitInlineAssembly(std::string_view Text) override; + [[nodiscard]] Expected emitComment(std::string_view Text) override; + [[nodiscard]] Expected finish() override; + + [[nodiscard]] Expected takeOutput(); + +private: + [[nodiscard]] Expected appendLine(std::string_view Prefix, std::string_view Text); + [[nodiscard]] Expected requireActiveSection() const; + + const MCInstPrinter *Printer; + MCSectionRef CurrentSection; + std::unordered_set 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 +#include +#include +#include + +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; + +} // 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 + +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 emitAssembly(const CompilationUnit &Unit, + const TargetDescription &Target, + AssemblyEmissionOptions Options = {}); +[[nodiscard]] Expected 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 +#include +#include + +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 + printInstruction(const mc::MCInst &Instruction) const override; + [[nodiscard]] Expected + 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 lowerInstruction(const MachineInstr &Instruction, + MachineMCContext &Context) const override; + [[nodiscard]] Expected emitTerminalInstruction(const MachineInstr &Instruction, + MachineMCContext &Context, + mc::MCStreamer &Streamer) const override; + + [[nodiscard]] Expected + textSection(const MachineFunction &Function) const override; + [[nodiscard]] Expected globalSection(const GlobalObjectDesc &Object, + MachineMCContext &Context) const override; + [[nodiscard]] Expected + constantPoolSection(const MachineFunction &Function, + const MachineConstantPoolEntry &Entry) const override; + [[nodiscard]] Expected + jumpTableSection(const MachineFunction &Function, + const MachineJumpTableEntry &Entry) const override; + + [[nodiscard]] Expected emitFileHeader(MachineMCContext &Context, + mc::MCStreamer &Streamer) const override; + [[nodiscard]] Expected emitFileTrailer(MachineMCContext &Context, + mc::MCStreamer &Streamer) const override; + [[nodiscard]] Expected emitGenericConstant(const GenericConstantData &Constant, + std::uint64_t Size, MachineMCContext &Context, + mc::MCStreamer &Streamer) const override; + [[nodiscard]] Expected emitGlobalInitializer(const GlobalInitializer &Initializer, + MachineMCContext &Context, + mc::MCStreamer &Streamer) const override; + [[nodiscard]] Expected emitTargetConstant(const TargetConstant &Constant, + MachineMCContext &Context, + mc::MCStreamer &Streamer) const override; + [[nodiscard]] Expected 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 +#include +#include +#include +#include + +namespace rcc { +namespace { + +[[nodiscard]] Expected symbolExpression(mc::MCSymbolRef Symbol) { + auto Expression = mc::MCExpr::symbol(std::move(Symbol)); + if (!Expression) + return std::unexpected(Expression.error()); + return *Expression; +} + +[[nodiscard]] Expected 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 +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 +lowerCFIInstruction(const MachineCFIInstruction &Instruction, MachineMCContext &Context) { + mc::MCCFIInstruction Result; + const auto Register = [&Result](const MachineCFIRegisterRule &Rule) { + Result.Operands.push_back(static_cast(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(Rule.Register.value())); + Result.Operands.push_back(Rule.Offset); + }; + + switch (Instruction.operation()) { + case MachineCFIOperation::SameValue: + Result.Operation = mc::MCCFIOperation::SameValue; + Register(*Instruction.getIf()); + 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()); + break; + case MachineCFIOperation::DefCFARegister: + Result.Operation = mc::MCCFIOperation::DefCFARegister; + Register(*Instruction.getIf()); + break; + case MachineCFIOperation::DefCFAOffset: + Result.Operation = mc::MCCFIOperation::DefCFAOffset; + Offset(*Instruction.getIf()); + break; + case MachineCFIOperation::DefCFA: + Result.Operation = mc::MCCFIOperation::DefCFA; + RegisterOffset(*Instruction.getIf()); + break; + case MachineCFIOperation::RelativeOffset: + Result.Operation = mc::MCCFIOperation::RelativeOffset; + RegisterOffset(*Instruction.getIf()); + break; + case MachineCFIOperation::AdjustCFAOffset: + Result.Operation = mc::MCCFIOperation::AdjustCFAOffset; + Offset(*Instruction.getIf()); + break; + case MachineCFIOperation::Escape: + Result.Operation = mc::MCCFIOperation::Escape; + Result.Bytes = Instruction.getIf()->Bytes; + break; + case MachineCFIOperation::Restore: + Result.Operation = mc::MCCFIOperation::Restore; + Register(*Instruction.getIf()); + break; + case MachineCFIOperation::Undefined: + Result.Operation = mc::MCCFIOperation::Undefined; + Register(*Instruction.getIf()); + break; + case MachineCFIOperation::Register: { + Result.Operation = mc::MCCFIOperation::Register; + const auto &Rule = *Instruction.getIf(); + Result.Operands.push_back(static_cast(Rule.Register.value())); + Result.Operands.push_back(static_cast(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()); + break; + case MachineCFIOperation::Label: { + Result.Operation = mc::MCCFIOperation::Label; + auto Symbol = Context.symbol(Instruction.getIf()->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()); + 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 emitNamedLabel(const MachineOperand &Operand, + MachineMCContext &Context, mc::MCStreamer &Streamer) { + const auto *SymbolOperand = Operand.getIf(); + 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 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 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 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 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(Object->Size))); + if (!Size) + return std::unexpected(Size.error()); + } + return {}; +} + +Expected 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 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 Emitted; + if (Instruction->opcode() == opcode(GenericOpcode::CFI_INSTRUCTION)) { + const auto *Index = Instruction->operandAt(0) == nullptr + ? nullptr + : Instruction->operandAt(0)->getIf(); + 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 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 Emitted; + if (const auto *Generic = std::get_if(&Description->Value)) { + Emitted = Lowering->emitGenericConstant(*Generic, Description->Size, *Context, *Streamer); + } else if (const auto *TargetValue = std::get_if(&Description->Value)) { + Emitted = Lowering->emitTargetConstant(*TargetValue, *Context, *Streamer); + } else if (const auto *Difference = + std::get_if(&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(Description->Size)); + } else if (const auto *Address = std::get_if(&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(Description->Size)); + } else if (const auto *TargetExpression = + std::get_if(&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 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 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 + "$" + "$" +) +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 +#include +#include +#include +#include +#include +#include + +namespace rcc { + +std::size_t MachineMCContext::FunctionIndexHash::operator()(FunctionIndexKey Key) const noexcept { + const std::size_t FunctionHash = std::hash{}(Key.Function); + const std::size_t IndexHash = std::hash{}(Key.Index); + return FunctionHash ^ (IndexHash + 0x9e3779b9U + (FunctionHash << 6U) + (FunctionHash >> 2U)); +} + +Expected 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 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 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 MachineMCContext::symbol(std::string Name, mc::MCSymbolKind Kind) { + return Context->getOrCreateSymbol(std::move(Name), Kind); +} + +Expected 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 TargetMCInstLowering::emitTerminalInstruction(const MachineInstr &, + MachineMCContext &, + mc::MCStreamer &) const { + return makeUnexpected(ErrorCode::Unsupported, + "target does not support this terminal emission instruction"); +} + +Expected TargetMCInstLowering::emitFileHeader(MachineMCContext &, mc::MCStreamer &) const { + return {}; +} + +Expected TargetMCInstLowering::emitFileTrailer(MachineMCContext &, mc::MCStreamer &) const { + return {}; +} + +Expected 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 TargetMCInstLowering::emitGlobalInitializer(const GlobalInitializer &, + MachineMCContext &, + mc::MCStreamer &) const { + return makeUnexpected(ErrorCode::Unsupported, + "target does not support assembly emission for global initializers"); +} + +Expected TargetMCInstLowering::emitTargetConstant(const TargetConstant &, MachineMCContext &, + mc::MCStreamer &) const { + return makeUnexpected(ErrorCode::Unsupported, + "target does not support assembly emission for this target constant"); +} + +Expected 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 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 CompilationUnit::createMachineFunction(std::string N return Result; } +Expected 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(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 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 StableIDs; for (std::size_t Index = 0; Index < Functions.size(); ++Index) { @@ -112,6 +159,30 @@ Expected 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 &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 +#include +#include +#include +#include +#include +#include +#include + +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 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 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 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(); + 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(Value->bitWidth()) + 7U) / 8U; + } + case GlobalInitializer::Kind::Floating: { + const auto *Value = Initializer.getIf(); + if (Value == nullptr) + return makeUnexpected(ErrorCode::InternalError, "floating initializer storage is invalid"); + return (static_cast(Value->bitWidth()) + 7U) / 8U; + } + case GlobalInitializer::Kind::Bytes: { + const auto *Value = Initializer.getIf>(); + if (Value == nullptr || Value->empty()) + return makeUnexpected(ErrorCode::VerificationError, "byte initializer cannot be empty"); + return static_cast(Value->size()); + } + case GlobalInitializer::Kind::ZeroFill: { + const auto *Value = Initializer.getIf(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(Emission->Bytes.size()); + } + case GlobalInitializer::Kind::AlignmentPadding: { + const auto *Value = Initializer.getIf(); + 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(); + 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::bytes(std::vector Value) { + if (Value.empty()) + return makeUnexpected(ErrorCode::InvalidArgument, "byte initializer cannot be empty"); + return GlobalInitializer(std::move(Value)); +} + +Expected 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::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(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::aggregate(std::vector 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(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::symbolAddress(GlobalSymbolAddress Value) { + GlobalInitializer Result(std::move(Value)); + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; +} + +Expected GlobalInitializer::symbolDifference(GlobalSymbolDifference Value) { + GlobalInitializer Result(std::move(Value)); + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; +} + +Expected GlobalInitializer::target(TargetConstant Value) { + GlobalInitializer Result(std::move(Value)); + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; +} + +Expected 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::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(Storage.index()); +} + +Expected 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 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 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::numeric_limits::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 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 + "$" + "$" +) +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 + +namespace rcc::mc { + +Expected 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(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 +#include +#include +#include +#include +#include + +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(RawCharacter); + if (Character == 0 || Character == '\n' || Character == '\r' || Character == 0x7fU) + return false; + } + return true; +} + +} // namespace + +Expected 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 MCContext::createTemporarySymbol(std::string_view Prefix) { + if (!isValidName(Prefix)) + return makeUnexpected(ErrorCode::InvalidArgument, "temporary MC symbol prefix is invalid"); + if (NextTemporarySymbol == std::numeric_limits::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 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 +#include +#include +#include +#include +#include + +namespace rcc::mc { +namespace { + +[[nodiscard]] std::optional checkedUnary(MCUnaryOperator Operator, + std::int64_t Value) noexcept { + switch (Operator) { + case MCUnaryOperator::Plus: + return Value; + case MCUnaryOperator::Minus: + if (Value == std::numeric_limits::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 +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::min() && Right == -1)) + return std::nullopt; + return Left / Right; + case MCBinaryOperator::Modulo: + if (Right == 0 || (Left == std::numeric_limits::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::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 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() == nullptr) + return makeUnexpected(ErrorCode::InternalError, "MC constant expression storage is invalid"); + return {}; + case MCExpr::Kind::SymbolRef: { + const auto *Reference = Expression.getIf(); + 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(); + 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(); + 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(); + 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(new MCExpr(Kind::Constant, Value)); +} + +Expected MCExpr::symbol(MCSymbolRef Symbol) { + if (!Symbol || Symbol->name().empty()) + return makeUnexpected(ErrorCode::InvalidArgument, "MC symbol expression requires a symbol"); + return std::shared_ptr( + new MCExpr(Kind::SymbolRef, MCSymbolRefExpr{.Symbol = std::move(Symbol)})); +} + +Expected MCExpr::unary(MCUnaryOperator Operator, MCExprRef Operand) { + if (!Operand) + return makeUnexpected(ErrorCode::InvalidArgument, "MC unary expression requires an operand"); + return std::shared_ptr( + new MCExpr(Kind::Unary, MCUnaryExpr{.Operator = Operator, .Operand = std::move(Operand)})); +} + +Expected 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(new MCExpr( + Kind::Binary, + MCBinaryExpr{.Operator = Operator, .Left = std::move(Left), .Right = std::move(Right)})); +} + +Expected 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( + new MCExpr(MCExpr::Kind::Target, MCTargetExpr{.Kind = Kind, .Operand = std::move(Operand)})); +} + +Expected 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 MCExpr::evaluateAbsolute() const noexcept { + switch (ExpressionKind) { + case Kind::Constant: + return *getIf(); + case Kind::SymbolRef: + case Kind::Target: + return std::nullopt; + case Kind::Unary: { + const auto *Unary = getIf(); + 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(); + 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 +#include +#include +#include +#include + +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(Name.front()))) + return false; + for (const char Character : Name.substr(1)) { + if (!Following(static_cast(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 +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()); + case MCExpr::Kind::SymbolRef: { + const auto *Reference = Expression.getIf(); + 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(); + 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(); + 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(); + 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 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(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(Character)); + } + Result.push_back('"'); + return Result; +} + +Expected 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 +#include + +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(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 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(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(Name.front()))) + return false; + return std::ranges::all_of(Name.substr(1), [Following](char Character) { + return Following(static_cast(Character)); + }); +} + +[[nodiscard]] std::string joinIntegerOperands(std::span 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 encodeULEB128(std::uint64_t Value) { + std::vector Bytes; + do { + std::uint8_t Byte = static_cast(Value & 0x7fU); + Value >>= 7U; + if (Value != 0) + Byte |= 0x80U; + Bytes.push_back(Byte); + } while (Value != 0); + return Bytes; +} + +} // namespace + +Expected 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 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 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 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 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 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 AsmTextStreamer::emitBytes(std::span 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 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 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 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 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 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 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 Bytes{0x2eU}; + const auto Encoded = encodeULEB128(static_cast(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 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 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 AsmTextStreamer::finish() { + if (IsFinished) + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "assembly stream was already finalized"); + IsFinished = true; + return {}; +} + +Expected 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( "$" 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 + +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 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 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 +#include +#include +#include +#include +#include +#include +#include + +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 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 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 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 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 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 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 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 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 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 RISCVInstPrinter::printTargetExpression(std::uint32_t Kind, + std::string_view Operand) const { + switch (static_cast(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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 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 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(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(*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 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 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(Kind), std::move(Expression)); + if (!TargetExpression) + return std::unexpected(TargetExpression.error()); + return *TargetExpression; +} + +[[nodiscard]] Expected 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 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(Raw); + if (!Value.isNegative()) + return static_cast(Raw); + const std::uint64_t Mask = (std::uint64_t{1} << Value.bitWidth()) - 1U; + return std::bit_cast(Raw | ~Mask); +} + +[[nodiscard]] Expected lowerOperand(const MachineOperand &Operand, + const MachineFunction &Function, + MachineMCContext &Context) { + if (const auto *RegisterValue = Operand.getIf()) { + 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()) { + 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()) { + 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()) { + 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(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()) { + 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()) { + 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()) { + 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()) { + 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()) { + 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()) { + 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()) { + 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 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 bytesFromWords(std::span Words, + std::uint64_t Size) { + std::vector Bytes(static_cast(Size), 0); + for (std::uint64_t ByteIndex = 0; ByteIndex < Size; ++ByteIndex) { + const std::size_t WordIndex = static_cast(ByteIndex / 8U); + if (WordIndex >= Words.size()) + break; + const unsigned Shift = static_cast((ByteIndex % 8U) * 8U); + Bytes[static_cast(ByteIndex)] = + static_cast((Words[WordIndex] >> Shift) & 0xffU); + } + return Bytes; +} + +[[nodiscard]] Expected 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(Raw) + : static_cast(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 emitPadding(std::uint64_t Size, mc::MCStreamer &Streamer) { + return Size == 0 ? Expected{} : Streamer.emitZeroFill(Size); +} + +[[nodiscard]] Expected 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 Bytes; + Bytes.reserve(Constant.ByteValue.size()); + for (const std::byte Byte : Constant.ByteValue) + Bytes.push_back(std::to_integer(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(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 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::min(Size, ChunkSize)); + const std::vector Chunk(InitialSize, Byte); + while (Size != 0) { + const std::size_t EmittedSize = static_cast(std::min(Size, ChunkSize)); + auto Emitted = Streamer.emitBytes(std::span(Chunk).first(EmittedSize)); + if (!Emitted) + return std::unexpected(Emitted.error()); + Size -= static_cast(EmittedSize); + } + return {}; +} + +[[nodiscard]] Expected 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(); + if (Value == nullptr) + return makeUnexpected(ErrorCode::InternalError, "integer initializer has no value"); + const std::uint64_t Size = (static_cast(Value->bitWidth()) + 7U) / 8U; + return Streamer.emitBytes(bytesFromWords(Value->words(), Size)); + } + case GlobalInitializer::Kind::Floating: { + const auto *Value = Initializer.getIf(); + if (Value == nullptr) + return makeUnexpected(ErrorCode::InternalError, "floating initializer has no value"); + const std::uint64_t Size = (static_cast(Value->bitWidth()) + 7U) / 8U; + return Streamer.emitBytes(bytesFromWords(Value->words(), Size)); + } + case GlobalInitializer::Kind::Bytes: { + const auto *Value = Initializer.getIf>(); + if (Value == nullptr) + return makeUnexpected(ErrorCode::InternalError, "byte initializer has no value"); + std::vector Bytes; + Bytes.reserve(Value->size()); + for (const std::byte Byte : *Value) + Bytes.push_back(std::to_integer(Byte)); + return Streamer.emitBytes(Bytes); + } + case GlobalInitializer::Kind::ZeroFill: { + const auto *Value = Initializer.getIf(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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 Bytes; + Bytes.reserve(Emission->Bytes.size()); + for (const std::byte Byte : Emission->Bytes) + Bytes.push_back(std::to_integer(Byte)); + return Streamer.emitBytes(Bytes); + } + case GlobalInitializer::Kind::AlignmentPadding: { + const auto *Value = Initializer.getIf(); + 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(); + 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(); + 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(); + 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>(); + 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(); + return Value != nullptr && Value->Value && initializerIsZero(*Value->Value, Depth + 1); + } + case GlobalInitializer::Kind::Aggregate: { + const auto *Value = Initializer.getIf(); + 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(); + return Value != nullptr && Value->Fill == 0; + } + case GlobalInitializer::Kind::Target: { + const auto *Value = Initializer.getIf(); + 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(); + return Value == nullptr || !Value->Value || initializerHasRelocation(*Value->Value, Depth + 1); + } + case GlobalInitializer::Kind::Aggregate: { + const auto *Value = Initializer.getIf(); + 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(); + 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 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 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(); + 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 RISCVMCTargetLowering::textSection(const MachineFunction &) const { + return textSectionDescription(); +} + +Expected 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 +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(Entry.index().value()) + 1U; + } + Result.Alignment = Description->Align.bytes(); + return Result; +} + +Expected +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(Entry.index().value()) + 1U; + } + Result.Alignment = Description->Align.bytes(); + return Result; +} + +Expected 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 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 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 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 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 Bytes; + Bytes.reserve(Emission->Bytes.size()); + for (const std::byte Byte : Emission->Bytes) + Bytes.push_back(std::to_integer(Byte)); + return Streamer.emitBytes(Bytes); +} + +Expected 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 +#include +#include +#include +#include +#include +#include + +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 \n"; + return false; + } + Result.InputPath = Arguments[++Index]; + } + if (Result.InputPath.empty()) { + std::cerr << "usage: rcc-backend-asm --input \n"; + return false; + } + return true; +} + +[[nodiscard]] bool readInput(const CommandLine &Command, std::string &Contents) { + if (Command.InputPath == "-") { + Contents.assign(std::istreambuf_iterator(std::cin), std::istreambuf_iterator()); + 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(Input), std::istreambuf_iterator()); + return true; +} + +template +[[nodiscard]] rcc::Expected addPass(rcc::MachinePassManager &Pipeline, + ArgumentT &&...Arguments) { + return Pipeline.addPass(std::make_unique(std::forward(Arguments)...)); +} + +[[nodiscard]] rcc::Expected 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(Pipeline, rcc::riscv::legalizerInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(rcc::MachineFunctionProperty::RegBankSelected)) { + auto Added = addPass(Pipeline, rcc::riscv::registerBankInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(rcc::MachineFunctionProperty::Selected)) { + auto Added = addPass(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(Pipeline, rcc::riscv::ssaInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(rcc::MachineFunctionProperty::TiedOpsRewritten)) { + auto Added = addPass(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(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=$" + "-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 #include #include @@ -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 +#include +#include +#include +#include +#include +#include + +namespace { + +class TestPrinter final : public rcc::mc::MCInstPrinter { +public: + [[nodiscard]] rcc::Expected + 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 + 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 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 +#include +#include +#include +#include +#include +#include + +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 +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 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; +} -- cgit v1.2.3