From 3a57627e59ba18b6f9f9aae7bb60c390c0d3575a Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Sun, 9 Aug 2026 22:05:14 +0800 Subject: [MC] Implement RV64GC object emission --- .../tools/AssemblyEmitter/AssemblyEmitterMain.cpp | 78 +++++++++++++++++-- .../AssemblyEmitter/AssemblyPipelineTest.cmake | 5 ++ backend/tools/AssemblyEmitter/CMakeLists.txt | 22 +++++- .../tools/AssemblyEmitter/ObjectPipelineTest.cmake | 88 ++++++++++++++++++++++ 4 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 backend/tools/AssemblyEmitter/ObjectPipelineTest.cmake (limited to 'backend/tools/AssemblyEmitter') diff --git a/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp index ff339fa..8697754 100644 --- a/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp +++ b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp @@ -10,8 +10,10 @@ #include "rcc/Support/TextDiagnostic.h" #include "rcc/Target/RISCV/RISCVAssemblyPrinter.h" #include "rcc/Target/RISCV/RISCVCodeGen.h" +#include "rcc/Target/RISCV/RISCVObjectFile.h" #include "rcc/Target/RISCV/RISCVTarget.h" +#include #include #include #include @@ -19,29 +21,86 @@ #include #include #include +#include namespace { +enum class OutputKind : std::uint8_t { + Assembly, + Object, +}; + struct CommandLine final { std::string InputPath; + std::string OutputPath = "-"; + OutputKind Kind = OutputKind::Assembly; }; +void printUsage() { + std::cerr << "usage: rcc-backend-asm --input " + "[--filetype asm|obj] [--output ]\n"; +} + [[nodiscard]] bool parseCommandLine(int ArgumentCount, char *Arguments[], CommandLine &Result) { for (int Index = 1; Index < ArgumentCount; ++Index) { const std::string_view Argument(Arguments[Index]); - if (Argument != "--input" || Index + 1 >= ArgumentCount) { - std::cerr << "usage: rcc-backend-asm --input \n"; + if (Argument == "--input" && Index + 1 < ArgumentCount) { + Result.InputPath = Arguments[++Index]; + continue; + } + if ((Argument == "--output" || Argument == "-o") && Index + 1 < ArgumentCount) { + Result.OutputPath = Arguments[++Index]; + continue; + } + if (Argument == "--filetype" && Index + 1 < ArgumentCount) { + const std::string_view Value(Arguments[++Index]); + if (Value == "asm" || Value == "assembly") { + Result.Kind = OutputKind::Assembly; + } else if (Value == "obj" || Value == "object") { + Result.Kind = OutputKind::Object; + } else { + printUsage(); + return false; + } + continue; + } + { + printUsage(); return false; } - Result.InputPath = Arguments[++Index]; } if (Result.InputPath.empty()) { - std::cerr << "usage: rcc-backend-asm --input \n"; + printUsage(); + return false; + } + return true; +} + +[[nodiscard]] bool writeOutput(const CommandLine &Command, std::span Contents) { + if (Command.OutputPath == "-") { + std::cout.write(reinterpret_cast(Contents.data()), + static_cast(Contents.size())); + return static_cast(std::cout); + } + std::ofstream Output(Command.OutputPath, std::ios::binary); + if (!Output) { + std::cerr << "rcc-backend-asm: cannot open output '" << Command.OutputPath << "'\n"; + return false; + } + Output.write(reinterpret_cast(Contents.data()), + static_cast(Contents.size())); + if (!Output) { + std::cerr << "rcc-backend-asm: cannot write output '" << Command.OutputPath << "'\n"; return false; } return true; } +[[nodiscard]] bool writeOutput(const CommandLine &Command, std::string_view Contents) { + const auto *Data = reinterpret_cast(Contents.data()); + return writeOutput(Command, std::span(Data, Contents.size())); +} + [[nodiscard]] bool readInput(const CommandLine &Command, std::string &Contents) { if (Command.InputPath == "-") { Contents.assign(std::istreambuf_iterator(std::cin), std::istreambuf_iterator()); @@ -160,12 +219,19 @@ int main(int ArgumentCount, char *Arguments[]) { } } + if (Command.Kind == OutputKind::Object) { + auto Object = rcc::riscv::emitObject(Unit, *Target); + if (!Object) { + std::cerr << "rcc-backend-asm: object emission failed: " << Object.error().message() << '\n'; + return 1; + } + return writeOutput(Command, *Object) ? 0 : 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; + return writeOutput(Command, *Assembly) ? 0 : 1; } diff --git a/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake b/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake index baeedcf..85977df 100644 --- a/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake +++ b/backend/tools/AssemblyEmitter/AssemblyPipelineTest.cmake @@ -22,6 +22,11 @@ if(INPUT MATCHES "RISCVDataPipeline" AND NOT FirstAssembly MATCHES "\\.LJTI[0-9]+_0:")) message(FATAL_ERROR "assembly pipeline did not emit its constant pool and jump table") endif() +if(INPUT MATCHES "RISCVStackFramePipeline" AND + (NOT FirstAssembly MATCHES "addi x2, x2, -32" OR + NOT FirstAssembly MATCHES "addi x2, x2, 32")) + message(FATAL_ERROR "assembly pipeline did not balance its stack frame") +endif() execute_process( COMMAND "${EMITTER}" --input "${INPUT}" diff --git a/backend/tools/AssemblyEmitter/CMakeLists.txt b/backend/tools/AssemblyEmitter/CMakeLists.txt index a262983..01c99aa 100644 --- a/backend/tools/AssemblyEmitter/CMakeLists.txt +++ b/backend/tools/AssemblyEmitter/CMakeLists.txt @@ -22,7 +22,16 @@ install( ) find_program(RCCBackendLLVMMC NAMES llvm-mc-20 llvm-mc) -foreach(InputName IN ITEMS RISCVGenericPipeline RISCVWideConstantPipeline RISCVDataPipeline) +find_program(RCCBackendLLVMReadELF NAMES llvm-readelf-20 llvm-readelf) +find_program(RCCBackendLLVMObjdump NAMES llvm-objdump-20 llvm-objdump) +foreach( + InputName + IN ITEMS + RISCVGenericPipeline + RISCVWideConstantPipeline + RISCVDataPipeline + RISCVStackFramePipeline +) add_test( NAME RCCBackend.AssemblyEmitter.${InputName} COMMAND @@ -33,6 +42,17 @@ foreach(InputName IN ITEMS RISCVGenericPipeline RISCVWideConstantPipeline RISCVD "-DLLVM_MC=${RCCBackendLLVMMC}" -P "${CMAKE_CURRENT_SOURCE_DIR}/AssemblyPipelineTest.cmake" ) + add_test( + NAME RCCBackend.ObjectEmitter.${InputName} + COMMAND + "${CMAKE_COMMAND}" + "-DEMITTER=$" + "-DINPUT=${RCC_BACKEND_SOURCE_DIR}/unittests/Target/Inputs/${InputName}.mir" + "-DOUTPUT=${CMAKE_CURRENT_BINARY_DIR}/${InputName}.o" + "-DLLVM_READELF=${RCCBackendLLVMReadELF}" + "-DLLVM_OBJDUMP=${RCCBackendLLVMObjdump}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/ObjectPipelineTest.cmake" + ) endforeach() rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/AssemblyEmitterMain.cpp") diff --git a/backend/tools/AssemblyEmitter/ObjectPipelineTest.cmake b/backend/tools/AssemblyEmitter/ObjectPipelineTest.cmake new file mode 100644 index 0000000..5ba800c --- /dev/null +++ b/backend/tools/AssemblyEmitter/ObjectPipelineTest.cmake @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +if(NOT DEFINED EMITTER OR NOT DEFINED INPUT OR NOT DEFINED OUTPUT) + message(FATAL_ERROR "object pipeline test is missing a required path") +endif() + +set(RepeatedOutput "${OUTPUT}.repeat") +execute_process( + COMMAND "${EMITTER}" --input "${INPUT}" --filetype obj --output "${OUTPUT}" + RESULT_VARIABLE FirstResult + ERROR_VARIABLE FirstDiagnostics +) +if(NOT FirstResult EQUAL 0) + message(FATAL_ERROR "object pipeline failed: ${FirstDiagnostics}") +endif() + +execute_process( + COMMAND "${EMITTER}" --input "${INPUT}" --filetype obj --output "${RepeatedOutput}" + RESULT_VARIABLE SecondResult + ERROR_VARIABLE SecondDiagnostics +) +if(NOT SecondResult EQUAL 0) + message(FATAL_ERROR "repeated object pipeline failed: ${SecondDiagnostics}") +endif() + +execute_process( + COMMAND "${CMAKE_COMMAND}" -E compare_files "${OUTPUT}" "${RepeatedOutput}" + RESULT_VARIABLE ComparisonResult +) +if(NOT ComparisonResult EQUAL 0) + message(FATAL_ERROR "object pipeline output is not deterministic") +endif() + +file(READ "${OUTPUT}" ObjectMagic OFFSET 0 LIMIT 4 HEX) +if(NOT ObjectMagic STREQUAL "7f454c46") + message(FATAL_ERROR "object pipeline did not produce an ELF file") +endif() + +if(DEFINED LLVM_READELF AND NOT LLVM_READELF STREQUAL "LLVM_READELF-NOTFOUND" AND + NOT LLVM_READELF STREQUAL "") + execute_process( + COMMAND "${LLVM_READELF}" -h -S -s -r "${OUTPUT}" + RESULT_VARIABLE ReadELFResult + OUTPUT_VARIABLE ReadELFOutput + ERROR_VARIABLE ReadELFDiagnostics + ) + if(NOT ReadELFResult EQUAL 0) + message(FATAL_ERROR "LLVM readelf rejected generated object: ${ReadELFDiagnostics}") + endif() + if(NOT ReadELFOutput MATCHES "Type:[ \t]+REL" OR + NOT ReadELFOutput MATCHES "Machine:[ \t]+RISC-V" OR + NOT ReadELFOutput MATCHES "Flags:[ \t]+0x5" OR + NOT ReadELFOutput MATCHES "\\.text" OR + NOT ReadELFOutput MATCHES "\\.eh_frame" OR + NOT ReadELFOutput MATCHES "\\.riscv\\.attributes") + message(FATAL_ERROR "generated object is missing required RV64GC ELF metadata") + endif() + if(INPUT MATCHES "RISCVDataPipeline" AND + (NOT ReadELFOutput MATCHES "\\.rodata" OR + NOT ReadELFOutput MATCHES "\\.rela\\.text" OR + NOT ReadELFOutput MATCHES "R_RISCV_64")) + message(FATAL_ERROR "data pipeline object is missing data or relocation sections") + endif() +endif() + +if(DEFINED LLVM_OBJDUMP AND NOT LLVM_OBJDUMP STREQUAL "LLVM_OBJDUMP-NOTFOUND" AND + NOT LLVM_OBJDUMP STREQUAL "") + execute_process( + COMMAND "${LLVM_OBJDUMP}" -dr --no-show-raw-insn "${OUTPUT}" + RESULT_VARIABLE ObjdumpResult + OUTPUT_VARIABLE ObjdumpOutput + ERROR_VARIABLE ObjdumpDiagnostics + ) + if(NOT ObjdumpResult EQUAL 0) + message(FATAL_ERROR "LLVM objdump rejected generated object: ${ObjdumpDiagnostics}") + endif() + if(NOT ObjdumpOutput MATCHES "file format elf64-littleriscv" OR + NOT ObjdumpOutput MATCHES "ret") + message(FATAL_ERROR "generated object does not contain RV64GC function code") + endif() + if(INPUT MATCHES "RISCVStackFramePipeline" AND + (NOT ObjdumpOutput MATCHES "addi[ \t]+sp, sp, -0x20" OR + NOT ObjdumpOutput MATCHES "addi[ \t]+sp, sp, 0x20")) + message(FATAL_ERROR "stack-frame object does not balance its stack pointer") + endif() +endif() + +file(REMOVE "${OUTPUT}" "${RepeatedOutput}") -- cgit v1.2.3