aboutsummaryrefslogtreecommitdiff
path: root/backend/tools
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/tools
parent495c675771f43cfa12ab7a426a538be72fec1805 (diff)
downloadrcc-project-379ff50dcc7ccf977379aee68fa9d10f69065d23.tar.gz
rcc-project-379ff50dcc7ccf977379aee68fa9d10f69065d23.tar.xz
[MC] Implement RV64GC assembly emission
Diffstat (limited to 'backend/tools')
-rw-r--r--backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp171
-rw-r--r--backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake52
-rw-r--r--backend/tools/AssemblyEmitter/CMakeLists.txt39
-rw-r--r--backend/tools/CMakeLists.txt1
4 files changed, 263 insertions, 0 deletions
diff --git a/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp
new file mode 100644
index 0000000..ff339fa
--- /dev/null
+++ b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineOptimizationPipeline.h"
+#include "rcc/CodeGen/MachinePass.h"
+#include "rcc/CodeGen/MachinePipelinePasses.h"
+#include "rcc/CodeGen/MachineSSAExit.h"
+#include "rcc/CodeGen/RegAllocPBQP.h"
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Support/TextDiagnostic.h"
+#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h"
+#include "rcc/Target/RISCV/RISCVCodeGen.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace {
+
+struct CommandLine final {
+ std::string InputPath;
+};
+
+[[nodiscard]] bool parseCommandLine(int ArgumentCount, char *Arguments[], CommandLine &Result) {
+ for (int Index = 1; Index < ArgumentCount; ++Index) {
+ const std::string_view Argument(Arguments[Index]);
+ if (Argument != "--input" || Index + 1 >= ArgumentCount) {
+ std::cerr << "usage: rcc-backend-asm --input <mir-file|->\n";
+ return false;
+ }
+ Result.InputPath = Arguments[++Index];
+ }
+ if (Result.InputPath.empty()) {
+ std::cerr << "usage: rcc-backend-asm --input <mir-file|->\n";
+ return false;
+ }
+ return true;
+}
+
+[[nodiscard]] bool readInput(const CommandLine &Command, std::string &Contents) {
+ if (Command.InputPath == "-") {
+ Contents.assign(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
+ return true;
+ }
+ std::ifstream Input(Command.InputPath, std::ios::binary);
+ if (!Input) {
+ std::cerr << "rcc-backend-asm: cannot open input '" << Command.InputPath << "'\n";
+ return false;
+ }
+ Contents.assign(std::istreambuf_iterator<char>(Input), std::istreambuf_iterator<char>());
+ return true;
+}
+
+template <typename PassT, typename... ArgumentT>
+[[nodiscard]] rcc::Expected<void> addPass(rcc::MachinePassManager &Pipeline,
+ ArgumentT &&...Arguments) {
+ return Pipeline.addPass(std::make_unique<PassT>(std::forward<ArgumentT>(Arguments)...));
+}
+
+[[nodiscard]] rcc::Expected<void> runBackendPipeline(rcc::MachineFunction &Function,
+ const rcc::TargetDescription &Target) {
+ rcc::MachinePassManager Pipeline;
+ const rcc::MachineOptimizationPipelineTargetInfo TargetInfo =
+ rcc::riscv::optimizationPipelineInfo();
+
+ if (!Function.properties().has(rcc::MachineFunctionProperty::Legalized)) {
+ auto Added = addPass<rcc::LegalizerPass>(Pipeline, rcc::riscv::legalizerInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::RegBankSelected)) {
+ auto Added = addPass<rcc::RegisterBankSelectPass>(Pipeline, rcc::riscv::registerBankInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::Selected)) {
+ auto Added = addPass<rcc::InstructionSelectPass>(Pipeline, rcc::riscv::instructionSelector());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::NoPHIs)) {
+ auto Added = rcc::addSelectedSSAOptimizationPipeline(Pipeline, TargetInfo);
+ if (!Added)
+ return std::unexpected(Added.error());
+ Added = addPass<rcc::PHIEliminationPass>(Pipeline, rcc::riscv::ssaInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::TiedOpsRewritten)) {
+ auto Added = addPass<rcc::TwoAddressRewritePass>(Pipeline, rcc::riscv::ssaInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::NoVRegs)) {
+ auto Added = rcc::addPreRegisterAllocationOptimizationPipeline(Pipeline, TargetInfo);
+ if (!Added)
+ return std::unexpected(Added.error());
+ Added = addPass<rcc::PBQPAllocatorPass>(Pipeline, rcc::riscv::registerAllocationInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(rcc::MachineFunctionProperty::NoExecutablePseudos)) {
+ auto Added = rcc::addPostRegisterAllocationPipeline(Pipeline, TargetInfo);
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+
+ rcc::MachineAnalysisManager Analyses;
+ auto Registered = rcc::registerMachineOptimizationAnalyses(Analyses);
+ if (!Registered)
+ return std::unexpected(Registered.error());
+ return Pipeline.run(Function, Target, Analyses);
+}
+
+} // namespace
+
+int main(int ArgumentCount, char *Arguments[]) {
+ CommandLine Command;
+ if (!parseCommandLine(ArgumentCount, Arguments, Command))
+ return 1;
+
+ std::string SourceText;
+ if (!readInput(Command, SourceText))
+ return 1;
+
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target) {
+ std::cerr << "rcc-backend-asm: cannot create RV64GC target: " << Target.error().message()
+ << '\n';
+ return 1;
+ }
+ rcc::mir::MIRDocumentParserOptions Options;
+ Options.DataLayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128";
+ Options.DefaultPointerSizeInBits = 64;
+ auto Parsed =
+ rcc::mir::parseMIRDocument(*Target, Command.InputPath, std::move(SourceText), Options);
+ if (!Parsed.succeeded()) {
+ for (const rcc::TextDiagnostic &Diagnostic : Parsed.diagnostics())
+ std::cerr << rcc::formatTextDiagnostic(Diagnostic, Parsed.source()) << '\n';
+ return 1;
+ }
+
+ rcc::CompilationUnit &Unit = Parsed.document()->unit();
+ for (std::size_t Index = 0; Index < Unit.functionCount(); ++Index) {
+ rcc::MachineFunction *Function = Unit.functionAt(Index);
+ if (Function == nullptr) {
+ std::cerr << "rcc-backend-asm: parsed document contains a null function\n";
+ return 1;
+ }
+ auto Run = runBackendPipeline(*Function, *Target);
+ if (!Run) {
+ std::cerr << "rcc-backend-asm: backend pipeline failed for '" << Function->name()
+ << "': " << Run.error().message() << '\n';
+ return 1;
+ }
+ }
+
+ auto Assembly = rcc::riscv::emitAssembly(Unit, *Target);
+ if (!Assembly) {
+ std::cerr << "rcc-backend-asm: assembly emission failed: " << Assembly.error().message()
+ << '\n';
+ return 1;
+ }
+ std::cout << *Assembly;
+ return 0;
+}
diff --git a/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake b/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake
new file mode 100644
index 0000000..baeedcf
--- /dev/null
+++ b/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+if(NOT DEFINED EMITTER OR NOT DEFINED INPUT OR NOT DEFINED OUTPUT)
+ message(FATAL_ERROR "assembly pipeline test is missing a required path")
+endif()
+
+execute_process(
+ COMMAND "${EMITTER}" --input "${INPUT}"
+ RESULT_VARIABLE FirstResult
+ OUTPUT_VARIABLE FirstAssembly
+ ERROR_VARIABLE FirstDiagnostics
+)
+if(NOT FirstResult EQUAL 0)
+ message(FATAL_ERROR "assembly pipeline failed: ${FirstDiagnostics}")
+endif()
+if(NOT FirstAssembly MATCHES "\\.attribute arch, \"rv64gc\"" OR
+ NOT FirstAssembly MATCHES "jalr x0, x1, 0")
+ message(FATAL_ERROR "assembly pipeline output is missing required RV64GC content")
+endif()
+if(INPUT MATCHES "RISCVDataPipeline" AND
+ (NOT FirstAssembly MATCHES "\\.LCPI[0-9]+_0:" OR
+ NOT FirstAssembly MATCHES "\\.LJTI[0-9]+_0:"))
+ message(FATAL_ERROR "assembly pipeline did not emit its constant pool and jump table")
+endif()
+
+execute_process(
+ COMMAND "${EMITTER}" --input "${INPUT}"
+ RESULT_VARIABLE SecondResult
+ OUTPUT_VARIABLE SecondAssembly
+ ERROR_VARIABLE SecondDiagnostics
+)
+if(NOT SecondResult EQUAL 0)
+ message(FATAL_ERROR "repeated assembly pipeline failed: ${SecondDiagnostics}")
+endif()
+if(NOT FirstAssembly STREQUAL SecondAssembly)
+ message(FATAL_ERROR "assembly pipeline output is not deterministic")
+endif()
+
+if(DEFINED LLVM_MC AND NOT LLVM_MC STREQUAL "LLVM_MC-NOTFOUND" AND
+ NOT LLVM_MC STREQUAL "")
+ file(WRITE "${OUTPUT}" "${FirstAssembly}")
+ execute_process(
+ COMMAND "${LLVM_MC}" -triple=riscv64-unknown-linux-gnu -filetype=null "${OUTPUT}"
+ RESULT_VARIABLE AssemblerResult
+ OUTPUT_VARIABLE AssemblerOutput
+ ERROR_VARIABLE AssemblerDiagnostics
+ )
+ file(REMOVE "${OUTPUT}")
+ if(NOT AssemblerResult EQUAL 0)
+ message(FATAL_ERROR "LLVM MC rejected generated assembly: ${AssemblerDiagnostics}")
+ endif()
+endif()
diff --git a/backend/tools/AssemblyEmitter/CMakeLists.txt b/backend/tools/AssemblyEmitter/CMakeLists.txt
new file mode 100644
index 0000000..a262983
--- /dev/null
+++ b/backend/tools/AssemblyEmitter/CMakeLists.txt
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+add_executable(RCCBackendAssemblyEmitter AssemblyEmitterMain.cpp)
+rcc_backend_configure_target(RCCBackendAssemblyEmitter)
+target_link_libraries(
+ RCCBackendAssemblyEmitter
+ PRIVATE
+ RCCBackend::MIR
+ RCCBackend::RISCV
+)
+
+set_target_properties(
+ RCCBackendAssemblyEmitter
+ PROPERTIES
+ OUTPUT_NAME rcc-backend-asm
+)
+
+install(
+ TARGETS RCCBackendAssemblyEmitter
+ EXPORT RCCBackendTargets
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+)
+
+find_program(RCCBackendLLVMMC NAMES llvm-mc-20 llvm-mc)
+foreach(InputName IN ITEMS RISCVGenericPipeline RISCVWideConstantPipeline RISCVDataPipeline)
+ add_test(
+ NAME RCCBackend.AssemblyEmitter.${InputName}
+ COMMAND
+ "${CMAKE_COMMAND}"
+ "-DEMITTER=$<TARGET_FILE:RCCBackendAssemblyEmitter>"
+ "-DINPUT=${RCC_BACKEND_SOURCE_DIR}/unittests/Target/Inputs/${InputName}.mir"
+ "-DOUTPUT=${CMAKE_CURRENT_BINARY_DIR}/${InputName}.s"
+ "-DLLVM_MC=${RCCBackendLLVMMC}"
+ -P "${CMAKE_CURRENT_SOURCE_DIR}/AssemblyPipelineTest.cmake"
+ )
+endforeach()
+
+rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/AssemblyEmitterMain.cpp")
+rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/AssemblyEmitterMain.cpp")
diff --git a/backend/tools/CMakeLists.txt b/backend/tools/CMakeLists.txt
index 67d7273..403f4a9 100644
--- a/backend/tools/CMakeLists.txt
+++ b/backend/tools/CMakeLists.txt
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-3.0-or-later
add_subdirectory(DescriptionGenerator)
+add_subdirectory(AssemblyEmitter)