aboutsummaryrefslogtreecommitdiff
path: root/backend/tools
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-10 20:36:50 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-10 20:36:50 +0800
commitadcf8c9b1bd1b47e00bf859693112cea1f7e8c9f (patch)
tree59067aa71befab31678946dbf5e19f57131dd6f2 /backend/tools
parent3a57627e59ba18b6f9f9aae7bb60c390c0d3575a (diff)
downloadrcc-project-adcf8c9b1bd1b47e00bf859693112cea1f7e8c9f.tar.gz
rcc-project-adcf8c9b1bd1b47e00bf859693112cea1f7e8c9f.tar.xz
[Tooling] Complete backend tooling and validation
Diffstat (limited to 'backend/tools')
-rw-r--r--backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp72
-rw-r--r--backend/tools/CMakeLists.txt1
-rw-r--r--backend/tools/MIRTool/CMakeLists.txt91
-rw-r--r--backend/tools/MIRTool/MIRToolMain.cpp989
-rw-r--r--backend/tools/MIRTool/MIRToolOperationsTest.cmake260
-rw-r--r--backend/tools/MIRTool/MIRToolOutputTest.cmake119
-rw-r--r--backend/tools/MIRTool/MIRToolStageTest.cmake155
7 files changed, 1617 insertions, 70 deletions
diff --git a/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp
index 8697754..30193cd 100644
--- a/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp
+++ b/backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp
@@ -1,26 +1,19 @@
// 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/RISCVObjectFile.h"
+#include "rcc/Target/RISCV/RISCVPipeline.h"
#include "rcc/Target/RISCV/RISCVTarget.h"
#include <cstdint>
#include <fstream>
#include <iostream>
#include <iterator>
-#include <memory>
#include <string>
#include <string_view>
-#include <utility>
#include <vector>
namespace {
@@ -115,67 +108,6 @@ void printUsage() {
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[]) {
@@ -211,7 +143,7 @@ int main(int ArgumentCount, char *Arguments[]) {
std::cerr << "rcc-backend-asm: parsed document contains a null function\n";
return 1;
}
- auto Run = runBackendPipeline(*Function, *Target);
+ auto Run = rcc::riscv::runCanonicalPipeline(*Function, *Target);
if (!Run) {
std::cerr << "rcc-backend-asm: backend pipeline failed for '" << Function->name()
<< "': " << Run.error().message() << '\n';
diff --git a/backend/tools/CMakeLists.txt b/backend/tools/CMakeLists.txt
index 403f4a9..4c6b7f6 100644
--- a/backend/tools/CMakeLists.txt
+++ b/backend/tools/CMakeLists.txt
@@ -2,3 +2,4 @@
add_subdirectory(DescriptionGenerator)
add_subdirectory(AssemblyEmitter)
+add_subdirectory(MIRTool)
diff --git a/backend/tools/MIRTool/CMakeLists.txt b/backend/tools/MIRTool/CMakeLists.txt
new file mode 100644
index 0000000..3388481
--- /dev/null
+++ b/backend/tools/MIRTool/CMakeLists.txt
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+add_executable(RCCBackendMIRTool MIRToolMain.cpp)
+rcc_backend_configure_target(RCCBackendMIRTool)
+target_link_libraries(
+ RCCBackendMIRTool
+ PRIVATE
+ RCCBackend::Tooling
+ RCCBackend::RISCV
+)
+
+set_target_properties(
+ RCCBackendMIRTool
+ PROPERTIES
+ OUTPUT_NAME rcc-backend-mir
+)
+
+install(
+ TARGETS RCCBackendMIRTool
+ EXPORT RCCBackendTargets
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+)
+
+find_program(RCCBackendMIRToolLLVMMC NAMES llvm-mc-20 llvm-mc)
+find_program(RCCBackendMIRToolLLVMReadELF NAMES llvm-readelf-20 llvm-readelf)
+find_program(RCCBackendMIRToolLLVMObjdump NAMES llvm-objdump-20 llvm-objdump)
+
+set(RCCBackendMIRToolInputDirectory "${RCC_BACKEND_SOURCE_DIR}/unittests/Target/Inputs")
+set(RCCBackendMIRToolTestDirectory "${CMAKE_CURRENT_BINARY_DIR}/tests")
+
+add_test(
+ NAME RCCBackend.MIRTool.StagesAndPasses
+ COMMAND
+ "${CMAKE_COMMAND}"
+ "-DTOOL=$<TARGET_FILE:RCCBackendMIRTool>"
+ "-DINPUT=${RCCBackendMIRToolInputDirectory}/RISCVGenericPipeline.mir"
+ "-DWORK_DIRECTORY=${RCCBackendMIRToolTestDirectory}/stages"
+ -P "${CMAKE_CURRENT_SOURCE_DIR}/MIRToolStageTest.cmake"
+)
+
+foreach(
+ InputName
+ IN ITEMS
+ RISCVGenericPipeline
+ RISCVWideConstantPipeline
+ RISCVDataPipeline
+ RISCVStackFramePipeline
+)
+ add_test(
+ NAME RCCBackend.MIRTool.Assembly.${InputName}
+ COMMAND
+ "${CMAKE_COMMAND}"
+ "-DTOOL=$<TARGET_FILE:RCCBackendMIRTool>"
+ "-DINPUT=${RCCBackendMIRToolInputDirectory}/${InputName}.mir"
+ "-DOUTPUT=${RCCBackendMIRToolTestDirectory}/${InputName}.s"
+ -DOUTPUT_KIND=assembly
+ "-DLLVM_MC=${RCCBackendMIRToolLLVMMC}"
+ -P "${CMAKE_CURRENT_SOURCE_DIR}/MIRToolOutputTest.cmake"
+ )
+ add_test(
+ NAME RCCBackend.MIRTool.Object.${InputName}
+ COMMAND
+ "${CMAKE_COMMAND}"
+ "-DTOOL=$<TARGET_FILE:RCCBackendMIRTool>"
+ "-DINPUT=${RCCBackendMIRToolInputDirectory}/${InputName}.mir"
+ "-DOUTPUT=${RCCBackendMIRToolTestDirectory}/${InputName}.o"
+ -DOUTPUT_KIND=object
+ "-DLLVM_READELF=${RCCBackendMIRToolLLVMReadELF}"
+ "-DLLVM_OBJDUMP=${RCCBackendMIRToolLLVMObjdump}"
+ -P "${CMAKE_CURRENT_SOURCE_DIR}/MIRToolOutputTest.cmake"
+ )
+endforeach()
+
+add_test(
+ NAME RCCBackend.MIRTool.Operations
+ COMMAND
+ "${CMAKE_COMMAND}"
+ "-DTOOL=$<TARGET_FILE:RCCBackendMIRTool>"
+ "-DINPUT=${RCCBackendMIRToolInputDirectory}/RISCVGenericPipeline.mir"
+ "-DWORK_DIRECTORY=${RCCBackendMIRToolTestDirectory}/operations"
+ -P "${CMAKE_CURRENT_SOURCE_DIR}/MIRToolOperationsTest.cmake"
+)
+
+set_tests_properties(
+ RCCBackend.MIRTool.StagesAndPasses
+ RCCBackend.MIRTool.Operations
+ PROPERTIES TIMEOUT 180
+)
+
+rcc_backend_register_format_files("${CMAKE_CURRENT_SOURCE_DIR}/MIRToolMain.cpp")
+rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/MIRToolMain.cpp")
diff --git a/backend/tools/MIRTool/MIRToolMain.cpp b/backend/tools/MIRTool/MIRToolMain.cpp
new file mode 100644
index 0000000..eeeaac5
--- /dev/null
+++ b/backend/tools/MIRTool/MIRToolMain.cpp
@@ -0,0 +1,989 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/Support/Error.h"
+#include "rcc/Target/RISCV/RISCVPipeline.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+#include "rcc/Tooling/ArtifactStore.h"
+#include "rcc/Tooling/BackendDriver.h"
+#include "rcc/Tooling/BackendInvocation.h"
+#include "rcc/Tooling/MIRFuzzer.h"
+#include "rcc/Tooling/MIRReducer.h"
+
+#include <algorithm>
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <mutex>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+namespace {
+
+using rcc::Error;
+using rcc::ErrorCode;
+using rcc::Expected;
+using rcc::tooling::ArtifactKind;
+using rcc::tooling::BackendInvocation;
+using rcc::tooling::BackendOutputKind;
+using rcc::tooling::BackendRunResult;
+using rcc::tooling::CanonicalMIR;
+
+enum class Operation : std::uint8_t {
+ Run,
+ Verify,
+ Reproduce,
+ ReduceValidity,
+ ReduceFailure,
+ Fuzz,
+ ListPasses,
+};
+
+enum class CachePolicy : std::uint8_t {
+ Off,
+ Read,
+ Write,
+ ReadWrite,
+};
+
+struct CommandLine final {
+ Operation Action = Operation::Run;
+ CachePolicy Cache = CachePolicy::Off;
+ std::string InputPath;
+ std::string OutputPath = "-";
+ std::filesystem::path ReproducerPath;
+ std::filesystem::path CachePath;
+ BackendOutputKind Output = BackendOutputKind::FinalMIR;
+ BackendInvocation Invocation = rcc::tooling::defaultRV64GCInvocation();
+ rcc::tooling::PipelineControls Controls;
+ rcc::tooling::MIRFuzzOptions FuzzOptions;
+ bool OutputWasSpecified = false;
+ bool PrintActions = false;
+ bool ShowHelp = false;
+};
+
+void printUsage(std::ostream &Output) {
+ Output << "usage: rcc-backend-mir [operation] [options] <input.mir|->\n"
+ "operations:\n"
+ " --verify Parse, canonicalize, and verify MIR\n"
+ " --reproduce <package> Strictly replay a reproducer package\n"
+ " --reduce <validity|failure> Structurally reduce MIR\n"
+ " --fuzz <layer,...|all> Run deterministic layered fuzzing\n"
+ " --list-passes List the canonical pipeline suffix\n"
+ "pipeline/output:\n"
+ " --run-pass <name> Run one named pass\n"
+ " --passes <name,...> Run an explicit pass sequence\n"
+ " --stop-before <pass[#N]> Stop before a pass occurrence\n"
+ " --stop-after <pass[#N]> Stop after a pass occurrence\n"
+ " --print-before <pass[#N],...> Print snapshots before passes\n"
+ " --print-after <pass[#N],...> Print snapshots after passes\n"
+ " --stage-snapshots Print every stage boundary\n"
+ " --emit <stage|assembly|object> Select the output artifact\n"
+ " -o, --output <file|-> Select the output path\n"
+ "reproduction/cache/fuzz:\n"
+ " --crash-reproducer <path> Save input and manifest before execution\n"
+ " --cache-dir <path> Select the content-addressed cache root\n"
+ " --cache-policy <policy> off, read, write, or read-write\n"
+ " --fuzz-iterations <count> Cases per selected fuzz layer\n"
+ "execution identity:\n"
+ " --threads <count> Function worker count\n"
+ " --seed <value> Deterministic random seed\n"
+ " --fuel <value> Per-function Action fuel\n"
+ " --enable-action <name> Enable an Action ID component\n"
+ " --print-actions Print stable Action decisions\n"
+ " -O0|-O1|-O2|-O3|-Os|-Oz Optimization level identity\n"
+ " --relocation-model <static|pic>\n"
+ " --code-model <small|medium|large>\n"
+ " --layout <native-object|compiler-final-layout|external-relaxation>\n"
+ " --cpu/--tune-cpu/--features/--abi/--data-layout <value>\n"
+ " --target-option <name=value> Explicit target option\n"
+ " --assembler-option <name=value>\n"
+ " --assembler-flavor/--assembler-version <value>\n";
+}
+
+[[nodiscard]] Expected<std::optional<std::string_view>> takeOption(std::string_view Argument,
+ std::string_view Name,
+ int &Index, int ArgumentCount,
+ char *Arguments[]) {
+ if (Argument == Name) {
+ if (Index + 1 >= ArgumentCount) {
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ "missing value for command-line option '" + std::string(Name) +
+ "'");
+ }
+ return std::string_view(Arguments[++Index]);
+ }
+ std::string Prefix(Name);
+ Prefix.push_back('=');
+ if (Argument.starts_with(Prefix))
+ return Argument.substr(Prefix.size());
+ return std::nullopt;
+}
+
+template <typename IntegerT>
+[[nodiscard]] Expected<IntegerT> parseInteger(std::string_view Text, std::string_view Description) {
+ IntegerT Value = 0;
+ const auto Result = std::from_chars(Text.data(), Text.data() + Text.size(), Value);
+ if (Result.ec != std::errc{} || Result.ptr != Text.data() + Text.size()) {
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ std::string(Description) + " must be an unsigned decimal integer");
+ }
+ return Value;
+}
+
+[[nodiscard]] Expected<std::vector<std::string>> splitList(std::string_view Text,
+ std::string_view Description) {
+ std::vector<std::string> Result;
+ std::size_t Begin = 0;
+ while (Begin <= Text.size()) {
+ const std::size_t Comma = Text.find(',', Begin);
+ const std::size_t End = Comma == std::string_view::npos ? Text.size() : Comma;
+ if (End == Begin) {
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ std::string(Description) + " contains an empty element");
+ }
+ Result.emplace_back(Text.substr(Begin, End - Begin));
+ if (Comma == std::string_view::npos)
+ break;
+ Begin = Comma + 1U;
+ }
+ return Result;
+}
+
+[[nodiscard]] Expected<rcc::tooling::BackendOption> parseBackendOption(std::string_view Text,
+ std::string_view Category) {
+ const std::size_t Equal = Text.find('=');
+ if (Equal == std::string_view::npos || Equal == 0) {
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ std::string(Category) + " option must use name=value syntax");
+ }
+ return rcc::tooling::BackendOption{std::string(Text.substr(0, Equal)),
+ std::string(Text.substr(Equal + 1U))};
+}
+
+[[nodiscard]] Expected<void> setOperation(CommandLine &Command, Operation Action) {
+ if (Command.Action != Operation::Run && Command.Action != Action) {
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ "command-line operations are mutually exclusive");
+ }
+ Command.Action = Action;
+ return {};
+}
+
+[[nodiscard]] Expected<CachePolicy> parseCachePolicy(std::string_view Value) {
+ if (Value == "off")
+ return CachePolicy::Off;
+ if (Value == "read")
+ return CachePolicy::Read;
+ if (Value == "write")
+ return CachePolicy::Write;
+ if (Value == "read-write")
+ return CachePolicy::ReadWrite;
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation, "unknown cache policy");
+}
+
+[[nodiscard]] Expected<void> parseFuzzLayers(std::string_view Value, CommandLine &Command) {
+ auto OperationResult = setOperation(Command, Operation::Fuzz);
+ if (!OperationResult)
+ return OperationResult;
+ if (Value == "all") {
+ Command.FuzzOptions.Layers.clear();
+ return {};
+ }
+ auto Values = splitList(Value, "fuzz layer list");
+ if (!Values)
+ return std::unexpected(Values.error());
+ Command.FuzzOptions.Layers.clear();
+ for (const std::string &LayerName : *Values) {
+ auto Layer = rcc::tooling::parseMIRFuzzLayer(LayerName);
+ if (!Layer)
+ return std::unexpected(Layer.error());
+ Command.FuzzOptions.Layers.push_back(*Layer);
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<CommandLine> parseCommandLine(int ArgumentCount, char *Arguments[]) {
+ CommandLine Command;
+ bool ExplicitPipeline = false;
+ for (int Index = 1; Index < ArgumentCount; ++Index) {
+ const std::string_view Argument(Arguments[Index]);
+ if (Argument == "--help" || Argument == "-h") {
+ Command.ShowHelp = true;
+ continue;
+ }
+ if (Argument == "--verify") {
+ auto Set = setOperation(Command, Operation::Verify);
+ if (!Set)
+ return std::unexpected(Set.error());
+ continue;
+ }
+ if (Argument == "--list-passes") {
+ auto Set = setOperation(Command, Operation::ListPasses);
+ if (!Set)
+ return std::unexpected(Set.error());
+ continue;
+ }
+ if (Argument == "--stage-snapshots") {
+ Command.Controls.CaptureStageSnapshots = true;
+ continue;
+ }
+ if (Argument == "--no-verify-each") {
+ Command.Controls.VerifyEach = false;
+ continue;
+ }
+ if (Argument == "--no-change-detection") {
+ Command.Controls.DetectUnreportedChanges = false;
+ continue;
+ }
+ if (Argument == "--print-actions") {
+ Command.PrintActions = true;
+ continue;
+ }
+ if (Argument == "-O0" || Argument == "-O1" || Argument == "-O2" || Argument == "-O3" ||
+ Argument == "-Os" || Argument == "-Oz") {
+ auto Level = rcc::tooling::parseOptimizationLevel(Argument.substr(1));
+ if (!Level)
+ return std::unexpected(Level.error());
+ Command.Invocation.Optimization = *Level;
+ continue;
+ }
+
+ auto Input = takeOption(Argument, "--input", Index, ArgumentCount, Arguments);
+ if (!Input)
+ return std::unexpected(Input.error());
+ if (*Input) {
+ if (!Command.InputPath.empty())
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation, "input path is specified twice");
+ Command.InputPath = std::string(**Input);
+ continue;
+ }
+ auto Output = takeOption(Argument, "--output", Index, ArgumentCount, Arguments);
+ if (!Output)
+ return std::unexpected(Output.error());
+ if (!*Output && Argument == "-o")
+ Output = takeOption(Argument, "-o", Index, ArgumentCount, Arguments);
+ if (!Output)
+ return std::unexpected(Output.error());
+ if (*Output) {
+ Command.OutputPath = std::string(**Output);
+ Command.OutputWasSpecified = true;
+ continue;
+ }
+ auto Emit = takeOption(Argument, "--emit", Index, ArgumentCount, Arguments);
+ if (!Emit)
+ return std::unexpected(Emit.error());
+ if (*Emit) {
+ auto Kind = rcc::tooling::parseBackendOutputKind(**Emit);
+ if (!Kind)
+ return std::unexpected(Kind.error());
+ Command.Output = *Kind;
+ Command.OutputWasSpecified = true;
+ continue;
+ }
+ auto RunPass = takeOption(Argument, "--run-pass", Index, ArgumentCount, Arguments);
+ if (!RunPass)
+ return std::unexpected(RunPass.error());
+ if (*RunPass) {
+ if (ExplicitPipeline)
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ "--run-pass and --passes cannot be combined");
+ Command.Invocation.PassPipeline = {std::string(**RunPass)};
+ ExplicitPipeline = true;
+ continue;
+ }
+ auto Passes = takeOption(Argument, "--passes", Index, ArgumentCount, Arguments);
+ if (!Passes)
+ return std::unexpected(Passes.error());
+ if (*Passes) {
+ if (ExplicitPipeline)
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ "--run-pass and --passes cannot be combined");
+ auto Names = splitList(**Passes, "pass pipeline");
+ if (!Names)
+ return std::unexpected(Names.error());
+ Command.Invocation.PassPipeline = std::move(*Names);
+ ExplicitPipeline = true;
+ continue;
+ }
+
+ const auto ParseSelector = [&](std::string_view Name,
+ std::optional<std::string> &Destination) -> Expected<bool> {
+ auto Value = takeOption(Argument, Name, Index, ArgumentCount, Arguments);
+ if (!Value)
+ return std::unexpected(Value.error());
+ if (!*Value)
+ return false;
+ Destination = std::string(**Value);
+ return true;
+ };
+ auto StopBefore = ParseSelector("--stop-before", Command.Controls.StopBefore);
+ if (!StopBefore)
+ return std::unexpected(StopBefore.error());
+ if (*StopBefore)
+ continue;
+ auto StopAfter = ParseSelector("--stop-after", Command.Controls.StopAfter);
+ if (!StopAfter)
+ return std::unexpected(StopAfter.error());
+ if (*StopAfter)
+ continue;
+
+ const auto ParseSelectorList = [&](std::string_view Name,
+ std::vector<std::string> &Destination) -> Expected<bool> {
+ auto Value = takeOption(Argument, Name, Index, ArgumentCount, Arguments);
+ if (!Value)
+ return std::unexpected(Value.error());
+ if (!*Value)
+ return false;
+ auto Selectors = splitList(**Value, Name);
+ if (!Selectors)
+ return std::unexpected(Selectors.error());
+ Destination.insert(Destination.end(), std::make_move_iterator(Selectors->begin()),
+ std::make_move_iterator(Selectors->end()));
+ return true;
+ };
+ auto PrintBefore = ParseSelectorList("--print-before", Command.Controls.PrintBefore);
+ if (!PrintBefore)
+ return std::unexpected(PrintBefore.error());
+ if (*PrintBefore)
+ continue;
+ auto PrintAfter = ParseSelectorList("--print-after", Command.Controls.PrintAfter);
+ if (!PrintAfter)
+ return std::unexpected(PrintAfter.error());
+ if (*PrintAfter)
+ continue;
+
+ auto Reproduce = takeOption(Argument, "--reproduce", Index, ArgumentCount, Arguments);
+ if (!Reproduce)
+ return std::unexpected(Reproduce.error());
+ if (*Reproduce) {
+ auto Set = setOperation(Command, Operation::Reproduce);
+ if (!Set)
+ return std::unexpected(Set.error());
+ Command.ReproducerPath = std::string(**Reproduce);
+ continue;
+ }
+ auto Reduce = takeOption(Argument, "--reduce", Index, ArgumentCount, Arguments);
+ if (!Reduce)
+ return std::unexpected(Reduce.error());
+ if (*Reduce) {
+ Operation Mode;
+ if (**Reduce == "validity")
+ Mode = Operation::ReduceValidity;
+ else if (**Reduce == "failure")
+ Mode = Operation::ReduceFailure;
+ else
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation, "unknown reducer mode");
+ auto Set = setOperation(Command, Mode);
+ if (!Set)
+ return std::unexpected(Set.error());
+ continue;
+ }
+ auto Fuzz = takeOption(Argument, "--fuzz", Index, ArgumentCount, Arguments);
+ if (!Fuzz)
+ return std::unexpected(Fuzz.error());
+ if (*Fuzz) {
+ auto Parsed = parseFuzzLayers(**Fuzz, Command);
+ if (!Parsed)
+ return std::unexpected(Parsed.error());
+ continue;
+ }
+ auto FuzzIterations =
+ takeOption(Argument, "--fuzz-iterations", Index, ArgumentCount, Arguments);
+ if (!FuzzIterations)
+ return std::unexpected(FuzzIterations.error());
+ if (*FuzzIterations) {
+ auto Count = parseInteger<std::size_t>(**FuzzIterations, "fuzz iteration count");
+ if (!Count)
+ return std::unexpected(Count.error());
+ Command.FuzzOptions.Iterations = *Count;
+ continue;
+ }
+ auto CrashReproducer =
+ takeOption(Argument, "--crash-reproducer", Index, ArgumentCount, Arguments);
+ if (!CrashReproducer)
+ return std::unexpected(CrashReproducer.error());
+ if (*CrashReproducer) {
+ Command.ReproducerPath = std::string(**CrashReproducer);
+ continue;
+ }
+ auto CacheDirectory = takeOption(Argument, "--cache-dir", Index, ArgumentCount, Arguments);
+ if (!CacheDirectory)
+ return std::unexpected(CacheDirectory.error());
+ if (*CacheDirectory) {
+ Command.CachePath = std::string(**CacheDirectory);
+ continue;
+ }
+ auto CacheMode = takeOption(Argument, "--cache-policy", Index, ArgumentCount, Arguments);
+ if (!CacheMode)
+ return std::unexpected(CacheMode.error());
+ if (*CacheMode) {
+ auto Policy = parseCachePolicy(**CacheMode);
+ if (!Policy)
+ return std::unexpected(Policy.error());
+ Command.Cache = *Policy;
+ continue;
+ }
+
+ const auto ParseUnsigned = [&](std::string_view Name, auto &Destination,
+ std::string_view Description) -> Expected<bool> {
+ auto Value = takeOption(Argument, Name, Index, ArgumentCount, Arguments);
+ if (!Value)
+ return std::unexpected(Value.error());
+ if (!*Value)
+ return false;
+ using IntegerT = std::remove_reference_t<decltype(Destination)>;
+ auto Parsed = parseInteger<IntegerT>(**Value, Description);
+ if (!Parsed)
+ return std::unexpected(Parsed.error());
+ Destination = *Parsed;
+ return true;
+ };
+ auto Threads =
+ ParseUnsigned("--threads", Command.Invocation.Determinism.ThreadCount, "thread count");
+ if (!Threads)
+ return std::unexpected(Threads.error());
+ if (*Threads)
+ continue;
+ auto Seed = ParseUnsigned("--seed", Command.Invocation.Determinism.RandomSeed, "random seed");
+ if (!Seed)
+ return std::unexpected(Seed.error());
+ if (*Seed) {
+ Command.FuzzOptions.Seed = Command.Invocation.Determinism.RandomSeed;
+ continue;
+ }
+ auto Fuel = takeOption(Argument, "--fuel", Index, ArgumentCount, Arguments);
+ if (!Fuel)
+ return std::unexpected(Fuel.error());
+ if (*Fuel) {
+ auto Value = parseInteger<std::uint64_t>(**Fuel, "Action fuel");
+ if (!Value)
+ return std::unexpected(Value.error());
+ Command.Invocation.ActionFuel = *Value;
+ continue;
+ }
+ auto EnabledAction = takeOption(Argument, "--enable-action", Index, ArgumentCount, Arguments);
+ if (!EnabledAction)
+ return std::unexpected(EnabledAction.error());
+ if (*EnabledAction) {
+ Command.Invocation.EnabledActions.emplace_back(**EnabledAction);
+ continue;
+ }
+
+ const auto ParseString = [&](std::string_view Name,
+ std::string &Destination) -> Expected<bool> {
+ auto Value = takeOption(Argument, Name, Index, ArgumentCount, Arguments);
+ if (!Value)
+ return std::unexpected(Value.error());
+ if (!*Value)
+ return false;
+ Destination = std::string(**Value);
+ return true;
+ };
+ auto CPU = ParseString("--cpu", Command.Invocation.CPU);
+ if (!CPU)
+ return std::unexpected(CPU.error());
+ if (*CPU)
+ continue;
+ auto TuneCPU = ParseString("--tune-cpu", Command.Invocation.TuneCPU);
+ if (!TuneCPU)
+ return std::unexpected(TuneCPU.error());
+ if (*TuneCPU)
+ continue;
+ auto ABI = ParseString("--abi", Command.Invocation.ABI);
+ if (!ABI)
+ return std::unexpected(ABI.error());
+ if (*ABI)
+ continue;
+ auto DataLayout = ParseString("--data-layout", Command.Invocation.DataLayout);
+ if (!DataLayout)
+ return std::unexpected(DataLayout.error());
+ if (*DataLayout)
+ continue;
+ auto AssemblerFlavor = ParseString("--assembler-flavor", Command.Invocation.AssemblerFlavor);
+ if (!AssemblerFlavor)
+ return std::unexpected(AssemblerFlavor.error());
+ if (*AssemblerFlavor)
+ continue;
+ auto AssemblerVersion = ParseString("--assembler-version", Command.Invocation.AssemblerVersion);
+ if (!AssemblerVersion)
+ return std::unexpected(AssemblerVersion.error());
+ if (*AssemblerVersion)
+ continue;
+ auto Features = takeOption(Argument, "--features", Index, ArgumentCount, Arguments);
+ if (!Features)
+ return std::unexpected(Features.error());
+ if (*Features) {
+ auto Values = splitList(**Features, "target feature list");
+ if (!Values)
+ return std::unexpected(Values.error());
+ Command.Invocation.Features = std::move(*Values);
+ continue;
+ }
+ auto Relocation = takeOption(Argument, "--relocation-model", Index, ArgumentCount, Arguments);
+ if (!Relocation)
+ return std::unexpected(Relocation.error());
+ if (*Relocation) {
+ auto Value = rcc::tooling::parseRelocationModel(**Relocation);
+ if (!Value)
+ return std::unexpected(Value.error());
+ Command.Invocation.Relocation = *Value;
+ continue;
+ }
+ auto Code = takeOption(Argument, "--code-model", Index, ArgumentCount, Arguments);
+ if (!Code)
+ return std::unexpected(Code.error());
+ if (*Code) {
+ auto Value = rcc::tooling::parseCodeModel(**Code);
+ if (!Value)
+ return std::unexpected(Value.error());
+ Command.Invocation.Code = *Value;
+ continue;
+ }
+ auto Layout = takeOption(Argument, "--layout", Index, ArgumentCount, Arguments);
+ if (!Layout)
+ return std::unexpected(Layout.error());
+ if (*Layout) {
+ auto Value = rcc::tooling::parseLayoutOwnership(**Layout);
+ if (!Value)
+ return std::unexpected(Value.error());
+ Command.Invocation.Layout = *Value;
+ continue;
+ }
+ auto TargetOption = takeOption(Argument, "--target-option", Index, ArgumentCount, Arguments);
+ if (!TargetOption)
+ return std::unexpected(TargetOption.error());
+ if (*TargetOption) {
+ auto Value = parseBackendOption(**TargetOption, "target");
+ if (!Value)
+ return std::unexpected(Value.error());
+ const auto Existing = std::ranges::find(Command.Invocation.TargetOptions, Value->Name,
+ &rcc::tooling::BackendOption::Name);
+ if (Existing == Command.Invocation.TargetOptions.end())
+ Command.Invocation.TargetOptions.push_back(std::move(*Value));
+ else
+ Existing->Value = std::move(Value->Value);
+ continue;
+ }
+ auto AssemblerOption =
+ takeOption(Argument, "--assembler-option", Index, ArgumentCount, Arguments);
+ if (!AssemblerOption)
+ return std::unexpected(AssemblerOption.error());
+ if (*AssemblerOption) {
+ auto Value = parseBackendOption(**AssemblerOption, "assembler");
+ if (!Value)
+ return std::unexpected(Value.error());
+ const auto Existing = std::ranges::find(Command.Invocation.AssemblerOptions, Value->Name,
+ &rcc::tooling::BackendOption::Name);
+ if (Existing == Command.Invocation.AssemblerOptions.end())
+ Command.Invocation.AssemblerOptions.push_back(std::move(*Value));
+ else
+ Existing->Value = std::move(Value->Value);
+ continue;
+ }
+
+ if (!Argument.starts_with('-')) {
+ if (!Command.InputPath.empty())
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation, "input path is specified twice");
+ Command.InputPath = std::string(Argument);
+ continue;
+ }
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ "unknown command-line option '" + std::string(Argument) + "'");
+ }
+
+ if (Command.ShowHelp)
+ return Command;
+ if (Command.Action == Operation::Reproduce) {
+ if (!Command.InputPath.empty())
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ "reproducer replay does not accept a separate MIR input");
+ } else if (Command.InputPath.empty()) {
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation, "a MIR input path is required");
+ }
+ if (Command.Cache != CachePolicy::Off && Command.CachePath.empty()) {
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ "a non-off cache policy requires --cache-dir");
+ }
+ if (Command.Action != Operation::Run && Command.Action != Operation::Reproduce &&
+ Command.Cache != CachePolicy::Off) {
+ return rcc::makeUnexpected(ErrorCode::InvalidInvocation,
+ "cache policies only apply to run and reproduce operations");
+ }
+ auto Canonical = Command.Invocation.canonicalizeAndVerify();
+ if (!Canonical)
+ return std::unexpected(Canonical.error());
+ return Command;
+}
+
+[[nodiscard]] Expected<std::string> readInput(std::string_view Path) {
+ if (Path == "-")
+ return std::string(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
+ std::ifstream Input(std::string(Path), std::ios::binary);
+ if (!Input)
+ return rcc::makeUnexpected(ErrorCode::IoError,
+ "cannot open input file '" + std::string(Path) + "'");
+ return std::string(std::istreambuf_iterator<char>(Input), std::istreambuf_iterator<char>());
+}
+
+[[nodiscard]] Expected<void> writeOutput(std::string_view Path,
+ std::span<const std::uint8_t> Bytes) {
+ if (Path == "-") {
+ if (!Bytes.empty()) {
+ std::cout.write(reinterpret_cast<const char *>(Bytes.data()),
+ static_cast<std::streamsize>(Bytes.size()));
+ }
+ if (!std::cout)
+ return rcc::makeUnexpected(ErrorCode::IoError, "cannot write standard output");
+ return {};
+ }
+ std::ofstream Output(std::string(Path), std::ios::binary | std::ios::trunc);
+ if (!Output)
+ return rcc::makeUnexpected(ErrorCode::IoError,
+ "cannot open output file '" + std::string(Path) + "'");
+ if (!Bytes.empty()) {
+ Output.write(reinterpret_cast<const char *>(Bytes.data()),
+ static_cast<std::streamsize>(Bytes.size()));
+ }
+ Output.close();
+ if (!Output)
+ return rcc::makeUnexpected(ErrorCode::IoError,
+ "cannot write output file '" + std::string(Path) + "'");
+ return {};
+}
+
+[[nodiscard]] Expected<void> writeText(std::string_view Path, std::string_view Text) {
+ const auto *Bytes = reinterpret_cast<const std::uint8_t *>(Text.data());
+ return writeOutput(Path, std::span<const std::uint8_t>(Bytes, Text.size()));
+}
+
+[[nodiscard]] Expected<BackendOutputKind> outputFromArtifact(ArtifactKind Artifact) {
+ const auto Value = static_cast<BackendOutputKind>(static_cast<std::uint8_t>(Artifact));
+ if (rcc::tooling::spelling(Value) == "invalid")
+ return rcc::makeUnexpected(ErrorCode::ReproducerError, "invalid reproducer artifact kind");
+ return Value;
+}
+
+void printSnapshots(const BackendRunResult &Result) {
+ for (const rcc::tooling::PipelineSnapshot &Snapshot : Result.Pipeline.Snapshots) {
+ std::cerr << "*** " << rcc::tooling::spelling(Snapshot.Stage);
+ if (!Snapshot.Pass.empty())
+ std::cerr << ' ' << Snapshot.Pass << '#' << Snapshot.Occurrence;
+ if (!Snapshot.Function.empty())
+ std::cerr << " [" << Snapshot.Function << ']';
+ std::cerr << " ***\n" << Snapshot.MIR;
+ if (!Snapshot.MIR.ends_with('\n'))
+ std::cerr << '\n';
+ }
+}
+
+void printActions(const BackendRunResult &Result) {
+ for (const rcc::MachineActionEvent &Action : Result.Actions) {
+ std::cerr << (Action.Accepted ? "accepted " : "rejected ") << Action.ID.toString()
+ << " cost=" << Action.Cost << '\n';
+ }
+}
+
+[[nodiscard]] Expected<void> writePreExecutionReproducer(const CommandLine &Command,
+ const CanonicalMIR &Input,
+ const rcc::TargetDescription &Target) {
+ if (Command.ReproducerPath.empty())
+ return {};
+ auto Manifest = rcc::tooling::createBackendManifest(
+ rcc::tooling::ManifestKind::Reproducer, rcc::tooling::manifestArtifactKind(Command.Output),
+ Command.Invocation, Target, Input.Text);
+ if (!Manifest)
+ return std::unexpected(Manifest.error());
+ return rcc::tooling::writeReproducer(Command.ReproducerPath, *Manifest, Input.Text);
+}
+
+[[nodiscard]] Expected<BackendRunResult>
+runWithCache(const CommandLine &Command, CanonicalMIR Input, const rcc::TargetDescription &Target,
+ rcc::MachinePassInstrumentation *Instrumentation = nullptr) {
+ if (Command.Cache == CachePolicy::Off) {
+ return rcc::tooling::runBackend(std::move(Input), Target, Command.Invocation, Command.Output,
+ Command.Controls, Instrumentation);
+ }
+ auto Kind = rcc::tooling::cacheArtifactKind(Command.Output);
+ if (!Kind)
+ return std::unexpected(Kind.error());
+ const ArtifactKind CacheKind = *Kind;
+ if (Command.Cache == CachePolicy::Read || Command.Cache == CachePolicy::ReadWrite) {
+ auto Cached = rcc::tooling::loadCacheEntry(Command.CachePath, CacheKind, Command.Invocation,
+ Target, Input.Text);
+ if (Cached) {
+ return BackendRunResult{
+ .Kind = Command.Output,
+ .Output = std::move(Cached->Artifact),
+ .Pipeline = {},
+ .Actions = {},
+ };
+ }
+ if (Command.Cache == CachePolicy::Read || Cached.error().code() != ErrorCode::CacheMiss)
+ return std::unexpected(Cached.error());
+ }
+
+ const std::string CanonicalInput = Input.Text;
+ auto Run = rcc::tooling::runBackend(std::move(Input), Target, Command.Invocation, Command.Output,
+ Command.Controls, Instrumentation);
+ if (!Run)
+ return std::unexpected(Run.error());
+ if (Command.Cache == CachePolicy::Write || Command.Cache == CachePolicy::ReadWrite) {
+ auto Manifest = rcc::tooling::createBackendManifest(
+ rcc::tooling::ManifestKind::Cache, CacheKind, Command.Invocation, Target, CanonicalInput,
+ std::span<const std::uint8_t>(Run->Output));
+ if (!Manifest)
+ return std::unexpected(Manifest.error());
+ auto Stored =
+ rcc::tooling::storeCacheEntry(Command.CachePath, *Manifest, CanonicalInput, Run->Output);
+ if (!Stored)
+ return std::unexpected(Stored.error());
+ }
+ return Run;
+}
+
+[[nodiscard]] Expected<void> runCommand(const CommandLine &OriginalCommand) {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return std::unexpected(Target.error());
+ CommandLine Command = OriginalCommand;
+
+ if (Command.Action == Operation::Reproduce) {
+ auto Package = rcc::tooling::loadReproducer(Command.ReproducerPath, *Target);
+ if (!Package)
+ return std::unexpected(Package.error());
+ auto Output = outputFromArtifact(Package->Manifest.Artifact);
+ if (!Output)
+ return std::unexpected(Output.error());
+ Command.Output = *Output;
+ Command.Invocation = Package->Manifest.Invocation;
+ Command.ReproducerPath.clear();
+ auto Run = runWithCache(Command, std::move(Package->Input), *Target);
+ if (!Run)
+ return std::unexpected(Run.error());
+ printSnapshots(*Run);
+ if (Command.PrintActions)
+ printActions(*Run);
+ return writeOutput(Command.OutputPath, Run->Output);
+ }
+
+ auto Source = readInput(Command.InputPath);
+ if (!Source)
+ return std::unexpected(Source.error());
+ auto Input = rcc::tooling::parseAndCanonicalizeMIR(Command.InputPath, std::move(*Source), *Target,
+ Command.Invocation);
+ if (!Input)
+ return std::unexpected(Input.error());
+
+ if (Command.Action == Operation::Verify) {
+ if (Command.OutputWasSpecified)
+ return writeText(Command.OutputPath, Input->Text);
+ return {};
+ }
+ if (Command.Action == Operation::ListPasses) {
+ std::vector<std::string> Names;
+ for (std::size_t Index = 0; Index < Input->Document.unit().functionCount(); ++Index) {
+ const rcc::MachineFunction *Function = Input->Document.unit().functionAt(Index);
+ if (Function == nullptr)
+ return rcc::makeUnexpected(ErrorCode::InternalError, "MIR input contains a null function");
+ auto FunctionNames = rcc::riscv::canonicalPipelinePassNames(*Function);
+ if (!FunctionNames)
+ return std::unexpected(FunctionNames.error());
+ for (std::string &Name : *FunctionNames) {
+ if (std::ranges::find(Names, Name) == Names.end())
+ Names.push_back(std::move(Name));
+ }
+ }
+ std::string Output;
+ for (const std::string &Name : Names) {
+ Output.append(Name);
+ Output.push_back('\n');
+ }
+ return writeText(Command.OutputPath, Output);
+ }
+ if (Command.Action == Operation::Fuzz) {
+ auto Result = rcc::tooling::fuzzMIR(Command.InputPath, Input->Text, Command.Invocation, *Target,
+ Command.FuzzOptions);
+ if (!Result)
+ return std::unexpected(Result.error());
+ std::string Output = "seed=" + std::to_string(Result->Seed) +
+ " executed=" + std::to_string(Result->executed()) +
+ " accepted=" + std::to_string(Result->accepted()) +
+ " rejected=" + std::to_string(Result->rejected()) + "\n";
+ for (const rcc::tooling::MIRFuzzLayerResult &Layer : Result->Layers) {
+ Output.append(rcc::tooling::spelling(Layer.Layer));
+ Output.append(": executed=");
+ Output.append(std::to_string(Layer.Executed));
+ Output.append(" accepted=");
+ Output.append(std::to_string(Layer.Accepted));
+ Output.append(" rejected=");
+ Output.append(std::to_string(Layer.Rejected));
+ Output.push_back('\n');
+ }
+ return writeText(Command.OutputPath, Output);
+ }
+ if (Command.Action == Operation::ReduceValidity) {
+ rcc::tooling::MIRReducerOptions Options;
+ Options.Mode = rcc::tooling::ReductionMode::PreserveValidity;
+ auto Reduced = rcc::tooling::reduceMIR(Command.InputPath, Input->Text, Command.Invocation,
+ *Target, {}, std::move(Options));
+ if (!Reduced)
+ return std::unexpected(Reduced.error());
+ return writeText(Command.OutputPath, Reduced->MIR);
+ }
+ if (Command.Action == Operation::ReduceFailure) {
+ auto InitialForRun = rcc::tooling::parseAndCanonicalizeMIR("reducer-initial.mir", Input->Text,
+ *Target, Command.Invocation);
+ if (!InitialForRun)
+ return std::unexpected(InitialForRun.error());
+ auto InitialRun = rcc::tooling::runBackend(
+ std::move(*InitialForRun), *Target, Command.Invocation, Command.Output, Command.Controls);
+ if (InitialRun) {
+ return rcc::makeUnexpected(ErrorCode::ReductionFailed,
+ "failure-preserving reduction requires an initially failing run");
+ }
+ const ErrorCode FailureCode = InitialRun.error().code();
+ const auto Interesting = [&](std::string_view MIR,
+ const BackendInvocation &Invocation) -> Expected<bool> {
+ auto Candidate = rcc::tooling::parseAndCanonicalizeMIR("reducer-interesting.mir",
+ std::string(MIR), *Target, Invocation);
+ if (!Candidate)
+ return std::unexpected(Candidate.error());
+ auto Run = rcc::tooling::runBackend(std::move(*Candidate), *Target, Invocation,
+ Command.Output, Command.Controls);
+ return !Run && Run.error().code() == FailureCode;
+ };
+ rcc::tooling::MIRReducerOptions Options;
+ Options.Mode = rcc::tooling::ReductionMode::PreserveFailure;
+ auto Reduced = rcc::tooling::reduceMIR(Command.InputPath, Input->Text, Command.Invocation,
+ *Target, Interesting, std::move(Options));
+ if (!Reduced)
+ return std::unexpected(Reduced.error());
+ return writeText(Command.OutputPath, Reduced->MIR);
+ }
+
+ auto Reproducer = writePreExecutionReproducer(Command, *Input, *Target);
+ if (!Reproducer)
+ return std::unexpected(Reproducer.error());
+
+ std::vector<rcc::tooling::StructuredDiagnostic> FailureDiagnostics;
+ std::mutex FailureDiagnosticsMutex;
+ rcc::MachinePassInstrumentation Instrumentation;
+ const auto RecordFunctionFailure = [&](std::string_view Rule, std::string_view Pass,
+ const rcc::MachineFunction &Function,
+ const Error &Failure) {
+ rcc::tooling::StructuredDiagnostic Diagnostic;
+ Diagnostic.Category = std::string(rcc::getErrorCodeName(Failure.code()));
+ Diagnostic.Stage =
+ std::string(rcc::tooling::spelling(rcc::tooling::stageForFunction(Function)));
+ Diagnostic.Pass = std::string(Pass);
+ Diagnostic.Function = std::string(Function.name());
+ Diagnostic.Rule = std::string(Rule);
+ Diagnostic.Message = std::string(Failure.message());
+ std::scoped_lock Lock(FailureDiagnosticsMutex);
+ FailureDiagnostics.push_back(std::move(Diagnostic));
+ };
+ Instrumentation.addFailureCallback(
+ [&](std::string_view Pass, const rcc::MachineFunction &Function, const Error &Failure) {
+ RecordFunctionFailure("machine-pass", Pass, Function, Failure);
+ });
+ Instrumentation.addAnalysisFailureCallback(
+ [&](std::string_view Analysis, const rcc::MachineFunction &Function, const Error &Failure) {
+ RecordFunctionFailure("machine-analysis", Analysis, Function, Failure);
+ });
+ Instrumentation.addUnitFailureCallback(
+ [&](std::string_view Pass, const rcc::CompilationUnit &, const Error &Failure) {
+ rcc::tooling::StructuredDiagnostic Diagnostic;
+ Diagnostic.Category = std::string(rcc::getErrorCodeName(Failure.code()));
+ Diagnostic.Stage = "compilation-unit";
+ Diagnostic.Pass = std::string(Pass);
+ Diagnostic.Rule = "compilation-unit-pass";
+ Diagnostic.Message = std::string(Failure.message());
+ std::scoped_lock Lock(FailureDiagnosticsMutex);
+ FailureDiagnostics.push_back(std::move(Diagnostic));
+ });
+ Instrumentation.addUnitAnalysisFailureCallback(
+ [&](std::string_view Analysis, const rcc::CompilationUnit &, const Error &Failure) {
+ rcc::tooling::StructuredDiagnostic Diagnostic;
+ Diagnostic.Category = std::string(rcc::getErrorCodeName(Failure.code()));
+ Diagnostic.Stage = "compilation-unit";
+ Diagnostic.Pass = std::string(Analysis);
+ Diagnostic.Rule = "compilation-unit-analysis";
+ Diagnostic.Message = std::string(Failure.message());
+ std::scoped_lock Lock(FailureDiagnosticsMutex);
+ FailureDiagnostics.push_back(std::move(Diagnostic));
+ });
+
+ auto Run = runWithCache(Command, std::move(*Input), *Target,
+ Command.ReproducerPath.empty() ? nullptr : &Instrumentation);
+ if (!Run) {
+ if (!Command.ReproducerPath.empty()) {
+ if (FailureDiagnostics.empty()) {
+ rcc::tooling::StructuredDiagnostic Diagnostic;
+ Diagnostic.Category = std::string(rcc::getErrorCodeName(Run.error().code()));
+ Diagnostic.Stage = "driver";
+ Diagnostic.Rule = "backend-driver";
+ Diagnostic.Message = std::string(Run.error().message());
+ FailureDiagnostics.push_back(std::move(Diagnostic));
+ }
+ std::ranges::sort(FailureDiagnostics, [](const auto &Left, const auto &Right) {
+ if (Left.Function != Right.Function)
+ return Left.Function < Right.Function;
+ if (Left.Stage != Right.Stage)
+ return Left.Stage < Right.Stage;
+ if (Left.Pass != Right.Pass)
+ return Left.Pass < Right.Pass;
+ if (Left.Rule != Right.Rule)
+ return Left.Rule < Right.Rule;
+ if (Left.Category != Right.Category)
+ return Left.Category < Right.Category;
+ return Left.Message < Right.Message;
+ });
+ auto Updated = rcc::tooling::updateReproducerDiagnostics(Command.ReproducerPath,
+ std::move(FailureDiagnostics));
+ if (!Updated) {
+ return rcc::makeUnexpected(ErrorCode::ReproducerError,
+ "backend failed with '" + std::string(Run.error().message()) +
+ "' and reproducer diagnostic update failed with '" +
+ std::string(Updated.error().message()) + "'");
+ }
+ }
+ return std::unexpected(Run.error());
+ }
+ printSnapshots(*Run);
+ if (Command.PrintActions)
+ printActions(*Run);
+ return writeOutput(Command.OutputPath, Run->Output);
+}
+
+} // namespace
+
+int main(int ArgumentCount, char *Arguments[]) {
+ auto Command = parseCommandLine(ArgumentCount, Arguments);
+ if (!Command) {
+ std::cerr << "rcc-backend-mir: " << Command.error().message() << '\n';
+ printUsage(std::cerr);
+ return 1;
+ }
+ if (Command->ShowHelp) {
+ printUsage(std::cout);
+ return 0;
+ }
+ auto Result = runCommand(*Command);
+ if (!Result) {
+ std::cerr << "rcc-backend-mir: " << rcc::getErrorCodeName(Result.error().code()) << ": "
+ << Result.error().message() << '\n';
+ return 1;
+ }
+ return 0;
+}
diff --git a/backend/tools/MIRTool/MIRToolOperationsTest.cmake b/backend/tools/MIRTool/MIRToolOperationsTest.cmake
new file mode 100644
index 0000000..097e571
--- /dev/null
+++ b/backend/tools/MIRTool/MIRToolOperationsTest.cmake
@@ -0,0 +1,260 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+if(NOT DEFINED TOOL OR NOT DEFINED INPUT OR NOT DEFINED WORK_DIRECTORY)
+ message(FATAL_ERROR "MIR tool operations test is missing a required path")
+endif()
+
+file(REMOVE_RECURSE "${WORK_DIRECTORY}")
+file(MAKE_DIRECTORY "${WORK_DIRECTORY}")
+
+set(CacheDirectory "${WORK_DIRECTORY}/cache")
+set(CachedAssembly "${WORK_DIRECTORY}/cached.s")
+set(ReloadedAssembly "${WORK_DIRECTORY}/reloaded.s")
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --emit assembly --cache-dir "${CacheDirectory}"
+ --cache-policy write --output "${CachedAssembly}"
+ RESULT_VARIABLE CacheWriteResult
+ ERROR_VARIABLE CacheWriteDiagnostics
+)
+if(NOT CacheWriteResult EQUAL 0)
+ message(FATAL_ERROR "cache write failed: ${CacheWriteDiagnostics}")
+endif()
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --emit assembly --cache-dir "${CacheDirectory}"
+ --cache-policy read --output "${ReloadedAssembly}"
+ RESULT_VARIABLE CacheReadResult
+ ERROR_VARIABLE CacheReadDiagnostics
+)
+if(NOT CacheReadResult EQUAL 0)
+ message(FATAL_ERROR "cache hit reload failed: ${CacheReadDiagnostics}")
+endif()
+execute_process(
+ COMMAND "${CMAKE_COMMAND}" -E compare_files "${CachedAssembly}" "${ReloadedAssembly}"
+ RESULT_VARIABLE CacheComparisonResult
+)
+if(NOT CacheComparisonResult EQUAL 0)
+ message(FATAL_ERROR "cache hit changed the artifact bytes")
+endif()
+
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --emit assembly --cache-dir
+ "${WORK_DIRECTORY}/empty-cache" --cache-policy read --output "${WORK_DIRECTORY}/miss.s"
+ RESULT_VARIABLE CacheMissResult
+ ERROR_VARIABLE CacheMissDiagnostics
+)
+if(CacheMissResult EQUAL 0 OR NOT CacheMissDiagnostics MATCHES "cache miss")
+ message(FATAL_ERROR "strict cache read did not report a cache miss")
+endif()
+
+file(GLOB_RECURSE CachedArtifacts "${CacheDirectory}/artifact.s")
+list(LENGTH CachedArtifacts CachedArtifactCount)
+if(NOT CachedArtifactCount EQUAL 1)
+ message(FATAL_ERROR "cache publication did not create exactly one assembly artifact")
+endif()
+list(GET CachedArtifacts 0 CachedArtifact)
+file(WRITE "${CachedArtifact}" "corrupt")
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --emit assembly --cache-dir "${CacheDirectory}"
+ --cache-policy read --output "${WORK_DIRECTORY}/corrupt.s"
+ RESULT_VARIABLE CorruptCacheResult
+ ERROR_VARIABLE CorruptCacheDiagnostics
+)
+if(CorruptCacheResult EQUAL 0 OR NOT CorruptCacheDiagnostics MATCHES "cache miss")
+ message(FATAL_ERROR "strict cache reload accepted a corrupted artifact")
+endif()
+
+set(SuccessReproducer "${WORK_DIRECTORY}/success-reproducer")
+set(SuccessAssembly "${WORK_DIRECTORY}/success.s")
+set(ReplayedAssembly "${WORK_DIRECTORY}/success-replayed.s")
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --emit assembly --crash-reproducer "${SuccessReproducer}"
+ --output "${SuccessAssembly}"
+ RESULT_VARIABLE ReproducerWriteResult
+ ERROR_VARIABLE ReproducerWriteDiagnostics
+)
+if(NOT ReproducerWriteResult EQUAL 0)
+ message(FATAL_ERROR "successful reproducer publication failed: ${ReproducerWriteDiagnostics}")
+endif()
+execute_process(
+ COMMAND "${TOOL}" --reproduce "${SuccessReproducer}" --output "${ReplayedAssembly}"
+ RESULT_VARIABLE ReproducerReplayResult
+ ERROR_VARIABLE ReproducerReplayDiagnostics
+)
+if(NOT ReproducerReplayResult EQUAL 0)
+ message(FATAL_ERROR "successful reproducer replay failed: ${ReproducerReplayDiagnostics}")
+endif()
+execute_process(
+ COMMAND "${CMAKE_COMMAND}" -E compare_files "${SuccessAssembly}" "${ReplayedAssembly}"
+ RESULT_VARIABLE ReproducerComparisonResult
+)
+if(NOT ReproducerComparisonResult EQUAL 0)
+ message(FATAL_ERROR "reproducer replay changed the artifact bytes")
+endif()
+
+set(RegBankInput "${WORK_DIRECTORY}/regbank.mir")
+execute_process(
+ COMMAND "${TOOL}" --input "${INPUT}" --emit regbank-mir --output "${RegBankInput}"
+ RESULT_VARIABLE RegBankResult
+ ERROR_VARIABLE RegBankDiagnostics
+)
+if(NOT RegBankResult EQUAL 0)
+ message(FATAL_ERROR "register-bank seed production failed: ${RegBankDiagnostics}")
+endif()
+
+set(FailureReproducer "${WORK_DIRECTORY}/failure-reproducer")
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${RegBankInput}" --run-pass InstructionSelect --emit selected-mir
+ --fuel 0 --crash-reproducer "${FailureReproducer}" --output "${WORK_DIRECTORY}/failure.mir"
+ RESULT_VARIABLE FailureResult
+ ERROR_VARIABLE FailureDiagnostics
+)
+if(FailureResult EQUAL 0 OR
+ NOT FailureDiagnostics MATCHES "required selection action was rejected")
+ message(FATAL_ERROR "zero Action fuel did not produce the expected failure")
+endif()
+file(READ "${FailureReproducer}/manifest.yaml" FailureManifest)
+if(NOT FailureManifest MATCHES "InstructionSelect" OR
+ NOT FailureManifest MATCHES "required selection action was rejected")
+ message(FATAL_ERROR "failure reproducer did not record structured pass diagnostics")
+endif()
+execute_process(
+ COMMAND "${TOOL}" --reproduce "${FailureReproducer}" --output "${WORK_DIRECTORY}/replay.mir"
+ RESULT_VARIABLE FailureReplayResult
+ ERROR_VARIABLE FailureReplayDiagnostics
+)
+if(FailureReplayResult EQUAL 0 OR
+ NOT FailureReplayDiagnostics MATCHES "required selection action was rejected")
+ message(FATAL_ERROR "failure reproducer did not replay the original failure")
+endif()
+
+set(ActionOutput "${WORK_DIRECTORY}/selected.mir")
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${RegBankInput}" --run-pass InstructionSelect --emit selected-mir
+ --print-actions --output "${ActionOutput}"
+ RESULT_VARIABLE ActionResult
+ ERROR_VARIABLE ActionDiagnostics
+)
+if(NOT ActionResult EQUAL 0 OR NOT ActionDiagnostics MATCHES "accepted InstructionSelect")
+ message(FATAL_ERROR "successful Action execution did not report stable decisions")
+endif()
+
+file(READ "${INPUT}" ReducerSeedText)
+string(REPLACE
+ " $x10 = COPY %2\n"
+ " %3:gpr(i64) = G_CONSTANT 99\n $x10 = COPY %2\n"
+ ReducerSeedText
+ "${ReducerSeedText}"
+)
+set(ReducerSeed "${WORK_DIRECTORY}/reducer-seed.mir")
+set(CanonicalReducerSeed "${WORK_DIRECTORY}/reducer-seed.canonical.mir")
+set(ReducedMIR "${WORK_DIRECTORY}/reduced.mir")
+file(WRITE "${ReducerSeed}" "${ReducerSeedText}")
+execute_process(
+ COMMAND "${TOOL}" --verify --input "${ReducerSeed}" --output "${CanonicalReducerSeed}"
+ RESULT_VARIABLE ReducerSeedResult
+ ERROR_VARIABLE ReducerSeedDiagnostics
+)
+if(NOT ReducerSeedResult EQUAL 0)
+ message(FATAL_ERROR "reducer seed failed canonicalization: ${ReducerSeedDiagnostics}")
+endif()
+execute_process(
+ COMMAND "${TOOL}" --reduce validity --input "${ReducerSeed}" --output "${ReducedMIR}"
+ RESULT_VARIABLE ValidityReducerResult
+ ERROR_VARIABLE ValidityReducerDiagnostics
+)
+if(NOT ValidityReducerResult EQUAL 0)
+ message(FATAL_ERROR "validity reduction failed: ${ValidityReducerDiagnostics}")
+endif()
+file(SIZE "${CanonicalReducerSeed}" CanonicalReducerSeedSize)
+file(SIZE "${ReducedMIR}" ReducedMIRSize)
+if(NOT ReducedMIRSize LESS CanonicalReducerSeedSize)
+ message(FATAL_ERROR "validity reducer did not make the canonical seed smaller")
+endif()
+execute_process(
+ COMMAND "${TOOL}" --verify --input "${ReducedMIR}"
+ RESULT_VARIABLE ReducedVerifyResult
+ ERROR_VARIABLE ReducedVerifyDiagnostics
+)
+if(NOT ReducedVerifyResult EQUAL 0)
+ message(FATAL_ERROR "validity reducer produced invalid MIR: ${ReducedVerifyDiagnostics}")
+endif()
+
+set(FailureReducedMIR "${WORK_DIRECTORY}/failure-reduced.mir")
+execute_process(
+ COMMAND
+ "${TOOL}" --reduce failure --input "${RegBankInput}" --run-pass InstructionSelect
+ --emit selected-mir --fuel 0 --output "${FailureReducedMIR}"
+ RESULT_VARIABLE FailureReducerResult
+ ERROR_VARIABLE FailureReducerDiagnostics
+)
+if(NOT FailureReducerResult EQUAL 0)
+ message(FATAL_ERROR "failure reduction failed: ${FailureReducerDiagnostics}")
+endif()
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${FailureReducedMIR}" --run-pass InstructionSelect --emit selected-mir
+ --fuel 0 --output "${WORK_DIRECTORY}/failure-reduced-output.mir"
+ RESULT_VARIABLE ReducedFailureResult
+ ERROR_VARIABLE ReducedFailureDiagnostics
+)
+if(ReducedFailureResult EQUAL 0 OR
+ NOT ReducedFailureDiagnostics MATCHES "required selection action was rejected")
+ message(FATAL_ERROR "failure reducer did not preserve its interestingness condition")
+endif()
+
+set(FirstFuzz "${WORK_DIRECTORY}/fuzz-1.txt")
+set(SecondFuzz "${WORK_DIRECTORY}/fuzz-2.txt")
+execute_process(
+ COMMAND
+ "${TOOL}" --fuzz all --fuzz-iterations 2 --seed 123 --input "${INPUT}" --output
+ "${FirstFuzz}"
+ RESULT_VARIABLE FirstFuzzResult
+ ERROR_VARIABLE FirstFuzzDiagnostics
+)
+if(NOT FirstFuzzResult EQUAL 0)
+ message(FATAL_ERROR "layered fuzz execution failed: ${FirstFuzzDiagnostics}")
+endif()
+execute_process(
+ COMMAND
+ "${TOOL}" --fuzz all --fuzz-iterations 2 --seed 123 --input "${INPUT}" --output
+ "${SecondFuzz}"
+ RESULT_VARIABLE SecondFuzzResult
+ ERROR_VARIABLE SecondFuzzDiagnostics
+)
+if(NOT SecondFuzzResult EQUAL 0)
+ message(FATAL_ERROR "repeated layered fuzz execution failed: ${SecondFuzzDiagnostics}")
+endif()
+execute_process(
+ COMMAND "${CMAKE_COMMAND}" -E compare_files "${FirstFuzz}" "${SecondFuzz}"
+ RESULT_VARIABLE FuzzComparisonResult
+)
+if(NOT FuzzComparisonResult EQUAL 0)
+ message(FATAL_ERROR "layered fuzz accounting is not deterministic")
+endif()
+file(READ "${FirstFuzz}" FuzzText)
+if(NOT FuzzText MATCHES "seed=123 executed=11" OR
+ NOT FuzzText MATCHES "parser: executed=2" OR
+ NOT FuzzText MATCHES "builder: executed=2" OR
+ NOT FuzzText MATCHES "property-stage: executed=2" OR
+ NOT FuzzText MATCHES "mutation: executed=2" OR
+ NOT FuzzText MATCHES "pipeline: executed=2" OR
+ NOT FuzzText MATCHES "semantics: executed=1")
+ message(FATAL_ERROR "layered fuzz output does not account for all six layers")
+endif()
+execute_process(
+ COMMAND "${TOOL}" --fuzz parser,parser --input "${INPUT}"
+ RESULT_VARIABLE DuplicateFuzzResult
+ ERROR_VARIABLE DuplicateFuzzDiagnostics
+)
+if(DuplicateFuzzResult EQUAL 0 OR NOT DuplicateFuzzDiagnostics MATCHES "must be unique")
+ message(FATAL_ERROR "CLI fuzzing accepted duplicate layers")
+endif()
+
+file(REMOVE_RECURSE "${WORK_DIRECTORY}")
diff --git a/backend/tools/MIRTool/MIRToolOutputTest.cmake b/backend/tools/MIRTool/MIRToolOutputTest.cmake
new file mode 100644
index 0000000..f8e4499
--- /dev/null
+++ b/backend/tools/MIRTool/MIRToolOutputTest.cmake
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+if(NOT DEFINED TOOL OR NOT DEFINED INPUT OR NOT DEFINED OUTPUT OR
+ NOT DEFINED OUTPUT_KIND)
+ message(FATAL_ERROR "MIR tool output test is missing a required argument")
+endif()
+
+get_filename_component(OutputDirectory "${OUTPUT}" DIRECTORY)
+file(MAKE_DIRECTORY "${OutputDirectory}")
+set(RepeatedOutput "${OUTPUT}.repeat")
+
+execute_process(
+ COMMAND "${TOOL}" --input "${INPUT}" --emit "${OUTPUT_KIND}" --output "${OUTPUT}"
+ RESULT_VARIABLE FirstResult
+ ERROR_VARIABLE FirstDiagnostics
+)
+if(NOT FirstResult EQUAL 0)
+ message(FATAL_ERROR "${OUTPUT_KIND} production failed: ${FirstDiagnostics}")
+endif()
+execute_process(
+ COMMAND "${TOOL}" --input "${INPUT}" --emit "${OUTPUT_KIND}" --output "${RepeatedOutput}"
+ RESULT_VARIABLE SecondResult
+ ERROR_VARIABLE SecondDiagnostics
+)
+if(NOT SecondResult EQUAL 0)
+ message(FATAL_ERROR "repeated ${OUTPUT_KIND} production failed: ${SecondDiagnostics}")
+endif()
+execute_process(
+ COMMAND "${CMAKE_COMMAND}" -E compare_files "${OUTPUT}" "${RepeatedOutput}"
+ RESULT_VARIABLE ComparisonResult
+)
+if(NOT ComparisonResult EQUAL 0)
+ message(FATAL_ERROR "${OUTPUT_KIND} output is not deterministic")
+endif()
+
+if(OUTPUT_KIND STREQUAL "assembly")
+ file(READ "${OUTPUT}" Assembly)
+ if(NOT Assembly MATCHES "\\.attribute arch, \"rv64gc\"" OR
+ NOT Assembly MATCHES "jalr x0, x1, 0")
+ message(FATAL_ERROR "assembly output is missing required RV64GC content")
+ endif()
+ if(INPUT MATCHES "RISCVDataPipeline" AND
+ (NOT Assembly MATCHES "\\.LCPI[0-9]+_0:" OR
+ NOT Assembly MATCHES "\\.LJTI[0-9]+_0:"))
+ message(FATAL_ERROR "assembly output is missing constant-pool or jump-table data")
+ endif()
+ if(INPUT MATCHES "RISCVStackFramePipeline" AND
+ (NOT Assembly MATCHES "addi x2, x2, -32" OR
+ NOT Assembly MATCHES "addi x2, x2, 32"))
+ message(FATAL_ERROR "assembly output does not balance its stack frame")
+ endif()
+ if(DEFINED LLVM_MC AND NOT LLVM_MC STREQUAL "LLVM_MC-NOTFOUND" AND
+ NOT LLVM_MC STREQUAL "")
+ execute_process(
+ COMMAND "${LLVM_MC}" -triple=riscv64-unknown-linux-gnu -filetype=null "${OUTPUT}"
+ RESULT_VARIABLE AssemblerResult
+ ERROR_VARIABLE AssemblerDiagnostics
+ )
+ if(NOT AssemblerResult EQUAL 0)
+ message(FATAL_ERROR "LLVM MC rejected MIR tool assembly: ${AssemblerDiagnostics}")
+ endif()
+ endif()
+elseif(OUTPUT_KIND STREQUAL "object")
+ file(READ "${OUTPUT}" ObjectMagic OFFSET 0 LIMIT 4 HEX)
+ if(NOT ObjectMagic STREQUAL "7f454c46")
+ message(FATAL_ERROR "object output is not 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 MIR tool 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 "object output 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 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 MIR tool object: ${ObjdumpDiagnostics}")
+ endif()
+ if(NOT ObjdumpOutput MATCHES "file format elf64-littleriscv" OR
+ NOT ObjdumpOutput MATCHES "ret")
+ message(FATAL_ERROR "object output 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()
+else()
+ message(FATAL_ERROR "MIR tool output test received an unknown output kind")
+endif()
+
+file(REMOVE "${OUTPUT}" "${RepeatedOutput}")
diff --git a/backend/tools/MIRTool/MIRToolStageTest.cmake b/backend/tools/MIRTool/MIRToolStageTest.cmake
new file mode 100644
index 0000000..4fefe6e
--- /dev/null
+++ b/backend/tools/MIRTool/MIRToolStageTest.cmake
@@ -0,0 +1,155 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+if(NOT DEFINED TOOL OR NOT DEFINED INPUT OR NOT DEFINED WORK_DIRECTORY)
+ message(FATAL_ERROR "MIR tool stage test is missing a required path")
+endif()
+
+file(REMOVE_RECURSE "${WORK_DIRECTORY}")
+file(MAKE_DIRECTORY "${WORK_DIRECTORY}")
+
+set(
+ Stages
+ prelegalizer-mir
+ legalized-mir
+ regbank-mir
+ selected-mir
+ post-ssa-mir
+ post-ra-mir
+ final-mir
+)
+foreach(Stage IN LISTS Stages)
+ set(StageOutput "${WORK_DIRECTORY}/${Stage}.mir")
+ set(RepeatedOutput "${WORK_DIRECTORY}/${Stage}.repeat.mir")
+ set(CanonicalOutput "${WORK_DIRECTORY}/${Stage}.canonical.mir")
+ execute_process(
+ COMMAND "${TOOL}" --input "${INPUT}" --emit "${Stage}" --output "${StageOutput}"
+ RESULT_VARIABLE StageResult
+ ERROR_VARIABLE StageDiagnostics
+ )
+ if(NOT StageResult EQUAL 0)
+ message(FATAL_ERROR "${Stage} production failed: ${StageDiagnostics}")
+ endif()
+ execute_process(
+ COMMAND "${TOOL}" --input "${INPUT}" --emit "${Stage}" --output "${RepeatedOutput}"
+ RESULT_VARIABLE RepeatedResult
+ ERROR_VARIABLE RepeatedDiagnostics
+ )
+ if(NOT RepeatedResult EQUAL 0)
+ message(FATAL_ERROR "repeated ${Stage} production failed: ${RepeatedDiagnostics}")
+ endif()
+ execute_process(
+ COMMAND "${CMAKE_COMMAND}" -E compare_files "${StageOutput}" "${RepeatedOutput}"
+ RESULT_VARIABLE DeterminismResult
+ )
+ if(NOT DeterminismResult EQUAL 0)
+ message(FATAL_ERROR "${Stage} output is not deterministic")
+ endif()
+ execute_process(
+ COMMAND "${TOOL}" --verify --input "${StageOutput}" --output "${CanonicalOutput}"
+ RESULT_VARIABLE VerifyResult
+ ERROR_VARIABLE VerifyDiagnostics
+ )
+ if(NOT VerifyResult EQUAL 0)
+ message(FATAL_ERROR "${Stage} output failed strict MIR reload: ${VerifyDiagnostics}")
+ endif()
+ execute_process(
+ COMMAND "${CMAKE_COMMAND}" -E compare_files "${StageOutput}" "${CanonicalOutput}"
+ RESULT_VARIABLE CanonicalResult
+ )
+ if(NOT CanonicalResult EQUAL 0)
+ message(FATAL_ERROR "${Stage} output is not canonical after strict reload")
+ endif()
+endforeach()
+
+set(PassList "${WORK_DIRECTORY}/passes.txt")
+execute_process(
+ COMMAND "${TOOL}" --list-passes --input "${INPUT}" --output "${PassList}"
+ RESULT_VARIABLE ListResult
+ ERROR_VARIABLE ListDiagnostics
+)
+if(NOT ListResult EQUAL 0)
+ message(FATAL_ERROR "pass listing failed: ${ListDiagnostics}")
+endif()
+file(READ "${PassList}" PassListText)
+foreach(RequiredPass IN ITEMS Legalizer RegisterBankSelect InstructionSelect PrologueEpilogueInsertion)
+ if(NOT PassListText MATCHES "(^|\n)${RequiredPass}(\n|$)")
+ message(FATAL_ERROR "canonical pass list is missing ${RequiredPass}")
+ endif()
+endforeach()
+
+set(LegalizedOutput "${WORK_DIRECTORY}/run-pass.mir")
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --run-pass Legalizer --emit legalized-mir --output
+ "${LegalizedOutput}"
+ RESULT_VARIABLE RunPassResult
+ ERROR_VARIABLE RunPassDiagnostics
+)
+if(NOT RunPassResult EQUAL 0)
+ message(FATAL_ERROR "single-pass execution failed: ${RunPassDiagnostics}")
+endif()
+
+set(RegBankOutput "${WORK_DIRECTORY}/passes.mir")
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --passes Legalizer,RegisterBankSelect --emit regbank-mir
+ --output "${RegBankOutput}"
+ RESULT_VARIABLE PassesResult
+ ERROR_VARIABLE PassesDiagnostics
+)
+if(NOT PassesResult EQUAL 0)
+ message(FATAL_ERROR "explicit pass pipeline failed: ${PassesDiagnostics}")
+endif()
+
+set(StoppedOutput "${WORK_DIRECTORY}/stopped.mir")
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --stop-after Legalizer --emit final-mir --output
+ "${StoppedOutput}"
+ RESULT_VARIABLE StopResult
+ ERROR_VARIABLE StopDiagnostics
+)
+if(NOT StopResult EQUAL 0)
+ message(FATAL_ERROR "stop-after control failed: ${StopDiagnostics}")
+endif()
+execute_process(
+ COMMAND "${TOOL}" --verify --input "${StoppedOutput}"
+ RESULT_VARIABLE StopVerifyResult
+ ERROR_VARIABLE StopVerifyDiagnostics
+)
+if(NOT StopVerifyResult EQUAL 0)
+ message(FATAL_ERROR "stop-after output failed strict reload: ${StopVerifyDiagnostics}")
+endif()
+
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --emit legalized-mir --print-before Legalizer --print-after
+ Legalizer --output "${WORK_DIRECTORY}/printed.mir"
+ RESULT_VARIABLE PrintResult
+ ERROR_VARIABLE PrintDiagnostics
+)
+if(NOT PrintResult EQUAL 0)
+ message(FATAL_ERROR "pass snapshot controls failed: ${PrintDiagnostics}")
+endif()
+if(NOT PrintDiagnostics MATCHES "[*][*][*] prelegalizer-mir Legalizer#1" OR
+ NOT PrintDiagnostics MATCHES "[*][*][*] legalized-mir Legalizer#1")
+ message(FATAL_ERROR "before/after pass snapshots are incomplete")
+endif()
+
+execute_process(
+ COMMAND
+ "${TOOL}" --input "${INPUT}" --emit final-mir --stage-snapshots --output
+ "${WORK_DIRECTORY}/snapshot-final.mir"
+ RESULT_VARIABLE SnapshotResult
+ ERROR_VARIABLE SnapshotDiagnostics
+)
+if(NOT SnapshotResult EQUAL 0)
+ message(FATAL_ERROR "stage snapshot execution failed: ${SnapshotDiagnostics}")
+endif()
+foreach(Stage IN LISTS Stages)
+ if(NOT SnapshotDiagnostics MATCHES "[*][*][*] ${Stage}")
+ message(FATAL_ERROR "stage snapshots are missing ${Stage}")
+ endif()
+endforeach()
+
+file(REMOVE_RECURSE "${WORK_DIRECTORY}")