diff options
Diffstat (limited to 'backend/tools')
| -rw-r--r-- | backend/tools/AssemblyEmitter/AssemblyEmitterMain.cpp | 72 | ||||
| -rw-r--r-- | backend/tools/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | backend/tools/MIRTool/CMakeLists.txt | 91 | ||||
| -rw-r--r-- | backend/tools/MIRTool/MIRToolMain.cpp | 989 | ||||
| -rw-r--r-- | backend/tools/MIRTool/MIRToolOperationsTest.cmake | 260 | ||||
| -rw-r--r-- | backend/tools/MIRTool/MIRToolOutputTest.cmake | 119 | ||||
| -rw-r--r-- | backend/tools/MIRTool/MIRToolStageTest.cmake | 155 |
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}") |
