aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-09 18:31:49 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-09 18:31:49 +0800
commit379ff50dcc7ccf977379aee68fa9d10f69065d23 (patch)
treee0fac3952d46d0bbcaea32bcfc6087c95fff2992 /backend/unittests
parent495c675771f43cfa12ab7a426a538be72fec1805 (diff)
downloadrcc-project-379ff50dcc7ccf977379aee68fa9d10f69065d23.tar.gz
rcc-project-379ff50dcc7ccf977379aee68fa9d10f69065d23.tar.xz
[MC] Implement RV64GC assembly emission
Diffstat (limited to 'backend/unittests')
-rw-r--r--backend/unittests/CMakeLists.txt1
-rw-r--r--backend/unittests/CodeGen/CompilationUnitTest.cpp44
-rw-r--r--backend/unittests/MC/CMakeLists.txt9
-rw-r--r--backend/unittests/MC/MCTest.cpp147
-rw-r--r--backend/unittests/Support/ErrorTest.cpp2
-rw-r--r--backend/unittests/Target/CMakeLists.txt13
-rw-r--r--backend/unittests/Target/Inputs/RISCVDataPipeline.mir18
-rw-r--r--backend/unittests/Target/Inputs/RISCVGenericPipeline.mir12
-rw-r--r--backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir10
-rw-r--r--backend/unittests/Target/RISCVAssemblyPrinterTest.cpp182
10 files changed, 436 insertions, 2 deletions
diff --git a/backend/unittests/CMakeLists.txt b/backend/unittests/CMakeLists.txt
index b93373d..d1ef6fa 100644
--- a/backend/unittests/CMakeLists.txt
+++ b/backend/unittests/CMakeLists.txt
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
add_subdirectory(Support)
+add_subdirectory(MC)
add_subdirectory(Target)
add_subdirectory(CodeGen)
add_subdirectory(MIR)
diff --git a/backend/unittests/CodeGen/CompilationUnitTest.cpp b/backend/unittests/CodeGen/CompilationUnitTest.cpp
index 39cd175..a31c9a8 100644
--- a/backend/unittests/CodeGen/CompilationUnitTest.cpp
+++ b/backend/unittests/CodeGen/CompilationUnitTest.cpp
@@ -4,6 +4,7 @@
#include "rcc/CodeGen/MachineFunction.h"
+#include <cstddef>
#include <iostream>
#include <utility>
@@ -43,14 +44,53 @@ int main() {
if (Unit.functionAt(2) != nullptr)
return fail("CompilationUnit must reject an out-of-range ordered lookup");
+ auto Zero = rcc::GlobalInitializer::zeroFill(4);
+ if (!Zero)
+ return fail("Global initializer creation must succeed");
+ rcc::GlobalObjectDesc Object;
+ Object.Symbol = "global";
+ Object.ObjectType = rcc::GenericConstantType::byte();
+ Object.Size = 4;
+ Object.Initializer.emplace(std::move(*Zero));
+ auto Global = Unit.addGlobalObject(std::move(Object));
+ if (!Global || Unit.globalObjectCount() != 1 || Unit.globalObject("global") != *Global ||
+ Unit.globalObjectAt(0) != *Global || Unit.globalObjectAt(1) != nullptr)
+ return fail("CompilationUnit must own and index global objects");
+
+ auto ConflictingFunction = Unit.createMachineFunction("global");
+ if (ConflictingFunction || Unit.functionCount() != 2)
+ return fail("CompilationUnit must reject function/global symbol conflicts atomically");
+
+ rcc::GlobalObjectDesc ConflictingGlobal;
+ ConflictingGlobal.Symbol = "first";
+ ConflictingGlobal.ObjectType = rcc::GenericConstantType::byte();
+ ConflictingGlobal.Size = 1;
+ auto Byte = rcc::GlobalInitializer::bytes({std::byte{1}});
+ if (!Byte)
+ return fail("Byte initializer creation must succeed");
+ ConflictingGlobal.Initializer.emplace(std::move(*Byte));
+ if (Unit.addGlobalObject(std::move(ConflictingGlobal)) || Unit.globalObjectCount() != 1)
+ return fail("CompilationUnit must reject global/function symbol conflicts atomically");
+
+ auto MismatchedBytes = rcc::GlobalInitializer::bytes({std::byte{1}, std::byte{2}});
+ if (!MismatchedBytes)
+ return fail("Mismatched initializer construction must succeed before object verification");
+ rcc::GlobalObjectDesc MismatchedObject;
+ MismatchedObject.Symbol = "mismatched";
+ MismatchedObject.ObjectType = rcc::GenericConstantType::byte();
+ MismatchedObject.Size = 1;
+ MismatchedObject.Initializer.emplace(std::move(*MismatchedBytes));
+ if (Unit.addGlobalObject(std::move(MismatchedObject)) || Unit.globalObjectCount() != 1)
+ return fail("CompilationUnit must reject mismatched global initializer sizes atomically");
+
auto Verification = Unit.verify();
if (!Verification)
return fail("A valid CompilationUnit must pass verification");
rcc::CompilationUnit Moved = std::move(Unit);
if (Moved.functionCount() != 2 || Moved.function("first") != *First ||
- Moved.function("second") != *Second)
- return fail("Moving a CompilationUnit must preserve owned function addresses and indices");
+ Moved.function("second") != *Second || Moved.globalObject("global") != *Global)
+ return fail("Moving a CompilationUnit must preserve owned object addresses and indices");
Verification = Moved.verify();
if (!Verification)
return fail("A moved CompilationUnit must pass verification");
diff --git a/backend/unittests/MC/CMakeLists.txt b/backend/unittests/MC/CMakeLists.txt
new file mode 100644
index 0000000..d62bb1d
--- /dev/null
+++ b/backend/unittests/MC/CMakeLists.txt
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+add_executable(RCCBackendMCTests MCTest.cpp)
+rcc_backend_configure_target(RCCBackendMCTests)
+target_link_libraries(RCCBackendMCTests PRIVATE RCCBackend::MC)
+add_test(NAME RCCBackend.MC.Core COMMAND RCCBackendMCTests)
+
+rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/MCTest.cpp")
+rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/MCTest.cpp")
diff --git a/backend/unittests/MC/MCTest.cpp b/backend/unittests/MC/MCTest.cpp
new file mode 100644
index 0000000..1876fdb
--- /dev/null
+++ b/backend/unittests/MC/MCTest.cpp
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/MC/MCCFI.h"
+#include "rcc/MC/MCContext.h"
+#include "rcc/MC/MCExpr.h"
+#include "rcc/MC/MCInst.h"
+#include "rcc/MC/MCInstPrinter.h"
+#include "rcc/MC/MCSection.h"
+#include "rcc/MC/MCStreamer.h"
+
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace {
+
+class TestPrinter final : public rcc::mc::MCInstPrinter {
+public:
+ [[nodiscard]] rcc::Expected<std::string>
+ printInstruction(const rcc::mc::MCInst &Instruction) const override {
+ if (Instruction.opcode().value() != 7 || Instruction.operands().size() != 2)
+ return rcc::makeUnexpected(rcc::ErrorCode::AssemblyLoweringFailed,
+ "unexpected test instruction");
+ const auto *Register = Instruction.operands()[0].registerValue();
+ const auto *Immediate = Instruction.operands()[1].immediateValue();
+ if (Register == nullptr || Immediate == nullptr)
+ return rcc::makeUnexpected(rcc::ErrorCode::AssemblyLoweringFailed,
+ "unexpected test operands");
+ return "probe r" + std::to_string(Register->value()) + ", " + std::to_string(*Immediate);
+ }
+
+ [[nodiscard]] rcc::Expected<std::string>
+ printTargetExpression(std::uint32_t Kind, std::string_view Operand) const override {
+ if (Kind != 3)
+ return rcc::makeUnexpected(rcc::ErrorCode::AssemblyLoweringFailed, "unknown test expression");
+ return "%test(" + std::string(Operand) + ")";
+ }
+};
+
+int fail(std::string_view Message) {
+ std::cerr << Message << '\n';
+ return 1;
+}
+
+[[nodiscard]] bool contains(std::string_view Text, std::string_view Needle) {
+ return Text.find(Needle) != std::string_view::npos;
+}
+
+} // namespace
+
+int main() {
+ rcc::mc::MCContext Context;
+ auto Symbol = Context.getOrCreateSymbol("object", rcc::mc::MCSymbolKind::Object);
+ auto SameSymbol = Context.getOrCreateSymbol("object", rcc::mc::MCSymbolKind::Generic);
+ if (!Symbol || !SameSymbol || *Symbol != *SameSymbol || Context.symbol("object") != *Symbol)
+ return fail("MCContext must uniquify compatible symbols");
+ if (Context.getOrCreateSymbol("object", rcc::mc::MCSymbolKind::Function) ||
+ Context.getOrCreateSymbol("bad\nname", rcc::mc::MCSymbolKind::Generic)) {
+ return fail("MCContext must reject conflicting or invalid symbols");
+ }
+ auto Temporary0 = Context.createTemporarySymbol(".Ltmp");
+ auto Temporary1 = Context.createTemporarySymbol(".Ltmp");
+ if (!Temporary0 || !Temporary1 || (*Temporary0)->name() != ".Ltmp0" ||
+ (*Temporary1)->name() != ".Ltmp1") {
+ return fail("temporary MC symbols must be deterministic");
+ }
+
+ auto SymbolExpr = rcc::mc::MCExpr::symbol(*Symbol);
+ if (!SymbolExpr)
+ return fail("symbol MC expression creation failed");
+ auto Add = rcc::mc::MCExpr::binary(rcc::mc::MCBinaryOperator::Add, *SymbolExpr,
+ rcc::mc::MCExpr::constant(4));
+ if (!Add)
+ return fail("binary MC expression creation failed");
+ auto TargetExpr = rcc::mc::MCExpr::target(3, *Add);
+ if (!TargetExpr || !(*TargetExpr)->verify())
+ return fail("structured MC expression creation failed");
+ auto Folded =
+ rcc::mc::MCExpr::binary(rcc::mc::MCBinaryOperator::ShiftLeft, rcc::mc::MCExpr::constant(-1),
+ rcc::mc::MCExpr::constant(63));
+ if (!Folded || (*Folded)->evaluateAbsolute() != std::int64_t(-9223372036854775807LL - 1))
+ return fail("absolute MC expression evaluation must use defined 64-bit semantics");
+
+ rcc::mc::MCSectionDesc TextDescription{
+ .Name = ".text",
+ .Kind = rcc::mc::MCSectionKind::Text,
+ .Flags = {rcc::mc::MCSectionFlag::Alloc, rcc::mc::MCSectionFlag::Executable},
+ .Alignment = 4,
+ .EntrySize = 0,
+ .Group = {},
+ .UniqueID = std::nullopt,
+ };
+ auto Text = Context.getOrCreateSection(TextDescription);
+ auto SameText = Context.getOrCreateSection(TextDescription);
+ if (!Text || !SameText || *Text != *SameText || Context.section(".text") != *Text)
+ return fail("MCContext must uniquify identical sections");
+ TextDescription.Alignment = 8;
+ auto DifferentlyAlignedText = Context.getOrCreateSection(TextDescription);
+ if (!DifferentlyAlignedText || *DifferentlyAlignedText != *Text)
+ return fail("MCContext must merge per-fragment section alignment requirements");
+ TextDescription.Flags.set(rcc::mc::MCSectionFlag::Writable);
+ if (Context.getOrCreateSection(std::move(TextDescription)))
+ return fail("MCContext must reject conflicting section descriptions");
+
+ TestPrinter Printer;
+ rcc::mc::AsmTextStreamer Streamer(Printer);
+ rcc::mc::MCInst Instruction(rcc::mc::MCOpcode::fromValue(7));
+ Instruction.setFlags(11);
+ Instruction.setSourceIdentity(42);
+ Instruction.addOperand(rcc::mc::MCOperand::registerOperand(rcc::mc::MCRegister::fromValue(5)));
+ Instruction.addOperand(rcc::mc::MCOperand::immediate(-9));
+ if (Streamer.emitInstruction(Instruction) || !Streamer.switchSection(*Text) ||
+ !Streamer.emitSymbolAttribute(*Symbol, rcc::mc::MCSymbolAttribute::Global) ||
+ !Streamer.emitLabel(*Symbol) || Streamer.emitLabel(*Symbol) ||
+ !Streamer.emitInstruction(Instruction) || !Streamer.emitValue(*TargetExpr, 8)) {
+ return fail("assembly streamer event validation failed");
+ }
+ const std::array<std::uint8_t, 3> Bytes{0x01, 0x02, 0xff};
+ if (!Streamer.emitBytes(Bytes) || !Streamer.emitZeroFill(6) ||
+ !Streamer.emitAlignment(16, 0xaa, 31) ||
+ !Streamer.emitSymbolSize(*Symbol, rcc::mc::MCExpr::constant(17))) {
+ return fail("assembly streamer data events failed");
+ }
+ rcc::mc::MCCFIInstruction CFI;
+ CFI.Operation = rcc::mc::MCCFIOperation::DefCFA;
+ CFI.Operands = {2, 16};
+ if (!Streamer.emitCFI(CFI) || !Streamer.emitInlineAssembly(" nop\n") ||
+ !Streamer.emitComment("finished") || !Streamer.finish() || Streamer.finish()) {
+ return fail("assembly streamer terminal events failed");
+ }
+ auto Output = Streamer.takeOutput();
+ if (!Output || Streamer.takeOutput())
+ return fail("assembly output ownership must transfer exactly once");
+ if (!contains(*Output, ".section .text,\"ax\",@progbits\n") ||
+ !contains(*Output, ".globl object\nobject:\n") || !contains(*Output, " probe r5, -9\n") ||
+ !contains(*Output, " .8byte %test((object+4))\n") ||
+ !contains(*Output, " .byte 0x01, 0x02, 0xff\n") || !contains(*Output, " .zero 6\n") ||
+ !contains(*Output, " .p2align 4, 170, 31\n") || !contains(*Output, ".size object, 17\n") ||
+ !contains(*Output, ".cfi_def_cfa 2, 16\n") || !contains(*Output, " nop\n # finished\n")) {
+ return fail("assembly streamer output is incomplete or unstable");
+ }
+ return 0;
+}
diff --git a/backend/unittests/Support/ErrorTest.cpp b/backend/unittests/Support/ErrorTest.cpp
index de75aa3..f111063 100644
--- a/backend/unittests/Support/ErrorTest.cpp
+++ b/backend/unittests/Support/ErrorTest.cpp
@@ -40,6 +40,8 @@ int main() {
return fail("final-encodability errors must have a stable name");
if (rcc::getErrorCodeName(rcc::ErrorCode::CFGOptimizationFailed) != "CFG optimization failed")
return fail("CFG-optimization errors must have a stable name");
+ if (rcc::getErrorCodeName(rcc::ErrorCode::AssemblyLoweringFailed) != "assembly lowering failed")
+ return fail("assembly-lowering errors must have a stable name");
return 0;
}
diff --git a/backend/unittests/Target/CMakeLists.txt b/backend/unittests/Target/CMakeLists.txt
index c1bef1d..1b1f344 100644
--- a/backend/unittests/Target/CMakeLists.txt
+++ b/backend/unittests/Target/CMakeLists.txt
@@ -71,6 +71,17 @@ add_test(
COMMAND RCCBackendRISCVLateCodeGenTests
)
+add_executable(RCCBackendRISCVAssemblyPrinterTests RISCVAssemblyPrinterTest.cpp)
+rcc_backend_configure_target(RCCBackendRISCVAssemblyPrinterTests)
+target_link_libraries(
+ RCCBackendRISCVAssemblyPrinterTests
+ PRIVATE RCCBackend::RISCV RCCBackend::CodeGen
+)
+add_test(
+ NAME RCCBackendRISCVAssemblyPrinterTests
+ COMMAND RCCBackendRISCVAssemblyPrinterTests
+)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/DescriptionGeneratorTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLoweringTest.cpp"
@@ -80,6 +91,7 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocationTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVLateCodeGenTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinterTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.h"
)
@@ -92,5 +104,6 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocationTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLoweringTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVLateCodeGenTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinterTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TestTarget.cpp"
)
diff --git a/backend/unittests/Target/Inputs/RISCVDataPipeline.mir b/backend/unittests/Target/Inputs/RISCVDataPipeline.mir
new file mode 100644
index 0000000..f847640
--- /dev/null
+++ b/backend/unittests/Target/Inputs/RISCVDataPipeline.mir
@@ -0,0 +1,18 @@
+---
+name: data_pipeline
+tracksRegLiveness: true
+isSSA: true
+constants:
+ - id: 0
+ value: 'i64 1234605616436508552'
+ alignment: 8
+jumpTable:
+ kind: block-address
+ entries:
+ - { id: 0, blocks: [ '%bb.0' ] }
+body: |
+ bb.0:
+ liveins: $x1
+ %0:gpr(i64) = G_CONSTANT 7
+ $x10 = COPY %0
+ PseudoRET implicit $x1, implicit $x10
diff --git a/backend/unittests/Target/Inputs/RISCVGenericPipeline.mir b/backend/unittests/Target/Inputs/RISCVGenericPipeline.mir
new file mode 100644
index 0000000..d68cd72
--- /dev/null
+++ b/backend/unittests/Target/Inputs/RISCVGenericPipeline.mir
@@ -0,0 +1,12 @@
+---
+name: generic_pipeline
+tracksRegLiveness: true
+isSSA: true
+body: |
+ bb.0:
+ liveins: $x1
+ %0:gpr(i64) = G_CONSTANT 42
+ %1:gpr(i64) = G_CONSTANT 1
+ %2:gpr(i64) = G_ADD %0, %1
+ $x10 = COPY %2
+ PseudoRET implicit $x1, implicit $x10
diff --git a/backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir b/backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir
new file mode 100644
index 0000000..9695691
--- /dev/null
+++ b/backend/unittests/Target/Inputs/RISCVWideConstantPipeline.mir
@@ -0,0 +1,10 @@
+---
+name: wide_constant_pipeline
+tracksRegLiveness: true
+isSSA: true
+body: |
+ bb.0:
+ liveins: $x1
+ %0:gpr(i64) = G_CONSTANT 9223372036854775807
+ $x10 = COPY %0
+ PseudoRET implicit $x1, implicit $x10
diff --git a/backend/unittests/Target/RISCVAssemblyPrinterTest.cpp b/backend/unittests/Target/RISCVAssemblyPrinterTest.cpp
new file mode 100644
index 0000000..e40ce8a
--- /dev/null
+++ b/backend/unittests/Target/RISCVAssemblyPrinterTest.cpp
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h"
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/GlobalObject.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+int fail(std::string_view Message) {
+ std::cerr << Message << '\n';
+ return 1;
+}
+
+[[nodiscard]] bool contains(std::string_view Text, std::string_view Needle) {
+ return Text.find(Needle) != std::string_view::npos;
+}
+
+[[nodiscard]] rcc::Expected<rcc::GlobalObjectDesc>
+makeObject(std::string Symbol, std::uint64_t Size, std::uint64_t Alignment,
+ rcc::GlobalInitializer Initializer, bool IsConstant = false) {
+ auto Align = rcc::Alignment::create(Alignment);
+ if (!Align)
+ return std::unexpected(Align.error());
+ rcc::GlobalObjectDesc Object;
+ Object.Symbol = std::move(Symbol);
+ Object.ObjectType = rcc::GenericConstantType::byte();
+ Object.Align = *Align;
+ Object.Size = Size;
+ Object.IsConstant = IsConstant;
+ Object.Initializer.emplace(std::move(Initializer));
+ auto Verification = Object.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Object;
+}
+
+[[nodiscard]] rcc::Expected<void> addObjects(rcc::CompilationUnit &Unit) {
+ auto AnswerValue = rcc::IntegerImmediate::fromUnsigned(32, 42);
+ if (!AnswerValue)
+ return std::unexpected(AnswerValue.error());
+ auto Answer =
+ makeObject("answer", 4, 4, rcc::GlobalInitializer::integer(std::move(*AnswerValue)), true);
+ if (!Answer)
+ return std::unexpected(Answer.error());
+ Answer->Visibility = rcc::GlobalVisibility::Hidden;
+ if (!Unit.addGlobalObject(std::move(*Answer)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add answer");
+
+ auto Zero = rcc::GlobalInitializer::zeroFill(16);
+ if (!Zero)
+ return std::unexpected(Zero.error());
+ auto Buffer = makeObject("zero_buffer", 16, 16, std::move(*Zero));
+ if (!Buffer)
+ return std::unexpected(Buffer.error());
+ Buffer->Linkage = rcc::GlobalLinkage::Internal;
+ if (!Unit.addGlobalObject(std::move(*Buffer)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add buffer");
+
+ auto TLSZero = rcc::GlobalInitializer::zeroFill(8);
+ if (!TLSZero)
+ return std::unexpected(TLSZero.error());
+ auto TLS = makeObject("tls_counter", 8, 8, std::move(*TLSZero));
+ if (!TLS)
+ return std::unexpected(TLS.error());
+ TLS->TLSModel = rcc::GlobalTLSModel::LocalExec;
+ if (!Unit.addGlobalObject(std::move(*TLS)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add TLS object");
+
+ auto Address = rcc::GlobalInitializer::symbolAddress(
+ rcc::GlobalSymbolAddress{.Symbol = "answer", .Addend = 8, .Size = 8, .TargetFlags = 0});
+ if (!Address)
+ return std::unexpected(Address.error());
+ auto Pointer = makeObject("answer_pointer", 8, 8, std::move(*Address), true);
+ if (!Pointer)
+ return std::unexpected(Pointer.error());
+ Pointer->Linkage = rcc::GlobalLinkage::WeakAny;
+ if (!Unit.addGlobalObject(std::move(*Pointer)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add pointer");
+
+ auto Bytes = rcc::GlobalInitializer::bytes(
+ {std::byte{0x52}, std::byte{0x43}, std::byte{0x43}, std::byte{0x00}});
+ if (!Bytes)
+ return std::unexpected(Bytes.error());
+ auto Grouped = makeObject("grouped_data", 4, 4, std::move(*Bytes));
+ if (!Grouped)
+ return std::unexpected(Grouped.error());
+ Grouped->Linkage = rcc::GlobalLinkage::LinkOnceODR;
+ Grouped->COMDAT = "grouped_data";
+ if (!Unit.addGlobalObject(std::move(*Grouped)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add COMDAT object");
+
+ auto CommonAlign = rcc::Alignment::create(16);
+ if (!CommonAlign)
+ return std::unexpected(CommonAlign.error());
+ rcc::GlobalObjectDesc Common;
+ Common.Symbol = "tentative";
+ Common.ObjectType = rcc::GenericConstantType::byte();
+ Common.Linkage = rcc::GlobalLinkage::Common;
+ Common.Align = *CommonAlign;
+ Common.Size = 32;
+ if (!Unit.addGlobalObject(std::move(Common)))
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "failed to add common object");
+ return {};
+}
+
+} // namespace
+
+int main(int ArgumentCount, char **Arguments) {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return fail(Target.error().message());
+
+ rcc::CompilationUnit Unit;
+ auto Added = addObjects(Unit);
+ if (!Added)
+ return fail(Added.error().message());
+ auto Assembly = rcc::riscv::emitAssembly(Unit, *Target);
+ if (!Assembly)
+ return fail(Assembly.error().message());
+
+ if (ArgumentCount == 2 && std::string_view(Arguments[1]) == "--print") {
+ std::cout << *Assembly;
+ return 0;
+ }
+
+ const std::string_view Text = *Assembly;
+ if (!contains(Text, ".section .rodata,\"a\",@progbits\n") || !contains(Text, ".globl answer\n") ||
+ !contains(Text, ".hidden answer\n") || !contains(Text, ".type answer, @object\n") ||
+ !contains(Text, "answer:\n") || !contains(Text, ".byte 0x2a, 0x00, 0x00, 0x00\n") ||
+ !contains(Text, ".size answer, 4\n")) {
+ return fail("read-only global assembly is incomplete");
+ }
+ if (!contains(Text, ".section .bss,\"aw\",@nobits\n") ||
+ !contains(Text, ".local zero_buffer\n") || !contains(Text, "zero_buffer:\n") ||
+ !contains(Text, ".zero 16\n")) {
+ return fail("BSS global assembly is incomplete");
+ }
+ if (!contains(Text, ".section .tbss,\"awT\",@nobits\n") ||
+ !contains(Text, ".type tls_counter, @tls_object\n")) {
+ return fail("TLS global assembly is incomplete");
+ }
+ if (!contains(Text, ".section .data.rel.ro,\"aw\",@progbits\n") ||
+ !contains(Text, ".weak answer_pointer\n") || !contains(Text, ".8byte (answer+8)\n")) {
+ return fail("relocatable constant global assembly is incomplete");
+ }
+ if (!contains(Text, ".section .data.grouped_data,\"awG\",@progbits,grouped_data,comdat\n") ||
+ !contains(Text, ".weak grouped_data\n")) {
+ return fail("COMDAT global assembly is incomplete");
+ }
+ if (!contains(Text, ".comm tentative,32,16\n"))
+ return fail("common global assembly is incomplete");
+ if (!contains(Text, ".attribute arch, \"rv64gc\"\n") ||
+ !contains(Text, ".section \".note.GNU-stack\",\"\",@progbits\n")) {
+ std::cerr << Text;
+ return fail("RV64GC file directives are incomplete");
+ }
+
+ rcc::CompilationUnit UnsupportedUnit;
+ auto UnsupportedZero = rcc::GlobalInitializer::zeroFill(1);
+ if (!UnsupportedZero)
+ return fail("unsupported global test initializer creation failed");
+ auto UnsupportedObject = makeObject("unsupported_global", 1, 1, std::move(*UnsupportedZero));
+ if (!UnsupportedObject)
+ return fail("unsupported global test object creation failed");
+ UnsupportedObject->TargetFlags = 1;
+ if (!UnsupportedUnit.addGlobalObject(std::move(*UnsupportedObject)))
+ return fail("unsupported global test insertion failed");
+ auto UnsupportedAssembly = rcc::riscv::emitAssembly(UnsupportedUnit, *Target);
+ if (UnsupportedAssembly || UnsupportedAssembly.error().code() != rcc::ErrorCode::Unsupported)
+ return fail("RV64GC assembly must reject unknown global target state");
+ return 0;
+}