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/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 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 1146 insertions(+) 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 (limited to 'backend/lib/MC') 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 -- cgit v1.2.3