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/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 +++++++++++++++++++++ 10 files changed, 436 insertions(+), 2 deletions(-) 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 (limited to 'backend/unittests') 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