aboutsummaryrefslogtreecommitdiff
path: root/backend/lib/Tooling/PipelineDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backend/lib/Tooling/PipelineDriver.cpp')
-rw-r--r--backend/lib/Tooling/PipelineDriver.cpp436
1 files changed, 436 insertions, 0 deletions
diff --git a/backend/lib/Tooling/PipelineDriver.cpp b/backend/lib/Tooling/PipelineDriver.cpp
new file mode 100644
index 0000000..330730e
--- /dev/null
+++ b/backend/lib/Tooling/PipelineDriver.cpp
@@ -0,0 +1,436 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/PipelineDriver.h"
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineOptimizationPipeline.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Target/RISCV/RISCVPipeline.h"
+
+#include <algorithm>
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <thread>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace rcc::tooling {
+namespace {
+
+struct FunctionRunResult final {
+ std::vector<ExecutedPass> ExecutedPasses;
+ std::vector<PipelineSnapshot> Snapshots;
+ std::optional<Error> Failure;
+};
+
+[[nodiscard]] unsigned stageOrdinal(BackendStage Stage) noexcept {
+ return static_cast<unsigned>(Stage);
+}
+
+[[nodiscard]] bool selectorMatches(std::string_view Selector, std::string_view Pass,
+ std::uint32_t Occurrence) {
+ if (Selector == "all" || Selector == Pass)
+ return true;
+ std::string Qualified(Pass);
+ Qualified.push_back('#');
+ Qualified.append(std::to_string(Occurrence));
+ return Selector == Qualified;
+}
+
+[[nodiscard]] bool anySelectorMatches(std::span<const std::string> Selectors, std::string_view Pass,
+ std::uint32_t Occurrence) {
+ return std::ranges::any_of(Selectors, [Pass, Occurrence](const std::string &Selector) {
+ return selectorMatches(Selector, Pass, Occurrence);
+ });
+}
+
+[[nodiscard]] std::optional<BackendStage> stageAfterPass(std::string_view Pass) noexcept {
+ if (Pass == "Legalizer")
+ return BackendStage::LegalizedMIR;
+ if (Pass == "RegisterBankSelect")
+ return BackendStage::RegisterBankMIR;
+ if (Pass == "InstructionSelect")
+ return BackendStage::SelectedMIR;
+ if (Pass == "TwoAddressRewrite")
+ return BackendStage::PostSSAMIR;
+ if (Pass == "PBQPAllocator")
+ return BackendStage::PostRegisterAllocationMIR;
+ if (Pass == "final-encodability-verifier")
+ return BackendStage::FinalMIR;
+ return std::nullopt;
+}
+
+[[nodiscard]] Expected<MachinePassManager::PassList>
+createPasses(const MachineFunction &Function, const BackendInvocation &Invocation,
+ MachinePassManagerOptions Options) {
+ MachinePassManager Pipeline(Options);
+ if (Invocation.PassPipeline.size() == 1 && Invocation.PassPipeline.front() == "default") {
+ auto Added = riscv::addCanonicalPipeline(Pipeline, Function);
+ if (!Added)
+ return std::unexpected(Added.error());
+ } else {
+ if (std::ranges::find(Invocation.PassPipeline, "default") != Invocation.PassPipeline.end()) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "the default pipeline marker cannot be mixed with named passes");
+ }
+ for (const std::string &Name : Invocation.PassPipeline) {
+ auto Pass = riscv::createCanonicalPipelinePass(Name);
+ if (!Pass)
+ return std::unexpected(Pass.error());
+ auto Added = Pipeline.addPass(std::move(*Pass));
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ }
+ return std::move(Pipeline).takePasses();
+}
+
+[[nodiscard]] Expected<void> validateSelector(std::string_view Selector) {
+ if (Selector == "all")
+ return {};
+ const std::size_t Hash = Selector.rfind('#');
+ const std::string_view Name =
+ Hash == std::string_view::npos ? Selector : Selector.substr(0, Hash);
+ if (Name.empty())
+ return makeUnexpected(ErrorCode::InvalidInvocation, "empty pipeline pass selector");
+ auto Pass = riscv::createCanonicalPipelinePass(Name);
+ if (!Pass)
+ return std::unexpected(Pass.error());
+ if (Hash != std::string_view::npos) {
+ const std::string_view Occurrence = Selector.substr(Hash + 1U);
+ if (Occurrence.empty() || Occurrence == "0" ||
+ !std::ranges::all_of(Occurrence,
+ [](char Character) { return Character >= '0' && Character <= '9'; })) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "pipeline pass occurrence must be a positive integer");
+ }
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> validateControls(const PipelineControls &Controls) {
+ if (Controls.StopBefore) {
+ auto Result = validateSelector(*Controls.StopBefore);
+ if (!Result)
+ return Result;
+ }
+ if (Controls.StopAfter) {
+ auto Result = validateSelector(*Controls.StopAfter);
+ if (!Result)
+ return Result;
+ }
+ for (const std::string &Selector : Controls.PrintBefore) {
+ auto Result = validateSelector(Selector);
+ if (!Result)
+ return Result;
+ }
+ for (const std::string &Selector : Controls.PrintAfter) {
+ auto Result = validateSelector(Selector);
+ if (!Result)
+ return Result;
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void>
+captureSnapshot(FunctionRunResult &Result, const mir::MIRDocument &Document,
+ const TargetDescription &Target, const BackendInvocation &Invocation,
+ SnapshotPhase Phase, BackendStage Stage, std::string_view Function,
+ std::string_view Pass, std::uint32_t Occurrence) {
+ mir::MIRDocumentPrinterOptions PrintOptions;
+ PrintOptions.Mode = mir::MIPrintMode::Complete;
+ PrintOptions.EmitEmbeddedIR = true;
+ PrintOptions.DataLayout = Invocation.DataLayout;
+ PrintOptions.DefaultPointerSizeInBits = 64;
+ auto Printed = mir::printMIRDocument(Document, Target, std::move(PrintOptions));
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ Result.Snapshots.push_back({
+ .Phase = Phase,
+ .Stage = Stage,
+ .Function = std::string(Function),
+ .Pass = std::string(Pass),
+ .Occurrence = Occurrence,
+ .MIR = std::move(*Printed),
+ });
+ return {};
+}
+
+[[nodiscard]] Expected<FunctionRunResult>
+runFunction(MachineFunction &Function, mir::MIRDocument &Document, const TargetDescription &Target,
+ const BackendInvocation &Invocation, const PipelineControls &Controls,
+ bool AllowSnapshots, MachinePassInstrumentation *Instrumentation) {
+ FunctionRunResult Result;
+ MachinePassManagerOptions ManagerOptions{
+ .VerifyBefore = true,
+ .VerifyEach = Controls.VerifyEach,
+ .DetectUnreportedChanges = Controls.DetectUnreportedChanges,
+ };
+ auto Passes = createPasses(Function, Invocation, ManagerOptions);
+ if (!Passes)
+ return std::unexpected(Passes.error());
+ MachineAnalysisManager Analyses;
+ auto Registered = registerMachineOptimizationAnalyses(Analyses);
+ if (!Registered)
+ return std::unexpected(Registered.error());
+
+ BackendStage CurrentStage = stageForFunction(Function);
+ if (AllowSnapshots && Controls.CaptureStageSnapshots) {
+ auto Captured = captureSnapshot(Result, Document, Target, Invocation, SnapshotPhase::Stage,
+ CurrentStage, Function.name(), {}, 0);
+ if (!Captured)
+ return std::unexpected(Captured.error());
+ }
+ if (Controls.StopAtStage && stageOrdinal(CurrentStage) >= stageOrdinal(*Controls.StopAtStage)) {
+ return Result;
+ }
+
+ std::unordered_map<std::string, std::uint32_t> Occurrences;
+ for (std::unique_ptr<MachineFunctionPass> &Pass : *Passes) {
+ if (Pass == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "pipeline contains a null pass");
+ const std::string Name = Pass->contract().Name;
+ const std::uint32_t Occurrence = ++Occurrences[Name];
+ if (Controls.StopBefore && selectorMatches(*Controls.StopBefore, Name, Occurrence))
+ break;
+ if (AllowSnapshots && anySelectorMatches(Controls.PrintBefore, Name, Occurrence)) {
+ auto Captured =
+ captureSnapshot(Result, Document, Target, Invocation, SnapshotPhase::BeforePass,
+ CurrentStage, Function.name(), Name, Occurrence);
+ if (!Captured)
+ return std::unexpected(Captured.error());
+ }
+
+ MachinePassManager SinglePass(ManagerOptions);
+ auto Added = SinglePass.addPass(std::move(Pass));
+ if (!Added)
+ return std::unexpected(Added.error());
+ auto Run = SinglePass.run(Function, Target, Analyses, Instrumentation);
+ if (!Run)
+ return std::unexpected(Run.error());
+ Result.ExecutedPasses.push_back({
+ .Function = std::string(Function.name()),
+ .Pass = Name,
+ .Occurrence = Occurrence,
+ });
+
+ if (const std::optional<BackendStage> NewStage = stageAfterPass(Name))
+ CurrentStage = *NewStage;
+ if (AllowSnapshots && anySelectorMatches(Controls.PrintAfter, Name, Occurrence)) {
+ auto Captured =
+ captureSnapshot(Result, Document, Target, Invocation, SnapshotPhase::AfterPass,
+ CurrentStage, Function.name(), Name, Occurrence);
+ if (!Captured)
+ return std::unexpected(Captured.error());
+ }
+ if (AllowSnapshots && Controls.CaptureStageSnapshots && stageAfterPass(Name)) {
+ auto Captured = captureSnapshot(Result, Document, Target, Invocation, SnapshotPhase::Stage,
+ CurrentStage, Function.name(), Name, Occurrence);
+ if (!Captured)
+ return std::unexpected(Captured.error());
+ }
+ if (Controls.StopAfter && selectorMatches(*Controls.StopAfter, Name, Occurrence))
+ break;
+ if (Controls.StopAtStage && stageOrdinal(CurrentStage) >= stageOrdinal(*Controls.StopAtStage)) {
+ break;
+ }
+ }
+ return Result;
+}
+
+[[nodiscard]] bool requiresSequentialExecution(const PipelineControls &Controls) noexcept {
+ return Controls.CaptureStageSnapshots || !Controls.PrintBefore.empty() ||
+ !Controls.PrintAfter.empty();
+}
+
+} // namespace
+
+BackendStage stageForFunction(const MachineFunction &Function) noexcept {
+ const MachineFunctionProperties &Properties = Function.properties();
+ if (Properties.has(MachineFunctionProperty::Selected) &&
+ Properties.has(MachineFunctionProperty::NoExecutablePseudos))
+ return BackendStage::FinalMIR;
+ if (Properties.has(MachineFunctionProperty::Selected) &&
+ Properties.has(MachineFunctionProperty::NoVRegs))
+ return BackendStage::PostRegisterAllocationMIR;
+ if (Properties.has(MachineFunctionProperty::Selected) &&
+ Properties.has(MachineFunctionProperty::TiedOpsRewritten)) {
+ return BackendStage::PostSSAMIR;
+ }
+ if (Properties.has(MachineFunctionProperty::Selected))
+ return BackendStage::SelectedMIR;
+ if (Properties.has(MachineFunctionProperty::RegBankSelected))
+ return BackendStage::RegisterBankMIR;
+ if (Properties.has(MachineFunctionProperty::Legalized))
+ return BackendStage::LegalizedMIR;
+ return BackendStage::PreLegalizerMIR;
+}
+
+std::string_view spelling(BackendStage Stage) noexcept {
+ switch (Stage) {
+ case BackendStage::PreLegalizerMIR:
+ return "prelegalizer-mir";
+ case BackendStage::LegalizedMIR:
+ return "legalized-mir";
+ case BackendStage::RegisterBankMIR:
+ return "regbank-mir";
+ case BackendStage::SelectedMIR:
+ return "selected-mir";
+ case BackendStage::PostSSAMIR:
+ return "post-ssa-mir";
+ case BackendStage::PostRegisterAllocationMIR:
+ return "post-ra-mir";
+ case BackendStage::FinalMIR:
+ return "final-mir";
+ }
+ return "invalid";
+}
+
+Expected<BackendStage> parseBackendStage(std::string_view Value) {
+ if (Value == "prelegalizer-mir")
+ return BackendStage::PreLegalizerMIR;
+ if (Value == "legalized-mir")
+ return BackendStage::LegalizedMIR;
+ if (Value == "regbank-mir")
+ return BackendStage::RegisterBankMIR;
+ if (Value == "selected-mir")
+ return BackendStage::SelectedMIR;
+ if (Value == "post-ssa-mir")
+ return BackendStage::PostSSAMIR;
+ if (Value == "post-ra-mir")
+ return BackendStage::PostRegisterAllocationMIR;
+ if (Value == "final-mir")
+ return BackendStage::FinalMIR;
+ return makeUnexpected(ErrorCode::InvalidInvocation, "unknown backend stage");
+}
+
+Expected<void> verifyRV64GCInvocation(const BackendInvocation &Invocation) {
+ BackendInvocation Canonical = Invocation;
+ auto Verified = Canonical.canonicalizeAndVerify();
+ if (!Verified)
+ return std::unexpected(Verified.error());
+ if (Canonical != Invocation) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "RV64GC backend invocation must be canonicalized before execution");
+ }
+ const BackendInvocation Expected = defaultRV64GCInvocation();
+ if (Invocation.Triple != Expected.Triple || Invocation.CPU != Expected.CPU ||
+ Invocation.TuneCPU != Expected.TuneCPU || Invocation.Features != Expected.Features ||
+ Invocation.ABI != Expected.ABI || Invocation.DataLayout != Expected.DataLayout) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "backend invocation does not select the supported RV64GC LP64D target");
+ }
+ if (Invocation.MIRReferenceRevision != LLVMReferenceRevision ||
+ Invocation.GenericOpcodeVersion != GenericOpcodeReferenceVersion ||
+ Invocation.TargetABILoweringVersion != RV64GCTargetABILoweringVersion) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "backend invocation reference versions do not match this build");
+ }
+ return {};
+}
+
+Expected<PipelineRunResult> runRV64GCPipeline(mir::MIRDocument &Document,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation,
+ PipelineControls Controls,
+ MachinePassInstrumentation *Instrumentation) {
+ auto InvocationVerification = verifyRV64GCInvocation(Invocation);
+ if (!InvocationVerification)
+ return std::unexpected(InvocationVerification.error());
+ auto ControlVerification = validateControls(Controls);
+ if (!ControlVerification)
+ return std::unexpected(ControlVerification.error());
+ auto UnitVerification = Document.unit().verify();
+ if (!UnitVerification)
+ return std::unexpected(UnitVerification.error());
+ if (Document.unit().functionCount() == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "pipeline input must contain at least one machine function");
+ }
+
+ MachinePassInstrumentation LocalInstrumentation;
+ MachinePassInstrumentation *ActiveInstrumentation =
+ Instrumentation == nullptr ? &LocalInstrumentation : Instrumentation;
+ ActiveInstrumentation->setPerFunctionFuel(Invocation.ActionFuel);
+ if (!Invocation.EnabledActions.empty()) {
+ const std::vector<std::string> Enabled = Invocation.EnabledActions;
+ ActiveInstrumentation->setActionFilter([Enabled](const MachineActionID &Action, std::uint64_t) {
+ return std::ranges::binary_search(Enabled, Action.Rule) ||
+ std::ranges::binary_search(Enabled, Action.Pass) ||
+ std::ranges::binary_search(Enabled, Action.toString());
+ });
+ }
+
+ const std::size_t FunctionCount = Document.unit().functionCount();
+ std::vector<FunctionRunResult> FunctionResults(FunctionCount);
+ const bool Sequential = requiresSequentialExecution(Controls) ||
+ Invocation.Determinism.ThreadCount == 1 || FunctionCount == 1;
+ if (Sequential) {
+ for (std::size_t Index = 0; Index < FunctionCount; ++Index) {
+ MachineFunction *Function = Document.unit().functionAt(Index);
+ if (Function == nullptr)
+ return makeUnexpected(ErrorCode::InternalError,
+ "compilation unit contains a null function");
+ auto Run = runFunction(*Function, Document, Target, Invocation, Controls, true,
+ ActiveInstrumentation);
+ if (!Run)
+ return std::unexpected(Run.error());
+ FunctionResults[Index] = std::move(*Run);
+ }
+ } else {
+ const std::size_t WorkerCount =
+ std::min<std::size_t>(Invocation.Determinism.ThreadCount, FunctionCount);
+ std::atomic<std::size_t> NextFunction = 0;
+ const auto Worker = [&]() {
+ while (true) {
+ const std::size_t Index = NextFunction.fetch_add(1, std::memory_order_relaxed);
+ if (Index >= FunctionCount)
+ return;
+ MachineFunction *Function = Document.unit().functionAt(Index);
+ if (Function == nullptr) {
+ FunctionResults[Index].Failure =
+ Error(ErrorCode::InternalError, "compilation unit contains a null function");
+ continue;
+ }
+ auto Run = runFunction(*Function, Document, Target, Invocation, Controls, false,
+ ActiveInstrumentation);
+ if (!Run) {
+ FunctionResults[Index].Failure = Run.error();
+ } else {
+ FunctionResults[Index] = std::move(*Run);
+ }
+ }
+ };
+ std::vector<std::thread> Workers;
+ Workers.reserve(WorkerCount);
+ for (std::size_t Index = 0; Index < WorkerCount; ++Index)
+ Workers.emplace_back(Worker);
+ for (std::thread &Thread : Workers)
+ Thread.join();
+ }
+
+ PipelineRunResult Result;
+ for (FunctionRunResult &FunctionResult : FunctionResults) {
+ if (FunctionResult.Failure)
+ return std::unexpected(std::move(*FunctionResult.Failure));
+ Result.ExecutedPasses.insert(Result.ExecutedPasses.end(),
+ std::make_move_iterator(FunctionResult.ExecutedPasses.begin()),
+ std::make_move_iterator(FunctionResult.ExecutedPasses.end()));
+ Result.Snapshots.insert(Result.Snapshots.end(),
+ std::make_move_iterator(FunctionResult.Snapshots.begin()),
+ std::make_move_iterator(FunctionResult.Snapshots.end()));
+ }
+ return Result;
+}
+
+} // namespace rcc::tooling