aboutsummaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/cmake/modules/RCCBackendDeveloperTools.cmake20
-rw-r--r--backend/include/rcc/CodeGen/MachineBlockPlacement.h7
-rw-r--r--backend/include/rcc/CodeGen/MachineBranchRelaxation.h5
-rw-r--r--backend/include/rcc/CodeGen/MachineCombiner.h5
-rw-r--r--backend/include/rcc/CodeGen/MachineCopyPropagation.h5
-rw-r--r--backend/include/rcc/CodeGen/MachinePass.h72
-rw-r--r--backend/include/rcc/CodeGen/MachinePipelinePasses.h4
-rw-r--r--backend/include/rcc/CodeGen/MachineSSAExit.h4
-rw-r--r--backend/include/rcc/CodeGen/PBQP/Solver.h49
-rw-r--r--backend/include/rcc/CodeGen/RegisterPressure.h2
-rw-r--r--backend/include/rcc/CodeGen/Spiller.h8
-rw-r--r--backend/include/rcc/Support/Error.h6
-rw-r--r--backend/include/rcc/Support/SHA256.h24
-rw-r--r--backend/include/rcc/Target/RISCV/RISCVPipeline.h35
-rw-r--r--backend/include/rcc/Tooling/ArtifactStore.h73
-rw-r--r--backend/include/rcc/Tooling/BackendDriver.h56
-rw-r--r--backend/include/rcc/Tooling/BackendInvocation.h119
-rw-r--r--backend/include/rcc/Tooling/BackendManifest.h75
-rw-r--r--backend/include/rcc/Tooling/MIRFuzzer.h66
-rw-r--r--backend/include/rcc/Tooling/MIRReducer.h81
-rw-r--r--backend/include/rcc/Tooling/PipelineDriver.h83
-rw-r--r--backend/lib/CMakeLists.txt1
-rw-r--r--backend/lib/CodeGen/InlineSpiller.cpp45
-rw-r--r--backend/lib/CodeGen/MachineBlockPlacement.cpp37
-rw-r--r--backend/lib/CodeGen/MachineBranchRelaxation.cpp21
-rw-r--r--backend/lib/CodeGen/MachineCSE.cpp7
-rw-r--r--backend/lib/CodeGen/MachineCombiner.cpp22
-rw-r--r--backend/lib/CodeGen/MachineCompilationStatus.cpp6
-rw-r--r--backend/lib/CodeGen/MachineCopyPropagation.cpp21
-rw-r--r--backend/lib/CodeGen/MachineLICM.cpp4
-rw-r--r--backend/lib/CodeGen/MachinePass.cpp136
-rw-r--r--backend/lib/CodeGen/MachinePipelinePasses.cpp76
-rw-r--r--backend/lib/CodeGen/MachinePseudoExpansion.cpp61
-rw-r--r--backend/lib/CodeGen/MachineSSAExit.cpp103
-rw-r--r--backend/lib/CodeGen/MachineScheduler.cpp38
-rw-r--r--backend/lib/CodeGen/MachineSchedulerInternal.h13
-rw-r--r--backend/lib/CodeGen/MachineSink.cpp4
-rw-r--r--backend/lib/CodeGen/MachineStackSlotColoring.cpp2
-rw-r--r--backend/lib/CodeGen/MachineTailDuplication.cpp5
-rw-r--r--backend/lib/CodeGen/PostRAScheduler.cpp9
-rw-r--r--backend/lib/CodeGen/RegAllocBase.cpp3
-rw-r--r--backend/lib/CodeGen/RegAllocBase.h5
-rw-r--r--backend/lib/CodeGen/RegAllocPBQP.cpp73
-rw-r--r--backend/lib/CodeGen/RegisterPressure.cpp6
-rw-r--r--backend/lib/MIR/CMakeLists.txt27
-rw-r--r--backend/lib/MIR/MIParser.cpp11
-rw-r--r--backend/lib/MIR/MIRDataParser.cpp288
-rw-r--r--backend/lib/MIR/MIRDocument.cpp2745
-rw-r--r--backend/lib/MIR/MIRDocumentParser.cpp358
-rw-r--r--backend/lib/MIR/MIRDocumentParser.h131
-rw-r--r--backend/lib/MIR/MIRDocumentReferenceValidation.cpp410
-rw-r--r--backend/lib/MIR/MIRFrameParser.cpp451
-rw-r--r--backend/lib/MIR/MIRFunctionParser.cpp536
-rw-r--r--backend/lib/MIR/MIRFunctionRecordsParser.cpp301
-rw-r--r--backend/lib/MIR/MIRMetadataParser.cpp285
-rw-r--r--backend/lib/MIR/MIRMetadataTextParser.cpp259
-rw-r--r--backend/lib/MIR/MIRMetadataTextParser.h70
-rw-r--r--backend/lib/MIR/MIRParserUtilities.h85
-rw-r--r--backend/lib/Support/CMakeLists.txt5
-rw-r--r--backend/lib/Support/Error.cpp12
-rw-r--r--backend/lib/Support/SHA256.cpp149
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt4
-rw-r--r--backend/lib/Target/RISCV/RISCVPipeline.cpp162
-rw-r--r--backend/lib/Tooling/ArtifactStore.cpp532
-rw-r--r--backend/lib/Tooling/BackendDriver.cpp214
-rw-r--r--backend/lib/Tooling/BackendInvocation.cpp500
-rw-r--r--backend/lib/Tooling/BackendManifest.cpp733
-rw-r--r--backend/lib/Tooling/CMakeLists.txt76
-rw-r--r--backend/lib/Tooling/MIRFuzzer.cpp613
-rw-r--r--backend/lib/Tooling/MIRReducer.cpp534
-rw-r--r--backend/lib/Tooling/PipelineDriver.cpp436
-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
-rw-r--r--backend/unittests/CMakeLists.txt1
-rw-r--r--backend/unittests/MIR/MIParserTest.cpp30
-rw-r--r--backend/unittests/Support/CMakeLists.txt8
-rw-r--r--backend/unittests/Support/SHA256Test.cpp47
-rw-r--r--backend/unittests/Target/RISCVLateCodeGenTest.cpp3
-rw-r--r--backend/unittests/Tooling/ArtifactStoreTest.cpp193
-rw-r--r--backend/unittests/Tooling/BackendManifestTest.cpp210
-rw-r--r--backend/unittests/Tooling/CMakeLists.txt48
-rw-r--r--backend/unittests/Tooling/MIRReducerFuzzerTest.cpp241
-rw-r--r--backend/unittests/Tooling/PipelineDriverTest.cpp284
-rw-r--r--backend/unittests/Tooling/ToolingTestSupport.h131
89 files changed, 11336 insertions, 2971 deletions
diff --git a/backend/cmake/modules/RCCBackendDeveloperTools.cmake b/backend/cmake/modules/RCCBackendDeveloperTools.cmake
index d3693cd..0313fe6 100644
--- a/backend/cmake/modules/RCCBackendDeveloperTools.cmake
+++ b/backend/cmake/modules/RCCBackendDeveloperTools.cmake
@@ -27,23 +27,35 @@ function(rcc_backend_add_developer_targets)
if(TidyFiles)
list(REMOVE_DUPLICATES TidyFiles)
+ # LLVM's repository runs clang-tidy-diff.py for changed lines. Keep the
+ # backend's explicit full audit deterministic by invoking clang-tidy once
+ # with the registered translation units instead of using a process pool.
add_custom_target(
rcc-backend-tidy
COMMAND
- "${RCC_BACKEND_CLANG_TIDY}" --warnings-as-errors=*
- "-p=${CMAKE_BINARY_DIR}" ${TidyFiles}
+ "${RCC_BACKEND_CLANG_TIDY}"
+ "--quiet"
+ "-p=${CMAKE_BINARY_DIR}"
+ "--warnings-as-errors=*"
+ ${TidyFiles}
COMMAND_EXPAND_LISTS
COMMENT "Running clang-tidy on RCC Backend"
+ USES_TERMINAL
VERBATIM
)
add_custom_target(
rcc-backend-analyze
COMMAND
- "${RCC_BACKEND_CLANG_TIDY}" "--checks=-*,clang-analyzer-*"
- --warnings-as-errors=* "-p=${CMAKE_BINARY_DIR}" ${TidyFiles}
+ "${RCC_BACKEND_CLANG_TIDY}"
+ "--quiet"
+ "-p=${CMAKE_BINARY_DIR}"
+ "-checks=-*,clang-analyzer-*"
+ "--warnings-as-errors=*"
+ ${TidyFiles}
COMMAND_EXPAND_LISTS
COMMENT "Running Clang Static Analyzer checks on RCC Backend"
+ USES_TERMINAL
VERBATIM
)
endif()
diff --git a/backend/include/rcc/CodeGen/MachineBlockPlacement.h b/backend/include/rcc/CodeGen/MachineBlockPlacement.h
index 27a06f4..52ddafd 100644
--- a/backend/include/rcc/CodeGen/MachineBlockPlacement.h
+++ b/backend/include/rcc/CodeGen/MachineBlockPlacement.h
@@ -15,9 +15,10 @@ public:
MachineAnalysisManager &Analyses) override;
private:
- [[nodiscard]] Expected<MachinePassResult>
- runImplementation(MachineFunction &Function, const TargetDescription &Target,
- MachineAnalysisManager &Analyses) const;
+ [[nodiscard]] Expected<MachinePassResult> runImplementation(MachineFunction &Function,
+ const TargetDescription &Target,
+ MachineAnalysisManager &Analyses,
+ MachineActionReplay &Actions) const;
};
} // namespace rcc
diff --git a/backend/include/rcc/CodeGen/MachineBranchRelaxation.h b/backend/include/rcc/CodeGen/MachineBranchRelaxation.h
index 1793a54..c88c1d5 100644
--- a/backend/include/rcc/CodeGen/MachineBranchRelaxation.h
+++ b/backend/include/rcc/CodeGen/MachineBranchRelaxation.h
@@ -27,8 +27,9 @@ public:
MachineAnalysisManager &Analyses) override;
private:
- [[nodiscard]] Expected<MachinePassResult>
- runImplementation(MachineFunction &Function, const TargetDescription &Target) const;
+ [[nodiscard]] Expected<MachinePassResult> runImplementation(MachineFunction &Function,
+ const TargetDescription &Target,
+ MachineActionReplay &Actions) const;
const TargetBranchInfo *Info;
MachineBranchRelaxationOptions Options;
diff --git a/backend/include/rcc/CodeGen/MachineCombiner.h b/backend/include/rcc/CodeGen/MachineCombiner.h
index 943d6ad..ef7f432 100644
--- a/backend/include/rcc/CodeGen/MachineCombiner.h
+++ b/backend/include/rcc/CodeGen/MachineCombiner.h
@@ -27,8 +27,9 @@ public:
MachineAnalysisManager &Analyses) override;
private:
- [[nodiscard]] Expected<MachinePassResult>
- runImplementation(MachineFunction &Function, const TargetDescription &Target) const;
+ [[nodiscard]] Expected<MachinePassResult> runImplementation(MachineFunction &Function,
+ const TargetDescription &Target,
+ MachineActionReplay &Actions) const;
const TargetMachineCombinerInfo *Info;
MachineCombinerStage Stage;
diff --git a/backend/include/rcc/CodeGen/MachineCopyPropagation.h b/backend/include/rcc/CodeGen/MachineCopyPropagation.h
index 5f84ac6..e8ee788 100644
--- a/backend/include/rcc/CodeGen/MachineCopyPropagation.h
+++ b/backend/include/rcc/CodeGen/MachineCopyPropagation.h
@@ -33,8 +33,9 @@ public:
MachineAnalysisManager &Analyses) override;
private:
- [[nodiscard]] Expected<MachinePassResult>
- runImplementation(MachineFunction &Function, const TargetDescription &Target) const;
+ [[nodiscard]] Expected<MachinePassResult> runImplementation(MachineFunction &Function,
+ const TargetDescription &Target,
+ MachineActionReplay &Actions) const;
const TargetSSAInfo *Info;
MachineCopyPropagationStage Stage;
diff --git a/backend/include/rcc/CodeGen/MachinePass.h b/backend/include/rcc/CodeGen/MachinePass.h
index 1637954..ee67924 100644
--- a/backend/include/rcc/CodeGen/MachinePass.h
+++ b/backend/include/rcc/CodeGen/MachinePass.h
@@ -5,6 +5,7 @@
#include "rcc/CodeGen/InstructionDescriptor.h"
#include "rcc/CodeGen/MachineFunctionProperties.h"
+#include "rcc/CodeGen/StableID.h"
#include "rcc/Support/Error.h"
#include <concepts>
@@ -279,6 +280,9 @@ public:
[[nodiscard]] Expected<std::unique_ptr<MachineAnalysisManager>> forkEmpty() const;
[[nodiscard]] Expected<bool>
requestAction(std::string_view Action, const MachineFunction &Function, std::uint64_t Cost = 1);
+ [[nodiscard]] Expected<bool> requestAction(std::string_view Rule, const MachineFunction &Function,
+ MachineInstrStableID Instruction,
+ std::uint64_t Cost = 1);
private:
using ErasedRunner = std::function<Expected<std::shared_ptr<void>>(
@@ -315,6 +319,7 @@ private:
std::unordered_map<MachineFunction *, std::unordered_map<AnalysisID, CacheEntry>> Cache;
std::unordered_map<MachineFunction *, std::vector<AnalysisID>> ActiveComputations;
MachinePassInstrumentation *Instrumentation = nullptr;
+ std::string ActivePass;
friend class MachinePassManager;
friend class CompilationUnitPassManager;
@@ -473,6 +478,58 @@ struct InstrumentationStatistic final {
std::uint64_t TotalNanoseconds = 0;
};
+struct MachineActionID final {
+ std::string Pass;
+ std::string Rule;
+ MachineFunctionStableID Function;
+ MachineInstrStableID Instruction;
+
+ [[nodiscard]] std::string toString() const;
+
+ friend bool operator==(const MachineActionID &, const MachineActionID &) = default;
+};
+
+struct MachineActionEvent final {
+ MachineActionID ID;
+ std::uint64_t Cost = 0;
+ bool Accepted = false;
+
+ friend bool operator==(const MachineActionEvent &, const MachineActionEvent &) = default;
+};
+
+class MachineActionReplay final {
+public:
+ explicit MachineActionReplay(MachineAnalysisManager &Analyses) noexcept : Analyses(&Analyses) {}
+ MachineActionReplay(
+ MachineAnalysisManager &Analyses,
+ const std::unordered_map<MachineInstrStableID, MachineInstrStableID> &SourceToClone)
+ : Analyses(&Analyses) {
+ for (const auto &[Source, Clone] : SourceToClone)
+ RecordingInstructionIDs.try_emplace(Clone, Source);
+ }
+
+ [[nodiscard]] Expected<bool> request(std::string_view Rule, const MachineFunction &Function,
+ MachineInstrStableID Instruction = {},
+ std::uint64_t Cost = 1);
+ void beginReplay() noexcept;
+ [[nodiscard]] Expected<void> finishReplay() const;
+
+private:
+ struct Decision final {
+ std::string Rule;
+ MachineFunctionStableID Function;
+ MachineInstrStableID Instruction;
+ std::uint64_t Cost = 0;
+ bool Accepted = false;
+ };
+
+ MachineAnalysisManager *Analyses = nullptr;
+ std::unordered_map<MachineInstrStableID, MachineInstrStableID> RecordingInstructionIDs;
+ std::vector<Decision> Decisions;
+ std::size_t NextDecision = 0;
+ bool IsReplaying = false;
+};
+
class MachinePassInstrumentation final {
public:
using BeforeCallback = std::function<void(std::string_view, const MachineFunction &)>;
@@ -489,8 +546,7 @@ public:
std::function<void(InstrumentationPhase, std::string_view, const MachineFunction &)>;
using ChangeCallback =
std::function<void(std::string_view, const MachineFunction &, MachinePassChanges, bool)>;
- using ActionFilter =
- std::function<bool(std::string_view, const MachineFunction &, std::uint64_t)>;
+ using ActionFilter = std::function<bool(const MachineActionID &, std::uint64_t)>;
using BeforeUnitCallback = std::function<void(std::string_view, const CompilationUnit &)>;
using AfterUnitCallback = std::function<void(std::string_view, const CompilationUnit &,
@@ -522,6 +578,7 @@ public:
void addUnitAnalysisFailureCallback(UnitAnalysisFailureCallback Callback);
[[nodiscard]] std::vector<InstrumentationStatistic> statistics() const;
+ [[nodiscard]] std::vector<MachineActionEvent> actionEvents() const;
void clearStatistics();
private:
@@ -548,7 +605,7 @@ private:
const Error &ErrorValue, std::uint64_t Nanoseconds) const;
void change(std::string_view Name, const MachineFunction &Function, MachinePassChanges Changes,
bool FingerprintChanged) const;
- [[nodiscard]] Expected<bool> requestAction(std::string_view Action,
+ [[nodiscard]] Expected<bool> requestAction(const MachineActionID &Action,
const MachineFunction &Function, std::uint64_t Cost);
void beforeUnit(std::string_view Name, const CompilationUnit &Unit) const;
@@ -577,6 +634,7 @@ private:
ActionFilter Filter;
std::optional<std::uint64_t> PerFunctionFuel;
std::unordered_map<std::uint64_t, std::uint64_t> ConsumedFuel;
+ std::vector<MachineActionEvent> ActionEvents;
std::vector<BeforeUnitCallback> BeforeUnit;
std::vector<AfterUnitCallback> AfterUnit;
@@ -602,6 +660,8 @@ struct MachinePassManagerOptions final {
class MachinePassManager final {
public:
+ using PassList = std::vector<std::unique_ptr<MachineFunctionPass>>;
+
explicit MachinePassManager(MachinePassManagerOptions Options = {}) : Options(Options) {}
MachinePassManager(const MachinePassManager &) = delete;
@@ -620,11 +680,15 @@ public:
return Passes.size();
}
+ [[nodiscard]] PassList takePasses() && noexcept {
+ return std::move(Passes);
+ }
+
private:
[[nodiscard]] Expected<void> validateContract(const MachinePassContract &Contract) const;
MachinePassManagerOptions Options;
- std::vector<std::unique_ptr<MachineFunctionPass>> Passes;
+ PassList Passes;
};
struct CompilationUnitPassManagerOptions final {
diff --git a/backend/include/rcc/CodeGen/MachinePipelinePasses.h b/backend/include/rcc/CodeGen/MachinePipelinePasses.h
index c5bdac0..08f0dac 100644
--- a/backend/include/rcc/CodeGen/MachinePipelinePasses.h
+++ b/backend/include/rcc/CodeGen/MachinePipelinePasses.h
@@ -21,7 +21,7 @@ public:
private:
[[nodiscard]] Expected<MachinePassResult> runImplementation(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &Analyses);
+ MachineActionReplay &Actions);
const TargetLegalizerInfo *Info;
};
@@ -53,7 +53,7 @@ public:
private:
[[nodiscard]] Expected<MachinePassResult> runImplementation(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &Analyses);
+ MachineActionReplay &Actions);
const TargetInstructionSelector *Info;
};
diff --git a/backend/include/rcc/CodeGen/MachineSSAExit.h b/backend/include/rcc/CodeGen/MachineSSAExit.h
index d98c0b9..fdf74f0 100644
--- a/backend/include/rcc/CodeGen/MachineSSAExit.h
+++ b/backend/include/rcc/CodeGen/MachineSSAExit.h
@@ -21,7 +21,7 @@ public:
private:
[[nodiscard]] Expected<MachinePassResult> runImplementation(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &Analyses);
+ MachineActionReplay &Actions);
const TargetSSAInfo *Info = nullptr;
};
@@ -39,7 +39,7 @@ public:
private:
[[nodiscard]] Expected<MachinePassResult> runImplementation(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &Analyses);
+ MachineActionReplay &Actions);
const TargetSSAInfo *Info = nullptr;
};
diff --git a/backend/include/rcc/CodeGen/PBQP/Solver.h b/backend/include/rcc/CodeGen/PBQP/Solver.h
index ff14857..f010fd1 100644
--- a/backend/include/rcc/CodeGen/PBQP/Solver.h
+++ b/backend/include/rcc/CodeGen/PBQP/Solver.h
@@ -9,6 +9,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
+#include <functional>
#include <map>
#include <optional>
#include <set>
@@ -18,9 +19,24 @@
namespace rcc::pbqp {
+enum class SolverActionKind : std::uint8_t {
+ Reduction,
+ Choice,
+};
+
+struct SolverAction final {
+ SolverActionKind Kind = SolverActionKind::Reduction;
+ ReductionKind Reduction = ReductionKind::DegreeZero;
+ NodeID Node;
+ std::size_t Value = 0;
+};
+
class DeterministicSolver final {
public:
- [[nodiscard]] Expected<Solution> solve(const Graph &Problem) const {
+ using ActionCallback = std::function<Expected<bool>(const SolverAction &)>;
+
+ [[nodiscard]] Expected<Solution> solve(const Graph &Problem,
+ const ActionCallback &Action = {}) const {
auto Verified = Problem.verify();
if (!Verified)
return std::unexpected(Verified.error());
@@ -31,10 +47,24 @@ public:
while (Work.activeNodeCount() != 0) {
const NodeID Selected = Work.selectReductionNode();
const std::size_t Degree = Work.degree(Selected);
- Expected<ReductionRecord> Reduced = Degree == 0 ? Work.reduceDegreeZero(Selected)
- : Degree == 1 ? Work.reduceDegreeOne(Selected)
- : Degree == 2 ? Work.reduceDegreeTwo(Selected)
- : Work.reduceHeuristically(Selected);
+ const ReductionKind Kind = Degree == 0 ? ReductionKind::DegreeZero
+ : Degree == 1 ? ReductionKind::DegreeOne
+ : Degree == 2 ? ReductionKind::DegreeTwo
+ : ReductionKind::Heuristic;
+ if (Action) {
+ auto Accepted = Action({SolverActionKind::Reduction, Kind, Selected, Degree});
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return makeUnexpected(ErrorCode::RegisterAllocationFailed,
+ "PBQP reduction action was rejected");
+ }
+ }
+ Expected<ReductionRecord> Reduced =
+ Kind == ReductionKind::DegreeZero ? Work.reduceDegreeZero(Selected)
+ : Kind == ReductionKind::DegreeOne ? Work.reduceDegreeOne(Selected)
+ : Kind == ReductionKind::DegreeTwo ? Work.reduceDegreeTwo(Selected)
+ : Work.reduceHeuristically(Selected);
if (!Reduced)
return std::unexpected(Reduced.error());
Reductions.push_back(std::move(*Reduced));
@@ -71,6 +101,15 @@ public:
return makeUnexpected(ErrorCode::RegisterAllocationFailed,
"PBQP reductions produced no finite option");
}
+ if (Action) {
+ auto Accepted = Action({SolverActionKind::Choice, Record.Kind, Record.Node, Choice});
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return makeUnexpected(ErrorCode::RegisterAllocationFailed,
+ "PBQP choice action was rejected");
+ }
+ }
auto Set = Result.setSelection(Record.Node, Choice);
if (!Set)
return std::unexpected(Set.error());
diff --git a/backend/include/rcc/CodeGen/RegisterPressure.h b/backend/include/rcc/CodeGen/RegisterPressure.h
index fa1209a..107bc29 100644
--- a/backend/include/rcc/CodeGen/RegisterPressure.h
+++ b/backend/include/rcc/CodeGen/RegisterPressure.h
@@ -22,7 +22,7 @@ struct MachineBlockLiveness;
namespace detail {
struct RegisterPressureBuilder;
-}
+} // namespace detail
struct RegisterPressureValue final {
RegisterPressureSetID Set;
diff --git a/backend/include/rcc/CodeGen/Spiller.h b/backend/include/rcc/CodeGen/Spiller.h
index 3d1fc66..f63158d 100644
--- a/backend/include/rcc/CodeGen/Spiller.h
+++ b/backend/include/rcc/CodeGen/Spiller.h
@@ -11,13 +11,16 @@
namespace rcc {
+class MachineActionReplay;
+
class Spiller final {
public:
Spiller(MachineFunction &Function, const TargetDescription &Target,
const TargetRegisterAllocationInfo &TargetInfo, VirtRegMap &Mapping,
- bool EnableRematerialization = true)
+ bool EnableRematerialization = true, MachineActionReplay *Actions = nullptr)
: Function(&Function), Target(&Target), TargetInfo(&TargetInfo), Mapping(&Mapping),
- Edit(Function, Target, Mapping), EnableRematerialization(EnableRematerialization) {}
+ Edit(Function, Target, Mapping), EnableRematerialization(EnableRematerialization),
+ Actions(Actions) {}
[[nodiscard]] Expected<std::vector<Register>> spill(Register VirtualReg);
@@ -34,6 +37,7 @@ private:
VirtRegMap *Mapping;
LiveRangeEdit Edit;
bool EnableRematerialization;
+ MachineActionReplay *Actions;
};
} // namespace rcc
diff --git a/backend/include/rcc/Support/Error.h b/backend/include/rcc/Support/Error.h
index 63c0b2f..0429cc0 100644
--- a/backend/include/rcc/Support/Error.h
+++ b/backend/include/rcc/Support/Error.h
@@ -30,6 +30,12 @@ enum class ErrorCode : std::uint8_t {
FixupApplicationFailed,
RelocationFailed,
ObjectWritingFailed,
+ InvalidInvocation,
+ ManifestError,
+ CacheMiss,
+ ReproducerError,
+ ReductionFailed,
+ FuzzingFailure,
};
[[nodiscard]] std::string_view getErrorCodeName(ErrorCode Code) noexcept;
diff --git a/backend/include/rcc/Support/SHA256.h b/backend/include/rcc/Support/SHA256.h
new file mode 100644
index 0000000..ff7b50b
--- /dev/null
+++ b/backend/include/rcc/Support/SHA256.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_SUPPORT_SHA256_H
+#define RCC_SUPPORT_SHA256_H
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string>
+#include <string_view>
+
+namespace rcc {
+
+using SHA256Digest = std::array<std::uint8_t, 32>;
+
+[[nodiscard]] SHA256Digest sha256(std::span<const std::byte> Input);
+[[nodiscard]] SHA256Digest sha256(std::string_view Input);
+[[nodiscard]] std::string sha256Hex(std::span<const std::byte> Input);
+[[nodiscard]] std::string sha256Hex(std::string_view Input);
+
+} // namespace rcc
+
+#endif // RCC_SUPPORT_SHA256_H
diff --git a/backend/include/rcc/Target/RISCV/RISCVPipeline.h b/backend/include/rcc/Target/RISCV/RISCVPipeline.h
new file mode 100644
index 0000000..8bd79e9
--- /dev/null
+++ b/backend/include/rcc/Target/RISCV/RISCVPipeline.h
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TARGET_RISCV_RISCVPIPELINE_H
+#define RCC_TARGET_RISCV_RISCVPIPELINE_H
+
+#include "rcc/CodeGen/MachinePass.h"
+#include "rcc/Support/Error.h"
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc {
+
+class MachineFunction;
+class TargetDescription;
+
+namespace riscv {
+
+[[nodiscard]] Expected<void> addCanonicalPipeline(MachinePassManager &Pipeline,
+ const MachineFunction &Function);
+[[nodiscard]] Expected<std::unique_ptr<MachineFunctionPass>>
+createCanonicalPipelinePass(std::string_view Name);
+[[nodiscard]] Expected<std::vector<std::string>>
+canonicalPipelinePassNames(const MachineFunction &Function);
+[[nodiscard]] Expected<void>
+runCanonicalPipeline(MachineFunction &Function, const TargetDescription &Target,
+ MachinePassManagerOptions Options = {},
+ MachinePassInstrumentation *Instrumentation = nullptr);
+
+} // namespace riscv
+} // namespace rcc
+
+#endif // RCC_TARGET_RISCV_RISCVPIPELINE_H
diff --git a/backend/include/rcc/Tooling/ArtifactStore.h b/backend/include/rcc/Tooling/ArtifactStore.h
new file mode 100644
index 0000000..a30f572
--- /dev/null
+++ b/backend/include/rcc/Tooling/ArtifactStore.h
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TOOLING_ARTIFACTSTORE_H
+#define RCC_TOOLING_ARTIFACTSTORE_H
+
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Support/Error.h"
+#include "rcc/Tooling/BackendManifest.h"
+
+#include <cstdint>
+#include <filesystem>
+#include <optional>
+#include <span>
+#include <string>
+#include <vector>
+
+namespace rcc {
+
+class TargetDescription;
+
+namespace tooling {
+
+struct CanonicalMIR final {
+ mir::MIRDocument Document;
+ std::string Text;
+};
+
+struct CacheEntry final {
+ BackendManifest Manifest;
+ std::string InputMIR;
+ std::vector<std::uint8_t> Artifact;
+ std::optional<CanonicalMIR> ParsedArtifact;
+};
+
+struct ReproducerPackage final {
+ BackendManifest Manifest;
+ CanonicalMIR Input;
+};
+
+[[nodiscard]] Expected<CanonicalMIR> parseAndCanonicalizeMIR(std::string SourceName,
+ std::string SourceText,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation);
+
+[[nodiscard]] Expected<BackendManifest>
+createBackendManifest(ManifestKind Kind, ArtifactKind Artifact, const BackendInvocation &Invocation,
+ const TargetDescription &Target, std::string_view CanonicalInputMIR,
+ std::optional<std::span<const std::uint8_t>> ArtifactBytes = std::nullopt,
+ std::vector<StructuredDiagnostic> Diagnostics = {});
+
+[[nodiscard]] Expected<void> storeCacheEntry(const std::filesystem::path &CacheRoot,
+ const BackendManifest &Manifest,
+ std::string_view CanonicalInputMIR,
+ std::span<const std::uint8_t> Artifact);
+[[nodiscard]] Expected<CacheEntry> loadCacheEntry(const std::filesystem::path &CacheRoot,
+ ArtifactKind Artifact,
+ const BackendInvocation &Invocation,
+ const TargetDescription &Target,
+ std::string_view CanonicalInputMIR);
+
+[[nodiscard]] Expected<void> writeReproducer(const std::filesystem::path &PackagePath,
+ const BackendManifest &Manifest,
+ std::string_view CanonicalInputMIR);
+[[nodiscard]] Expected<void>
+updateReproducerDiagnostics(const std::filesystem::path &PackagePath,
+ std::vector<StructuredDiagnostic> Diagnostics);
+[[nodiscard]] Expected<ReproducerPackage> loadReproducer(const std::filesystem::path &PackagePath,
+ const TargetDescription &Target);
+
+} // namespace tooling
+} // namespace rcc
+
+#endif // RCC_TOOLING_ARTIFACTSTORE_H
diff --git a/backend/include/rcc/Tooling/BackendDriver.h b/backend/include/rcc/Tooling/BackendDriver.h
new file mode 100644
index 0000000..f762c1e
--- /dev/null
+++ b/backend/include/rcc/Tooling/BackendDriver.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TOOLING_BACKENDDRIVER_H
+#define RCC_TOOLING_BACKENDDRIVER_H
+
+#include "rcc/CodeGen/MachinePass.h"
+#include "rcc/Support/Error.h"
+#include "rcc/Tooling/ArtifactStore.h"
+#include "rcc/Tooling/PipelineDriver.h"
+
+#include <cstdint>
+#include <span>
+#include <string_view>
+#include <vector>
+
+namespace rcc {
+
+class TargetDescription;
+
+namespace tooling {
+
+enum class BackendOutputKind : std::uint8_t {
+ PreLegalizerMIR,
+ LegalizedMIR,
+ RegisterBankMIR,
+ SelectedMIR,
+ PostSSAMIR,
+ PostRegisterAllocationMIR,
+ FinalMIR,
+ Assembly,
+ RelocatableObject,
+};
+
+struct BackendRunResult final {
+ BackendOutputKind Kind = BackendOutputKind::FinalMIR;
+ std::vector<std::uint8_t> Output;
+ PipelineRunResult Pipeline;
+ std::vector<MachineActionEvent> Actions;
+
+ [[nodiscard]] std::string_view text() const noexcept;
+};
+
+[[nodiscard]] Expected<BackendRunResult>
+runBackend(CanonicalMIR Input, const TargetDescription &Target, const BackendInvocation &Invocation,
+ BackendOutputKind Output, PipelineControls Controls = {},
+ MachinePassInstrumentation *Instrumentation = nullptr);
+
+[[nodiscard]] std::string_view spelling(BackendOutputKind Output) noexcept;
+[[nodiscard]] Expected<BackendOutputKind> parseBackendOutputKind(std::string_view Value);
+[[nodiscard]] ArtifactKind manifestArtifactKind(BackendOutputKind Output) noexcept;
+[[nodiscard]] Expected<ArtifactKind> cacheArtifactKind(BackendOutputKind Output);
+
+} // namespace tooling
+} // namespace rcc
+
+#endif // RCC_TOOLING_BACKENDDRIVER_H
diff --git a/backend/include/rcc/Tooling/BackendInvocation.h b/backend/include/rcc/Tooling/BackendInvocation.h
new file mode 100644
index 0000000..db1c5d2
--- /dev/null
+++ b/backend/include/rcc/Tooling/BackendInvocation.h
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TOOLING_BACKENDINVOCATION_H
+#define RCC_TOOLING_BACKENDINVOCATION_H
+
+#include "rcc/Support/Error.h"
+
+#include <cstdint>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc {
+
+class TargetDescription;
+
+namespace tooling {
+
+inline constexpr std::uint32_t BackendInvocationSchemaVersion = 1;
+inline constexpr std::string_view LLVMReferenceRevision =
+ "2b40c303e79f64c5c7642032554eda026594d604";
+inline constexpr std::string_view GenericOpcodeReferenceVersion = "rcc-generic-opcode-v1";
+inline constexpr std::string_view RV64GCTargetABILoweringVersion = "rcc-rv64gc-lp64d-v1";
+inline constexpr std::string_view CompilerBuildFingerprint = "rcc-backend-0.1.0-stage17";
+
+enum class RelocationModel : std::uint8_t {
+ Static,
+ PositionIndependent,
+};
+
+enum class CodeModel : std::uint8_t {
+ Small,
+ Medium,
+ Large,
+};
+
+enum class OptimizationLevel : std::uint8_t {
+ O0,
+ O1,
+ O2,
+ O3,
+ Os,
+ Oz,
+};
+
+enum class LayoutOwnership : std::uint8_t {
+ NativeObject,
+ CompilerFinalLayout,
+ ExternalRelaxation,
+};
+
+struct BackendOption final {
+ std::string Name;
+ std::string Value;
+
+ friend bool operator==(const BackendOption &, const BackendOption &) = default;
+};
+
+struct DeterminismOptions final {
+ std::uint64_t RandomSeed = 0;
+ std::uint32_t ThreadCount = 1;
+ bool StableScheduling = true;
+
+ friend bool operator==(const DeterminismOptions &, const DeterminismOptions &) = default;
+};
+
+struct BackendInvocation final {
+ std::uint32_t SchemaVersion = BackendInvocationSchemaVersion;
+ std::string Triple;
+ std::string CPU;
+ std::string TuneCPU;
+ std::vector<std::string> Features;
+ std::string ABI;
+ std::string DataLayout;
+ RelocationModel Relocation = RelocationModel::PositionIndependent;
+ CodeModel Code = CodeModel::Small;
+ std::vector<BackendOption> TargetOptions;
+ LayoutOwnership Layout = LayoutOwnership::NativeObject;
+ std::string AssemblerFlavor;
+ std::string AssemblerVersion;
+ std::vector<BackendOption> AssemblerOptions;
+ OptimizationLevel Optimization = OptimizationLevel::O2;
+ std::vector<std::string> PassPipeline{"default"};
+ DeterminismOptions Determinism;
+ std::optional<std::uint64_t> ActionFuel;
+ std::vector<std::string> EnabledActions;
+ std::string MIRReferenceRevision{LLVMReferenceRevision};
+ std::string GenericOpcodeVersion{GenericOpcodeReferenceVersion};
+ std::string TargetABILoweringVersion{RV64GCTargetABILoweringVersion};
+ std::string BuildFingerprint{CompilerBuildFingerprint};
+
+ [[nodiscard]] Expected<void> canonicalizeAndVerify();
+ [[nodiscard]] std::string canonicalText() const;
+
+ friend bool operator==(const BackendInvocation &, const BackendInvocation &) = default;
+};
+
+[[nodiscard]] BackendInvocation defaultRV64GCInvocation();
+[[nodiscard]] Expected<std::string>
+computeTargetDescriptionFingerprint(const TargetDescription &Target);
+[[nodiscard]] Expected<std::string> computeCacheKey(std::string_view CanonicalMIR,
+ const BackendInvocation &Invocation,
+ std::string_view TargetFingerprint);
+
+[[nodiscard]] std::string_view spelling(RelocationModel Value) noexcept;
+[[nodiscard]] std::string_view spelling(CodeModel Value) noexcept;
+[[nodiscard]] std::string_view spelling(OptimizationLevel Value) noexcept;
+[[nodiscard]] std::string_view spelling(LayoutOwnership Value) noexcept;
+[[nodiscard]] Expected<RelocationModel> parseRelocationModel(std::string_view Value);
+[[nodiscard]] Expected<CodeModel> parseCodeModel(std::string_view Value);
+[[nodiscard]] Expected<OptimizationLevel> parseOptimizationLevel(std::string_view Value);
+[[nodiscard]] Expected<LayoutOwnership> parseLayoutOwnership(std::string_view Value);
+
+} // namespace tooling
+} // namespace rcc
+
+#endif // RCC_TOOLING_BACKENDINVOCATION_H
diff --git a/backend/include/rcc/Tooling/BackendManifest.h b/backend/include/rcc/Tooling/BackendManifest.h
new file mode 100644
index 0000000..d5d5733
--- /dev/null
+++ b/backend/include/rcc/Tooling/BackendManifest.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TOOLING_BACKENDMANIFEST_H
+#define RCC_TOOLING_BACKENDMANIFEST_H
+
+#include "rcc/Tooling/BackendInvocation.h"
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc::tooling {
+
+inline constexpr std::uint32_t BackendManifestSchemaVersion = 1;
+
+enum class ManifestKind : std::uint8_t {
+ Cache,
+ Reproducer,
+};
+
+enum class ArtifactKind : std::uint8_t {
+ PreLegalizerMIR,
+ LegalizedMIR,
+ RegisterBankMIR,
+ SelectedMIR,
+ PostSSAMIR,
+ PostRegisterAllocationMIR,
+ FinalMIR,
+ Assembly,
+ RelocatableObject,
+};
+
+struct StructuredDiagnostic final {
+ std::string Category;
+ std::string Stage;
+ std::string Pass;
+ std::string Function;
+ std::optional<std::uint64_t> Block;
+ std::optional<std::uint64_t> Instruction;
+ std::optional<std::uint32_t> Operand;
+ std::string SourceLocation;
+ std::string TargetConstraint;
+ std::string Rule;
+ std::string Message;
+
+ friend bool operator==(const StructuredDiagnostic &, const StructuredDiagnostic &) = default;
+};
+
+struct BackendManifest final {
+ std::uint32_t SchemaVersion = BackendManifestSchemaVersion;
+ ManifestKind Kind = ManifestKind::Reproducer;
+ ArtifactKind Artifact = ArtifactKind::FinalMIR;
+ BackendInvocation Invocation;
+ std::string InputMIRHash;
+ std::string TargetFingerprint;
+ std::string CacheKey;
+ std::optional<std::string> ArtifactHash;
+ std::vector<StructuredDiagnostic> Diagnostics;
+
+ [[nodiscard]] Expected<void> verify() const;
+
+ friend bool operator==(const BackendManifest &, const BackendManifest &) = default;
+};
+
+[[nodiscard]] Expected<std::string> printBackendManifest(const BackendManifest &Manifest);
+[[nodiscard]] Expected<BackendManifest> parseBackendManifest(std::string SourceName,
+ std::string SourceText);
+[[nodiscard]] std::string_view spelling(ManifestKind Value) noexcept;
+[[nodiscard]] std::string_view spelling(ArtifactKind Value) noexcept;
+
+} // namespace rcc::tooling
+
+#endif // RCC_TOOLING_BACKENDMANIFEST_H
diff --git a/backend/include/rcc/Tooling/MIRFuzzer.h b/backend/include/rcc/Tooling/MIRFuzzer.h
new file mode 100644
index 0000000..7fccb6d
--- /dev/null
+++ b/backend/include/rcc/Tooling/MIRFuzzer.h
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TOOLING_MIRFUZZER_H
+#define RCC_TOOLING_MIRFUZZER_H
+
+#include "rcc/Support/Error.h"
+#include "rcc/Tooling/BackendInvocation.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc {
+
+class TargetDescription;
+
+namespace tooling {
+
+enum class MIRFuzzLayer : std::uint8_t {
+ Parser,
+ BuilderStructural,
+ PropertyStage,
+ Mutation,
+ Pipeline,
+ RestrictedSemantics,
+};
+
+struct MIRFuzzOptions final {
+ std::uint64_t Seed = 0;
+ std::size_t Iterations = 100;
+ std::size_t MaximumInputSize = 1U << 20U;
+ std::vector<MIRFuzzLayer> Layers;
+};
+
+struct MIRFuzzLayerResult final {
+ MIRFuzzLayer Layer = MIRFuzzLayer::Parser;
+ std::size_t Executed = 0;
+ std::size_t Accepted = 0;
+ std::size_t Rejected = 0;
+
+ friend bool operator==(const MIRFuzzLayerResult &, const MIRFuzzLayerResult &) = default;
+};
+
+struct MIRFuzzResult final {
+ std::uint64_t Seed = 0;
+ std::vector<MIRFuzzLayerResult> Layers;
+
+ [[nodiscard]] std::size_t executed() const noexcept;
+ [[nodiscard]] std::size_t accepted() const noexcept;
+ [[nodiscard]] std::size_t rejected() const noexcept;
+};
+
+[[nodiscard]] Expected<MIRFuzzResult> fuzzMIR(std::string SourceName, std::string SourceText,
+ BackendInvocation Invocation,
+ const TargetDescription &Target,
+ MIRFuzzOptions Options = {});
+
+[[nodiscard]] std::string_view spelling(MIRFuzzLayer Layer) noexcept;
+[[nodiscard]] Expected<MIRFuzzLayer> parseMIRFuzzLayer(std::string_view Value);
+
+} // namespace tooling
+} // namespace rcc
+
+#endif // RCC_TOOLING_MIRFUZZER_H
diff --git a/backend/include/rcc/Tooling/MIRReducer.h b/backend/include/rcc/Tooling/MIRReducer.h
new file mode 100644
index 0000000..35ef9c0
--- /dev/null
+++ b/backend/include/rcc/Tooling/MIRReducer.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TOOLING_MIRREDUCER_H
+#define RCC_TOOLING_MIRREDUCER_H
+
+#include "rcc/Support/Error.h"
+#include "rcc/Tooling/BackendInvocation.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc {
+
+class TargetDescription;
+
+namespace tooling {
+
+enum class ReductionMode : std::uint8_t {
+ PreserveValidity,
+ PreserveFailure,
+};
+
+enum class ReductionGranularity : std::uint8_t {
+ CompilationUnit,
+ MachineFunction,
+ BasicBlock,
+ CFGEdge,
+ Instruction,
+ Operand,
+ RegisterSubgraph,
+ FrameObject,
+ ConstantPool,
+ JumpTable,
+ TargetFeature,
+ PassPipeline,
+};
+
+struct MIRReducerOptions final {
+ ReductionMode Mode = ReductionMode::PreserveFailure;
+ std::size_t MaximumAttempts = 100'000;
+ std::vector<ReductionGranularity> Granularities;
+};
+
+using MIRInterestingnessTest =
+ std::function<Expected<bool>(std::string_view, const BackendInvocation &)>;
+
+// PreserveFailure requires and evaluates the interestingness test. PreserveValidity accepts every
+// smaller candidate that reparses, canonicalizes, and verifies; its interestingness test is
+// ignored.
+
+struct MIRReductionStep final {
+ ReductionGranularity Granularity = ReductionGranularity::Instruction;
+ std::string Description;
+ std::size_t PreviousSize = 0;
+ std::size_t ReducedSize = 0;
+};
+
+struct MIRReductionResult final {
+ std::string MIR;
+ BackendInvocation Invocation;
+ std::vector<MIRReductionStep> Steps;
+ std::size_t Attempts = 0;
+ bool Exhausted = false;
+};
+
+[[nodiscard]] Expected<MIRReductionResult> reduceMIR(std::string SourceName, std::string SourceText,
+ BackendInvocation Invocation,
+ const TargetDescription &Target,
+ const MIRInterestingnessTest &Interesting,
+ MIRReducerOptions Options = {});
+
+[[nodiscard]] std::string_view spelling(ReductionGranularity Granularity) noexcept;
+
+} // namespace tooling
+} // namespace rcc
+
+#endif // RCC_TOOLING_MIRREDUCER_H
diff --git a/backend/include/rcc/Tooling/PipelineDriver.h b/backend/include/rcc/Tooling/PipelineDriver.h
new file mode 100644
index 0000000..2de341c
--- /dev/null
+++ b/backend/include/rcc/Tooling/PipelineDriver.h
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef RCC_TOOLING_PIPELINEDRIVER_H
+#define RCC_TOOLING_PIPELINEDRIVER_H
+
+#include "rcc/CodeGen/MachinePass.h"
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Tooling/BackendInvocation.h"
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc {
+
+class TargetDescription;
+
+namespace tooling {
+
+enum class BackendStage : std::uint8_t {
+ PreLegalizerMIR,
+ LegalizedMIR,
+ RegisterBankMIR,
+ SelectedMIR,
+ PostSSAMIR,
+ PostRegisterAllocationMIR,
+ FinalMIR,
+};
+
+enum class SnapshotPhase : std::uint8_t {
+ Stage,
+ BeforePass,
+ AfterPass,
+};
+
+struct PipelineControls final {
+ std::optional<std::string> StopBefore;
+ std::optional<std::string> StopAfter;
+ std::vector<std::string> PrintBefore;
+ std::vector<std::string> PrintAfter;
+ std::optional<BackendStage> StopAtStage;
+ bool CaptureStageSnapshots = false;
+ bool VerifyEach = true;
+ bool DetectUnreportedChanges = true;
+};
+
+struct PipelineSnapshot final {
+ SnapshotPhase Phase = SnapshotPhase::Stage;
+ BackendStage Stage = BackendStage::PreLegalizerMIR;
+ std::string Function;
+ std::string Pass;
+ std::uint32_t Occurrence = 0;
+ std::string MIR;
+};
+
+struct ExecutedPass final {
+ std::string Function;
+ std::string Pass;
+ std::uint32_t Occurrence = 0;
+
+ friend bool operator==(const ExecutedPass &, const ExecutedPass &) = default;
+};
+
+struct PipelineRunResult final {
+ std::vector<ExecutedPass> ExecutedPasses;
+ std::vector<PipelineSnapshot> Snapshots;
+};
+
+[[nodiscard]] Expected<void> verifyRV64GCInvocation(const BackendInvocation &Invocation);
+[[nodiscard]] Expected<PipelineRunResult>
+runRV64GCPipeline(mir::MIRDocument &Document, const TargetDescription &Target,
+ const BackendInvocation &Invocation, PipelineControls Controls = {},
+ MachinePassInstrumentation *Instrumentation = nullptr);
+[[nodiscard]] BackendStage stageForFunction(const MachineFunction &Function) noexcept;
+[[nodiscard]] std::string_view spelling(BackendStage Stage) noexcept;
+[[nodiscard]] Expected<BackendStage> parseBackendStage(std::string_view Value);
+
+} // namespace tooling
+} // namespace rcc
+
+#endif // RCC_TOOLING_PIPELINEDRIVER_H
diff --git a/backend/lib/CMakeLists.txt b/backend/lib/CMakeLists.txt
index 0160f1b..a6ba7ef 100644
--- a/backend/lib/CMakeLists.txt
+++ b/backend/lib/CMakeLists.txt
@@ -5,3 +5,4 @@ add_subdirectory(CodeGen)
add_subdirectory(MIR)
add_subdirectory(MC)
add_subdirectory(AsmPrinter)
+add_subdirectory(Tooling)
diff --git a/backend/lib/CodeGen/InlineSpiller.cpp b/backend/lib/CodeGen/InlineSpiller.cpp
index 589b7b8..14de321 100644
--- a/backend/lib/CodeGen/InlineSpiller.cpp
+++ b/backend/lib/CodeGen/InlineSpiller.cpp
@@ -7,6 +7,7 @@
#include "rcc/CodeGen/MachineIRBuilder.h"
#include "rcc/CodeGen/MachineInstr.h"
#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/MachinePass.h"
#include "rcc/CodeGen/MachineRegisterInfo.h"
#include "rcc/CodeGen/Spiller.h"
#include "rcc/CodeGen/TargetDescription.h"
@@ -42,11 +43,11 @@ Expected<std::optional<std::vector<Register>>> Spiller::tryRematerialize(Registe
const MachineInstr *DefiningInstruction = Definition == nullptr ? nullptr : Definition->parent();
if (DefiningInstruction == nullptr || DefiningInstruction->parent() == nullptr)
return std::optional<std::vector<Register>>{};
- auto Accepted =
+ auto Rematerializable =
TargetInfo->isRematerializable(*DefiningInstruction, VirtualReg, *Function, *Target);
- if (!Accepted)
- return std::unexpected(Accepted.error());
- if (!*Accepted)
+ if (!Rematerializable)
+ return std::unexpected(Rematerializable.error());
+ if (!*Rematerializable)
return std::optional<std::vector<Register>>{};
struct UseSite final {
@@ -77,6 +78,16 @@ Expected<std::optional<std::vector<Register>>> Spiller::tryRematerialize(Registe
if (Sites.empty())
return std::optional<std::vector<Register>>{};
+ if (Actions != nullptr) {
+ for (const UseSite &Site : Sites) {
+ auto Accepted = Actions->request("rematerialize", *Function, Site.Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted)
+ return std::optional<std::vector<Register>>{};
+ }
+ }
+
std::vector<Register> NewRegisters;
NewRegisters.reserve(Sites.size());
for (UseSite &Site : Sites) {
@@ -169,6 +180,19 @@ Expected<std::vector<Register>> Spiller::spillAtInstructions(Register VirtualReg
}
std::vector<Register> NewRegisters;
+ if (Actions != nullptr) {
+ for (const SpillSite &Site : Sites) {
+ if (Site.OperandIndices.empty())
+ continue;
+ auto Accepted = Actions->request("split-live-range", *Function, Site.Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return makeUnexpected(ErrorCode::RegisterAllocationFailed,
+ "required live-range split action was rejected");
+ }
+ }
+ }
for (SpillSite &Site : Sites) {
for (const std::size_t Index : Site.DebugIndices) {
MachineOperand *Operand = Site.Instruction->operandAt(Index);
@@ -259,6 +283,19 @@ Expected<std::vector<Register>> Spiller::spill(Register VirtualReg) {
return makeUnexpected(ErrorCode::RegisterAllocationFailed,
"register pressure requires spilling a non-spillable value");
}
+ if (Actions != nullptr) {
+ const MachineOperand *Definition = Function->registerInfo().getUniqueVRegDef(VirtualReg);
+ const MachineInstr *Instruction = Definition == nullptr ? nullptr : Definition->parent();
+ auto Accepted =
+ Actions->request("spill", *Function,
+ Instruction == nullptr ? MachineInstrStableID{} : Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return makeUnexpected(ErrorCode::RegisterAllocationFailed,
+ "required spill action was rejected");
+ }
+ }
auto Rematerialized = tryRematerialize(VirtualReg);
if (!Rematerialized)
return std::unexpected(Rematerialized.error());
diff --git a/backend/lib/CodeGen/MachineBlockPlacement.cpp b/backend/lib/CodeGen/MachineBlockPlacement.cpp
index fea1283..daccba0 100644
--- a/backend/lib/CodeGen/MachineBlockPlacement.cpp
+++ b/backend/lib/CodeGen/MachineBlockPlacement.cpp
@@ -114,21 +114,25 @@ Expected<MachinePassResult> MachineBlockPlacementPass::run(MachineFunction &Func
if (!Trial)
return std::unexpected(prefixedError(Trial.error(), "block placement setup failed: "));
MachineAnalysisManager TrialAnalyses;
- auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = runImplementation(Function, Target, Analyses);
+ Actions.beginReplay();
+ auto Result = runImplementation(Function, Target, Analyses, Actions);
if (!Result) {
return std::unexpected(prefixedError(
Result.error(), "block placement diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
-Expected<MachinePassResult>
-MachineBlockPlacementPass::runImplementation(MachineFunction &Function,
- const TargetDescription &Target,
- MachineAnalysisManager &Analyses) const {
+Expected<MachinePassResult> MachineBlockPlacementPass::runImplementation(
+ MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses,
+ MachineActionReplay &Actions) const {
auto Probabilities = Analyses.getResult<MachineBranchProbabilityAnalysis>(Function, Target);
if (!Probabilities)
return std::unexpected(Probabilities.error());
@@ -154,7 +158,7 @@ MachineBlockPlacementPass::runImplementation(MachineFunction &Function,
return Result;
}
- auto factsFor = [&Facts](const MachineBasicBlock &Block) -> const PlacementFacts & {
+ auto FactsFor = [&Facts](const MachineBasicBlock &Block) -> const PlacementFacts & {
return *std::ranges::find_if(
Facts, [&Block](const PlacementFacts &Value) { return Value.Block == &Block; });
};
@@ -186,7 +190,7 @@ MachineBlockPlacementPass::runImplementation(MachineFunction &Function,
continue;
if (BestSuccessor == nullptr || BestProbability < Successor.Probability ||
(BestProbability == Successor.Probability &&
- betterRoot(factsFor(*Successor.Block), factsFor(*BestSuccessor)))) {
+ betterRoot(FactsFor(*Successor.Block), FactsFor(*BestSuccessor)))) {
BestSuccessor = Successor.Block;
BestProbability = Successor.Probability;
}
@@ -223,7 +227,22 @@ MachineBlockPlacementPass::runImplementation(MachineFunction &Function,
bool Changed = false;
for (std::size_t Index = 1; Index < Order.size(); ++Index) {
- Changed |= Order[Index - 1U]->next() != Order[Index];
+ if (Order[Index - 1U]->next() != Order[Index]) {
+ Changed = true;
+ break;
+ }
+ }
+ if (Changed) {
+ auto Accepted = Actions.request("apply-block-layout", Function);
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ MachinePassResult Result;
+ Result.Preserved = PreservedAnalyses::all();
+ return Result;
+ }
+ }
+ for (std::size_t Index = 1; Index < Order.size(); ++Index) {
auto Moved = Function.moveBlockAfter(*Order[Index - 1U], *Order[Index]);
if (!Moved)
return std::unexpected(Moved.error());
diff --git a/backend/lib/CodeGen/MachineBranchRelaxation.cpp b/backend/lib/CodeGen/MachineBranchRelaxation.cpp
index d56f3a0..b2a330a 100644
--- a/backend/lib/CodeGen/MachineBranchRelaxation.cpp
+++ b/backend/lib/CodeGen/MachineBranchRelaxation.cpp
@@ -109,7 +109,7 @@ const MachinePassContract &MachineBranchRelaxationPass::contract() const noexcep
Expected<MachinePassResult> MachineBranchRelaxationPass::run(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineAnalysisManager &Analyses) {
if (!Function.properties().containsAll(contract().Requires)) {
return makeUnexpected(ErrorCode::VerificationError,
"branch relaxation requires finalized frame and tracked liveness");
@@ -119,20 +119,26 @@ Expected<MachinePassResult> MachineBranchRelaxationPass::run(MachineFunction &Fu
MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt});
if (!Trial)
return std::unexpected(prefixedError(Trial.error(), "branch relaxation setup failed: "));
- auto TrialResult = runImplementation(*Trial->ClonedFunction, Target);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = runImplementation(Function, Target);
+ Actions.beginReplay();
+ auto Result = runImplementation(Function, Target, Actions);
if (!Result) {
return std::unexpected(prefixedError(
Result.error(), "branch relaxation diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
Expected<MachinePassResult>
MachineBranchRelaxationPass::runImplementation(MachineFunction &Function,
- const TargetDescription &Target) const {
+ const TargetDescription &Target,
+ MachineActionReplay &Actions) const {
if (Options.ConvergenceWatchdog == 0)
return makeUnexpected(ErrorCode::InvalidArgument, "branch relaxation watchdog must be nonzero");
MachinePassResult Result;
@@ -162,6 +168,13 @@ MachineBranchRelaxationPass::runImplementation(MachineFunction &Function,
return std::unexpected(Offset.error());
if (Info->isBranchOffsetInRange(*Instruction, *Offset))
continue;
+ auto Accepted = Actions.request("relax-branch", Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return makeUnexpected(ErrorCode::BranchRelaxationFailed,
+ "required branch relaxation action was rejected");
+ }
auto Relaxed = Info->relaxBranch(*Instruction, *Offset, Function, Target);
if (!Relaxed)
return std::unexpected(Relaxed.error());
diff --git a/backend/lib/CodeGen/MachineCSE.cpp b/backend/lib/CodeGen/MachineCSE.cpp
index a144403..47bba04 100644
--- a/backend/lib/CodeGen/MachineCSE.cpp
+++ b/backend/lib/CodeGen/MachineCSE.cpp
@@ -161,8 +161,11 @@ template <typename ValueT>
if (Reg == nullptr || !Reg->isVirtual())
continue;
auto Info = Registers.virtualRegisterInfo(*Reg);
- if (Info && Info->get().Type && Info->get().Type->isFloat())
- return true;
+ if (Info) {
+ const auto &Type = Info->get().Type;
+ if (Type && Type->isFloat())
+ return true;
+ }
}
return false;
}
diff --git a/backend/lib/CodeGen/MachineCombiner.cpp b/backend/lib/CodeGen/MachineCombiner.cpp
index 513d282..54915ab 100644
--- a/backend/lib/CodeGen/MachineCombiner.cpp
+++ b/backend/lib/CodeGen/MachineCombiner.cpp
@@ -279,7 +279,7 @@ const MachinePassContract &MachineCombinerPass::contract() const noexcept {
Expected<MachinePassResult> MachineCombinerPass::run(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineAnalysisManager &Analyses) {
if (!Function.properties().containsAll(contract().Requires)) {
return makeUnexpected(ErrorCode::VerificationError,
"machine combiner input does not satisfy its stage contract");
@@ -291,20 +291,25 @@ Expected<MachinePassResult> MachineCombinerPass::run(MachineFunction &Function,
return std::unexpected(
prefixedError(Trial.error(), "machine combiner transaction setup failed: "));
}
- auto TrialResult = runImplementation(*Trial->ClonedFunction, Target);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = runImplementation(Function, Target);
+ Actions.beginReplay();
+ auto Result = runImplementation(Function, Target, Actions);
if (!Result) {
return std::unexpected(prefixedError(
Result.error(), "machine combiner diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
Expected<MachinePassResult>
-MachineCombinerPass::runImplementation(MachineFunction &Function,
- const TargetDescription &Target) const {
+MachineCombinerPass::runImplementation(MachineFunction &Function, const TargetDescription &Target,
+ MachineActionReplay &Actions) const {
if (Options.MaximumIterations == 0)
return makeUnexpected(ErrorCode::InvalidArgument,
"machine combiner iteration budget must be nonzero");
@@ -321,6 +326,13 @@ MachineCombinerPass::runImplementation(MachineFunction &Function,
continue;
if (Rewrites == Options.MaximumRewrites)
break;
+ std::string Rule = "combine.";
+ Rule.append(Pattern.Name);
+ auto Accepted = Actions.request(Rule, Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted)
+ continue;
auto Executed = executePattern(*Instruction, Pattern, Function, Target);
if (!Executed)
return std::unexpected(Executed.error());
diff --git a/backend/lib/CodeGen/MachineCompilationStatus.cpp b/backend/lib/CodeGen/MachineCompilationStatus.cpp
index 3ebfa0f..df009a4 100644
--- a/backend/lib/CodeGen/MachineCompilationStatus.cpp
+++ b/backend/lib/CodeGen/MachineCompilationStatus.cpp
@@ -55,6 +55,12 @@ isKnownDiagnosticSeverity(MachineCompilationDiagnosticSeverity Severity) noexcep
case ErrorCode::FixupApplicationFailed:
case ErrorCode::RelocationFailed:
case ErrorCode::ObjectWritingFailed:
+ case ErrorCode::InvalidInvocation:
+ case ErrorCode::ManifestError:
+ case ErrorCode::CacheMiss:
+ case ErrorCode::ReproducerError:
+ case ErrorCode::ReductionFailed:
+ case ErrorCode::FuzzingFailure:
case ErrorCode::IoError:
case ErrorCode::InternalError:
return true;
diff --git a/backend/lib/CodeGen/MachineCopyPropagation.cpp b/backend/lib/CodeGen/MachineCopyPropagation.cpp
index e537a06..f9211f9 100644
--- a/backend/lib/CodeGen/MachineCopyPropagation.cpp
+++ b/backend/lib/CodeGen/MachineCopyPropagation.cpp
@@ -292,7 +292,7 @@ const MachinePassContract &MachineCopyPropagationPass::contract() const noexcept
Expected<MachinePassResult> MachineCopyPropagationPass::run(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineAnalysisManager &Analyses) {
if (!Function.properties().containsAll(contract().Requires)) {
return makeUnexpected(ErrorCode::VerificationError,
"machine COPY propagation has unmet stage properties");
@@ -304,20 +304,26 @@ Expected<MachinePassResult> MachineCopyPropagationPass::run(MachineFunction &Fun
return std::unexpected(
prefixedError(Trial.error(), "COPY propagation transaction setup failed: "));
}
- auto TrialResult = runImplementation(*Trial->ClonedFunction, Target);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = runImplementation(Function, Target);
+ Actions.beginReplay();
+ auto Result = runImplementation(Function, Target, Actions);
if (!Result) {
return std::unexpected(prefixedError(
Result.error(), "COPY propagation diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
Expected<MachinePassResult>
MachineCopyPropagationPass::runImplementation(MachineFunction &Function,
- const TargetDescription &Target) const {
+ const TargetDescription &Target,
+ MachineActionReplay &Actions) const {
MachinePassResult Result;
std::size_t PropagatedCopies = 0;
for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) {
@@ -329,6 +335,13 @@ MachineCopyPropagationPass::runImplementation(MachineFunction &Function,
Instruction = Next;
continue;
}
+ auto Accepted = Actions.request("propagate-copy", Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ Instruction = Next;
+ continue;
+ }
Expected<bool> Changed =
Stage == MachineCopyPropagationStage::PreRegisterAllocation
? propagateVirtualCopy(*Instruction, *Operands, Function, Target, *Info)
diff --git a/backend/lib/CodeGen/MachineLICM.cpp b/backend/lib/CodeGen/MachineLICM.cpp
index 8c8d732..52fffe7 100644
--- a/backend/lib/CodeGen/MachineLICM.cpp
+++ b/backend/lib/CodeGen/MachineLICM.cpp
@@ -146,8 +146,8 @@ namespace {
auto Info = Function.registerInfo().virtualRegisterInfo(*Reg);
if (!Info)
return std::unexpected(Info.error());
- if (Info->get().Type && Info->get().Type->isFloat() &&
- !Instruction.flags().has(MachineInstrFlag::NoFPExcept)) {
+ const auto &Type = Info->get().Type;
+ if (Type && Type->isFloat() && !Instruction.flags().has(MachineInstrFlag::NoFPExcept)) {
return false;
}
}
diff --git a/backend/lib/CodeGen/MachinePass.cpp b/backend/lib/CodeGen/MachinePass.cpp
index ae69b1c..0ce6f70 100644
--- a/backend/lib/CodeGen/MachinePass.cpp
+++ b/backend/lib/CodeGen/MachinePass.cpp
@@ -141,6 +141,79 @@ void combineFingerprint(std::uint64_t &State, std::uint64_t Value) noexcept {
} // namespace
+std::string MachineActionID::toString() const {
+ std::string Result;
+ Result.reserve(Pass.size() + Rule.size() + 48U);
+ Result.append(Pass);
+ Result.push_back(':');
+ Result.append(Rule);
+ Result.append(":f");
+ Result.append(std::to_string(Function.value()));
+ Result.append(":i");
+ Result.append(std::to_string(Instruction.value()));
+ return Result;
+}
+
+Expected<bool> MachineActionReplay::request(std::string_view Rule, const MachineFunction &Function,
+ MachineInstrStableID Instruction, std::uint64_t Cost) {
+ if (!IsReplaying) {
+ const auto Position = RecordingInstructionIDs.find(Instruction);
+ const MachineInstrStableID CanonicalInstruction =
+ Position == RecordingInstructionIDs.end() ? Instruction : Position->second;
+ auto Accepted = Analyses->requestAction(Rule, Function, CanonicalInstruction, Cost);
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ Decisions.push_back({
+ .Rule = std::string(Rule),
+ .Function = Function.stableID(),
+ .Instruction = CanonicalInstruction,
+ .Cost = Cost,
+ .Accepted = *Accepted,
+ });
+ return *Accepted;
+ }
+
+ if (NextDecision >= Decisions.size()) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "machine action replay requested an unrecorded decision");
+ }
+ const Decision &Recorded = Decisions[NextDecision++];
+ if (Recorded.Rule != Rule || Recorded.Function != Function.stableID() ||
+ Recorded.Instruction != Instruction || Recorded.Cost != Cost) {
+ std::string Message = "machine action replay diverged from transaction preflight: recorded ";
+ Message.append(Recorded.Rule);
+ Message.append(":f");
+ Message.append(std::to_string(Recorded.Function.value()));
+ Message.append(":i");
+ Message.append(std::to_string(Recorded.Instruction.value()));
+ Message.append(" cost=");
+ Message.append(std::to_string(Recorded.Cost));
+ Message.append(", requested ");
+ Message.append(Rule);
+ Message.append(":f");
+ Message.append(std::to_string(Function.stableID().value()));
+ Message.append(":i");
+ Message.append(std::to_string(Instruction.value()));
+ Message.append(" cost=");
+ Message.append(std::to_string(Cost));
+ return makeUnexpected(ErrorCode::InternalError, std::move(Message));
+ }
+ return Recorded.Accepted;
+}
+
+void MachineActionReplay::beginReplay() noexcept {
+ IsReplaying = true;
+ NextDecision = 0;
+}
+
+Expected<void> MachineActionReplay::finishReplay() const {
+ if (!IsReplaying || NextDecision != Decisions.size()) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "machine action replay did not consume every recorded decision");
+ }
+ return {};
+}
+
Expected<void>
MachineAnalysisManager::registerAnalysisImpl(MachineAnalysisRegistration Registration,
ErasedRunner Runner, ErasedInvalidator Invalidator) {
@@ -374,12 +447,25 @@ Expected<void> MachineAnalysisManager::validateParallelRequirements(
Expected<bool> MachineAnalysisManager::requestAction(std::string_view Action,
const MachineFunction &Function,
std::uint64_t Cost) {
- if (Action.empty() || Cost == 0)
+ return requestAction(Action, Function, MachineInstrStableID{}, Cost);
+}
+
+Expected<bool> MachineAnalysisManager::requestAction(std::string_view Rule,
+ const MachineFunction &Function,
+ MachineInstrStableID Instruction,
+ std::uint64_t Cost) {
+ if (Rule.empty() || Cost == 0)
return makeUnexpected(ErrorCode::InvalidArgument,
"machine action requires a name and nonzero cost");
if (Instrumentation == nullptr)
return true;
- return Instrumentation->requestAction(Action, Function, Cost);
+ MachineActionID ID{
+ .Pass = ActivePass.empty() ? "unscoped" : ActivePass,
+ .Rule = std::string(Rule),
+ .Function = Function.stableID(),
+ .Instruction = Instruction,
+ };
+ return Instrumentation->requestAction(ID, Function, Cost);
}
Expected<void> CompilationUnitAnalysisManager::registerAnalysisImpl(
@@ -639,6 +725,7 @@ void MachinePassInstrumentation::setPerFunctionFuel(std::optional<std::uint64_t>
std::scoped_lock Lock(Mutex);
PerFunctionFuel = Fuel;
ConsumedFuel.clear();
+ ActionEvents.clear();
}
void MachinePassInstrumentation::addBeforeUnitCallback(BeforeUnitCallback Callback) {
@@ -879,25 +966,37 @@ void MachinePassInstrumentation::change(std::string_view Name, const MachineFunc
Callback(Name, Function, Changes, FingerprintChanged);
}
-Expected<bool> MachinePassInstrumentation::requestAction(std::string_view Action,
+Expected<bool> MachinePassInstrumentation::requestAction(const MachineActionID &Action,
const MachineFunction &Function,
std::uint64_t Cost) {
+ if (Action.Pass.empty() || Action.Rule.empty() || Action.Function != Function.stableID() ||
+ Cost == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "machine action identity is invalid");
+ }
std::scoped_lock ActionLock(ActionMutex);
ActionFilter CurrentFilter;
{
std::scoped_lock Lock(Mutex);
CurrentFilter = Filter;
}
- if (CurrentFilter && !CurrentFilter(Action, Function, Cost))
+ if (CurrentFilter && !CurrentFilter(Action, Cost)) {
+ std::scoped_lock Lock(Mutex);
+ ActionEvents.push_back({Action, Cost, false});
return false;
+ }
std::scoped_lock Lock(Mutex);
- if (!PerFunctionFuel)
+ if (!PerFunctionFuel) {
+ ActionEvents.push_back({Action, Cost, true});
return true;
+ }
std::uint64_t &Consumed = ConsumedFuel[Function.stableID().value()];
- if (Consumed > *PerFunctionFuel || Cost > *PerFunctionFuel - Consumed)
+ if (Consumed > *PerFunctionFuel || Cost > *PerFunctionFuel - Consumed) {
+ ActionEvents.push_back({Action, Cost, false});
return false;
+ }
Consumed += Cost;
+ ActionEvents.push_back({Action, Cost, true});
return true;
}
@@ -1007,10 +1106,33 @@ std::vector<InstrumentationStatistic> MachinePassInstrumentation::statistics() c
return Result;
}
+std::vector<MachineActionEvent> MachinePassInstrumentation::actionEvents() const {
+ std::vector<MachineActionEvent> Result;
+ {
+ std::scoped_lock Lock(Mutex);
+ Result = ActionEvents;
+ }
+ std::ranges::sort(Result, [](const MachineActionEvent &Left, const MachineActionEvent &Right) {
+ if (Left.ID.Function.value() != Right.ID.Function.value())
+ return Left.ID.Function.value() < Right.ID.Function.value();
+ if (Left.ID.Instruction.value() != Right.ID.Instruction.value())
+ return Left.ID.Instruction.value() < Right.ID.Instruction.value();
+ if (Left.ID.Pass != Right.ID.Pass)
+ return Left.ID.Pass < Right.ID.Pass;
+ if (Left.ID.Rule != Right.ID.Rule)
+ return Left.ID.Rule < Right.ID.Rule;
+ if (Left.Cost != Right.Cost)
+ return Left.Cost < Right.Cost;
+ return Left.Accepted < Right.Accepted;
+ });
+ return Result;
+}
+
void MachinePassInstrumentation::clearStatistics() {
std::scoped_lock Lock(Mutex);
Statistics.clear();
ConsumedFuel.clear();
+ ActionEvents.clear();
}
Expected<void> MachinePassManager::validateContract(const MachinePassContract &Contract) const {
@@ -1087,7 +1209,9 @@ Expected<void> MachinePassManager::run(MachineFunction &Function, const TargetDe
if (Instrumentation != nullptr)
Instrumentation->before(Name, Function);
const Clock::time_point Start = Clock::now();
+ Analyses.ActivePass = std::string(Name);
auto PassResult = Pass->run(Function, Target, Analyses);
+ Analyses.ActivePass.clear();
const std::uint64_t Duration = elapsedNanoseconds(Start);
if (!PassResult) {
Error Failure =
diff --git a/backend/lib/CodeGen/MachinePipelinePasses.cpp b/backend/lib/CodeGen/MachinePipelinePasses.cpp
index 209439c..c7c5dc3 100644
--- a/backend/lib/CodeGen/MachinePipelinePasses.cpp
+++ b/backend/lib/CodeGen/MachinePipelinePasses.cpp
@@ -233,6 +233,36 @@ registerConfigurationFingerprint(const MachineRegisterInfo &RegInfo) noexcept {
return Error(Source.code(), std::move(Prefix));
}
+[[nodiscard]] std::string_view legalizationActionName(LegalizationAction Action) noexcept {
+ switch (Action) {
+ case LegalizationAction::Legal:
+ return "legal";
+ case LegalizationAction::Unsupported:
+ return "unsupported";
+ case LegalizationAction::Custom:
+ return "custom";
+ case LegalizationAction::Lower:
+ return "lower";
+ case LegalizationAction::Libcall:
+ return "libcall";
+ case LegalizationAction::WidenScalar:
+ return "widen-scalar";
+ case LegalizationAction::NarrowScalar:
+ return "narrow-scalar";
+ case LegalizationAction::MoreElements:
+ return "more-elements";
+ case LegalizationAction::FewerElements:
+ return "fewer-elements";
+ case LegalizationAction::Bitcast:
+ return "bitcast";
+ case LegalizationAction::ChangeElementType:
+ return "change-element-type";
+ case LegalizationAction::ChangeAddressSpace:
+ return "change-address-space";
+ }
+ return "unknown";
+}
+
[[nodiscard]] Expected<void> repairTrackedLiveness(MachineFunction &Function,
const TargetDescription &Target,
MachinePassResult &Result) {
@@ -268,21 +298,25 @@ Expected<MachinePassResult> LegalizerPass::run(MachineFunction &Function,
MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt});
if (!Trial)
return std::unexpected(prefixedError(Trial.error(), "legalizer transaction setup failed: "));
- MachineAnalysisManager TrialAnalyses;
- auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = runImplementation(Function, Target, Analyses);
+ Actions.beginReplay();
+ auto Result = runImplementation(Function, Target, Actions);
if (!Result) {
return std::unexpected(prefixedError(
Result.error(), "legalizer diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
Expected<MachinePassResult> LegalizerPass::runImplementation(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineActionReplay &Actions) {
MachinePassResult Result;
std::unordered_set<OpcodeState, OpcodeStateHash> SeenStates;
@@ -328,6 +362,18 @@ Expected<MachinePassResult> LegalizerPass::runImplementation(MachineFunction &Fu
if (!LoweringValidation)
return std::unexpected(
prefixedError(LoweringValidation.error(), "legalizer lowering precondition failed: "));
+ std::string Rule = "legalize.";
+ Rule.append(legalizationActionName(*Action));
+ Rule.push_back('.');
+ Rule.append(Descriptor->Name);
+ auto Accepted = Actions.request(Rule, Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return std::unexpected(
+ passFailure("required legalizer action was rejected for '" + Descriptor->Name + "'",
+ ErrorCode::VerificationError));
+ }
auto Lowered = Info->lower(*Instruction, Function, Target, *Action);
if (!Lowered)
return std::unexpected(Lowered.error());
@@ -521,22 +567,26 @@ Expected<MachinePassResult> InstructionSelectPass::run(MachineFunction &Function
return std::unexpected(
prefixedError(Trial.error(), "instruction-selection transaction setup failed: "));
}
- MachineAnalysisManager TrialAnalyses;
- auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = runImplementation(Function, Target, Analyses);
+ Actions.beginReplay();
+ auto Result = runImplementation(Function, Target, Actions);
if (!Result) {
return std::unexpected(
prefixedError(Result.error(),
"instruction selection diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
Expected<MachinePassResult>
InstructionSelectPass::runImplementation(MachineFunction &Function, const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineActionReplay &Actions) {
MachinePassResult Result;
std::vector<MachineInstr *> Worklist;
@@ -580,6 +630,16 @@ InstructionSelectPass::runImplementation(MachineFunction &Function, const Target
auto Match = Info->canSelect(*Instruction, Function, Target);
if (!Match)
return std::unexpected(Match.error());
+ std::string Rule = "select.";
+ Rule.append(CurrentDescriptor->Name);
+ auto Accepted = Actions.request(Rule, Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return std::unexpected(passFailure("required selection action was rejected for '" +
+ CurrentDescriptor->Name + "'",
+ ErrorCode::VerificationError));
+ }
auto Selection = Info->select(*Instruction, Function, Target);
if (!Selection)
return std::unexpected(Selection.error());
diff --git a/backend/lib/CodeGen/MachinePseudoExpansion.cpp b/backend/lib/CodeGen/MachinePseudoExpansion.cpp
index e654d97..676804a 100644
--- a/backend/lib/CodeGen/MachinePseudoExpansion.cpp
+++ b/backend/lib/CodeGen/MachinePseudoExpansion.cpp
@@ -202,7 +202,7 @@ using BlockSizeSnapshot = std::vector<std::pair<MachineBlockStableID, std::uint6
[[nodiscard]] Expected<MachinePassResult>
prepareImplementation(MachineFunction &Function, const TargetDescription &Target,
- const TargetPseudoExpansionInfo &Info) {
+ const TargetPseudoExpansionInfo &Info, MachineActionReplay &Actions) {
bool Changed = false;
for (MachineInstr *Instruction : Function.allInstructions()) {
if (Instruction == nullptr || Instruction->parent() == nullptr)
@@ -213,6 +213,15 @@ prepareImplementation(MachineFunction &Function, const TargetDescription &Target
"pseudo preparation encountered an unknown opcode");
if (Descriptor->Owner != PseudoOwner::PostRAExpansion)
continue;
+ std::string Rule = "prepare-pseudo.";
+ Rule.append(Descriptor->Name);
+ auto Accepted = Actions.request(Rule, Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return makeUnexpected(ErrorCode::PseudoExpansionFailed,
+ "required pseudo preparation action was rejected");
+ }
auto Prepared = Info.preparePostRAPseudo(*Instruction, Function, Target);
if (!Prepared)
return std::unexpected(Prepared.error());
@@ -232,7 +241,7 @@ prepareImplementation(MachineFunction &Function, const TargetDescription &Target
[[nodiscard]] Expected<MachinePassResult>
expandImplementation(MachineFunction &Function, const TargetDescription &Target,
const TargetPseudoExpansionInfo &Info, MachinePseudoExpansionPhase Phase,
- const TargetBranchInfo *BranchInfo) {
+ const TargetBranchInfo *BranchInfo, MachineActionReplay &Actions) {
const ResourceSnapshot Before = snapshotResources(Function);
std::optional<BlockSizeSnapshot> SizesBefore;
if (Phase == MachinePseudoExpansionPhase::TargetLate) {
@@ -261,6 +270,15 @@ expandImplementation(MachineFunction &Function, const TargetDescription &Target,
if (!Descriptor->Flags.has(InstructionFlag::Pseudo) || Descriptor->Owner != ownerFor(Phase)) {
continue;
}
+ std::string Rule = "expand-pseudo.";
+ Rule.append(Descriptor->Name);
+ auto Accepted = Actions.request(Rule, Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return makeUnexpected(ErrorCode::PseudoExpansionFailed,
+ "required pseudo expansion action was rejected");
+ }
MachineBasicBlock *ExpansionBlock = Instruction->parent();
std::optional<std::uint64_t> ExpansionSizeBefore;
if (Phase == MachinePseudoExpansionPhase::TargetLate) {
@@ -335,40 +353,51 @@ expandImplementation(MachineFunction &Function, const TargetDescription &Target,
[[nodiscard]] Expected<MachinePassResult>
runPreparationTransaction(MachineFunction &Function, const TargetDescription &Target,
- const TargetPseudoExpansionInfo &Info) {
+ const TargetPseudoExpansionInfo &Info, MachineAnalysisManager &Analyses) {
auto Trial = cloneMachineFunction(
Function, Target,
MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt});
if (!Trial)
return std::unexpected(prefixedError(Trial.error(), "pseudo preparation setup failed: "));
- auto TrialResult = prepareImplementation(*Trial->ClonedFunction, Target, Info);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = prepareImplementation(*Trial->ClonedFunction, Target, Info, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = prepareImplementation(Function, Target, Info);
+ Actions.beginReplay();
+ auto Result = prepareImplementation(Function, Target, Info, Actions);
if (!Result) {
return std::unexpected(
prefixedError(Result.error(), "pseudo preparation diverged after successful preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
[[nodiscard]] Expected<MachinePassResult>
runExpansionTransaction(MachineFunction &Function, const TargetDescription &Target,
const TargetPseudoExpansionInfo &Info, MachinePseudoExpansionPhase Phase,
- const TargetBranchInfo *BranchInfo) {
+ const TargetBranchInfo *BranchInfo, MachineAnalysisManager &Analyses) {
auto Trial = cloneMachineFunction(
Function, Target,
MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt});
if (!Trial)
return std::unexpected(prefixedError(Trial.error(), "pseudo expansion setup failed: "));
- auto TrialResult = expandImplementation(*Trial->ClonedFunction, Target, Info, Phase, BranchInfo);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult =
+ expandImplementation(*Trial->ClonedFunction, Target, Info, Phase, BranchInfo, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = expandImplementation(Function, Target, Info, Phase, BranchInfo);
+ Actions.beginReplay();
+ auto Result = expandImplementation(Function, Target, Info, Phase, BranchInfo, Actions);
if (!Result) {
return std::unexpected(
prefixedError(Result.error(), "pseudo expansion diverged after successful preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
@@ -380,11 +409,11 @@ const MachinePassContract &PreparePostRAPseudosPass::contract() const noexcept {
Expected<MachinePassResult> PreparePostRAPseudosPass::run(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineAnalysisManager &Analyses) {
if (!Function.properties().containsAll(contract().Requires))
return makeUnexpected(ErrorCode::VerificationError,
"post-RA pseudo preparation requires selected MachineIR");
- return runPreparationTransaction(Function, Target, *Info);
+ return runPreparationTransaction(Function, Target, *Info, Analyses);
}
const MachinePassContract &PrePEITargetPseudoExpansionPass::contract() const noexcept {
@@ -393,12 +422,12 @@ const MachinePassContract &PrePEITargetPseudoExpansionPass::contract() const noe
Expected<MachinePassResult> PrePEITargetPseudoExpansionPass::run(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineAnalysisManager &Analyses) {
if (!Function.properties().containsAll(contract().Requires))
return makeUnexpected(ErrorCode::VerificationError,
"pre-PEI pseudo expansion requires allocated tracked-liveness MIR");
return runExpansionTransaction(Function, Target, *Info, MachinePseudoExpansionPhase::PrePEI,
- nullptr);
+ nullptr, Analyses);
}
const MachinePassContract &ExpandPostRAPseudosPass::contract() const noexcept {
@@ -407,12 +436,12 @@ const MachinePassContract &ExpandPostRAPseudosPass::contract() const noexcept {
Expected<MachinePassResult> ExpandPostRAPseudosPass::run(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineAnalysisManager &Analyses) {
if (!Function.properties().containsAll(contract().Requires))
return makeUnexpected(ErrorCode::VerificationError,
"post-RA pseudo expansion requires finalized frame and liveness");
return runExpansionTransaction(Function, Target, *Info, MachinePseudoExpansionPhase::PostRA,
- nullptr);
+ nullptr, Analyses);
}
const MachinePassContract &TargetLatePseudoExpansionPass::contract() const noexcept {
@@ -421,12 +450,12 @@ const MachinePassContract &TargetLatePseudoExpansionPass::contract() const noexc
Expected<MachinePassResult> TargetLatePseudoExpansionPass::run(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineAnalysisManager &Analyses) {
if (!Function.properties().containsAll(contract().Requires))
return makeUnexpected(ErrorCode::VerificationError,
"target-late pseudo expansion requires finalized allocated MIR");
return runExpansionTransaction(Function, Target, *Info, MachinePseudoExpansionPhase::TargetLate,
- BranchInfo);
+ BranchInfo, Analyses);
}
} // namespace rcc
diff --git a/backend/lib/CodeGen/MachineSSAExit.cpp b/backend/lib/CodeGen/MachineSSAExit.cpp
index e413b45..f2b0102 100644
--- a/backend/lib/CodeGen/MachineSSAExit.cpp
+++ b/backend/lib/CodeGen/MachineSSAExit.cpp
@@ -805,22 +805,26 @@ Expected<MachinePassResult> PHIEliminationPass::run(MachineFunction &Function,
return std::unexpected(
prefixedError(Trial.error(), "PHI-elimination transaction setup failed: "));
}
- MachineAnalysisManager TrialAnalyses;
- auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = runImplementation(Function, Target, Analyses);
+ Actions.beginReplay();
+ auto Result = runImplementation(Function, Target, Actions);
if (!Result) {
return std::unexpected(prefixedError(
Result.error(), "PHI elimination diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
Expected<MachinePassResult> PHIEliminationPass::runImplementation(MachineFunction &Function,
const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineActionReplay &Actions) {
MachinePassResult Result;
std::unordered_map<EdgeKey, MachineBasicBlock *, EdgeKeyHash> SplitEdges;
const std::vector<MachineBasicBlock *> Blocks(Function.allBlocks().begin(),
@@ -834,6 +838,13 @@ Expected<MachinePassResult> PHIEliminationPass::runImplementation(MachineFunctio
continue;
for (PhiPlan &Plan : *Plans) {
+ auto Accepted = Actions.request("eliminate-phi", Function, Plan.Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "required PHI elimination action was rejected");
+ }
auto Fresh = cloneRegisterConstraints(Function, Plan.Destination.Reg);
if (!Fresh)
return std::unexpected(Fresh.error());
@@ -928,22 +939,26 @@ Expected<MachinePassResult> TwoAddressRewritePass::run(MachineFunction &Function
if (!Trial) {
return std::unexpected(prefixedError(Trial.error(), "two-address transaction setup failed: "));
}
- MachineAnalysisManager TrialAnalyses;
- auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
- auto Result = runImplementation(Function, Target, Analyses);
+ Actions.beginReplay();
+ auto Result = runImplementation(Function, Target, Actions);
if (!Result) {
return std::unexpected(prefixedError(
Result.error(), "two-address rewrite diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
return Result;
}
Expected<MachinePassResult>
TwoAddressRewritePass::runImplementation(MachineFunction &Function, const TargetDescription &Target,
- MachineAnalysisManager &) {
+ MachineActionReplay &Actions) {
MachinePassResult Result;
const std::vector<MachineInstr *> Instructions(Function.allInstructions().begin(),
Function.allInstructions().end());
@@ -951,6 +966,12 @@ TwoAddressRewritePass::runImplementation(MachineFunction &Function, const Target
if (Instruction->parent() == nullptr)
continue;
if (Instruction->opcode() == opcode(GenericOpcode::REG_SEQUENCE)) {
+ auto Accepted = Actions.request("expand-reg-sequence", Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "required REG_SEQUENCE expansion action was rejected");
auto Expanded = expandRegSequence(*Instruction, Function, Target, *Info);
if (!Expanded)
return std::unexpected(Expanded.error());
@@ -959,6 +980,12 @@ TwoAddressRewritePass::runImplementation(MachineFunction &Function, const Target
continue;
}
if (Instruction->opcode() == opcode(GenericOpcode::INSERT_SUBREG)) {
+ auto Accepted = Actions.request("expand-insert-subreg", Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "required INSERT_SUBREG expansion action was rejected");
auto Expanded = expandInsertSubreg(*Instruction, Function, Target, *Info);
if (!Expanded)
return std::unexpected(Expanded.error());
@@ -1004,6 +1031,12 @@ TwoAddressRewritePass::runImplementation(MachineFunction &Function, const Target
continue;
if (isImplicitlyUndef(Function, *Use)) {
+ auto Accepted = Actions.request("rewrite-undef-tie", Function, Instruction->stableID());
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "required undef tie rewrite action was rejected");
auto Rewritten = rewriteUndefTie(*Use, *Def);
if (!Rewritten)
return std::unexpected(Rewritten.error());
@@ -1012,28 +1045,46 @@ TwoAddressRewritePass::runImplementation(MachineFunction &Function, const Target
continue;
}
- auto Commuted = tryCommute(*Instruction, UseIndex, *DefReg, Target, Function);
- if (!Commuted)
- return std::unexpected(Commuted.error());
- if (*Commuted) {
- Result.Changed = true;
- Result.Changes.add(MachinePassChange::Operands);
- continue;
+ auto AllowCommute =
+ Actions.request("commute-tied-operands", Function, Instruction->stableID());
+ if (!AllowCommute)
+ return std::unexpected(AllowCommute.error());
+ if (*AllowCommute) {
+ auto Commuted = tryCommute(*Instruction, UseIndex, *DefReg, Target, Function);
+ if (!Commuted)
+ return std::unexpected(Commuted.error());
+ if (*Commuted) {
+ Result.Changed = true;
+ Result.Changes.add(MachinePassChange::Operands);
+ continue;
+ }
}
- auto Converted =
- Info->tryConvertToThreeAddress(*Instruction, DefIndex, UseIndex, Function, Target);
- if (!Converted)
- return std::unexpected(Converted.error());
- if (*Converted) {
- Result.Changed = true;
- Result.Changes.add(MachinePassChange::Instructions);
- Result.Changes.add(MachinePassChange::Operands);
- Result.Changes.add(MachinePassChange::Registers);
- Restart = true;
- break;
+ auto AllowConversion =
+ Actions.request("convert-three-address", Function, Instruction->stableID());
+ if (!AllowConversion)
+ return std::unexpected(AllowConversion.error());
+ if (*AllowConversion) {
+ auto Converted =
+ Info->tryConvertToThreeAddress(*Instruction, DefIndex, UseIndex, Function, Target);
+ if (!Converted)
+ return std::unexpected(Converted.error());
+ if (*Converted) {
+ Result.Changed = true;
+ Result.Changes.add(MachinePassChange::Instructions);
+ Result.Changes.add(MachinePassChange::Operands);
+ Result.Changes.add(MachinePassChange::Registers);
+ Restart = true;
+ break;
+ }
}
+ auto AllowCopy = Actions.request("insert-tied-copy", Function, Instruction->stableID());
+ if (!AllowCopy)
+ return std::unexpected(AllowCopy.error());
+ if (!*AllowCopy)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "required tied COPY insertion action was rejected");
MachineBasicBlock *Block = Instruction->parent();
if (Block == nullptr)
return makeUnexpected(ErrorCode::InternalError,
diff --git a/backend/lib/CodeGen/MachineScheduler.cpp b/backend/lib/CodeGen/MachineScheduler.cpp
index f861be2..c85489b 100644
--- a/backend/lib/CodeGen/MachineScheduler.cpp
+++ b/backend/lib/CodeGen/MachineScheduler.cpp
@@ -129,7 +129,8 @@ struct ScheduleCandidate final {
scheduleDAG(const ScheduleDAG &DAG, const MachineFunction &Function,
const TargetDescription &Target, const TargetSchedulingModel &Model,
ScheduleDAGMode Mode, const MachineBlockLiveness &Liveness,
- std::uint64_t BlockFrequency, bool EnableBidirectionalScheduling) {
+ std::uint64_t BlockFrequency, bool EnableBidirectionalScheduling,
+ MachineActionReplay *Actions) {
auto TopPressure = RegisterPressureTracker::create(DAG, Function, Target, Model, Mode, Liveness,
RegisterPressureDirection::TopDown);
if (!TopPressure)
@@ -214,6 +215,14 @@ scheduleDAG(const ScheduleDAG &DAG, const MachineFunction &Function,
}
const ScheduleUnit &Selected = DAG.units()[Best->Unit];
+ if (Actions != nullptr) {
+ auto Accepted = Actions->request(Best->Bottom ? "schedule-bottom" : "schedule-top", Function,
+ Selected.StableID);
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ if (!*Accepted)
+ return std::vector<std::size_t>{};
+ }
Scheduled[Best->Unit] = true;
if (Best->Bottom) {
auto Issued = BottomHazards->issue(Selected, Best->Cycle);
@@ -263,7 +272,7 @@ scheduleRegion(MachineBasicBlock &Block, std::span<MachineInstr *const> Instruct
MachineFunction &Function, const TargetDescription &Target,
const TargetSchedulingModel &Model, ScheduleDAGMode Mode,
const MachineBlockLiveness &Liveness, std::uint64_t BlockFrequency,
- MachineSchedulerOptions Options) {
+ MachineSchedulerOptions Options, MachineActionReplay *Actions) {
if (Instructions.size() < 2 || Instructions.size() > Options.MaximumRegionSize)
return false;
auto DAG = ScheduleDAG::build(Block, Instructions, Function, Target, Model, Mode);
@@ -272,9 +281,11 @@ scheduleRegion(MachineBasicBlock &Block, std::span<MachineInstr *const> Instruct
if (DAG->units().size() < 2)
return false;
auto UnitOrder = scheduleDAG(*DAG, Function, Target, Model, Mode, Liveness, BlockFrequency,
- Options.EnableBidirectionalScheduling);
+ Options.EnableBidirectionalScheduling, Actions);
if (!UnitOrder)
return std::unexpected(UnitOrder.error());
+ if (UnitOrder->empty())
+ return false;
std::vector<MachineInstr *> NewOrder;
NewOrder.reserve(Instructions.size());
@@ -305,10 +316,12 @@ scheduleRegion(MachineBasicBlock &Block, std::span<MachineInstr *const> Instruct
} // namespace
-Expected<MachineSchedulingRunResult> runMachineScheduler(
- MachineFunction &Function, const TargetDescription &Target, const TargetSchedulingModel &Model,
- ScheduleDAGMode Mode, const LiveVariablesAnalysis::Result &Liveness,
- const MachineBlockFrequencyAnalysis::Result &Frequencies, MachineSchedulerOptions Options) {
+Expected<MachineSchedulingRunResult>
+runMachineScheduler(MachineFunction &Function, const TargetDescription &Target,
+ const TargetSchedulingModel &Model, ScheduleDAGMode Mode,
+ const LiveVariablesAnalysis::Result &Liveness,
+ const MachineBlockFrequencyAnalysis::Result &Frequencies,
+ MachineSchedulerOptions Options, MachineActionReplay *Actions) {
if (Options.MaximumRegionSize == 0) {
return makeUnexpected(ErrorCode::InvalidArgument,
"machine scheduler region budget must be nonzero");
@@ -324,7 +337,7 @@ Expected<MachineSchedulingRunResult> runMachineScheduler(
std::vector<MachineInstr *> Region;
auto FlushRegion = [&]() -> Expected<void> {
auto Changed = scheduleRegion(*Block, Region, Function, Target, Model, Mode, *BlockLiveness,
- Frequencies.frequency(*Block), Options);
+ Frequencies.frequency(*Block), Options, Actions);
if (!Changed)
return std::unexpected(Changed.error());
BlockChanged |= *Changed;
@@ -403,9 +416,10 @@ Expected<MachinePassResult> MachineSchedulerPass::run(MachineFunction &Function,
TrialAnalyses.getResult<MachineBlockFrequencyAnalysis>(*Trial->ClonedFunction, Target);
if (!TrialFrequencies)
return std::unexpected(TrialFrequencies.error());
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
auto TrialResult = runMachineScheduler(*Trial->ClonedFunction, Target, *Model,
ScheduleDAGMode::PreRegisterAllocation, **TrialLiveness,
- **TrialFrequencies, Options);
+ **TrialFrequencies, Options, &Actions);
if (!TrialResult)
return std::unexpected(TrialResult.error());
@@ -415,12 +429,16 @@ Expected<MachinePassResult> MachineSchedulerPass::run(MachineFunction &Function,
auto Frequencies = Analyses.getResult<MachineBlockFrequencyAnalysis>(Function, Target);
if (!Frequencies)
return std::unexpected(Frequencies.error());
+ Actions.beginReplay();
auto Run = runMachineScheduler(Function, Target, *Model, ScheduleDAGMode::PreRegisterAllocation,
- **Liveness, **Frequencies, Options);
+ **Liveness, **Frequencies, Options, &Actions);
if (!Run) {
return std::unexpected(prefixedError(
Run.error(), "machine scheduler diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
MachinePassResult Result;
Result.Changed = Run->Changed;
diff --git a/backend/lib/CodeGen/MachineSchedulerInternal.h b/backend/lib/CodeGen/MachineSchedulerInternal.h
index a612548..92f8eea 100644
--- a/backend/lib/CodeGen/MachineSchedulerInternal.h
+++ b/backend/lib/CodeGen/MachineSchedulerInternal.h
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
+// The private header has no stable include-root-relative guard name.
+// NOLINTNEXTLINE(llvm-header-guard)
#ifndef RCC_LIB_CODEGEN_MACHINESCHEDULERINTERNAL_H
#define RCC_LIB_CODEGEN_MACHINESCHEDULERINTERNAL_H
@@ -14,6 +16,7 @@
namespace rcc {
class MachineBasicBlock;
+class MachineActionReplay;
class MachineFunction;
class TargetDescription;
class TargetSchedulingModel;
@@ -22,10 +25,12 @@ struct MachineSchedulingRunResult final {
std::vector<MachineBasicBlock *> ChangedBlocks;
};
-[[nodiscard]] Expected<MachineSchedulingRunResult> runMachineScheduler(
- MachineFunction &Function, const TargetDescription &Target, const TargetSchedulingModel &Model,
- ScheduleDAGMode Mode, const LiveVariablesAnalysis::Result &Liveness,
- const MachineBlockFrequencyAnalysis::Result &Frequencies, MachineSchedulerOptions Options);
+[[nodiscard]] Expected<MachineSchedulingRunResult>
+runMachineScheduler(MachineFunction &Function, const TargetDescription &Target,
+ const TargetSchedulingModel &Model, ScheduleDAGMode Mode,
+ const LiveVariablesAnalysis::Result &Liveness,
+ const MachineBlockFrequencyAnalysis::Result &Frequencies,
+ MachineSchedulerOptions Options, MachineActionReplay *Actions = nullptr);
} // namespace rcc
diff --git a/backend/lib/CodeGen/MachineSink.cpp b/backend/lib/CodeGen/MachineSink.cpp
index 9e73cea..873de67 100644
--- a/backend/lib/CodeGen/MachineSink.cpp
+++ b/backend/lib/CodeGen/MachineSink.cpp
@@ -123,8 +123,8 @@ namespace {
auto Info = Function.registerInfo().virtualRegisterInfo(*Reg);
if (!Info)
return std::unexpected(Info.error());
- if (Info->get().Type && Info->get().Type->isFloat() &&
- !Instruction.flags().has(MachineInstrFlag::NoFPExcept)) {
+ const auto &Type = Info->get().Type;
+ if (Type && Type->isFloat() && !Instruction.flags().has(MachineInstrFlag::NoFPExcept)) {
return false;
}
}
diff --git a/backend/lib/CodeGen/MachineStackSlotColoring.cpp b/backend/lib/CodeGen/MachineStackSlotColoring.cpp
index b3b9af5..e60a7a2 100644
--- a/backend/lib/CodeGen/MachineStackSlotColoring.cpp
+++ b/backend/lib/CodeGen/MachineStackSlotColoring.cpp
@@ -178,7 +178,7 @@ collectSlotUses(MachineFunction &Function) {
[[nodiscard]] bool intervalsOverlap(const SlotInterval &Left, const SlotInterval &Right) noexcept {
if (Left.Block != Right.Block)
return true;
- return !(Left.Last < Right.First || Right.Last < Left.First);
+ return Left.Last >= Right.First && Right.Last >= Left.First;
}
struct StackColor final {
diff --git a/backend/lib/CodeGen/MachineTailDuplication.cpp b/backend/lib/CodeGen/MachineTailDuplication.cpp
index a130680..40d448f 100644
--- a/backend/lib/CodeGen/MachineTailDuplication.cpp
+++ b/backend/lib/CodeGen/MachineTailDuplication.cpp
@@ -286,7 +286,8 @@ MachineTailDuplicationPass::runImplementation(MachineFunction &Function,
Tail = Next;
continue;
}
- const std::size_t Cost = (*Candidate)->Body.size() + 1U;
+ const auto &TailCandidate = Candidate->value();
+ const std::size_t Cost = TailCandidate.Body.size() + 1U;
const std::vector<MachineBasicBlock *> Predecessors(Tail->predecessors().begin(),
Tail->predecessors().end());
for (MachineBasicBlock *Predecessor : Predecessors) {
@@ -296,7 +297,7 @@ MachineTailDuplicationPass::runImplementation(MachineFunction &Function,
break;
}
auto Duplicated =
- duplicateTailIntoPredecessor(**Candidate, *Tail, *Predecessor, Function, Target, *Info);
+ duplicateTailIntoPredecessor(TailCandidate, *Tail, *Predecessor, Function, Target, *Info);
if (!Duplicated)
return std::unexpected(Duplicated.error());
if (!*Duplicated)
diff --git a/backend/lib/CodeGen/PostRAScheduler.cpp b/backend/lib/CodeGen/PostRAScheduler.cpp
index 9aff5ac..026f763 100644
--- a/backend/lib/CodeGen/PostRAScheduler.cpp
+++ b/backend/lib/CodeGen/PostRAScheduler.cpp
@@ -287,9 +287,10 @@ Expected<MachinePassResult> PostRASchedulerPass::run(MachineFunction &Function,
TrialAnalyses.getResult<MachineBlockFrequencyAnalysis>(*Trial->ClonedFunction, Target);
if (!TrialFrequencies)
return std::unexpected(TrialFrequencies.error());
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
auto TrialRun = runMachineScheduler(*Trial->ClonedFunction, Target, *Model,
ScheduleDAGMode::PostRegisterAllocation, **TrialLiveness,
- **TrialFrequencies, Options);
+ **TrialFrequencies, Options, &Actions);
if (!TrialRun)
return std::unexpected(TrialRun.error());
auto TrialRepair =
@@ -303,12 +304,16 @@ Expected<MachinePassResult> PostRASchedulerPass::run(MachineFunction &Function,
auto Frequencies = Analyses.getResult<MachineBlockFrequencyAnalysis>(Function, Target);
if (!Frequencies)
return std::unexpected(Frequencies.error());
+ Actions.beginReplay();
auto Run = runMachineScheduler(Function, Target, *Model, ScheduleDAGMode::PostRegisterAllocation,
- **Liveness, **Frequencies, Options);
+ **Liveness, **Frequencies, Options, &Actions);
if (!Run) {
return std::unexpected(prefixedError(
Run.error(), "post-RA scheduler diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
auto Repaired = repairPostRAMachineBlockLiveness(Run->ChangedBlocks, Target, **Liveness);
if (!Repaired)
return std::unexpected(Repaired.error());
diff --git a/backend/lib/CodeGen/RegAllocBase.cpp b/backend/lib/CodeGen/RegAllocBase.cpp
index 1aae22d..5d065dd 100644
--- a/backend/lib/CodeGen/RegAllocBase.cpp
+++ b/backend/lib/CodeGen/RegAllocBase.cpp
@@ -63,7 +63,8 @@ Expected<regalloc_detail::AllocationRunResult> RegAllocBase::run() {
std::ranges::sort(Round->Spilled, [](Register Left, Register Right) {
return Left.opaqueValue() < Right.opaqueValue();
});
- Spiller ValueSpiller(Function, Target, TargetInfo, Mapping, Options.EnableRematerialization);
+ Spiller ValueSpiller(Function, Target, TargetInfo, Mapping, Options.EnableRematerialization,
+ Actions);
for (const Register VirtualReg : Round->Spilled) {
VirtRegMapEntry *Entry = Mapping.entry(VirtualReg);
if (Entry != nullptr) {
diff --git a/backend/lib/CodeGen/RegAllocBase.h b/backend/lib/CodeGen/RegAllocBase.h
index 5af5ed0..a1682b2 100644
--- a/backend/lib/CodeGen/RegAllocBase.h
+++ b/backend/lib/CodeGen/RegAllocBase.h
@@ -28,9 +28,9 @@ public:
protected:
RegAllocBase(MachineFunction &Function, const TargetDescription &Target,
const TargetRegisterAllocationInfo &TargetInfo, MachineAnalysisManager &Analyses,
- RegisterAllocationOptions Options) noexcept
+ RegisterAllocationOptions Options, MachineActionReplay *Actions = nullptr) noexcept
: Function(Function), Target(Target), TargetInfo(TargetInfo), Analyses(Analyses),
- Options(Options) {}
+ Options(Options), Actions(Actions) {}
[[nodiscard]] virtual Expected<AllocationRoundResult>
allocateRound(const LiveIntervalsAnalysis::Result &Intervals, VirtRegMap &Mapping) = 0;
@@ -40,6 +40,7 @@ protected:
const TargetRegisterAllocationInfo &TargetInfo;
MachineAnalysisManager &Analyses;
RegisterAllocationOptions Options;
+ MachineActionReplay *Actions = nullptr;
};
[[nodiscard]] std::vector<const LiveInterval *>
diff --git a/backend/lib/CodeGen/RegAllocPBQP.cpp b/backend/lib/CodeGen/RegAllocPBQP.cpp
index a5a12f7..f1da2c1 100644
--- a/backend/lib/CodeGen/RegAllocPBQP.cpp
+++ b/backend/lib/CodeGen/RegAllocPBQP.cpp
@@ -15,9 +15,10 @@
#include <algorithm>
#include <cstdint>
#include <limits>
+#include <map>
#include <optional>
#include <ranges>
-#include <set>
+#include <string>
#include <utility>
#include <vector>
@@ -30,9 +31,9 @@ struct PBQPAllocationNode final {
std::vector<std::optional<PhysicalRegister>> Options;
};
-[[nodiscard]] std::set<std::pair<std::uint32_t, std::uint32_t>>
+[[nodiscard]] std::map<std::pair<std::uint32_t, std::uint32_t>, MachineInstrStableID>
copyRelatedRegisters(const MachineFunction &Function) {
- std::set<std::pair<std::uint32_t, std::uint32_t>> Result;
+ std::map<std::pair<std::uint32_t, std::uint32_t>, MachineInstrStableID> Result;
for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
Block = Block->next()) {
for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;
@@ -48,12 +49,33 @@ copyRelatedRegisters(const MachineFunction &Function) {
std::uint32_t Right = Source->opaqueValue();
if (Right < Left)
std::swap(Left, Right);
- Result.emplace(Left, Right);
+ Result.try_emplace({Left, Right}, Instruction->stableID());
}
}
return Result;
}
+[[nodiscard]] std::string_view reductionName(pbqp::ReductionKind Kind) noexcept {
+ switch (Kind) {
+ case pbqp::ReductionKind::DegreeZero:
+ return "degree-zero";
+ case pbqp::ReductionKind::DegreeOne:
+ return "degree-one";
+ case pbqp::ReductionKind::DegreeTwo:
+ return "degree-two";
+ case pbqp::ReductionKind::Heuristic:
+ return "heuristic";
+ }
+ return "unknown";
+}
+
+[[nodiscard]] MachineInstrStableID actionInstructionForRegister(const MachineFunction &Function,
+ Register Reg) noexcept {
+ const MachineOperand *Definition = Function.registerInfo().getUniqueVRegDef(Reg);
+ const MachineInstr *Instruction = Definition == nullptr ? nullptr : Definition->parent();
+ return Instruction == nullptr ? MachineInstrStableID{} : Instruction->stableID();
+}
+
[[nodiscard]] pbqp::Cost boundedCost(std::uint64_t Value) noexcept {
const std::uint64_t Limit = static_cast<std::uint64_t>(pbqp::InfiniteCost - 1);
return static_cast<pbqp::Cost>(std::min(Value, Limit));
@@ -63,8 +85,9 @@ class PBQPAllocatorEngine final : public RegAllocBase {
public:
PBQPAllocatorEngine(MachineFunction &Function, const TargetDescription &Target,
const TargetRegisterAllocationInfo &TargetInfo,
- MachineAnalysisManager &Analyses, RegisterAllocationOptions Options)
- : RegAllocBase(Function, Target, TargetInfo, Analyses, Options) {}
+ MachineAnalysisManager &Analyses, RegisterAllocationOptions Options,
+ MachineActionReplay *Actions)
+ : RegAllocBase(Function, Target, TargetInfo, Analyses, Options, Actions) {}
private:
[[nodiscard]] Expected<AllocationRoundResult>
@@ -152,8 +175,14 @@ private:
std::uint32_t RightKey = Right.VirtualReg.opaqueValue();
if (RightKey < LeftKey)
std::swap(LeftKey, RightKey);
- const bool CopyRelated =
- Options.EnableCopyCoalescing && CopyPairs.contains({LeftKey, RightKey});
+ const auto CopyPosition = CopyPairs.find({LeftKey, RightKey});
+ bool CopyRelated = Options.EnableCopyCoalescing && CopyPosition != CopyPairs.end();
+ if (CopyRelated && Actions != nullptr) {
+ auto Accepted = Actions->request("coalesce-copy", Function, CopyPosition->second);
+ if (!Accepted)
+ return std::unexpected(Accepted.error());
+ CopyRelated = *Accepted;
+ }
pbqp::CostMatrix Costs(Left.Options.size(), Right.Options.size(), 0);
bool HasCost = false;
for (std::size_t LeftOption = 1; LeftOption < Left.Options.size(); ++LeftOption) {
@@ -188,7 +217,23 @@ private:
}
pbqp::DeterministicSolver Solver;
- auto Solution = Solver.solve(Problem);
+ auto Solution = Solver.solve(Problem, [&](const pbqp::SolverAction &Action) -> Expected<bool> {
+ if (Actions == nullptr)
+ return true;
+ if (Action.Node.value() >= Nodes.size()) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "PBQP action names an unknown allocation node");
+ }
+ const Register VirtualReg = Nodes[Action.Node.value()].VirtualReg;
+ std::string Rule =
+ Action.Kind == pbqp::SolverActionKind::Reduction ? "pbqp-reduce." : "pbqp-choice.";
+ Rule.append(reductionName(Action.Reduction));
+ if (Action.Kind == pbqp::SolverActionKind::Choice) {
+ Rule.append(".option-");
+ Rule.append(std::to_string(Action.Value));
+ }
+ return Actions->request(Rule, Function, actionInstructionForRegister(Function, VirtualReg));
+ });
if (!Solution)
return std::unexpected(Solution.error());
@@ -267,7 +312,9 @@ Expected<MachinePassResult> PBQPAllocatorPass::run(MachineFunction &Function,
auto Registered = registerStage11MachineAnalyses(TrialAnalyses);
if (!Registered)
return std::unexpected(Registered.error());
- PBQPAllocatorEngine TrialAllocator(*Trial->ClonedFunction, Target, *Info, TrialAnalyses, Options);
+ MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs);
+ PBQPAllocatorEngine TrialAllocator(*Trial->ClonedFunction, Target, *Info, TrialAnalyses, Options,
+ &Actions);
auto TrialRun = TrialAllocator.run();
if (!TrialRun)
return std::unexpected(TrialRun.error());
@@ -275,12 +322,16 @@ Expected<MachinePassResult> PBQPAllocatorPass::run(MachineFunction &Function,
regalloc_detail::verifyAllocationTrial(*Trial->ClonedFunction, Target, contract());
if (!TrialVerification)
return std::unexpected(TrialVerification.error());
- PBQPAllocatorEngine Allocator(Function, Target, *Info, Analyses, Options);
+ Actions.beginReplay();
+ PBQPAllocatorEngine Allocator(Function, Target, *Info, Analyses, Options, &Actions);
auto Allocated = Allocator.run();
if (!Allocated) {
return std::unexpected(regalloc_detail::withPrefix(
Allocated.error(), "PBQPAllocator diverged after a successful transaction preflight: "));
}
+ auto Replayed = Actions.finishReplay();
+ if (!Replayed)
+ return std::unexpected(Replayed.error());
auto Reserved = regalloc_detail::reserveFunctionRegisters(Function, FunctionReserved);
if (!Reserved)
return std::unexpected(Reserved.error());
diff --git a/backend/lib/CodeGen/RegisterPressure.cpp b/backend/lib/CodeGen/RegisterPressure.cpp
index 523a443..cbd8d8e 100644
--- a/backend/lib/CodeGen/RegisterPressure.cpp
+++ b/backend/lib/CodeGen/RegisterPressure.cpp
@@ -102,12 +102,12 @@ struct RegisterPressureBuilder final {
auto Info = Function->registerInfo().virtualRegisterInfo(Reg);
if (!Info)
return std::unexpected(Info.error());
- if (!Info->get().RegClass) {
+ const auto &RegClass = Info->get().RegClass;
+ if (!RegClass) {
return makeUnexpected(ErrorCode::VerificationError,
"pre-RA pressure tracking requires constrained virtual registers");
}
- const std::span<const RegisterPressureSetID> Sets =
- Model->pressureSetsForClass(*Info->get().RegClass);
+ const std::span<const RegisterPressureSetID> Sets = Model->pressureSetsForClass(*RegClass);
return std::vector<RegisterPressureSetID>{Sets.begin(), Sets.end()};
}
diff --git a/backend/lib/MIR/CMakeLists.txt b/backend/lib/MIR/CMakeLists.txt
index 2ef63ed..f227390 100644
--- a/backend/lib/MIR/CMakeLists.txt
+++ b/backend/lib/MIR/CMakeLists.txt
@@ -6,8 +6,16 @@ set(
LLVMConstantSyntax.cpp
MIDebugSyntax.cpp
MILexer.cpp
+ MIRDataParser.cpp
MIRDocument.cpp
+ MIRDocumentParser.cpp
+ MIRDocumentReferenceValidation.cpp
MIRDocumentPrinter.cpp
+ MIRFrameParser.cpp
+ MIRFunctionParser.cpp
+ MIRFunctionRecordsParser.cpp
+ MIRMetadataParser.cpp
+ MIRMetadataTextParser.cpp
MIParser.cpp
MIPrinter.cpp
)
@@ -55,9 +63,20 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntax.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIDebugSyntax.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MILexer.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDataParser.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIRDocument.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentParser.h"
"${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentReferenceValidation.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentReferenceValidation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentPrinter.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRFrameParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRFunctionParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRFunctionRecordsParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataTextParser.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataTextParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRParserUtilities.h"
"${CMAKE_CURRENT_SOURCE_DIR}/MIParser.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIPrinter.cpp"
${RCCBackendMIRHeaders}
@@ -67,8 +86,16 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntax.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIDebugSyntax.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MILexer.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDataParser.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIRDocument.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentReferenceValidation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentPrinter.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRFrameParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRFunctionParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRFunctionRecordsParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataParser.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataTextParser.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIParser.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MIPrinter.cpp"
)
diff --git a/backend/lib/MIR/MIParser.cpp b/backend/lib/MIR/MIParser.cpp
index ecf4986..483c2f2 100644
--- a/backend/lib/MIR/MIParser.cpp
+++ b/backend/lib/MIR/MIParser.cpp
@@ -1098,14 +1098,21 @@ bool MIParser::Impl::parseRegisterOperand(bool IsDef, ParsedOperand &Result) {
} else if (at(MITokenKind::Identifier)) {
if (const RegisterClassDesc *Class =
Target.registers().registerClass(token().stringValue())) {
- if ((Info.RegClass && *Info.RegClass != Class->ID) || Info.Bank)
+ if ((Info.RegClass && *Info.RegClass != Class->ID) ||
+ (Info.Bank && *Info.Bank != Class->DefaultBank))
return fail("conflicting virtual register class");
auto SetClass = Function.registerInfo().setRegisterClass(Reg, Class->ID);
if (!SetClass)
return importError(token(), SetClass.error());
+ auto SetBank = Function.registerInfo().setRegisterBank(Reg, Class->DefaultBank);
+ if (!SetBank)
+ return importError(token(), SetBank.error());
} else if (const RegisterBankDesc *Bank =
Target.registers().registerBank(token().stringValue())) {
- if ((Info.Bank && *Info.Bank != Bank->ID) || Info.RegClass)
+ const RegisterClassDesc *ExistingClass =
+ Info.RegClass ? Target.registers().registerClass(*Info.RegClass) : nullptr;
+ if ((Info.Bank && *Info.Bank != Bank->ID) ||
+ (Info.RegClass && (ExistingClass == nullptr || ExistingClass->DefaultBank != Bank->ID)))
return fail("conflicting virtual register bank");
auto SetBank = Function.registerInfo().setRegisterBank(Reg, Bank->ID);
if (!SetBank)
diff --git a/backend/lib/MIR/MIRDataParser.cpp b/backend/lib/MIR/MIRDataParser.cpp
new file mode 100644
index 0000000..fc70f0f
--- /dev/null
+++ b/backend/lib/MIR/MIRDataParser.cpp
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "MIRDocumentParser.h"
+#include "MIRDocumentReferenceValidation.h"
+#include "MIRMetadataTextParser.h"
+#include "MIRParserUtilities.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionAttributes.h"
+#include "rcc/CodeGen/MachineFunctionProperties.h"
+#include "rcc/CodeGen/MachineFunctionRecords.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/MIR/LLVMConstantSyntax.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::mir {
+
+using parser_detail::parseBool;
+using parser_detail::parsePrefixedID;
+using parser_detail::parseSigned;
+using parser_detail::parseUnsigned;
+using parser_detail::parseUnsigned32;
+using parser_detail::trim;
+
+bool MIRDocumentParser::parseConstants(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context) {
+ const yaml::Node *Constants = mappingValue(Root, "constants");
+ if (Constants == nullptr)
+ return false;
+ if (requireKind(*Constants, yaml::NodeKind::Sequence, "constant pool"))
+ return true;
+ constexpr std::array Keys{
+ std::string_view{"id"},
+ std::string_view{"value"},
+ std::string_view{"alignment"},
+ std::string_view{"isTargetSpecific"},
+ };
+ std::vector<LLVMNamedTypeDefinition> NamedTypes;
+ const std::string_view DataLayout = effectiveDataLayout();
+ if (ActiveDocument->EmbeddedIR) {
+ const auto Definitions = ActiveDocument->EmbeddedIR->typeDefinitions();
+ NamedTypes.reserve(Definitions.size());
+ for (const EmbeddedIRTypeDefinition &Definition : Definitions) {
+ NamedTypes.push_back({.Name = Definition.Name,
+ .Definition = Definition.Definition,
+ .NumericName = Definition.NumericName});
+ }
+ }
+ std::unordered_set<std::uint32_t> IDs;
+ for (const yaml::NodeID ConstantID : Constants->sequence()) {
+ const yaml::Node *Constant = node(ConstantID);
+ if (Constant == nullptr ||
+ requireKind(*Constant, yaml::NodeKind::Mapping, "constant pool entry") ||
+ validateKeys(*Constant, Keys, "constant pool entry")) {
+ return true;
+ }
+ const yaml::Node *IDNode = mappingValue(*Constant, "id");
+ const yaml::Node *ValueNode = mappingValue(*Constant, "value");
+ if (IDNode == nullptr || ValueNode == nullptr)
+ return fail(Constant->range(), "constant pool entry requires 'id' and 'value'");
+ std::uint64_t WideID = 0;
+ if (unsignedValue(*IDNode, WideID, "a 32-bit constant pool ID") ||
+ WideID > std::numeric_limits<std::uint32_t>::max()) {
+ return true;
+ }
+ const auto TextID = static_cast<std::uint32_t>(WideID);
+ if (!IDs.insert(TextID).second)
+ return fail(IDNode->range(), "duplicate constant pool ID");
+
+ bool IsTargetSpecific = false;
+ if (const yaml::Node *TargetNode = mappingValue(*Constant, "isTargetSpecific")) {
+ if (boolean(*TargetNode, IsTargetSpecific))
+ return true;
+ }
+ if (IsTargetSpecific) {
+ std::string_view ValueText;
+ if (scalar(*ValueNode, ValueText, "target constant"))
+ return true;
+ auto TargetConstantValue = Target.mirText().parseTargetConstant(ValueText);
+ if (!TargetConstantValue)
+ return importError(ValueNode->range(), TargetConstantValue.error(), "mir-target-constant");
+ auto Emission = TargetConstantValue->emit();
+ if (!Emission)
+ return importError(ValueNode->range(), Emission.error(), "mir-target-constant");
+ auto RequiredAlignment = TargetConstantValue->requiredAlignment();
+ if (!RequiredAlignment)
+ return importError(ValueNode->range(), RequiredAlignment.error(), "mir-target-constant");
+
+ MachineConstantPoolEntryDesc Description;
+ Description.Size = Emission->Bytes.size();
+ Description.Align = *RequiredAlignment;
+ Description.Value = std::move(*TargetConstantValue);
+ if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) {
+ if (alignment(*AlignmentNode, Description.Align, true))
+ return true;
+ }
+ auto Added = Function.constantPool().addEntry(std::move(Description));
+ if (!Added)
+ return importError(Constant->range(), Added.error());
+ Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added});
+ continue;
+ }
+
+ std::string_view ValueText;
+ if (scalar(*ValueNode, ValueText, "LLVM constant"))
+ return true;
+ const LLVMConstantSyntaxOptions ConstantOptions{
+ .DefaultPointerSizeInBits = Context.DefaultPointerSizeInBits,
+ .DataLayout = DataLayout,
+ .NamedTypes = NamedTypes,
+ };
+ auto Data = parseLLVMConstant(trim(ValueText), ConstantOptions);
+ if (!Data)
+ return importError(ValueNode->range(), Data.error(), "mir-llvm-constant");
+ auto Size = llvmConstantStorageSize(Data->Type, ConstantOptions);
+ if (!Size)
+ return importError(ValueNode->range(), Size.error(), "mir-llvm-constant");
+
+ MachineConstantPoolEntryDesc Description;
+ Description.Size = *Size;
+ Description.Value = std::move(*Data);
+ if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) {
+ if (alignment(*AlignmentNode, Description.Align, true))
+ return true;
+ } else {
+ auto Preferred = llvmConstantPreferredAlignment(
+ std::get<GenericConstantData>(Description.Value).Type, ConstantOptions);
+ if (!Preferred)
+ return importError(ValueNode->range(), Preferred.error(), "mir-llvm-constant");
+ Description.Align = *Preferred;
+ }
+ auto Added = Function.constantPool().addEntry(std::move(Description));
+ if (!Added)
+ return importError(Constant->range(), Added.error());
+ Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added});
+ }
+ return false;
+}
+
+bool MIRDocumentParser::reserveJumpTables(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context,
+ std::vector<PendingJumpTable> &Pending) {
+ const yaml::Node *JumpTable = mappingValue(Root, "jumpTable");
+ if (JumpTable == nullptr)
+ return false;
+ if (requireKind(*JumpTable, yaml::NodeKind::Mapping, "jump table"))
+ return true;
+ constexpr std::array JumpTableKeys{
+ std::string_view{"kind"},
+ std::string_view{"entries"},
+ };
+ if (validateKeys(*JumpTable, JumpTableKeys, "jump table"))
+ return true;
+ const yaml::Node *KindNode = mappingValue(*JumpTable, "kind");
+ if (KindNode == nullptr)
+ return fail(JumpTable->range(), "jump table requires 'kind'");
+ std::string_view KindName;
+ if (scalar(*KindNode, KindName, "jump table kind"))
+ return true;
+
+ JumpTableEntryEncoding Encoding;
+ std::uint8_t EntrySize = 8;
+ if (KindName == "block-address")
+ Encoding = JumpTableEntryEncoding::BlockAddress;
+ else if (KindName == "gp-rel64-block-address")
+ Encoding = JumpTableEntryEncoding::GPRelative64BlockAddress;
+ else if (KindName == "gp-rel32-block-address") {
+ Encoding = JumpTableEntryEncoding::GPRelative32BlockAddress;
+ EntrySize = 4;
+ } else if (KindName == "label-difference32") {
+ Encoding = JumpTableEntryEncoding::LabelDifference32;
+ EntrySize = 4;
+ } else if (KindName == "label-difference64")
+ Encoding = JumpTableEntryEncoding::LabelDifference64;
+ else if (KindName == "inline") {
+ Encoding = JumpTableEntryEncoding::Inline;
+ EntrySize = 4;
+ } else if (KindName == "custom32") {
+ Encoding = JumpTableEntryEncoding::Custom32;
+ EntrySize = 4;
+ } else {
+ return fail(KindNode->range(), "unknown jump table kind '" + std::string(KindName) + "'");
+ }
+
+ const yaml::Node *Entries = mappingValue(*JumpTable, "entries");
+ if (Entries == nullptr)
+ return false;
+ if (requireKind(*Entries, yaml::NodeKind::Sequence, "jump table entries"))
+ return true;
+ constexpr std::array EntryKeys{
+ std::string_view{"id"},
+ std::string_view{"blocks"},
+ };
+ std::unordered_set<std::uint32_t> IDs;
+ for (const yaml::NodeID EntryID : Entries->sequence()) {
+ const yaml::Node *Entry = node(EntryID);
+ if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "jump table entry") ||
+ validateKeys(*Entry, EntryKeys, "jump table entry")) {
+ return true;
+ }
+ const yaml::Node *IDNode = mappingValue(*Entry, "id");
+ if (IDNode == nullptr)
+ return fail(Entry->range(), "jump table entry requires 'id'");
+ std::uint64_t WideID = 0;
+ if (unsignedValue(*IDNode, WideID, "a 32-bit jump table ID") ||
+ WideID > std::numeric_limits<std::uint32_t>::max()) {
+ return true;
+ }
+ const auto TextID = static_cast<std::uint32_t>(WideID);
+ if (!IDs.insert(TextID).second)
+ return fail(IDNode->range(), "duplicate jump table ID");
+ auto Reserved = Function.jumpTableInfo().reserveTable();
+ if (!Reserved)
+ return importError(Entry->range(), Reserved.error());
+ PendingJumpTable NewPending{
+ .Index = *Reserved,
+ .TextID = TextID,
+ .Encoding = Encoding,
+ .EntrySize = EntrySize,
+ .Blocks = {},
+ };
+ if (const yaml::Node *Blocks = mappingValue(*Entry, "blocks")) {
+ if (requireKind(*Blocks, yaml::NodeKind::Sequence, "jump table blocks"))
+ return true;
+ for (const yaml::NodeID BlockID : Blocks->sequence()) {
+ const yaml::Node *BlockNode = node(BlockID);
+ std::string_view Text;
+ if (BlockNode == nullptr || scalar(*BlockNode, Text, "basic block reference"))
+ return true;
+ NewPending.Blocks.emplace_back(Text);
+ }
+ }
+ if (NewPending.Blocks.empty())
+ return fail(Entry->range(), "jump table entry must contain at least one block");
+ Context.JumpTables.push_back({.TextID = TextID, .Index = *Reserved});
+ Pending.push_back(std::move(NewPending));
+ }
+ return false;
+}
+
+bool MIRDocumentParser::defineJumpTables(MachineFunction &Function,
+ std::span<const PendingJumpTable> Pending) {
+ for (const PendingJumpTable &Table : Pending) {
+ MachineJumpTableDesc Description;
+ Description.Encoding = Table.Encoding;
+ Description.EntrySize = Table.EntrySize;
+ Description.Align = *Alignment::create(Table.EntrySize);
+ for (const std::string &Text : Table.Blocks) {
+ MachineBasicBlock *Block = nullptr;
+ if (parseBlockReference(Text, Result.Source.range(0, 0), Function, Block))
+ return true;
+ if (Block == nullptr)
+ return fail(Result.Source.range(0, 0), "reference to undefined machine basic block");
+ Description.Targets.push_back(Block);
+ }
+ auto DefineResult =
+ Function.jumpTableInfo().defineReservedTable(Table.Index, std::move(Description));
+ if (!DefineResult)
+ return importError(Result.Source.range(0, 0), DefineResult.error());
+ }
+ return false;
+}
+
+} // namespace rcc::mir
diff --git a/backend/lib/MIR/MIRDocument.cpp b/backend/lib/MIR/MIRDocument.cpp
index 67e6ffb..ad9ec40 100644
--- a/backend/lib/MIR/MIRDocument.cpp
+++ b/backend/lib/MIR/MIRDocument.cpp
@@ -2,2755 +2,12 @@
#include "rcc/MIR/MIRDocument.h"
-#include "MIRDocumentReferenceValidation.h"
-#include "rcc/CodeGen/MachineBasicBlock.h"
-#include "rcc/CodeGen/MachineConstantPool.h"
-#include "rcc/CodeGen/MachineFrameInfo.h"
-#include "rcc/CodeGen/MachineFunction.h"
-#include "rcc/CodeGen/MachineFunctionAttributes.h"
-#include "rcc/CodeGen/MachineFunctionProperties.h"
-#include "rcc/CodeGen/MachineFunctionRecords.h"
-#include "rcc/CodeGen/MachineInstr.h"
-#include "rcc/CodeGen/MachineJumpTableInfo.h"
-#include "rcc/CodeGen/MachineMetadata.h"
-#include "rcc/CodeGen/MachineRegisterInfo.h"
-#include "rcc/CodeGen/MachineVerifier.h"
-#include "rcc/CodeGen/TargetDescription.h"
-#include "rcc/CodeGen/TargetMIRTextInfo.h"
-#include "rcc/CodeGen/TargetRegisterInfo.h"
-#include "rcc/MIR/LLVMConstantSyntax.h"
-#include "rcc/MIR/MIDebugSyntax.h"
-#include "rcc/MIR/MILexer.h"
+#include "MIRDocumentParser.h"
-#include <algorithm>
-#include <array>
-#include <charconv>
-#include <cstddef>
-#include <cstdint>
-#include <functional>
-#include <initializer_list>
-#include <limits>
-#include <optional>
-#include <ranges>
-#include <span>
#include <string>
-#include <string_view>
-#include <unordered_map>
-#include <unordered_set>
#include <utility>
-#include <vector>
namespace rcc::mir {
-namespace {
-
-[[nodiscard]] std::string_view trim(std::string_view Text) noexcept {
- while (!Text.empty() && (Text.front() == ' ' || Text.front() == '\t' || Text.front() == '\r' ||
- Text.front() == '\n')) {
- Text.remove_prefix(1);
- }
- while (!Text.empty() && (Text.back() == ' ' || Text.back() == '\t' || Text.back() == '\r' ||
- Text.back() == '\n')) {
- Text.remove_suffix(1);
- }
- return Text;
-}
-
-[[nodiscard]] bool parseUnsigned(std::string_view Text, std::uint64_t &Value) noexcept {
- Text = trim(Text);
- int Base = 10;
- if (Text.starts_with("0x") || Text.starts_with("0X")) {
- Text.remove_prefix(2);
- Base = 16;
- }
- if (Text.empty())
- return false;
- const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, Base);
- return Error == std::errc{} && End == Text.data() + Text.size();
-}
-
-[[nodiscard]] bool parseSigned(std::string_view Text, std::int64_t &Value) noexcept {
- Text = trim(Text);
- if (Text.empty())
- return false;
- const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10);
- return Error == std::errc{} && End == Text.data() + Text.size();
-}
-
-[[nodiscard]] bool parseUnsigned32(std::string_view Text, std::uint32_t &Value) noexcept {
- std::uint64_t Wide = 0;
- if (!parseUnsigned(Text, Wide) || Wide > std::numeric_limits<std::uint32_t>::max())
- return false;
- Value = static_cast<std::uint32_t>(Wide);
- return true;
-}
-
-[[nodiscard]] bool parseBool(std::string_view Text, bool &Value) noexcept {
- if (Text == "true") {
- Value = true;
- return true;
- }
- if (Text == "false") {
- Value = false;
- return true;
- }
- return false;
-}
-
-[[nodiscard]] std::optional<std::uint32_t> parsePrefixedID(std::string_view Text,
- std::string_view Prefix) noexcept {
- Text = trim(Text);
- if (!Text.starts_with(Prefix))
- return std::nullopt;
- Text.remove_prefix(Prefix.size());
- const std::size_t Suffix = Text.find_first_of(". \t");
- if (Suffix != std::string_view::npos)
- Text = Text.substr(0, Suffix);
- std::uint32_t ID = 0;
- if (!parseUnsigned32(Text, ID))
- return std::nullopt;
- return ID;
-}
-
-struct PendingJumpTable final {
- JumpTableIndex Index;
- std::uint32_t TextID = 0;
- JumpTableEntryEncoding Encoding = JumpTableEntryEncoding::BlockAddress;
- std::uint8_t EntrySize = 8;
- std::vector<std::string> Blocks;
-};
-
-class MetadataTextParser final {
-public:
- using Resolver = std::function<Expected<MachineMetadataReference>(std::uint32_t Slot)>;
-
- MetadataTextParser(std::string Text, Resolver Resolve)
- : Lexer("MIR metadata scalar", std::move(Text)), Resolve(std::move(Resolve)) {
- do {
- Tokens.push_back(Lexer.next());
- } while (!Tokens.back().isErrorOrEnd());
- }
-
- [[nodiscard]] Expected<MachineMetadataReference> parseReferenceOnly();
- [[nodiscard]] Expected<MachineMetadataValue> parseValue();
- [[nodiscard]] Expected<MachineDebugLocation> parseLocation();
-
-private:
- [[nodiscard]] const MIToken &token() const noexcept {
- return Tokens[Position];
- }
- [[nodiscard]] bool at(MITokenKind Kind) const noexcept {
- return token().is(Kind);
- }
- void advance() noexcept {
- if (Position + 1U < Tokens.size())
- ++Position;
- }
- [[nodiscard]] bool consume(MITokenKind Kind) noexcept {
- if (!at(Kind))
- return false;
- advance();
- return true;
- }
- [[nodiscard]] Expected<void> expect(MITokenKind Kind, std::string_view Description);
- [[nodiscard]] Expected<void> finish();
- [[nodiscard]] Expected<MachineMetadataReference> parseReference();
- [[nodiscard]] Expected<MachineDIExpression> parseExpression();
- [[nodiscard]] Expected<void> parseDILocation(std::vector<MachineDILocationFrame> &Frames,
- std::optional<MachineMetadataReference> &Terminal);
-
- MILexer Lexer;
- Resolver Resolve;
- std::vector<MIToken> Tokens;
- std::size_t Position = 0;
-};
-
-Expected<void> MetadataTextParser::expect(MITokenKind Kind, std::string_view Description) {
- if (consume(Kind))
- return {};
- return makeUnexpected(ErrorCode::ParseError,
- "expected " + std::string(Description) + " in MIR metadata scalar");
-}
-
-Expected<void> MetadataTextParser::finish() {
- while (consume(MITokenKind::Newline)) {
- }
- if (Lexer.failed()) {
- if (!Lexer.diagnostics().empty()) {
- return makeUnexpected(ErrorCode::ParseError,
- std::string(Lexer.diagnostics().front().Message));
- }
- return makeUnexpected(ErrorCode::ParseError, "invalid MIR metadata scalar");
- }
- if (!at(MITokenKind::EndOfFile))
- return makeUnexpected(ErrorCode::ParseError, "unexpected trailing MIR metadata tokens");
- return {};
-}
-
-Expected<MachineMetadataReference> MetadataTextParser::parseReference() {
- if (auto Exclaim = expect(MITokenKind::Exclaim, "'!' before a metadata slot"); !Exclaim)
- return std::unexpected(Exclaim.error());
- if (!at(MITokenKind::IntegerLiteral))
- return makeUnexpected(ErrorCode::ParseError, "expected a 32-bit metadata slot");
- std::uint32_t Slot = 0;
- if (!parseUnsigned32(token().integerValue(), Slot))
- return makeUnexpected(ErrorCode::ParseError, "metadata slot is outside the 32-bit range");
- advance();
- return Resolve(Slot);
-}
-
-Expected<MachineMetadataReference> MetadataTextParser::parseReferenceOnly() {
- auto Reference = parseReference();
- if (!Reference)
- return std::unexpected(Reference.error());
- if (auto Finished = finish(); !Finished)
- return std::unexpected(Finished.error());
- return Reference;
-}
-
-Expected<MachineDIExpression> MetadataTextParser::parseExpression() {
- if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DIExpression")) {
- return makeUnexpected(ErrorCode::ParseError, "expected an inline !DIExpression");
- }
- advance();
- if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DIExpression"); !Left)
- return std::unexpected(Left.error());
- std::vector<std::uint64_t> Elements;
- while (!at(MITokenKind::RightParen)) {
- if (at(MITokenKind::Identifier)) {
- const auto Opcode = parseDIExpressionOpcodeName(token().stringValue());
- if (!Opcode) {
- return makeUnexpected(ErrorCode::ParseError, "unknown DIExpression opcode '" +
- std::string(token().stringValue()) + "'");
- }
- Elements.push_back(*Opcode);
- advance();
- } else if (at(MITokenKind::IntegerLiteral)) {
- std::int64_t Signed = 0;
- std::uint64_t Unsigned = 0;
- if (parseSigned(token().integerValue(), Signed))
- Elements.push_back(static_cast<std::uint64_t>(Signed));
- else if (parseUnsigned(token().integerValue(), Unsigned))
- Elements.push_back(Unsigned);
- else
- return makeUnexpected(ErrorCode::ParseError, "DIExpression integer exceeds 64 bits");
- advance();
- } else {
- return makeUnexpected(ErrorCode::ParseError, "expected a DIExpression opcode or integer");
- }
- if (!consume(MITokenKind::Comma))
- break;
- }
- if (auto Right = expect(MITokenKind::RightParen, "')' after !DIExpression"); !Right)
- return std::unexpected(Right.error());
- return MachineDIExpression(std::move(Elements));
-}
-
-Expected<MachineMetadataValue> MetadataTextParser::parseValue() {
- MachineMetadataValue Value;
- if (at(MITokenKind::Exclaim)) {
- auto Reference = parseReference();
- if (!Reference)
- return std::unexpected(Reference.error());
- auto Created = MachineMetadataValue::reference(*Reference);
- if (!Created)
- return std::unexpected(Created.error());
- Value = std::move(*Created);
- } else {
- auto Expression = parseExpression();
- if (!Expression)
- return std::unexpected(Expression.error());
- Value = MachineMetadataValue::expression(std::move(*Expression));
- }
- if (auto Finished = finish(); !Finished)
- return std::unexpected(Finished.error());
- return Value;
-}
-
-Expected<void>
-MetadataTextParser::parseDILocation(std::vector<MachineDILocationFrame> &Frames,
- std::optional<MachineMetadataReference> &Terminal) {
- if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DILocation")) {
- return makeUnexpected(ErrorCode::ParseError, "expected an inline !DILocation");
- }
- advance();
- if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DILocation"); !Left)
- return Left;
-
- MachineDILocationFrame Frame;
- bool HasLine = false;
- bool HasScope = false;
- std::vector<MachineDILocationFrame> NestedFrames;
- std::optional<MachineMetadataReference> NestedTerminal;
- std::unordered_set<std::string> Seen;
- while (!at(MITokenKind::RightParen)) {
- if (!at(MITokenKind::Identifier))
- return makeUnexpected(ErrorCode::ParseError, "expected a DILocation field name");
- const std::string Field(token().stringValue());
- if (!Seen.insert(Field).second) {
- return makeUnexpected(ErrorCode::ParseError, "duplicate DILocation field '" + Field + "'");
- }
- advance();
- if (auto Colon = expect(MITokenKind::Colon, "':' after a DILocation field"); !Colon)
- return Colon;
-
- if (Field == "line") {
- std::uint32_t Value = 0;
- if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned32(token().integerValue(), Value)) {
- return makeUnexpected(ErrorCode::ParseError,
- "DILocation line must be an unsigned 32-bit integer");
- }
- Frame.Line = Value;
- HasLine = true;
- advance();
- } else if (Field == "column") {
- std::uint64_t Value = 0;
- if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) ||
- Value > std::numeric_limits<std::uint16_t>::max()) {
- return makeUnexpected(ErrorCode::ParseError,
- "DILocation column must be an unsigned 16-bit integer");
- }
- Frame.Column = static_cast<std::uint16_t>(Value);
- advance();
- } else if (Field == "scope") {
- auto Scope = parseReference();
- if (!Scope)
- return std::unexpected(Scope.error());
- if (Scope->scope() != MachineMetadataScope::EmbeddedIR) {
- return makeUnexpected(ErrorCode::ParseError,
- "DILocation scope must reference embedded LLVM IR metadata");
- }
- Frame.Scope = *Scope;
- HasScope = true;
- } else if (Field == "inlinedAt") {
- if (at(MITokenKind::MetadataKeyword) && token().isMetadataKeyword("!DILocation")) {
- if (auto Nested = parseDILocation(NestedFrames, NestedTerminal); !Nested)
- return Nested;
- } else {
- auto Reference = parseReference();
- if (!Reference)
- return std::unexpected(Reference.error());
- if (Reference->scope() != MachineMetadataScope::EmbeddedIR) {
- return makeUnexpected(ErrorCode::ParseError,
- "DILocation inlinedAt must reference embedded LLVM IR metadata");
- }
- NestedTerminal = *Reference;
- }
- } else if (Field == "isImplicitCode") {
- if (!at(MITokenKind::Identifier) ||
- (token().stringValue() != "true" && token().stringValue() != "false")) {
- return makeUnexpected(ErrorCode::ParseError,
- "DILocation isImplicitCode must be true or false");
- }
- Frame.IsImplicitCode = token().stringValue() == "true";
- advance();
- } else if (Field == "atomGroup") {
- std::uint64_t Value = 0;
- if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) ||
- Value > MachineDILocation::MaxAtomGroup) {
- return makeUnexpected(ErrorCode::ParseError, "DILocation atomGroup exceeds 61 bits");
- }
- Frame.AtomGroup = Value;
- advance();
- } else if (Field == "atomRank") {
- std::uint64_t Value = 0;
- if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) ||
- Value > 7) {
- return makeUnexpected(ErrorCode::ParseError, "DILocation atomRank exceeds three bits");
- }
- Frame.AtomRank = static_cast<std::uint8_t>(Value);
- advance();
- } else {
- return makeUnexpected(ErrorCode::ParseError, "unknown DILocation field '" + Field + "'");
- }
- if (!consume(MITokenKind::Comma))
- break;
- }
- if (auto Right = expect(MITokenKind::RightParen, "')' after !DILocation"); !Right)
- return Right;
- if (!HasLine || !HasScope)
- return makeUnexpected(ErrorCode::ParseError, "DILocation requires line and scope fields");
- Frames.push_back(Frame);
- Frames.insert(Frames.end(), NestedFrames.begin(), NestedFrames.end());
- Terminal = NestedTerminal;
- return {};
-}
-
-Expected<MachineDebugLocation> MetadataTextParser::parseLocation() {
- MachineDebugLocation Location;
- if (at(MITokenKind::Exclaim)) {
- auto Reference = parseReference();
- if (!Reference)
- return std::unexpected(Reference.error());
- auto Created = MachineDebugLocation::reference(*Reference);
- if (!Created)
- return std::unexpected(Created.error());
- Location = std::move(*Created);
- } else {
- std::vector<MachineDILocationFrame> Frames;
- std::optional<MachineMetadataReference> Terminal;
- if (auto Parsed = parseDILocation(Frames, Terminal); !Parsed)
- return std::unexpected(Parsed.error());
- auto Created = MachineDILocation::create(std::move(Frames), Terminal);
- if (!Created)
- return std::unexpected(Created.error());
- Location = MachineDebugLocation::inlineLocation(std::move(*Created));
- }
- if (auto Finished = finish(); !Finished)
- return std::unexpected(Finished.error());
- return Location;
-}
-
-} // namespace
-
-class MIRDocumentParser final {
-public:
- MIRDocumentParser(const TargetDescription &Target, std::string SourceName, std::string SourceText,
- MIRDocumentParserOptions Options)
- : Target(Target), Options(std::move(Options)),
- Result(std::move(SourceName), std::move(SourceText)) {}
-
- [[nodiscard]] MIRDocumentParseResult run();
-
-private:
- [[nodiscard]] bool fail(const TextRange &Range, std::string Message,
- std::string Category = "mir-yaml");
- [[nodiscard]] bool importError(const TextRange &Range, const Error &Failure,
- std::string Category = "mir-yaml");
- [[nodiscard]] const yaml::Node *node(yaml::NodeID ID) const noexcept {
- return Stream->node(ID);
- }
- [[nodiscard]] const yaml::Node *mappingValue(const yaml::Node &Mapping,
- std::string_view Key) const noexcept;
- [[nodiscard]] bool requireKind(const yaml::Node &Node, yaml::NodeKind Kind,
- std::string_view Description);
- [[nodiscard]] bool validateKeys(const yaml::Node &Mapping, std::span<const std::string_view> Keys,
- std::string_view Description);
- [[nodiscard]] bool scalar(const yaml::Node &Node, std::string_view &Value,
- std::string_view Description);
- [[nodiscard]] bool boolean(const yaml::Node &Node, bool &Value);
- [[nodiscard]] bool unsignedValue(const yaml::Node &Node, std::uint64_t &Value,
- std::string_view Description);
- [[nodiscard]] bool signedValue(const yaml::Node &Node, std::int64_t &Value,
- std::string_view Description);
- [[nodiscard]] bool alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero);
- [[nodiscard]] bool physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg);
- [[nodiscard]] bool physicalRegister(std::string_view Text, const TextRange &Range,
- PhysicalRegister &Reg);
- [[nodiscard]] bool virtualRegister(std::string_view Text, const TextRange &Range,
- const MIParserContext &Context, Register &Reg);
- [[nodiscard]] Expected<MachineMetadataReference>
- resolveMetadataReference(const MachineFunction &Function, std::uint32_t Slot) const;
- [[nodiscard]] bool parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function,
- MachineVariableDebugInfo &DebugInfo);
- [[nodiscard]] bool parseFunction(const yaml::Node &Root, MIRDocument &Document);
- [[nodiscard]] bool parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function,
- MIRFunctionDocumentInfo &DocumentInfo);
- [[nodiscard]] bool recomputeProperties(const yaml::Node &Root, MachineFunction &Function,
- const MIRFunctionDocumentInfo &DocumentInfo);
- [[nodiscard]] bool parseRegisters(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context);
- [[nodiscard]] bool parseMetadata(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context);
- [[nodiscard]] bool parseStackObjects(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context);
- [[nodiscard]] bool parseConstants(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context);
- [[nodiscard]] bool parseFrameState(const yaml::Node &Root, MachineFunction &Function,
- const MIParserContext &Context);
- [[nodiscard]] bool reserveJumpTables(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context,
- std::vector<PendingJumpTable> &Pending);
- [[nodiscard]] bool defineJumpTables(MachineFunction &Function,
- std::span<const PendingJumpTable> Pending);
- [[nodiscard]] bool parseBody(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context);
- [[nodiscard]] TextRange bodyDiagnosticRange(const yaml::Node &Body,
- const TextRange &BodyRange) const noexcept;
- [[nodiscard]] bool parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function);
- [[nodiscard]] bool parseTargetState(const yaml::Node &Root, MachineFunction &Function);
- [[nodiscard]] bool convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination);
- [[nodiscard]] bool validateDocumentReferences(const MachineFunction &Function);
- [[nodiscard]] std::string_view effectiveDataLayout() const noexcept;
- [[nodiscard]] LLVMConstantSyntaxOptions pointerLayoutOptions() const noexcept;
-
- [[nodiscard]] MachineBasicBlock *blockAt(MachineFunction &Function,
- std::uint32_t Number) const noexcept;
- [[nodiscard]] MachineInstr *instructionAt(MachineFunction &Function, std::uint32_t Block,
- std::uint32_t Offset) const noexcept;
- [[nodiscard]] bool parseBlockReference(std::string_view Text, const TextRange &Range,
- MachineFunction &Function, MachineBasicBlock *&Block);
- [[nodiscard]] bool parseFrameReference(const yaml::Node &Node, const MIParserContext &Context,
- FrameIndex &Index);
- [[nodiscard]] bool parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function,
- std::vector<MachineFrameSaveRestorePoint> &Points);
-
- const TargetDescription &Target;
- MIRDocumentParserOptions Options;
- MIRDocumentParseResult Result;
- const yaml::Stream *Stream = nullptr;
- MIRDocument *ActiveDocument = nullptr;
- MachineFunction *ActiveFunction = nullptr;
-};
-
-bool MIRDocumentParser::fail(const TextRange &Range, std::string Message, std::string Category) {
- if (Result.Diagnostics.empty()) {
- if (ActiveFunction != nullptr)
- Message = "function '" + std::string(ActiveFunction->name()) + "': " + std::move(Message);
- Result.Diagnostics.push_back(TextDiagnostic{
- .Severity = DiagnosticSeverity::Error,
- .Category = std::move(Category),
- .Message = std::move(Message),
- .SourceName = std::string(Result.Source.name()),
- .Range = Range,
- });
- }
- return true;
-}
-
-bool MIRDocumentParser::importError(const TextRange &Range, const Error &Failure,
- std::string Category) {
- return fail(Range, std::string(Failure.message()), std::move(Category));
-}
-
-const yaml::Node *MIRDocumentParser::mappingValue(const yaml::Node &Mapping,
- std::string_view Key) const noexcept {
- const auto Value = Mapping.mappingValue(Key);
- return Value ? node(*Value) : nullptr;
-}
-
-std::string_view MIRDocumentParser::effectiveDataLayout() const noexcept {
- if (ActiveDocument != nullptr) {
- if (const EmbeddedIRReferenceIndex *Embedded = ActiveDocument->embeddedIR()) {
- if (const auto DataLayout = Embedded->dataLayout())
- return *DataLayout;
- }
- }
- return Options.DataLayout;
-}
-
-LLVMConstantSyntaxOptions MIRDocumentParser::pointerLayoutOptions() const noexcept {
- return {
- .DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits,
- .DataLayout = effectiveDataLayout(),
- .NamedTypes = {},
- };
-}
-
-bool MIRDocumentParser::requireKind(const yaml::Node &Node, yaml::NodeKind Kind,
- std::string_view Description) {
- if (Node.kind() == Kind)
- return false;
- return fail(Node.range(), std::string(Description) + " has the wrong YAML node kind");
-}
-
-bool MIRDocumentParser::validateKeys(const yaml::Node &Mapping,
- std::span<const std::string_view> Keys,
- std::string_view Description) {
- for (const yaml::MappingEntry &Entry : Mapping.mapping()) {
- if (std::ranges::find(Keys, Entry.Key) == Keys.end()) {
- return fail(Entry.KeyRange,
- "unknown " + std::string(Description) + " key '" + Entry.Key + "'");
- }
- }
- return false;
-}
-
-bool MIRDocumentParser::scalar(const yaml::Node &Node, std::string_view &Value,
- std::string_view Description) {
- if (requireKind(Node, yaml::NodeKind::Scalar, Description))
- return true;
- Value = Node.scalar();
- return false;
-}
-
-bool MIRDocumentParser::boolean(const yaml::Node &Node, bool &Value) {
- std::string_view Text;
- if (scalar(Node, Text, "boolean") || !parseBool(Text, Value))
- return fail(Node.range(), "expected 'true' or 'false'");
- return false;
-}
-
-bool MIRDocumentParser::unsignedValue(const yaml::Node &Node, std::uint64_t &Value,
- std::string_view Description) {
- std::string_view Text;
- if (scalar(Node, Text, Description) || !parseUnsigned(Text, Value))
- return fail(Node.range(), "expected " + std::string(Description));
- return false;
-}
-
-bool MIRDocumentParser::signedValue(const yaml::Node &Node, std::int64_t &Value,
- std::string_view Description) {
- std::string_view Text;
- if (scalar(Node, Text, Description) || !parseSigned(Text, Value))
- return fail(Node.range(), "expected " + std::string(Description));
- return false;
-}
-
-bool MIRDocumentParser::alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero) {
- std::uint64_t Bytes = 0;
- if (unsignedValue(Node, Bytes, "an unsigned alignment"))
- return true;
- if (Bytes == 0 && AllowZero) {
- Value = Alignment{};
- return false;
- }
- auto Parsed = Alignment::create(Bytes);
- if (!Parsed)
- return importError(Node.range(), Parsed.error());
- Value = *Parsed;
- return false;
-}
-
-bool MIRDocumentParser::physicalRegister(std::string_view Text, const TextRange &Range,
- PhysicalRegister &Reg) {
- Text = trim(Text);
- if (!Text.starts_with('$') || Text.size() == 1)
- return fail(Range, "physical register must use the '$name' MIR spelling");
- const PhysicalRegisterDesc *Description = Target.registers().physicalRegister(Text.substr(1));
- if (Description == nullptr)
- return fail(Range, "unknown physical register '" + std::string(Text) + "'");
- Reg = Description->Reg;
- return false;
-}
-
-bool MIRDocumentParser::physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg) {
- std::string_view Text;
- return scalar(Node, Text, "physical register") || physicalRegister(Text, Node.range(), Reg);
-}
-
-bool MIRDocumentParser::virtualRegister(std::string_view Text, const TextRange &Range,
- const MIParserContext &Context, Register &Reg) {
- const auto ID = parsePrefixedID(Text, "%");
- if (!ID)
- return fail(Range, "virtual register must use the '%N' MIR spelling");
- const auto Found =
- std::ranges::find_if(Context.VirtualRegisters,
- [ID](const MIVirtualRegisterSlot &Slot) { return Slot.TextID == *ID; });
- if (Found == Context.VirtualRegisters.end())
- return fail(Range, "reference to undefined virtual register '%" + std::to_string(*ID) + "'");
- Reg = Found->Reg;
- return false;
-}
-
-Expected<MachineMetadataReference>
-MIRDocumentParser::resolveMetadataReference(const MachineFunction &Function,
- std::uint32_t Slot) const {
- if (ActiveDocument != nullptr && ActiveDocument->EmbeddedIR &&
- ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) {
- return MachineMetadataReference::embeddedIR(Slot);
- }
- if (Function.metadata().node(Slot) != nullptr)
- return MachineMetadataReference::machineFunction(Slot);
- return makeUnexpected(ErrorCode::ParseError,
- "metadata reference uses undefined slot '!" + std::to_string(Slot) + "'");
-}
-
-bool MIRDocumentParser::parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function,
- MachineVariableDebugInfo &DebugInfo) {
- const auto Resolve = [this, &Function](std::uint32_t Slot) {
- return resolveMetadataReference(Function, Slot);
- };
- if (const yaml::Node *VariableNode = mappingValue(Entry, "debug-info-variable")) {
- std::string_view Text;
- if (scalar(*VariableNode, Text, "debug variable"))
- return true;
- if (!Text.empty()) {
- MetadataTextParser Parser(std::string(Text), Resolve);
- auto Reference = Parser.parseReferenceOnly();
- if (!Reference)
- return importError(VariableNode->range(), Reference.error(), "mir-debug-metadata");
- if (Reference->scope() != MachineMetadataScope::EmbeddedIR) {
- return fail(VariableNode->range(),
- "debug variable must reference embedded LLVM IR metadata",
- "mir-debug-metadata");
- }
- DebugInfo.Variable = *Reference;
- }
- }
- if (const yaml::Node *ExpressionNode = mappingValue(Entry, "debug-info-expression")) {
- std::string_view Text;
- if (scalar(*ExpressionNode, Text, "debug expression"))
- return true;
- if (!Text.empty()) {
- MetadataTextParser Parser(std::string(Text), Resolve);
- auto Value = Parser.parseValue();
- if (!Value)
- return importError(ExpressionNode->range(), Value.error(), "mir-debug-metadata");
- if (Value->reference() != nullptr &&
- Value->reference()->scope() != MachineMetadataScope::EmbeddedIR) {
- return fail(ExpressionNode->range(),
- "debug expression reference must use embedded LLVM IR metadata",
- "mir-debug-metadata");
- }
- DebugInfo.Expression = std::move(*Value);
- }
- }
- if (const yaml::Node *LocationNode = mappingValue(Entry, "debug-info-location")) {
- std::string_view Text;
- if (scalar(*LocationNode, Text, "debug location"))
- return true;
- if (!Text.empty()) {
- MetadataTextParser Parser(std::string(Text), Resolve);
- auto Location = Parser.parseLocation();
- if (!Location)
- return importError(LocationNode->range(), Location.error(), "mir-debug-metadata");
- if (Location->reference() != nullptr &&
- Location->reference()->scope() != MachineMetadataScope::EmbeddedIR) {
- return fail(LocationNode->range(),
- "variable debug location must reference embedded LLVM IR metadata",
- "mir-debug-metadata");
- }
- DebugInfo.Location = std::move(*Location);
- }
- }
- if (!DebugInfo.Variable && !DebugInfo.Expression && !DebugInfo.Location)
- return false;
- auto Verification = DebugInfo.verifyStructure();
- if (!Verification)
- return importError(Entry.range(), Verification.error(), "mir-debug-metadata");
- return false;
-}
-
-MIRDocumentParseResult MIRDocumentParser::run() {
- yaml::ParseResult YAMLResult = yaml::parse(std::string(Result.Source.name()),
- std::string(Result.Source.text()), Options.YAML);
- if (!YAMLResult.succeeded()) {
- const auto Diagnostics = YAMLResult.diagnostics();
- Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end());
- return std::move(Result);
- }
- Stream = YAMLResult.stream();
- if (Stream == nullptr || Stream->documents().empty()) {
- static_cast<void>(fail(Result.Source.range(0, 0), "MIR stream contains no YAML documents"));
- return std::move(Result);
- }
-
- MIRDocument Staging;
- ActiveDocument = &Staging;
- std::size_t FirstFunction = 0;
- const yaml::Node *FirstRoot = node(Stream->documents().front().Root);
- if (FirstRoot != nullptr && FirstRoot->kind() == yaml::NodeKind::Scalar) {
- EmbeddedIRIndexResult Embedded = indexEmbeddedIR(
- std::string(Result.Source.name()) + ":embedded-ir", std::string(FirstRoot->scalar()));
- if (!Embedded.succeeded()) {
- const auto Diagnostics = Embedded.diagnostics();
- Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end());
- return std::move(Result);
- }
- Staging.EmbeddedIR = std::move(Embedded).takeIndex();
- FirstFunction = 1;
- }
-
- if (const EmbeddedIRReferenceIndex *Embedded = Staging.embeddedIR()) {
- if (const auto DataLayout = Embedded->dataLayout();
- DataLayout && !Options.DataLayout.empty() && *DataLayout != Options.DataLayout) {
- static_cast<void>(fail(Stream->documents().front().Range,
- "explicit DataLayout does not match embedded LLVM IR data layout"));
- return std::move(Result);
- }
- }
- if (!effectiveDataLayout().empty()) {
- auto PointerSize = llvmPointerSizeInBits(0, pointerLayoutOptions());
- if (!PointerSize) {
- static_cast<void>(
- importError(Stream->documents().front().Range, PointerSize.error(), "mir-data-layout"));
- return std::move(Result);
- }
- }
-
- if (FirstFunction == Stream->documents().size()) {
- static_cast<void>(fail(Stream->documents().front().Range,
- "MIR stream must contain at least one machine function document"));
- return std::move(Result);
- }
-
- for (std::size_t Index = FirstFunction; Index < Stream->documents().size(); ++Index) {
- const yaml::Node *Root = node(Stream->documents()[Index].Root);
- if (Root == nullptr || requireKind(*Root, yaml::NodeKind::Mapping, "machine function") ||
- parseFunction(*Root, Staging)) {
- return std::move(Result);
- }
- }
-
- auto UnitResult = Staging.Unit.verify();
- if (!UnitResult) {
- static_cast<void>(
- fail(Stream->documents().back().Range, "parsed compilation unit failed verification: " +
- std::string(UnitResult.error().message())));
- return std::move(Result);
- }
- ActiveDocument = nullptr;
- Result.Document = std::move(Staging);
- return std::move(Result);
-}
-
-bool MIRDocumentParser::parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function,
- MIRFunctionDocumentInfo &DocumentInfo) {
- if (const yaml::Node *AlignNode = mappingValue(Root, "alignment")) {
- Alignment Align;
- if (alignment(*AlignNode, Align, true))
- return true;
- if (Align.bytes() != 1 || AlignNode->scalar() != "0")
- Function.attributes().setAlignment(Align);
- }
-
- constexpr std::array PropertyBindings{
- std::pair{"legalized", MachineFunctionProperty::Legalized},
- std::pair{"regBankSelected", MachineFunctionProperty::RegBankSelected},
- std::pair{"selected", MachineFunctionProperty::Selected},
- std::pair{"failedISel", MachineFunctionProperty::FailedISel},
- std::pair{"tracksRegLiveness", MachineFunctionProperty::TracksLiveness},
- std::pair{"noPhis", MachineFunctionProperty::NoPHIs},
- std::pair{"isSSA", MachineFunctionProperty::IsSSA},
- std::pair{"noVRegs", MachineFunctionProperty::NoVRegs},
- std::pair{"failsVerification", MachineFunctionProperty::FailsVerification},
- };
- for (const auto &[Key, Property] : PropertyBindings) {
- const yaml::Node *ValueNode = mappingValue(Root, Key);
- if (ValueNode == nullptr)
- continue;
- bool Value = false;
- if (boolean(*ValueNode, Value))
- return true;
- if (Value)
- Function.properties().set(Property);
- else
- Function.properties().clear(Property);
- if (Key == std::string_view("noPhis"))
- DocumentInfo.NoPHIs = Value;
- else if (Key == std::string_view("isSSA"))
- DocumentInfo.IsSSA = Value;
- else if (Key == std::string_view("noVRegs"))
- DocumentInfo.NoVRegs = Value;
- }
-
- constexpr std::array AttributeBindings{
- std::pair{"exposesReturnsTwice", MachineFunctionAttribute::ExposesReturnsTwice},
- std::pair{"hasWinCFI", MachineFunctionAttribute::HasWinCFI},
- std::pair{"hasFakeUses", MachineFunctionAttribute::HasFakeUses},
- std::pair{"callsEHReturn", MachineFunctionAttribute::CallsEHReturn},
- std::pair{"callsUnwindInit", MachineFunctionAttribute::CallsUnwindInit},
- std::pair{"hasEHContTarget", MachineFunctionAttribute::HasEHContTarget},
- std::pair{"hasEHScopes", MachineFunctionAttribute::HasEHScopes},
- std::pair{"hasEHFunclets", MachineFunctionAttribute::HasEHFunclets},
- std::pair{"isOutlined", MachineFunctionAttribute::IsOutlined},
- std::pair{"debugInstrRef", MachineFunctionAttribute::DebugInstrRef},
- std::pair{"tracksDebugUserValues", MachineFunctionAttribute::TracksDebugUserValues},
- };
- for (const auto &[Key, Attribute] : AttributeBindings) {
- const yaml::Node *ValueNode = mappingValue(Root, Key);
- if (ValueNode == nullptr)
- continue;
- bool Value = false;
- if (boolean(*ValueNode, Value))
- return true;
- if (Value)
- Function.attributes().set(Attribute);
- else
- Function.attributes().clear(Attribute);
- if (Key == std::string_view("hasFakeUses"))
- DocumentInfo.HasFakeUses = Value;
- }
-
- return false;
-}
-
-bool MIRDocumentParser::recomputeProperties(const yaml::Node &Root, MachineFunction &Function,
- const MIRFunctionDocumentInfo &DocumentInfo) {
- bool HasPHI = false;
- bool HasFakeUse = false;
- bool IsSSA = true;
- bool HasTiedOperands = false;
- bool AllTiedOperandsRewritten = true;
- std::unordered_set<Register> Definitions;
- for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
- Block = Block->next()) {
- for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;
- Instruction = Instruction->next()) {
- const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode());
- if (Descriptor == nullptr)
- return fail(Root.range(), "instruction has no target descriptor");
- HasPHI |= Descriptor->Flags.has(InstructionFlag::Phi);
- HasFakeUse |= Descriptor->Flags.has(InstructionFlag::FakeUse);
- const auto Operands = Instruction->operands();
- for (std::size_t Index = 0; Index < Operands.size(); ++Index) {
- const MachineOperand &Operand = Operands[Index];
- const auto Reg = Operand.registerValue();
- if (Reg && Reg->isVirtual() && Operand.isDef() && !Definitions.insert(*Reg).second) {
- IsSSA = false;
- }
- if (!Operand.tiedOperandIndex() || !Operand.isUse())
- continue;
- HasTiedOperands = true;
- const MachineOperand *Definition = Instruction->operandAt(*Operand.tiedOperandIndex());
- if (Definition == nullptr || Definition->registerValue() != Reg)
- AllTiedOperandsRewritten = false;
- }
- }
- }
- const bool NoPHIs = !HasPHI;
- const bool NoVRegs =
- std::ranges::none_of(Function.registerInfo().virtualRegisters(),
- [](const VirtualRegisterInfo &Info) { return !Info.IsTombstone; });
-
- const auto ApplyComputed = [this, &Root, &Function](std::optional<bool> Explicit, bool Computed,
- MachineFunctionProperty Property,
- std::string_view Name) -> bool {
- if (Explicit && *Explicit && !Computed) {
- return fail(Root.range(), "explicit property '" + std::string(Name) +
- "' conflicts with the parsed machine function");
- }
- if (Explicit.value_or(Computed))
- Function.properties().set(Property);
- else
- Function.properties().clear(Property);
- return false;
- };
- if (ApplyComputed(DocumentInfo.NoPHIs, NoPHIs, MachineFunctionProperty::NoPHIs, "noPhis") ||
- ApplyComputed(DocumentInfo.IsSSA, IsSSA, MachineFunctionProperty::IsSSA, "isSSA") ||
- ApplyComputed(DocumentInfo.NoVRegs, NoVRegs, MachineFunctionProperty::NoVRegs, "noVRegs")) {
- return true;
- }
-
- if (DocumentInfo.HasFakeUses && !*DocumentInfo.HasFakeUses && HasFakeUse) {
- return fail(Root.range(),
- "explicit property 'hasFakeUses: false' conflicts with a fake-use instruction");
- }
- if (DocumentInfo.HasFakeUses.value_or(HasFakeUse))
- Function.attributes().set(MachineFunctionAttribute::HasFakeUses);
- else
- Function.attributes().clear(MachineFunctionAttribute::HasFakeUses);
-
- if (HasTiedOperands && AllTiedOperandsRewritten)
- Function.properties().set(MachineFunctionProperty::TiedOpsRewritten);
- else
- Function.properties().clear(MachineFunctionProperty::TiedOpsRewritten);
- return false;
-}
-
-bool MIRDocumentParser::parseRegisters(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context) {
- const yaml::Node *Registers = mappingValue(Root, "registers");
- if (Registers != nullptr) {
- if (requireKind(*Registers, yaml::NodeKind::Sequence, "registers"))
- return true;
- constexpr std::array RegisterKeys{
- std::string_view{"id"},
- std::string_view{"class"},
- std::string_view{"preferred-register"},
- std::string_view{"flags"},
- };
- std::unordered_set<std::uint32_t> IDs;
- for (const yaml::NodeID ElementID : Registers->sequence()) {
- const yaml::Node *Element = node(ElementID);
- if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "register entry") ||
- validateKeys(*Element, RegisterKeys, "register")) {
- return true;
- }
- const yaml::Node *IDNode = mappingValue(*Element, "id");
- const yaml::Node *ClassNode = mappingValue(*Element, "class");
- if (IDNode == nullptr || ClassNode == nullptr)
- return fail(Element->range(), "register entry requires 'id' and 'class'");
- std::uint64_t WideID = 0;
- std::string_view ClassName;
- if (unsignedValue(*IDNode, WideID, "a 32-bit virtual-register ID") ||
- WideID > std::numeric_limits<std::uint32_t>::max() ||
- scalar(*ClassNode, ClassName, "register class or bank")) {
- return true;
- }
- const auto TextID = static_cast<std::uint32_t>(WideID);
- if (!IDs.insert(TextID).second)
- return fail(IDNode->range(), "duplicate virtual register ID");
- auto Created = Function.registerInfo().createVirtualRegister();
- if (!Created)
- return importError(IDNode->range(), Created.error());
- Context.VirtualRegisters.push_back({TextID, *Created});
-
- if (ClassName != "_") {
- if (const RegisterClassDesc *Class = Target.registers().registerClass(ClassName)) {
- auto SetResult = Function.registerInfo().setRegisterClass(*Created, Class->ID);
- if (!SetResult)
- return importError(ClassNode->range(), SetResult.error());
- } else if (const RegisterBankDesc *Bank = Target.registers().registerBank(ClassName)) {
- auto SetResult = Function.registerInfo().setRegisterBank(*Created, Bank->ID);
- if (!SetResult)
- return importError(ClassNode->range(), SetResult.error());
- } else {
- return fail(ClassNode->range(),
- "unknown register class or bank '" + std::string(ClassName) + "'");
- }
- }
-
- if (const yaml::Node *Preferred = mappingValue(*Element, "preferred-register")) {
- PhysicalRegister Reg;
- if (physicalRegister(*Preferred, Reg))
- return true;
- auto AddResult = Function.registerInfo().addAllocationHint(*Created, Reg);
- if (!AddResult)
- return importError(Preferred->range(), AddResult.error());
- }
- if (const yaml::Node *Flags = mappingValue(*Element, "flags")) {
- if (requireKind(*Flags, yaml::NodeKind::Sequence, "virtual-register flags"))
- return true;
- std::uint8_t Mask = 0;
- for (const yaml::NodeID FlagID : Flags->sequence()) {
- const yaml::Node *FlagNode = node(FlagID);
- std::string_view Name;
- if (FlagNode == nullptr || scalar(*FlagNode, Name, "virtual-register flag"))
- return true;
- const auto Flag = Target.mirText().virtualRegisterFlag(Name);
- if (!Flag)
- return fail(FlagNode->range(),
- "unknown virtual-register flag '" + std::string(Name) + "'");
- if ((Mask & *Flag) != 0)
- return fail(FlagNode->range(), "duplicate virtual-register flag");
- Mask = static_cast<std::uint8_t>(Mask | *Flag);
- }
- auto SetResult = Function.registerInfo().setTargetRegisterFlags(*Created, Mask);
- if (!SetResult)
- return importError(Flags->range(), SetResult.error());
- }
- }
- }
-
- const yaml::Node *LiveIns = mappingValue(Root, "liveins");
- if (LiveIns != nullptr) {
- if (requireKind(*LiveIns, yaml::NodeKind::Sequence, "liveins"))
- return true;
- constexpr std::array LiveInKeys{
- std::string_view{"reg"},
- std::string_view{"virtual-reg"},
- };
- for (const yaml::NodeID ElementID : LiveIns->sequence()) {
- const yaml::Node *Element = node(ElementID);
- if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "live-in entry") ||
- validateKeys(*Element, LiveInKeys, "live-in")) {
- return true;
- }
- const yaml::Node *RegNode = mappingValue(*Element, "reg");
- if (RegNode == nullptr)
- return fail(Element->range(), "live-in entry requires 'reg'");
- PhysicalRegister Reg;
- if (physicalRegister(*RegNode, Reg))
- return true;
- std::optional<Register> Initial;
- if (const yaml::Node *VirtualNode = mappingValue(*Element, "virtual-reg")) {
- std::string_view Text;
- Register Virtual;
- if (scalar(*VirtualNode, Text, "virtual register") ||
- virtualRegister(Text, VirtualNode->range(), Context, Virtual)) {
- return true;
- }
- Initial = Virtual;
- }
- auto AddResult = Function.registerInfo().addLiveIn(Reg, Initial);
- if (!AddResult)
- return importError(Element->range(), AddResult.error());
- }
- }
-
- if (const yaml::Node *CalleeSaved = mappingValue(Root, "calleeSavedRegisters")) {
- if (requireKind(*CalleeSaved, yaml::NodeKind::Sequence, "callee-saved registers"))
- return true;
- std::vector<PhysicalRegister> RegistersValue;
- for (const yaml::NodeID RegID : CalleeSaved->sequence()) {
- const yaml::Node *RegNode = node(RegID);
- PhysicalRegister Reg;
- if (RegNode == nullptr || physicalRegister(*RegNode, Reg))
- return true;
- RegistersValue.push_back(Reg);
- }
- auto SetResult = Function.records().setCalleeSavedRegisters(std::move(RegistersValue));
- if (!SetResult)
- return importError(CalleeSaved->range(), SetResult.error());
- }
- return false;
-}
-
-bool MIRDocumentParser::parseMetadata(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context) {
- if (ActiveDocument->EmbeddedIR) {
- for (const EmbeddedIRMetadataDefinition &Reference :
- ActiveDocument->EmbeddedIR->metadataDefinitions()) {
- std::uint32_t ID = 0;
- if (!parseUnsigned32(Reference.Name, ID))
- continue;
- if (std::ranges::any_of(Context.Metadata,
- [ID](const MIMetadataSlot &Slot) { return Slot.TextID == ID; })) {
- return fail(Root.range(), "embedded LLVM IR metadata slots are not unique");
- }
- Context.Metadata.push_back(
- {.TextID = ID, .Reference = MachineMetadataReference::embeddedIR(ID)});
- }
- }
-
- const yaml::Node *Metadata = mappingValue(Root, "machineMetadataNodes");
- if (Metadata == nullptr)
- return false;
- if (requireKind(*Metadata, yaml::NodeKind::Sequence, "machine metadata nodes"))
- return true;
-
- struct Definition final {
- const yaml::Node *Node = nullptr;
- std::uint32_t Slot = 0;
- bool IsDistinct = false;
- std::string_view Elements;
- };
- std::vector<Definition> Definitions;
- std::unordered_set<std::uint32_t> Slots;
-
- for (const yaml::NodeID DefinitionID : Metadata->sequence()) {
- const yaml::Node *DefinitionNode = node(DefinitionID);
- std::string_view Text;
- if (DefinitionNode == nullptr || scalar(*DefinitionNode, Text, "machine metadata definition")) {
- return true;
- }
- Text = trim(Text);
- if (!Text.starts_with('!'))
- return fail(DefinitionNode->range(), "machine metadata definition must start with '!N'");
- const std::size_t Equals = Text.find('=');
- if (Equals == std::string_view::npos)
- return fail(DefinitionNode->range(), "machine metadata definition requires '='");
- std::uint32_t Slot = 0;
- if (!parseUnsigned32(trim(Text.substr(1, Equals - 1)), Slot))
- return fail(DefinitionNode->range(), "machine metadata definition has an invalid slot");
- if (!Slots.insert(Slot).second)
- return fail(DefinitionNode->range(), "duplicate machine metadata slot");
- std::string_view Right = trim(Text.substr(Equals + 1));
- bool IsDistinct = false;
- if (Right.starts_with("distinct")) {
- Right = trim(Right.substr(std::string_view("distinct").size()));
- IsDistinct = true;
- }
- if (!Right.starts_with("!{") || !Right.ends_with('}'))
- return fail(DefinitionNode->range(), "machine metadata node must be a metadata tuple");
- Definitions.push_back({.Node = DefinitionNode,
- .Slot = Slot,
- .IsDistinct = IsDistinct,
- .Elements = Right.substr(2, Right.size() - 3)});
- Context.Metadata.push_back(
- {.TextID = Slot, .Reference = MachineMetadataReference::machineFunction(Slot)});
- }
-
- auto ResolveReference = [this,
- &Slots](std::uint32_t Slot,
- const TextRange &Range) -> Expected<MachineMetadataElement> {
- MachineMetadataReference Reference;
- if (ActiveDocument->EmbeddedIR != std::nullopt &&
- ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) {
- Reference = MachineMetadataReference::embeddedIR(Slot);
- } else if (Slots.contains(Slot)) {
- Reference = MachineMetadataReference::machineFunction(Slot);
- } else {
- return makeUnexpected(ErrorCode::ParseError, "metadata tuple references undefined slot '!" +
- std::to_string(Slot) + "' at line " +
- std::to_string(Range.Begin.Line));
- }
- return MachineMetadataElement::reference(Reference);
- };
-
- for (const Definition &Definition : Definitions) {
- MachineMetadataNode NewNode{
- .Slot = Definition.Slot,
- .IsDistinct = Definition.IsDistinct,
- .Elements = {},
- };
- std::string_view Remaining = trim(Definition.Elements);
- while (!Remaining.empty()) {
- std::size_t End = 0;
- bool InString = false;
- bool Escaped = false;
- for (; End < Remaining.size(); ++End) {
- const char Character = Remaining[End];
- if (InString && Escaped) {
- Escaped = false;
- continue;
- }
- if (InString && Character == '\\') {
- Escaped = true;
- continue;
- }
- if (Character == '"') {
- InString = !InString;
- continue;
- }
- if (!InString && Character == ',')
- break;
- }
- if (InString)
- return fail(Definition.Node->range(), "unterminated machine metadata string");
- std::string_view Element = trim(Remaining.substr(0, End));
- if (Element.empty())
- return fail(Definition.Node->range(), "empty machine metadata tuple element");
-
- if (Element == "null") {
- NewNode.Elements.push_back(MachineMetadataElement::null());
- } else if (Element.starts_with('!') && Element.size() > 1 && Element[1] >= '0' &&
- Element[1] <= '9') {
- std::uint32_t ReferenceSlot = 0;
- if (!parseUnsigned32(Element.substr(1), ReferenceSlot))
- return fail(Definition.Node->range(), "invalid metadata tuple reference");
- auto Reference = ResolveReference(ReferenceSlot, Definition.Node->range());
- if (!Reference)
- return importError(Definition.Node->range(), Reference.error());
- NewNode.Elements.push_back(std::move(*Reference));
- } else if (Element.starts_with("!\"") && Element.ends_with('"')) {
- std::string Value;
- std::string_view Encoded = Element.substr(2, Element.size() - 3);
- for (std::size_t Index = 0; Index < Encoded.size(); ++Index) {
- if (Encoded[Index] == '\\' && Index + 1 < Encoded.size()) {
- const char EscapedCharacter = Encoded[++Index];
- if (EscapedCharacter != '\\' && EscapedCharacter != '"')
- return fail(Definition.Node->range(), "unsupported machine metadata string escape");
- Value.push_back(EscapedCharacter);
- } else {
- Value.push_back(Encoded[Index]);
- }
- }
- NewNode.Elements.push_back(MachineMetadataElement::string(std::move(Value)));
- } else {
- return fail(Definition.Node->range(), "unsupported machine metadata tuple element");
- }
-
- if (End == Remaining.size())
- Remaining = {};
- else
- Remaining = trim(Remaining.substr(End + 1));
- }
- auto AddResult = Function.metadata().addNode(std::move(NewNode));
- if (!AddResult)
- return importError(Definition.Node->range(), AddResult.error());
- }
- return false;
-}
-
-bool MIRDocumentParser::parseStackObjects(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context) {
- auto ParseList = [this, &Function, &Context](const yaml::Node &List, bool Fixed) -> bool {
- if (requireKind(List, yaml::NodeKind::Sequence,
- Fixed ? "fixed stack objects" : "stack objects")) {
- return true;
- }
- constexpr std::array FixedKeys{
- std::string_view{"id"},
- std::string_view{"type"},
- std::string_view{"offset"},
- std::string_view{"size"},
- std::string_view{"alignment"},
- std::string_view{"stack-id"},
- std::string_view{"isImmutable"},
- std::string_view{"isAliased"},
- std::string_view{"callee-saved-register"},
- std::string_view{"callee-saved-restored"},
- std::string_view{"debug-info-variable"},
- std::string_view{"debug-info-expression"},
- std::string_view{"debug-info-location"},
- };
- constexpr std::array StackKeys{
- std::string_view{"id"},
- std::string_view{"name"},
- std::string_view{"type"},
- std::string_view{"offset"},
- std::string_view{"size"},
- std::string_view{"alignment"},
- std::string_view{"stack-id"},
- std::string_view{"callee-saved-register"},
- std::string_view{"callee-saved-restored"},
- std::string_view{"local-offset"},
- std::string_view{"debug-info-variable"},
- std::string_view{"debug-info-expression"},
- std::string_view{"debug-info-location"},
- };
- std::unordered_set<std::uint32_t> IDs;
- for (const yaml::NodeID ElementID : List.sequence()) {
- const yaml::Node *Element = node(ElementID);
- if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "stack object") ||
- (Fixed ? validateKeys(*Element, FixedKeys, "fixed stack object")
- : validateKeys(*Element, StackKeys, "stack object"))) {
- return true;
- }
- const yaml::Node *IDNode = mappingValue(*Element, "id");
- if (IDNode == nullptr)
- return fail(Element->range(), "stack object requires 'id'");
- std::uint64_t WideID = 0;
- if (unsignedValue(*IDNode, WideID, "a 32-bit stack object ID") ||
- WideID > std::numeric_limits<std::uint32_t>::max()) {
- return true;
- }
- const auto TextID = static_cast<std::uint32_t>(WideID);
- if (!IDs.insert(TextID).second)
- return fail(IDNode->range(), "duplicate stack object ID");
-
- StackObjectDesc Description;
- Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default;
- Description.Offset = 0;
- if (const yaml::Node *NameNode = mappingValue(*Element, "name")) {
- std::string_view Name;
- if (scalar(*NameNode, Name, "stack object name") || Name.empty())
- return fail(NameNode->range(), "stack object name cannot be empty");
- Description.AllocaName = std::string(Name);
- }
- if (const yaml::Node *TypeNode = mappingValue(*Element, "type")) {
- std::string_view Type;
- if (scalar(*TypeNode, Type, "stack object type"))
- return true;
- if (Type == "default")
- Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default;
- else if (Type == "spill-slot")
- Description.Kind = Fixed ? StackObjectKind::FixedSpillSlot : StackObjectKind::SpillSlot;
- else if (!Fixed && Type == "variable-sized")
- Description.Kind = StackObjectKind::VariableSized;
- else
- return fail(TypeNode->range(), "unknown stack object type '" + std::string(Type) + "'");
- }
- if (const yaml::Node *OffsetNode = mappingValue(*Element, "offset")) {
- std::int64_t Offset = 0;
- if (signedValue(*OffsetNode, Offset, "a signed stack object offset"))
- return true;
- Description.Offset = Offset;
- } else if (Fixed) {
- Description.Offset = 0;
- }
- if (Description.Kind != StackObjectKind::VariableSized) {
- const yaml::Node *SizeNode = mappingValue(*Element, "size");
- if (SizeNode == nullptr) {
- if (!Fixed)
- return fail(Element->range(), "fixed-size stack object requires 'size'");
- Description.Size = 0;
- } else {
- std::uint64_t Size = 0;
- if (unsignedValue(*SizeNode, Size, "an unsigned stack object size"))
- return true;
- Description.Size = Size;
- }
- }
- if (const yaml::Node *AlignmentNode = mappingValue(*Element, "alignment")) {
- if (alignment(*AlignmentNode, Description.Align, true))
- return true;
- }
- if (const yaml::Node *StackIDNode = mappingValue(*Element, "stack-id")) {
- std::string_view Name;
- if (scalar(*StackIDNode, Name, "stack ID"))
- return true;
- const auto Stack = parseStackIDName(Name);
- if (!Stack)
- return fail(StackIDNode->range(), "unknown stack ID '" + std::string(Name) + "'");
- Description.Stack = *Stack;
- }
- if (const yaml::Node *ImmutableNode = mappingValue(*Element, "isImmutable")) {
- if (boolean(*ImmutableNode, Description.IsImmutable))
- return true;
- }
- if (const yaml::Node *AliasedNode = mappingValue(*Element, "isAliased")) {
- if (boolean(*AliasedNode, Description.IsAliased))
- return true;
- }
- if (const yaml::Node *SavedNode = mappingValue(*Element, "callee-saved-register")) {
- PhysicalRegister Reg;
- if (physicalRegister(*SavedNode, Reg))
- return true;
- Description.CalleeSavedRegister = Reg;
- }
- if (const yaml::Node *RestoredNode = mappingValue(*Element, "callee-saved-restored")) {
- if (boolean(*RestoredNode, Description.CalleeSavedRestored))
- return true;
- }
- if (const yaml::Node *LocalNode = mappingValue(*Element, "local-offset")) {
- std::int64_t Offset = 0;
- if (signedValue(*LocalNode, Offset, "a signed local frame offset"))
- return true;
- Description.LocalOffset = Offset;
- }
- MachineVariableDebugInfo DebugInfo;
- if (parseVariableDebugInfo(*Element, Function, DebugInfo))
- return true;
- if (DebugInfo.Variable || DebugInfo.Expression || DebugInfo.Location)
- Description.DebugInfo = std::move(DebugInfo);
-
- auto Created = Function.frameInfo().createObject(std::move(Description));
- if (!Created)
- return importError(Element->range(), Created.error());
- MIFrameObjectSlot Slot{
- .TextID = TextID,
- .Index = *Created,
- .Name = std::nullopt,
- };
- if (const StackObjectDesc *CreatedDescription =
- Function.frameInfo().object(*Created)->description();
- CreatedDescription != nullptr && CreatedDescription->AllocaName) {
- Slot.Name = *CreatedDescription->AllocaName;
- }
- (Fixed ? Context.FixedStackObjects : Context.StackObjects).push_back(std::move(Slot));
- }
- return false;
- };
-
- if (const yaml::Node *Fixed = mappingValue(Root, "fixedStack")) {
- if (ParseList(*Fixed, true))
- return true;
- }
- if (const yaml::Node *Stack = mappingValue(Root, "stack")) {
- if (ParseList(*Stack, false))
- return true;
- }
- return false;
-}
-
-bool MIRDocumentParser::parseConstants(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context) {
- const yaml::Node *Constants = mappingValue(Root, "constants");
- if (Constants == nullptr)
- return false;
- if (requireKind(*Constants, yaml::NodeKind::Sequence, "constant pool"))
- return true;
- constexpr std::array Keys{
- std::string_view{"id"},
- std::string_view{"value"},
- std::string_view{"alignment"},
- std::string_view{"isTargetSpecific"},
- };
- std::vector<LLVMNamedTypeDefinition> NamedTypes;
- const std::string_view DataLayout = effectiveDataLayout();
- if (ActiveDocument->EmbeddedIR) {
- const auto Definitions = ActiveDocument->EmbeddedIR->typeDefinitions();
- NamedTypes.reserve(Definitions.size());
- for (const EmbeddedIRTypeDefinition &Definition : Definitions) {
- NamedTypes.push_back({.Name = Definition.Name,
- .Definition = Definition.Definition,
- .NumericName = Definition.NumericName});
- }
- }
- std::unordered_set<std::uint32_t> IDs;
- for (const yaml::NodeID ConstantID : Constants->sequence()) {
- const yaml::Node *Constant = node(ConstantID);
- if (Constant == nullptr ||
- requireKind(*Constant, yaml::NodeKind::Mapping, "constant pool entry") ||
- validateKeys(*Constant, Keys, "constant pool entry")) {
- return true;
- }
- const yaml::Node *IDNode = mappingValue(*Constant, "id");
- const yaml::Node *ValueNode = mappingValue(*Constant, "value");
- if (IDNode == nullptr || ValueNode == nullptr)
- return fail(Constant->range(), "constant pool entry requires 'id' and 'value'");
- std::uint64_t WideID = 0;
- if (unsignedValue(*IDNode, WideID, "a 32-bit constant pool ID") ||
- WideID > std::numeric_limits<std::uint32_t>::max()) {
- return true;
- }
- const auto TextID = static_cast<std::uint32_t>(WideID);
- if (!IDs.insert(TextID).second)
- return fail(IDNode->range(), "duplicate constant pool ID");
-
- bool IsTargetSpecific = false;
- if (const yaml::Node *TargetNode = mappingValue(*Constant, "isTargetSpecific")) {
- if (boolean(*TargetNode, IsTargetSpecific))
- return true;
- }
- if (IsTargetSpecific) {
- std::string_view ValueText;
- if (scalar(*ValueNode, ValueText, "target constant"))
- return true;
- auto TargetConstantValue = Target.mirText().parseTargetConstant(ValueText);
- if (!TargetConstantValue)
- return importError(ValueNode->range(), TargetConstantValue.error(), "mir-target-constant");
- auto Emission = TargetConstantValue->emit();
- if (!Emission)
- return importError(ValueNode->range(), Emission.error(), "mir-target-constant");
- auto RequiredAlignment = TargetConstantValue->requiredAlignment();
- if (!RequiredAlignment)
- return importError(ValueNode->range(), RequiredAlignment.error(), "mir-target-constant");
-
- MachineConstantPoolEntryDesc Description;
- Description.Size = Emission->Bytes.size();
- Description.Align = *RequiredAlignment;
- Description.Value = std::move(*TargetConstantValue);
- if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) {
- if (alignment(*AlignmentNode, Description.Align, true))
- return true;
- }
- auto Added = Function.constantPool().addEntry(std::move(Description));
- if (!Added)
- return importError(Constant->range(), Added.error());
- Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added});
- continue;
- }
-
- std::string_view ValueText;
- if (scalar(*ValueNode, ValueText, "LLVM constant"))
- return true;
- const LLVMConstantSyntaxOptions ConstantOptions{
- .DefaultPointerSizeInBits = Context.DefaultPointerSizeInBits,
- .DataLayout = DataLayout,
- .NamedTypes = NamedTypes,
- };
- auto Data = parseLLVMConstant(trim(ValueText), ConstantOptions);
- if (!Data)
- return importError(ValueNode->range(), Data.error(), "mir-llvm-constant");
- auto Size = llvmConstantStorageSize(Data->Type, ConstantOptions);
- if (!Size)
- return importError(ValueNode->range(), Size.error(), "mir-llvm-constant");
-
- MachineConstantPoolEntryDesc Description;
- Description.Size = *Size;
- Description.Value = std::move(*Data);
- if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) {
- if (alignment(*AlignmentNode, Description.Align, true))
- return true;
- } else {
- auto Preferred = llvmConstantPreferredAlignment(
- std::get<GenericConstantData>(Description.Value).Type, ConstantOptions);
- if (!Preferred)
- return importError(ValueNode->range(), Preferred.error(), "mir-llvm-constant");
- Description.Align = *Preferred;
- }
- auto Added = Function.constantPool().addEntry(std::move(Description));
- if (!Added)
- return importError(Constant->range(), Added.error());
- Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added});
- }
- return false;
-}
-
-bool MIRDocumentParser::parseFrameReference(const yaml::Node &Node, const MIParserContext &Context,
- FrameIndex &Index) {
- std::string_view Text;
- if (scalar(Node, Text, "frame index"))
- return true;
- bool Fixed = false;
- std::optional<std::uint32_t> ID = parsePrefixedID(Text, "%stack.");
- if (!ID) {
- ID = parsePrefixedID(Text, "%fixed-stack.");
- Fixed = true;
- }
- if (!ID)
- return fail(Node.range(), "frame index must use '%stack.N' or '%fixed-stack.N'");
- const auto &Slots = Fixed ? Context.FixedStackObjects : Context.StackObjects;
- const auto Found = std::ranges::find_if(
- Slots, [ID](const MIFrameObjectSlot &Slot) { return Slot.TextID == *ID; });
- if (Found == Slots.end())
- return fail(Node.range(), "reference to undefined frame object");
- Index = Found->Index;
- return false;
-}
-
-MachineBasicBlock *MIRDocumentParser::blockAt(MachineFunction &Function,
- std::uint32_t Number) const noexcept {
- for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) {
- if (Block->denseNumber() == Number)
- return Block;
- }
- return nullptr;
-}
-
-MachineInstr *MIRDocumentParser::instructionAt(MachineFunction &Function, std::uint32_t BlockNumber,
- std::uint32_t Offset) const noexcept {
- MachineBasicBlock *Block = blockAt(Function, BlockNumber);
- if (Block == nullptr)
- return nullptr;
- MachineInstr *Instruction = Block->firstInstruction();
- for (std::uint32_t Index = 0; Instruction != nullptr && Index < Offset; ++Index)
- Instruction = Instruction->next();
- return Instruction;
-}
-
-bool MIRDocumentParser::parseBlockReference(std::string_view Text, const TextRange &Range,
- MachineFunction &Function, MachineBasicBlock *&Block) {
- const auto Number = parsePrefixedID(Text, "%bb.");
- if (!Number)
- return fail(Range, "basic block reference must use the '%bb.N' MIR spelling");
- Block = blockAt(Function, *Number);
- if (Block == nullptr)
- return fail(Range, "reference to undefined machine basic block");
- return false;
-}
-
-bool MIRDocumentParser::parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function,
- std::vector<MachineFrameSaveRestorePoint> &Points) {
- if (requireKind(Node, yaml::NodeKind::Sequence, "frame save/restore points"))
- return true;
- constexpr std::array Keys{
- std::string_view{"point"},
- std::string_view{"registers"},
- };
- for (const yaml::NodeID PointID : Node.sequence()) {
- const yaml::Node *PointNode = node(PointID);
- if (PointNode == nullptr ||
- requireKind(*PointNode, yaml::NodeKind::Mapping, "frame save/restore point") ||
- validateKeys(*PointNode, Keys, "frame save/restore point")) {
- return true;
- }
- const yaml::Node *BlockNode = mappingValue(*PointNode, "point");
- if (BlockNode == nullptr)
- return fail(PointNode->range(), "frame save/restore point requires 'point'");
- std::string_view BlockText;
- MachineBasicBlock *Block = nullptr;
- if (scalar(*BlockNode, BlockText, "basic block reference") ||
- parseBlockReference(BlockText, BlockNode->range(), Function, Block)) {
- return true;
- }
- MachineFrameSaveRestorePoint Point{.Block = Block->stableID(), .Registers = {}};
- if (const yaml::Node *RegistersNode = mappingValue(*PointNode, "registers")) {
- if (requireKind(*RegistersNode, yaml::NodeKind::Sequence, "save/restore point registers")) {
- return true;
- }
- for (const yaml::NodeID RegID : RegistersNode->sequence()) {
- const yaml::Node *RegNode = node(RegID);
- PhysicalRegister Reg;
- if (RegNode == nullptr || physicalRegister(*RegNode, Reg))
- return true;
- Point.Registers.push_back(Reg);
- }
- }
- Points.push_back(std::move(Point));
- }
- return false;
-}
-
-bool MIRDocumentParser::parseFrameState(const yaml::Node &Root, MachineFunction &Function,
- const MIParserContext &Context) {
- const yaml::Node *Frame = mappingValue(Root, "frameInfo");
- if (Frame == nullptr)
- return false;
- if (requireKind(*Frame, yaml::NodeKind::Mapping, "frameInfo"))
- return true;
- constexpr std::array Keys{
- std::string_view{"isFrameAddressTaken"},
- std::string_view{"isReturnAddressTaken"},
- std::string_view{"hasStackMap"},
- std::string_view{"hasPatchPoint"},
- std::string_view{"stackSize"},
- std::string_view{"offsetAdjustment"},
- std::string_view{"maxAlignment"},
- std::string_view{"adjustsStack"},
- std::string_view{"hasCalls"},
- std::string_view{"framePointerPolicy"},
- std::string_view{"stackProtector"},
- std::string_view{"functionContext"},
- std::string_view{"maxCallFrameSize"},
- std::string_view{"cvBytesOfCalleeSavedRegisters"},
- std::string_view{"hasOpaqueSPAdjustment"},
- std::string_view{"hasVAStart"},
- std::string_view{"hasMustTailInVarArgFunc"},
- std::string_view{"hasTailCall"},
- std::string_view{"isCalleeSavedInfoValid"},
- std::string_view{"localFrameSize"},
- std::string_view{"savePoint"},
- std::string_view{"restorePoint"},
- };
- if (validateKeys(*Frame, Keys, "frameInfo"))
- return true;
-
- MachineFrameState State = Function.frameInfo().state();
- const std::array BoolBindings{
- std::pair{"isFrameAddressTaken", &State.IsFrameAddressTaken},
- std::pair{"isReturnAddressTaken", &State.IsReturnAddressTaken},
- std::pair{"hasStackMap", &State.HasStackMap},
- std::pair{"hasPatchPoint", &State.HasPatchPoint},
- std::pair{"adjustsStack", &State.AdjustsStack},
- std::pair{"hasCalls", &State.HasCalls},
- std::pair{"hasOpaqueSPAdjustment", &State.HasOpaqueSPAdjustment},
- std::pair{"hasVAStart", &State.HasVAStart},
- std::pair{"hasMustTailInVarArgFunc", &State.HasMustTailInVarArgFunc},
- std::pair{"hasTailCall", &State.HasTailCall},
- std::pair{"isCalleeSavedInfoValid", &State.IsCalleeSavedInfoValid},
- };
- for (const auto &[Key, Destination] : BoolBindings) {
- if (const yaml::Node *Value = mappingValue(*Frame, Key)) {
- if (boolean(*Value, *Destination))
- return true;
- }
- }
-
- if (const yaml::Node *Value = mappingValue(*Frame, "stackSize")) {
- if (unsignedValue(*Value, State.StackSize, "an unsigned stack size"))
- return true;
- }
- if (const yaml::Node *Value = mappingValue(*Frame, "offsetAdjustment")) {
- std::int64_t Wide = 0;
- if (signedValue(*Value, Wide, "a 32-bit signed offset adjustment") ||
- Wide < std::numeric_limits<std::int32_t>::min() ||
- Wide > std::numeric_limits<std::int32_t>::max()) {
- return fail(Value->range(), "offset adjustment is outside the 32-bit signed range");
- }
- State.OffsetAdjustment = static_cast<std::int32_t>(Wide);
- }
- if (const yaml::Node *Value = mappingValue(*Frame, "maxAlignment")) {
- std::uint64_t Bytes = 0;
- if (unsignedValue(*Value, Bytes, "an unsigned maximum alignment"))
- return true;
- if (Bytes == 0) {
- State.MaxAlignment.reset();
- } else {
- auto Parsed = Alignment::create(Bytes);
- if (!Parsed)
- return importError(Value->range(), Parsed.error());
- State.MaxAlignment = *Parsed;
- }
- }
- if (const yaml::Node *Value = mappingValue(*Frame, "framePointerPolicy")) {
- std::string_view Name;
- if (scalar(*Value, Name, "frame-pointer policy"))
- return true;
- const auto Policy = parseMachineFramePointerPolicy(Name);
- if (!Policy)
- return fail(Value->range(), "unknown frame-pointer policy '" + std::string(Name) + "'");
- State.FramePointerPolicy = *Policy;
- }
- for (const auto &[Key, Destination] : {std::pair{"stackProtector", &State.StackProtector},
- std::pair{"functionContext", &State.FunctionContext}}) {
- if (const yaml::Node *Value = mappingValue(*Frame, Key)) {
- FrameIndex Index;
- if (parseFrameReference(*Value, Context, Index))
- return true;
- *Destination = Index;
- }
- }
- if (const yaml::Node *Value = mappingValue(*Frame, "maxCallFrameSize")) {
- std::uint64_t Size = 0;
- if (unsignedValue(*Value, Size, "a 32-bit unsigned maximum call-frame size") ||
- Size > std::numeric_limits<std::uint32_t>::max()) {
- return fail(Value->range(), "maximum call-frame size is outside the 32-bit unsigned range");
- }
- State.MaxCallFrameSize = Size;
- }
- for (const auto &[Key, Destination] :
- {std::pair{"cvBytesOfCalleeSavedRegisters", &State.CVBytesOfCalleeSavedRegisters},
- std::pair{"localFrameSize", &State.LocalFrameSize}}) {
- if (const yaml::Node *Value = mappingValue(*Frame, Key)) {
- std::uint64_t Wide = 0;
- if (unsignedValue(*Value, Wide, "a 32-bit unsigned frame value") ||
- Wide > std::numeric_limits<std::uint32_t>::max()) {
- return fail(Value->range(), "frame value is outside the 32-bit unsigned range");
- }
- *Destination = static_cast<std::uint32_t>(Wide);
- }
- }
- if (const yaml::Node *Value = mappingValue(*Frame, "savePoint")) {
- if (parseSaveRestorePoints(*Value, Function, State.SavePoints))
- return true;
- }
- if (const yaml::Node *Value = mappingValue(*Frame, "restorePoint")) {
- if (parseSaveRestorePoints(*Value, Function, State.RestorePoints))
- return true;
- }
- auto StateResult = Function.frameInfo().setState(std::move(State));
- if (!StateResult)
- return importError(Frame->range(), StateResult.error());
- return false;
-}
-
-bool MIRDocumentParser::reserveJumpTables(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context,
- std::vector<PendingJumpTable> &Pending) {
- const yaml::Node *JumpTable = mappingValue(Root, "jumpTable");
- if (JumpTable == nullptr)
- return false;
- if (requireKind(*JumpTable, yaml::NodeKind::Mapping, "jump table"))
- return true;
- constexpr std::array JumpTableKeys{
- std::string_view{"kind"},
- std::string_view{"entries"},
- };
- if (validateKeys(*JumpTable, JumpTableKeys, "jump table"))
- return true;
- const yaml::Node *KindNode = mappingValue(*JumpTable, "kind");
- if (KindNode == nullptr)
- return fail(JumpTable->range(), "jump table requires 'kind'");
- std::string_view KindName;
- if (scalar(*KindNode, KindName, "jump table kind"))
- return true;
-
- JumpTableEntryEncoding Encoding;
- std::uint8_t EntrySize = 8;
- if (KindName == "block-address")
- Encoding = JumpTableEntryEncoding::BlockAddress;
- else if (KindName == "gp-rel64-block-address")
- Encoding = JumpTableEntryEncoding::GPRelative64BlockAddress;
- else if (KindName == "gp-rel32-block-address") {
- Encoding = JumpTableEntryEncoding::GPRelative32BlockAddress;
- EntrySize = 4;
- } else if (KindName == "label-difference32") {
- Encoding = JumpTableEntryEncoding::LabelDifference32;
- EntrySize = 4;
- } else if (KindName == "label-difference64")
- Encoding = JumpTableEntryEncoding::LabelDifference64;
- else if (KindName == "inline") {
- Encoding = JumpTableEntryEncoding::Inline;
- EntrySize = 4;
- } else if (KindName == "custom32") {
- Encoding = JumpTableEntryEncoding::Custom32;
- EntrySize = 4;
- } else {
- return fail(KindNode->range(), "unknown jump table kind '" + std::string(KindName) + "'");
- }
-
- const yaml::Node *Entries = mappingValue(*JumpTable, "entries");
- if (Entries == nullptr)
- return false;
- if (requireKind(*Entries, yaml::NodeKind::Sequence, "jump table entries"))
- return true;
- constexpr std::array EntryKeys{
- std::string_view{"id"},
- std::string_view{"blocks"},
- };
- std::unordered_set<std::uint32_t> IDs;
- for (const yaml::NodeID EntryID : Entries->sequence()) {
- const yaml::Node *Entry = node(EntryID);
- if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "jump table entry") ||
- validateKeys(*Entry, EntryKeys, "jump table entry")) {
- return true;
- }
- const yaml::Node *IDNode = mappingValue(*Entry, "id");
- if (IDNode == nullptr)
- return fail(Entry->range(), "jump table entry requires 'id'");
- std::uint64_t WideID = 0;
- if (unsignedValue(*IDNode, WideID, "a 32-bit jump table ID") ||
- WideID > std::numeric_limits<std::uint32_t>::max()) {
- return true;
- }
- const auto TextID = static_cast<std::uint32_t>(WideID);
- if (!IDs.insert(TextID).second)
- return fail(IDNode->range(), "duplicate jump table ID");
- auto Reserved = Function.jumpTableInfo().reserveTable();
- if (!Reserved)
- return importError(Entry->range(), Reserved.error());
- PendingJumpTable NewPending{
- .Index = *Reserved,
- .TextID = TextID,
- .Encoding = Encoding,
- .EntrySize = EntrySize,
- .Blocks = {},
- };
- if (const yaml::Node *Blocks = mappingValue(*Entry, "blocks")) {
- if (requireKind(*Blocks, yaml::NodeKind::Sequence, "jump table blocks"))
- return true;
- for (const yaml::NodeID BlockID : Blocks->sequence()) {
- const yaml::Node *BlockNode = node(BlockID);
- std::string_view Text;
- if (BlockNode == nullptr || scalar(*BlockNode, Text, "basic block reference"))
- return true;
- NewPending.Blocks.emplace_back(Text);
- }
- }
- if (NewPending.Blocks.empty())
- return fail(Entry->range(), "jump table entry must contain at least one block");
- Context.JumpTables.push_back({.TextID = TextID, .Index = *Reserved});
- Pending.push_back(std::move(NewPending));
- }
- return false;
-}
-
-bool MIRDocumentParser::defineJumpTables(MachineFunction &Function,
- std::span<const PendingJumpTable> Pending) {
- for (const PendingJumpTable &Table : Pending) {
- MachineJumpTableDesc Description;
- Description.Encoding = Table.Encoding;
- Description.EntrySize = Table.EntrySize;
- Description.Align = *Alignment::create(Table.EntrySize);
- for (const std::string &Text : Table.Blocks) {
- MachineBasicBlock *Block = nullptr;
- if (parseBlockReference(Text, Result.Source.range(0, 0), Function, Block))
- return true;
- Description.Targets.push_back(Block);
- }
- auto DefineResult =
- Function.jumpTableInfo().defineReservedTable(Table.Index, std::move(Description));
- if (!DefineResult)
- return importError(Result.Source.range(0, 0), DefineResult.error());
- }
- return false;
-}
-
-TextRange MIRDocumentParser::bodyDiagnosticRange(const yaml::Node &Body,
- const TextRange &BodyRange) const noexcept {
- if (Body.scalarStyle() != yaml::ScalarStyle::Literal)
- return Body.range();
-
- const std::size_t FirstBodyLine = Body.range().Begin.Line + 1U;
- const std::size_t LastBodyLine = Body.range().End.Line;
- if (FirstBodyLine > LastBodyLine || FirstBodyLine == 0)
- return Body.range();
-
- std::size_t Indent = 0;
- bool FoundContent = false;
- for (std::size_t Line = FirstBodyLine; Line <= LastBodyLine; ++Line) {
- const std::string_view Text = Result.Source.line(Line);
- std::size_t CurrentIndent = 0;
- while (CurrentIndent < Text.size() && Text[CurrentIndent] == ' ')
- ++CurrentIndent;
- if (CurrentIndent == Text.size())
- continue;
- Indent = CurrentIndent;
- FoundContent = true;
- break;
- }
- if (!FoundContent)
- return Body.range();
-
- const TextPosition Fallback = Body.range().Begin;
- const auto MapPosition = [this, FirstBodyLine, LastBodyLine, Indent,
- Fallback](TextPosition Position) {
- const std::size_t SourceLine = FirstBodyLine + Position.Line - 1U;
- if (Position.Line == 0 || SourceLine > LastBodyLine || SourceLine > Result.Source.lineCount()) {
- return Fallback;
- }
- const std::string_view SourceLineText = Result.Source.line(SourceLine);
- const std::size_t BodyColumn = Position.Column == 0 ? 0 : Position.Column - 1U;
- const std::size_t SourceColumn = std::min(Indent + BodyColumn, SourceLineText.size());
- const std::size_t Offset = Result.Source.lineOffsets()[SourceLine - 1U] + SourceColumn;
- return Result.Source.positionAt(Offset);
- };
- return {.Begin = MapPosition(BodyRange.Begin), .End = MapPosition(BodyRange.End)};
-}
-
-bool MIRDocumentParser::parseBody(const yaml::Node &Root, MachineFunction &Function,
- MIParserContext &Context) {
- const yaml::Node *Body = mappingValue(Root, "body");
- std::string BodyText;
- TextRange BodyRange = Root.range();
- if (Body != nullptr) {
- std::string_view Text;
- if (scalar(*Body, Text, "machine function body"))
- return true;
- BodyText = Text;
- BodyRange = Body->range();
- }
-
- if (Options.DefaultPointerSizeInBits != 0)
- Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits;
- if (!effectiveDataLayout().empty()) {
- const LLVMConstantSyntaxOptions PointerOptions = pointerLayoutOptions();
- Context.PointerSizeInBits = [PointerOptions](std::uint32_t AddressSpace) {
- return llvmPointerSizeInBits(AddressSpace, PointerOptions);
- };
- }
- Context.SyncScopes = ActiveDocument->SyncScopes;
- Context.InternSyncScope = [this](std::string_view Name) -> Expected<SyncScopeID> {
- if (Name.empty())
- return makeUnexpected(ErrorCode::ParseError, "synchronization scope name cannot be empty");
- const auto Existing =
- std::ranges::find_if(ActiveDocument->SyncScopes,
- [Name](const MISyncScopeSlot &Slot) { return Slot.Name == Name; });
- if (Existing != ActiveDocument->SyncScopes.end())
- return Existing->ID;
- if (ActiveDocument->SyncScopes.size() >=
- static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) {
- return makeUnexpected(ErrorCode::Unsupported,
- "MIR document contains too many synchronization scopes");
- }
- const auto ID =
- SyncScopeID::fromValue(static_cast<std::uint32_t>(ActiveDocument->SyncScopes.size() + 1U));
- ActiveDocument->SyncScopes.push_back({.Name = std::string(Name), .ID = ID});
- return ID;
- };
- MIParserOptions BodyOptions = Options.MachineInstructions;
- BodyOptions.VerifyAfterParse = false;
- MIParser Parser(Function, Target,
- std::string(Result.Source.name()) + ":" + std::string(Function.name()),
- std::move(BodyText), Context, BodyOptions);
- auto Parsed = Parser.parse();
- if (!Parsed) {
- if (!Parser.diagnostics().empty()) {
- const TextRange Range = Body == nullptr
- ? BodyRange
- : bodyDiagnosticRange(*Body, Parser.diagnostics().front().Range);
- return fail(Range, Parser.diagnostics().front().Message, "mir-body");
- }
- return importError(BodyRange, Parsed.error(), "mir-body");
- }
- return false;
-}
-
-bool MIRDocumentParser::convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination) {
- switch (Source.kind()) {
- case yaml::NodeKind::Null:
- Destination = MIRTargetNode::null();
- return false;
- case yaml::NodeKind::Scalar:
- Destination = MIRTargetNode::scalar(std::string(Source.scalar()));
- return false;
- case yaml::NodeKind::Sequence: {
- std::vector<MIRTargetNode> Values;
- Values.reserve(Source.sequence().size());
- for (const yaml::NodeID ElementID : Source.sequence()) {
- const yaml::Node *Element = node(ElementID);
- if (Element == nullptr)
- return fail(Source.range(), "target MIR sequence contains an invalid node");
- MIRTargetNode Value;
- if (convertTargetNode(*Element, Value))
- return true;
- Values.push_back(std::move(Value));
- }
- Destination = MIRTargetNode::sequence(std::move(Values));
- return false;
- }
- case yaml::NodeKind::Mapping: {
- std::vector<MIRTargetMappingEntry> Entries;
- Entries.reserve(Source.mapping().size());
- for (const yaml::MappingEntry &SourceEntry : Source.mapping()) {
- const yaml::Node *ValueNode = node(SourceEntry.Value);
- if (ValueNode == nullptr)
- return fail(SourceEntry.KeyRange, "target MIR mapping contains an invalid node");
- MIRTargetNode Value;
- if (convertTargetNode(*ValueNode, Value))
- return true;
- Entries.push_back({SourceEntry.Key, std::move(Value)});
- }
- Destination = MIRTargetNode::mapping(std::move(Entries));
- return false;
- }
- }
- return fail(Source.range(), "target MIR node has an unknown kind");
-}
-
-bool MIRDocumentParser::parseTargetState(const yaml::Node &Root, MachineFunction &Function) {
- const yaml::Node *StateNode = mappingValue(Root, "machineFunctionInfo");
- if (StateNode == nullptr)
- return false;
- MIRTargetNode TargetNode;
- if (convertTargetNode(*StateNode, TargetNode))
- return true;
- auto State = Target.mirText().parseFunctionState(TargetNode);
- if (!State)
- return importError(StateNode->range(), State.error(), "mir-target-state");
- auto SetResult = Function.setTargetState(std::move(*State));
- if (!SetResult)
- return importError(StateNode->range(), SetResult.error(), "mir-target-state");
- return false;
-}
-
-bool MIRDocumentParser::parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function) {
- auto ParseLocation = [this, &Function](const yaml::Node &Entry,
- MachineInstr *&Instruction) -> bool {
- const yaml::Node *BlockNode = mappingValue(Entry, "bb");
- const yaml::Node *OffsetNode = mappingValue(Entry, "offset");
- if (BlockNode == nullptr || OffsetNode == nullptr)
- return fail(Entry.range(), "instruction location requires 'bb' and 'offset'");
- std::uint64_t Block = 0;
- std::uint64_t Offset = 0;
- if (unsignedValue(*BlockNode, Block, "a 32-bit basic block number") ||
- unsignedValue(*OffsetNode, Offset, "a 32-bit instruction offset") ||
- Block > std::numeric_limits<std::uint32_t>::max() ||
- Offset > std::numeric_limits<std::uint32_t>::max()) {
- return true;
- }
- Instruction = instructionAt(Function, static_cast<std::uint32_t>(Block),
- static_cast<std::uint32_t>(Offset));
- if (Instruction == nullptr)
- return fail(Entry.range(), "instruction location is outside the machine function body");
- return false;
- };
-
- if (const yaml::Node *CallSites = mappingValue(Root, "callSites")) {
- if (requireKind(*CallSites, yaml::NodeKind::Sequence, "call-site records"))
- return true;
- constexpr std::array CallSiteKeys{
- std::string_view{"bb"},
- std::string_view{"offset"},
- std::string_view{"fwdArgRegs"},
- std::string_view{"calleeTypeIds"},
- };
- constexpr std::array ArgumentKeys{
- std::string_view{"arg"},
- std::string_view{"reg"},
- };
- for (const yaml::NodeID CallSiteID : CallSites->sequence()) {
- const yaml::Node *CallSiteNode = node(CallSiteID);
- if (CallSiteNode == nullptr ||
- requireKind(*CallSiteNode, yaml::NodeKind::Mapping, "call-site record") ||
- validateKeys(*CallSiteNode, CallSiteKeys, "call-site record")) {
- return true;
- }
- MachineInstr *Call = nullptr;
- if (ParseLocation(*CallSiteNode, Call))
- return true;
- MachineCallSiteInfo Info{
- .CallInstruction = Call->stableID(),
- .ForwardedArguments = {},
- .CalleeTypeIDs = {},
- };
- if (const yaml::Node *Arguments = mappingValue(*CallSiteNode, "fwdArgRegs")) {
- if (requireKind(*Arguments, yaml::NodeKind::Sequence, "forwarded call arguments")) {
- return true;
- }
- for (const yaml::NodeID ArgumentID : Arguments->sequence()) {
- const yaml::Node *Argument = node(ArgumentID);
- if (Argument == nullptr ||
- requireKind(*Argument, yaml::NodeKind::Mapping, "forwarded call argument") ||
- validateKeys(*Argument, ArgumentKeys, "forwarded call argument")) {
- return true;
- }
- const yaml::Node *NumberNode = mappingValue(*Argument, "arg");
- const yaml::Node *RegNode = mappingValue(*Argument, "reg");
- if (NumberNode == nullptr || RegNode == nullptr)
- return fail(Argument->range(), "forwarded call argument requires 'arg' and 'reg'");
- std::uint16_t Number = 0;
- std::uint64_t WideNumber = 0;
- PhysicalRegister Reg;
- if (unsignedValue(*NumberNode, WideNumber, "a 16-bit argument number") ||
- WideNumber > std::numeric_limits<std::uint16_t>::max() ||
- physicalRegister(*RegNode, Reg)) {
- return true;
- }
- Number = static_cast<std::uint16_t>(WideNumber);
- Info.ForwardedArguments.push_back({Reg, Number});
- }
- }
- if (const yaml::Node *TypeIDs = mappingValue(*CallSiteNode, "calleeTypeIds")) {
- if (requireKind(*TypeIDs, yaml::NodeKind::Sequence, "callee type IDs"))
- return true;
- for (const yaml::NodeID TypeID : TypeIDs->sequence()) {
- const yaml::Node *TypeNode = node(TypeID);
- std::uint64_t Value = 0;
- if (TypeNode == nullptr ||
- unsignedValue(*TypeNode, Value, "an unsigned callee type ID")) {
- return true;
- }
- Info.CalleeTypeIDs.push_back(Value);
- }
- }
- auto AddResult = Function.records().addCallSite(std::move(Info));
- if (!AddResult)
- return importError(CallSiteNode->range(), AddResult.error());
- }
- }
-
- if (const yaml::Node *CalledGlobals = mappingValue(Root, "calledGlobals")) {
- if (requireKind(*CalledGlobals, yaml::NodeKind::Sequence, "called-global records"))
- return true;
- constexpr std::array Keys{
- std::string_view{"bb"},
- std::string_view{"offset"},
- std::string_view{"callee"},
- std::string_view{"flags"},
- };
- for (const yaml::NodeID CalledID : CalledGlobals->sequence()) {
- const yaml::Node *CalledNode = node(CalledID);
- if (CalledNode == nullptr ||
- requireKind(*CalledNode, yaml::NodeKind::Mapping, "called-global record") ||
- validateKeys(*CalledNode, Keys, "called-global record")) {
- return true;
- }
- MachineInstr *Call = nullptr;
- if (ParseLocation(*CalledNode, Call))
- return true;
- const yaml::Node *CalleeNode = mappingValue(*CalledNode, "callee");
- const yaml::Node *FlagsNode = mappingValue(*CalledNode, "flags");
- if (CalleeNode == nullptr || FlagsNode == nullptr)
- return fail(CalledNode->range(), "called-global record requires 'callee' and 'flags'");
- std::string_view Callee;
- std::uint64_t WideFlags = 0;
- if (scalar(*CalleeNode, Callee, "callee name") ||
- unsignedValue(*FlagsNode, WideFlags, "32-bit called-global flags") ||
- WideFlags > std::numeric_limits<std::uint32_t>::max()) {
- return true;
- }
- if (ActiveDocument->EmbeddedIR && !ActiveDocument->EmbeddedIR->hasGlobalValue(Callee)) {
- return fail(CalleeNode->range(), "called global references an undefined embedded global");
- }
- auto AddResult =
- Function.records().addCalledGlobal({.CallInstruction = Call->stableID(),
- .Callee = std::string(Callee),
- .Flags = static_cast<std::uint32_t>(WideFlags)});
- if (!AddResult)
- return importError(CalledNode->range(), AddResult.error());
- }
- }
-
- if (const yaml::Node *Substitutions = mappingValue(Root, "debugValueSubstitutions")) {
- if (requireKind(*Substitutions, yaml::NodeKind::Sequence, "debug value substitutions")) {
- return true;
- }
- constexpr std::array Keys{
- std::string_view{"srcinst"}, std::string_view{"srcop"}, std::string_view{"dstinst"},
- std::string_view{"dstop"}, std::string_view{"subreg"},
- };
- for (const yaml::NodeID SubstitutionID : Substitutions->sequence()) {
- const yaml::Node *Substitution = node(SubstitutionID);
- if (Substitution == nullptr ||
- requireKind(*Substitution, yaml::NodeKind::Mapping, "debug value substitution") ||
- validateKeys(*Substitution, Keys, "debug value substitution")) {
- return true;
- }
- std::array<std::uint32_t, 5> Values{};
- for (std::size_t Index = 0; Index < Keys.size(); ++Index) {
- const yaml::Node *ValueNode = mappingValue(*Substitution, Keys[Index]);
- if (ValueNode == nullptr)
- return fail(Substitution->range(), "debug value substitution requires every fixed field");
- std::uint64_t Wide = 0;
- if (unsignedValue(*ValueNode, Wide, "a 32-bit debug substitution value") ||
- Wide > std::numeric_limits<std::uint32_t>::max()) {
- return true;
- }
- Values[Index] = static_cast<std::uint32_t>(Wide);
- }
- auto AddResult = Function.records().addDebugValueSubstitution(
- {.Source = {Values[0], Values[1]},
- .Destination = {Values[2], Values[3]},
- .Subregister = SubRegIndex::fromValue(Values[4])});
- if (!AddResult)
- return importError(Substitution->range(), AddResult.error());
- }
- }
-
- if (const yaml::Node *PrefetchTargets = mappingValue(Root, "prefetch-targets")) {
- if (requireKind(*PrefetchTargets, yaml::NodeKind::Sequence, "prefetch targets"))
- return true;
- for (const yaml::NodeID TargetID : PrefetchTargets->sequence()) {
- const yaml::Node *TargetNode = node(TargetID);
- std::string_view Text;
- if (TargetNode == nullptr || scalar(*TargetNode, Text, "prefetch target"))
- return true;
- Text = trim(Text);
- if (!Text.starts_with("bb_id"))
- return fail(TargetNode->range(), "prefetch target must start with 'bb_id'");
- Text = trim(Text.substr(std::string_view("bb_id").size()));
- std::array<std::uint32_t, 3> Values{};
- for (std::size_t Index = 0; Index < Values.size(); ++Index) {
- const std::size_t Comma = Text.find(',');
- const std::string_view Part = Index + 1 == Values.size() ? Text : Text.substr(0, Comma);
- if ((Index + 1 != Values.size() && Comma == std::string_view::npos) ||
- !parseUnsigned32(trim(Part), Values[Index])) {
- return fail(TargetNode->range(), "prefetch target has invalid numeric fields");
- }
- if (Index + 1 != Values.size())
- Text = trim(Text.substr(Comma + 1));
- }
- auto AddResult = Function.records().addPrefetchTarget(
- {.Block = {Values[0], Values[1]}, .CallsiteIndex = Values[2]});
- if (!AddResult)
- return importError(TargetNode->range(), AddResult.error());
- }
- }
-
- if (const yaml::Node *EntryValues = mappingValue(Root, "entry_values")) {
- if (requireKind(*EntryValues, yaml::NodeKind::Sequence, "entry values"))
- return true;
- constexpr std::array Keys{
- std::string_view{"entry-value-register"},
- std::string_view{"debug-info-variable"},
- std::string_view{"debug-info-expression"},
- std::string_view{"debug-info-location"},
- };
- for (const yaml::NodeID EntryID : EntryValues->sequence()) {
- const yaml::Node *Entry = node(EntryID);
- if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "entry value") ||
- validateKeys(*Entry, Keys, "entry value")) {
- return true;
- }
- const yaml::Node *RegNode = mappingValue(*Entry, "entry-value-register");
- const yaml::Node *VariableNode = mappingValue(*Entry, "debug-info-variable");
- const yaml::Node *ExpressionNode = mappingValue(*Entry, "debug-info-expression");
- const yaml::Node *LocationNode = mappingValue(*Entry, "debug-info-location");
- if (RegNode == nullptr || VariableNode == nullptr || ExpressionNode == nullptr ||
- LocationNode == nullptr) {
- return fail(Entry->range(), "entry value requires all four fixed fields");
- }
- PhysicalRegister Reg;
- if (physicalRegister(*RegNode, Reg))
- return true;
- MachineVariableDebugInfo DebugInfo;
- if (parseVariableDebugInfo(*Entry, Function, DebugInfo))
- return true;
- if (!DebugInfo.Variable || !DebugInfo.Expression || !DebugInfo.Location) {
- return fail(Entry->range(), "entry value debug metadata cannot be empty",
- "mir-debug-metadata");
- }
- auto AddResult =
- Function.records().addEntryValue({.Reg = Reg, .DebugInfo = std::move(DebugInfo)});
- if (!AddResult)
- return importError(Entry->range(), AddResult.error());
- }
- }
- return false;
-}
-
-Expected<void> verifyMIRDocumentReferences(const MIRDocument &Document,
- const MachineFunction &Function, bool UseEmbeddedIR) {
- const EmbeddedIRReferenceIndex *Embedded = UseEmbeddedIR ? Document.embeddedIR() : nullptr;
- const EmbeddedIRFunction *IRFunction = nullptr;
- if (Embedded != nullptr) {
- IRFunction = Embedded->function(Function.name());
- if (IRFunction == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- "machine function has no matching embedded LLVM IR function");
- }
- }
-
- const auto ValidateGlobal = [Embedded](std::string_view Name,
- std::string_view Description) -> Expected<void> {
- if (Embedded == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) +
- " requires embedded LLVM IR to resolve global '" +
- std::string(Name) + "'");
- }
- if (Embedded->hasGlobalValue(Name))
- return {};
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) +
- " references an undefined embedded LLVM IR global '" +
- std::string(Name) + "'");
- };
- const auto ValidateBlockAddress = [Embedded](std::string_view FunctionName,
- std::string_view BlockName,
- std::string_view Description) -> Expected<void> {
- if (Embedded == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) +
- " requires embedded LLVM IR to resolve a block address");
- }
- const EmbeddedIRFunction *AddressFunction = Embedded->function(FunctionName);
- if (AddressFunction == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) +
- " references an undefined embedded LLVM IR function '" +
- std::string(FunctionName) + "'");
- }
- if (AddressFunction->block(BlockName) == nullptr) {
- return makeUnexpected(
- ErrorCode::VerificationError,
- std::string(Description) + " references an undefined embedded LLVM IR block '" +
- std::string(BlockName) + "' in function '" + std::string(FunctionName) + "'");
- }
- return {};
- };
- const auto ValidateIRValue = [IRFunction](std::string_view Name,
- std::string_view Description) -> Expected<void> {
- if (IRFunction == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) +
- " requires embedded LLVM IR to resolve IR value '%" +
- std::string(Name) + "'");
- }
- if (IRFunction->value(Name) != nullptr)
- return {};
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) +
- " references an undefined embedded LLVM IR value '%" +
- std::string(Name) + "'");
- };
- const auto ValidateMetadata = [Embedded](MachineMetadataReference Reference,
- std::initializer_list<std::string_view> ExpectedTypes,
- std::string_view Description) -> Expected<void> {
- if (!Reference.isValid()) {
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) + " has an invalid metadata reference");
- }
- if (Reference.scope() != MachineMetadataScope::EmbeddedIR)
- return {};
- if (Embedded == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) + " requires embedded LLVM IR metadata !" +
- std::to_string(Reference.slot()));
- }
- const EmbeddedIRMetadataDefinition *Definition =
- Embedded->metadata(std::to_string(Reference.slot()));
- if (Definition == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) +
- " references undefined embedded LLVM IR metadata !" +
- std::to_string(Reference.slot()));
- }
- if (ExpectedTypes.size() == 0 ||
- std::ranges::find(ExpectedTypes, Definition->DynamicType) != ExpectedTypes.end()) {
- return {};
- }
- std::string Expected;
- for (const std::string_view Type : ExpectedTypes) {
- if (!Expected.empty())
- Expected.append(" or ");
- Expected.append(Type);
- }
- return makeUnexpected(ErrorCode::VerificationError,
- std::string(Description) + " requires " + Expected + " metadata, but !" +
- std::to_string(Reference.slot()) + " is " +
- (Definition->DynamicType.empty()
- ? std::string("an unclassified metadata node")
- : Definition->DynamicType));
- };
- const auto ValidateMetadataValue =
- [&ValidateMetadata](const MachineMetadataValue &Value,
- std::string_view Description) -> Expected<void> {
- const MachineMetadataReference *Reference = Value.reference();
- return Reference == nullptr ? Expected<void>{} : ValidateMetadata(*Reference, {}, Description);
- };
- const auto ValidateLocation =
- [&ValidateMetadata](const MachineDebugLocation &Location,
- std::string_view Description) -> Expected<void> {
- if (const MachineMetadataReference *Reference = Location.reference())
- return ValidateMetadata(*Reference, {"DILocation"}, Description);
- const MachineDILocation *Inline = Location.inlineLocation();
- if (Inline == nullptr)
- return {};
- for (const MachineDILocationFrame &Frame : Inline->inlineChain()) {
- auto Verification =
- ValidateMetadata(Frame.Scope, {"DISubprogram", "DILexicalBlock", "DILexicalBlockFile"},
- "inline DILocation scope");
- if (!Verification)
- return Verification;
- }
- if (Inline->terminalInlinedAt()) {
- return ValidateMetadata(*Inline->terminalInlinedAt(), {"DILocation"},
- "inline DILocation inlinedAt");
- }
- return {};
- };
- const auto ValidateVariableDebugInfo =
- [&ValidateMetadata, &ValidateMetadataValue,
- &ValidateLocation](const MachineVariableDebugInfo &DebugInfo) -> Expected<void> {
- if (DebugInfo.Variable) {
- auto Verification =
- ValidateMetadata(*DebugInfo.Variable, {"DILocalVariable"}, "debug variable");
- if (!Verification)
- return Verification;
- }
- if (DebugInfo.Expression) {
- if (const MachineMetadataReference *Reference = DebugInfo.Expression->reference();
- Reference != nullptr) {
- auto Verification = ValidateMetadata(*Reference, {"DIExpression"}, "debug expression");
- if (!Verification)
- return Verification;
- }
- auto Verification = ValidateMetadataValue(*DebugInfo.Expression, "debug expression");
- if (!Verification)
- return Verification;
- }
- if (DebugInfo.Location)
- return ValidateLocation(*DebugInfo.Location, "debug location");
- return {};
- };
- const auto ValidateGenericType = [Embedded](const auto &Self,
- const GenericConstantType &Type) -> Expected<void> {
- if (Type.Kind == GenericConstantTypeKind::Named) {
- if (Embedded == nullptr) {
- return makeUnexpected(
- ErrorCode::VerificationError,
- "generic constant type requires embedded LLVM IR to resolve named type '%" + Type.Name +
- "'");
- }
- if (Embedded->typeDefinition(Type.Name) == nullptr) {
- return makeUnexpected(
- ErrorCode::VerificationError,
- "generic constant type references an undefined embedded LLVM IR named type '%" +
- Type.Name + "'");
- }
- }
- for (const GenericConstantType &Element : Type.Elements) {
- auto Verification = Self(Self, Element);
- if (!Verification)
- return Verification;
- }
- return {};
- };
- const auto ValidateGenericConstant =
- [&ValidateBlockAddress, &ValidateGenericType,
- &ValidateGlobal](const auto &Self, const GenericConstantData &Constant) -> Expected<void> {
- auto TypeVerification = ValidateGenericType(ValidateGenericType, Constant.Type);
- if (!TypeVerification)
- return TypeVerification;
- if (Constant.SourceElementType) {
- TypeVerification = ValidateGenericType(ValidateGenericType, *Constant.SourceElementType);
- if (!TypeVerification)
- return TypeVerification;
- }
- switch (Constant.Kind) {
- case GenericConstantKind::GlobalAddress:
- case GenericConstantKind::DSOLocalEquivalent:
- case GenericConstantKind::NoCFI:
- if (auto Verification = ValidateGlobal(Constant.Symbol, "generic constant"); !Verification)
- return Verification;
- break;
- case GenericConstantKind::BlockAddress:
- if (auto Verification = ValidateBlockAddress(Constant.Symbol, Constant.Block,
- "generic constant blockaddress");
- !Verification) {
- return Verification;
- }
- break;
- default:
- break;
- }
- for (const GenericConstantData &Element : Constant.Elements) {
- auto Verification = Self(Self, Element);
- if (!Verification)
- return Verification;
- }
- return {};
- };
-
- for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
- Block = Block->next()) {
- if (const auto Name = Block->attributes().irBlockName(); Name) {
- if (IRFunction == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- "machine basic block requires embedded LLVM IR to resolve block '" +
- std::string(*Name) + "'");
- }
- if (IRFunction->block(*Name) == nullptr) {
- return makeUnexpected(
- ErrorCode::VerificationError,
- "machine basic block references an undefined embedded LLVM IR block '" +
- std::string(*Name) + "'");
- }
- }
- }
- for (const StackObject &Object : Function.frameInfo().objects()) {
- const StackObjectDesc *Description = Object.description();
- if (Description == nullptr || !Description->AllocaName)
- continue;
- if (IRFunction == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- "stack object requires embedded LLVM IR to resolve alloca '" +
- *Description->AllocaName + "'");
- }
- if (IRFunction->alloca(*Description->AllocaName) == nullptr) {
- return makeUnexpected(ErrorCode::VerificationError,
- "stack object references an undefined embedded LLVM IR alloca '" +
- *Description->AllocaName + "'");
- }
- }
-
- for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) {
- const MachineConstantPoolEntryDesc *Description = Entry.description();
- if (Description == nullptr)
- continue;
- if (const auto *Generic = std::get_if<GenericConstantData>(&Description->Value)) {
- auto Verification = ValidateGenericConstant(ValidateGenericConstant, *Generic);
- if (!Verification)
- return Verification;
- } else if (const auto *Address = std::get_if<BlockAddressConstant>(&Description->Value)) {
- auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName,
- "constant-pool block address");
- if (!Verification)
- return Verification;
- }
- }
-
- for (const MachineMetadataNode &Node : Function.metadata().nodes()) {
- for (const MachineMetadataElement &Element : Node.Elements) {
- const MachineMetadataReference *Reference = Element.reference();
- if (Reference == nullptr)
- continue;
- auto Verification = ValidateMetadata(*Reference, {}, "machine metadata node");
- if (!Verification)
- return Verification;
- }
- }
-
- for (const StackObject &Object : Function.frameInfo().objects()) {
- const StackObjectDesc *Description = Object.description();
- if (Description == nullptr || !Description->DebugInfo)
- continue;
- auto Verification = ValidateVariableDebugInfo(*Description->DebugInfo);
- if (!Verification)
- return Verification;
- }
- for (const MachineEntryValueInfo &Entry : Function.records().entryValues()) {
- auto Verification = ValidateVariableDebugInfo(Entry.DebugInfo);
- if (!Verification)
- return Verification;
- }
- for (const MachineCalledGlobal &Called : Function.records().calledGlobals()) {
- auto Verification = ValidateGlobal(Called.Callee, "called-global record");
- if (!Verification)
- return Verification;
- }
-
- for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
- Block = Block->next()) {
- for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;
- Instruction = Instruction->next()) {
- if (Instruction->debugLocation()) {
- auto Verification =
- ValidateLocation(*Instruction->debugLocation(), "instruction debug location");
- if (!Verification)
- return Verification;
- }
- const std::array InstructionMetadata{
- Instruction->heapAllocMarker(),
- Instruction->pcSections(),
- Instruction->mmra(),
- };
- for (const auto &Reference : InstructionMetadata) {
- if (!Reference)
- continue;
- auto Verification = ValidateMetadata(*Reference, {}, "instruction metadata attachment");
- if (!Verification)
- return Verification;
- }
-
- for (const MachineOperand &Operand : Instruction->operands()) {
- if (const auto *Global = Operand.getIf<GlobalSymbolOperand>(); Global != nullptr) {
- auto Verification = ValidateGlobal(Global->Name, "global-symbol operand");
- if (!Verification)
- return Verification;
- }
- if (const auto *Address = Operand.getIf<BlockAddressOperand>(); Address != nullptr) {
- auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName,
- "block-address operand");
- if (!Verification)
- return Verification;
- }
- if (const auto *Metadata = Operand.getIf<MetadataOperand>(); Metadata != nullptr) {
- auto Verification = ValidateMetadataValue(Metadata->Value, "metadata operand");
- if (!Verification)
- return Verification;
- }
- }
-
- for (const MachineMemOperand *Memory : Instruction->memOperands()) {
- const MachineMemOperandDesc &Description = Memory->description();
- const std::optional<std::string_view> BaseName = Description.Base.symbolName();
- switch (Description.Base.kind()) {
- case MemoryBase::Kind::GlobalSymbol:
- case MemoryBase::Kind::CallEntryGlobal:
- if (BaseName) {
- auto Verification = ValidateGlobal(*BaseName, "machine memory base");
- if (!Verification)
- return Verification;
- }
- break;
- case MemoryBase::Kind::IRValue:
- if (BaseName) {
- auto Verification = ValidateIRValue(*BaseName, "machine memory base");
- if (!Verification)
- return Verification;
- }
- break;
- default:
- break;
- }
- const std::array MetadataReferences{
- Description.TBAA, Description.AliasScope,
- Description.NoAlias, Description.NoAliasAddressSpace,
- Description.RangeMetadata,
- };
- for (const auto &Reference : MetadataReferences) {
- if (!Reference)
- continue;
- auto Verification = ValidateMetadata(*Reference, {}, "machine memory operand metadata");
- if (!Verification)
- return Verification;
- }
- if (Description.SyncScope.value() == 0)
- continue;
- const auto Scope =
- std::ranges::find_if(Document.syncScopes(), [&Description](const auto &Slot) {
- return Slot.ID == Description.SyncScope;
- });
- if (Scope == Document.syncScopes().end()) {
- return makeUnexpected(
- ErrorCode::VerificationError,
- "machine memory operand synchronization scope has no document name");
- }
- }
- }
- }
- return {};
-}
-
-bool MIRDocumentParser::validateDocumentReferences(const MachineFunction &Function) {
- auto Verification = verifyMIRDocumentReferences(*ActiveDocument, Function, true);
- if (Verification)
- return false;
- return importError(Result.Source.range(0, 0), Verification.error());
-}
-
-bool MIRDocumentParser::parseFunction(const yaml::Node &Root, MIRDocument &Document) {
- constexpr std::array Keys{
- std::string_view{"name"},
- std::string_view{"alignment"},
- std::string_view{"exposesReturnsTwice"},
- std::string_view{"legalized"},
- std::string_view{"regBankSelected"},
- std::string_view{"selected"},
- std::string_view{"failedISel"},
- std::string_view{"tracksRegLiveness"},
- std::string_view{"hasWinCFI"},
- std::string_view{"noPhis"},
- std::string_view{"isSSA"},
- std::string_view{"noVRegs"},
- std::string_view{"hasFakeUses"},
- std::string_view{"callsEHReturn"},
- std::string_view{"callsUnwindInit"},
- std::string_view{"hasEHContTarget"},
- std::string_view{"hasEHScopes"},
- std::string_view{"hasEHFunclets"},
- std::string_view{"isOutlined"},
- std::string_view{"debugInstrRef"},
- std::string_view{"failsVerification"},
- std::string_view{"tracksDebugUserValues"},
- std::string_view{"registers"},
- std::string_view{"liveins"},
- std::string_view{"calleeSavedRegisters"},
- std::string_view{"frameInfo"},
- std::string_view{"fixedStack"},
- std::string_view{"stack"},
- std::string_view{"entry_values"},
- std::string_view{"callSites"},
- std::string_view{"debugValueSubstitutions"},
- std::string_view{"constants"},
- std::string_view{"machineFunctionInfo"},
- std::string_view{"jumpTable"},
- std::string_view{"machineMetadataNodes"},
- std::string_view{"calledGlobals"},
- std::string_view{"prefetch-targets"},
- std::string_view{"body"},
- };
- if (validateKeys(Root, Keys, "machine function"))
- return true;
- const yaml::Node *NameNode = mappingValue(Root, "name");
- if (NameNode == nullptr)
- return fail(Root.range(), "machine function requires 'name'");
- std::string_view Name;
- if (scalar(*NameNode, Name, "machine function name") || Name.empty())
- return fail(NameNode->range(), "machine function name cannot be empty");
-
- if (Document.EmbeddedIR) {
- const EmbeddedIRFunction *IRFunction = Document.EmbeddedIR->function(Name);
- if (IRFunction == nullptr)
- return fail(NameNode->range(),
- "machine function has no corresponding embedded LLVM IR function");
- }
-
- auto Created = Document.Unit.createMachineFunction(std::string(Name));
- if (!Created)
- return importError(NameNode->range(), Created.error());
- MachineFunction &Function = **Created;
- ActiveFunction = &Function;
-
- MIRFunctionDocumentInfo DocumentInfo{
- .Function = Function.stableID(),
- .NoPHIs = std::nullopt,
- .IsSSA = std::nullopt,
- .NoVRegs = std::nullopt,
- .HasFakeUses = std::nullopt,
- };
- MIParserContext Context;
- Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits;
-
- std::vector<PendingJumpTable> PendingJumpTables;
- if (parseFunctionScalars(Root, Function, DocumentInfo) ||
- parseMetadata(Root, Function, Context) || parseRegisters(Root, Function, Context) ||
- parseStackObjects(Root, Function, Context) || parseConstants(Root, Function, Context) ||
- reserveJumpTables(Root, Function, Context, PendingJumpTables) ||
- parseBody(Root, Function, Context) || defineJumpTables(Function, PendingJumpTables) ||
- recomputeProperties(Root, Function, DocumentInfo) ||
- parseFrameState(Root, Function, Context) || parseTargetState(Root, Function) ||
- parseFunctionRecords(Root, Function) || validateDocumentReferences(Function)) {
- return true;
- }
-
- if (Options.VerifyMachineFunctions) {
- auto Verified = MachineVerifier::verify(Function, Target);
- if (!Verified)
- return importError(Root.range(), Verified.error(), "mir-verifier");
- }
- Document.FunctionInfo.push_back(DocumentInfo);
- ActiveFunction = nullptr;
- return false;
-}
MIRDocument::MIRDocument() = default;
MIRDocument::MIRDocument(CompilationUnit Unit) : Unit(std::move(Unit)) {}
diff --git a/backend/lib/MIR/MIRDocumentParser.cpp b/backend/lib/MIR/MIRDocumentParser.cpp
new file mode 100644
index 0000000..defcce9
--- /dev/null
+++ b/backend/lib/MIR/MIRDocumentParser.cpp
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "MIRDocumentParser.h"
+
+#include "MIRDocumentReferenceValidation.h"
+#include "MIRMetadataTextParser.h"
+#include "MIRParserUtilities.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionAttributes.h"
+#include "rcc/CodeGen/MachineFunctionProperties.h"
+#include "rcc/CodeGen/MachineFunctionRecords.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/MIR/LLVMConstantSyntax.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::mir {
+
+using parser_detail::parseBool;
+using parser_detail::parsePrefixedID;
+using parser_detail::parseSigned;
+using parser_detail::parseUnsigned;
+using parser_detail::parseUnsigned32;
+using parser_detail::trim;
+
+bool MIRDocumentParser::fail(const TextRange &Range, std::string Message, std::string Category) {
+ if (Result.Diagnostics.empty()) {
+ if (ActiveFunction != nullptr)
+ Message = "function '" + std::string(ActiveFunction->name()) + "': " + std::move(Message);
+ Result.Diagnostics.push_back(TextDiagnostic{
+ .Severity = DiagnosticSeverity::Error,
+ .Category = std::move(Category),
+ .Message = std::move(Message),
+ .SourceName = std::string(Result.Source.name()),
+ .Range = Range,
+ });
+ }
+ return true;
+}
+
+bool MIRDocumentParser::importError(const TextRange &Range, const Error &Failure,
+ std::string Category) {
+ return fail(Range, std::string(Failure.message()), std::move(Category));
+}
+
+const yaml::Node *MIRDocumentParser::mappingValue(const yaml::Node &Mapping,
+ std::string_view Key) const noexcept {
+ const auto Value = Mapping.mappingValue(Key);
+ return Value ? node(*Value) : nullptr;
+}
+
+std::string_view MIRDocumentParser::effectiveDataLayout() const noexcept {
+ if (ActiveDocument != nullptr) {
+ if (const EmbeddedIRReferenceIndex *Embedded = ActiveDocument->embeddedIR()) {
+ if (const auto DataLayout = Embedded->dataLayout())
+ return *DataLayout;
+ }
+ }
+ return Options.DataLayout;
+}
+
+LLVMConstantSyntaxOptions MIRDocumentParser::pointerLayoutOptions() const noexcept {
+ return {
+ .DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits,
+ .DataLayout = effectiveDataLayout(),
+ .NamedTypes = {},
+ };
+}
+
+bool MIRDocumentParser::requireKind(const yaml::Node &Node, yaml::NodeKind Kind,
+ std::string_view Description) {
+ if (Node.kind() == Kind)
+ return false;
+ return fail(Node.range(), std::string(Description) + " has the wrong YAML node kind");
+}
+
+bool MIRDocumentParser::validateKeys(const yaml::Node &Mapping,
+ std::span<const std::string_view> Keys,
+ std::string_view Description) {
+ for (const yaml::MappingEntry &Entry : Mapping.mapping()) {
+ if (std::ranges::find(Keys, Entry.Key) == Keys.end()) {
+ return fail(Entry.KeyRange,
+ "unknown " + std::string(Description) + " key '" + Entry.Key + "'");
+ }
+ }
+ return false;
+}
+
+bool MIRDocumentParser::scalar(const yaml::Node &Node, std::string_view &Value,
+ std::string_view Description) {
+ if (requireKind(Node, yaml::NodeKind::Scalar, Description))
+ return true;
+ Value = Node.scalar();
+ return false;
+}
+
+bool MIRDocumentParser::boolean(const yaml::Node &Node, bool &Value) {
+ std::string_view Text;
+ if (scalar(Node, Text, "boolean") || !parseBool(Text, Value))
+ return fail(Node.range(), "expected 'true' or 'false'");
+ return false;
+}
+
+bool MIRDocumentParser::unsignedValue(const yaml::Node &Node, std::uint64_t &Value,
+ std::string_view Description) {
+ std::string_view Text;
+ if (scalar(Node, Text, Description) || !parseUnsigned(Text, Value))
+ return fail(Node.range(), "expected " + std::string(Description));
+ return false;
+}
+
+bool MIRDocumentParser::signedValue(const yaml::Node &Node, std::int64_t &Value,
+ std::string_view Description) {
+ std::string_view Text;
+ if (scalar(Node, Text, Description) || !parseSigned(Text, Value))
+ return fail(Node.range(), "expected " + std::string(Description));
+ return false;
+}
+
+bool MIRDocumentParser::alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero) {
+ std::uint64_t Bytes = 0;
+ if (unsignedValue(Node, Bytes, "an unsigned alignment"))
+ return true;
+ if (Bytes == 0 && AllowZero) {
+ Value = Alignment{};
+ return false;
+ }
+ auto Parsed = Alignment::create(Bytes);
+ if (!Parsed)
+ return importError(Node.range(), Parsed.error());
+ Value = *Parsed;
+ return false;
+}
+
+bool MIRDocumentParser::physicalRegister(std::string_view Text, const TextRange &Range,
+ PhysicalRegister &Reg) {
+ Text = trim(Text);
+ if (!Text.starts_with('$') || Text.size() == 1)
+ return fail(Range, "physical register must use the '$name' MIR spelling");
+ const PhysicalRegisterDesc *Description = Target.registers().physicalRegister(Text.substr(1));
+ if (Description == nullptr)
+ return fail(Range, "unknown physical register '" + std::string(Text) + "'");
+ Reg = Description->Reg;
+ return false;
+}
+
+bool MIRDocumentParser::physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg) {
+ std::string_view Text;
+ return scalar(Node, Text, "physical register") || physicalRegister(Text, Node.range(), Reg);
+}
+
+bool MIRDocumentParser::virtualRegister(std::string_view Text, const TextRange &Range,
+ const MIParserContext &Context, Register &Reg) {
+ const auto ID = parsePrefixedID(Text, "%");
+ if (!ID)
+ return fail(Range, "virtual register must use the '%N' MIR spelling");
+ const auto Found =
+ std::ranges::find_if(Context.VirtualRegisters,
+ [ID](const MIVirtualRegisterSlot &Slot) { return Slot.TextID == *ID; });
+ if (Found == Context.VirtualRegisters.end())
+ return fail(Range, "reference to undefined virtual register '%" + std::to_string(*ID) + "'");
+ Reg = Found->Reg;
+ return false;
+}
+
+MIRDocumentParseResult MIRDocumentParser::run() {
+ yaml::ParseResult YAMLResult = yaml::parse(std::string(Result.Source.name()),
+ std::string(Result.Source.text()), Options.YAML);
+ if (!YAMLResult.succeeded()) {
+ const auto Diagnostics = YAMLResult.diagnostics();
+ Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end());
+ return std::move(Result);
+ }
+ Stream = YAMLResult.stream();
+ if (Stream == nullptr || Stream->documents().empty()) {
+ static_cast<void>(fail(Result.Source.range(0, 0), "MIR stream contains no YAML documents"));
+ return std::move(Result);
+ }
+
+ MIRDocument Staging;
+ ActiveDocument = &Staging;
+ std::size_t FirstFunction = 0;
+ const yaml::Node *FirstRoot = node(Stream->documents().front().Root);
+ if (FirstRoot != nullptr && FirstRoot->kind() == yaml::NodeKind::Scalar) {
+ EmbeddedIRIndexResult Embedded = indexEmbeddedIR(
+ std::string(Result.Source.name()) + ":embedded-ir", std::string(FirstRoot->scalar()));
+ if (!Embedded.succeeded()) {
+ const auto Diagnostics = Embedded.diagnostics();
+ Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end());
+ return std::move(Result);
+ }
+ Staging.EmbeddedIR = std::move(Embedded).takeIndex();
+ FirstFunction = 1;
+ }
+
+ if (const EmbeddedIRReferenceIndex *Embedded = Staging.embeddedIR()) {
+ if (const auto DataLayout = Embedded->dataLayout();
+ DataLayout && !Options.DataLayout.empty() && *DataLayout != Options.DataLayout) {
+ static_cast<void>(fail(Stream->documents().front().Range,
+ "explicit DataLayout does not match embedded LLVM IR data layout"));
+ return std::move(Result);
+ }
+ }
+ if (!effectiveDataLayout().empty()) {
+ auto PointerSize = llvmPointerSizeInBits(0, pointerLayoutOptions());
+ if (!PointerSize) {
+ static_cast<void>(
+ importError(Stream->documents().front().Range, PointerSize.error(), "mir-data-layout"));
+ return std::move(Result);
+ }
+ }
+
+ if (FirstFunction == Stream->documents().size()) {
+ static_cast<void>(fail(Stream->documents().front().Range,
+ "MIR stream must contain at least one machine function document"));
+ return std::move(Result);
+ }
+
+ for (std::size_t Index = FirstFunction; Index < Stream->documents().size(); ++Index) {
+ const yaml::Node *Root = node(Stream->documents()[Index].Root);
+ if (Root == nullptr || requireKind(*Root, yaml::NodeKind::Mapping, "machine function") ||
+ parseFunction(*Root, Staging)) {
+ return std::move(Result);
+ }
+ }
+
+ auto UnitResult = Staging.Unit.verify();
+ if (!UnitResult) {
+ static_cast<void>(
+ fail(Stream->documents().back().Range, "parsed compilation unit failed verification: " +
+ std::string(UnitResult.error().message())));
+ return std::move(Result);
+ }
+ ActiveDocument = nullptr;
+ Result.Document = std::move(Staging);
+ return std::move(Result);
+}
+
+bool MIRDocumentParser::parseFunction(const yaml::Node &Root, MIRDocument &Document) {
+ constexpr std::array Keys{
+ std::string_view{"name"},
+ std::string_view{"alignment"},
+ std::string_view{"exposesReturnsTwice"},
+ std::string_view{"legalized"},
+ std::string_view{"regBankSelected"},
+ std::string_view{"selected"},
+ std::string_view{"failedISel"},
+ std::string_view{"tracksRegLiveness"},
+ std::string_view{"hasWinCFI"},
+ std::string_view{"noPhis"},
+ std::string_view{"isSSA"},
+ std::string_view{"noVRegs"},
+ std::string_view{"hasFakeUses"},
+ std::string_view{"callsEHReturn"},
+ std::string_view{"callsUnwindInit"},
+ std::string_view{"hasEHContTarget"},
+ std::string_view{"hasEHScopes"},
+ std::string_view{"hasEHFunclets"},
+ std::string_view{"isOutlined"},
+ std::string_view{"debugInstrRef"},
+ std::string_view{"failsVerification"},
+ std::string_view{"tracksDebugUserValues"},
+ std::string_view{"registers"},
+ std::string_view{"liveins"},
+ std::string_view{"calleeSavedRegisters"},
+ std::string_view{"frameInfo"},
+ std::string_view{"fixedStack"},
+ std::string_view{"stack"},
+ std::string_view{"entry_values"},
+ std::string_view{"callSites"},
+ std::string_view{"debugValueSubstitutions"},
+ std::string_view{"constants"},
+ std::string_view{"machineFunctionInfo"},
+ std::string_view{"jumpTable"},
+ std::string_view{"machineMetadataNodes"},
+ std::string_view{"calledGlobals"},
+ std::string_view{"prefetch-targets"},
+ std::string_view{"body"},
+ };
+ if (validateKeys(Root, Keys, "machine function"))
+ return true;
+ const yaml::Node *NameNode = mappingValue(Root, "name");
+ if (NameNode == nullptr)
+ return fail(Root.range(), "machine function requires 'name'");
+ std::string_view Name;
+ if (scalar(*NameNode, Name, "machine function name") || Name.empty())
+ return fail(NameNode->range(), "machine function name cannot be empty");
+
+ if (Document.EmbeddedIR) {
+ const EmbeddedIRFunction *IRFunction = Document.EmbeddedIR->function(Name);
+ if (IRFunction == nullptr)
+ return fail(NameNode->range(),
+ "machine function has no corresponding embedded LLVM IR function");
+ }
+
+ auto Created = Document.Unit.createMachineFunction(std::string(Name));
+ if (!Created)
+ return importError(NameNode->range(), Created.error());
+ MachineFunction &Function = **Created;
+ ActiveFunction = &Function;
+
+ MIRFunctionDocumentInfo DocumentInfo{
+ .Function = Function.stableID(),
+ .NoPHIs = std::nullopt,
+ .IsSSA = std::nullopt,
+ .NoVRegs = std::nullopt,
+ .HasFakeUses = std::nullopt,
+ };
+ MIParserContext Context;
+ Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits;
+
+ std::vector<PendingJumpTable> PendingJumpTables;
+ if (parseFunctionScalars(Root, Function, DocumentInfo) ||
+ parseMetadata(Root, Function, Context) || parseRegisters(Root, Function, Context) ||
+ parseStackObjects(Root, Function, Context) || parseConstants(Root, Function, Context) ||
+ reserveJumpTables(Root, Function, Context, PendingJumpTables) ||
+ parseBody(Root, Function, Context) || defineJumpTables(Function, PendingJumpTables) ||
+ parseFrameState(Root, Function, Context) ||
+ recomputeProperties(Root, Function, DocumentInfo) || parseTargetState(Root, Function) ||
+ parseFunctionRecords(Root, Function) || validateDocumentReferences(Function)) {
+ return true;
+ }
+
+ if (Options.VerifyMachineFunctions) {
+ auto Verified = MachineVerifier::verify(Function, Target);
+ if (!Verified)
+ return importError(Root.range(), Verified.error(), "mir-verifier");
+ }
+ Document.FunctionInfo.push_back(DocumentInfo);
+ ActiveFunction = nullptr;
+ return false;
+}
+
+} // namespace rcc::mir
diff --git a/backend/lib/MIR/MIRDocumentParser.h b/backend/lib/MIR/MIRDocumentParser.h
new file mode 100644
index 0000000..53a31b4
--- /dev/null
+++ b/backend/lib/MIR/MIRDocumentParser.h
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// NOLINTNEXTLINE(llvm-header-guard)
+#ifndef RCC_LIB_MIR_MIRDOCUMENTPARSER_H
+#define RCC_LIB_MIR_MIRDOCUMENTPARSER_H
+
+#include "rcc/CodeGen/MachineDebugInfo.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+#include "rcc/MIR/LLVMConstantSyntax.h"
+#include "rcc/MIR/MIRDocument.h"
+
+#include <cstdint>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+
+class MachineBasicBlock;
+class MachineFunction;
+class MachineInstr;
+
+namespace mir {
+
+struct PendingJumpTable final {
+ JumpTableIndex Index;
+ std::uint32_t TextID = 0;
+ JumpTableEntryEncoding Encoding = JumpTableEntryEncoding::BlockAddress;
+ std::uint8_t EntrySize = 8;
+ std::vector<std::string> Blocks;
+};
+
+class MIRDocumentParser final {
+public:
+ MIRDocumentParser(const TargetDescription &Target, std::string SourceName, std::string SourceText,
+ MIRDocumentParserOptions Options)
+ : Target(Target), Options(std::move(Options)),
+ Result(std::move(SourceName), std::move(SourceText)) {}
+
+ [[nodiscard]] MIRDocumentParseResult run();
+
+private:
+ [[nodiscard]] bool fail(const TextRange &Range, std::string Message,
+ std::string Category = "mir-yaml");
+ [[nodiscard]] bool importError(const TextRange &Range, const Error &Failure,
+ std::string Category = "mir-yaml");
+ [[nodiscard]] const yaml::Node *node(yaml::NodeID ID) const noexcept {
+ return Stream->node(ID);
+ }
+ [[nodiscard]] const yaml::Node *mappingValue(const yaml::Node &Mapping,
+ std::string_view Key) const noexcept;
+ [[nodiscard]] bool requireKind(const yaml::Node &Node, yaml::NodeKind Kind,
+ std::string_view Description);
+ [[nodiscard]] bool validateKeys(const yaml::Node &Mapping, std::span<const std::string_view> Keys,
+ std::string_view Description);
+ [[nodiscard]] bool scalar(const yaml::Node &Node, std::string_view &Value,
+ std::string_view Description);
+ [[nodiscard]] bool boolean(const yaml::Node &Node, bool &Value);
+ [[nodiscard]] bool unsignedValue(const yaml::Node &Node, std::uint64_t &Value,
+ std::string_view Description);
+ [[nodiscard]] bool signedValue(const yaml::Node &Node, std::int64_t &Value,
+ std::string_view Description);
+ [[nodiscard]] bool alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero);
+ [[nodiscard]] bool physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg);
+ [[nodiscard]] bool physicalRegister(std::string_view Text, const TextRange &Range,
+ PhysicalRegister &Reg);
+ [[nodiscard]] bool virtualRegister(std::string_view Text, const TextRange &Range,
+ const MIParserContext &Context, Register &Reg);
+ [[nodiscard]] Expected<MachineMetadataReference>
+ resolveMetadataReference(const MachineFunction &Function, std::uint32_t Slot) const;
+ [[nodiscard]] bool parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function,
+ MachineVariableDebugInfo &DebugInfo);
+ [[nodiscard]] bool parseFunction(const yaml::Node &Root, MIRDocument &Document);
+ [[nodiscard]] bool parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function,
+ MIRFunctionDocumentInfo &DocumentInfo);
+ [[nodiscard]] bool recomputeProperties(const yaml::Node &Root, MachineFunction &Function,
+ const MIRFunctionDocumentInfo &DocumentInfo);
+ [[nodiscard]] bool parseRegisters(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context);
+ [[nodiscard]] bool parseMetadata(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context);
+ [[nodiscard]] bool parseStackObjects(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context);
+ [[nodiscard]] bool parseConstants(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context);
+ [[nodiscard]] bool parseFrameState(const yaml::Node &Root, MachineFunction &Function,
+ const MIParserContext &Context);
+ [[nodiscard]] bool reserveJumpTables(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context,
+ std::vector<PendingJumpTable> &Pending);
+ [[nodiscard]] bool defineJumpTables(MachineFunction &Function,
+ std::span<const PendingJumpTable> Pending);
+ [[nodiscard]] bool parseBody(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context);
+ [[nodiscard]] TextRange bodyDiagnosticRange(const yaml::Node &Body,
+ const TextRange &BodyRange) const noexcept;
+ [[nodiscard]] bool parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function);
+ [[nodiscard]] bool parseTargetState(const yaml::Node &Root, MachineFunction &Function);
+ [[nodiscard]] bool convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination);
+ [[nodiscard]] bool validateDocumentReferences(const MachineFunction &Function);
+ [[nodiscard]] std::string_view effectiveDataLayout() const noexcept;
+ [[nodiscard]] LLVMConstantSyntaxOptions pointerLayoutOptions() const noexcept;
+
+ [[nodiscard]] MachineBasicBlock *blockAt(MachineFunction &Function,
+ std::uint32_t Number) const noexcept;
+ [[nodiscard]] MachineInstr *instructionAt(MachineFunction &Function, std::uint32_t Block,
+ std::uint32_t Offset) const noexcept;
+ [[nodiscard]] bool parseBlockReference(std::string_view Text, const TextRange &Range,
+ MachineFunction &Function, MachineBasicBlock *&Block);
+ [[nodiscard]] bool parseFrameReference(const yaml::Node &Node, const MIParserContext &Context,
+ FrameIndex &Index);
+ [[nodiscard]] bool parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function,
+ std::vector<MachineFrameSaveRestorePoint> &Points);
+
+ const TargetDescription &Target;
+ MIRDocumentParserOptions Options;
+ MIRDocumentParseResult Result;
+ const yaml::Stream *Stream = nullptr;
+ MIRDocument *ActiveDocument = nullptr;
+ MachineFunction *ActiveFunction = nullptr;
+};
+
+} // namespace mir
+} // namespace rcc
+
+#endif // RCC_LIB_MIR_MIRDOCUMENTPARSER_H
diff --git a/backend/lib/MIR/MIRDocumentReferenceValidation.cpp b/backend/lib/MIR/MIRDocumentReferenceValidation.cpp
new file mode 100644
index 0000000..a6dbb56
--- /dev/null
+++ b/backend/lib/MIR/MIRDocumentReferenceValidation.cpp
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "MIRDocumentReferenceValidation.h"
+
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionRecords.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/MIR/MIRDocument.h"
+
+#include <algorithm>
+#include <array>
+#include <initializer_list>
+#include <optional>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <variant>
+
+namespace rcc::mir {
+
+Expected<void> verifyMIRDocumentReferences(const MIRDocument &Document,
+ const MachineFunction &Function, bool UseEmbeddedIR) {
+ const EmbeddedIRReferenceIndex *Embedded = UseEmbeddedIR ? Document.embeddedIR() : nullptr;
+ const EmbeddedIRFunction *IRFunction = nullptr;
+ if (Embedded != nullptr) {
+ IRFunction = Embedded->function(Function.name());
+ if (IRFunction == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "machine function has no matching embedded LLVM IR function");
+ }
+ }
+
+ const auto ValidateGlobal = [Embedded](std::string_view Name,
+ std::string_view Description) -> Expected<void> {
+ if (Embedded == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) +
+ " requires embedded LLVM IR to resolve global '" +
+ std::string(Name) + "'");
+ }
+ if (Embedded->hasGlobalValue(Name))
+ return {};
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) +
+ " references an undefined embedded LLVM IR global '" +
+ std::string(Name) + "'");
+ };
+ const auto ValidateBlockAddress = [Embedded](std::string_view FunctionName,
+ std::string_view BlockName,
+ std::string_view Description) -> Expected<void> {
+ if (Embedded == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) +
+ " requires embedded LLVM IR to resolve a block address");
+ }
+ const EmbeddedIRFunction *AddressFunction = Embedded->function(FunctionName);
+ if (AddressFunction == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) +
+ " references an undefined embedded LLVM IR function '" +
+ std::string(FunctionName) + "'");
+ }
+ if (AddressFunction->block(BlockName) == nullptr) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ std::string(Description) + " references an undefined embedded LLVM IR block '" +
+ std::string(BlockName) + "' in function '" + std::string(FunctionName) + "'");
+ }
+ return {};
+ };
+ const auto ValidateIRValue = [IRFunction](std::string_view Name,
+ std::string_view Description) -> Expected<void> {
+ if (IRFunction == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) +
+ " requires embedded LLVM IR to resolve IR value '%" +
+ std::string(Name) + "'");
+ }
+ if (IRFunction->value(Name) != nullptr)
+ return {};
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) +
+ " references an undefined embedded LLVM IR value '%" +
+ std::string(Name) + "'");
+ };
+ const auto ValidateMetadata = [Embedded](MachineMetadataReference Reference,
+ std::initializer_list<std::string_view> ExpectedTypes,
+ std::string_view Description) -> Expected<void> {
+ if (!Reference.isValid()) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) + " has an invalid metadata reference");
+ }
+ if (Reference.scope() != MachineMetadataScope::EmbeddedIR)
+ return {};
+ if (Embedded == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) + " requires embedded LLVM IR metadata !" +
+ std::to_string(Reference.slot()));
+ }
+ const EmbeddedIRMetadataDefinition *Definition =
+ Embedded->metadata(std::to_string(Reference.slot()));
+ if (Definition == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) +
+ " references undefined embedded LLVM IR metadata !" +
+ std::to_string(Reference.slot()));
+ }
+ if (ExpectedTypes.size() == 0 ||
+ std::ranges::find(ExpectedTypes, Definition->DynamicType) != ExpectedTypes.end()) {
+ return {};
+ }
+ std::string Expected;
+ for (const std::string_view Type : ExpectedTypes) {
+ if (!Expected.empty())
+ Expected.append(" or ");
+ Expected.append(Type);
+ }
+ return makeUnexpected(ErrorCode::VerificationError,
+ std::string(Description) + " requires " + Expected + " metadata, but !" +
+ std::to_string(Reference.slot()) + " is " +
+ (Definition->DynamicType.empty()
+ ? std::string("an unclassified metadata node")
+ : Definition->DynamicType));
+ };
+ const auto ValidateMetadataValue =
+ [&ValidateMetadata](const MachineMetadataValue &Value,
+ std::string_view Description) -> Expected<void> {
+ const MachineMetadataReference *Reference = Value.reference();
+ return Reference == nullptr ? Expected<void>{} : ValidateMetadata(*Reference, {}, Description);
+ };
+ const auto ValidateLocation =
+ [&ValidateMetadata](const MachineDebugLocation &Location,
+ std::string_view Description) -> Expected<void> {
+ if (const MachineMetadataReference *Reference = Location.reference())
+ return ValidateMetadata(*Reference, {"DILocation"}, Description);
+ const MachineDILocation *Inline = Location.inlineLocation();
+ if (Inline == nullptr)
+ return {};
+ for (const MachineDILocationFrame &Frame : Inline->inlineChain()) {
+ auto Verification =
+ ValidateMetadata(Frame.Scope, {"DISubprogram", "DILexicalBlock", "DILexicalBlockFile"},
+ "inline DILocation scope");
+ if (!Verification)
+ return Verification;
+ }
+ if (Inline->terminalInlinedAt()) {
+ return ValidateMetadata(*Inline->terminalInlinedAt(), {"DILocation"},
+ "inline DILocation inlinedAt");
+ }
+ return {};
+ };
+ const auto ValidateVariableDebugInfo =
+ [&ValidateMetadata, &ValidateMetadataValue,
+ &ValidateLocation](const MachineVariableDebugInfo &DebugInfo) -> Expected<void> {
+ if (DebugInfo.Variable) {
+ auto Verification =
+ ValidateMetadata(*DebugInfo.Variable, {"DILocalVariable"}, "debug variable");
+ if (!Verification)
+ return Verification;
+ }
+ if (DebugInfo.Expression) {
+ if (const MachineMetadataReference *Reference = DebugInfo.Expression->reference();
+ Reference != nullptr) {
+ auto Verification = ValidateMetadata(*Reference, {"DIExpression"}, "debug expression");
+ if (!Verification)
+ return Verification;
+ }
+ auto Verification = ValidateMetadataValue(*DebugInfo.Expression, "debug expression");
+ if (!Verification)
+ return Verification;
+ }
+ if (DebugInfo.Location)
+ return ValidateLocation(*DebugInfo.Location, "debug location");
+ return {};
+ };
+ const auto ValidateGenericType = [Embedded](const auto &Self,
+ const GenericConstantType &Type) -> Expected<void> {
+ if (Type.Kind == GenericConstantTypeKind::Named) {
+ if (Embedded == nullptr) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "generic constant type requires embedded LLVM IR to resolve named type '%" + Type.Name +
+ "'");
+ }
+ if (Embedded->typeDefinition(Type.Name) == nullptr) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "generic constant type references an undefined embedded LLVM IR named type '%" +
+ Type.Name + "'");
+ }
+ }
+ for (const GenericConstantType &Element : Type.Elements) {
+ auto Verification = Self(Self, Element);
+ if (!Verification)
+ return Verification;
+ }
+ return {};
+ };
+ const auto ValidateGenericConstant =
+ [&ValidateBlockAddress, &ValidateGenericType,
+ &ValidateGlobal](const auto &Self, const GenericConstantData &Constant) -> Expected<void> {
+ auto TypeVerification = ValidateGenericType(ValidateGenericType, Constant.Type);
+ if (!TypeVerification)
+ return TypeVerification;
+ if (Constant.SourceElementType) {
+ TypeVerification = ValidateGenericType(ValidateGenericType, *Constant.SourceElementType);
+ if (!TypeVerification)
+ return TypeVerification;
+ }
+ switch (Constant.Kind) {
+ case GenericConstantKind::GlobalAddress:
+ case GenericConstantKind::DSOLocalEquivalent:
+ case GenericConstantKind::NoCFI:
+ if (auto Verification = ValidateGlobal(Constant.Symbol, "generic constant"); !Verification)
+ return Verification;
+ break;
+ case GenericConstantKind::BlockAddress:
+ if (auto Verification = ValidateBlockAddress(Constant.Symbol, Constant.Block,
+ "generic constant blockaddress");
+ !Verification) {
+ return Verification;
+ }
+ break;
+ default:
+ break;
+ }
+ for (const GenericConstantData &Element : Constant.Elements) {
+ auto Verification = Self(Self, Element);
+ if (!Verification)
+ return Verification;
+ }
+ return {};
+ };
+
+ for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
+ Block = Block->next()) {
+ if (const auto Name = Block->attributes().irBlockName(); Name) {
+ if (IRFunction == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "machine basic block requires embedded LLVM IR to resolve block '" +
+ std::string(*Name) + "'");
+ }
+ if (IRFunction->block(*Name) == nullptr) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "machine basic block references an undefined embedded LLVM IR block '" +
+ std::string(*Name) + "'");
+ }
+ }
+ }
+ for (const StackObject &Object : Function.frameInfo().objects()) {
+ const StackObjectDesc *Description = Object.description();
+ if (Description == nullptr || !Description->AllocaName)
+ continue;
+ if (IRFunction == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "stack object requires embedded LLVM IR to resolve alloca '" +
+ *Description->AllocaName + "'");
+ }
+ if (IRFunction->alloca(*Description->AllocaName) == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "stack object references an undefined embedded LLVM IR alloca '" +
+ *Description->AllocaName + "'");
+ }
+ }
+
+ for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) {
+ const MachineConstantPoolEntryDesc *Description = Entry.description();
+ if (Description == nullptr)
+ continue;
+ if (const auto *Generic = std::get_if<GenericConstantData>(&Description->Value)) {
+ auto Verification = ValidateGenericConstant(ValidateGenericConstant, *Generic);
+ if (!Verification)
+ return Verification;
+ } else if (const auto *Address = std::get_if<BlockAddressConstant>(&Description->Value)) {
+ auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName,
+ "constant-pool block address");
+ if (!Verification)
+ return Verification;
+ }
+ }
+
+ for (const MachineMetadataNode &Node : Function.metadata().nodes()) {
+ for (const MachineMetadataElement &Element : Node.Elements) {
+ const MachineMetadataReference *Reference = Element.reference();
+ if (Reference == nullptr)
+ continue;
+ auto Verification = ValidateMetadata(*Reference, {}, "machine metadata node");
+ if (!Verification)
+ return Verification;
+ }
+ }
+
+ for (const StackObject &Object : Function.frameInfo().objects()) {
+ const StackObjectDesc *Description = Object.description();
+ if (Description == nullptr || !Description->DebugInfo)
+ continue;
+ auto Verification = ValidateVariableDebugInfo(*Description->DebugInfo);
+ if (!Verification)
+ return Verification;
+ }
+ for (const MachineEntryValueInfo &Entry : Function.records().entryValues()) {
+ auto Verification = ValidateVariableDebugInfo(Entry.DebugInfo);
+ if (!Verification)
+ return Verification;
+ }
+ for (const MachineCalledGlobal &Called : Function.records().calledGlobals()) {
+ auto Verification = ValidateGlobal(Called.Callee, "called-global record");
+ if (!Verification)
+ return Verification;
+ }
+
+ for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
+ Block = Block->next()) {
+ for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;
+ Instruction = Instruction->next()) {
+ if (Instruction->debugLocation()) {
+ auto Verification =
+ ValidateLocation(*Instruction->debugLocation(), "instruction debug location");
+ if (!Verification)
+ return Verification;
+ }
+ const std::array InstructionMetadata{
+ Instruction->heapAllocMarker(),
+ Instruction->pcSections(),
+ Instruction->mmra(),
+ };
+ for (const auto &Reference : InstructionMetadata) {
+ if (!Reference)
+ continue;
+ auto Verification = ValidateMetadata(*Reference, {}, "instruction metadata attachment");
+ if (!Verification)
+ return Verification;
+ }
+
+ for (const MachineOperand &Operand : Instruction->operands()) {
+ if (const auto *Global = Operand.getIf<GlobalSymbolOperand>(); Global != nullptr) {
+ auto Verification = ValidateGlobal(Global->Name, "global-symbol operand");
+ if (!Verification)
+ return Verification;
+ }
+ if (const auto *Address = Operand.getIf<BlockAddressOperand>(); Address != nullptr) {
+ auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName,
+ "block-address operand");
+ if (!Verification)
+ return Verification;
+ }
+ if (const auto *Metadata = Operand.getIf<MetadataOperand>(); Metadata != nullptr) {
+ auto Verification = ValidateMetadataValue(Metadata->Value, "metadata operand");
+ if (!Verification)
+ return Verification;
+ }
+ }
+
+ for (const MachineMemOperand *Memory : Instruction->memOperands()) {
+ const MachineMemOperandDesc &Description = Memory->description();
+ const std::optional<std::string_view> BaseName = Description.Base.symbolName();
+ switch (Description.Base.kind()) {
+ case MemoryBase::Kind::GlobalSymbol:
+ case MemoryBase::Kind::CallEntryGlobal:
+ if (BaseName) {
+ auto Verification = ValidateGlobal(*BaseName, "machine memory base");
+ if (!Verification)
+ return Verification;
+ }
+ break;
+ case MemoryBase::Kind::IRValue:
+ if (BaseName) {
+ auto Verification = ValidateIRValue(*BaseName, "machine memory base");
+ if (!Verification)
+ return Verification;
+ }
+ break;
+ default:
+ break;
+ }
+ const std::array MetadataReferences{
+ Description.TBAA, Description.AliasScope,
+ Description.NoAlias, Description.NoAliasAddressSpace,
+ Description.RangeMetadata,
+ };
+ for (const auto &Reference : MetadataReferences) {
+ if (!Reference)
+ continue;
+ auto Verification = ValidateMetadata(*Reference, {}, "machine memory operand metadata");
+ if (!Verification)
+ return Verification;
+ }
+ if (Description.SyncScope.value() == 0)
+ continue;
+ const auto Scope =
+ std::ranges::find_if(Document.syncScopes(), [&Description](const auto &Slot) {
+ return Slot.ID == Description.SyncScope;
+ });
+ if (Scope == Document.syncScopes().end()) {
+ return makeUnexpected(
+ ErrorCode::VerificationError,
+ "machine memory operand synchronization scope has no document name");
+ }
+ }
+ }
+ }
+ return {};
+}
+
+} // namespace rcc::mir
diff --git a/backend/lib/MIR/MIRFrameParser.cpp b/backend/lib/MIR/MIRFrameParser.cpp
new file mode 100644
index 0000000..76ace8f
--- /dev/null
+++ b/backend/lib/MIR/MIRFrameParser.cpp
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "MIRDocumentParser.h"
+#include "MIRDocumentReferenceValidation.h"
+#include "MIRMetadataTextParser.h"
+#include "MIRParserUtilities.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionAttributes.h"
+#include "rcc/CodeGen/MachineFunctionProperties.h"
+#include "rcc/CodeGen/MachineFunctionRecords.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/MIR/LLVMConstantSyntax.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::mir {
+
+using parser_detail::parseBool;
+using parser_detail::parsePrefixedID;
+using parser_detail::parseSigned;
+using parser_detail::parseUnsigned;
+using parser_detail::parseUnsigned32;
+using parser_detail::trim;
+
+bool MIRDocumentParser::parseStackObjects(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context) {
+ auto ParseList = [this, &Function, &Context](const yaml::Node &List, bool Fixed) -> bool {
+ if (requireKind(List, yaml::NodeKind::Sequence,
+ Fixed ? "fixed stack objects" : "stack objects")) {
+ return true;
+ }
+ constexpr std::array FixedKeys{
+ std::string_view{"id"},
+ std::string_view{"type"},
+ std::string_view{"offset"},
+ std::string_view{"size"},
+ std::string_view{"alignment"},
+ std::string_view{"stack-id"},
+ std::string_view{"isImmutable"},
+ std::string_view{"isAliased"},
+ std::string_view{"callee-saved-register"},
+ std::string_view{"callee-saved-restored"},
+ std::string_view{"debug-info-variable"},
+ std::string_view{"debug-info-expression"},
+ std::string_view{"debug-info-location"},
+ };
+ constexpr std::array StackKeys{
+ std::string_view{"id"},
+ std::string_view{"name"},
+ std::string_view{"type"},
+ std::string_view{"offset"},
+ std::string_view{"size"},
+ std::string_view{"alignment"},
+ std::string_view{"stack-id"},
+ std::string_view{"callee-saved-register"},
+ std::string_view{"callee-saved-restored"},
+ std::string_view{"local-offset"},
+ std::string_view{"debug-info-variable"},
+ std::string_view{"debug-info-expression"},
+ std::string_view{"debug-info-location"},
+ };
+ std::unordered_set<std::uint32_t> IDs;
+ for (const yaml::NodeID ElementID : List.sequence()) {
+ const yaml::Node *Element = node(ElementID);
+ if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "stack object") ||
+ (Fixed ? validateKeys(*Element, FixedKeys, "fixed stack object")
+ : validateKeys(*Element, StackKeys, "stack object"))) {
+ return true;
+ }
+ const yaml::Node *IDNode = mappingValue(*Element, "id");
+ if (IDNode == nullptr)
+ return fail(Element->range(), "stack object requires 'id'");
+ std::uint64_t WideID = 0;
+ if (unsignedValue(*IDNode, WideID, "a 32-bit stack object ID") ||
+ WideID > std::numeric_limits<std::uint32_t>::max()) {
+ return true;
+ }
+ const auto TextID = static_cast<std::uint32_t>(WideID);
+ if (!IDs.insert(TextID).second)
+ return fail(IDNode->range(), "duplicate stack object ID");
+
+ StackObjectDesc Description;
+ Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default;
+ Description.Offset = 0;
+ if (const yaml::Node *NameNode = mappingValue(*Element, "name")) {
+ std::string_view Name;
+ if (scalar(*NameNode, Name, "stack object name") || Name.empty())
+ return fail(NameNode->range(), "stack object name cannot be empty");
+ Description.AllocaName = std::string(Name);
+ }
+ if (const yaml::Node *TypeNode = mappingValue(*Element, "type")) {
+ std::string_view Type;
+ if (scalar(*TypeNode, Type, "stack object type"))
+ return true;
+ if (Type == "default")
+ Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default;
+ else if (Type == "spill-slot")
+ Description.Kind = Fixed ? StackObjectKind::FixedSpillSlot : StackObjectKind::SpillSlot;
+ else if (!Fixed && Type == "variable-sized")
+ Description.Kind = StackObjectKind::VariableSized;
+ else
+ return fail(TypeNode->range(), "unknown stack object type '" + std::string(Type) + "'");
+ }
+ if (const yaml::Node *OffsetNode = mappingValue(*Element, "offset")) {
+ std::int64_t Offset = 0;
+ if (signedValue(*OffsetNode, Offset, "a signed stack object offset"))
+ return true;
+ Description.Offset = Offset;
+ } else if (Fixed) {
+ Description.Offset = 0;
+ }
+ if (Description.Kind != StackObjectKind::VariableSized) {
+ const yaml::Node *SizeNode = mappingValue(*Element, "size");
+ if (SizeNode == nullptr) {
+ if (!Fixed)
+ return fail(Element->range(), "fixed-size stack object requires 'size'");
+ Description.Size = 0;
+ } else {
+ std::uint64_t Size = 0;
+ if (unsignedValue(*SizeNode, Size, "an unsigned stack object size"))
+ return true;
+ Description.Size = Size;
+ }
+ }
+ if (const yaml::Node *AlignmentNode = mappingValue(*Element, "alignment")) {
+ if (alignment(*AlignmentNode, Description.Align, true))
+ return true;
+ }
+ if (const yaml::Node *StackIDNode = mappingValue(*Element, "stack-id")) {
+ std::string_view Name;
+ if (scalar(*StackIDNode, Name, "stack ID"))
+ return true;
+ const auto Stack = parseStackIDName(Name);
+ if (!Stack)
+ return fail(StackIDNode->range(), "unknown stack ID '" + std::string(Name) + "'");
+ Description.Stack = *Stack;
+ }
+ if (const yaml::Node *ImmutableNode = mappingValue(*Element, "isImmutable")) {
+ if (boolean(*ImmutableNode, Description.IsImmutable))
+ return true;
+ }
+ if (const yaml::Node *AliasedNode = mappingValue(*Element, "isAliased")) {
+ if (boolean(*AliasedNode, Description.IsAliased))
+ return true;
+ }
+ if (const yaml::Node *SavedNode = mappingValue(*Element, "callee-saved-register")) {
+ PhysicalRegister Reg;
+ if (physicalRegister(*SavedNode, Reg))
+ return true;
+ Description.CalleeSavedRegister = Reg;
+ }
+ if (const yaml::Node *RestoredNode = mappingValue(*Element, "callee-saved-restored")) {
+ if (boolean(*RestoredNode, Description.CalleeSavedRestored))
+ return true;
+ }
+ if (const yaml::Node *LocalNode = mappingValue(*Element, "local-offset")) {
+ std::int64_t Offset = 0;
+ if (signedValue(*LocalNode, Offset, "a signed local frame offset"))
+ return true;
+ Description.LocalOffset = Offset;
+ }
+ MachineVariableDebugInfo DebugInfo;
+ if (parseVariableDebugInfo(*Element, Function, DebugInfo))
+ return true;
+ if (DebugInfo.Variable || DebugInfo.Expression || DebugInfo.Location)
+ Description.DebugInfo = std::move(DebugInfo);
+
+ auto Created = Function.frameInfo().createObject(std::move(Description));
+ if (!Created)
+ return importError(Element->range(), Created.error());
+ MIFrameObjectSlot Slot{
+ .TextID = TextID,
+ .Index = *Created,
+ .Name = std::nullopt,
+ };
+ if (const StackObjectDesc *CreatedDescription =
+ Function.frameInfo().object(*Created)->description();
+ CreatedDescription != nullptr && CreatedDescription->AllocaName) {
+ Slot.Name = *CreatedDescription->AllocaName;
+ }
+ (Fixed ? Context.FixedStackObjects : Context.StackObjects).push_back(std::move(Slot));
+ }
+ return false;
+ };
+
+ if (const yaml::Node *Fixed = mappingValue(Root, "fixedStack")) {
+ if (ParseList(*Fixed, true))
+ return true;
+ }
+ if (const yaml::Node *Stack = mappingValue(Root, "stack")) {
+ if (ParseList(*Stack, false))
+ return true;
+ }
+ return false;
+}
+
+bool MIRDocumentParser::parseFrameReference(const yaml::Node &Node, const MIParserContext &Context,
+ FrameIndex &Index) {
+ std::string_view Text;
+ if (scalar(Node, Text, "frame index"))
+ return true;
+ bool Fixed = false;
+ std::optional<std::uint32_t> ID = parsePrefixedID(Text, "%stack.");
+ if (!ID) {
+ ID = parsePrefixedID(Text, "%fixed-stack.");
+ Fixed = true;
+ }
+ if (!ID)
+ return fail(Node.range(), "frame index must use '%stack.N' or '%fixed-stack.N'");
+ const auto &Slots = Fixed ? Context.FixedStackObjects : Context.StackObjects;
+ const auto Found = std::ranges::find_if(
+ Slots, [ID](const MIFrameObjectSlot &Slot) { return Slot.TextID == *ID; });
+ if (Found == Slots.end())
+ return fail(Node.range(), "reference to undefined frame object");
+ Index = Found->Index;
+ return false;
+}
+
+MachineBasicBlock *MIRDocumentParser::blockAt(MachineFunction &Function,
+ std::uint32_t Number) const noexcept {
+ for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) {
+ if (Block->denseNumber() == Number)
+ return Block;
+ }
+ return nullptr;
+}
+
+MachineInstr *MIRDocumentParser::instructionAt(MachineFunction &Function, std::uint32_t BlockNumber,
+ std::uint32_t Offset) const noexcept {
+ MachineBasicBlock *Block = blockAt(Function, BlockNumber);
+ if (Block == nullptr)
+ return nullptr;
+ MachineInstr *Instruction = Block->firstInstruction();
+ for (std::uint32_t Index = 0; Instruction != nullptr && Index < Offset; ++Index)
+ Instruction = Instruction->next();
+ return Instruction;
+}
+
+bool MIRDocumentParser::parseBlockReference(std::string_view Text, const TextRange &Range,
+ MachineFunction &Function, MachineBasicBlock *&Block) {
+ const auto Number = parsePrefixedID(Text, "%bb.");
+ if (!Number)
+ return fail(Range, "basic block reference must use the '%bb.N' MIR spelling");
+ Block = blockAt(Function, *Number);
+ if (Block == nullptr)
+ return fail(Range, "reference to undefined machine basic block");
+ return false;
+}
+
+bool MIRDocumentParser::parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function,
+ std::vector<MachineFrameSaveRestorePoint> &Points) {
+ if (requireKind(Node, yaml::NodeKind::Sequence, "frame save/restore points"))
+ return true;
+ constexpr std::array Keys{
+ std::string_view{"point"},
+ std::string_view{"registers"},
+ };
+ for (const yaml::NodeID PointID : Node.sequence()) {
+ const yaml::Node *PointNode = node(PointID);
+ if (PointNode == nullptr ||
+ requireKind(*PointNode, yaml::NodeKind::Mapping, "frame save/restore point") ||
+ validateKeys(*PointNode, Keys, "frame save/restore point")) {
+ return true;
+ }
+ const yaml::Node *BlockNode = mappingValue(*PointNode, "point");
+ if (BlockNode == nullptr)
+ return fail(PointNode->range(), "frame save/restore point requires 'point'");
+ std::string_view BlockText;
+ MachineBasicBlock *Block = nullptr;
+ if (scalar(*BlockNode, BlockText, "basic block reference") ||
+ parseBlockReference(BlockText, BlockNode->range(), Function, Block)) {
+ return true;
+ }
+ if (Block == nullptr)
+ return fail(BlockNode->range(), "reference to undefined machine basic block");
+ MachineFrameSaveRestorePoint Point{.Block = Block->stableID(), .Registers = {}};
+ if (const yaml::Node *RegistersNode = mappingValue(*PointNode, "registers")) {
+ if (requireKind(*RegistersNode, yaml::NodeKind::Sequence, "save/restore point registers")) {
+ return true;
+ }
+ for (const yaml::NodeID RegID : RegistersNode->sequence()) {
+ const yaml::Node *RegNode = node(RegID);
+ PhysicalRegister Reg;
+ if (RegNode == nullptr || physicalRegister(*RegNode, Reg))
+ return true;
+ Point.Registers.push_back(Reg);
+ }
+ }
+ Points.push_back(std::move(Point));
+ }
+ return false;
+}
+
+bool MIRDocumentParser::parseFrameState(const yaml::Node &Root, MachineFunction &Function,
+ const MIParserContext &Context) {
+ const yaml::Node *Frame = mappingValue(Root, "frameInfo");
+ if (Frame == nullptr)
+ return false;
+ if (requireKind(*Frame, yaml::NodeKind::Mapping, "frameInfo"))
+ return true;
+ constexpr std::array Keys{
+ std::string_view{"isFrameAddressTaken"},
+ std::string_view{"isReturnAddressTaken"},
+ std::string_view{"hasStackMap"},
+ std::string_view{"hasPatchPoint"},
+ std::string_view{"stackSize"},
+ std::string_view{"offsetAdjustment"},
+ std::string_view{"maxAlignment"},
+ std::string_view{"adjustsStack"},
+ std::string_view{"hasCalls"},
+ std::string_view{"framePointerPolicy"},
+ std::string_view{"stackProtector"},
+ std::string_view{"functionContext"},
+ std::string_view{"maxCallFrameSize"},
+ std::string_view{"cvBytesOfCalleeSavedRegisters"},
+ std::string_view{"hasOpaqueSPAdjustment"},
+ std::string_view{"hasVAStart"},
+ std::string_view{"hasMustTailInVarArgFunc"},
+ std::string_view{"hasTailCall"},
+ std::string_view{"isCalleeSavedInfoValid"},
+ std::string_view{"localFrameSize"},
+ std::string_view{"savePoint"},
+ std::string_view{"restorePoint"},
+ };
+ if (validateKeys(*Frame, Keys, "frameInfo"))
+ return true;
+
+ MachineFrameState State = Function.frameInfo().state();
+ const std::array BoolBindings{
+ std::pair{"isFrameAddressTaken", &State.IsFrameAddressTaken},
+ std::pair{"isReturnAddressTaken", &State.IsReturnAddressTaken},
+ std::pair{"hasStackMap", &State.HasStackMap},
+ std::pair{"hasPatchPoint", &State.HasPatchPoint},
+ std::pair{"adjustsStack", &State.AdjustsStack},
+ std::pair{"hasCalls", &State.HasCalls},
+ std::pair{"hasOpaqueSPAdjustment", &State.HasOpaqueSPAdjustment},
+ std::pair{"hasVAStart", &State.HasVAStart},
+ std::pair{"hasMustTailInVarArgFunc", &State.HasMustTailInVarArgFunc},
+ std::pair{"hasTailCall", &State.HasTailCall},
+ std::pair{"isCalleeSavedInfoValid", &State.IsCalleeSavedInfoValid},
+ };
+ for (const auto &[Key, Destination] : BoolBindings) {
+ if (const yaml::Node *Value = mappingValue(*Frame, Key)) {
+ if (boolean(*Value, *Destination))
+ return true;
+ }
+ }
+
+ if (const yaml::Node *Value = mappingValue(*Frame, "stackSize")) {
+ if (unsignedValue(*Value, State.StackSize, "an unsigned stack size"))
+ return true;
+ }
+ if (const yaml::Node *Value = mappingValue(*Frame, "offsetAdjustment")) {
+ std::int64_t Wide = 0;
+ if (signedValue(*Value, Wide, "a 32-bit signed offset adjustment") ||
+ Wide < std::numeric_limits<std::int32_t>::min() ||
+ Wide > std::numeric_limits<std::int32_t>::max()) {
+ return fail(Value->range(), "offset adjustment is outside the 32-bit signed range");
+ }
+ State.OffsetAdjustment = static_cast<std::int32_t>(Wide);
+ }
+ if (const yaml::Node *Value = mappingValue(*Frame, "maxAlignment")) {
+ std::uint64_t Bytes = 0;
+ if (unsignedValue(*Value, Bytes, "an unsigned maximum alignment"))
+ return true;
+ if (Bytes == 0) {
+ State.MaxAlignment.reset();
+ } else {
+ auto Parsed = Alignment::create(Bytes);
+ if (!Parsed)
+ return importError(Value->range(), Parsed.error());
+ State.MaxAlignment = *Parsed;
+ }
+ }
+ if (const yaml::Node *Value = mappingValue(*Frame, "framePointerPolicy")) {
+ std::string_view Name;
+ if (scalar(*Value, Name, "frame-pointer policy"))
+ return true;
+ const auto Policy = parseMachineFramePointerPolicy(Name);
+ if (!Policy)
+ return fail(Value->range(), "unknown frame-pointer policy '" + std::string(Name) + "'");
+ State.FramePointerPolicy = *Policy;
+ }
+ for (const auto &[Key, Destination] : {std::pair{"stackProtector", &State.StackProtector},
+ std::pair{"functionContext", &State.FunctionContext}}) {
+ if (const yaml::Node *Value = mappingValue(*Frame, Key)) {
+ FrameIndex Index;
+ if (parseFrameReference(*Value, Context, Index))
+ return true;
+ *Destination = Index;
+ }
+ }
+ if (const yaml::Node *Value = mappingValue(*Frame, "maxCallFrameSize")) {
+ std::uint64_t Size = 0;
+ if (unsignedValue(*Value, Size, "a 32-bit unsigned maximum call-frame size") ||
+ Size > std::numeric_limits<std::uint32_t>::max()) {
+ return fail(Value->range(), "maximum call-frame size is outside the 32-bit unsigned range");
+ }
+ State.MaxCallFrameSize = Size;
+ }
+ for (const auto &[Key, Destination] :
+ {std::pair{"cvBytesOfCalleeSavedRegisters", &State.CVBytesOfCalleeSavedRegisters},
+ std::pair{"localFrameSize", &State.LocalFrameSize}}) {
+ if (const yaml::Node *Value = mappingValue(*Frame, Key)) {
+ std::uint64_t Wide = 0;
+ if (unsignedValue(*Value, Wide, "a 32-bit unsigned frame value") ||
+ Wide > std::numeric_limits<std::uint32_t>::max()) {
+ return fail(Value->range(), "frame value is outside the 32-bit unsigned range");
+ }
+ *Destination = static_cast<std::uint32_t>(Wide);
+ }
+ }
+ if (const yaml::Node *Value = mappingValue(*Frame, "savePoint")) {
+ if (parseSaveRestorePoints(*Value, Function, State.SavePoints))
+ return true;
+ }
+ if (const yaml::Node *Value = mappingValue(*Frame, "restorePoint")) {
+ if (parseSaveRestorePoints(*Value, Function, State.RestorePoints))
+ return true;
+ }
+ auto StateResult = Function.frameInfo().setState(std::move(State));
+ if (!StateResult)
+ return importError(Frame->range(), StateResult.error());
+ return false;
+}
+
+} // namespace rcc::mir
diff --git a/backend/lib/MIR/MIRFunctionParser.cpp b/backend/lib/MIR/MIRFunctionParser.cpp
new file mode 100644
index 0000000..07cb2b0
--- /dev/null
+++ b/backend/lib/MIR/MIRFunctionParser.cpp
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "MIRDocumentParser.h"
+#include "MIRDocumentReferenceValidation.h"
+#include "MIRMetadataTextParser.h"
+#include "MIRParserUtilities.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionAttributes.h"
+#include "rcc/CodeGen/MachineFunctionProperties.h"
+#include "rcc/CodeGen/MachineFunctionRecords.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/MIR/LLVMConstantSyntax.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::mir {
+
+using parser_detail::parseBool;
+using parser_detail::parsePrefixedID;
+using parser_detail::parseSigned;
+using parser_detail::parseUnsigned;
+using parser_detail::parseUnsigned32;
+using parser_detail::trim;
+
+bool MIRDocumentParser::parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function,
+ MIRFunctionDocumentInfo &DocumentInfo) {
+ if (const yaml::Node *AlignNode = mappingValue(Root, "alignment")) {
+ Alignment Align;
+ if (alignment(*AlignNode, Align, true))
+ return true;
+ if (Align.bytes() != 1 || AlignNode->scalar() != "0")
+ Function.attributes().setAlignment(Align);
+ }
+
+ constexpr std::array PropertyBindings{
+ std::pair{"legalized", MachineFunctionProperty::Legalized},
+ std::pair{"regBankSelected", MachineFunctionProperty::RegBankSelected},
+ std::pair{"selected", MachineFunctionProperty::Selected},
+ std::pair{"failedISel", MachineFunctionProperty::FailedISel},
+ std::pair{"tracksRegLiveness", MachineFunctionProperty::TracksLiveness},
+ std::pair{"noPhis", MachineFunctionProperty::NoPHIs},
+ std::pair{"isSSA", MachineFunctionProperty::IsSSA},
+ std::pair{"noVRegs", MachineFunctionProperty::NoVRegs},
+ std::pair{"failsVerification", MachineFunctionProperty::FailsVerification},
+ };
+ for (const auto &[Key, Property] : PropertyBindings) {
+ const yaml::Node *ValueNode = mappingValue(Root, Key);
+ if (ValueNode == nullptr)
+ continue;
+ bool Value = false;
+ if (boolean(*ValueNode, Value))
+ return true;
+ if (Value)
+ Function.properties().set(Property);
+ else
+ Function.properties().clear(Property);
+ if (Key == std::string_view("noPhis"))
+ DocumentInfo.NoPHIs = Value;
+ else if (Key == std::string_view("isSSA"))
+ DocumentInfo.IsSSA = Value;
+ else if (Key == std::string_view("noVRegs"))
+ DocumentInfo.NoVRegs = Value;
+ }
+
+ constexpr std::array AttributeBindings{
+ std::pair{"exposesReturnsTwice", MachineFunctionAttribute::ExposesReturnsTwice},
+ std::pair{"hasWinCFI", MachineFunctionAttribute::HasWinCFI},
+ std::pair{"hasFakeUses", MachineFunctionAttribute::HasFakeUses},
+ std::pair{"callsEHReturn", MachineFunctionAttribute::CallsEHReturn},
+ std::pair{"callsUnwindInit", MachineFunctionAttribute::CallsUnwindInit},
+ std::pair{"hasEHContTarget", MachineFunctionAttribute::HasEHContTarget},
+ std::pair{"hasEHScopes", MachineFunctionAttribute::HasEHScopes},
+ std::pair{"hasEHFunclets", MachineFunctionAttribute::HasEHFunclets},
+ std::pair{"isOutlined", MachineFunctionAttribute::IsOutlined},
+ std::pair{"debugInstrRef", MachineFunctionAttribute::DebugInstrRef},
+ std::pair{"tracksDebugUserValues", MachineFunctionAttribute::TracksDebugUserValues},
+ };
+ for (const auto &[Key, Attribute] : AttributeBindings) {
+ const yaml::Node *ValueNode = mappingValue(Root, Key);
+ if (ValueNode == nullptr)
+ continue;
+ bool Value = false;
+ if (boolean(*ValueNode, Value))
+ return true;
+ if (Value)
+ Function.attributes().set(Attribute);
+ else
+ Function.attributes().clear(Attribute);
+ if (Key == std::string_view("hasFakeUses"))
+ DocumentInfo.HasFakeUses = Value;
+ }
+
+ return false;
+}
+
+bool MIRDocumentParser::recomputeProperties(const yaml::Node &Root, MachineFunction &Function,
+ const MIRFunctionDocumentInfo &DocumentInfo) {
+ bool HasPHI = false;
+ bool HasFakeUse = false;
+ bool IsSSA = true;
+ bool HasTiedOperands = false;
+ bool AllTiedOperandsRewritten = true;
+ bool HasFrameIndex = false;
+ bool HasExecutablePseudo = false;
+ std::unordered_set<Register> Definitions;
+ for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr;
+ Block = Block->next()) {
+ for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;
+ Instruction = Instruction->next()) {
+ const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode());
+ if (Descriptor == nullptr)
+ return fail(Root.range(), "instruction has no target descriptor");
+ HasPHI |= Descriptor->Flags.has(InstructionFlag::Phi);
+ HasFakeUse |= Descriptor->Flags.has(InstructionFlag::FakeUse);
+ HasExecutablePseudo |= Descriptor->Flags.has(InstructionFlag::Pseudo) &&
+ !Descriptor->Flags.has(InstructionFlag::Meta) &&
+ !Descriptor->Flags.has(InstructionFlag::TerminalEmission);
+ const auto Operands = Instruction->operands();
+ for (std::size_t Index = 0; Index < Operands.size(); ++Index) {
+ const MachineOperand &Operand = Operands[Index];
+ HasFrameIndex |= Operand.getIf<FrameIndexOperand>() != nullptr;
+ const auto Reg = Operand.registerValue();
+ if (Reg && Reg->isVirtual() && Operand.isDef() && !Definitions.insert(*Reg).second) {
+ IsSSA = false;
+ }
+ if (!Operand.tiedOperandIndex() || !Operand.isUse())
+ continue;
+ HasTiedOperands = true;
+ const MachineOperand *Definition = Instruction->operandAt(*Operand.tiedOperandIndex());
+ if (Definition == nullptr || Definition->registerValue() != Reg)
+ AllTiedOperandsRewritten = false;
+ }
+ for (const MachineMemOperand *Memory : Instruction->memOperands())
+ HasFrameIndex |= Memory->description().Base.frameIndex().has_value();
+ }
+ }
+ const bool NoPHIs = !HasPHI;
+ const bool NoVRegs =
+ std::ranges::none_of(Function.registerInfo().virtualRegisters(),
+ [](const VirtualRegisterInfo &Info) { return !Info.IsTombstone; });
+
+ const auto ApplyComputed = [this, &Root, &Function](std::optional<bool> Explicit, bool Computed,
+ MachineFunctionProperty Property,
+ std::string_view Name) -> bool {
+ if (Explicit && *Explicit && !Computed) {
+ return fail(Root.range(), "explicit property '" + std::string(Name) +
+ "' conflicts with the parsed machine function");
+ }
+ if (Explicit.value_or(Computed))
+ Function.properties().set(Property);
+ else
+ Function.properties().clear(Property);
+ return false;
+ };
+ if (ApplyComputed(DocumentInfo.NoPHIs, NoPHIs, MachineFunctionProperty::NoPHIs, "noPhis") ||
+ ApplyComputed(DocumentInfo.IsSSA, IsSSA, MachineFunctionProperty::IsSSA, "isSSA") ||
+ ApplyComputed(DocumentInfo.NoVRegs, NoVRegs, MachineFunctionProperty::NoVRegs, "noVRegs")) {
+ return true;
+ }
+
+ if (DocumentInfo.HasFakeUses && !*DocumentInfo.HasFakeUses && HasFakeUse) {
+ return fail(Root.range(),
+ "explicit property 'hasFakeUses: false' conflicts with a fake-use instruction");
+ }
+ if (DocumentInfo.HasFakeUses.value_or(HasFakeUse))
+ Function.attributes().set(MachineFunctionAttribute::HasFakeUses);
+ else
+ Function.attributes().clear(MachineFunctionAttribute::HasFakeUses);
+
+ const bool Selected = Function.properties().has(MachineFunctionProperty::Selected);
+ const bool StructurallyPostSSA =
+ Selected && NoPHIs && !Function.properties().has(MachineFunctionProperty::IsSSA);
+ if (AllTiedOperandsRewritten && (HasTiedOperands || StructurallyPostSSA))
+ Function.properties().set(MachineFunctionProperty::TiedOpsRewritten);
+ else
+ Function.properties().clear(MachineFunctionProperty::TiedOpsRewritten);
+
+ if (HasFrameIndex)
+ Function.properties().clear(MachineFunctionProperty::NoFrameIndices);
+ else
+ Function.properties().set(MachineFunctionProperty::NoFrameIndices);
+
+ const MachineFrameState &FrameState = Function.frameInfo().state();
+ const bool StructurallyFinalizedFrame = Selected && NoVRegs && !HasFrameIndex &&
+ FrameState.IsCalleeSavedInfoValid &&
+ FrameState.MaxAlignment.has_value();
+ if (StructurallyFinalizedFrame) {
+ if (!Function.frameInfo().isFrozen()) {
+ auto Frozen = Function.frameInfo().freeze(FrameState.StackSize, *FrameState.MaxAlignment);
+ if (!Frozen)
+ return importError(Root.range(), Frozen.error(), "mir-frame-finalization");
+ }
+ Function.properties().set(MachineFunctionProperty::FrameFinalized);
+ } else {
+ Function.properties().clear(MachineFunctionProperty::FrameFinalized);
+ }
+
+ if (StructurallyFinalizedFrame && !HasExecutablePseudo)
+ Function.properties().set(MachineFunctionProperty::NoExecutablePseudos);
+ else
+ Function.properties().clear(MachineFunctionProperty::NoExecutablePseudos);
+ return false;
+}
+
+bool MIRDocumentParser::parseRegisters(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context) {
+ const yaml::Node *Registers = mappingValue(Root, "registers");
+ if (Registers != nullptr) {
+ if (requireKind(*Registers, yaml::NodeKind::Sequence, "registers"))
+ return true;
+ constexpr std::array RegisterKeys{
+ std::string_view{"id"},
+ std::string_view{"class"},
+ std::string_view{"preferred-register"},
+ std::string_view{"flags"},
+ };
+ std::unordered_set<std::uint32_t> IDs;
+ for (const yaml::NodeID ElementID : Registers->sequence()) {
+ const yaml::Node *Element = node(ElementID);
+ if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "register entry") ||
+ validateKeys(*Element, RegisterKeys, "register")) {
+ return true;
+ }
+ const yaml::Node *IDNode = mappingValue(*Element, "id");
+ const yaml::Node *ClassNode = mappingValue(*Element, "class");
+ if (IDNode == nullptr || ClassNode == nullptr)
+ return fail(Element->range(), "register entry requires 'id' and 'class'");
+ std::uint64_t WideID = 0;
+ std::string_view ClassName;
+ if (unsignedValue(*IDNode, WideID, "a 32-bit virtual-register ID") ||
+ WideID > std::numeric_limits<std::uint32_t>::max() ||
+ scalar(*ClassNode, ClassName, "register class or bank")) {
+ return true;
+ }
+ const auto TextID = static_cast<std::uint32_t>(WideID);
+ if (!IDs.insert(TextID).second)
+ return fail(IDNode->range(), "duplicate virtual register ID");
+ auto Created = Function.registerInfo().createVirtualRegister();
+ if (!Created)
+ return importError(IDNode->range(), Created.error());
+ Context.VirtualRegisters.push_back({TextID, *Created});
+
+ if (ClassName != "_") {
+ if (const RegisterClassDesc *Class = Target.registers().registerClass(ClassName)) {
+ auto SetResult = Function.registerInfo().setRegisterClass(*Created, Class->ID);
+ if (!SetResult)
+ return importError(ClassNode->range(), SetResult.error());
+ SetResult = Function.registerInfo().setRegisterBank(*Created, Class->DefaultBank);
+ if (!SetResult)
+ return importError(ClassNode->range(), SetResult.error());
+ } else if (const RegisterBankDesc *Bank = Target.registers().registerBank(ClassName)) {
+ auto SetResult = Function.registerInfo().setRegisterBank(*Created, Bank->ID);
+ if (!SetResult)
+ return importError(ClassNode->range(), SetResult.error());
+ } else {
+ return fail(ClassNode->range(),
+ "unknown register class or bank '" + std::string(ClassName) + "'");
+ }
+ }
+
+ if (const yaml::Node *Preferred = mappingValue(*Element, "preferred-register")) {
+ PhysicalRegister Reg;
+ if (physicalRegister(*Preferred, Reg))
+ return true;
+ auto AddResult = Function.registerInfo().addAllocationHint(*Created, Reg);
+ if (!AddResult)
+ return importError(Preferred->range(), AddResult.error());
+ }
+ if (const yaml::Node *Flags = mappingValue(*Element, "flags")) {
+ if (requireKind(*Flags, yaml::NodeKind::Sequence, "virtual-register flags"))
+ return true;
+ std::uint8_t Mask = 0;
+ for (const yaml::NodeID FlagID : Flags->sequence()) {
+ const yaml::Node *FlagNode = node(FlagID);
+ std::string_view Name;
+ if (FlagNode == nullptr || scalar(*FlagNode, Name, "virtual-register flag"))
+ return true;
+ const auto Flag = Target.mirText().virtualRegisterFlag(Name);
+ if (!Flag)
+ return fail(FlagNode->range(),
+ "unknown virtual-register flag '" + std::string(Name) + "'");
+ if ((Mask & *Flag) != 0)
+ return fail(FlagNode->range(), "duplicate virtual-register flag");
+ Mask = static_cast<std::uint8_t>(Mask | *Flag);
+ }
+ auto SetResult = Function.registerInfo().setTargetRegisterFlags(*Created, Mask);
+ if (!SetResult)
+ return importError(Flags->range(), SetResult.error());
+ }
+ }
+ }
+
+ const yaml::Node *LiveIns = mappingValue(Root, "liveins");
+ if (LiveIns != nullptr) {
+ if (requireKind(*LiveIns, yaml::NodeKind::Sequence, "liveins"))
+ return true;
+ constexpr std::array LiveInKeys{
+ std::string_view{"reg"},
+ std::string_view{"virtual-reg"},
+ };
+ for (const yaml::NodeID ElementID : LiveIns->sequence()) {
+ const yaml::Node *Element = node(ElementID);
+ if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "live-in entry") ||
+ validateKeys(*Element, LiveInKeys, "live-in")) {
+ return true;
+ }
+ const yaml::Node *RegNode = mappingValue(*Element, "reg");
+ if (RegNode == nullptr)
+ return fail(Element->range(), "live-in entry requires 'reg'");
+ PhysicalRegister Reg;
+ if (physicalRegister(*RegNode, Reg))
+ return true;
+ std::optional<Register> Initial;
+ if (const yaml::Node *VirtualNode = mappingValue(*Element, "virtual-reg")) {
+ std::string_view Text;
+ Register Virtual;
+ if (scalar(*VirtualNode, Text, "virtual register") ||
+ virtualRegister(Text, VirtualNode->range(), Context, Virtual)) {
+ return true;
+ }
+ Initial = Virtual;
+ }
+ auto AddResult = Function.registerInfo().addLiveIn(Reg, Initial);
+ if (!AddResult)
+ return importError(Element->range(), AddResult.error());
+ }
+ }
+
+ if (const yaml::Node *CalleeSaved = mappingValue(Root, "calleeSavedRegisters")) {
+ if (requireKind(*CalleeSaved, yaml::NodeKind::Sequence, "callee-saved registers"))
+ return true;
+ std::vector<PhysicalRegister> RegistersValue;
+ for (const yaml::NodeID RegID : CalleeSaved->sequence()) {
+ const yaml::Node *RegNode = node(RegID);
+ PhysicalRegister Reg;
+ if (RegNode == nullptr || physicalRegister(*RegNode, Reg))
+ return true;
+ RegistersValue.push_back(Reg);
+ }
+ auto SetResult = Function.records().setCalleeSavedRegisters(std::move(RegistersValue));
+ if (!SetResult)
+ return importError(CalleeSaved->range(), SetResult.error());
+ }
+ return false;
+}
+
+TextRange MIRDocumentParser::bodyDiagnosticRange(const yaml::Node &Body,
+ const TextRange &BodyRange) const noexcept {
+ if (Body.scalarStyle() != yaml::ScalarStyle::Literal)
+ return Body.range();
+
+ const std::size_t FirstBodyLine = Body.range().Begin.Line + 1U;
+ const std::size_t LastBodyLine = Body.range().End.Line;
+ if (FirstBodyLine > LastBodyLine || FirstBodyLine == 0)
+ return Body.range();
+
+ std::size_t Indent = 0;
+ bool FoundContent = false;
+ for (std::size_t Line = FirstBodyLine; Line <= LastBodyLine; ++Line) {
+ const std::string_view Text = Result.Source.line(Line);
+ std::size_t CurrentIndent = 0;
+ while (CurrentIndent < Text.size() && Text[CurrentIndent] == ' ')
+ ++CurrentIndent;
+ if (CurrentIndent == Text.size())
+ continue;
+ Indent = CurrentIndent;
+ FoundContent = true;
+ break;
+ }
+ if (!FoundContent)
+ return Body.range();
+
+ const TextPosition Fallback = Body.range().Begin;
+ const auto MapPosition = [this, FirstBodyLine, LastBodyLine, Indent,
+ Fallback](TextPosition Position) {
+ const std::size_t SourceLine = FirstBodyLine + Position.Line - 1U;
+ if (Position.Line == 0 || SourceLine > LastBodyLine || SourceLine > Result.Source.lineCount()) {
+ return Fallback;
+ }
+ const std::string_view SourceLineText = Result.Source.line(SourceLine);
+ const std::size_t BodyColumn = Position.Column == 0 ? 0 : Position.Column - 1U;
+ const std::size_t SourceColumn = std::min(Indent + BodyColumn, SourceLineText.size());
+ const std::size_t Offset = Result.Source.lineOffsets()[SourceLine - 1U] + SourceColumn;
+ return Result.Source.positionAt(Offset);
+ };
+ return {.Begin = MapPosition(BodyRange.Begin), .End = MapPosition(BodyRange.End)};
+}
+
+bool MIRDocumentParser::parseBody(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context) {
+ const yaml::Node *Body = mappingValue(Root, "body");
+ std::string BodyText;
+ TextRange BodyRange = Root.range();
+ if (Body != nullptr) {
+ std::string_view Text;
+ if (scalar(*Body, Text, "machine function body"))
+ return true;
+ BodyText = Text;
+ BodyRange = Body->range();
+ }
+
+ if (Options.DefaultPointerSizeInBits != 0)
+ Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits;
+ if (!effectiveDataLayout().empty()) {
+ const LLVMConstantSyntaxOptions PointerOptions = pointerLayoutOptions();
+ Context.PointerSizeInBits = [PointerOptions](std::uint32_t AddressSpace) {
+ return llvmPointerSizeInBits(AddressSpace, PointerOptions);
+ };
+ }
+ Context.SyncScopes = ActiveDocument->SyncScopes;
+ Context.InternSyncScope = [this](std::string_view Name) -> Expected<SyncScopeID> {
+ if (Name.empty())
+ return makeUnexpected(ErrorCode::ParseError, "synchronization scope name cannot be empty");
+ const auto Existing =
+ std::ranges::find_if(ActiveDocument->SyncScopes,
+ [Name](const MISyncScopeSlot &Slot) { return Slot.Name == Name; });
+ if (Existing != ActiveDocument->SyncScopes.end())
+ return Existing->ID;
+ if (ActiveDocument->SyncScopes.size() >=
+ static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "MIR document contains too many synchronization scopes");
+ }
+ const auto ID =
+ SyncScopeID::fromValue(static_cast<std::uint32_t>(ActiveDocument->SyncScopes.size() + 1U));
+ ActiveDocument->SyncScopes.push_back({.Name = std::string(Name), .ID = ID});
+ return ID;
+ };
+ MIParserOptions BodyOptions = Options.MachineInstructions;
+ BodyOptions.VerifyAfterParse = false;
+ MIParser Parser(Function, Target,
+ std::string(Result.Source.name()) + ":" + std::string(Function.name()),
+ std::move(BodyText), Context, BodyOptions);
+ auto Parsed = Parser.parse();
+ if (!Parsed) {
+ if (!Parser.diagnostics().empty()) {
+ const TextRange Range = Body == nullptr
+ ? BodyRange
+ : bodyDiagnosticRange(*Body, Parser.diagnostics().front().Range);
+ return fail(Range, Parser.diagnostics().front().Message, "mir-body");
+ }
+ return importError(BodyRange, Parsed.error(), "mir-body");
+ }
+ return false;
+}
+
+bool MIRDocumentParser::convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination) {
+ switch (Source.kind()) {
+ case yaml::NodeKind::Null:
+ Destination = MIRTargetNode::null();
+ return false;
+ case yaml::NodeKind::Scalar:
+ Destination = MIRTargetNode::scalar(std::string(Source.scalar()));
+ return false;
+ case yaml::NodeKind::Sequence: {
+ std::vector<MIRTargetNode> Values;
+ Values.reserve(Source.sequence().size());
+ for (const yaml::NodeID ElementID : Source.sequence()) {
+ const yaml::Node *Element = node(ElementID);
+ if (Element == nullptr)
+ return fail(Source.range(), "target MIR sequence contains an invalid node");
+ MIRTargetNode Value;
+ if (convertTargetNode(*Element, Value))
+ return true;
+ Values.push_back(std::move(Value));
+ }
+ Destination = MIRTargetNode::sequence(std::move(Values));
+ return false;
+ }
+ case yaml::NodeKind::Mapping: {
+ std::vector<MIRTargetMappingEntry> Entries;
+ Entries.reserve(Source.mapping().size());
+ for (const yaml::MappingEntry &SourceEntry : Source.mapping()) {
+ const yaml::Node *ValueNode = node(SourceEntry.Value);
+ if (ValueNode == nullptr)
+ return fail(SourceEntry.KeyRange, "target MIR mapping contains an invalid node");
+ MIRTargetNode Value;
+ if (convertTargetNode(*ValueNode, Value))
+ return true;
+ Entries.push_back({SourceEntry.Key, std::move(Value)});
+ }
+ Destination = MIRTargetNode::mapping(std::move(Entries));
+ return false;
+ }
+ }
+ return fail(Source.range(), "target MIR node has an unknown kind");
+}
+
+bool MIRDocumentParser::parseTargetState(const yaml::Node &Root, MachineFunction &Function) {
+ const yaml::Node *StateNode = mappingValue(Root, "machineFunctionInfo");
+ if (StateNode == nullptr)
+ return false;
+ MIRTargetNode TargetNode;
+ if (convertTargetNode(*StateNode, TargetNode))
+ return true;
+ auto State = Target.mirText().parseFunctionState(TargetNode);
+ if (!State)
+ return importError(StateNode->range(), State.error(), "mir-target-state");
+ auto SetResult = Function.setTargetState(std::move(*State));
+ if (!SetResult)
+ return importError(StateNode->range(), SetResult.error(), "mir-target-state");
+ return false;
+}
+
+bool MIRDocumentParser::validateDocumentReferences(const MachineFunction &Function) {
+ auto Verification = verifyMIRDocumentReferences(*ActiveDocument, Function, true);
+ if (Verification)
+ return false;
+ return importError(Result.Source.range(0, 0), Verification.error());
+}
+
+} // namespace rcc::mir
diff --git a/backend/lib/MIR/MIRFunctionRecordsParser.cpp b/backend/lib/MIR/MIRFunctionRecordsParser.cpp
new file mode 100644
index 0000000..dcd486e
--- /dev/null
+++ b/backend/lib/MIR/MIRFunctionRecordsParser.cpp
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "MIRDocumentParser.h"
+#include "MIRDocumentReferenceValidation.h"
+#include "MIRMetadataTextParser.h"
+#include "MIRParserUtilities.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionAttributes.h"
+#include "rcc/CodeGen/MachineFunctionProperties.h"
+#include "rcc/CodeGen/MachineFunctionRecords.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/MIR/LLVMConstantSyntax.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::mir {
+
+using parser_detail::parseBool;
+using parser_detail::parsePrefixedID;
+using parser_detail::parseSigned;
+using parser_detail::parseUnsigned;
+using parser_detail::parseUnsigned32;
+using parser_detail::trim;
+
+bool MIRDocumentParser::parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function) {
+ auto ParseLocation = [this, &Function](const yaml::Node &Entry,
+ MachineInstr *&Instruction) -> bool {
+ const yaml::Node *BlockNode = mappingValue(Entry, "bb");
+ const yaml::Node *OffsetNode = mappingValue(Entry, "offset");
+ if (BlockNode == nullptr || OffsetNode == nullptr)
+ return fail(Entry.range(), "instruction location requires 'bb' and 'offset'");
+ std::uint64_t Block = 0;
+ std::uint64_t Offset = 0;
+ if (unsignedValue(*BlockNode, Block, "a 32-bit basic block number") ||
+ unsignedValue(*OffsetNode, Offset, "a 32-bit instruction offset") ||
+ Block > std::numeric_limits<std::uint32_t>::max() ||
+ Offset > std::numeric_limits<std::uint32_t>::max()) {
+ return true;
+ }
+ Instruction = instructionAt(Function, static_cast<std::uint32_t>(Block),
+ static_cast<std::uint32_t>(Offset));
+ if (Instruction == nullptr)
+ return fail(Entry.range(), "instruction location is outside the machine function body");
+ return false;
+ };
+
+ if (const yaml::Node *CallSites = mappingValue(Root, "callSites")) {
+ if (requireKind(*CallSites, yaml::NodeKind::Sequence, "call-site records"))
+ return true;
+ constexpr std::array CallSiteKeys{
+ std::string_view{"bb"},
+ std::string_view{"offset"},
+ std::string_view{"fwdArgRegs"},
+ std::string_view{"calleeTypeIds"},
+ };
+ constexpr std::array ArgumentKeys{
+ std::string_view{"arg"},
+ std::string_view{"reg"},
+ };
+ for (const yaml::NodeID CallSiteID : CallSites->sequence()) {
+ const yaml::Node *CallSiteNode = node(CallSiteID);
+ if (CallSiteNode == nullptr ||
+ requireKind(*CallSiteNode, yaml::NodeKind::Mapping, "call-site record") ||
+ validateKeys(*CallSiteNode, CallSiteKeys, "call-site record")) {
+ return true;
+ }
+ MachineInstr *Call = nullptr;
+ if (ParseLocation(*CallSiteNode, Call))
+ return true;
+ if (Call == nullptr)
+ return fail(CallSiteNode->range(),
+ "instruction location is outside the machine function body");
+ MachineCallSiteInfo Info{
+ .CallInstruction = Call->stableID(),
+ .ForwardedArguments = {},
+ .CalleeTypeIDs = {},
+ };
+ if (const yaml::Node *Arguments = mappingValue(*CallSiteNode, "fwdArgRegs")) {
+ if (requireKind(*Arguments, yaml::NodeKind::Sequence, "forwarded call arguments")) {
+ return true;
+ }
+ for (const yaml::NodeID ArgumentID : Arguments->sequence()) {
+ const yaml::Node *Argument = node(ArgumentID);
+ if (Argument == nullptr ||
+ requireKind(*Argument, yaml::NodeKind::Mapping, "forwarded call argument") ||
+ validateKeys(*Argument, ArgumentKeys, "forwarded call argument")) {
+ return true;
+ }
+ const yaml::Node *NumberNode = mappingValue(*Argument, "arg");
+ const yaml::Node *RegNode = mappingValue(*Argument, "reg");
+ if (NumberNode == nullptr || RegNode == nullptr)
+ return fail(Argument->range(), "forwarded call argument requires 'arg' and 'reg'");
+ std::uint16_t Number = 0;
+ std::uint64_t WideNumber = 0;
+ PhysicalRegister Reg;
+ if (unsignedValue(*NumberNode, WideNumber, "a 16-bit argument number") ||
+ WideNumber > std::numeric_limits<std::uint16_t>::max() ||
+ physicalRegister(*RegNode, Reg)) {
+ return true;
+ }
+ Number = static_cast<std::uint16_t>(WideNumber);
+ Info.ForwardedArguments.push_back({Reg, Number});
+ }
+ }
+ if (const yaml::Node *TypeIDs = mappingValue(*CallSiteNode, "calleeTypeIds")) {
+ if (requireKind(*TypeIDs, yaml::NodeKind::Sequence, "callee type IDs"))
+ return true;
+ for (const yaml::NodeID TypeID : TypeIDs->sequence()) {
+ const yaml::Node *TypeNode = node(TypeID);
+ std::uint64_t Value = 0;
+ if (TypeNode == nullptr ||
+ unsignedValue(*TypeNode, Value, "an unsigned callee type ID")) {
+ return true;
+ }
+ Info.CalleeTypeIDs.push_back(Value);
+ }
+ }
+ auto AddResult = Function.records().addCallSite(std::move(Info));
+ if (!AddResult)
+ return importError(CallSiteNode->range(), AddResult.error());
+ }
+ }
+
+ if (const yaml::Node *CalledGlobals = mappingValue(Root, "calledGlobals")) {
+ if (requireKind(*CalledGlobals, yaml::NodeKind::Sequence, "called-global records"))
+ return true;
+ constexpr std::array Keys{
+ std::string_view{"bb"},
+ std::string_view{"offset"},
+ std::string_view{"callee"},
+ std::string_view{"flags"},
+ };
+ for (const yaml::NodeID CalledID : CalledGlobals->sequence()) {
+ const yaml::Node *CalledNode = node(CalledID);
+ if (CalledNode == nullptr ||
+ requireKind(*CalledNode, yaml::NodeKind::Mapping, "called-global record") ||
+ validateKeys(*CalledNode, Keys, "called-global record")) {
+ return true;
+ }
+ MachineInstr *Call = nullptr;
+ if (ParseLocation(*CalledNode, Call))
+ return true;
+ if (Call == nullptr)
+ return fail(CalledNode->range(),
+ "instruction location is outside the machine function body");
+ const yaml::Node *CalleeNode = mappingValue(*CalledNode, "callee");
+ const yaml::Node *FlagsNode = mappingValue(*CalledNode, "flags");
+ if (CalleeNode == nullptr || FlagsNode == nullptr)
+ return fail(CalledNode->range(), "called-global record requires 'callee' and 'flags'");
+ std::string_view Callee;
+ std::uint64_t WideFlags = 0;
+ if (scalar(*CalleeNode, Callee, "callee name") ||
+ unsignedValue(*FlagsNode, WideFlags, "32-bit called-global flags") ||
+ WideFlags > std::numeric_limits<std::uint32_t>::max()) {
+ return true;
+ }
+ if (ActiveDocument->EmbeddedIR && !ActiveDocument->EmbeddedIR->hasGlobalValue(Callee)) {
+ return fail(CalleeNode->range(), "called global references an undefined embedded global");
+ }
+ auto AddResult =
+ Function.records().addCalledGlobal({.CallInstruction = Call->stableID(),
+ .Callee = std::string(Callee),
+ .Flags = static_cast<std::uint32_t>(WideFlags)});
+ if (!AddResult)
+ return importError(CalledNode->range(), AddResult.error());
+ }
+ }
+
+ if (const yaml::Node *Substitutions = mappingValue(Root, "debugValueSubstitutions")) {
+ if (requireKind(*Substitutions, yaml::NodeKind::Sequence, "debug value substitutions")) {
+ return true;
+ }
+ constexpr std::array Keys{
+ std::string_view{"srcinst"}, std::string_view{"srcop"}, std::string_view{"dstinst"},
+ std::string_view{"dstop"}, std::string_view{"subreg"},
+ };
+ for (const yaml::NodeID SubstitutionID : Substitutions->sequence()) {
+ const yaml::Node *Substitution = node(SubstitutionID);
+ if (Substitution == nullptr ||
+ requireKind(*Substitution, yaml::NodeKind::Mapping, "debug value substitution") ||
+ validateKeys(*Substitution, Keys, "debug value substitution")) {
+ return true;
+ }
+ std::array<std::uint32_t, 5> Values{};
+ for (std::size_t Index = 0; Index < Keys.size(); ++Index) {
+ const yaml::Node *ValueNode = mappingValue(*Substitution, Keys[Index]);
+ if (ValueNode == nullptr)
+ return fail(Substitution->range(), "debug value substitution requires every fixed field");
+ std::uint64_t Wide = 0;
+ if (unsignedValue(*ValueNode, Wide, "a 32-bit debug substitution value") ||
+ Wide > std::numeric_limits<std::uint32_t>::max()) {
+ return true;
+ }
+ Values[Index] = static_cast<std::uint32_t>(Wide);
+ }
+ auto AddResult = Function.records().addDebugValueSubstitution(
+ {.Source = {Values[0], Values[1]},
+ .Destination = {Values[2], Values[3]},
+ .Subregister = SubRegIndex::fromValue(Values[4])});
+ if (!AddResult)
+ return importError(Substitution->range(), AddResult.error());
+ }
+ }
+
+ if (const yaml::Node *PrefetchTargets = mappingValue(Root, "prefetch-targets")) {
+ if (requireKind(*PrefetchTargets, yaml::NodeKind::Sequence, "prefetch targets"))
+ return true;
+ for (const yaml::NodeID TargetID : PrefetchTargets->sequence()) {
+ const yaml::Node *TargetNode = node(TargetID);
+ std::string_view Text;
+ if (TargetNode == nullptr || scalar(*TargetNode, Text, "prefetch target"))
+ return true;
+ Text = trim(Text);
+ if (!Text.starts_with("bb_id"))
+ return fail(TargetNode->range(), "prefetch target must start with 'bb_id'");
+ Text = trim(Text.substr(std::string_view("bb_id").size()));
+ std::array<std::uint32_t, 3> Values{};
+ for (std::size_t Index = 0; Index < Values.size(); ++Index) {
+ const std::size_t Comma = Text.find(',');
+ const std::string_view Part = Index + 1 == Values.size() ? Text : Text.substr(0, Comma);
+ if ((Index + 1 != Values.size() && Comma == std::string_view::npos) ||
+ !parseUnsigned32(trim(Part), Values[Index])) {
+ return fail(TargetNode->range(), "prefetch target has invalid numeric fields");
+ }
+ if (Index + 1 != Values.size())
+ Text = trim(Text.substr(Comma + 1));
+ }
+ auto AddResult = Function.records().addPrefetchTarget(
+ {.Block = {Values[0], Values[1]}, .CallsiteIndex = Values[2]});
+ if (!AddResult)
+ return importError(TargetNode->range(), AddResult.error());
+ }
+ }
+
+ if (const yaml::Node *EntryValues = mappingValue(Root, "entry_values")) {
+ if (requireKind(*EntryValues, yaml::NodeKind::Sequence, "entry values"))
+ return true;
+ constexpr std::array Keys{
+ std::string_view{"entry-value-register"},
+ std::string_view{"debug-info-variable"},
+ std::string_view{"debug-info-expression"},
+ std::string_view{"debug-info-location"},
+ };
+ for (const yaml::NodeID EntryID : EntryValues->sequence()) {
+ const yaml::Node *Entry = node(EntryID);
+ if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "entry value") ||
+ validateKeys(*Entry, Keys, "entry value")) {
+ return true;
+ }
+ const yaml::Node *RegNode = mappingValue(*Entry, "entry-value-register");
+ const yaml::Node *VariableNode = mappingValue(*Entry, "debug-info-variable");
+ const yaml::Node *ExpressionNode = mappingValue(*Entry, "debug-info-expression");
+ const yaml::Node *LocationNode = mappingValue(*Entry, "debug-info-location");
+ if (RegNode == nullptr || VariableNode == nullptr || ExpressionNode == nullptr ||
+ LocationNode == nullptr) {
+ return fail(Entry->range(), "entry value requires all four fixed fields");
+ }
+ PhysicalRegister Reg;
+ if (physicalRegister(*RegNode, Reg))
+ return true;
+ MachineVariableDebugInfo DebugInfo;
+ if (parseVariableDebugInfo(*Entry, Function, DebugInfo))
+ return true;
+ if (!DebugInfo.Variable || !DebugInfo.Expression || !DebugInfo.Location) {
+ return fail(Entry->range(), "entry value debug metadata cannot be empty",
+ "mir-debug-metadata");
+ }
+ auto AddResult =
+ Function.records().addEntryValue({.Reg = Reg, .DebugInfo = std::move(DebugInfo)});
+ if (!AddResult)
+ return importError(Entry->range(), AddResult.error());
+ }
+ }
+ return false;
+}
+
+} // namespace rcc::mir
diff --git a/backend/lib/MIR/MIRMetadataParser.cpp b/backend/lib/MIR/MIRMetadataParser.cpp
new file mode 100644
index 0000000..6ce717b
--- /dev/null
+++ b/backend/lib/MIR/MIRMetadataParser.cpp
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "MIRDocumentParser.h"
+#include "MIRDocumentReferenceValidation.h"
+#include "MIRMetadataTextParser.h"
+#include "MIRParserUtilities.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineConstantPool.h"
+#include "rcc/CodeGen/MachineFrameInfo.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionAttributes.h"
+#include "rcc/CodeGen/MachineFunctionProperties.h"
+#include "rcc/CodeGen/MachineFunctionRecords.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineJumpTableInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/MIR/LLVMConstantSyntax.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::mir {
+
+using parser_detail::parseBool;
+using parser_detail::parsePrefixedID;
+using parser_detail::parseSigned;
+using parser_detail::parseUnsigned;
+using parser_detail::parseUnsigned32;
+using parser_detail::trim;
+
+Expected<MachineMetadataReference>
+MIRDocumentParser::resolveMetadataReference(const MachineFunction &Function,
+ std::uint32_t Slot) const {
+ if (ActiveDocument != nullptr && ActiveDocument->EmbeddedIR &&
+ ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) {
+ return MachineMetadataReference::embeddedIR(Slot);
+ }
+ if (Function.metadata().node(Slot) != nullptr)
+ return MachineMetadataReference::machineFunction(Slot);
+ return makeUnexpected(ErrorCode::ParseError,
+ "metadata reference uses undefined slot '!" + std::to_string(Slot) + "'");
+}
+
+bool MIRDocumentParser::parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function,
+ MachineVariableDebugInfo &DebugInfo) {
+ const auto Resolve = [this, &Function](std::uint32_t Slot) {
+ return resolveMetadataReference(Function, Slot);
+ };
+ if (const yaml::Node *VariableNode = mappingValue(Entry, "debug-info-variable")) {
+ std::string_view Text;
+ if (scalar(*VariableNode, Text, "debug variable"))
+ return true;
+ if (!Text.empty()) {
+ MetadataTextParser Parser(std::string(Text), Resolve);
+ auto Reference = Parser.parseReferenceOnly();
+ if (!Reference)
+ return importError(VariableNode->range(), Reference.error(), "mir-debug-metadata");
+ if (Reference->scope() != MachineMetadataScope::EmbeddedIR) {
+ return fail(VariableNode->range(),
+ "debug variable must reference embedded LLVM IR metadata",
+ "mir-debug-metadata");
+ }
+ DebugInfo.Variable = *Reference;
+ }
+ }
+ if (const yaml::Node *ExpressionNode = mappingValue(Entry, "debug-info-expression")) {
+ std::string_view Text;
+ if (scalar(*ExpressionNode, Text, "debug expression"))
+ return true;
+ if (!Text.empty()) {
+ MetadataTextParser Parser(std::string(Text), Resolve);
+ auto Value = Parser.parseValue();
+ if (!Value)
+ return importError(ExpressionNode->range(), Value.error(), "mir-debug-metadata");
+ if (Value->reference() != nullptr &&
+ Value->reference()->scope() != MachineMetadataScope::EmbeddedIR) {
+ return fail(ExpressionNode->range(),
+ "debug expression reference must use embedded LLVM IR metadata",
+ "mir-debug-metadata");
+ }
+ DebugInfo.Expression = std::move(*Value);
+ }
+ }
+ if (const yaml::Node *LocationNode = mappingValue(Entry, "debug-info-location")) {
+ std::string_view Text;
+ if (scalar(*LocationNode, Text, "debug location"))
+ return true;
+ if (!Text.empty()) {
+ MetadataTextParser Parser(std::string(Text), Resolve);
+ auto Location = Parser.parseLocation();
+ if (!Location)
+ return importError(LocationNode->range(), Location.error(), "mir-debug-metadata");
+ if (Location->reference() != nullptr &&
+ Location->reference()->scope() != MachineMetadataScope::EmbeddedIR) {
+ return fail(LocationNode->range(),
+ "variable debug location must reference embedded LLVM IR metadata",
+ "mir-debug-metadata");
+ }
+ DebugInfo.Location = std::move(*Location);
+ }
+ }
+ if (!DebugInfo.Variable && !DebugInfo.Expression && !DebugInfo.Location)
+ return false;
+ auto Verification = DebugInfo.verifyStructure();
+ if (!Verification)
+ return importError(Entry.range(), Verification.error(), "mir-debug-metadata");
+ return false;
+}
+
+bool MIRDocumentParser::parseMetadata(const yaml::Node &Root, MachineFunction &Function,
+ MIParserContext &Context) {
+ if (ActiveDocument->EmbeddedIR) {
+ for (const EmbeddedIRMetadataDefinition &Reference :
+ ActiveDocument->EmbeddedIR->metadataDefinitions()) {
+ std::uint32_t ID = 0;
+ if (!parseUnsigned32(Reference.Name, ID))
+ continue;
+ if (std::ranges::any_of(Context.Metadata,
+ [ID](const MIMetadataSlot &Slot) { return Slot.TextID == ID; })) {
+ return fail(Root.range(), "embedded LLVM IR metadata slots are not unique");
+ }
+ Context.Metadata.push_back(
+ {.TextID = ID, .Reference = MachineMetadataReference::embeddedIR(ID)});
+ }
+ }
+
+ const yaml::Node *Metadata = mappingValue(Root, "machineMetadataNodes");
+ if (Metadata == nullptr)
+ return false;
+ if (requireKind(*Metadata, yaml::NodeKind::Sequence, "machine metadata nodes"))
+ return true;
+
+ struct Definition final {
+ const yaml::Node *Node = nullptr;
+ std::uint32_t Slot = 0;
+ bool IsDistinct = false;
+ std::string_view Elements;
+ };
+ std::vector<Definition> Definitions;
+ std::unordered_set<std::uint32_t> Slots;
+
+ for (const yaml::NodeID DefinitionID : Metadata->sequence()) {
+ const yaml::Node *DefinitionNode = node(DefinitionID);
+ std::string_view Text;
+ if (DefinitionNode == nullptr || scalar(*DefinitionNode, Text, "machine metadata definition")) {
+ return true;
+ }
+ Text = trim(Text);
+ if (!Text.starts_with('!'))
+ return fail(DefinitionNode->range(), "machine metadata definition must start with '!N'");
+ const std::size_t Equals = Text.find('=');
+ if (Equals == std::string_view::npos)
+ return fail(DefinitionNode->range(), "machine metadata definition requires '='");
+ std::uint32_t Slot = 0;
+ if (!parseUnsigned32(trim(Text.substr(1, Equals - 1)), Slot))
+ return fail(DefinitionNode->range(), "machine metadata definition has an invalid slot");
+ if (!Slots.insert(Slot).second)
+ return fail(DefinitionNode->range(), "duplicate machine metadata slot");
+ std::string_view Right = trim(Text.substr(Equals + 1));
+ bool IsDistinct = false;
+ if (Right.starts_with("distinct")) {
+ Right = trim(Right.substr(std::string_view("distinct").size()));
+ IsDistinct = true;
+ }
+ if (!Right.starts_with("!{") || !Right.ends_with('}'))
+ return fail(DefinitionNode->range(), "machine metadata node must be a metadata tuple");
+ Definitions.push_back({.Node = DefinitionNode,
+ .Slot = Slot,
+ .IsDistinct = IsDistinct,
+ .Elements = Right.substr(2, Right.size() - 3)});
+ Context.Metadata.push_back(
+ {.TextID = Slot, .Reference = MachineMetadataReference::machineFunction(Slot)});
+ }
+
+ auto ResolveReference = [this,
+ &Slots](std::uint32_t Slot,
+ const TextRange &Range) -> Expected<MachineMetadataElement> {
+ MachineMetadataReference Reference;
+ if (ActiveDocument->EmbeddedIR != std::nullopt &&
+ ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) {
+ Reference = MachineMetadataReference::embeddedIR(Slot);
+ } else if (Slots.contains(Slot)) {
+ Reference = MachineMetadataReference::machineFunction(Slot);
+ } else {
+ return makeUnexpected(ErrorCode::ParseError, "metadata tuple references undefined slot '!" +
+ std::to_string(Slot) + "' at line " +
+ std::to_string(Range.Begin.Line));
+ }
+ return MachineMetadataElement::reference(Reference);
+ };
+
+ for (const Definition &Definition : Definitions) {
+ MachineMetadataNode NewNode{
+ .Slot = Definition.Slot,
+ .IsDistinct = Definition.IsDistinct,
+ .Elements = {},
+ };
+ std::string_view Remaining = trim(Definition.Elements);
+ while (!Remaining.empty()) {
+ std::size_t End = 0;
+ bool InString = false;
+ bool Escaped = false;
+ for (; End < Remaining.size(); ++End) {
+ const char Character = Remaining[End];
+ if (InString && Escaped) {
+ Escaped = false;
+ continue;
+ }
+ if (InString && Character == '\\') {
+ Escaped = true;
+ continue;
+ }
+ if (Character == '"') {
+ InString = !InString;
+ continue;
+ }
+ if (!InString && Character == ',')
+ break;
+ }
+ if (InString)
+ return fail(Definition.Node->range(), "unterminated machine metadata string");
+ std::string_view Element = trim(Remaining.substr(0, End));
+ if (Element.empty())
+ return fail(Definition.Node->range(), "empty machine metadata tuple element");
+
+ if (Element == "null") {
+ NewNode.Elements.push_back(MachineMetadataElement::null());
+ } else if (Element.starts_with('!') && Element.size() > 1 && Element[1] >= '0' &&
+ Element[1] <= '9') {
+ std::uint32_t ReferenceSlot = 0;
+ if (!parseUnsigned32(Element.substr(1), ReferenceSlot))
+ return fail(Definition.Node->range(), "invalid metadata tuple reference");
+ auto Reference = ResolveReference(ReferenceSlot, Definition.Node->range());
+ if (!Reference)
+ return importError(Definition.Node->range(), Reference.error());
+ NewNode.Elements.push_back(std::move(*Reference));
+ } else if (Element.starts_with("!\"") && Element.ends_with('"')) {
+ std::string Value;
+ std::string_view Encoded = Element.substr(2, Element.size() - 3);
+ for (std::size_t Index = 0; Index < Encoded.size(); ++Index) {
+ if (Encoded[Index] == '\\' && Index + 1 < Encoded.size()) {
+ const char EscapedCharacter = Encoded[++Index];
+ if (EscapedCharacter != '\\' && EscapedCharacter != '"')
+ return fail(Definition.Node->range(), "unsupported machine metadata string escape");
+ Value.push_back(EscapedCharacter);
+ } else {
+ Value.push_back(Encoded[Index]);
+ }
+ }
+ NewNode.Elements.push_back(MachineMetadataElement::string(std::move(Value)));
+ } else {
+ return fail(Definition.Node->range(), "unsupported machine metadata tuple element");
+ }
+
+ if (End == Remaining.size())
+ Remaining = {};
+ else
+ Remaining = trim(Remaining.substr(End + 1));
+ }
+ auto AddResult = Function.metadata().addNode(std::move(NewNode));
+ if (!AddResult)
+ return importError(Definition.Node->range(), AddResult.error());
+ }
+ return false;
+}
+
+} // namespace rcc::mir
diff --git a/backend/lib/MIR/MIRMetadataTextParser.cpp b/backend/lib/MIR/MIRMetadataTextParser.cpp
new file mode 100644
index 0000000..51b039f
--- /dev/null
+++ b/backend/lib/MIR/MIRMetadataTextParser.cpp
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "MIRMetadataTextParser.h"
+
+#include "MIRParserUtilities.h"
+#include "rcc/MIR/MIDebugSyntax.h"
+
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::mir {
+
+using parser_detail::parseSigned;
+using parser_detail::parseUnsigned;
+using parser_detail::parseUnsigned32;
+
+Expected<void> MetadataTextParser::expect(MITokenKind Kind, std::string_view Description) {
+ if (consume(Kind))
+ return {};
+ return makeUnexpected(ErrorCode::ParseError,
+ "expected " + std::string(Description) + " in MIR metadata scalar");
+}
+
+Expected<void> MetadataTextParser::finish() {
+ while (consume(MITokenKind::Newline)) {
+ }
+ if (Lexer.failed()) {
+ if (!Lexer.diagnostics().empty()) {
+ return makeUnexpected(ErrorCode::ParseError,
+ std::string(Lexer.diagnostics().front().Message));
+ }
+ return makeUnexpected(ErrorCode::ParseError, "invalid MIR metadata scalar");
+ }
+ if (!at(MITokenKind::EndOfFile))
+ return makeUnexpected(ErrorCode::ParseError, "unexpected trailing MIR metadata tokens");
+ return {};
+}
+
+Expected<MachineMetadataReference> MetadataTextParser::parseReference() {
+ if (auto Exclaim = expect(MITokenKind::Exclaim, "'!' before a metadata slot"); !Exclaim)
+ return std::unexpected(Exclaim.error());
+ if (!at(MITokenKind::IntegerLiteral))
+ return makeUnexpected(ErrorCode::ParseError, "expected a 32-bit metadata slot");
+ std::uint32_t Slot = 0;
+ if (!parseUnsigned32(token().integerValue(), Slot))
+ return makeUnexpected(ErrorCode::ParseError, "metadata slot is outside the 32-bit range");
+ advance();
+ return Resolve(Slot);
+}
+
+Expected<MachineMetadataReference> MetadataTextParser::parseReferenceOnly() {
+ auto Reference = parseReference();
+ if (!Reference)
+ return std::unexpected(Reference.error());
+ if (auto Finished = finish(); !Finished)
+ return std::unexpected(Finished.error());
+ return Reference;
+}
+
+Expected<MachineDIExpression> MetadataTextParser::parseExpression() {
+ if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DIExpression")) {
+ return makeUnexpected(ErrorCode::ParseError, "expected an inline !DIExpression");
+ }
+ advance();
+ if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DIExpression"); !Left)
+ return std::unexpected(Left.error());
+ std::vector<std::uint64_t> Elements;
+ while (!at(MITokenKind::RightParen)) {
+ if (at(MITokenKind::Identifier)) {
+ const auto Opcode = parseDIExpressionOpcodeName(token().stringValue());
+ if (!Opcode) {
+ return makeUnexpected(ErrorCode::ParseError, "unknown DIExpression opcode '" +
+ std::string(token().stringValue()) + "'");
+ }
+ Elements.push_back(*Opcode);
+ advance();
+ } else if (at(MITokenKind::IntegerLiteral)) {
+ std::int64_t Signed = 0;
+ std::uint64_t Unsigned = 0;
+ if (parseSigned(token().integerValue(), Signed))
+ Elements.push_back(static_cast<std::uint64_t>(Signed));
+ else if (parseUnsigned(token().integerValue(), Unsigned))
+ Elements.push_back(Unsigned);
+ else
+ return makeUnexpected(ErrorCode::ParseError, "DIExpression integer exceeds 64 bits");
+ advance();
+ } else {
+ return makeUnexpected(ErrorCode::ParseError, "expected a DIExpression opcode or integer");
+ }
+ if (!consume(MITokenKind::Comma))
+ break;
+ }
+ if (auto Right = expect(MITokenKind::RightParen, "')' after !DIExpression"); !Right)
+ return std::unexpected(Right.error());
+ return MachineDIExpression(std::move(Elements));
+}
+
+Expected<MachineMetadataValue> MetadataTextParser::parseValue() {
+ MachineMetadataValue Value;
+ if (at(MITokenKind::Exclaim)) {
+ auto Reference = parseReference();
+ if (!Reference)
+ return std::unexpected(Reference.error());
+ auto Created = MachineMetadataValue::reference(*Reference);
+ if (!Created)
+ return std::unexpected(Created.error());
+ Value = std::move(*Created);
+ } else {
+ auto Expression = parseExpression();
+ if (!Expression)
+ return std::unexpected(Expression.error());
+ Value = MachineMetadataValue::expression(std::move(*Expression));
+ }
+ if (auto Finished = finish(); !Finished)
+ return std::unexpected(Finished.error());
+ return Value;
+}
+
+Expected<void>
+MetadataTextParser::parseDILocation(std::vector<MachineDILocationFrame> &Frames,
+ std::optional<MachineMetadataReference> &Terminal) {
+ if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DILocation")) {
+ return makeUnexpected(ErrorCode::ParseError, "expected an inline !DILocation");
+ }
+ advance();
+ if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DILocation"); !Left)
+ return Left;
+
+ MachineDILocationFrame Frame;
+ bool HasLine = false;
+ bool HasScope = false;
+ std::vector<MachineDILocationFrame> NestedFrames;
+ std::optional<MachineMetadataReference> NestedTerminal;
+ std::unordered_set<std::string> Seen;
+ while (!at(MITokenKind::RightParen)) {
+ if (!at(MITokenKind::Identifier))
+ return makeUnexpected(ErrorCode::ParseError, "expected a DILocation field name");
+ const std::string Field(token().stringValue());
+ if (!Seen.insert(Field).second) {
+ return makeUnexpected(ErrorCode::ParseError, "duplicate DILocation field '" + Field + "'");
+ }
+ advance();
+ if (auto Colon = expect(MITokenKind::Colon, "':' after a DILocation field"); !Colon)
+ return Colon;
+
+ if (Field == "line") {
+ std::uint32_t Value = 0;
+ if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned32(token().integerValue(), Value)) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "DILocation line must be an unsigned 32-bit integer");
+ }
+ Frame.Line = Value;
+ HasLine = true;
+ advance();
+ } else if (Field == "column") {
+ std::uint64_t Value = 0;
+ if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) ||
+ Value > std::numeric_limits<std::uint16_t>::max()) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "DILocation column must be an unsigned 16-bit integer");
+ }
+ Frame.Column = static_cast<std::uint16_t>(Value);
+ advance();
+ } else if (Field == "scope") {
+ auto Scope = parseReference();
+ if (!Scope)
+ return std::unexpected(Scope.error());
+ if (Scope->scope() != MachineMetadataScope::EmbeddedIR) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "DILocation scope must reference embedded LLVM IR metadata");
+ }
+ Frame.Scope = *Scope;
+ HasScope = true;
+ } else if (Field == "inlinedAt") {
+ if (at(MITokenKind::MetadataKeyword) && token().isMetadataKeyword("!DILocation")) {
+ if (auto Nested = parseDILocation(NestedFrames, NestedTerminal); !Nested)
+ return Nested;
+ } else {
+ auto Reference = parseReference();
+ if (!Reference)
+ return std::unexpected(Reference.error());
+ if (Reference->scope() != MachineMetadataScope::EmbeddedIR) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "DILocation inlinedAt must reference embedded LLVM IR metadata");
+ }
+ NestedTerminal = *Reference;
+ }
+ } else if (Field == "isImplicitCode") {
+ if (!at(MITokenKind::Identifier) ||
+ (token().stringValue() != "true" && token().stringValue() != "false")) {
+ return makeUnexpected(ErrorCode::ParseError,
+ "DILocation isImplicitCode must be true or false");
+ }
+ Frame.IsImplicitCode = token().stringValue() == "true";
+ advance();
+ } else if (Field == "atomGroup") {
+ std::uint64_t Value = 0;
+ if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) ||
+ Value > MachineDILocation::MaxAtomGroup) {
+ return makeUnexpected(ErrorCode::ParseError, "DILocation atomGroup exceeds 61 bits");
+ }
+ Frame.AtomGroup = Value;
+ advance();
+ } else if (Field == "atomRank") {
+ std::uint64_t Value = 0;
+ if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) ||
+ Value > 7) {
+ return makeUnexpected(ErrorCode::ParseError, "DILocation atomRank exceeds three bits");
+ }
+ Frame.AtomRank = static_cast<std::uint8_t>(Value);
+ advance();
+ } else {
+ return makeUnexpected(ErrorCode::ParseError, "unknown DILocation field '" + Field + "'");
+ }
+ if (!consume(MITokenKind::Comma))
+ break;
+ }
+ if (auto Right = expect(MITokenKind::RightParen, "')' after !DILocation"); !Right)
+ return Right;
+ if (!HasLine || !HasScope)
+ return makeUnexpected(ErrorCode::ParseError, "DILocation requires line and scope fields");
+ Frames.push_back(Frame);
+ Frames.insert(Frames.end(), NestedFrames.begin(), NestedFrames.end());
+ Terminal = NestedTerminal;
+ return {};
+}
+
+Expected<MachineDebugLocation> MetadataTextParser::parseLocation() {
+ MachineDebugLocation Location;
+ if (at(MITokenKind::Exclaim)) {
+ auto Reference = parseReference();
+ if (!Reference)
+ return std::unexpected(Reference.error());
+ auto Created = MachineDebugLocation::reference(*Reference);
+ if (!Created)
+ return std::unexpected(Created.error());
+ Location = std::move(*Created);
+ } else {
+ std::vector<MachineDILocationFrame> Frames;
+ std::optional<MachineMetadataReference> Terminal;
+ if (auto Parsed = parseDILocation(Frames, Terminal); !Parsed)
+ return std::unexpected(Parsed.error());
+ auto Created = MachineDILocation::create(std::move(Frames), Terminal);
+ if (!Created)
+ return std::unexpected(Created.error());
+ Location = MachineDebugLocation::inlineLocation(std::move(*Created));
+ }
+ if (auto Finished = finish(); !Finished)
+ return std::unexpected(Finished.error());
+ return Location;
+}
+
+} // namespace rcc::mir
diff --git a/backend/lib/MIR/MIRMetadataTextParser.h b/backend/lib/MIR/MIRMetadataTextParser.h
new file mode 100644
index 0000000..b8ebb12
--- /dev/null
+++ b/backend/lib/MIR/MIRMetadataTextParser.h
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// NOLINTNEXTLINE(llvm-header-guard)
+#ifndef RCC_LIB_MIR_MIRMETADATATEXTPARSER_H
+#define RCC_LIB_MIR_MIRMETADATATEXTPARSER_H
+
+#include "rcc/CodeGen/MachineDebugInfo.h"
+#include "rcc/CodeGen/MachineMetadata.h"
+#include "rcc/MIR/MILexer.h"
+#include "rcc/Support/Error.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace rcc::mir {
+
+class MetadataTextParser final {
+public:
+ using Resolver = std::function<Expected<MachineMetadataReference>(std::uint32_t Slot)>;
+
+ MetadataTextParser(std::string Text, Resolver Resolve)
+ : Lexer("MIR metadata scalar", std::move(Text)), Resolve(std::move(Resolve)) {
+ do {
+ Tokens.push_back(Lexer.next());
+ } while (!Tokens.back().isErrorOrEnd());
+ }
+
+ [[nodiscard]] Expected<MachineMetadataReference> parseReferenceOnly();
+ [[nodiscard]] Expected<MachineMetadataValue> parseValue();
+ [[nodiscard]] Expected<MachineDebugLocation> parseLocation();
+
+private:
+ [[nodiscard]] const MIToken &token() const noexcept {
+ return Tokens[Position];
+ }
+ [[nodiscard]] bool at(MITokenKind Kind) const noexcept {
+ return token().is(Kind);
+ }
+ void advance() noexcept {
+ if (Position + 1U < Tokens.size())
+ ++Position;
+ }
+ [[nodiscard]] bool consume(MITokenKind Kind) noexcept {
+ if (!at(Kind))
+ return false;
+ advance();
+ return true;
+ }
+ [[nodiscard]] Expected<void> expect(MITokenKind Kind, std::string_view Description);
+ [[nodiscard]] Expected<void> finish();
+ [[nodiscard]] Expected<MachineMetadataReference> parseReference();
+ [[nodiscard]] Expected<MachineDIExpression> parseExpression();
+ [[nodiscard]] Expected<void> parseDILocation(std::vector<MachineDILocationFrame> &Frames,
+ std::optional<MachineMetadataReference> &Terminal);
+
+ MILexer Lexer;
+ Resolver Resolve;
+ std::vector<MIToken> Tokens;
+ std::size_t Position = 0;
+};
+
+} // namespace rcc::mir
+
+#endif // RCC_LIB_MIR_MIRMETADATATEXTPARSER_H
diff --git a/backend/lib/MIR/MIRParserUtilities.h b/backend/lib/MIR/MIRParserUtilities.h
new file mode 100644
index 0000000..2f69548
--- /dev/null
+++ b/backend/lib/MIR/MIRParserUtilities.h
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// NOLINTNEXTLINE(llvm-header-guard)
+#ifndef RCC_LIB_MIR_MIRPARSERUTILITIES_H
+#define RCC_LIB_MIR_MIRPARSERUTILITIES_H
+
+#include <charconv>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string_view>
+
+namespace rcc::mir::parser_detail {
+
+[[nodiscard]] inline std::string_view trim(std::string_view Text) noexcept {
+ while (!Text.empty() && (Text.front() == ' ' || Text.front() == '\t' || Text.front() == '\r' ||
+ Text.front() == '\n')) {
+ Text.remove_prefix(1);
+ }
+ while (!Text.empty() && (Text.back() == ' ' || Text.back() == '\t' || Text.back() == '\r' ||
+ Text.back() == '\n')) {
+ Text.remove_suffix(1);
+ }
+ return Text;
+}
+
+[[nodiscard]] inline bool parseUnsigned(std::string_view Text, std::uint64_t &Value) noexcept {
+ Text = trim(Text);
+ int Base = 10;
+ if (Text.starts_with("0x") || Text.starts_with("0X")) {
+ Text.remove_prefix(2);
+ Base = 16;
+ }
+ if (Text.empty())
+ return false;
+ const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, Base);
+ return Error == std::errc{} && End == Text.data() + Text.size();
+}
+
+[[nodiscard]] inline bool parseSigned(std::string_view Text, std::int64_t &Value) noexcept {
+ Text = trim(Text);
+ if (Text.empty())
+ return false;
+ const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10);
+ return Error == std::errc{} && End == Text.data() + Text.size();
+}
+
+[[nodiscard]] inline bool parseUnsigned32(std::string_view Text, std::uint32_t &Value) noexcept {
+ std::uint64_t Wide = 0;
+ if (!parseUnsigned(Text, Wide) || Wide > std::numeric_limits<std::uint32_t>::max())
+ return false;
+ Value = static_cast<std::uint32_t>(Wide);
+ return true;
+}
+
+[[nodiscard]] inline bool parseBool(std::string_view Text, bool &Value) noexcept {
+ if (Text == "true") {
+ Value = true;
+ return true;
+ }
+ if (Text == "false") {
+ Value = false;
+ return true;
+ }
+ return false;
+}
+
+[[nodiscard]] inline std::optional<std::uint32_t>
+parsePrefixedID(std::string_view Text, std::string_view Prefix) noexcept {
+ Text = trim(Text);
+ if (!Text.starts_with(Prefix))
+ return std::nullopt;
+ Text.remove_prefix(Prefix.size());
+ const std::size_t Suffix = Text.find_first_of(". \t");
+ if (Suffix != std::string_view::npos)
+ Text = Text.substr(0, Suffix);
+ std::uint32_t ID = 0;
+ if (!parseUnsigned32(Text, ID))
+ return std::nullopt;
+ return ID;
+}
+
+} // namespace rcc::mir::parser_detail
+
+#endif // RCC_LIB_MIR_MIRPARSERUTILITIES_H
diff --git a/backend/lib/Support/CMakeLists.txt b/backend/lib/Support/CMakeLists.txt
index ee85084..c081106 100644
--- a/backend/lib/Support/CMakeLists.txt
+++ b/backend/lib/Support/CMakeLists.txt
@@ -1,9 +1,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
-set(RCCBackendSupportSources Error.cpp TextDiagnostic.cpp YAML.cpp)
+set(RCCBackendSupportSources Error.cpp SHA256.cpp TextDiagnostic.cpp YAML.cpp)
set(
RCCBackendSupportHeaders
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/Error.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/SHA256.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/TextDiagnostic.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/YAML.h"
)
@@ -37,12 +38,14 @@ install(
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/Error.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/SHA256.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TextDiagnostic.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/YAML.cpp"
${RCCBackendSupportHeaders}
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/Error.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/SHA256.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TextDiagnostic.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/YAML.cpp"
)
diff --git a/backend/lib/Support/Error.cpp b/backend/lib/Support/Error.cpp
index da4299f..2bda044 100644
--- a/backend/lib/Support/Error.cpp
+++ b/backend/lib/Support/Error.cpp
@@ -38,6 +38,18 @@ std::string_view getErrorCodeName(ErrorCode Code) noexcept {
return "relocation failed";
case ErrorCode::ObjectWritingFailed:
return "object writing failed";
+ case ErrorCode::InvalidInvocation:
+ return "invalid backend invocation";
+ case ErrorCode::ManifestError:
+ return "manifest error";
+ case ErrorCode::CacheMiss:
+ return "cache miss";
+ case ErrorCode::ReproducerError:
+ return "reproducer error";
+ case ErrorCode::ReductionFailed:
+ return "reduction failed";
+ case ErrorCode::FuzzingFailure:
+ return "fuzzing failure";
case ErrorCode::IoError:
return "I/O error";
case ErrorCode::InternalError:
diff --git a/backend/lib/Support/SHA256.cpp b/backend/lib/Support/SHA256.cpp
new file mode 100644
index 0000000..8825f89
--- /dev/null
+++ b/backend/lib/Support/SHA256.cpp
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Support/SHA256.h"
+
+#include <array>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace rcc {
+namespace {
+
+constexpr std::array<std::uint32_t, 64> RoundConstants{
+ 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU, 0x59f111f1U, 0x923f82a4U,
+ 0xab1c5ed5U, 0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU,
+ 0x9bdc06a7U, 0xc19bf174U, 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, 0x2de92c6fU,
+ 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, 0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U,
+ 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U, 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU,
+ 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U, 0xa2bfe8a1U, 0xa81a664bU,
+ 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U, 0x19a4c116U,
+ 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U,
+ 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U,
+ 0xc67178f2U,
+};
+
+constexpr std::array<std::uint32_t, 8> InitialState{
+ 0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU,
+ 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U,
+};
+
+[[nodiscard]] std::uint32_t loadBigEndian(const std::uint8_t *Input) noexcept {
+ return (static_cast<std::uint32_t>(Input[0]) << 24U) |
+ (static_cast<std::uint32_t>(Input[1]) << 16U) |
+ (static_cast<std::uint32_t>(Input[2]) << 8U) | static_cast<std::uint32_t>(Input[3]);
+}
+
+void storeBigEndian(std::uint32_t Value, std::uint8_t *Output) noexcept {
+ Output[0] = static_cast<std::uint8_t>(Value >> 24U);
+ Output[1] = static_cast<std::uint8_t>(Value >> 16U);
+ Output[2] = static_cast<std::uint8_t>(Value >> 8U);
+ Output[3] = static_cast<std::uint8_t>(Value);
+}
+
+void compress(std::array<std::uint32_t, 8> &State, const std::uint8_t *Block) noexcept {
+ std::array<std::uint32_t, 64> Words{};
+ for (std::size_t Index = 0; Index < 16; ++Index)
+ Words[Index] = loadBigEndian(Block + Index * 4U);
+ for (std::size_t Index = 16; Index < Words.size(); ++Index) {
+ const std::uint32_t S0 = std::rotr(Words[Index - 15], 7) ^ std::rotr(Words[Index - 15], 18) ^
+ (Words[Index - 15] >> 3U);
+ const std::uint32_t S1 = std::rotr(Words[Index - 2], 17) ^ std::rotr(Words[Index - 2], 19) ^
+ (Words[Index - 2] >> 10U);
+ Words[Index] = Words[Index - 16] + S0 + Words[Index - 7] + S1;
+ }
+
+ std::uint32_t A = State[0];
+ std::uint32_t B = State[1];
+ std::uint32_t C = State[2];
+ std::uint32_t D = State[3];
+ std::uint32_t E = State[4];
+ std::uint32_t F = State[5];
+ std::uint32_t G = State[6];
+ std::uint32_t H = State[7];
+ for (std::size_t Index = 0; Index < Words.size(); ++Index) {
+ const std::uint32_t Sum1 = std::rotr(E, 6) ^ std::rotr(E, 11) ^ std::rotr(E, 25);
+ const std::uint32_t Choice = (E & F) ^ (~E & G);
+ const std::uint32_t Temporary1 = H + Sum1 + Choice + RoundConstants[Index] + Words[Index];
+ const std::uint32_t Sum0 = std::rotr(A, 2) ^ std::rotr(A, 13) ^ std::rotr(A, 22);
+ const std::uint32_t Majority = (A & B) ^ (A & C) ^ (B & C);
+ const std::uint32_t Temporary2 = Sum0 + Majority;
+ H = G;
+ G = F;
+ F = E;
+ E = D + Temporary1;
+ D = C;
+ C = B;
+ B = A;
+ A = Temporary1 + Temporary2;
+ }
+ State[0] += A;
+ State[1] += B;
+ State[2] += C;
+ State[3] += D;
+ State[4] += E;
+ State[5] += F;
+ State[6] += G;
+ State[7] += H;
+}
+
+[[nodiscard]] std::string toHex(const SHA256Digest &Digest) {
+ constexpr std::string_view Digits = "0123456789abcdef";
+ std::string Result;
+ Result.resize(Digest.size() * 2U);
+ for (std::size_t Index = 0; Index < Digest.size(); ++Index) {
+ Result[Index * 2U] = Digits[Digest[Index] >> 4U];
+ Result[Index * 2U + 1U] = Digits[Digest[Index] & 0x0fU];
+ }
+ return Result;
+}
+
+} // namespace
+
+SHA256Digest sha256(std::span<const std::byte> Input) {
+ const auto *Bytes = reinterpret_cast<const std::uint8_t *>(Input.data());
+ std::array<std::uint32_t, 8> State = InitialState;
+ std::size_t Offset = 0;
+ while (Input.size() - Offset >= 64U) {
+ compress(State, Bytes + Offset);
+ Offset += 64U;
+ }
+
+ std::vector<std::uint8_t> Tail;
+ Tail.reserve(128);
+ Tail.insert(Tail.end(), Bytes + Offset, Bytes + Input.size());
+ Tail.push_back(0x80U);
+ while ((Tail.size() + 8U) % 64U != 0)
+ Tail.push_back(0);
+ const std::uint64_t BitLength = static_cast<std::uint64_t>(Input.size()) * 8U;
+ for (unsigned Byte = 0; Byte < 8; ++Byte) {
+ const unsigned Shift = 56U - Byte * 8U;
+ Tail.push_back(static_cast<std::uint8_t>(BitLength >> Shift));
+ }
+ for (std::size_t Index = 0; Index < Tail.size(); Index += 64U)
+ compress(State, Tail.data() + Index);
+
+ SHA256Digest Result{};
+ for (std::size_t Index = 0; Index < State.size(); ++Index)
+ storeBigEndian(State[Index], Result.data() + Index * 4U);
+ return Result;
+}
+
+SHA256Digest sha256(std::string_view Input) {
+ const auto *Bytes = reinterpret_cast<const std::byte *>(Input.data());
+ return sha256(std::span<const std::byte>(Bytes, Input.size()));
+}
+
+std::string sha256Hex(std::span<const std::byte> Input) {
+ return toHex(sha256(Input));
+}
+
+std::string sha256Hex(std::string_view Input) {
+ return toHex(sha256(Input));
+}
+
+} // namespace rcc
diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt
index 55578a2..156dfd6 100644
--- a/backend/lib/Target/RISCV/CMakeLists.txt
+++ b/backend/lib/Target/RISCV/CMakeLists.txt
@@ -60,6 +60,7 @@ add_library(
RISCVMachineCombiner.cpp
RISCVMachineMemoryFolding.cpp
RISCVOptimizationPipeline.cpp
+ RISCVPipeline.cpp
RISCVPseudoExpansion.cpp
RISCVRegisterAllocation.cpp
RISCVScheduling.cpp
@@ -125,6 +126,7 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVOptimizationPipeline.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPipeline.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVPseudoExpansion.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVScheduling.cpp"
@@ -138,6 +140,7 @@ rcc_backend_register_format_files(
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVFixupKinds.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVMCCodeEmitter.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVObjectFile.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVPipeline.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVMCTargetLowering.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTarget.h"
"${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTargetConstant.h"
@@ -157,6 +160,7 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVOptimizationPipeline.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPipeline.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVPseudoExpansion.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVScheduling.cpp"
diff --git a/backend/lib/Target/RISCV/RISCVPipeline.cpp b/backend/lib/Target/RISCV/RISCVPipeline.cpp
new file mode 100644
index 0000000..8d05bba
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVPipeline.cpp
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Target/RISCV/RISCVPipeline.h"
+
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineOptimizationPipeline.h"
+#include "rcc/CodeGen/MachinePipelinePasses.h"
+#include "rcc/CodeGen/MachineSSAExit.h"
+#include "rcc/CodeGen/RegAllocPBQP.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/Target/RISCV/RISCVCodeGen.h"
+
+#include <memory>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace rcc::riscv {
+namespace {
+
+template <typename PassT, typename... ArgumentT>
+[[nodiscard]] Expected<void> addPass(MachinePassManager &Pipeline, ArgumentT &&...Arguments) {
+ return Pipeline.addPass(std::make_unique<PassT>(std::forward<ArgumentT>(Arguments)...));
+}
+
+[[nodiscard]] Expected<void> addInitialPipeline(MachinePassManager &Pipeline) {
+ auto Added = addPass<LegalizerPass>(Pipeline, legalizerInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ Added = addPass<RegisterBankSelectPass>(Pipeline, registerBankInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ return addPass<InstructionSelectPass>(Pipeline, instructionSelector());
+}
+
+[[nodiscard]] Expected<void> addSSAExitPipeline(MachinePassManager &Pipeline) {
+ auto Added = addSelectedSSAOptimizationPipeline(Pipeline, optimizationPipelineInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ Added = addPass<PHIEliminationPass>(Pipeline, ssaInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ return addPass<TwoAddressRewritePass>(Pipeline, ssaInfo());
+}
+
+[[nodiscard]] Expected<void> addRegisterAllocationPipeline(MachinePassManager &Pipeline) {
+ auto Added = addPreRegisterAllocationOptimizationPipeline(Pipeline, optimizationPipelineInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ return addPass<PBQPAllocatorPass>(Pipeline, registerAllocationInfo());
+}
+
+[[nodiscard]] Expected<void> addCompleteRegistry(MachinePassManager &Pipeline) {
+ auto Added = addInitialPipeline(Pipeline);
+ if (!Added)
+ return std::unexpected(Added.error());
+ Added = addSSAExitPipeline(Pipeline);
+ if (!Added)
+ return std::unexpected(Added.error());
+ Added = addRegisterAllocationPipeline(Pipeline);
+ if (!Added)
+ return std::unexpected(Added.error());
+ return addPostRegisterAllocationPipeline(Pipeline, optimizationPipelineInfo());
+}
+
+} // namespace
+
+Expected<void> addCanonicalPipeline(MachinePassManager &Pipeline, const MachineFunction &Function) {
+ if (!Function.properties().has(MachineFunctionProperty::Legalized)) {
+ auto Added = addPass<LegalizerPass>(Pipeline, legalizerInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(MachineFunctionProperty::RegBankSelected)) {
+ auto Added = addPass<RegisterBankSelectPass>(Pipeline, registerBankInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(MachineFunctionProperty::Selected)) {
+ auto Added = addPass<InstructionSelectPass>(Pipeline, instructionSelector());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(MachineFunctionProperty::TiedOpsRewritten)) {
+ auto Added = addSelectedSSAOptimizationPipeline(Pipeline, optimizationPipelineInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ if (!Function.properties().has(MachineFunctionProperty::NoPHIs)) {
+ Added = addPass<PHIEliminationPass>(Pipeline, ssaInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ }
+ if (!Function.properties().has(MachineFunctionProperty::TiedOpsRewritten)) {
+ auto Added = addPass<TwoAddressRewritePass>(Pipeline, ssaInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(MachineFunctionProperty::NoVRegs)) {
+ auto Added = addRegisterAllocationPipeline(Pipeline);
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ if (!Function.properties().has(MachineFunctionProperty::NoExecutablePseudos)) {
+ auto Added = addPostRegisterAllocationPipeline(Pipeline, optimizationPipelineInfo());
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ return {};
+}
+
+Expected<std::unique_ptr<MachineFunctionPass>> createCanonicalPipelinePass(std::string_view Name) {
+ if (Name.empty())
+ return makeUnexpected(ErrorCode::InvalidArgument, "pipeline pass name cannot be empty");
+ MachinePassManager Registry;
+ auto Added = addCompleteRegistry(Registry);
+ if (!Added)
+ return std::unexpected(Added.error());
+ MachinePassManager::PassList Passes = std::move(Registry).takePasses();
+ auto Position = std::ranges::find_if(Passes, [Name](const auto &Pass) {
+ return Pass != nullptr && Pass->contract().Name == Name;
+ });
+ if (Position == Passes.end()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "unknown RV64GC machine pass '" + std::string(Name) + "'");
+ }
+ return std::move(*Position);
+}
+
+Expected<std::vector<std::string>> canonicalPipelinePassNames(const MachineFunction &Function) {
+ MachinePassManager Pipeline;
+ auto Added = addCanonicalPipeline(Pipeline, Function);
+ if (!Added)
+ return std::unexpected(Added.error());
+ MachinePassManager::PassList Passes = std::move(Pipeline).takePasses();
+ std::vector<std::string> Result;
+ Result.reserve(Passes.size());
+ for (const std::unique_ptr<MachineFunctionPass> &Pass : Passes) {
+ if (Pass == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "canonical pipeline contains a null pass");
+ Result.push_back(Pass->contract().Name);
+ }
+ return Result;
+}
+
+Expected<void> runCanonicalPipeline(MachineFunction &Function, const TargetDescription &Target,
+ MachinePassManagerOptions Options,
+ MachinePassInstrumentation *Instrumentation) {
+ MachinePassManager Pipeline(Options);
+ auto Added = addCanonicalPipeline(Pipeline, Function);
+ if (!Added)
+ return std::unexpected(Added.error());
+ MachineAnalysisManager Analyses;
+ auto Registered = registerMachineOptimizationAnalyses(Analyses);
+ if (!Registered)
+ return std::unexpected(Registered.error());
+ return Pipeline.run(Function, Target, Analyses, Instrumentation);
+}
+
+} // namespace rcc::riscv
diff --git a/backend/lib/Tooling/ArtifactStore.cpp b/backend/lib/Tooling/ArtifactStore.cpp
new file mode 100644
index 0000000..76fc0df
--- /dev/null
+++ b/backend/lib/Tooling/ArtifactStore.cpp
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/ArtifactStore.h"
+
+#include "rcc/CodeGen/CompilationUnit.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Support/SHA256.h"
+#include "rcc/Support/TextDiagnostic.h"
+#include "rcc/Tooling/BackendInvocation.h"
+
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <iterator>
+#include <limits>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+namespace rcc::tooling {
+namespace {
+
+constexpr std::uintmax_t MaximumPackageFileSize = std::uintmax_t{1} << 30U;
+std::atomic<std::uint64_t> TemporarySequence = 0;
+
+[[nodiscard]] bool isMIRArtifact(ArtifactKind Artifact) noexcept {
+ switch (Artifact) {
+ case ArtifactKind::PreLegalizerMIR:
+ case ArtifactKind::LegalizedMIR:
+ case ArtifactKind::RegisterBankMIR:
+ case ArtifactKind::SelectedMIR:
+ case ArtifactKind::PostSSAMIR:
+ case ArtifactKind::PostRegisterAllocationMIR:
+ case ArtifactKind::FinalMIR:
+ return true;
+ case ArtifactKind::Assembly:
+ case ArtifactKind::RelocatableObject:
+ return false;
+ }
+ return false;
+}
+
+[[nodiscard]] bool isCacheableArtifact(ArtifactKind Artifact) noexcept {
+ switch (Artifact) {
+ case ArtifactKind::PreLegalizerMIR:
+ case ArtifactKind::SelectedMIR:
+ case ArtifactKind::PostRegisterAllocationMIR:
+ case ArtifactKind::FinalMIR:
+ case ArtifactKind::Assembly:
+ case ArtifactKind::RelocatableObject:
+ return true;
+ case ArtifactKind::LegalizedMIR:
+ case ArtifactKind::RegisterBankMIR:
+ case ArtifactKind::PostSSAMIR:
+ return false;
+ }
+ return false;
+}
+
+[[nodiscard]] std::string_view artifactFileName(ArtifactKind Artifact) noexcept {
+ switch (Artifact) {
+ case ArtifactKind::PreLegalizerMIR:
+ case ArtifactKind::LegalizedMIR:
+ case ArtifactKind::RegisterBankMIR:
+ case ArtifactKind::SelectedMIR:
+ case ArtifactKind::PostSSAMIR:
+ case ArtifactKind::PostRegisterAllocationMIR:
+ case ArtifactKind::FinalMIR:
+ return "artifact.mir";
+ case ArtifactKind::Assembly:
+ return "artifact.s";
+ case ArtifactKind::RelocatableObject:
+ return "artifact.o";
+ }
+ return "artifact.bin";
+}
+
+[[nodiscard]] std::filesystem::path cacheEntryPath(const std::filesystem::path &Root,
+ std::string_view Key) {
+ return Root / std::string(Key.substr(0, 2)) / std::string(Key);
+}
+
+[[nodiscard]] Expected<std::vector<std::uint8_t>> readBytes(const std::filesystem::path &Path,
+ ErrorCode Code) {
+ std::error_code FileError;
+ const std::uintmax_t Size = std::filesystem::file_size(Path, FileError);
+ if (FileError)
+ return makeUnexpected(Code, "cannot inspect package file '" + Path.string() + "'");
+ if (Size > MaximumPackageFileSize ||
+ Size > static_cast<std::uintmax_t>(std::numeric_limits<std::size_t>::max())) {
+ return makeUnexpected(Code, "package file exceeds the supported size limit");
+ }
+ std::ifstream Input(Path, std::ios::binary);
+ if (!Input)
+ return makeUnexpected(Code, "cannot open package file '" + Path.string() + "'");
+ std::vector<std::uint8_t> Result(static_cast<std::size_t>(Size));
+ if (!Result.empty()) {
+ Input.read(reinterpret_cast<char *>(Result.data()),
+ static_cast<std::streamsize>(Result.size()));
+ }
+ if (!Input)
+ return makeUnexpected(Code, "cannot read package file '" + Path.string() + "'");
+ return Result;
+}
+
+[[nodiscard]] Expected<std::string> readText(const std::filesystem::path &Path, ErrorCode Code) {
+ auto Bytes = readBytes(Path, Code);
+ if (!Bytes)
+ return std::unexpected(Bytes.error());
+ return std::string(reinterpret_cast<const char *>(Bytes->data()), Bytes->size());
+}
+
+[[nodiscard]] Expected<void> writeBytes(const std::filesystem::path &Path,
+ std::span<const std::uint8_t> Bytes, ErrorCode Code) {
+ std::ofstream Output(Path, std::ios::binary | std::ios::trunc);
+ if (!Output)
+ return makeUnexpected(Code, "cannot create package file '" + Path.string() + "'");
+ if (!Bytes.empty()) {
+ Output.write(reinterpret_cast<const char *>(Bytes.data()),
+ static_cast<std::streamsize>(Bytes.size()));
+ }
+ Output.close();
+ if (!Output)
+ return makeUnexpected(Code, "cannot write package file '" + Path.string() + "'");
+ return {};
+}
+
+[[nodiscard]] Expected<void> writeText(const std::filesystem::path &Path, std::string_view Text,
+ ErrorCode Code) {
+ const auto *Bytes = reinterpret_cast<const std::uint8_t *>(Text.data());
+ return writeBytes(Path, std::span<const std::uint8_t>(Bytes, Text.size()), Code);
+}
+
+[[nodiscard]] std::filesystem::path temporaryPath(const std::filesystem::path &Destination) {
+ const std::uint64_t Sequence = TemporarySequence.fetch_add(1, std::memory_order_relaxed);
+ return Destination.parent_path() /
+ (Destination.filename().string() + ".tmp." + std::to_string(Sequence));
+}
+
+[[nodiscard]] Expected<void> replaceTextAtomically(const std::filesystem::path &Path,
+ std::string_view Text, ErrorCode Code) {
+ const std::filesystem::path Temporary = temporaryPath(Path);
+ auto Written = writeText(Temporary, Text, Code);
+ if (!Written)
+ return Written;
+ std::error_code FileError;
+ std::filesystem::rename(Temporary, Path, FileError);
+ if (!FileError)
+ return {};
+ std::error_code CleanupError;
+ std::filesystem::remove(Temporary, CleanupError);
+ return makeUnexpected(Code, "cannot atomically replace package file '" + Path.string() + "'");
+}
+
+[[nodiscard]] Expected<void>
+writePackageAtomically(const std::filesystem::path &Destination, std::string_view ManifestText,
+ std::string_view InputMIR, std::string_view ArtifactName,
+ std::span<const std::uint8_t> Artifact, bool HasArtifact, ErrorCode Code) {
+ if (Destination.empty())
+ return makeUnexpected(Code, "package destination cannot be empty");
+ std::error_code FileError;
+ const std::filesystem::path Parent = Destination.parent_path();
+ if (!Parent.empty()) {
+ std::filesystem::create_directories(Parent, FileError);
+ if (FileError)
+ return makeUnexpected(Code, "cannot create package parent directory");
+ }
+ if (std::filesystem::exists(Destination, FileError))
+ return makeUnexpected(Code, "package destination already exists");
+ if (FileError)
+ return makeUnexpected(Code, "cannot inspect package destination");
+
+ const std::filesystem::path Temporary = temporaryPath(Destination);
+ std::filesystem::create_directory(Temporary, FileError);
+ if (FileError)
+ return makeUnexpected(Code, "cannot create temporary package directory");
+ const auto Cleanup = [&]() {
+ std::error_code CleanupError;
+ std::filesystem::remove_all(Temporary, CleanupError);
+ };
+ auto Written = writeText(Temporary / "manifest.yaml", ManifestText, Code);
+ if (!Written) {
+ Cleanup();
+ return Written;
+ }
+ Written = writeText(Temporary / "input.mir", InputMIR, Code);
+ if (!Written) {
+ Cleanup();
+ return Written;
+ }
+ if (HasArtifact) {
+ Written = writeBytes(Temporary / ArtifactName, Artifact, Code);
+ if (!Written) {
+ Cleanup();
+ return Written;
+ }
+ }
+ std::filesystem::rename(Temporary, Destination, FileError);
+ if (FileError) {
+ Cleanup();
+ return makeUnexpected(Code, "cannot publish package atomically");
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> verifyFunctions(const mir::MIRDocument &Document,
+ const TargetDescription &Target) {
+ auto UnitVerification = Document.unit().verify();
+ if (!UnitVerification)
+ return std::unexpected(UnitVerification.error());
+ for (std::size_t Index = 0; Index < Document.unit().functionCount(); ++Index) {
+ const MachineFunction *Function = Document.unit().functionAt(Index);
+ if (Function == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "MIR document contains a null machine function");
+ auto FunctionVerification = MachineVerifier::verify(*Function, Target);
+ if (!FunctionVerification)
+ return std::unexpected(FunctionVerification.error());
+ }
+ return {};
+}
+
+[[nodiscard]] Error parseFailure(const mir::MIRDocumentParseResult &Parsed, ErrorCode Code) {
+ if (Parsed.diagnostics().empty())
+ return Error(Code, "MIR package input could not be parsed");
+ return Error(Code, formatTextDiagnostic(Parsed.diagnostics().front(), Parsed.source()));
+}
+
+[[nodiscard]] Expected<void> verifyManifestIdentity(const BackendManifest &Manifest,
+ ManifestKind Kind, ArtifactKind Artifact,
+ const BackendInvocation &Invocation,
+ std::string_view InputHash,
+ std::string_view TargetFingerprint,
+ std::string_view CacheKey, ErrorCode Code) {
+ if (Manifest.Kind != Kind || Manifest.Artifact != Artifact || Manifest.Invocation != Invocation ||
+ Manifest.InputMIRHash != InputHash || Manifest.TargetFingerprint != TargetFingerprint ||
+ Manifest.CacheKey != CacheKey) {
+ return makeUnexpected(Code, "package manifest does not match the requested backend input");
+ }
+ return {};
+}
+
+} // namespace
+
+Expected<CanonicalMIR> parseAndCanonicalizeMIR(std::string SourceName, std::string SourceText,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation) {
+ BackendInvocation CanonicalInvocation = Invocation;
+ auto InvocationVerification = CanonicalInvocation.canonicalizeAndVerify();
+ if (!InvocationVerification)
+ return std::unexpected(InvocationVerification.error());
+ if (CanonicalInvocation != Invocation) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "MIR parsing requires a canonical backend invocation");
+ }
+ mir::MIRDocumentParserOptions ParseOptions;
+ ParseOptions.DataLayout = Invocation.DataLayout;
+ ParseOptions.DefaultPointerSizeInBits = 64;
+ ParseOptions.VerifyMachineFunctions = true;
+ mir::MIRDocumentParseResult Parsed =
+ mir::parseMIRDocument(Target, std::move(SourceName), std::move(SourceText), ParseOptions);
+ if (!Parsed.succeeded())
+ return std::unexpected(parseFailure(Parsed, ErrorCode::ParseError));
+ if (Parsed.document() == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "successful MIR parse has no document");
+ auto Verification = verifyFunctions(*Parsed.document(), Target);
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ mir::MIRDocumentPrinterOptions PrintOptions;
+ PrintOptions.Mode = mir::MIPrintMode::Complete;
+ PrintOptions.EmitEmbeddedIR = true;
+ PrintOptions.DataLayout = Invocation.DataLayout;
+ PrintOptions.DefaultPointerSizeInBits = 64;
+ auto Printed = mir::printMIRDocument(*Parsed.document(), Target, std::move(PrintOptions));
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ return CanonicalMIR{
+ .Document = std::move(*Parsed.document()),
+ .Text = std::move(*Printed),
+ };
+}
+
+Expected<BackendManifest>
+createBackendManifest(ManifestKind Kind, ArtifactKind Artifact, const BackendInvocation &Invocation,
+ const TargetDescription &Target, std::string_view CanonicalInputMIR,
+ std::optional<std::span<const std::uint8_t>> ArtifactBytes,
+ std::vector<StructuredDiagnostic> Diagnostics) {
+ BackendInvocation CanonicalInvocation = Invocation;
+ auto InvocationVerification = CanonicalInvocation.canonicalizeAndVerify();
+ if (!InvocationVerification)
+ return std::unexpected(InvocationVerification.error());
+ if (CanonicalInvocation != Invocation) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "manifest creation requires a canonical backend invocation");
+ }
+ auto Fingerprint = computeTargetDescriptionFingerprint(Target);
+ if (!Fingerprint)
+ return std::unexpected(Fingerprint.error());
+ auto Key = computeCacheKey(CanonicalInputMIR, Invocation, *Fingerprint);
+ if (!Key)
+ return std::unexpected(Key.error());
+ BackendManifest Result{
+ .SchemaVersion = BackendManifestSchemaVersion,
+ .Kind = Kind,
+ .Artifact = Artifact,
+ .Invocation = Invocation,
+ .InputMIRHash = sha256Hex(CanonicalInputMIR),
+ .TargetFingerprint = std::move(*Fingerprint),
+ .CacheKey = std::move(*Key),
+ .ArtifactHash = std::nullopt,
+ .Diagnostics = std::move(Diagnostics),
+ };
+ if (ArtifactBytes) {
+ const auto *Bytes = reinterpret_cast<const std::byte *>(ArtifactBytes->data());
+ Result.ArtifactHash = sha256Hex(
+ std::span<const std::byte>(Bytes, static_cast<std::size_t>(ArtifactBytes->size())));
+ }
+ auto Verification = Result.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Result;
+}
+
+Expected<void> storeCacheEntry(const std::filesystem::path &CacheRoot,
+ const BackendManifest &Manifest, std::string_view CanonicalInputMIR,
+ std::span<const std::uint8_t> Artifact) {
+ auto Verification = Manifest.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ if (Manifest.Kind != ManifestKind::Cache || !Manifest.ArtifactHash) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "cache entry requires a cache manifest and artifact hash");
+ }
+ if (!isCacheableArtifact(Manifest.Artifact)) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest artifact is not an allowed cache boundary");
+ }
+ if (sha256Hex(CanonicalInputMIR) != Manifest.InputMIRHash) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "cache input MIR does not match its manifest hash");
+ }
+ const auto *ArtifactBytes = reinterpret_cast<const std::byte *>(Artifact.data());
+ if (sha256Hex(std::span<const std::byte>(ArtifactBytes, Artifact.size())) !=
+ *Manifest.ArtifactHash) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "cache artifact does not match its manifest hash");
+ }
+ auto Printed = printBackendManifest(Manifest);
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ const std::filesystem::path Destination = cacheEntryPath(CacheRoot, Manifest.CacheKey);
+ return writePackageAtomically(Destination, *Printed, CanonicalInputMIR,
+ artifactFileName(Manifest.Artifact), Artifact, true,
+ ErrorCode::IoError);
+}
+
+Expected<CacheEntry> loadCacheEntry(const std::filesystem::path &CacheRoot, ArtifactKind Artifact,
+ const BackendInvocation &Invocation,
+ const TargetDescription &Target,
+ std::string_view CanonicalInputMIR) {
+ if (!isCacheableArtifact(Artifact))
+ return makeUnexpected(ErrorCode::CacheMiss, "requested artifact is not a cache boundary");
+ BackendInvocation CanonicalInvocation = Invocation;
+ auto InvocationVerification = CanonicalInvocation.canonicalizeAndVerify();
+ if (!InvocationVerification)
+ return std::unexpected(InvocationVerification.error());
+ if (CanonicalInvocation != Invocation)
+ return makeUnexpected(ErrorCode::CacheMiss, "cache invocation is not canonical");
+ auto Fingerprint = computeTargetDescriptionFingerprint(Target);
+ if (!Fingerprint)
+ return std::unexpected(Fingerprint.error());
+ auto Key = computeCacheKey(CanonicalInputMIR, Invocation, *Fingerprint);
+ if (!Key)
+ return std::unexpected(Key.error());
+ const std::filesystem::path EntryPath = cacheEntryPath(CacheRoot, *Key);
+ std::error_code FileError;
+ if (!std::filesystem::is_directory(EntryPath, FileError) || FileError)
+ return makeUnexpected(ErrorCode::CacheMiss, "cache entry is absent");
+ auto ManifestText = readText(EntryPath / "manifest.yaml", ErrorCode::CacheMiss);
+ if (!ManifestText)
+ return std::unexpected(ManifestText.error());
+ auto Manifest =
+ parseBackendManifest((EntryPath / "manifest.yaml").string(), std::move(*ManifestText));
+ if (!Manifest)
+ return makeUnexpected(ErrorCode::CacheMiss, std::string(Manifest.error().message()));
+ const std::string InputHash = sha256Hex(CanonicalInputMIR);
+ auto Identity = verifyManifestIdentity(*Manifest, ManifestKind::Cache, Artifact, Invocation,
+ InputHash, *Fingerprint, *Key, ErrorCode::CacheMiss);
+ if (!Identity)
+ return std::unexpected(Identity.error());
+ if (!Manifest->ArtifactHash)
+ return makeUnexpected(ErrorCode::CacheMiss, "cache manifest has no artifact hash");
+
+ auto StoredInput = readText(EntryPath / "input.mir", ErrorCode::CacheMiss);
+ if (!StoredInput)
+ return std::unexpected(StoredInput.error());
+ if (*StoredInput != CanonicalInputMIR || sha256Hex(*StoredInput) != Manifest->InputMIRHash)
+ return makeUnexpected(ErrorCode::CacheMiss, "cached input MIR is inconsistent");
+ auto StrictInput = parseAndCanonicalizeMIR("cache-input.mir", *StoredInput, Target, Invocation);
+ if (!StrictInput || StrictInput->Text != *StoredInput)
+ return makeUnexpected(ErrorCode::CacheMiss, "cached input MIR is not canonical and valid");
+
+ auto ArtifactBytes = readBytes(EntryPath / artifactFileName(Artifact), ErrorCode::CacheMiss);
+ if (!ArtifactBytes)
+ return std::unexpected(ArtifactBytes.error());
+ const auto *Bytes = reinterpret_cast<const std::byte *>(ArtifactBytes->data());
+ if (sha256Hex(std::span<const std::byte>(Bytes, ArtifactBytes->size())) !=
+ *Manifest->ArtifactHash) {
+ return makeUnexpected(ErrorCode::CacheMiss, "cached artifact hash is inconsistent");
+ }
+ std::optional<CanonicalMIR> ParsedArtifact;
+ if (isMIRArtifact(Artifact)) {
+ std::string ArtifactText(reinterpret_cast<const char *>(ArtifactBytes->data()),
+ ArtifactBytes->size());
+ auto Parsed = parseAndCanonicalizeMIR("cache-artifact.mir", ArtifactText, Target, Invocation);
+ if (!Parsed || Parsed->Text != ArtifactText)
+ return makeUnexpected(ErrorCode::CacheMiss, "cached MIR artifact is not canonical and valid");
+ ParsedArtifact = std::move(*Parsed);
+ }
+ return CacheEntry{
+ .Manifest = std::move(*Manifest),
+ .InputMIR = std::move(*StoredInput),
+ .Artifact = std::move(*ArtifactBytes),
+ .ParsedArtifact = std::move(ParsedArtifact),
+ };
+}
+
+Expected<void> writeReproducer(const std::filesystem::path &PackagePath,
+ const BackendManifest &Manifest,
+ std::string_view CanonicalInputMIR) {
+ auto Verification = Manifest.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ if (Manifest.Kind != ManifestKind::Reproducer || Manifest.ArtifactHash) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "reproducer requires a reproducer manifest without an artifact hash");
+ }
+ if (sha256Hex(CanonicalInputMIR) != Manifest.InputMIRHash) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "reproducer input MIR does not match its manifest hash");
+ }
+ auto Printed = printBackendManifest(Manifest);
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ return writePackageAtomically(PackagePath, *Printed, CanonicalInputMIR, {}, {}, false,
+ ErrorCode::ReproducerError);
+}
+
+Expected<void> updateReproducerDiagnostics(const std::filesystem::path &PackagePath,
+ std::vector<StructuredDiagnostic> Diagnostics) {
+ const std::filesystem::path ManifestPath = PackagePath / "manifest.yaml";
+ auto ManifestText = readText(ManifestPath, ErrorCode::ReproducerError);
+ if (!ManifestText)
+ return std::unexpected(ManifestText.error());
+ auto Manifest = parseBackendManifest(ManifestPath.string(), std::move(*ManifestText));
+ if (!Manifest) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "cannot update an invalid reproducer manifest: " +
+ std::string(Manifest.error().message()));
+ }
+ if (Manifest->Kind != ManifestKind::Reproducer || Manifest->ArtifactHash) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "diagnostics can only update a reproducer package");
+ }
+ auto Input = readText(PackagePath / "input.mir", ErrorCode::ReproducerError);
+ if (!Input)
+ return std::unexpected(Input.error());
+ if (sha256Hex(*Input) != Manifest->InputMIRHash) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "reproducer input changed before diagnostic update");
+ }
+ Manifest->Diagnostics = std::move(Diagnostics);
+ auto Printed = printBackendManifest(*Manifest);
+ if (!Printed)
+ return makeUnexpected(ErrorCode::ReproducerError, std::string(Printed.error().message()));
+ return replaceTextAtomically(ManifestPath, *Printed, ErrorCode::ReproducerError);
+}
+
+Expected<ReproducerPackage> loadReproducer(const std::filesystem::path &PackagePath,
+ const TargetDescription &Target) {
+ auto ManifestText = readText(PackagePath / "manifest.yaml", ErrorCode::ReproducerError);
+ if (!ManifestText)
+ return std::unexpected(ManifestText.error());
+ auto Manifest =
+ parseBackendManifest((PackagePath / "manifest.yaml").string(), std::move(*ManifestText));
+ if (!Manifest)
+ return makeUnexpected(ErrorCode::ReproducerError, std::string(Manifest.error().message()));
+ if (Manifest->Kind != ManifestKind::Reproducer || Manifest->ArtifactHash) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "package does not contain a valid reproducer manifest");
+ }
+ auto Fingerprint = computeTargetDescriptionFingerprint(Target);
+ if (!Fingerprint)
+ return std::unexpected(Fingerprint.error());
+ if (Manifest->TargetFingerprint != *Fingerprint) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "reproducer target fingerprint does not match this backend");
+ }
+ auto InputText = readText(PackagePath / "input.mir", ErrorCode::ReproducerError);
+ if (!InputText)
+ return std::unexpected(InputText.error());
+ auto Input = parseAndCanonicalizeMIR((PackagePath / "input.mir").string(), *InputText, Target,
+ Manifest->Invocation);
+ if (!Input)
+ return makeUnexpected(ErrorCode::ReproducerError, std::string(Input.error().message()));
+ if (Input->Text != *InputText || sha256Hex(Input->Text) != Manifest->InputMIRHash) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "reproducer input MIR is not canonical or has the wrong hash");
+ }
+ auto Key = computeCacheKey(Input->Text, Manifest->Invocation, *Fingerprint);
+ if (!Key)
+ return std::unexpected(Key.error());
+ if (*Key != Manifest->CacheKey) {
+ return makeUnexpected(ErrorCode::ReproducerError,
+ "reproducer cache key is inconsistent with its explicit inputs");
+ }
+ return ReproducerPackage{
+ .Manifest = std::move(*Manifest),
+ .Input = std::move(*Input),
+ };
+}
+
+} // namespace rcc::tooling
diff --git a/backend/lib/Tooling/BackendDriver.cpp b/backend/lib/Tooling/BackendDriver.cpp
new file mode 100644
index 0000000..85a2cf9
--- /dev/null
+++ b/backend/lib/Tooling/BackendDriver.cpp
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/BackendDriver.h"
+
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h"
+#include "rcc/Target/RISCV/RISCVObjectFile.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace rcc::tooling {
+namespace {
+
+[[nodiscard]] bool isMIR(BackendOutputKind Output) noexcept {
+ return Output <= BackendOutputKind::FinalMIR;
+}
+
+[[nodiscard]] BackendStage outputStage(BackendOutputKind Output) noexcept {
+ switch (Output) {
+ case BackendOutputKind::PreLegalizerMIR:
+ return BackendStage::PreLegalizerMIR;
+ case BackendOutputKind::LegalizedMIR:
+ return BackendStage::LegalizedMIR;
+ case BackendOutputKind::RegisterBankMIR:
+ return BackendStage::RegisterBankMIR;
+ case BackendOutputKind::SelectedMIR:
+ return BackendStage::SelectedMIR;
+ case BackendOutputKind::PostSSAMIR:
+ return BackendStage::PostSSAMIR;
+ case BackendOutputKind::PostRegisterAllocationMIR:
+ return BackendStage::PostRegisterAllocationMIR;
+ case BackendOutputKind::FinalMIR:
+ case BackendOutputKind::Assembly:
+ case BackendOutputKind::RelocatableObject:
+ return BackendStage::FinalMIR;
+ }
+ return BackendStage::FinalMIR;
+}
+
+[[nodiscard]] std::uint8_t ordinal(BackendStage Stage) noexcept {
+ return static_cast<std::uint8_t>(Stage);
+}
+
+[[nodiscard]] Expected<std::vector<std::uint8_t>> printMIR(const mir::MIRDocument &Document,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation) {
+ mir::MIRDocumentPrinterOptions Options;
+ Options.Mode = mir::MIPrintMode::Complete;
+ Options.EmitEmbeddedIR = true;
+ Options.DataLayout = Invocation.DataLayout;
+ Options.DefaultPointerSizeInBits = 64;
+ auto Printed = mir::printMIRDocument(Document, Target, std::move(Options));
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ return std::vector<std::uint8_t>(Printed->begin(), Printed->end());
+}
+
+} // namespace
+
+std::string_view BackendRunResult::text() const noexcept {
+ if (Kind == BackendOutputKind::RelocatableObject || Output.empty())
+ return {};
+ return {reinterpret_cast<const char *>(Output.data()), Output.size()};
+}
+
+Expected<BackendRunResult> runBackend(CanonicalMIR Input, const TargetDescription &Target,
+ const BackendInvocation &Invocation, BackendOutputKind Output,
+ PipelineControls Controls,
+ MachinePassInstrumentation *Instrumentation) {
+ auto InvocationVerification = verifyRV64GCInvocation(Invocation);
+ if (!InvocationVerification)
+ return std::unexpected(InvocationVerification.error());
+ if (Input.Document.unit().functionCount() == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "backend input must contain at least one machine function");
+ }
+
+ BackendStage InitialStage = BackendStage::FinalMIR;
+ for (std::size_t Index = 0; Index < Input.Document.unit().functionCount(); ++Index) {
+ const MachineFunction *Function = Input.Document.unit().functionAt(Index);
+ if (Function == nullptr)
+ return makeUnexpected(ErrorCode::InternalError, "backend input contains a null function");
+ const BackendStage Stage = stageForFunction(*Function);
+ if (Index == 0 || ordinal(Stage) < ordinal(InitialStage))
+ InitialStage = Stage;
+ }
+ const BackendStage RequiredStage = outputStage(Output);
+ if (isMIR(Output) && ordinal(RequiredStage) < ordinal(InitialStage)) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "backend cannot reconstruct an earlier MIR stage from a later snapshot");
+ }
+
+ if (isMIR(Output)) {
+ if (Controls.StopAtStage && *Controls.StopAtStage != RequiredStage) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "output stage conflicts with the explicit pipeline stop stage");
+ }
+ Controls.StopAtStage = RequiredStage;
+ }
+
+ MachinePassInstrumentation LocalInstrumentation;
+ MachinePassInstrumentation *ActiveInstrumentation =
+ Instrumentation == nullptr ? &LocalInstrumentation : Instrumentation;
+ auto Pipeline = runRV64GCPipeline(Input.Document, Target, Invocation, std::move(Controls),
+ ActiveInstrumentation);
+ if (!Pipeline)
+ return std::unexpected(Pipeline.error());
+
+ std::vector<std::uint8_t> Bytes;
+ if (isMIR(Output)) {
+ auto Printed = printMIR(Input.Document, Target, Invocation);
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ Bytes = std::move(*Printed);
+ } else if (Output == BackendOutputKind::Assembly) {
+ auto Assembly = riscv::emitAssembly(Input.Document.unit(), Target);
+ if (!Assembly)
+ return std::unexpected(Assembly.error());
+ Bytes.assign(Assembly->begin(), Assembly->end());
+ } else {
+ auto Object = riscv::emitObject(Input.Document.unit(), Target);
+ if (!Object)
+ return std::unexpected(Object.error());
+ Bytes = std::move(*Object);
+ }
+
+ return BackendRunResult{
+ .Kind = Output,
+ .Output = std::move(Bytes),
+ .Pipeline = std::move(*Pipeline),
+ .Actions = ActiveInstrumentation->actionEvents(),
+ };
+}
+
+std::string_view spelling(BackendOutputKind Output) noexcept {
+ switch (Output) {
+ case BackendOutputKind::PreLegalizerMIR:
+ return "prelegalizer-mir";
+ case BackendOutputKind::LegalizedMIR:
+ return "legalized-mir";
+ case BackendOutputKind::RegisterBankMIR:
+ return "regbank-mir";
+ case BackendOutputKind::SelectedMIR:
+ return "selected-mir";
+ case BackendOutputKind::PostSSAMIR:
+ return "post-ssa-mir";
+ case BackendOutputKind::PostRegisterAllocationMIR:
+ return "post-ra-mir";
+ case BackendOutputKind::FinalMIR:
+ return "final-mir";
+ case BackendOutputKind::Assembly:
+ return "assembly";
+ case BackendOutputKind::RelocatableObject:
+ return "object";
+ }
+ return "invalid";
+}
+
+Expected<BackendOutputKind> parseBackendOutputKind(std::string_view Value) {
+ if (Value == "prelegalizer-mir")
+ return BackendOutputKind::PreLegalizerMIR;
+ if (Value == "legalized-mir")
+ return BackendOutputKind::LegalizedMIR;
+ if (Value == "regbank-mir")
+ return BackendOutputKind::RegisterBankMIR;
+ if (Value == "selected-mir")
+ return BackendOutputKind::SelectedMIR;
+ if (Value == "post-ssa-mir")
+ return BackendOutputKind::PostSSAMIR;
+ if (Value == "post-ra-mir")
+ return BackendOutputKind::PostRegisterAllocationMIR;
+ if (Value == "final-mir")
+ return BackendOutputKind::FinalMIR;
+ if (Value == "assembly" || Value == "asm")
+ return BackendOutputKind::Assembly;
+ if (Value == "object" || Value == "obj")
+ return BackendOutputKind::RelocatableObject;
+ return makeUnexpected(ErrorCode::InvalidInvocation, "unknown backend output kind");
+}
+
+ArtifactKind manifestArtifactKind(BackendOutputKind Output) noexcept {
+ return static_cast<ArtifactKind>(static_cast<std::uint8_t>(Output));
+}
+
+Expected<ArtifactKind> cacheArtifactKind(BackendOutputKind Output) {
+ switch (Output) {
+ case BackendOutputKind::PreLegalizerMIR:
+ return ArtifactKind::PreLegalizerMIR;
+ case BackendOutputKind::SelectedMIR:
+ return ArtifactKind::SelectedMIR;
+ case BackendOutputKind::PostRegisterAllocationMIR:
+ return ArtifactKind::PostRegisterAllocationMIR;
+ case BackendOutputKind::FinalMIR:
+ return ArtifactKind::FinalMIR;
+ case BackendOutputKind::Assembly:
+ return ArtifactKind::Assembly;
+ case BackendOutputKind::RelocatableObject:
+ return ArtifactKind::RelocatableObject;
+ case BackendOutputKind::LegalizedMIR:
+ case BackendOutputKind::RegisterBankMIR:
+ case BackendOutputKind::PostSSAMIR:
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "the selected intermediate stage is not a cache boundary");
+ }
+ return makeUnexpected(ErrorCode::InvalidInvocation, "invalid backend output kind");
+}
+
+} // namespace rcc::tooling
diff --git a/backend/lib/Tooling/BackendInvocation.cpp b/backend/lib/Tooling/BackendInvocation.cpp
new file mode 100644
index 0000000..677cd2d
--- /dev/null
+++ b/backend/lib/Tooling/BackendInvocation.cpp
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/BackendInvocation.h"
+
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/RegisterMask.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/CodeGen/TargetMIRTextInfo.h"
+#include "rcc/CodeGen/TargetRegisterInfo.h"
+#include "rcc/Support/SHA256.h"
+
+#include <algorithm>
+#include <cctype>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+
+namespace rcc::tooling {
+namespace {
+
+void appendField(std::string &Output, std::string_view Name, std::string_view Value) {
+ Output.append(Name);
+ Output.push_back('=');
+ Output.append(std::to_string(Value.size()));
+ Output.push_back(':');
+ Output.append(Value);
+ Output.push_back('\n');
+}
+
+template <typename IntegerT>
+void appendInteger(std::string &Output, std::string_view Name, IntegerT Value) {
+ appendField(Output, Name, std::to_string(Value));
+}
+
+void appendBoolean(std::string &Output, std::string_view Name, bool Value) {
+ appendField(Output, Name, Value ? "true" : "false");
+}
+
+[[nodiscard]] bool isValidName(std::string_view Value) noexcept {
+ return !Value.empty() && std::ranges::all_of(Value, [](unsigned char Character) {
+ return std::isalnum(Character) != 0 || Character == '-' || Character == '_' || Character == '.';
+ });
+}
+
+[[nodiscard]] Expected<void> canonicalizeOptions(std::vector<BackendOption> &Options,
+ std::string_view Category) {
+ std::ranges::sort(Options, {}, &BackendOption::Name);
+ for (std::size_t Index = 0; Index < Options.size(); ++Index) {
+ if (!isValidName(Options[Index].Name)) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ std::string(Category) + " option has an invalid name");
+ }
+ if (Index != 0 && Options[Index - 1].Name == Options[Index].Name) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ std::string(Category) + " option names must be unique");
+ }
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> canonicalizeFeatures(std::vector<std::string> &Features) {
+ for (std::string &Feature : Features) {
+ if (Feature.empty())
+ return makeUnexpected(ErrorCode::InvalidInvocation, "target feature cannot be empty");
+ if (Feature.front() != '+' && Feature.front() != '-')
+ Feature.insert(Feature.begin(), '+');
+ if (!isValidName(std::string_view(Feature).substr(1))) {
+ return makeUnexpected(ErrorCode::InvalidInvocation, "target feature has an invalid spelling");
+ }
+ }
+ std::ranges::sort(Features, [](const std::string &Left, const std::string &Right) {
+ return std::string_view(Left).substr(1) < std::string_view(Right).substr(1);
+ });
+ for (std::size_t Index = 1; Index < Features.size(); ++Index) {
+ if (std::string_view(Features[Index - 1]).substr(1) ==
+ std::string_view(Features[Index]).substr(1)) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "target feature names must be unique and non-conflicting");
+ }
+ }
+ return {};
+}
+
+[[nodiscard]] bool isSHA256(std::string_view Value) noexcept {
+ return Value.size() == 64 && std::ranges::all_of(Value, [](unsigned char Character) {
+ return std::isxdigit(Character) != 0 &&
+ (std::isdigit(Character) != 0 || std::islower(Character) != 0);
+ });
+}
+
+void appendOperandDescriptor(std::string &Output, const OperandDescriptor &Operand) {
+ appendInteger(Output, "operand.kinds", Operand.Kinds.rawValue());
+ appendInteger(Output, "operand.role", static_cast<unsigned>(Operand.RegisterRole));
+ appendInteger(Output, "operand.reg-class", Operand.RegClass ? Operand.RegClass->value() : 0U);
+ appendInteger(Output, "operand.reg-bank", Operand.RegBank ? Operand.RegBank->value() : 0U);
+ appendInteger(Output, "operand.immediate-predicate",
+ Operand.ImmediatePredicate ? Operand.ImmediatePredicate->value() : 0U);
+ appendInteger(Output, "operand.memory-role", static_cast<unsigned>(Operand.MemoryRole));
+ appendInteger(Output, "operand.tie",
+ Operand.DefaultTiedOperand ? *Operand.DefaultTiedOperand + 1U : 0U);
+ appendBoolean(Output, "operand.early-clobber", Operand.EarlyClobber);
+ appendBoolean(Output, "operand.optional", Operand.Optional);
+ appendBoolean(Output, "operand.variadic-group", Operand.VariadicGroup);
+ appendBoolean(Output, "operand.allow-subreg", Operand.AllowSubReg);
+ appendBoolean(Output, "operand.allow-any-target-flags", Operand.AllowAnyTargetFlags);
+ appendInteger(Output, "operand.named", Operand.Name ? Operand.Name->value() : 0U);
+ appendInteger(Output, "operand.target-type",
+ Operand.TargetOperandType ? Operand.TargetOperandType->value() : 0U);
+ appendInteger(Output, "operand.target-flags", Operand.AllowedTargetFlags);
+ appendInteger(Output, "operand.type-kinds",
+ Operand.TypeConstraint ? Operand.TypeConstraint->Kinds.rawValue() : 0U);
+ appendInteger(Output, "operand.type-equality",
+ Operand.TypeConstraint && Operand.TypeConstraint->EqualityGroup
+ ? static_cast<std::uint32_t>(*Operand.TypeConstraint->EqualityGroup) + 1U
+ : 0U);
+}
+
+void appendInstructionSet(std::string &Output, const InstructionSet &Instructions) {
+ appendInteger(Output, "instructions.count", Instructions.descriptors().size());
+ for (const InstructionDescriptor &Descriptor : Instructions.descriptors()) {
+ appendInteger(Output, "instruction.opcode", Descriptor.InstructionOpcode.value());
+ appendField(Output, "instruction.name", Descriptor.Name);
+ appendInteger(Output, "instruction.explicit-defs", Descriptor.NumExplicitDefs);
+ appendBoolean(Output, "instruction.variadic", Descriptor.Variadic);
+ appendInteger(Output, "instruction.minimum-variadic", Descriptor.MinimumVariadicRepetitions);
+ appendBoolean(Output, "instruction.extra-implicit", Descriptor.AllowExtraImplicitOperands);
+ appendInteger(Output, "instruction.flags", Descriptor.Flags.rawValue());
+ appendInteger(Output, "instruction.scheduling-class", Descriptor.SchedulingClass.value());
+ appendInteger(Output, "instruction.encoding-category", Descriptor.EncodingCategory.value());
+ appendInteger(Output, "instruction.assembly-format", Descriptor.AssemblyFormat.value());
+ appendBoolean(Output, "instruction.memory-operands", Descriptor.RequiresMemoryOperands);
+ appendInteger(Output, "instruction.pseudo-owner", static_cast<unsigned>(Descriptor.Owner));
+ appendInteger(Output, "instruction.deadline", static_cast<unsigned>(Descriptor.Deadline));
+ appendBoolean(Output, "instruction.explicit-verifier",
+ Descriptor.VerifyExplicitOperands != nullptr);
+ appendBoolean(Output, "instruction.semantic-verifier",
+ Descriptor.VerifyInstructionSemantics != nullptr);
+ appendInteger(Output, "instruction.operands", Descriptor.Operands.size());
+ for (const OperandDescriptor &Operand : Descriptor.Operands)
+ appendOperandDescriptor(Output, Operand);
+ appendInteger(Output, "instruction.implicit-defs", Descriptor.ImplicitDefs.size());
+ for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitDefs) {
+ appendInteger(Output, "implicit.register", Implicit.Reg.number());
+ appendBoolean(Output, "implicit.early-clobber", Implicit.EarlyClobber);
+ appendBoolean(Output, "implicit.renamable", Implicit.Renamable);
+ }
+ appendInteger(Output, "instruction.implicit-uses", Descriptor.ImplicitUses.size());
+ for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitUses) {
+ appendInteger(Output, "implicit.register", Implicit.Reg.number());
+ appendBoolean(Output, "implicit.early-clobber", Implicit.EarlyClobber);
+ appendBoolean(Output, "implicit.renamable", Implicit.Renamable);
+ }
+ }
+}
+
+void appendRegisterInfo(std::string &Output, const TargetRegisterInfo &Registers,
+ const TargetMIRTextInfo &MIRText) {
+ appendInteger(Output, "register-units.count", Registers.registerUnits().size());
+ for (const RegisterUnitDesc &Unit : Registers.registerUnits()) {
+ appendInteger(Output, "register-unit.id", Unit.ID.value());
+ appendField(Output, "register-unit.name", Unit.Name);
+ }
+ appendInteger(Output, "registers.count", Registers.physicalRegisters().size());
+ for (const PhysicalRegisterDesc &Register : Registers.physicalRegisters()) {
+ appendInteger(Output, "register.id", Register.Reg.number());
+ appendField(Output, "register.name", Register.Name);
+ appendInteger(Output, "register.encoding", Register.Encoding);
+ appendBoolean(Output, "register.constant", Register.IsConstant);
+ appendBoolean(Output, "register.artificial", Register.IsArtificial);
+ appendBoolean(Output, "register.reserved", Register.IsReserved);
+ for (const RegisterUnitLane &Unit : Register.Units) {
+ appendInteger(Output, "register.unit", Unit.Unit.value());
+ appendInteger(Output, "register.lanes", Unit.Lanes.rawValue());
+ }
+ for (const std::string &Name : Register.AlternativeNames)
+ appendField(Output, "register.alternative-name", Name);
+ appendInteger(
+ Output, "register.dwarf",
+ MIRText.dwarfRegister(Register.Reg).value_or(std::numeric_limits<std::uint32_t>::max()));
+ }
+ appendInteger(Output, "subregister-indices.count", Registers.subRegIndices().size());
+ for (const SubRegIndexDesc &Index : Registers.subRegIndices()) {
+ appendInteger(Output, "subregister-index.id", Index.ID.value());
+ appendField(Output, "subregister-index.name", Index.Name);
+ appendInteger(Output, "subregister-index.lanes", Index.Lanes.rawValue());
+ appendInteger(Output, "subregister-index.size", Index.SizeInBits);
+ appendInteger(Output, "subregister-index.offset", Index.OffsetInBits);
+ }
+ appendInteger(Output, "register-classes.count", Registers.registerClasses().size());
+ for (const RegisterClassDesc &Class : Registers.registerClasses()) {
+ appendInteger(Output, "register-class.id", Class.ID.value());
+ appendField(Output, "register-class.name", Class.Name);
+ appendInteger(Output, "register-class.size", Class.SizeInBits);
+ appendInteger(Output, "register-class.spill-size", Class.SpillSizeInBits);
+ appendInteger(Output, "register-class.spill-alignment", Class.SpillAlignmentInBits);
+ appendInteger(Output, "register-class.lanes", Class.Lanes.rawValue());
+ appendInteger(Output, "register-class.bank", Class.DefaultBank.value());
+ appendBoolean(Output, "register-class.allocatable", Class.Allocatable);
+ appendInteger(Output, "register-class.copy-cost", Class.CopyCost);
+ appendBoolean(Output, "register-class.covered-by-subregs", Class.CoveredBySubRegs);
+ for (PhysicalRegister Register : Class.Registers)
+ appendInteger(Output, "register-class.register", Register.number());
+ for (PhysicalRegister Register : Class.AllocationOrder)
+ appendInteger(Output, "register-class.allocation-order", Register.number());
+ for (SubRegIndex Index : Class.SupportedSubRegs)
+ appendInteger(Output, "register-class.subregister", Index.value());
+ for (RegisterClassID SuperClass : Class.SuperClasses)
+ appendInteger(Output, "register-class.superclass", SuperClass.value());
+ }
+ appendInteger(Output, "register-banks.count", Registers.registerBanks().size());
+ for (const RegisterBankDesc &Bank : Registers.registerBanks()) {
+ appendInteger(Output, "register-bank.id", Bank.ID.value());
+ appendField(Output, "register-bank.name", Bank.Name);
+ appendInteger(Output, "register-bank.maximum-size", Bank.MaximumSizeInBits);
+ for (RegisterClassID Class : Bank.RegisterClasses)
+ appendInteger(Output, "register-bank.class", Class.value());
+ }
+}
+
+void appendMIRTextInfo(std::string &Output, const TargetMIRTextInfo &MIRText) {
+ appendInteger(Output, "mir.direct-flag-mask", MIRText.directOperandFlagMask());
+ for (const MIRRegisterMaskDesc &Mask : MIRText.registerMasks()) {
+ appendField(Output, "mir.register-mask", Mask.Name);
+ if (Mask.Mask != nullptr) {
+ for (std::uint64_t Word : Mask.Mask->words())
+ appendInteger(Output, "mir.register-mask-word", Word);
+ }
+ }
+ for (const MIRTargetIndexDesc &Index : MIRText.targetIndices()) {
+ appendField(Output, "mir.target-index-name", Index.Name);
+ appendInteger(Output, "mir.target-index", Index.Index.value());
+ }
+ for (const MIROperandFlagDesc &Flag : MIRText.directOperandFlags()) {
+ appendField(Output, "mir.direct-flag-name", Flag.Name);
+ appendInteger(Output, "mir.direct-flag", Flag.Value);
+ }
+ for (const MIROperandFlagDesc &Flag : MIRText.bitmaskOperandFlags()) {
+ appendField(Output, "mir.bitmask-flag-name", Flag.Name);
+ appendInteger(Output, "mir.bitmask-flag", Flag.Value);
+ }
+ for (const MIRMachineMemOperandFlagDesc &Flag : MIRText.machineMemOperandFlags()) {
+ appendField(Output, "mir.mmo-flag-name", Flag.Name);
+ appendInteger(Output, "mir.mmo-flag", Flag.Value);
+ }
+ for (const MIRVirtualRegisterFlagDesc &Flag : MIRText.virtualRegisterFlags()) {
+ appendField(Output, "mir.vreg-flag-name", Flag.Name);
+ appendInteger(Output, "mir.vreg-flag", Flag.Value);
+ }
+ for (const MIRIntrinsicDesc &Intrinsic : MIRText.intrinsics()) {
+ appendField(Output, "mir.intrinsic-name", Intrinsic.Name);
+ appendInteger(Output, "mir.intrinsic", Intrinsic.ID.value());
+ }
+ for (StackID ID : MIRText.supportedStackIDs())
+ appendInteger(Output, "mir.stack-id", ID.value());
+ appendInteger(Output, "mir.function-state-type",
+ MIRText.functionStateType() ? MIRText.functionStateType()->value() : 0U);
+}
+
+} // namespace
+
+std::string_view spelling(RelocationModel Value) noexcept {
+ switch (Value) {
+ case RelocationModel::Static:
+ return "static";
+ case RelocationModel::PositionIndependent:
+ return "pic";
+ }
+ return "invalid";
+}
+
+std::string_view spelling(CodeModel Value) noexcept {
+ switch (Value) {
+ case CodeModel::Small:
+ return "small";
+ case CodeModel::Medium:
+ return "medium";
+ case CodeModel::Large:
+ return "large";
+ }
+ return "invalid";
+}
+
+std::string_view spelling(OptimizationLevel Value) noexcept {
+ switch (Value) {
+ case OptimizationLevel::O0:
+ return "O0";
+ case OptimizationLevel::O1:
+ return "O1";
+ case OptimizationLevel::O2:
+ return "O2";
+ case OptimizationLevel::O3:
+ return "O3";
+ case OptimizationLevel::Os:
+ return "Os";
+ case OptimizationLevel::Oz:
+ return "Oz";
+ }
+ return "invalid";
+}
+
+std::string_view spelling(LayoutOwnership Value) noexcept {
+ switch (Value) {
+ case LayoutOwnership::NativeObject:
+ return "native-object";
+ case LayoutOwnership::CompilerFinalLayout:
+ return "compiler-final-layout";
+ case LayoutOwnership::ExternalRelaxation:
+ return "external-relaxation";
+ }
+ return "invalid";
+}
+
+Expected<RelocationModel> parseRelocationModel(std::string_view Value) {
+ if (Value == "static")
+ return RelocationModel::Static;
+ if (Value == "pic")
+ return RelocationModel::PositionIndependent;
+ return makeUnexpected(ErrorCode::ManifestError, "unknown relocation model");
+}
+
+Expected<CodeModel> parseCodeModel(std::string_view Value) {
+ if (Value == "small")
+ return CodeModel::Small;
+ if (Value == "medium")
+ return CodeModel::Medium;
+ if (Value == "large")
+ return CodeModel::Large;
+ return makeUnexpected(ErrorCode::ManifestError, "unknown code model");
+}
+
+Expected<OptimizationLevel> parseOptimizationLevel(std::string_view Value) {
+ if (Value == "O0")
+ return OptimizationLevel::O0;
+ if (Value == "O1")
+ return OptimizationLevel::O1;
+ if (Value == "O2")
+ return OptimizationLevel::O2;
+ if (Value == "O3")
+ return OptimizationLevel::O3;
+ if (Value == "Os")
+ return OptimizationLevel::Os;
+ if (Value == "Oz")
+ return OptimizationLevel::Oz;
+ return makeUnexpected(ErrorCode::ManifestError, "unknown optimization level");
+}
+
+Expected<LayoutOwnership> parseLayoutOwnership(std::string_view Value) {
+ if (Value == "native-object")
+ return LayoutOwnership::NativeObject;
+ if (Value == "compiler-final-layout")
+ return LayoutOwnership::CompilerFinalLayout;
+ if (Value == "external-relaxation")
+ return LayoutOwnership::ExternalRelaxation;
+ return makeUnexpected(ErrorCode::ManifestError, "unknown layout ownership");
+}
+
+Expected<void> BackendInvocation::canonicalizeAndVerify() {
+ if (SchemaVersion != BackendInvocationSchemaVersion) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "backend invocation schema version is unsupported");
+ }
+ if (Triple.empty() || CPU.empty() || TuneCPU.empty() || ABI.empty() || DataLayout.empty() ||
+ MIRReferenceRevision.empty() || GenericOpcodeVersion.empty() ||
+ TargetABILoweringVersion.empty() || BuildFingerprint.empty()) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "backend invocation is missing a required target or version fact");
+ }
+ if (Determinism.ThreadCount == 0) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "backend invocation thread count must be nonzero");
+ }
+ auto FeatureResult = canonicalizeFeatures(Features);
+ if (!FeatureResult)
+ return FeatureResult;
+ auto TargetOptionResult = canonicalizeOptions(TargetOptions, "target");
+ if (!TargetOptionResult)
+ return TargetOptionResult;
+ auto AssemblerOptionResult = canonicalizeOptions(AssemblerOptions, "assembler");
+ if (!AssemblerOptionResult)
+ return AssemblerOptionResult;
+ if (PassPipeline.empty() ||
+ std::ranges::any_of(PassPipeline, [](const std::string &Pass) { return Pass.empty(); })) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "backend invocation pass pipeline cannot be empty");
+ }
+ if (Layout == LayoutOwnership::ExternalRelaxation &&
+ (AssemblerFlavor.empty() || AssemblerVersion.empty())) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "external relaxation requires explicit assembler flavor and version");
+ }
+ if (AssemblerFlavor.empty() != AssemblerVersion.empty()) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "assembler flavor and version must be specified together");
+ }
+ std::ranges::sort(EnabledActions);
+ if (std::ranges::any_of(EnabledActions,
+ [](const std::string &Action) { return !isValidName(Action); }) ||
+ std::ranges::adjacent_find(EnabledActions) != EnabledActions.end()) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "enabled action names must be valid and unique");
+ }
+ return {};
+}
+
+std::string BackendInvocation::canonicalText() const {
+ std::string Output;
+ appendInteger(Output, "schema", SchemaVersion);
+ appendField(Output, "triple", Triple);
+ appendField(Output, "cpu", CPU);
+ appendField(Output, "tune-cpu", TuneCPU);
+ for (const std::string &Feature : Features)
+ appendField(Output, "feature", Feature);
+ appendField(Output, "abi", ABI);
+ appendField(Output, "data-layout", DataLayout);
+ appendField(Output, "relocation-model", spelling(Relocation));
+ appendField(Output, "code-model", spelling(Code));
+ for (const BackendOption &Option : TargetOptions) {
+ appendField(Output, "target-option-name", Option.Name);
+ appendField(Output, "target-option-value", Option.Value);
+ }
+ appendField(Output, "layout-ownership", spelling(Layout));
+ appendField(Output, "assembler-flavor", AssemblerFlavor);
+ appendField(Output, "assembler-version", AssemblerVersion);
+ for (const BackendOption &Option : AssemblerOptions) {
+ appendField(Output, "assembler-option-name", Option.Name);
+ appendField(Output, "assembler-option-value", Option.Value);
+ }
+ appendField(Output, "optimization-level", spelling(Optimization));
+ for (const std::string &Pass : PassPipeline)
+ appendField(Output, "pass", Pass);
+ appendInteger(Output, "random-seed", Determinism.RandomSeed);
+ appendInteger(Output, "thread-count", Determinism.ThreadCount);
+ appendBoolean(Output, "stable-scheduling", Determinism.StableScheduling);
+ appendField(Output, "action-fuel", ActionFuel ? std::to_string(*ActionFuel) : "unlimited");
+ for (const std::string &Action : EnabledActions)
+ appendField(Output, "enabled-action", Action);
+ appendField(Output, "mir-reference-revision", MIRReferenceRevision);
+ appendField(Output, "generic-opcode-version", GenericOpcodeVersion);
+ appendField(Output, "target-abi-lowering-version", TargetABILoweringVersion);
+ appendField(Output, "build-fingerprint", BuildFingerprint);
+ return Output;
+}
+
+BackendInvocation defaultRV64GCInvocation() {
+ BackendInvocation Result;
+ Result.Triple = "riscv64-unknown-linux-gnu";
+ Result.CPU = "generic-rv64gc";
+ Result.TuneCPU = "generic-rv64gc";
+ Result.Features = {"+i", "+m", "+a", "+f", "+d", "+c", "+zicsr", "+zifencei"};
+ Result.ABI = "lp64d";
+ Result.DataLayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128";
+ Result.TargetOptions = {
+ {"function-sections", "false"},
+ {"omit-frame-pointer", "true"},
+ {"stack-realignment", "false"},
+ };
+ Result.AssemblerFlavor = "rcc-integrated-gnu";
+ Result.AssemblerVersion = "rcc-mc-v1";
+ Result.AssemblerOptions = {{"relax", "true"}};
+ auto Verified = Result.canonicalizeAndVerify();
+ (void)Verified;
+ return Result;
+}
+
+Expected<std::string> computeTargetDescriptionFingerprint(const TargetDescription &Target) {
+ std::string Canonical;
+ appendField(Canonical, "fingerprint-schema", "rcc-target-fingerprint-v1");
+ appendInstructionSet(Canonical, Target.instructions());
+ appendRegisterInfo(Canonical, Target.registers(), Target.mirText());
+ appendMIRTextInfo(Canonical, Target.mirText());
+ return sha256Hex(Canonical);
+}
+
+Expected<std::string> computeCacheKey(std::string_view CanonicalMIR,
+ const BackendInvocation &Invocation,
+ std::string_view TargetFingerprint) {
+ BackendInvocation CanonicalInvocation = Invocation;
+ auto Verified = CanonicalInvocation.canonicalizeAndVerify();
+ if (!Verified)
+ return std::unexpected(Verified.error());
+ if (!isSHA256(TargetFingerprint)) {
+ return makeUnexpected(ErrorCode::InvalidInvocation,
+ "target description fingerprint must be a lowercase SHA-256 digest");
+ }
+ std::string Input;
+ appendField(Input, "cache-key-schema", "rcc-backend-cache-key-v1");
+ appendField(Input, "input-mir-semantic-hash", sha256Hex(CanonicalMIR));
+ appendField(Input, "target-description-fingerprint", TargetFingerprint);
+ appendField(Input, "invocation", CanonicalInvocation.canonicalText());
+ return sha256Hex(Input);
+}
+
+} // namespace rcc::tooling
diff --git a/backend/lib/Tooling/BackendManifest.cpp b/backend/lib/Tooling/BackendManifest.cpp
new file mode 100644
index 0000000..04c410a
--- /dev/null
+++ b/backend/lib/Tooling/BackendManifest.cpp
@@ -0,0 +1,733 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/BackendManifest.h"
+
+#include "rcc/Support/YAML.h"
+
+#include <algorithm>
+#include <cctype>
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+namespace rcc::tooling {
+namespace {
+
+[[nodiscard]] bool isDigest(std::string_view Value) noexcept {
+ return Value.size() == 64 && std::ranges::all_of(Value, [](unsigned char Character) {
+ return (Character >= '0' && Character <= '9') || (Character >= 'a' && Character <= 'f');
+ });
+}
+
+[[nodiscard]] std::string quoted(std::string_view Value) {
+ constexpr std::string_view Hex = "0123456789abcdef";
+ std::string Result{"\""};
+ for (char RawCharacter : Value) {
+ const auto Character = static_cast<unsigned char>(RawCharacter);
+ switch (Character) {
+ case '\\':
+ Result.append("\\\\");
+ break;
+ case '"':
+ Result.append("\\\"");
+ break;
+ case '\n':
+ Result.append("\\n");
+ break;
+ case '\r':
+ Result.append("\\r");
+ break;
+ case '\t':
+ Result.append("\\t");
+ break;
+ default:
+ if (Character < 0x20U || Character == 0x7fU) {
+ Result.append("\\u00");
+ Result.push_back(Hex[Character >> 4U]);
+ Result.push_back(Hex[Character & 0x0fU]);
+ } else {
+ Result.push_back(static_cast<char>(Character));
+ }
+ break;
+ }
+ }
+ Result.push_back('"');
+ return Result;
+}
+
+void appendScalar(std::string &Output, std::string_view Indent, std::string_view Name,
+ std::string_view Value) {
+ Output.append(Indent);
+ Output.append(Name);
+ Output.append(": ");
+ Output.append(quoted(Value));
+ Output.push_back('\n');
+}
+
+template <typename IntegerT>
+void appendInteger(std::string &Output, std::string_view Indent, std::string_view Name,
+ IntegerT Value) {
+ Output.append(Indent);
+ Output.append(Name);
+ Output.append(": ");
+ Output.append(std::to_string(Value));
+ Output.push_back('\n');
+}
+
+void appendBoolean(std::string &Output, std::string_view Indent, std::string_view Name,
+ bool Value) {
+ Output.append(Indent);
+ Output.append(Name);
+ Output.append(Value ? ": true\n" : ": false\n");
+}
+
+void appendStringSequence(std::string &Output, std::string_view Indent, std::string_view Name,
+ std::span<const std::string> Values) {
+ Output.append(Indent);
+ Output.append(Name);
+ if (Values.empty()) {
+ Output.append(": []\n");
+ return;
+ }
+ Output.append(":\n");
+ for (const std::string &Value : Values) {
+ Output.append(Indent);
+ Output.append(" - ");
+ Output.append(quoted(Value));
+ Output.push_back('\n');
+ }
+}
+
+void appendOptions(std::string &Output, std::string_view Indent, std::string_view Name,
+ std::span<const BackendOption> Options) {
+ Output.append(Indent);
+ Output.append(Name);
+ if (Options.empty()) {
+ Output.append(": []\n");
+ return;
+ }
+ Output.append(":\n");
+ for (const BackendOption &Option : Options) {
+ Output.append(Indent);
+ Output.append(" - name: ");
+ Output.append(quoted(Option.Name));
+ Output.push_back('\n');
+ appendScalar(Output, std::string(Indent) + " ", "value", Option.Value);
+ }
+}
+
+void appendInvocation(std::string &Output, const BackendInvocation &Invocation) {
+ appendInteger(Output, " ", "schema", Invocation.SchemaVersion);
+ appendScalar(Output, " ", "triple", Invocation.Triple);
+ appendScalar(Output, " ", "cpu", Invocation.CPU);
+ appendScalar(Output, " ", "tune_cpu", Invocation.TuneCPU);
+ appendStringSequence(Output, " ", "features", Invocation.Features);
+ appendScalar(Output, " ", "abi", Invocation.ABI);
+ appendScalar(Output, " ", "data_layout", Invocation.DataLayout);
+ appendScalar(Output, " ", "relocation_model", spelling(Invocation.Relocation));
+ appendScalar(Output, " ", "code_model", spelling(Invocation.Code));
+ appendOptions(Output, " ", "target_options", Invocation.TargetOptions);
+ appendScalar(Output, " ", "layout_ownership", spelling(Invocation.Layout));
+ appendScalar(Output, " ", "assembler_flavor", Invocation.AssemblerFlavor);
+ appendScalar(Output, " ", "assembler_version", Invocation.AssemblerVersion);
+ appendOptions(Output, " ", "assembler_options", Invocation.AssemblerOptions);
+ appendScalar(Output, " ", "optimization_level", spelling(Invocation.Optimization));
+ appendStringSequence(Output, " ", "pass_pipeline", Invocation.PassPipeline);
+ appendInteger(Output, " ", "random_seed", Invocation.Determinism.RandomSeed);
+ appendInteger(Output, " ", "thread_count", Invocation.Determinism.ThreadCount);
+ appendBoolean(Output, " ", "stable_scheduling", Invocation.Determinism.StableScheduling);
+ if (Invocation.ActionFuel) {
+ appendInteger(Output, " ", "action_fuel", *Invocation.ActionFuel);
+ } else {
+ Output.append(" action_fuel: null\n");
+ }
+ appendStringSequence(Output, " ", "enabled_actions", Invocation.EnabledActions);
+ appendScalar(Output, " ", "mir_reference_revision", Invocation.MIRReferenceRevision);
+ appendScalar(Output, " ", "generic_opcode_version", Invocation.GenericOpcodeVersion);
+ appendScalar(Output, " ", "target_abi_lowering_version", Invocation.TargetABILoweringVersion);
+ appendScalar(Output, " ", "build_fingerprint", Invocation.BuildFingerprint);
+}
+
+void appendOptionalInteger(std::string &Output, std::string_view Indent, std::string_view Name,
+ std::optional<std::uint64_t> Value) {
+ if (Value) {
+ appendInteger(Output, Indent, Name, *Value);
+ } else {
+ Output.append(Indent);
+ Output.append(Name);
+ Output.append(": null\n");
+ }
+}
+
+class ManifestParser final {
+public:
+ ManifestParser(std::string SourceName, std::string SourceText)
+ : Parsed(yaml::parse(std::move(SourceName), std::move(SourceText))) {}
+
+ [[nodiscard]] Expected<BackendManifest> run() {
+ if (!Parsed.succeeded()) {
+ const std::span<const TextDiagnostic> Diagnostics = Parsed.diagnostics();
+ return makeUnexpected(ErrorCode::ManifestError, Diagnostics.empty()
+ ? "manifest YAML parsing failed"
+ : Diagnostics.front().Message);
+ }
+ Stream = Parsed.stream();
+ if (Stream == nullptr || Stream->documents().size() != 1) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest must contain exactly one YAML document");
+ }
+ const yaml::Node *Root = Stream->node(Stream->documents().front().Root);
+ auto RootMapping = requireMapping(Root, "manifest root");
+ if (!RootMapping)
+ return std::unexpected(RootMapping.error());
+ auto RootKeys =
+ rejectUnknown(**RootMapping,
+ {"schema", "kind", "artifact", "input_mir_hash", "target_fingerprint",
+ "cache_key", "artifact_hash", "invocation", "diagnostics"},
+ "manifest root");
+ if (!RootKeys)
+ return std::unexpected(RootKeys.error());
+
+ BackendManifest Result;
+ auto Schema = requiredUnsigned<std::uint32_t>(**RootMapping, "schema", "manifest root");
+ auto Kind = requiredScalar(**RootMapping, "kind", "manifest root");
+ auto Artifact = requiredScalar(**RootMapping, "artifact", "manifest root");
+ auto InputHash = requiredScalar(**RootMapping, "input_mir_hash", "manifest root");
+ auto TargetHash = requiredScalar(**RootMapping, "target_fingerprint", "manifest root");
+ auto CacheKey = requiredScalar(**RootMapping, "cache_key", "manifest root");
+ const yaml::Node *Invocation = mappingValue(**RootMapping, "invocation");
+ const yaml::Node *Diagnostics = mappingValue(**RootMapping, "diagnostics");
+ if (!Schema || !Kind || !Artifact || !InputHash || !TargetHash || !CacheKey)
+ return firstError(Schema, Kind, Artifact, InputHash, TargetHash, CacheKey);
+ Result.SchemaVersion = *Schema;
+ auto ParsedKind = parseManifestKind(*Kind);
+ if (!ParsedKind)
+ return std::unexpected(ParsedKind.error());
+ Result.Kind = *ParsedKind;
+ auto ParsedArtifact = parseArtifactKind(*Artifact);
+ if (!ParsedArtifact)
+ return std::unexpected(ParsedArtifact.error());
+ Result.Artifact = *ParsedArtifact;
+ Result.InputMIRHash = std::move(*InputHash);
+ Result.TargetFingerprint = std::move(*TargetHash);
+ Result.CacheKey = std::move(*CacheKey);
+ if (const yaml::Node *ArtifactHash = mappingValue(**RootMapping, "artifact_hash");
+ ArtifactHash != nullptr && ArtifactHash->kind() != yaml::NodeKind::Null) {
+ auto Value = scalar(ArtifactHash, "manifest artifact_hash");
+ if (!Value)
+ return std::unexpected(Value.error());
+ Result.ArtifactHash = std::move(*Value);
+ }
+ auto ParsedInvocation = parseInvocation(Invocation);
+ if (!ParsedInvocation)
+ return std::unexpected(ParsedInvocation.error());
+ Result.Invocation = std::move(*ParsedInvocation);
+ auto ParsedDiagnostics = parseDiagnostics(Diagnostics);
+ if (!ParsedDiagnostics)
+ return std::unexpected(ParsedDiagnostics.error());
+ Result.Diagnostics = std::move(*ParsedDiagnostics);
+ auto Verification = Result.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Result;
+ }
+
+private:
+ template <typename... Results>
+ [[nodiscard]] Expected<BackendManifest> firstError(const Results &...Values) const {
+ std::optional<Error> Failure;
+ const auto Capture = [&Failure](const auto &Value) {
+ if (!Value && !Failure)
+ Failure = Value.error();
+ };
+ (Capture(Values), ...);
+ if (!Failure) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "manifest parser requested a missing field error without a failure");
+ }
+ return std::unexpected(*Failure);
+ }
+
+ [[nodiscard]] Expected<const yaml::Node *> requireMapping(const yaml::Node *Node,
+ std::string_view Context) const {
+ if (Node == nullptr || Node->kind() != yaml::NodeKind::Mapping) {
+ return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + " must be a mapping");
+ }
+ return Node;
+ }
+
+ [[nodiscard]] Expected<void> rejectUnknown(const yaml::Node &Node,
+ std::initializer_list<std::string_view> Allowed,
+ std::string_view Context) const {
+ std::unordered_set<std::string_view> Seen;
+ for (const yaml::MappingEntry &Entry : Node.mapping()) {
+ if (std::ranges::find(Allowed, Entry.Key) == Allowed.end()) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ std::string(Context) + " contains unknown key '" + Entry.Key + "'");
+ }
+ if (!Seen.insert(Entry.Key).second) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ std::string(Context) + " contains duplicate key '" + Entry.Key + "'");
+ }
+ }
+ return {};
+ }
+
+ [[nodiscard]] const yaml::Node *mappingValue(const yaml::Node &Node,
+ std::string_view Name) const noexcept {
+ const std::optional<yaml::NodeID> ID = Node.mappingValue(Name);
+ return ID && Stream != nullptr ? Stream->node(*ID) : nullptr;
+ }
+
+ [[nodiscard]] Expected<std::string> scalar(const yaml::Node *Node,
+ std::string_view Context) const {
+ if (Node == nullptr || Node->kind() != yaml::NodeKind::Scalar) {
+ return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + " must be a scalar");
+ }
+ return std::string(Node->scalar());
+ }
+
+ [[nodiscard]] Expected<std::string> requiredScalar(const yaml::Node &Node, std::string_view Name,
+ std::string_view Context) const {
+ const yaml::Node *Value = mappingValue(Node, Name);
+ if (Value == nullptr) {
+ return makeUnexpected(ErrorCode::ManifestError, std::string(Context) +
+ " is missing required key '" +
+ std::string(Name) + "'");
+ }
+ return scalar(Value, std::string(Context) + "." + std::string(Name));
+ }
+
+ template <typename IntegerT>
+ [[nodiscard]] Expected<IntegerT> unsignedValue(const yaml::Node *Node,
+ std::string_view Context) const {
+ auto Text = scalar(Node, Context);
+ if (!Text)
+ return std::unexpected(Text.error());
+ IntegerT Result = 0;
+ const char *Begin = Text->data();
+ const char *End = Begin + Text->size();
+ const auto [Position, ErrorValue] = std::from_chars(Begin, End, Result);
+ if (ErrorValue != std::errc{} || Position != End) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ std::string(Context) + " must be an unsigned integer");
+ }
+ return Result;
+ }
+
+ template <typename IntegerT>
+ [[nodiscard]] Expected<IntegerT> requiredUnsigned(const yaml::Node &Node, std::string_view Name,
+ std::string_view Context) const {
+ const yaml::Node *Value = mappingValue(Node, Name);
+ if (Value == nullptr) {
+ return makeUnexpected(ErrorCode::ManifestError, std::string(Context) +
+ " is missing required key '" +
+ std::string(Name) + "'");
+ }
+ return unsignedValue<IntegerT>(Value, std::string(Context) + "." + std::string(Name));
+ }
+
+ [[nodiscard]] Expected<bool> requiredBoolean(const yaml::Node &Node, std::string_view Name,
+ std::string_view Context) const {
+ auto Value = requiredScalar(Node, Name, Context);
+ if (!Value)
+ return std::unexpected(Value.error());
+ if (*Value == "true")
+ return true;
+ if (*Value == "false")
+ return false;
+ return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + "." + std::string(Name) +
+ " must be true or false");
+ }
+
+ [[nodiscard]] Expected<std::vector<std::string>> stringSequence(const yaml::Node *Node,
+ std::string_view Context) const {
+ if (Node == nullptr || Node->kind() != yaml::NodeKind::Sequence) {
+ return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + " must be a sequence");
+ }
+ std::vector<std::string> Result;
+ Result.reserve(Node->sequence().size());
+ for (yaml::NodeID ID : Node->sequence()) {
+ auto Value = scalar(Stream->node(ID), Context);
+ if (!Value)
+ return std::unexpected(Value.error());
+ Result.push_back(std::move(*Value));
+ }
+ return Result;
+ }
+
+ [[nodiscard]] Expected<std::vector<BackendOption>> parseOptions(const yaml::Node *Node,
+ std::string_view Context) const {
+ if (Node == nullptr || Node->kind() != yaml::NodeKind::Sequence) {
+ return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + " must be a sequence");
+ }
+ std::vector<BackendOption> Result;
+ Result.reserve(Node->sequence().size());
+ for (yaml::NodeID ID : Node->sequence()) {
+ auto Entry = requireMapping(Stream->node(ID), Context);
+ if (!Entry)
+ return std::unexpected(Entry.error());
+ auto Keys = rejectUnknown(**Entry, {"name", "value"}, Context);
+ if (!Keys)
+ return std::unexpected(Keys.error());
+ auto Name = requiredScalar(**Entry, "name", Context);
+ auto Value = requiredScalar(**Entry, "value", Context);
+ if (!Name)
+ return std::unexpected(Name.error());
+ if (!Value)
+ return std::unexpected(Value.error());
+ Result.push_back({std::move(*Name), std::move(*Value)});
+ }
+ return Result;
+ }
+
+ [[nodiscard]] Expected<BackendInvocation> parseInvocation(const yaml::Node *Node) const {
+ auto Mapping = requireMapping(Node, "manifest invocation");
+ if (!Mapping)
+ return std::unexpected(Mapping.error());
+ auto Keys = rejectUnknown(**Mapping,
+ {"schema",
+ "triple",
+ "cpu",
+ "tune_cpu",
+ "features",
+ "abi",
+ "data_layout",
+ "relocation_model",
+ "code_model",
+ "target_options",
+ "layout_ownership",
+ "assembler_flavor",
+ "assembler_version",
+ "assembler_options",
+ "optimization_level",
+ "pass_pipeline",
+ "random_seed",
+ "thread_count",
+ "stable_scheduling",
+ "action_fuel",
+ "enabled_actions",
+ "mir_reference_revision",
+ "generic_opcode_version",
+ "target_abi_lowering_version",
+ "build_fingerprint"},
+ "manifest invocation");
+ if (!Keys)
+ return std::unexpected(Keys.error());
+
+ BackendInvocation Result;
+ auto Schema = requiredUnsigned<std::uint32_t>(**Mapping, "schema", "manifest invocation");
+ auto Triple = requiredScalar(**Mapping, "triple", "manifest invocation");
+ auto CPU = requiredScalar(**Mapping, "cpu", "manifest invocation");
+ auto TuneCPU = requiredScalar(**Mapping, "tune_cpu", "manifest invocation");
+ auto Features = stringSequence(mappingValue(**Mapping, "features"), "invocation.features");
+ auto ABI = requiredScalar(**Mapping, "abi", "manifest invocation");
+ auto DataLayout = requiredScalar(**Mapping, "data_layout", "manifest invocation");
+ auto Relocation = requiredScalar(**Mapping, "relocation_model", "manifest invocation");
+ auto Code = requiredScalar(**Mapping, "code_model", "manifest invocation");
+ auto TargetOptions =
+ parseOptions(mappingValue(**Mapping, "target_options"), "invocation.target_options");
+ auto Layout = requiredScalar(**Mapping, "layout_ownership", "manifest invocation");
+ auto AssemblerFlavor = requiredScalar(**Mapping, "assembler_flavor", "manifest invocation");
+ auto AssemblerVersion = requiredScalar(**Mapping, "assembler_version", "manifest invocation");
+ auto AssemblerOptions =
+ parseOptions(mappingValue(**Mapping, "assembler_options"), "invocation.assembler_options");
+ auto Optimization = requiredScalar(**Mapping, "optimization_level", "manifest invocation");
+ auto Pipeline =
+ stringSequence(mappingValue(**Mapping, "pass_pipeline"), "invocation.pass_pipeline");
+ auto RandomSeed =
+ requiredUnsigned<std::uint64_t>(**Mapping, "random_seed", "manifest invocation");
+ auto ThreadCount =
+ requiredUnsigned<std::uint32_t>(**Mapping, "thread_count", "manifest invocation");
+ auto Stable = requiredBoolean(**Mapping, "stable_scheduling", "manifest invocation");
+ auto Actions =
+ stringSequence(mappingValue(**Mapping, "enabled_actions"), "invocation.enabled_actions");
+ auto MIRRevision = requiredScalar(**Mapping, "mir_reference_revision", "manifest invocation");
+ auto GenericVersion =
+ requiredScalar(**Mapping, "generic_opcode_version", "manifest invocation");
+ auto ABIVersion =
+ requiredScalar(**Mapping, "target_abi_lowering_version", "manifest invocation");
+ auto Build = requiredScalar(**Mapping, "build_fingerprint", "manifest invocation");
+
+ if (!Schema || !Triple || !CPU || !TuneCPU || !Features || !ABI || !DataLayout || !Relocation ||
+ !Code || !TargetOptions || !Layout || !AssemblerFlavor || !AssemblerVersion ||
+ !AssemblerOptions || !Optimization || !Pipeline || !RandomSeed || !ThreadCount || !Stable ||
+ !Actions || !MIRRevision || !GenericVersion || !ABIVersion || !Build) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest invocation is missing or has an invalid required field");
+ }
+ Result.SchemaVersion = *Schema;
+ Result.Triple = std::move(*Triple);
+ Result.CPU = std::move(*CPU);
+ Result.TuneCPU = std::move(*TuneCPU);
+ Result.Features = std::move(*Features);
+ Result.ABI = std::move(*ABI);
+ Result.DataLayout = std::move(*DataLayout);
+ auto ParsedRelocation = parseRelocationModel(*Relocation);
+ auto ParsedCode = parseCodeModel(*Code);
+ auto ParsedLayout = parseLayoutOwnership(*Layout);
+ auto ParsedOptimization = parseOptimizationLevel(*Optimization);
+ if (!ParsedRelocation || !ParsedCode || !ParsedLayout || !ParsedOptimization) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest invocation contains an unknown enum value");
+ }
+ Result.Relocation = *ParsedRelocation;
+ Result.Code = *ParsedCode;
+ Result.TargetOptions = std::move(*TargetOptions);
+ Result.Layout = *ParsedLayout;
+ Result.AssemblerFlavor = std::move(*AssemblerFlavor);
+ Result.AssemblerVersion = std::move(*AssemblerVersion);
+ Result.AssemblerOptions = std::move(*AssemblerOptions);
+ Result.Optimization = *ParsedOptimization;
+ Result.PassPipeline = std::move(*Pipeline);
+ Result.Determinism = {
+ .RandomSeed = *RandomSeed,
+ .ThreadCount = *ThreadCount,
+ .StableScheduling = *Stable,
+ };
+ const yaml::Node *Fuel = mappingValue(**Mapping, "action_fuel");
+ if (Fuel == nullptr) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest invocation is missing required key 'action_fuel'");
+ }
+ if (Fuel->kind() != yaml::NodeKind::Null) {
+ auto Value = unsignedValue<std::uint64_t>(Fuel, "invocation.action_fuel");
+ if (!Value)
+ return std::unexpected(Value.error());
+ Result.ActionFuel = *Value;
+ }
+ Result.EnabledActions = std::move(*Actions);
+ Result.MIRReferenceRevision = std::move(*MIRRevision);
+ Result.GenericOpcodeVersion = std::move(*GenericVersion);
+ Result.TargetABILoweringVersion = std::move(*ABIVersion);
+ Result.BuildFingerprint = std::move(*Build);
+ auto Verified = Result.canonicalizeAndVerify();
+ if (!Verified)
+ return std::unexpected(Verified.error());
+ return Result;
+ }
+
+ [[nodiscard]] Expected<std::vector<StructuredDiagnostic>>
+ parseDiagnostics(const yaml::Node *Node) const {
+ if (Node == nullptr || Node->kind() != yaml::NodeKind::Sequence) {
+ return makeUnexpected(ErrorCode::ManifestError, "manifest diagnostics must be a sequence");
+ }
+ std::vector<StructuredDiagnostic> Result;
+ for (yaml::NodeID ID : Node->sequence()) {
+ auto Mapping = requireMapping(Stream->node(ID), "manifest diagnostic");
+ if (!Mapping)
+ return std::unexpected(Mapping.error());
+ auto Keys =
+ rejectUnknown(**Mapping,
+ {"category", "stage", "pass", "function", "block", "instruction", "operand",
+ "source_location", "target_constraint", "rule", "message"},
+ "manifest diagnostic");
+ if (!Keys)
+ return std::unexpected(Keys.error());
+ StructuredDiagnostic Diagnostic;
+ auto Category = requiredScalar(**Mapping, "category", "manifest diagnostic");
+ auto Stage = requiredScalar(**Mapping, "stage", "manifest diagnostic");
+ auto Pass = requiredScalar(**Mapping, "pass", "manifest diagnostic");
+ auto Function = requiredScalar(**Mapping, "function", "manifest diagnostic");
+ auto Source = requiredScalar(**Mapping, "source_location", "manifest diagnostic");
+ auto Constraint = requiredScalar(**Mapping, "target_constraint", "manifest diagnostic");
+ auto Rule = requiredScalar(**Mapping, "rule", "manifest diagnostic");
+ auto Message = requiredScalar(**Mapping, "message", "manifest diagnostic");
+ if (!Category || !Stage || !Pass || !Function || !Source || !Constraint || !Rule ||
+ !Message) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest diagnostic is missing a required string field");
+ }
+ Diagnostic.Category = std::move(*Category);
+ Diagnostic.Stage = std::move(*Stage);
+ Diagnostic.Pass = std::move(*Pass);
+ Diagnostic.Function = std::move(*Function);
+ Diagnostic.SourceLocation = std::move(*Source);
+ Diagnostic.TargetConstraint = std::move(*Constraint);
+ Diagnostic.Rule = std::move(*Rule);
+ Diagnostic.Message = std::move(*Message);
+ auto OptionalValue = [&](std::string_view Name, auto &Destination) -> Expected<void> {
+ const yaml::Node *Value = mappingValue(**Mapping, Name);
+ if (Value == nullptr) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest diagnostic is missing key '" + std::string(Name) + "'");
+ }
+ if (Value->kind() == yaml::NodeKind::Null)
+ return {};
+ using ValueT = typename std::remove_reference_t<decltype(Destination)>::value_type;
+ auto ParsedValue = unsignedValue<ValueT>(Value, Name);
+ if (!ParsedValue)
+ return std::unexpected(ParsedValue.error());
+ Destination = *ParsedValue;
+ return {};
+ };
+ auto Block = OptionalValue("block", Diagnostic.Block);
+ auto Instruction = OptionalValue("instruction", Diagnostic.Instruction);
+ auto Operand = OptionalValue("operand", Diagnostic.Operand);
+ if (!Block)
+ return std::unexpected(Block.error());
+ if (!Instruction)
+ return std::unexpected(Instruction.error());
+ if (!Operand)
+ return std::unexpected(Operand.error());
+ Result.push_back(std::move(Diagnostic));
+ }
+ return Result;
+ }
+
+ [[nodiscard]] Expected<ManifestKind> parseManifestKind(std::string_view Value) const {
+ if (Value == "cache")
+ return ManifestKind::Cache;
+ if (Value == "reproducer")
+ return ManifestKind::Reproducer;
+ return makeUnexpected(ErrorCode::ManifestError, "unknown manifest kind");
+ }
+
+ [[nodiscard]] Expected<ArtifactKind> parseArtifactKind(std::string_view Value) const {
+ if (Value == "prelegalizer-mir")
+ return ArtifactKind::PreLegalizerMIR;
+ if (Value == "legalized-mir")
+ return ArtifactKind::LegalizedMIR;
+ if (Value == "regbank-mir")
+ return ArtifactKind::RegisterBankMIR;
+ if (Value == "selected-mir")
+ return ArtifactKind::SelectedMIR;
+ if (Value == "post-ssa-mir")
+ return ArtifactKind::PostSSAMIR;
+ if (Value == "post-ra-mir")
+ return ArtifactKind::PostRegisterAllocationMIR;
+ if (Value == "final-mir")
+ return ArtifactKind::FinalMIR;
+ if (Value == "assembly")
+ return ArtifactKind::Assembly;
+ if (Value == "object")
+ return ArtifactKind::RelocatableObject;
+ return makeUnexpected(ErrorCode::ManifestError, "unknown artifact kind");
+ }
+
+ yaml::ParseResult Parsed;
+ const yaml::Stream *Stream = nullptr;
+};
+
+} // namespace
+
+std::string_view spelling(ManifestKind Value) noexcept {
+ switch (Value) {
+ case ManifestKind::Cache:
+ return "cache";
+ case ManifestKind::Reproducer:
+ return "reproducer";
+ }
+ return "invalid";
+}
+
+std::string_view spelling(ArtifactKind Value) noexcept {
+ switch (Value) {
+ case ArtifactKind::PreLegalizerMIR:
+ return "prelegalizer-mir";
+ case ArtifactKind::LegalizedMIR:
+ return "legalized-mir";
+ case ArtifactKind::RegisterBankMIR:
+ return "regbank-mir";
+ case ArtifactKind::SelectedMIR:
+ return "selected-mir";
+ case ArtifactKind::PostSSAMIR:
+ return "post-ssa-mir";
+ case ArtifactKind::PostRegisterAllocationMIR:
+ return "post-ra-mir";
+ case ArtifactKind::FinalMIR:
+ return "final-mir";
+ case ArtifactKind::Assembly:
+ return "assembly";
+ case ArtifactKind::RelocatableObject:
+ return "object";
+ }
+ return "invalid";
+}
+
+Expected<void> BackendManifest::verify() const {
+ if (SchemaVersion != BackendManifestSchemaVersion) {
+ return makeUnexpected(ErrorCode::ManifestError, "manifest schema version is unsupported");
+ }
+ BackendInvocation Canonical = Invocation;
+ auto InvocationVerification = Canonical.canonicalizeAndVerify();
+ if (!InvocationVerification)
+ return std::unexpected(InvocationVerification.error());
+ if (Canonical != Invocation) {
+ return makeUnexpected(ErrorCode::ManifestError, "manifest invocation is not in canonical form");
+ }
+ if (!isDigest(InputMIRHash) || !isDigest(TargetFingerprint) || !isDigest(CacheKey) ||
+ (ArtifactHash && !isDigest(*ArtifactHash))) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest hashes must be lowercase SHA-256 digests");
+ }
+ for (const StructuredDiagnostic &Diagnostic : Diagnostics) {
+ if (Diagnostic.Category.empty() || Diagnostic.Message.empty()) {
+ return makeUnexpected(ErrorCode::ManifestError,
+ "manifest diagnostic category and message cannot be empty");
+ }
+ }
+ return {};
+}
+
+Expected<std::string> printBackendManifest(const BackendManifest &Manifest) {
+ auto Verification = Manifest.verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ std::string Output;
+ Output.append("---\n");
+ appendInteger(Output, "", "schema", Manifest.SchemaVersion);
+ appendScalar(Output, "", "kind", spelling(Manifest.Kind));
+ appendScalar(Output, "", "artifact", spelling(Manifest.Artifact));
+ appendScalar(Output, "", "input_mir_hash", Manifest.InputMIRHash);
+ appendScalar(Output, "", "target_fingerprint", Manifest.TargetFingerprint);
+ appendScalar(Output, "", "cache_key", Manifest.CacheKey);
+ if (Manifest.ArtifactHash) {
+ appendScalar(Output, "", "artifact_hash", *Manifest.ArtifactHash);
+ } else {
+ Output.append("artifact_hash: null\n");
+ }
+ Output.append("invocation:\n");
+ appendInvocation(Output, Manifest.Invocation);
+ if (Manifest.Diagnostics.empty()) {
+ Output.append("diagnostics: []\n");
+ return Output;
+ }
+ Output.append("diagnostics:\n");
+ for (const StructuredDiagnostic &Diagnostic : Manifest.Diagnostics) {
+ Output.append(" - category: ");
+ Output.append(quoted(Diagnostic.Category));
+ Output.push_back('\n');
+ appendScalar(Output, " ", "stage", Diagnostic.Stage);
+ appendScalar(Output, " ", "pass", Diagnostic.Pass);
+ appendScalar(Output, " ", "function", Diagnostic.Function);
+ appendOptionalInteger(Output, " ", "block", Diagnostic.Block);
+ appendOptionalInteger(Output, " ", "instruction", Diagnostic.Instruction);
+ if (Diagnostic.Operand) {
+ appendInteger(Output, " ", "operand", *Diagnostic.Operand);
+ } else {
+ Output.append(" operand: null\n");
+ }
+ appendScalar(Output, " ", "source_location", Diagnostic.SourceLocation);
+ appendScalar(Output, " ", "target_constraint", Diagnostic.TargetConstraint);
+ appendScalar(Output, " ", "rule", Diagnostic.Rule);
+ appendScalar(Output, " ", "message", Diagnostic.Message);
+ }
+ return Output;
+}
+
+Expected<BackendManifest> parseBackendManifest(std::string SourceName, std::string SourceText) {
+ return ManifestParser(std::move(SourceName), std::move(SourceText)).run();
+}
+
+} // namespace rcc::tooling
diff --git a/backend/lib/Tooling/CMakeLists.txt b/backend/lib/Tooling/CMakeLists.txt
new file mode 100644
index 0000000..bf20e37
--- /dev/null
+++ b/backend/lib/Tooling/CMakeLists.txt
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+set(
+ RCCBackendToolingSources
+ BackendInvocation.cpp
+ BackendManifest.cpp
+ BackendDriver.cpp
+ ArtifactStore.cpp
+ MIRFuzzer.cpp
+ MIRReducer.cpp
+ PipelineDriver.cpp
+)
+set(
+ RCCBackendToolingHeaders
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/BackendInvocation.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/BackendManifest.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/BackendDriver.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/ArtifactStore.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/MIRFuzzer.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/MIRReducer.h"
+ "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/PipelineDriver.h"
+)
+
+add_library(RCCBackendTooling ${RCCBackendToolingSources})
+add_library(RCCBackend::Tooling ALIAS RCCBackendTooling)
+
+rcc_backend_configure_target(RCCBackendTooling)
+
+target_include_directories(
+ RCCBackendTooling
+ PUBLIC
+ "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/include>"
+ "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
+)
+target_link_libraries(
+ RCCBackendTooling
+ PUBLIC
+ RCCBackend::MIR
+ RCCBackend::RISCV
+ RCCBackend::Support
+)
+
+set_target_properties(
+ RCCBackendTooling
+ PROPERTIES
+ EXPORT_NAME Tooling
+ OUTPUT_NAME RCCBackendTooling
+)
+
+install(
+ TARGETS RCCBackendTooling
+ EXPORT RCCBackendTargets
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+)
+
+rcc_backend_register_format_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/BackendInvocation.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/BackendManifest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/BackendDriver.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/ArtifactStore.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRFuzzer.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRReducer.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/PipelineDriver.cpp"
+ ${RCCBackendToolingHeaders}
+)
+rcc_backend_register_tidy_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/BackendInvocation.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/BackendManifest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/BackendDriver.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/ArtifactStore.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRFuzzer.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRReducer.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/PipelineDriver.cpp"
+)
diff --git a/backend/lib/Tooling/MIRFuzzer.cpp b/backend/lib/Tooling/MIRFuzzer.cpp
new file mode 100644
index 0000000..e8a1a7a
--- /dev/null
+++ b/backend/lib/Tooling/MIRFuzzer.cpp
@@ -0,0 +1,613 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/MIRFuzzer.h"
+
+#include "rcc/CodeGen/GenericOpcodes.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/LowLevelType.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineOperand.h"
+#include "rcc/CodeGen/MachineRegisterInfo.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Target/RISCV/RISCVPipeline.h"
+#include "rcc/Tooling/ArtifactStore.h"
+#include "rcc/Tooling/BackendDriver.h"
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace rcc::tooling {
+namespace {
+
+class DeterministicRandom final {
+public:
+ explicit DeterministicRandom(std::uint64_t Seed) noexcept
+ : State(Seed == 0 ? 0x243f6a8885a308d3ULL : Seed) {}
+
+ [[nodiscard]] std::uint64_t next() noexcept {
+ State ^= State >> 12U;
+ State ^= State << 25U;
+ State ^= State >> 27U;
+ return State * 0x2545f4914f6cdd1dULL;
+ }
+
+ [[nodiscard]] std::size_t index(std::size_t Bound) noexcept {
+ return Bound == 0 ? 0 : static_cast<std::size_t>(next() % Bound);
+ }
+
+private:
+ std::uint64_t State;
+};
+
+[[nodiscard]] std::vector<MIRFuzzLayer> defaultLayers() {
+ return {
+ MIRFuzzLayer::Parser, MIRFuzzLayer::BuilderStructural, MIRFuzzLayer::PropertyStage,
+ MIRFuzzLayer::Mutation, MIRFuzzLayer::Pipeline, MIRFuzzLayer::RestrictedSemantics,
+ };
+}
+
+[[nodiscard]] Error fuzzFailure(MIRFuzzLayer Layer, std::size_t Iteration, const Error &Failure) {
+ return Error(ErrorCode::FuzzingFailure,
+ std::string(spelling(Layer)) + " fuzz case " + std::to_string(Iteration) +
+ " violated an invariant: " + std::string(Failure.message()));
+}
+
+[[nodiscard]] std::string byteMutation(std::string_view Input, DeterministicRandom &Random,
+ std::size_t MaximumSize) {
+ std::string Result(Input);
+ switch (Random.index(5)) {
+ case 0:
+ if (!Result.empty())
+ Result[Random.index(Result.size())] = static_cast<char>(Random.next() & 0xffU);
+ break;
+ case 1:
+ if (!Result.empty())
+ Result.erase(Random.index(Result.size()), 1);
+ break;
+ case 2:
+ if (Result.size() < MaximumSize)
+ Result.insert(Result.begin() + static_cast<std::ptrdiff_t>(Random.index(Result.size() + 1U)),
+ static_cast<char>(Random.next() & 0xffU));
+ break;
+ case 3:
+ if (!Result.empty())
+ Result.resize(Random.index(Result.size()));
+ break;
+ case 4:
+ if (!Result.empty() && Result.size() < MaximumSize) {
+ const std::size_t Begin = Random.index(Result.size());
+ const std::size_t Length = std::min(Random.index(32U) + 1U, Result.size() - Begin);
+ const std::string Fragment = Result.substr(Begin, Length);
+ const std::size_t Available = MaximumSize - Result.size();
+ Result.append(Fragment.substr(0, Available));
+ }
+ break;
+ default:
+ break;
+ }
+ return Result;
+}
+
+[[nodiscard]] std::string structuralTextMutation(std::string_view Input,
+ DeterministicRandom &Random) {
+ std::string Result(Input);
+ std::vector<std::size_t> LineStarts{0};
+ for (std::size_t Position = 0; Position < Result.size(); ++Position) {
+ if (Result[Position] == '\n' && Position + 1U < Result.size())
+ LineStarts.push_back(Position + 1U);
+ }
+ if (LineStarts.empty())
+ return Result;
+ const std::size_t LineIndex = Random.index(LineStarts.size());
+ const std::size_t Begin = LineStarts[LineIndex];
+ const std::size_t End = Result.find('\n', Begin);
+ const std::size_t LineEnd = End == std::string::npos ? Result.size() : End + 1U;
+ switch (Random.index(4)) {
+ case 0:
+ Result.erase(Begin, LineEnd - Begin);
+ break;
+ case 1:
+ Result.insert(LineEnd, Result.substr(Begin, LineEnd - Begin));
+ break;
+ case 2: {
+ const std::size_t Digit = Result.find_first_of("0123456789", Begin);
+ if (Digit < LineEnd)
+ Result[Digit] = static_cast<char>('0' + Random.index(10));
+ break;
+ }
+ case 3: {
+ const std::size_t Percent = Result.find('%', Begin);
+ if (Percent < LineEnd && Percent + 1U < Result.size())
+ Result[Percent + 1U] = static_cast<char>('0' + Random.index(10));
+ break;
+ }
+ default:
+ break;
+ }
+ return Result;
+}
+
+[[nodiscard]] Expected<void> verifyCanonicalRoundTrip(std::string Text,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation) {
+ auto First = parseAndCanonicalizeMIR("fuzz-round-trip.mir", std::move(Text), Target, Invocation);
+ if (!First)
+ return std::unexpected(First.error());
+ auto Second = parseAndCanonicalizeMIR("fuzz-round-trip-2.mir", First->Text, Target, Invocation);
+ if (!Second)
+ return std::unexpected(Second.error());
+ if (First->Text != Second->Text) {
+ return makeUnexpected(ErrorCode::FuzzingFailure,
+ "successful MIR round-trip is not structurally deterministic");
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<bool> parserCase(std::string_view Canonical, DeterministicRandom &Random,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation,
+ std::size_t MaximumSize) {
+ std::string Mutated = byteMutation(Canonical, Random, MaximumSize);
+ auto Parsed = parseAndCanonicalizeMIR("parser-fuzz.mir", Mutated, Target, Invocation);
+ if (!Parsed)
+ return false;
+ auto RoundTrip = verifyCanonicalRoundTrip(std::move(Parsed->Text), Target, Invocation);
+ if (!RoundTrip)
+ return std::unexpected(RoundTrip.error());
+ return true;
+}
+
+[[nodiscard]] Expected<bool> builderCase(std::string_view Canonical,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation) {
+ auto Parsed =
+ parseAndCanonicalizeMIR("builder-fuzz.mir", std::string(Canonical), Target, Invocation);
+ if (!Parsed)
+ return std::unexpected(Parsed.error());
+ MachineFunction *Function = Parsed->Document.unit().functionAt(0);
+ if (Function == nullptr || stageForFunction(*Function) != BackendStage::PreLegalizerMIR)
+ return false;
+ MachineBasicBlock *Block = Function->firstBlock();
+ if (Block == nullptr)
+ return false;
+ auto Type = LLT::makeInteger(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ auto Register = Function->registerInfo().createVirtualRegister(*Type);
+ if (!Register)
+ return std::unexpected(Register.error());
+ MachineIRBuilder Builder(*Function, Target);
+ Expected<void> InsertPoint = Block->firstInstruction() == nullptr
+ ? Builder.setInsertPoint(*Block)
+ : Builder.setInsertPointBefore(*Block->firstInstruction());
+ if (!InsertPoint)
+ return std::unexpected(InsertPoint.error());
+ auto Pending = Builder.buildInstr(opcode(GenericOpcode::G_IMPLICIT_DEF));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ auto Added = Pending->addDef(*Register);
+ if (!Added)
+ return std::unexpected(Added.error());
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ auto Verified = MachineVerifier::verify(*Function, Target);
+ if (!Verified)
+ return std::unexpected(Verified.error());
+
+ mir::MIRDocumentPrinterOptions PrintOptions;
+ PrintOptions.Mode = mir::MIPrintMode::Complete;
+ PrintOptions.EmitEmbeddedIR = true;
+ PrintOptions.DataLayout = Invocation.DataLayout;
+ PrintOptions.DefaultPointerSizeInBits = 64;
+ auto Printed = mir::printMIRDocument(Parsed->Document, Target, std::move(PrintOptions));
+ if (!Printed)
+ return std::unexpected(Printed.error());
+ auto RoundTrip = verifyCanonicalRoundTrip(std::move(*Printed), Target, Invocation);
+ if (!RoundTrip)
+ return std::unexpected(RoundTrip.error());
+ return true;
+}
+
+[[nodiscard]] BackendOutputKind outputForStage(BackendStage Stage) noexcept {
+ return static_cast<BackendOutputKind>(static_cast<std::uint8_t>(Stage));
+}
+
+[[nodiscard]] Expected<bool> propertyCase(std::string_view Canonical, DeterministicRandom &Random,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation) {
+ auto Parsed =
+ parseAndCanonicalizeMIR("property-fuzz.mir", std::string(Canonical), Target, Invocation);
+ if (!Parsed)
+ return std::unexpected(Parsed.error());
+ const auto Stage = static_cast<BackendStage>(Random.index(7U));
+ const MachineFunction *Function = Parsed->Document.unit().functionAt(0);
+ if (Function == nullptr ||
+ static_cast<std::uint8_t>(Stage) < static_cast<std::uint8_t>(stageForFunction(*Function))) {
+ return false;
+ }
+ auto Run = runBackend(std::move(*Parsed), Target, Invocation, outputForStage(Stage));
+ if (!Run)
+ return std::unexpected(Run.error());
+ auto RoundTrip = verifyCanonicalRoundTrip(std::string(Run->text()), Target, Invocation);
+ if (!RoundTrip)
+ return std::unexpected(RoundTrip.error());
+ return true;
+}
+
+[[nodiscard]] Expected<bool> mutationCase(std::string_view Canonical, DeterministicRandom &Random,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation) {
+ std::string Mutated = structuralTextMutation(Canonical, Random);
+ auto Parsed =
+ parseAndCanonicalizeMIR("mutation-fuzz.mir", std::move(Mutated), Target, Invocation);
+ if (!Parsed)
+ return false;
+ auto RoundTrip = verifyCanonicalRoundTrip(std::move(Parsed->Text), Target, Invocation);
+ if (!RoundTrip)
+ return std::unexpected(RoundTrip.error());
+ return true;
+}
+
+[[nodiscard]] Expected<bool> pipelineCase(std::string_view Canonical, DeterministicRandom &Random,
+ const TargetDescription &Target,
+ BackendInvocation Invocation) {
+ auto Parsed =
+ parseAndCanonicalizeMIR("pipeline-fuzz.mir", std::string(Canonical), Target, Invocation);
+ if (!Parsed)
+ return std::unexpected(Parsed.error());
+ const MachineFunction *Function = Parsed->Document.unit().functionAt(0);
+ if (Function == nullptr)
+ return std::unexpected(Error(ErrorCode::InternalError, "pipeline fuzz input has no function"));
+ auto Names = riscv::canonicalPipelinePassNames(*Function);
+ if (!Names)
+ return std::unexpected(Names.error());
+ PipelineControls Controls;
+ if (!Names->empty())
+ Controls.StopAfter = (*Names)[Random.index(Names->size())];
+ Controls.VerifyEach = true;
+ Controls.DetectUnreportedChanges = true;
+ Invocation.Determinism.ThreadCount = std::array<std::uint32_t, 3>{1, 2, 4}[Random.index(3U)];
+ const std::string Stop = Controls.StopAfter ? *Controls.StopAfter : "end";
+ auto Run =
+ runBackend(std::move(*Parsed), Target, Invocation, BackendOutputKind::FinalMIR, Controls);
+ if (!Run)
+ return std::unexpected(Run.error());
+ auto RoundTrip = verifyCanonicalRoundTrip(std::string(Run->text()), Target, Invocation);
+ if (!RoundTrip) {
+ return std::unexpected(
+ Error(RoundTrip.error().code(),
+ "pipeline stop-after '" + Stop + "': " + std::string(RoundTrip.error().message())));
+ }
+ return true;
+}
+
+using RegisterValues = std::unordered_map<std::uint64_t, std::uint64_t>;
+
+[[nodiscard]] Expected<Register> registerAt(const MachineInstr &Instruction, std::size_t Index) {
+ const MachineOperand *Operand = Instruction.operandAt(Index);
+ if (Operand == nullptr)
+ return makeUnexpected(ErrorCode::Unsupported, "semantic subset requires register operands");
+ const std::optional<Register> Reg = Operand->registerValue();
+ if (!Reg)
+ return makeUnexpected(ErrorCode::Unsupported, "semantic subset requires register operands");
+ return *Reg;
+}
+
+[[nodiscard]] Expected<std::uint64_t> immediateAt(const MachineInstr &Instruction,
+ std::size_t Index) {
+ const MachineOperand *Operand = Instruction.operandAt(Index);
+ if (Operand == nullptr)
+ return makeUnexpected(ErrorCode::Unsupported, "semantic subset requires an immediate operand");
+ if (const auto *Immediate = Operand->getIf<std::int64_t>())
+ return static_cast<std::uint64_t>(*Immediate);
+ if (const auto *Immediate = Operand->getIf<IntegerImmediateOperand>()) {
+ if (Immediate->Value.bitWidth() > 64 || Immediate->Value.words().empty())
+ return makeUnexpected(ErrorCode::Unsupported,
+ "semantic subset only supports integer immediates up to 64 bits");
+ return Immediate->Value.words().front();
+ }
+ return makeUnexpected(ErrorCode::Unsupported, "semantic subset requires an integer immediate");
+}
+
+[[nodiscard]] std::uint64_t readRegister(Register Reg, const RegisterValues &Values,
+ PhysicalRegister Zero) noexcept {
+ if (Reg.physicalRegister() == Zero)
+ return 0;
+ const auto Position = Values.find(Reg.opaqueValue());
+ return Position == Values.end() ? 0 : Position->second;
+}
+
+[[nodiscard]] Expected<std::uint64_t> interpretStraightLine(const MachineFunction &Function,
+ const TargetDescription &Target) {
+ if (Function.blockCount() != 1 || Function.firstBlock() == nullptr ||
+ !Function.firstBlock()->successors().empty()) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "semantic subset requires one straight-line basic block");
+ }
+ const PhysicalRegisterDesc *ZeroDescription = Target.registers().physicalRegister("x0");
+ const PhysicalRegisterDesc *ReturnDescription = Target.registers().physicalRegister("x10");
+ if (ZeroDescription == nullptr || ReturnDescription == nullptr)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "semantic subset requires RV64 integer registers");
+ const PhysicalRegister Zero = ZeroDescription->Reg;
+ const Register Return = Register::fromPhysical(ReturnDescription->Reg);
+ RegisterValues Values;
+
+ for (const MachineInstr *Instruction = Function.firstBlock()->firstInstruction();
+ Instruction != nullptr; Instruction = Instruction->next()) {
+ const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode());
+ if (Descriptor == nullptr)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "semantic subset encountered an unknown opcode");
+ const std::string_view Name = Descriptor->Name;
+ if (Name == "PseudoRET" || Name == "JALR_RET")
+ return readRegister(Return, Values, Zero);
+
+ if (Name == "G_CONSTANT") {
+ auto Destination = registerAt(*Instruction, 0);
+ auto Immediate = immediateAt(*Instruction, 1);
+ if (!Destination || !Immediate)
+ return makeUnexpected(ErrorCode::Unsupported, "unsupported generic constant form");
+ Values[Destination->opaqueValue()] = *Immediate;
+ continue;
+ }
+ if (Name == "COPY") {
+ auto Destination = registerAt(*Instruction, 0);
+ auto Source = registerAt(*Instruction, 1);
+ if (!Destination || !Source)
+ return makeUnexpected(ErrorCode::Unsupported, "unsupported copy form");
+ Values[Destination->opaqueValue()] = readRegister(*Source, Values, Zero);
+ continue;
+ }
+ if (Name == "LUI") {
+ auto Destination = registerAt(*Instruction, 0);
+ auto Immediate = immediateAt(*Instruction, 1);
+ if (!Destination || !Immediate)
+ return makeUnexpected(ErrorCode::Unsupported, "unsupported LUI form");
+ Values[Destination->opaqueValue()] = *Immediate << 12U;
+ continue;
+ }
+
+ const bool ImmediateForm = Name == "ADDI" || Name == "XORI" || Name == "ORI" ||
+ Name == "ANDI" || Name == "SLLI" || Name == "SRLI" || Name == "SRAI";
+ const bool RegisterForm =
+ Name == "G_ADD" || Name == "G_SUB" || Name == "G_MUL" || Name == "G_AND" ||
+ Name == "G_OR" || Name == "G_XOR" || Name == "G_SHL" || Name == "G_LSHR" ||
+ Name == "G_ASHR" || Name == "ADD" || Name == "SUB" || Name == "MUL" || Name == "AND" ||
+ Name == "OR" || Name == "XOR" || Name == "SLL" || Name == "SRL" || Name == "SRA";
+ if (!ImmediateForm && !RegisterForm && !Descriptor->Flags.has(InstructionFlag::ProducesCode)) {
+ continue;
+ }
+ if (!ImmediateForm && !RegisterForm) {
+ return makeUnexpected(ErrorCode::Unsupported,
+ "semantic subset does not model opcode '" + std::string(Name) + "'");
+ }
+ auto Destination = registerAt(*Instruction, 0);
+ auto LeftRegister = registerAt(*Instruction, 1);
+ if (!Destination || !LeftRegister)
+ return makeUnexpected(ErrorCode::Unsupported, "unsupported binary instruction form");
+ const std::uint64_t Left = readRegister(*LeftRegister, Values, Zero);
+ std::uint64_t Right = 0;
+ if (ImmediateForm) {
+ auto Immediate = immediateAt(*Instruction, 2);
+ if (!Immediate)
+ return makeUnexpected(ErrorCode::Unsupported, "unsupported immediate instruction form");
+ Right = *Immediate;
+ } else {
+ auto RightRegister = registerAt(*Instruction, 2);
+ if (!RightRegister)
+ return makeUnexpected(ErrorCode::Unsupported, "unsupported register instruction form");
+ Right = readRegister(*RightRegister, Values, Zero);
+ }
+ std::uint64_t Value = 0;
+ if (Name == "G_ADD" || Name == "ADD" || Name == "ADDI")
+ Value = Left + Right;
+ else if (Name == "G_SUB" || Name == "SUB")
+ Value = Left - Right;
+ else if (Name == "G_MUL" || Name == "MUL")
+ Value = Left * Right;
+ else if (Name == "G_AND" || Name == "AND" || Name == "ANDI")
+ Value = Left & Right;
+ else if (Name == "G_OR" || Name == "OR" || Name == "ORI")
+ Value = Left | Right;
+ else if (Name == "G_XOR" || Name == "XOR" || Name == "XORI")
+ Value = Left ^ Right;
+ else if (Name == "G_SHL" || Name == "SLL" || Name == "SLLI")
+ Value = Left << (Right & 63U);
+ else if (Name == "G_LSHR" || Name == "SRL" || Name == "SRLI")
+ Value = Left >> (Right & 63U);
+ else
+ Value = static_cast<std::uint64_t>(std::bit_cast<std::int64_t>(Left) >> (Right & 63U));
+ Values[Destination->opaqueValue()] = Value;
+ }
+ return makeUnexpected(ErrorCode::Unsupported, "semantic subset did not encounter a return");
+}
+
+[[nodiscard]] Expected<bool> semanticCase(std::string_view Canonical,
+ const TargetDescription &Target,
+ const BackendInvocation &Invocation) {
+ auto Original =
+ parseAndCanonicalizeMIR("semantic-input.mir", std::string(Canonical), Target, Invocation);
+ if (!Original)
+ return std::unexpected(Original.error());
+ const MachineFunction *InputFunction = Original->Document.unit().functionAt(0);
+ if (InputFunction == nullptr)
+ return std::unexpected(Error(ErrorCode::InternalError, "semantic fuzz input has no function"));
+ auto ExpectedValue = interpretStraightLine(*InputFunction, Target);
+ if (!ExpectedValue && ExpectedValue.error().code() == ErrorCode::Unsupported)
+ return false;
+ if (!ExpectedValue)
+ return std::unexpected(ExpectedValue.error());
+
+ auto CompiledInput =
+ parseAndCanonicalizeMIR("semantic-pipeline.mir", std::string(Canonical), Target, Invocation);
+ if (!CompiledInput)
+ return std::unexpected(CompiledInput.error());
+ auto Compiled =
+ runBackend(std::move(*CompiledInput), Target, Invocation, BackendOutputKind::FinalMIR);
+ if (!Compiled)
+ return std::unexpected(Compiled.error());
+ auto Final = parseAndCanonicalizeMIR("semantic-final.mir", std::string(Compiled->text()), Target,
+ Invocation);
+ if (!Final)
+ return std::unexpected(Final.error());
+ const MachineFunction *FinalFunction = Final->Document.unit().functionAt(0);
+ if (FinalFunction == nullptr)
+ return std::unexpected(Error(ErrorCode::InternalError, "semantic final MIR has no function"));
+ auto ActualValue = interpretStraightLine(*FinalFunction, Target);
+ if (!ActualValue && ActualValue.error().code() == ErrorCode::Unsupported)
+ return false;
+ if (!ActualValue)
+ return std::unexpected(ActualValue.error());
+ if (*ExpectedValue != *ActualValue) {
+ return makeUnexpected(ErrorCode::FuzzingFailure,
+ "restricted semantic comparison changed the integer return value from " +
+ std::to_string(*ExpectedValue) + " to " +
+ std::to_string(*ActualValue));
+ }
+ return true;
+}
+
+} // namespace
+
+std::size_t MIRFuzzResult::executed() const noexcept {
+ std::size_t Result = 0;
+ for (const MIRFuzzLayerResult &Layer : Layers)
+ Result += Layer.Executed;
+ return Result;
+}
+
+std::size_t MIRFuzzResult::accepted() const noexcept {
+ std::size_t Result = 0;
+ for (const MIRFuzzLayerResult &Layer : Layers)
+ Result += Layer.Accepted;
+ return Result;
+}
+
+std::size_t MIRFuzzResult::rejected() const noexcept {
+ std::size_t Result = 0;
+ for (const MIRFuzzLayerResult &Layer : Layers)
+ Result += Layer.Rejected;
+ return Result;
+}
+
+Expected<MIRFuzzResult> fuzzMIR(std::string SourceName, std::string SourceText,
+ BackendInvocation Invocation, const TargetDescription &Target,
+ MIRFuzzOptions Options) {
+ if (Options.Iterations == 0 || Options.MaximumInputSize == 0) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "MIR fuzzing requires nonzero iteration and input-size limits");
+ }
+ if (std::ranges::any_of(Options.Layers,
+ [](MIRFuzzLayer Layer) { return spelling(Layer) == "invalid"; })) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "MIR fuzz layer is not recognized");
+ }
+ auto InvocationVerification = Invocation.canonicalizeAndVerify();
+ if (!InvocationVerification)
+ return std::unexpected(InvocationVerification.error());
+ auto Input =
+ parseAndCanonicalizeMIR(std::move(SourceName), std::move(SourceText), Target, Invocation);
+ if (!Input)
+ return std::unexpected(Input.error());
+ if (Input->Text.size() > Options.MaximumInputSize)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MIR fuzz seed exceeds the input-size limit");
+ if (Options.Layers.empty())
+ Options.Layers = defaultLayers();
+ std::ranges::sort(Options.Layers);
+ if (std::ranges::adjacent_find(Options.Layers) != Options.Layers.end()) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "MIR fuzz layers must be unique");
+ }
+
+ MIRFuzzResult Result{.Seed = Options.Seed, .Layers = {}};
+ DeterministicRandom Random(Options.Seed);
+ for (MIRFuzzLayer Layer : Options.Layers) {
+ MIRFuzzLayerResult LayerResult{.Layer = Layer};
+ const std::size_t Iterations =
+ Layer == MIRFuzzLayer::RestrictedSemantics ? 1U : Options.Iterations;
+ for (std::size_t Iteration = 0; Iteration < Iterations; ++Iteration) {
+ ++LayerResult.Executed;
+ Expected<bool> Case = false;
+ switch (Layer) {
+ case MIRFuzzLayer::Parser:
+ Case = parserCase(Input->Text, Random, Target, Invocation, Options.MaximumInputSize);
+ break;
+ case MIRFuzzLayer::BuilderStructural:
+ Case = builderCase(Input->Text, Target, Invocation);
+ break;
+ case MIRFuzzLayer::PropertyStage:
+ Case = propertyCase(Input->Text, Random, Target, Invocation);
+ break;
+ case MIRFuzzLayer::Mutation:
+ Case = mutationCase(Input->Text, Random, Target, Invocation);
+ break;
+ case MIRFuzzLayer::Pipeline:
+ Case = pipelineCase(Input->Text, Random, Target, Invocation);
+ break;
+ case MIRFuzzLayer::RestrictedSemantics:
+ Case = semanticCase(Input->Text, Target, Invocation);
+ break;
+ }
+ if (!Case)
+ return std::unexpected(fuzzFailure(Layer, Iteration, Case.error()));
+ if (*Case)
+ ++LayerResult.Accepted;
+ else
+ ++LayerResult.Rejected;
+ }
+ Result.Layers.push_back(LayerResult);
+ }
+ return Result;
+}
+
+std::string_view spelling(MIRFuzzLayer Layer) noexcept {
+ switch (Layer) {
+ case MIRFuzzLayer::Parser:
+ return "parser";
+ case MIRFuzzLayer::BuilderStructural:
+ return "builder";
+ case MIRFuzzLayer::PropertyStage:
+ return "property-stage";
+ case MIRFuzzLayer::Mutation:
+ return "mutation";
+ case MIRFuzzLayer::Pipeline:
+ return "pipeline";
+ case MIRFuzzLayer::RestrictedSemantics:
+ return "semantics";
+ }
+ return "invalid";
+}
+
+Expected<MIRFuzzLayer> parseMIRFuzzLayer(std::string_view Value) {
+ if (Value == "parser")
+ return MIRFuzzLayer::Parser;
+ if (Value == "builder")
+ return MIRFuzzLayer::BuilderStructural;
+ if (Value == "property-stage")
+ return MIRFuzzLayer::PropertyStage;
+ if (Value == "mutation")
+ return MIRFuzzLayer::Mutation;
+ if (Value == "pipeline")
+ return MIRFuzzLayer::Pipeline;
+ if (Value == "semantics")
+ return MIRFuzzLayer::RestrictedSemantics;
+ return makeUnexpected(ErrorCode::InvalidInvocation, "unknown MIR fuzz layer");
+}
+
+} // namespace rcc::tooling
diff --git a/backend/lib/Tooling/MIRReducer.cpp b/backend/lib/Tooling/MIRReducer.cpp
new file mode 100644
index 0000000..a247584
--- /dev/null
+++ b/backend/lib/Tooling/MIRReducer.cpp
@@ -0,0 +1,534 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/MIRReducer.h"
+
+#include "rcc/CodeGen/TargetDescription.h"
+#include "rcc/Tooling/ArtifactStore.h"
+
+#include <algorithm>
+#include <cctype>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <ranges>
+#include <set>
+#include <span>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace rcc::tooling {
+namespace {
+
+struct Line final {
+ std::size_t Begin = 0;
+ std::size_t End = 0;
+ std::size_t ContentEnd = 0;
+ std::size_t Indent = 0;
+};
+
+struct Candidate final {
+ std::string MIR;
+ BackendInvocation Invocation;
+ std::string Description;
+};
+
+[[nodiscard]] std::vector<ReductionGranularity> defaultGranularities() {
+ return {
+ ReductionGranularity::MachineFunction, ReductionGranularity::BasicBlock,
+ ReductionGranularity::CFGEdge, ReductionGranularity::RegisterSubgraph,
+ ReductionGranularity::Instruction, ReductionGranularity::Operand,
+ ReductionGranularity::FrameObject, ReductionGranularity::ConstantPool,
+ ReductionGranularity::JumpTable, ReductionGranularity::TargetFeature,
+ ReductionGranularity::PassPipeline,
+ };
+}
+
+[[nodiscard]] std::vector<Line> lines(std::string_view Text) {
+ std::vector<Line> Result;
+ std::size_t Begin = 0;
+ while (Begin < Text.size()) {
+ const std::size_t Newline = Text.find('\n', Begin);
+ const std::size_t End = Newline == std::string_view::npos ? Text.size() : Newline + 1U;
+ std::size_t ContentEnd = Newline == std::string_view::npos ? Text.size() : Newline;
+ if (ContentEnd > Begin && Text[ContentEnd - 1U] == '\r')
+ --ContentEnd;
+ std::size_t Indent = 0;
+ while (Begin + Indent < ContentEnd && Text[Begin + Indent] == ' ')
+ ++Indent;
+ Result.push_back({Begin, End, ContentEnd, Indent});
+ Begin = End;
+ }
+ if (Text.empty())
+ Result.push_back({});
+ return Result;
+}
+
+[[nodiscard]] std::string_view lineText(std::string_view Text, const Line &Value) {
+ return Text.substr(Value.Begin, Value.ContentEnd - Value.Begin);
+}
+
+[[nodiscard]] std::string_view trimmedLine(std::string_view Text, const Line &Value) {
+ return Text.substr(Value.Begin + Value.Indent, Value.ContentEnd - Value.Begin - Value.Indent);
+}
+
+[[nodiscard]] std::string withoutRange(std::string_view Text, std::size_t Begin, std::size_t End) {
+ std::string Result;
+ Result.reserve(Text.size() - (End - Begin));
+ Result.append(Text.substr(0, Begin));
+ Result.append(Text.substr(End));
+ return Result;
+}
+
+void addCandidate(std::vector<Candidate> &Result, std::set<std::string> &Seen,
+ std::string CandidateMIR, const BackendInvocation &Invocation,
+ std::string Description, std::string_view OriginalMIR) {
+ if (CandidateMIR == OriginalMIR || !Seen.insert(CandidateMIR).second)
+ return;
+ Result.push_back({std::move(CandidateMIR), Invocation, std::move(Description)});
+}
+
+[[nodiscard]] bool isBodyStart(std::string_view Text, const Line &Value) {
+ return Value.Indent == 0 && trimmedLine(Text, Value) == "body: |";
+}
+
+[[nodiscard]] bool isBlockLabel(std::string_view Text, const Line &Value) {
+ const std::string_view Trimmed = trimmedLine(Text, Value);
+ return Value.Indent == 2 && Trimmed.starts_with("bb.") && Trimmed.ends_with(':');
+}
+
+[[nodiscard]] bool isInstructionLine(std::string_view Text, const Line &Value, bool InBody) {
+ if (!InBody || Value.Indent < 4)
+ return false;
+ const std::string_view Trimmed = trimmedLine(Text, Value);
+ return !Trimmed.empty() && !Trimmed.starts_with("liveins:") &&
+ !Trimmed.starts_with("successors:") && !Trimmed.starts_with("align ") &&
+ !Trimmed.starts_with(';');
+}
+
+[[nodiscard]] std::vector<Candidate> functionCandidates(std::string_view MIR,
+ const BackendInvocation &Invocation) {
+ const std::vector<Line> Lines = lines(MIR);
+ std::vector<std::size_t> Starts;
+ for (std::size_t Index = 0; Index < Lines.size(); ++Index) {
+ if (lineText(MIR, Lines[Index]) == "---")
+ Starts.push_back(Index);
+ }
+ std::vector<Candidate> Result;
+ std::set<std::string> Seen;
+ if (Starts.size() <= 1)
+ return Result;
+ for (std::size_t Position = 0; Position < Starts.size(); ++Position) {
+ const std::size_t Begin = Lines[Starts[Position]].Begin;
+ const std::size_t End =
+ Position + 1U < Starts.size() ? Lines[Starts[Position + 1U]].Begin : MIR.size();
+ addCandidate(Result, Seen, withoutRange(MIR, Begin, End), Invocation,
+ "remove YAML document " + std::to_string(Position), MIR);
+ }
+ return Result;
+}
+
+[[nodiscard]] std::vector<Candidate> blockCandidates(std::string_view MIR,
+ const BackendInvocation &Invocation) {
+ const std::vector<Line> Lines = lines(MIR);
+ std::vector<Candidate> Result;
+ std::set<std::string> Seen;
+ bool InBody = false;
+ for (std::size_t Index = 0; Index < Lines.size(); ++Index) {
+ if (isBodyStart(MIR, Lines[Index])) {
+ InBody = true;
+ continue;
+ }
+ if (InBody && Lines[Index].Indent == 0) {
+ InBody = isBodyStart(MIR, Lines[Index]);
+ continue;
+ }
+ if (!InBody || !isBlockLabel(MIR, Lines[Index]))
+ continue;
+ std::size_t EndIndex = Index + 1U;
+ while (EndIndex < Lines.size() && Lines[EndIndex].Indent != 0 &&
+ !isBlockLabel(MIR, Lines[EndIndex])) {
+ ++EndIndex;
+ }
+ const std::size_t End = EndIndex < Lines.size() ? Lines[EndIndex].Begin : MIR.size();
+ addCandidate(Result, Seen, withoutRange(MIR, Lines[Index].Begin, End), Invocation,
+ "remove machine basic block", MIR);
+ }
+ return Result;
+}
+
+[[nodiscard]] std::vector<Candidate> instructionCandidates(std::string_view MIR,
+ const BackendInvocation &Invocation) {
+ const std::vector<Line> Lines = lines(MIR);
+ std::vector<Candidate> Result;
+ std::set<std::string> Seen;
+ bool InBody = false;
+ for (const Line &Value : Lines) {
+ if (isBodyStart(MIR, Value)) {
+ InBody = true;
+ continue;
+ }
+ if (InBody && Value.Indent == 0) {
+ InBody = false;
+ continue;
+ }
+ if (!isInstructionLine(MIR, Value, InBody))
+ continue;
+ addCandidate(Result, Seen, withoutRange(MIR, Value.Begin, Value.End), Invocation,
+ "remove machine instruction", MIR);
+ }
+ return Result;
+}
+
+[[nodiscard]] std::vector<Candidate> cfgEdgeCandidates(std::string_view MIR,
+ const BackendInvocation &Invocation) {
+ const std::vector<Line> Lines = lines(MIR);
+ std::vector<Candidate> Result;
+ std::set<std::string> Seen;
+ bool InBody = false;
+ for (const Line &Value : Lines) {
+ if (isBodyStart(MIR, Value)) {
+ InBody = true;
+ continue;
+ }
+ if (InBody && Value.Indent == 0) {
+ InBody = false;
+ continue;
+ }
+ const std::string_view Trimmed = trimmedLine(MIR, Value);
+ if (!InBody || !Trimmed.starts_with("successors:"))
+ continue;
+ const std::size_t LastComma = Trimmed.rfind(',');
+ if (LastComma == std::string_view::npos) {
+ addCandidate(Result, Seen, withoutRange(MIR, Value.Begin, Value.End), Invocation,
+ "remove CFG successor list", MIR);
+ continue;
+ }
+ const std::size_t EraseBegin = Value.Begin + Value.Indent + LastComma;
+ addCandidate(Result, Seen, withoutRange(MIR, EraseBegin, Value.ContentEnd), Invocation,
+ "remove CFG successor", MIR);
+ }
+ return Result;
+}
+
+[[nodiscard]] std::vector<Candidate> operandCandidates(std::string_view MIR,
+ const BackendInvocation &Invocation) {
+ const std::vector<Line> Lines = lines(MIR);
+ std::vector<Candidate> Result;
+ std::set<std::string> Seen;
+ bool InBody = false;
+ for (const Line &Value : Lines) {
+ if (isBodyStart(MIR, Value)) {
+ InBody = true;
+ continue;
+ }
+ if (InBody && Value.Indent == 0) {
+ InBody = false;
+ continue;
+ }
+ if (!isInstructionLine(MIR, Value, InBody))
+ continue;
+ const std::string_view Text = lineText(MIR, Value);
+ const std::size_t LastComma = Text.rfind(',');
+ if (LastComma != std::string_view::npos) {
+ addCandidate(Result, Seen, withoutRange(MIR, Value.Begin + LastComma, Value.ContentEnd),
+ Invocation, "remove trailing machine operand", MIR);
+ }
+ const std::size_t Implicit = Text.rfind(" implicit ");
+ if (Implicit != std::string_view::npos) {
+ addCandidate(Result, Seen, withoutRange(MIR, Value.Begin + Implicit, Value.ContentEnd),
+ Invocation, "remove implicit machine operand", MIR);
+ }
+ }
+ return Result;
+}
+
+[[nodiscard]] bool containsRegisterToken(std::string_view LineText, std::string_view Token) {
+ std::size_t Position = 0;
+ while ((Position = LineText.find(Token, Position)) != std::string_view::npos) {
+ const std::size_t End = Position + Token.size();
+ const bool LeftBoundary = Position == 0 || LineText[Position - 1U] != '%';
+ const bool RightBoundary =
+ End == LineText.size() || std::isdigit(static_cast<unsigned char>(LineText[End])) == 0;
+ if (LeftBoundary && RightBoundary)
+ return true;
+ Position = End;
+ }
+ return false;
+}
+
+[[nodiscard]] std::vector<Candidate> registerCandidates(std::string_view MIR,
+ const BackendInvocation &Invocation) {
+ const std::vector<Line> Lines = lines(MIR);
+ std::set<std::string> Registers;
+ bool InBody = false;
+ for (const Line &Value : Lines) {
+ if (isBodyStart(MIR, Value)) {
+ InBody = true;
+ continue;
+ }
+ if (InBody && Value.Indent == 0) {
+ InBody = false;
+ continue;
+ }
+ if (!isInstructionLine(MIR, Value, InBody))
+ continue;
+ const std::string_view Text = lineText(MIR, Value);
+ for (std::size_t Position = 0; Position < Text.size(); ++Position) {
+ if (Text[Position] != '%' || Position + 1U >= Text.size() ||
+ std::isdigit(static_cast<unsigned char>(Text[Position + 1U])) == 0) {
+ continue;
+ }
+ std::size_t End = Position + 2U;
+ while (End < Text.size() && std::isdigit(static_cast<unsigned char>(Text[End])) != 0) {
+ ++End;
+ }
+ Registers.emplace(Text.substr(Position, End - Position));
+ Position = End - 1U;
+ }
+ }
+
+ std::vector<Candidate> Result;
+ std::set<std::string> Seen;
+ for (const std::string &Register : Registers) {
+ std::string CandidateMIR;
+ CandidateMIR.reserve(MIR.size());
+ InBody = false;
+ for (const Line &Value : Lines) {
+ if (isBodyStart(MIR, Value))
+ InBody = true;
+ else if (InBody && Value.Indent == 0)
+ InBody = false;
+ if (isInstructionLine(MIR, Value, InBody) &&
+ containsRegisterToken(lineText(MIR, Value), Register)) {
+ continue;
+ }
+ CandidateMIR.append(MIR.substr(Value.Begin, Value.End - Value.Begin));
+ }
+ addCandidate(Result, Seen, std::move(CandidateMIR), Invocation,
+ "remove register subgraph " + Register, MIR);
+ }
+ return Result;
+}
+
+[[nodiscard]] std::vector<Candidate> sequenceItemCandidates(std::string_view MIR,
+ const BackendInvocation &Invocation,
+ std::string_view Field,
+ std::size_t ItemIndent,
+ std::string_view Description) {
+ const std::vector<Line> Lines = lines(MIR);
+ std::vector<Candidate> Result;
+ std::set<std::string> Seen;
+ bool InField = false;
+ for (std::size_t Index = 0; Index < Lines.size(); ++Index) {
+ const std::string_view Trimmed = trimmedLine(MIR, Lines[Index]);
+ if (Lines[Index].Indent == 0 && Trimmed == std::string(Field) + ":") {
+ InField = true;
+ continue;
+ }
+ if (InField && Lines[Index].Indent == 0) {
+ InField = false;
+ continue;
+ }
+ if (!InField || Lines[Index].Indent != ItemIndent || !Trimmed.starts_with("- "))
+ continue;
+ std::size_t EndIndex = Index + 1U;
+ while (EndIndex < Lines.size() && Lines[EndIndex].Indent > ItemIndent)
+ ++EndIndex;
+ const std::size_t End = EndIndex < Lines.size() ? Lines[EndIndex].Begin : MIR.size();
+ addCandidate(Result, Seen, withoutRange(MIR, Lines[Index].Begin, End), Invocation,
+ std::string(Description), MIR);
+ }
+ return Result;
+}
+
+[[nodiscard]] std::vector<Candidate> invocationCandidates(std::string_view MIR,
+ const BackendInvocation &Invocation,
+ ReductionGranularity Granularity) {
+ std::vector<Candidate> Result;
+ if (Granularity == ReductionGranularity::TargetFeature) {
+ for (std::size_t Index = Invocation.Features.size(); Index > 0; --Index) {
+ BackendInvocation CandidateInvocation = Invocation;
+ const std::string Feature = CandidateInvocation.Features[Index - 1U];
+ CandidateInvocation.Features.erase(CandidateInvocation.Features.begin() +
+ static_cast<std::ptrdiff_t>(Index - 1U));
+ auto Verified = CandidateInvocation.canonicalizeAndVerify();
+ if (Verified) {
+ Result.push_back(
+ {std::string(MIR), std::move(CandidateInvocation), "remove target feature " + Feature});
+ }
+ }
+ } else if (Granularity == ReductionGranularity::PassPipeline &&
+ (Invocation.PassPipeline.size() != 1 ||
+ Invocation.PassPipeline.front() != "default")) {
+ for (std::size_t Index = Invocation.PassPipeline.size(); Index > 0; --Index) {
+ if (Invocation.PassPipeline.size() == 1)
+ break;
+ BackendInvocation CandidateInvocation = Invocation;
+ const std::string Pass = CandidateInvocation.PassPipeline[Index - 1U];
+ CandidateInvocation.PassPipeline.erase(CandidateInvocation.PassPipeline.begin() +
+ static_cast<std::ptrdiff_t>(Index - 1U));
+ Result.push_back(
+ {std::string(MIR), std::move(CandidateInvocation), "remove pipeline pass " + Pass});
+ }
+ }
+ return Result;
+}
+
+[[nodiscard]] std::vector<Candidate> candidatesFor(ReductionGranularity Granularity,
+ std::string_view MIR,
+ const BackendInvocation &Invocation) {
+ switch (Granularity) {
+ case ReductionGranularity::CompilationUnit:
+ case ReductionGranularity::MachineFunction:
+ return functionCandidates(MIR, Invocation);
+ case ReductionGranularity::BasicBlock:
+ return blockCandidates(MIR, Invocation);
+ case ReductionGranularity::CFGEdge:
+ return cfgEdgeCandidates(MIR, Invocation);
+ case ReductionGranularity::Instruction:
+ return instructionCandidates(MIR, Invocation);
+ case ReductionGranularity::Operand:
+ return operandCandidates(MIR, Invocation);
+ case ReductionGranularity::RegisterSubgraph:
+ return registerCandidates(MIR, Invocation);
+ case ReductionGranularity::FrameObject: {
+ std::vector<Candidate> Result =
+ sequenceItemCandidates(MIR, Invocation, "fixedStack", 2, "remove fixed stack object");
+ std::vector<Candidate> Stack =
+ sequenceItemCandidates(MIR, Invocation, "stack", 2, "remove stack object");
+ Result.insert(Result.end(), std::make_move_iterator(Stack.begin()),
+ std::make_move_iterator(Stack.end()));
+ return Result;
+ }
+ case ReductionGranularity::ConstantPool:
+ return sequenceItemCandidates(MIR, Invocation, "constants", 2, "remove constant-pool entry");
+ case ReductionGranularity::JumpTable:
+ return sequenceItemCandidates(MIR, Invocation, "entries", 4, "remove jump-table entry");
+ case ReductionGranularity::TargetFeature:
+ case ReductionGranularity::PassPipeline:
+ return invocationCandidates(MIR, Invocation, Granularity);
+ }
+ return {};
+}
+
+} // namespace
+
+std::string_view spelling(ReductionGranularity Granularity) noexcept {
+ switch (Granularity) {
+ case ReductionGranularity::CompilationUnit:
+ return "compilation-unit";
+ case ReductionGranularity::MachineFunction:
+ return "machine-function";
+ case ReductionGranularity::BasicBlock:
+ return "basic-block";
+ case ReductionGranularity::CFGEdge:
+ return "cfg-edge";
+ case ReductionGranularity::Instruction:
+ return "instruction";
+ case ReductionGranularity::Operand:
+ return "operand";
+ case ReductionGranularity::RegisterSubgraph:
+ return "register-subgraph";
+ case ReductionGranularity::FrameObject:
+ return "frame-object";
+ case ReductionGranularity::ConstantPool:
+ return "constant-pool";
+ case ReductionGranularity::JumpTable:
+ return "jump-table";
+ case ReductionGranularity::TargetFeature:
+ return "target-feature";
+ case ReductionGranularity::PassPipeline:
+ return "pass-pipeline";
+ }
+ return "invalid";
+}
+
+Expected<MIRReductionResult> reduceMIR(std::string SourceName, std::string SourceText,
+ BackendInvocation Invocation,
+ const TargetDescription &Target,
+ const MIRInterestingnessTest &Interesting,
+ MIRReducerOptions Options) {
+ if (Options.Mode == ReductionMode::PreserveFailure && !Interesting)
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "failure-preserving MIR reduction requires an interestingness test");
+ if (Options.MaximumAttempts == 0)
+ return makeUnexpected(ErrorCode::InvalidArgument, "MIR reducer attempt budget must be nonzero");
+ std::set<ReductionGranularity> SeenGranularities;
+ for (ReductionGranularity Granularity : Options.Granularities) {
+ if (spelling(Granularity) == "invalid") {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "MIR reducer granularity is not recognized");
+ }
+ if (!SeenGranularities.insert(Granularity).second) {
+ return makeUnexpected(ErrorCode::InvalidArgument, "MIR reducer granularities must be unique");
+ }
+ }
+ auto InvocationVerification = Invocation.canonicalizeAndVerify();
+ if (!InvocationVerification)
+ return std::unexpected(InvocationVerification.error());
+ auto Initial =
+ parseAndCanonicalizeMIR(std::move(SourceName), std::move(SourceText), Target, Invocation);
+ if (!Initial)
+ return std::unexpected(Initial.error());
+ if (Options.Mode == ReductionMode::PreserveFailure) {
+ auto InitiallyInteresting = Interesting(Initial->Text, Invocation);
+ if (!InitiallyInteresting)
+ return std::unexpected(InitiallyInteresting.error());
+ if (!*InitiallyInteresting) {
+ return makeUnexpected(ErrorCode::ReductionFailed,
+ "initial MIR input does not satisfy the interestingness test");
+ }
+ }
+ if (Options.Granularities.empty())
+ Options.Granularities = defaultGranularities();
+
+ MIRReductionResult Result{
+ .MIR = std::move(Initial->Text),
+ .Invocation = std::move(Invocation),
+ .Steps = {},
+ .Attempts = 0,
+ .Exhausted = false,
+ };
+ for (ReductionGranularity Granularity : Options.Granularities) {
+ bool Changed = true;
+ while (Changed && Result.Attempts < Options.MaximumAttempts) {
+ Changed = false;
+ std::vector<Candidate> Candidates = candidatesFor(Granularity, Result.MIR, Result.Invocation);
+ for (Candidate &CandidateValue : Candidates) {
+ if (Result.Attempts == Options.MaximumAttempts)
+ break;
+ ++Result.Attempts;
+ auto Canonical = parseAndCanonicalizeMIR("reducer-candidate.mir", CandidateValue.MIR,
+ Target, CandidateValue.Invocation);
+ if (!Canonical)
+ continue;
+ if (Canonical->Text == Result.MIR && CandidateValue.Invocation == Result.Invocation)
+ continue;
+ if (Options.Mode == ReductionMode::PreserveFailure) {
+ auto IsInteresting = Interesting(Canonical->Text, CandidateValue.Invocation);
+ if (!IsInteresting)
+ return std::unexpected(IsInteresting.error());
+ if (!*IsInteresting)
+ continue;
+ }
+ const std::size_t PreviousSize = Result.MIR.size();
+ Result.MIR = std::move(Canonical->Text);
+ Result.Invocation = std::move(CandidateValue.Invocation);
+ Result.Steps.push_back({
+ .Granularity = Granularity,
+ .Description = std::move(CandidateValue.Description),
+ .PreviousSize = PreviousSize,
+ .ReducedSize = Result.MIR.size(),
+ });
+ Changed = true;
+ break;
+ }
+ }
+ }
+ Result.Exhausted = Result.Attempts == Options.MaximumAttempts;
+ return Result;
+}
+
+} // namespace rcc::tooling
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
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}")
diff --git a/backend/unittests/CMakeLists.txt b/backend/unittests/CMakeLists.txt
index d1ef6fa..87e42b3 100644
--- a/backend/unittests/CMakeLists.txt
+++ b/backend/unittests/CMakeLists.txt
@@ -5,3 +5,4 @@ add_subdirectory(MC)
add_subdirectory(Target)
add_subdirectory(CodeGen)
add_subdirectory(MIR)
+add_subdirectory(Tooling)
diff --git a/backend/unittests/MIR/MIParserTest.cpp b/backend/unittests/MIR/MIParserTest.cpp
index 552de25..07aef75 100644
--- a/backend/unittests/MIR/MIParserTest.cpp
+++ b/backend/unittests/MIR/MIParserTest.cpp
@@ -406,6 +406,35 @@ void testUnserializableLowLevelType(TestContext &Context) {
}
void expectParseError(TestContext &Context, const rcc::TargetDescription &Target, std::string Body,
+ std::string_view Message);
+
+void testCompatibleRegisterClassAndBankAnnotations(TestContext &Context) {
+ auto Target = createTarget();
+ auto Function = createFunction(Context, 5);
+ Context.expect(Target.has_value(), "register-constraint target must be valid");
+ if (!Target || !Function)
+ return;
+
+ rcc::mir::MIParser Parser(*Function, *Target, "compatible-register-constraints.mir",
+ "bb.0:\n %0:gpr64 = TEST_COPY $g0\n $g1 = TEST_COPY %0:gpr\n");
+ auto Parsed = Parser.parse();
+ const auto Registers = Function->registerInfo().virtualRegisters();
+ Context.expect(Parsed.has_value() && Registers.size() == 1 &&
+ Registers.front().RegClass == rcc::test::TestRegisterClass::GPR64 &&
+ Registers.front().Bank == rcc::test::TestRegisterBank::GPR,
+ "compatible class and bank annotations must coexist on a virtual register");
+ if (!Parsed) {
+ for (const rcc::TextDiagnostic &Diagnostic : Parser.diagnostics())
+ std::cerr << Diagnostic.Category << ": " << Diagnostic.Message << '\n';
+ std::cerr << "error: " << Parsed.error().message() << '\n';
+ }
+
+ expectParseError(Context, *Target,
+ "bb.0:\n %0:gpr64 = TEST_COPY $g0\n $g1 = TEST_COPY %0:fpr\n",
+ "conflicting virtual register bank");
+}
+
+void expectParseError(TestContext &Context, const rcc::TargetDescription &Target, std::string Body,
std::string_view Message) {
auto Function = createFunction(Context, 10);
if (!Function)
@@ -453,6 +482,7 @@ int main() {
testTwoPassBodyParsing(Context);
testAddressSpacePointerWidthResolver(Context);
testUnserializableLowLevelType(Context);
+ testCompatibleRegisterClassAndBankAnnotations(Context);
testDiagnostics(Context);
return Context.result();
}
diff --git a/backend/unittests/Support/CMakeLists.txt b/backend/unittests/Support/CMakeLists.txt
index 50a1e7c..c41c438 100644
--- a/backend/unittests/Support/CMakeLists.txt
+++ b/backend/unittests/Support/CMakeLists.txt
@@ -18,13 +18,21 @@ target_link_libraries(RCCBackendYAMLTests PRIVATE RCCBackend::Support)
add_test(NAME RCCBackend.Support.YAML COMMAND RCCBackendYAMLTests)
+add_executable(RCCBackendSHA256Tests SHA256Test.cpp)
+rcc_backend_configure_target(RCCBackendSHA256Tests)
+target_link_libraries(RCCBackendSHA256Tests PRIVATE RCCBackend::Support)
+
+add_test(NAME RCCBackend.Support.SHA256 COMMAND RCCBackendSHA256Tests)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/ErrorTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/SHA256Test.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TextDiagnosticTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/YAMLTest.cpp"
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/ErrorTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/SHA256Test.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TextDiagnosticTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/YAMLTest.cpp"
)
diff --git a/backend/unittests/Support/SHA256Test.cpp b/backend/unittests/Support/SHA256Test.cpp
new file mode 100644
index 0000000..fe7b386
--- /dev/null
+++ b/backend/unittests/Support/SHA256Test.cpp
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Support/SHA256.h"
+
+#include <cstddef>
+#include <iostream>
+#include <span>
+#include <string>
+#include <string_view>
+
+namespace {
+
+int fail(const char *Message) {
+ std::cerr << Message << '\n';
+ return 1;
+}
+
+} // namespace
+
+int main() {
+ if (rcc::sha256Hex("") != "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") {
+ return fail("SHA-256 must match the empty-input standard vector");
+ }
+ if (rcc::sha256Hex("abc") != "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") {
+ return fail("SHA-256 must match the short standard vector");
+ }
+ constexpr std::string_view MultiBlock =
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
+ if (rcc::sha256Hex(MultiBlock) !=
+ "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1") {
+ return fail("SHA-256 must match the multi-block standard vector");
+ }
+
+ const std::string MillionA(1'000'000, 'a');
+ if (rcc::sha256Hex(MillionA) !=
+ "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0") {
+ return fail("SHA-256 must match the million-byte standard vector");
+ }
+
+ const std::string Binary("a\0b", 3);
+ const auto *Bytes = reinterpret_cast<const std::byte *>(Binary.data());
+ if (rcc::sha256Hex(std::span<const std::byte>(Bytes, Binary.size())) !=
+ "59b271ae1bbcb1d31d41929817f4b16fb439eb4f31520b5ad1d5ce98920a7138") {
+ return fail("SHA-256 must hash embedded null bytes without truncation");
+ }
+ return 0;
+}
diff --git a/backend/unittests/Target/RISCVLateCodeGenTest.cpp b/backend/unittests/Target/RISCVLateCodeGenTest.cpp
index 7ab3a59..b9479b2 100644
--- a/backend/unittests/Target/RISCVLateCodeGenTest.cpp
+++ b/backend/unittests/Target/RISCVLateCodeGenTest.cpp
@@ -1034,7 +1034,8 @@ void testLongJALRelaxation(TestContext &Context, const rcc::TargetDescription &T
Context.expect(!TooFar && TooFar.error().code() == rcc::ErrorCode::BranchRelaxationFailed &&
JAL->parent() == Entry,
"long JAL relaxation must reject an unencodable PC-relative distance atomically");
- auto Relaxed = rcc::riscv::branchInfo().relaxBranch(*JAL, 2 * 1024 * 1024, *Function, Target);
+ auto Relaxed =
+ rcc::riscv::branchInfo().relaxBranch(*JAL, std::int64_t{2} * 1024 * 1024, *Function, Target);
if (!Relaxed)
std::cerr << "long JAL relaxation failure: " << Relaxed.error().message() << '\n';
rcc::MachineInstr *High = Entry->firstInstruction();
diff --git a/backend/unittests/Tooling/ArtifactStoreTest.cpp b/backend/unittests/Tooling/ArtifactStoreTest.cpp
new file mode 100644
index 0000000..3f9febb
--- /dev/null
+++ b/backend/unittests/Tooling/ArtifactStoreTest.cpp
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/ArtifactStore.h"
+
+#include "ToolingTestSupport.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <cstdint>
+#include <filesystem>
+#include <span>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace {
+
+[[nodiscard]] std::vector<std::uint8_t> bytes(std::string_view Text) {
+ const auto *Begin = reinterpret_cast<const std::uint8_t *>(Text.data());
+ return std::vector<std::uint8_t>(Begin, Begin + Text.size());
+}
+
+[[nodiscard]] std::filesystem::path cacheEntry(const std::filesystem::path &Root,
+ std::string_view Key) {
+ return Root / std::string(Key.substr(0, 2)) / std::string(Key);
+}
+
+[[nodiscard]] bool isCacheMiss(const rcc::Expected<rcc::tooling::CacheEntry> &Result) {
+ return !Result && Result.error().code() == rcc::ErrorCode::CacheMiss;
+}
+
+} // namespace
+
+int main() {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return tooling_test::fail(Target.error().message());
+ const rcc::tooling::BackendInvocation Invocation = rcc::tooling::defaultRV64GCInvocation();
+ auto Input = tooling_test::loadCanonicalInput("RISCVGenericPipeline.mir", *Target, Invocation);
+ if (!Input)
+ return tooling_test::fail(Input.error().message());
+ auto Temporary = tooling_test::TemporaryDirectory::create("rcc-artifact-store-test");
+ if (!Temporary)
+ return tooling_test::fail(Temporary.error().message());
+
+ const std::vector<std::uint8_t> Assembly = bytes(".text\nret\n");
+ auto CacheManifest = rcc::tooling::createBackendManifest(
+ rcc::tooling::ManifestKind::Cache, rcc::tooling::ArtifactKind::Assembly, Invocation, *Target,
+ Input->Text, std::span<const std::uint8_t>(Assembly));
+ if (!CacheManifest)
+ return tooling_test::fail(CacheManifest.error().message());
+ const std::filesystem::path CacheRoot = Temporary->path() / "cache";
+ auto Stored = rcc::tooling::storeCacheEntry(CacheRoot, *CacheManifest, Input->Text, Assembly);
+ if (!Stored)
+ return tooling_test::fail(Stored.error().message());
+ auto Loaded = rcc::tooling::loadCacheEntry(CacheRoot, rcc::tooling::ArtifactKind::Assembly,
+ Invocation, *Target, Input->Text);
+ if (!Loaded || Loaded->Manifest != *CacheManifest || Loaded->InputMIR != Input->Text ||
+ Loaded->Artifact != Assembly || Loaded->ParsedArtifact) {
+ return tooling_test::fail("cache hit must reload and verify every stored artifact fact");
+ }
+ if (rcc::tooling::storeCacheEntry(CacheRoot, *CacheManifest, Input->Text, Assembly))
+ return tooling_test::fail("cache publication must not overwrite an existing entry");
+ auto WrongKind = rcc::tooling::loadCacheEntry(CacheRoot, rcc::tooling::ArtifactKind::FinalMIR,
+ Invocation, *Target, Input->Text);
+ if (!isCacheMiss(WrongKind))
+ return tooling_test::fail("cache lookup must include the requested artifact kind");
+ auto NonBoundary = rcc::tooling::loadCacheEntry(
+ CacheRoot, rcc::tooling::ArtifactKind::LegalizedMIR, Invocation, *Target, Input->Text);
+ if (!isCacheMiss(NonBoundary))
+ return tooling_test::fail("cache lookup must reject non-boundary MIR stages");
+ rcc::tooling::BackendInvocation OtherInvocation = Invocation;
+ OtherInvocation.Determinism.ThreadCount = 2;
+ auto OtherInvocationResult = rcc::tooling::loadCacheEntry(
+ CacheRoot, rcc::tooling::ArtifactKind::Assembly, OtherInvocation, *Target, Input->Text);
+ if (!isCacheMiss(OtherInvocationResult))
+ return tooling_test::fail("cache lookup must include deterministic invocation facts");
+
+ const std::vector<std::uint8_t> MIRBytes = bytes(Input->Text);
+ auto MIRManifest = rcc::tooling::createBackendManifest(
+ rcc::tooling::ManifestKind::Cache, rcc::tooling::ArtifactKind::PreLegalizerMIR, Invocation,
+ *Target, Input->Text, std::span<const std::uint8_t>(MIRBytes));
+ if (!MIRManifest)
+ return tooling_test::fail(MIRManifest.error().message());
+ const std::filesystem::path MIRCacheRoot = Temporary->path() / "mir-cache";
+ Stored = rcc::tooling::storeCacheEntry(MIRCacheRoot, *MIRManifest, Input->Text, MIRBytes);
+ if (!Stored)
+ return tooling_test::fail(Stored.error().message());
+ auto LoadedMIR = rcc::tooling::loadCacheEntry(
+ MIRCacheRoot, rcc::tooling::ArtifactKind::PreLegalizerMIR, Invocation, *Target, Input->Text);
+ if (!LoadedMIR || !LoadedMIR->ParsedArtifact || LoadedMIR->ParsedArtifact->Text != Input->Text) {
+ return tooling_test::fail("cached MIR artifacts must be parsed and canonicalized on reload");
+ }
+
+ const auto StoreCorruptible = [&](std::string_view Name) -> rcc::Expected<std::filesystem::path> {
+ const std::filesystem::path Root = Temporary->path() / Name;
+ auto Result = rcc::tooling::storeCacheEntry(Root, *CacheManifest, Input->Text, Assembly);
+ if (!Result)
+ return std::unexpected(Result.error());
+ return cacheEntry(Root, CacheManifest->CacheKey);
+ };
+
+ auto CorruptArtifactPath = StoreCorruptible("corrupt-artifact-cache");
+ if (!CorruptArtifactPath)
+ return tooling_test::fail(CorruptArtifactPath.error().message());
+ auto Written = tooling_test::writeText(*CorruptArtifactPath / "artifact.s", "corrupt");
+ if (!Written)
+ return tooling_test::fail(Written.error().message());
+ auto CorruptArtifact = rcc::tooling::loadCacheEntry(Temporary->path() / "corrupt-artifact-cache",
+ rcc::tooling::ArtifactKind::Assembly,
+ Invocation, *Target, Input->Text);
+ if (!isCacheMiss(CorruptArtifact))
+ return tooling_test::fail("cache reload must reject a corrupted artifact");
+
+ auto CorruptInputPath = StoreCorruptible("corrupt-input-cache");
+ if (!CorruptInputPath)
+ return tooling_test::fail(CorruptInputPath.error().message());
+ Written = tooling_test::writeText(*CorruptInputPath / "input.mir", "corrupt");
+ if (!Written)
+ return tooling_test::fail(Written.error().message());
+ auto CorruptInput = rcc::tooling::loadCacheEntry(Temporary->path() / "corrupt-input-cache",
+ rcc::tooling::ArtifactKind::Assembly, Invocation,
+ *Target, Input->Text);
+ if (!isCacheMiss(CorruptInput))
+ return tooling_test::fail("cache reload must reject a corrupted canonical input");
+
+ auto CorruptManifestPath = StoreCorruptible("corrupt-manifest-cache");
+ if (!CorruptManifestPath)
+ return tooling_test::fail(CorruptManifestPath.error().message());
+ Written = tooling_test::writeText(*CorruptManifestPath / "manifest.yaml", "---\ninvalid: true\n");
+ if (!Written)
+ return tooling_test::fail(Written.error().message());
+ auto CorruptManifest = rcc::tooling::loadCacheEntry(Temporary->path() / "corrupt-manifest-cache",
+ rcc::tooling::ArtifactKind::Assembly,
+ Invocation, *Target, Input->Text);
+ if (!isCacheMiss(CorruptManifest))
+ return tooling_test::fail("cache reload must reject a corrupted manifest");
+
+ std::vector<std::uint8_t> WrongArtifact = Assembly;
+ WrongArtifact.push_back(0);
+ if (rcc::tooling::storeCacheEntry(Temporary->path() / "wrong-artifact-cache", *CacheManifest,
+ Input->Text, WrongArtifact)) {
+ return tooling_test::fail("cache store must reject bytes that disagree with the manifest");
+ }
+
+ auto ReproducerManifest = rcc::tooling::createBackendManifest(
+ rcc::tooling::ManifestKind::Reproducer, rcc::tooling::ArtifactKind::Assembly, Invocation,
+ *Target, Input->Text);
+ if (!ReproducerManifest)
+ return tooling_test::fail(ReproducerManifest.error().message());
+ const std::filesystem::path ReproducerPath = Temporary->path() / "reproducer";
+ auto ReproducerWritten =
+ rcc::tooling::writeReproducer(ReproducerPath, *ReproducerManifest, Input->Text);
+ if (!ReproducerWritten)
+ return tooling_test::fail(ReproducerWritten.error().message());
+ auto Reproducer = rcc::tooling::loadReproducer(ReproducerPath, *Target);
+ if (!Reproducer || Reproducer->Manifest != *ReproducerManifest ||
+ Reproducer->Input.Text != Input->Text) {
+ return tooling_test::fail("reproducer reload must verify manifest and canonical MIR identity");
+ }
+ if (rcc::tooling::writeReproducer(ReproducerPath, *ReproducerManifest, Input->Text))
+ return tooling_test::fail("reproducer publication must not overwrite an existing package");
+
+ rcc::tooling::StructuredDiagnostic Diagnostic;
+ Diagnostic.Category = "verification error";
+ Diagnostic.Stage = "regbank-mir";
+ Diagnostic.Pass = "InstructionSelect";
+ Diagnostic.Function = "generic_pipeline";
+ Diagnostic.Rule = "machine-pass";
+ Diagnostic.Message = "required selection Action was rejected";
+ auto Updated = rcc::tooling::updateReproducerDiagnostics(ReproducerPath, {Diagnostic});
+ if (!Updated)
+ return tooling_test::fail(Updated.error().message());
+ Reproducer = rcc::tooling::loadReproducer(ReproducerPath, *Target);
+ if (!Reproducer || Reproducer->Manifest.Diagnostics !=
+ std::vector<rcc::tooling::StructuredDiagnostic>({Diagnostic})) {
+ return tooling_test::fail("reproducer diagnostic update must round-trip atomically");
+ }
+ for (const std::filesystem::directory_entry &Entry :
+ std::filesystem::directory_iterator(ReproducerPath)) {
+ if (Entry.path().filename().string().contains(".tmp."))
+ return tooling_test::fail("reproducer diagnostic update must not leave temporary files");
+ }
+
+ Written = tooling_test::writeText(ReproducerPath / "input.mir", "corrupt");
+ if (!Written)
+ return tooling_test::fail(Written.error().message());
+ if (rcc::tooling::loadReproducer(ReproducerPath, *Target))
+ return tooling_test::fail("reproducer reload must reject a modified input");
+ if (rcc::tooling::updateReproducerDiagnostics(ReproducerPath, {Diagnostic}))
+ return tooling_test::fail("diagnostic update must reject a modified reproducer input");
+
+ return 0;
+}
diff --git a/backend/unittests/Tooling/BackendManifestTest.cpp b/backend/unittests/Tooling/BackendManifestTest.cpp
new file mode 100644
index 0000000..5a6a0e9
--- /dev/null
+++ b/backend/unittests/Tooling/BackendManifestTest.cpp
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/BackendManifest.h"
+
+#include "ToolingTestSupport.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+using rcc::tooling::BackendInvocation;
+
+[[nodiscard]] bool keyDiffers(const BackendInvocation &Base, BackendInvocation Changed,
+ std::string_view Input, std::string_view Fingerprint) {
+ auto Canonicalized = Changed.canonicalizeAndVerify();
+ if (!Canonicalized)
+ return false;
+ auto BaseKey = rcc::tooling::computeCacheKey(Input, Base, Fingerprint);
+ auto ChangedKey = rcc::tooling::computeCacheKey(Input, Changed, Fingerprint);
+ return BaseKey && ChangedKey && *BaseKey != *ChangedKey;
+}
+
+} // namespace
+
+int main() {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return tooling_test::fail(Target.error().message());
+ const BackendInvocation Base = rcc::tooling::defaultRV64GCInvocation();
+ auto Fingerprint = rcc::tooling::computeTargetDescriptionFingerprint(*Target);
+ if (!Fingerprint || Fingerprint->size() != 64)
+ return tooling_test::fail("target description fingerprint must be a SHA-256 digest");
+ auto RepeatedFingerprint = rcc::tooling::computeTargetDescriptionFingerprint(*Target);
+ if (!RepeatedFingerprint || *RepeatedFingerprint != *Fingerprint)
+ return tooling_test::fail("target description fingerprint must be deterministic");
+
+ BackendInvocation Unordered = Base;
+ std::ranges::reverse(Unordered.Features);
+ std::ranges::reverse(Unordered.TargetOptions);
+ Unordered.EnabledActions = {"spill", "coalesce"};
+ auto Canonicalized = Unordered.canonicalizeAndVerify();
+ if (!Canonicalized || !std::ranges::is_sorted(Unordered.Features) ||
+ !std::ranges::is_sorted(Unordered.TargetOptions, {}, &rcc::tooling::BackendOption::Name) ||
+ Unordered.EnabledActions != std::vector<std::string>({"coalesce", "spill"})) {
+ return tooling_test::fail("backend invocation canonicalization must sort explicit identities");
+ }
+ BackendInvocation DuplicateFeature = Base;
+ DuplicateFeature.Features.push_back("+i");
+ if (DuplicateFeature.canonicalizeAndVerify())
+ return tooling_test::fail("backend invocation must reject duplicate target features");
+ BackendInvocation ZeroThreads = Base;
+ ZeroThreads.Determinism.ThreadCount = 0;
+ if (ZeroThreads.canonicalizeAndVerify())
+ return tooling_test::fail("backend invocation must reject a zero thread count");
+
+ constexpr std::string_view Input = "canonical MIR";
+ auto BaseKey = rcc::tooling::computeCacheKey(Input, Base, *Fingerprint);
+ if (!BaseKey || BaseKey->size() != 64)
+ return tooling_test::fail("cache key must be a SHA-256 digest");
+ auto DifferentInput = rcc::tooling::computeCacheKey("different MIR", Base, *Fingerprint);
+ if (!DifferentInput || *DifferentInput == *BaseKey)
+ return tooling_test::fail("cache key must include canonical MIR");
+ std::string DifferentFingerprint(64, '0');
+ auto DifferentTarget = rcc::tooling::computeCacheKey(Input, Base, DifferentFingerprint);
+ if (!DifferentTarget || *DifferentTarget == *BaseKey)
+ return tooling_test::fail("cache key must include the target description fingerprint");
+
+ BackendInvocation Changed = Base;
+ Changed.Triple += "-variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the target triple");
+ Changed = Base;
+ Changed.CPU += "-variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the target CPU");
+ Changed = Base;
+ Changed.TuneCPU += "-variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the tuning CPU");
+ Changed = Base;
+ Changed.Features.front() = "+variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include target features");
+ Changed = Base;
+ Changed.ABI += "-variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the ABI");
+ Changed = Base;
+ Changed.DataLayout += "-variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the data layout");
+ Changed = Base;
+ Changed.Relocation = rcc::tooling::RelocationModel::Static;
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the relocation model");
+ Changed = Base;
+ Changed.Code = rcc::tooling::CodeModel::Medium;
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the code model");
+ Changed = Base;
+ Changed.TargetOptions.front().Value = "variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include target options");
+ Changed = Base;
+ Changed.Layout = rcc::tooling::LayoutOwnership::CompilerFinalLayout;
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include layout ownership");
+ Changed = Base;
+ Changed.AssemblerVersion += "-variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include assembler identity");
+ Changed = Base;
+ Changed.AssemblerOptions.front().Value = "false";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include assembler options");
+ Changed = Base;
+ Changed.Optimization = rcc::tooling::OptimizationLevel::O3;
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the optimization level");
+ Changed = Base;
+ Changed.PassPipeline = {"Legalizer"};
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the pass pipeline");
+ Changed = Base;
+ Changed.Determinism.RandomSeed = 1;
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the random seed");
+ Changed = Base;
+ Changed.Determinism.ThreadCount = 2;
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the thread count");
+ Changed = Base;
+ Changed.Determinism.StableScheduling = false;
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include stable scheduling");
+ Changed = Base;
+ Changed.ActionFuel = 7;
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include Action fuel");
+ Changed = Base;
+ Changed.EnabledActions = {"Legalizer"};
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include enabled Actions");
+ Changed = Base;
+ Changed.BuildFingerprint += "-variant";
+ if (!keyDiffers(Base, Changed, Input, *Fingerprint))
+ return tooling_test::fail("cache key must include the backend build fingerprint");
+
+ rcc::tooling::StructuredDiagnostic Diagnostic;
+ Diagnostic.Category = "verification error";
+ Diagnostic.Stage = "selected-mir";
+ Diagnostic.Pass = "test-pass";
+ Diagnostic.Function = "test-function";
+ Diagnostic.Block = 3;
+ Diagnostic.Instruction = 7;
+ Diagnostic.Operand = 1;
+ Diagnostic.SourceLocation = "input.mir:4:5";
+ Diagnostic.TargetConstraint = "GPR";
+ Diagnostic.Rule = "test-rule";
+ Diagnostic.Message = "quoted \"message\"\nline";
+ auto Manifest = rcc::tooling::createBackendManifest(rcc::tooling::ManifestKind::Reproducer,
+ rcc::tooling::ArtifactKind::Assembly, Base,
+ *Target, Input, std::nullopt, {Diagnostic});
+ if (!Manifest)
+ return tooling_test::fail(Manifest.error().message());
+ auto Printed = rcc::tooling::printBackendManifest(*Manifest);
+ if (!Printed)
+ return tooling_test::fail(Printed.error().message());
+ auto Parsed = rcc::tooling::parseBackendManifest("manifest.yaml", *Printed);
+ if (!Parsed || *Parsed != *Manifest)
+ return tooling_test::fail("manifest printer and strict parser must round-trip all fields");
+
+ BackendInvocation EmptyCollections = Base;
+ EmptyCollections.TargetOptions.clear();
+ EmptyCollections.AssemblerOptions.clear();
+ auto EmptyManifest = rcc::tooling::createBackendManifest(rcc::tooling::ManifestKind::Reproducer,
+ rcc::tooling::ArtifactKind::FinalMIR,
+ EmptyCollections, *Target, Input);
+ if (!EmptyManifest)
+ return tooling_test::fail(EmptyManifest.error().message());
+ auto EmptyPrinted = rcc::tooling::printBackendManifest(*EmptyManifest);
+ if (!EmptyPrinted)
+ return tooling_test::fail(EmptyPrinted.error().message());
+ auto EmptyParsed = rcc::tooling::parseBackendManifest("empty-manifest.yaml", *EmptyPrinted);
+ if (!EmptyParsed || *EmptyParsed != *EmptyManifest)
+ return tooling_test::fail("manifest must preserve explicitly empty collections");
+
+ std::string Unknown = *Printed;
+ Unknown.insert(Unknown.find("schema:"), "unknown: 1\n");
+ if (rcc::tooling::parseBackendManifest("unknown.yaml", std::move(Unknown)))
+ return tooling_test::fail("strict manifest parsing must reject unknown keys");
+ std::string Duplicate = *Printed;
+ Duplicate.insert(Duplicate.find("schema:"), "schema: 1\n");
+ if (rcc::tooling::parseBackendManifest("duplicate.yaml", std::move(Duplicate)))
+ return tooling_test::fail("strict manifest parsing must reject duplicate keys");
+ std::string Missing = *Printed;
+ const std::size_t ABI = Missing.find(" abi:");
+ Missing.erase(ABI, Missing.find('\n', ABI) - ABI + 1U);
+ if (rcc::tooling::parseBackendManifest("missing.yaml", std::move(Missing)))
+ return tooling_test::fail("strict manifest parsing must reject missing invocation facts");
+
+ return 0;
+}
diff --git a/backend/unittests/Tooling/CMakeLists.txt b/backend/unittests/Tooling/CMakeLists.txt
new file mode 100644
index 0000000..3d637ec
--- /dev/null
+++ b/backend/unittests/Tooling/CMakeLists.txt
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+function(rcc_backend_add_tooling_test target source test_name)
+ add_executable(${target} ${source})
+ rcc_backend_configure_target(${target})
+ target_link_libraries(${target} PRIVATE RCCBackend::Tooling RCCBackend::RISCV)
+ target_compile_definitions(
+ ${target}
+ PRIVATE
+ RCC_BACKEND_TOOLING_TEST_INPUT_DIR="${RCC_BACKEND_SOURCE_DIR}/unittests/Target/Inputs"
+ )
+ add_test(NAME ${test_name} COMMAND ${target})
+endfunction()
+
+rcc_backend_add_tooling_test(
+ RCCBackendManifestTests
+ BackendManifestTest.cpp
+ RCCBackend.Tooling.BackendManifest
+)
+rcc_backend_add_tooling_test(
+ RCCBackendArtifactStoreTests
+ ArtifactStoreTest.cpp
+ RCCBackend.Tooling.ArtifactStore
+)
+rcc_backend_add_tooling_test(
+ RCCBackendPipelineDriverTests
+ PipelineDriverTest.cpp
+ RCCBackend.Tooling.PipelineDriver
+)
+rcc_backend_add_tooling_test(
+ RCCBackendMIRReducerFuzzerTests
+ MIRReducerFuzzerTest.cpp
+ RCCBackend.Tooling.MIRReducerFuzzer
+)
+
+rcc_backend_register_format_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/BackendManifestTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/ArtifactStoreTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/PipelineDriverTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRReducerFuzzerTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/ToolingTestSupport.h"
+)
+rcc_backend_register_tidy_files(
+ "${CMAKE_CURRENT_SOURCE_DIR}/BackendManifestTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/ArtifactStoreTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/PipelineDriverTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MIRReducerFuzzerTest.cpp"
+)
diff --git a/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp b/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp
new file mode 100644
index 0000000..236f4e9
--- /dev/null
+++ b/backend/unittests/Tooling/MIRReducerFuzzerTest.cpp
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "ToolingTestSupport.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+#include "rcc/Tooling/BackendDriver.h"
+#include "rcc/Tooling/MIRFuzzer.h"
+#include "rcc/Tooling/MIRReducer.h"
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+[[nodiscard]] bool hasErrorCode(const auto &Result, rcc::ErrorCode Code) {
+ return !Result && Result.error().code() == Code;
+}
+
+[[nodiscard]] bool hasReductionStep(const rcc::tooling::MIRReductionResult &Result,
+ rcc::tooling::ReductionGranularity Granularity) {
+ for (const rcc::tooling::MIRReductionStep &Step : Result.Steps) {
+ if (Step.Granularity == Granularity)
+ return true;
+ }
+ return false;
+}
+
+[[nodiscard]] rcc::Expected<std::string> addDeadInstruction(std::string Source) {
+ constexpr std::string_view Anchor = " $x10 = COPY %2\n";
+ const std::size_t Position = Source.find(Anchor);
+ if (Position == std::string::npos) {
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError,
+ "test MIR does not contain the expected COPY");
+ }
+ Source.insert(Position, " %3:gpr(i64) = G_CONSTANT 99\n");
+ return Source;
+}
+
+} // namespace
+
+int main() {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return tooling_test::fail(Target.error().message());
+ const rcc::tooling::BackendInvocation Invocation = rcc::tooling::defaultRV64GCInvocation();
+ auto Source = tooling_test::readInput("RISCVGenericPipeline.mir");
+ if (!Source)
+ return tooling_test::fail(Source.error().message());
+ auto ReducibleSource = addDeadInstruction(*Source);
+ if (!ReducibleSource)
+ return tooling_test::fail(ReducibleSource.error().message());
+ auto CanonicalReducible = rcc::tooling::parseAndCanonicalizeMIR(
+ "validity-seed.mir", *ReducibleSource, *Target, Invocation);
+ if (!CanonicalReducible)
+ return tooling_test::fail(CanonicalReducible.error().message());
+
+ rcc::tooling::MIRReducerOptions ValidityOptions;
+ ValidityOptions.Mode = rcc::tooling::ReductionMode::PreserveValidity;
+ ValidityOptions.MaximumAttempts = 128;
+ ValidityOptions.Granularities = {rcc::tooling::ReductionGranularity::Instruction};
+ auto ValidityReduction = rcc::tooling::reduceMIR("validity-reducer.mir", *ReducibleSource,
+ Invocation, *Target, {}, ValidityOptions);
+ if (!ValidityReduction || ValidityReduction->MIR.size() >= CanonicalReducible->Text.size() ||
+ !hasReductionStep(*ValidityReduction, rcc::tooling::ReductionGranularity::Instruction)) {
+ return tooling_test::fail(
+ "validity reduction must remove a dead instruction and record the accepted step");
+ }
+ auto ReducedCanonical = rcc::tooling::parseAndCanonicalizeMIR(
+ "validity-reduced.mir", ValidityReduction->MIR, *Target, ValidityReduction->Invocation);
+ if (!ReducedCanonical || ReducedCanonical->Text != ValidityReduction->MIR)
+ return tooling_test::fail("validity reduction must return canonical verified MIR");
+
+ rcc::tooling::MIRReducerOptions FeatureOptions;
+ FeatureOptions.Mode = rcc::tooling::ReductionMode::PreserveFailure;
+ FeatureOptions.MaximumAttempts = 128;
+ FeatureOptions.Granularities = {rcc::tooling::ReductionGranularity::TargetFeature};
+ const auto KeepTwoFeatures = [](std::string_view,
+ const rcc::tooling::BackendInvocation &Candidate) {
+ return rcc::Expected<bool>(Candidate.Features.size() >= 2);
+ };
+ auto FeatureReduction = rcc::tooling::reduceMIR("feature-reducer.mir", *Source, Invocation,
+ *Target, KeepTwoFeatures, FeatureOptions);
+ if (!FeatureReduction || FeatureReduction->Invocation.Features.size() != 2 ||
+ !hasReductionStep(*FeatureReduction, rcc::tooling::ReductionGranularity::TargetFeature)) {
+ return tooling_test::fail("target-feature reduction must preserve its interestingness limit");
+ }
+
+ rcc::tooling::BackendInvocation PassInvocation = Invocation;
+ PassInvocation.PassPipeline = {"Legalizer", "RegisterBankSelect", "InstructionSelect"};
+ rcc::tooling::MIRReducerOptions PassOptions;
+ PassOptions.Mode = rcc::tooling::ReductionMode::PreserveFailure;
+ PassOptions.MaximumAttempts = 32;
+ PassOptions.Granularities = {rcc::tooling::ReductionGranularity::PassPipeline};
+ const auto KeepOnePass = [](std::string_view, const rcc::tooling::BackendInvocation &Candidate) {
+ return rcc::Expected<bool>(!Candidate.PassPipeline.empty());
+ };
+ auto PassReduction = rcc::tooling::reduceMIR("pass-reducer.mir", *Source, PassInvocation, *Target,
+ KeepOnePass, PassOptions);
+ if (!PassReduction || PassReduction->Invocation.PassPipeline.size() != 1 ||
+ !hasReductionStep(*PassReduction, rcc::tooling::ReductionGranularity::PassPipeline)) {
+ return tooling_test::fail("pass-pipeline reduction must retain one interesting pass");
+ }
+
+ auto RegBankInput = rcc::tooling::parseAndCanonicalizeMIR("failure-seed.mir", *ReducibleSource,
+ *Target, Invocation);
+ if (!RegBankInput)
+ return tooling_test::fail(RegBankInput.error().message());
+ auto RegBank = rcc::tooling::runBackend(std::move(*RegBankInput), *Target, Invocation,
+ rcc::tooling::BackendOutputKind::RegisterBankMIR);
+ if (!RegBank)
+ return tooling_test::fail(RegBank.error().message());
+ rcc::tooling::BackendInvocation FailureInvocation = Invocation;
+ FailureInvocation.PassPipeline = {"InstructionSelect"};
+ FailureInvocation.ActionFuel = 0;
+ const auto PreservesActionFailure = [&](std::string_view MIR,
+ const rcc::tooling::BackendInvocation &Candidate) {
+ auto Input = rcc::tooling::parseAndCanonicalizeMIR("failure-candidate.mir", std::string(MIR),
+ *Target, Candidate);
+ if (!Input)
+ return rcc::Expected<bool>(std::unexpected(Input.error()));
+ auto Run = rcc::tooling::runBackend(std::move(*Input), *Target, Candidate,
+ rcc::tooling::BackendOutputKind::SelectedMIR);
+ return rcc::Expected<bool>(!Run && Run.error().code() == rcc::ErrorCode::VerificationError);
+ };
+ rcc::tooling::MIRReducerOptions FailureOptions;
+ FailureOptions.Mode = rcc::tooling::ReductionMode::PreserveFailure;
+ FailureOptions.MaximumAttempts = 128;
+ FailureOptions.Granularities = {rcc::tooling::ReductionGranularity::Instruction};
+ auto FailureReduction =
+ rcc::tooling::reduceMIR("failure-reducer.mir", std::string(RegBank->text()),
+ FailureInvocation, *Target, PreservesActionFailure, FailureOptions);
+ if (!FailureReduction ||
+ !hasReductionStep(*FailureReduction, rcc::tooling::ReductionGranularity::Instruction)) {
+ return tooling_test::fail("failure reduction must retain the Action/Fuel failure");
+ }
+ auto StillInteresting =
+ PreservesActionFailure(FailureReduction->MIR, FailureReduction->Invocation);
+ if (!StillInteresting || !*StillInteresting)
+ return tooling_test::fail("reduced failure input must still satisfy its predicate");
+
+ rcc::tooling::MIRReducerOptions InvalidReducerOptions = ValidityOptions;
+ InvalidReducerOptions.MaximumAttempts = 0;
+ auto ZeroBudget = rcc::tooling::reduceMIR("zero-budget.mir", *Source, Invocation, *Target, {},
+ InvalidReducerOptions);
+ if (!hasErrorCode(ZeroBudget, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("reducer must reject a zero attempt budget");
+ InvalidReducerOptions = FeatureOptions;
+ auto MissingPredicate = rcc::tooling::reduceMIR("missing-predicate.mir", *Source, Invocation,
+ *Target, {}, InvalidReducerOptions);
+ if (!hasErrorCode(MissingPredicate, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("failure reduction must require an interestingness predicate");
+ const auto NeverInteresting = [](std::string_view, const rcc::tooling::BackendInvocation &) {
+ return rcc::Expected<bool>(false);
+ };
+ auto FalsePredicate = rcc::tooling::reduceMIR("false-predicate.mir", *Source, Invocation, *Target,
+ NeverInteresting, FeatureOptions);
+ if (!hasErrorCode(FalsePredicate, rcc::ErrorCode::ReductionFailed))
+ return tooling_test::fail("reducer must reject an initially uninteresting input");
+ InvalidReducerOptions = ValidityOptions;
+ InvalidReducerOptions.Granularities = {
+ rcc::tooling::ReductionGranularity::Instruction,
+ rcc::tooling::ReductionGranularity::Instruction,
+ };
+ auto DuplicateGranularity = rcc::tooling::reduceMIR(
+ "duplicate-granularity.mir", *Source, Invocation, *Target, {}, InvalidReducerOptions);
+ if (!hasErrorCode(DuplicateGranularity, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("reducer must reject duplicate granularities");
+ volatile std::uint8_t InvalidGranularityValue = 255;
+ InvalidReducerOptions.Granularities = {
+ static_cast<rcc::tooling::ReductionGranularity>(InvalidGranularityValue)};
+ auto InvalidGranularity = rcc::tooling::reduceMIR("invalid-granularity.mir", *Source, Invocation,
+ *Target, {}, InvalidReducerOptions);
+ if (!hasErrorCode(InvalidGranularity, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("reducer must reject an unknown granularity");
+
+ rcc::tooling::MIRFuzzOptions FuzzOptions;
+ FuzzOptions.Seed = 0x123456789abcdef0ULL;
+ FuzzOptions.Iterations = 2;
+ auto FirstFuzz =
+ rcc::tooling::fuzzMIR("fuzz-seed.mir", *Source, Invocation, *Target, FuzzOptions);
+ auto SecondFuzz =
+ rcc::tooling::fuzzMIR("fuzz-seed.mir", *Source, Invocation, *Target, FuzzOptions);
+ if (!FirstFuzz || !SecondFuzz || FirstFuzz->Seed != FuzzOptions.Seed ||
+ FirstFuzz->Layers != SecondFuzz->Layers || FirstFuzz->Layers.size() != 6 ||
+ FirstFuzz->executed() != 11 ||
+ FirstFuzz->accepted() + FirstFuzz->rejected() != FirstFuzz->executed()) {
+ return tooling_test::fail("all six fuzz layers must produce deterministic accounting");
+ }
+ constexpr std::array LayerNames{
+ std::string_view("parser"), std::string_view("builder"),
+ std::string_view("property-stage"), std::string_view("mutation"),
+ std::string_view("pipeline"), std::string_view("semantics"),
+ };
+ for (std::size_t Index = 0; Index < LayerNames.size(); ++Index) {
+ auto Parsed = rcc::tooling::parseMIRFuzzLayer(LayerNames[Index]);
+ if (!Parsed || rcc::tooling::spelling(*Parsed) != LayerNames[Index] ||
+ FirstFuzz->Layers[Index].Layer != *Parsed) {
+ return tooling_test::fail("fuzz layer spelling and parsing must round-trip");
+ }
+ }
+ if (rcc::tooling::parseMIRFuzzLayer("unknown"))
+ return tooling_test::fail("fuzz layer parser must reject unknown names");
+
+ rcc::tooling::MIRFuzzOptions InvalidFuzzOptions = FuzzOptions;
+ InvalidFuzzOptions.Iterations = 0;
+ auto ZeroIterations = rcc::tooling::fuzzMIR("zero-iterations.mir", *Source, Invocation, *Target,
+ InvalidFuzzOptions);
+ if (!hasErrorCode(ZeroIterations, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("fuzzer must reject zero iterations");
+ InvalidFuzzOptions = FuzzOptions;
+ InvalidFuzzOptions.MaximumInputSize = 0;
+ auto ZeroInputLimit = rcc::tooling::fuzzMIR("zero-input-limit.mir", *Source, Invocation, *Target,
+ InvalidFuzzOptions);
+ if (!hasErrorCode(ZeroInputLimit, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("fuzzer must reject a zero input-size limit");
+ InvalidFuzzOptions = FuzzOptions;
+ InvalidFuzzOptions.MaximumInputSize = 1;
+ auto OversizedSeed =
+ rcc::tooling::fuzzMIR("oversized-seed.mir", *Source, Invocation, *Target, InvalidFuzzOptions);
+ if (!hasErrorCode(OversizedSeed, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("fuzzer must reject a seed larger than its input limit");
+ InvalidFuzzOptions = FuzzOptions;
+ InvalidFuzzOptions.Layers = {rcc::tooling::MIRFuzzLayer::Parser,
+ rcc::tooling::MIRFuzzLayer::Parser};
+ auto DuplicateLayer = rcc::tooling::fuzzMIR("duplicate-layer.mir", *Source, Invocation, *Target,
+ InvalidFuzzOptions);
+ if (!hasErrorCode(DuplicateLayer, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("fuzzer must reject duplicate layers");
+ volatile std::uint8_t InvalidFuzzLayerValue = 255;
+ InvalidFuzzOptions.Layers = {static_cast<rcc::tooling::MIRFuzzLayer>(InvalidFuzzLayerValue)};
+ auto InvalidLayer =
+ rcc::tooling::fuzzMIR("invalid-layer.mir", *Source, Invocation, *Target, InvalidFuzzOptions);
+ if (!hasErrorCode(InvalidLayer, rcc::ErrorCode::InvalidArgument))
+ return tooling_test::fail("fuzzer must reject an unknown layer");
+
+ return 0;
+}
diff --git a/backend/unittests/Tooling/PipelineDriverTest.cpp b/backend/unittests/Tooling/PipelineDriverTest.cpp
new file mode 100644
index 0000000..4a223b6
--- /dev/null
+++ b/backend/unittests/Tooling/PipelineDriverTest.cpp
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/Tooling/PipelineDriver.h"
+
+#include "ToolingTestSupport.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/MIR/MIRDocument.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+#include "rcc/Tooling/BackendDriver.h"
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+[[nodiscard]] rcc::Expected<std::string>
+printDocument(const rcc::mir::MIRDocument &Document, const rcc::TargetDescription &Target,
+ const rcc::tooling::BackendInvocation &Invocation) {
+ rcc::mir::MIRDocumentPrinterOptions Options;
+ Options.Mode = rcc::mir::MIPrintMode::Complete;
+ Options.EmitEmbeddedIR = true;
+ Options.DataLayout = Invocation.DataLayout;
+ Options.DefaultPointerSizeInBits = 64;
+ return rcc::mir::printMIRDocument(Document, Target, std::move(Options));
+}
+
+[[nodiscard]] rcc::Expected<rcc::tooling::BackendRunResult>
+runInput(std::string_view SourceName, std::string SourceText, const rcc::TargetDescription &Target,
+ const rcc::tooling::BackendInvocation &Invocation, rcc::tooling::BackendOutputKind Output,
+ rcc::tooling::PipelineControls Controls = {}) {
+ auto Input = rcc::tooling::parseAndCanonicalizeMIR(std::string(SourceName), std::move(SourceText),
+ Target, Invocation);
+ if (!Input)
+ return std::unexpected(Input.error());
+ return rcc::tooling::runBackend(std::move(*Input), Target, Invocation, Output,
+ std::move(Controls));
+}
+
+[[nodiscard]] rcc::Expected<std::string> makeMultiFunctionInput() {
+ auto First = tooling_test::readInput("RISCVGenericPipeline.mir");
+ if (!First)
+ return std::unexpected(First.error());
+ std::string Second = *First;
+ constexpr std::string_view OriginalName = "name: generic_pipeline";
+ const std::size_t Name = Second.find(OriginalName);
+ if (Name == std::string::npos)
+ return rcc::makeUnexpected(rcc::ErrorCode::InternalError, "test MIR function name is absent");
+ Second.replace(Name, OriginalName.size(), "name: generic_pipeline_second");
+ First->push_back('\n');
+ First->append(Second);
+ return First;
+}
+
+struct DeterministicOutputs final {
+ std::vector<std::uint8_t> FinalMIR;
+ std::vector<std::uint8_t> Assembly;
+ std::vector<std::uint8_t> Object;
+ std::vector<rcc::MachineActionEvent> Actions;
+ std::vector<rcc::tooling::ExecutedPass> ExecutedPasses;
+};
+
+[[nodiscard]] rcc::Expected<DeterministicOutputs>
+runDeterminismCase(std::string_view Input, const rcc::TargetDescription &Target,
+ std::uint32_t ThreadCount) {
+ rcc::tooling::BackendInvocation Invocation = rcc::tooling::defaultRV64GCInvocation();
+ Invocation.Determinism.ThreadCount = ThreadCount;
+ auto Final = runInput("determinism-final.mir", std::string(Input), Target, Invocation,
+ rcc::tooling::BackendOutputKind::FinalMIR);
+ if (!Final)
+ return std::unexpected(Final.error());
+ auto Assembly = runInput("determinism-assembly.mir", std::string(Input), Target, Invocation,
+ rcc::tooling::BackendOutputKind::Assembly);
+ if (!Assembly)
+ return std::unexpected(Assembly.error());
+ auto Object = runInput("determinism-object.mir", std::string(Input), Target, Invocation,
+ rcc::tooling::BackendOutputKind::RelocatableObject);
+ if (!Object)
+ return std::unexpected(Object.error());
+ return DeterministicOutputs{
+ .FinalMIR = std::move(Final->Output),
+ .Assembly = std::move(Assembly->Output),
+ .Object = std::move(Object->Output),
+ .Actions = std::move(Final->Actions),
+ .ExecutedPasses = std::move(Final->Pipeline.ExecutedPasses),
+ };
+}
+
+} // namespace
+
+int main() {
+ auto Target = rcc::riscv::createTargetDescription();
+ if (!Target)
+ return tooling_test::fail(Target.error().message());
+ const rcc::tooling::BackendInvocation Invocation = rcc::tooling::defaultRV64GCInvocation();
+ auto Source = tooling_test::readInput("RISCVGenericPipeline.mir");
+ if (!Source)
+ return tooling_test::fail(Source.error().message());
+
+ constexpr std::array StageCases{
+ std::pair{rcc::tooling::BackendOutputKind::PreLegalizerMIR,
+ rcc::tooling::BackendStage::PreLegalizerMIR},
+ std::pair{rcc::tooling::BackendOutputKind::LegalizedMIR,
+ rcc::tooling::BackendStage::LegalizedMIR},
+ std::pair{rcc::tooling::BackendOutputKind::RegisterBankMIR,
+ rcc::tooling::BackendStage::RegisterBankMIR},
+ std::pair{rcc::tooling::BackendOutputKind::SelectedMIR,
+ rcc::tooling::BackendStage::SelectedMIR},
+ std::pair{rcc::tooling::BackendOutputKind::PostSSAMIR,
+ rcc::tooling::BackendStage::PostSSAMIR},
+ std::pair{rcc::tooling::BackendOutputKind::PostRegisterAllocationMIR,
+ rcc::tooling::BackendStage::PostRegisterAllocationMIR},
+ std::pair{rcc::tooling::BackendOutputKind::FinalMIR, rcc::tooling::BackendStage::FinalMIR},
+ };
+ std::optional<std::string> SelectedMIR;
+ for (const auto &[Output, ExpectedStage] : StageCases) {
+ auto Run = runInput("stage.mir", *Source, *Target, Invocation, Output);
+ if (!Run)
+ return tooling_test::fail(Run.error().message());
+ auto Parsed = rcc::tooling::parseAndCanonicalizeMIR(
+ "stage-output.mir", std::string(Run->text()), *Target, Invocation);
+ if (!Parsed || Parsed->Document.unit().functionCount() != 1)
+ return tooling_test::fail("every MIR stage output must strictly parse and verify");
+ const rcc::MachineFunction *Function = Parsed->Document.unit().functionAt(0);
+ if (Function == nullptr)
+ return tooling_test::fail("MIR stage output must contain a machine function");
+ const rcc::tooling::BackendStage ActualStage = rcc::tooling::stageForFunction(*Function);
+ if (ActualStage != ExpectedStage) {
+ return tooling_test::fail(
+ "MIR output requested " + std::string(rcc::tooling::spelling(ExpectedStage)) +
+ " but reparsed as " + std::string(rcc::tooling::spelling(ActualStage)));
+ }
+ if (Output == rcc::tooling::BackendOutputKind::SelectedMIR)
+ SelectedMIR = std::string(Run->text());
+ }
+
+ auto Assembly = runInput("assembly.mir", *Source, *Target, Invocation,
+ rcc::tooling::BackendOutputKind::Assembly);
+ if (!Assembly || !Assembly->text().contains("generic_pipeline") ||
+ !Assembly->text().contains("jalr x0, x1, 0")) {
+ return tooling_test::fail("assembly output must use the complete RV64GC production pipeline");
+ }
+ auto Object = runInput("object.mir", *Source, *Target, Invocation,
+ rcc::tooling::BackendOutputKind::RelocatableObject);
+ constexpr std::array<std::uint8_t, 4> ELFMagic{0x7fU, 'E', 'L', 'F'};
+ if (!Object || Object->Output.size() < ELFMagic.size() ||
+ !std::ranges::equal(ELFMagic, std::span<const std::uint8_t>(Object->Output).first(4))) {
+ return tooling_test::fail("object output must be a native ELF relocatable object");
+ }
+
+ auto SnapshotInput =
+ rcc::tooling::parseAndCanonicalizeMIR("snapshots.mir", *Source, *Target, Invocation);
+ if (!SnapshotInput)
+ return tooling_test::fail(SnapshotInput.error().message());
+ rcc::tooling::PipelineControls SnapshotControls;
+ SnapshotControls.CaptureStageSnapshots = true;
+ auto Snapshots = rcc::tooling::runRV64GCPipeline(SnapshotInput->Document, *Target, Invocation,
+ SnapshotControls);
+ if (!Snapshots || Snapshots->Snapshots.size() != 7 ||
+ Snapshots->Snapshots.front().Stage != rcc::tooling::BackendStage::PreLegalizerMIR ||
+ Snapshots->Snapshots.back().Stage != rcc::tooling::BackendStage::FinalMIR) {
+ return tooling_test::fail("pipeline must capture every stable MIR stage boundary");
+ }
+ for (const rcc::tooling::PipelineSnapshot &Snapshot : Snapshots->Snapshots) {
+ if (Snapshot.Phase != rcc::tooling::SnapshotPhase::Stage || Snapshot.MIR.empty())
+ return tooling_test::fail("stage snapshots must contain complete canonical MIR");
+ }
+
+ auto PrintInput =
+ rcc::tooling::parseAndCanonicalizeMIR("print-points.mir", *Source, *Target, Invocation);
+ if (!PrintInput)
+ return tooling_test::fail(PrintInput.error().message());
+ rcc::tooling::PipelineControls PrintControls;
+ PrintControls.PrintBefore = {"Legalizer"};
+ PrintControls.PrintAfter = {"Legalizer#1"};
+ auto PrintPoints =
+ rcc::tooling::runRV64GCPipeline(PrintInput->Document, *Target, Invocation, PrintControls);
+ if (!PrintPoints || PrintPoints->Snapshots.size() != 2 ||
+ PrintPoints->Snapshots[0].Phase != rcc::tooling::SnapshotPhase::BeforePass ||
+ PrintPoints->Snapshots[1].Phase != rcc::tooling::SnapshotPhase::AfterPass) {
+ return tooling_test::fail("pass selectors must capture deterministic before/after snapshots");
+ }
+
+ auto StopInput =
+ rcc::tooling::parseAndCanonicalizeMIR("stop-after.mir", *Source, *Target, Invocation);
+ if (!StopInput)
+ return tooling_test::fail(StopInput.error().message());
+ rcc::tooling::PipelineControls StopControls;
+ StopControls.StopAfter = "Legalizer#1";
+ auto Stopped =
+ rcc::tooling::runRV64GCPipeline(StopInput->Document, *Target, Invocation, StopControls);
+ const rcc::MachineFunction *StoppedFunction = StopInput->Document.unit().functionAt(0);
+ if (!Stopped || StoppedFunction == nullptr ||
+ rcc::tooling::stageForFunction(*StoppedFunction) !=
+ rcc::tooling::BackendStage::LegalizedMIR ||
+ Stopped->ExecutedPasses.size() != 1) {
+ return tooling_test::fail("stop-after must stop at the selected pass occurrence");
+ }
+ StopControls.StopAfter = "Legalizer#0";
+ if (rcc::tooling::runRV64GCPipeline(StopInput->Document, *Target, Invocation, StopControls))
+ return tooling_test::fail("pipeline controls must reject occurrence zero");
+
+ rcc::tooling::BackendInvocation MixedPipeline = Invocation;
+ MixedPipeline.PassPipeline = {"default", "Legalizer"};
+ auto MixedInput =
+ rcc::tooling::parseAndCanonicalizeMIR("mixed-pipeline.mir", *Source, *Target, MixedPipeline);
+ if (!MixedInput)
+ return tooling_test::fail(MixedInput.error().message());
+ if (rcc::tooling::runRV64GCPipeline(MixedInput->Document, *Target, MixedPipeline))
+ return tooling_test::fail("named passes must not be mixed with the default pipeline marker");
+
+ if (!SelectedMIR)
+ return tooling_test::fail("selected MIR stage was not captured");
+ auto LaterInput =
+ rcc::tooling::parseAndCanonicalizeMIR("later-stage.mir", *SelectedMIR, *Target, Invocation);
+ if (!LaterInput)
+ return tooling_test::fail(LaterInput.error().message());
+ if (rcc::tooling::runBackend(std::move(*LaterInput), *Target, Invocation,
+ rcc::tooling::BackendOutputKind::PreLegalizerMIR)) {
+ return tooling_test::fail(
+ "backend must not reconstruct an earlier stage from a later snapshot");
+ }
+
+ auto RegBankRun = runInput("action-regbank.mir", *Source, *Target, Invocation,
+ rcc::tooling::BackendOutputKind::RegisterBankMIR);
+ if (!RegBankRun)
+ return tooling_test::fail(RegBankRun.error().message());
+ rcc::tooling::BackendInvocation InstructionSelectInvocation = Invocation;
+ InstructionSelectInvocation.PassPipeline = {"InstructionSelect"};
+ InstructionSelectInvocation.ActionFuel = 0;
+ auto RejectedInput = rcc::tooling::parseAndCanonicalizeMIR(
+ "action-rejected.mir", std::string(RegBankRun->text()), *Target, InstructionSelectInvocation);
+ if (!RejectedInput)
+ return tooling_test::fail(RejectedInput.error().message());
+ const std::string BeforeRejected = RejectedInput->Text;
+ if (rcc::tooling::runRV64GCPipeline(RejectedInput->Document, *Target,
+ InstructionSelectInvocation)) {
+ return tooling_test::fail("zero Action fuel must reject required instruction selection");
+ }
+ auto AfterRejected = printDocument(RejectedInput->Document, *Target, InstructionSelectInvocation);
+ if (!AfterRejected || *AfterRejected != BeforeRejected)
+ return tooling_test::fail("Action rejection must not leave a partially modified MachineIR");
+
+ InstructionSelectInvocation.ActionFuel.reset();
+ auto AcceptedFirst =
+ runInput("action-accepted-1.mir", std::string(RegBankRun->text()), *Target,
+ InstructionSelectInvocation, rcc::tooling::BackendOutputKind::SelectedMIR);
+ auto AcceptedSecond =
+ runInput("action-accepted-2.mir", std::string(RegBankRun->text()), *Target,
+ InstructionSelectInvocation, rcc::tooling::BackendOutputKind::SelectedMIR);
+ if (!AcceptedFirst || !AcceptedSecond || AcceptedFirst->Actions.empty() ||
+ AcceptedFirst->Actions != AcceptedSecond->Actions ||
+ AcceptedFirst->Output != AcceptedSecond->Output) {
+ return tooling_test::fail("Action replay and stable IDs must be deterministic");
+ }
+ for (const rcc::MachineActionEvent &Action : AcceptedFirst->Actions) {
+ if (!Action.Accepted || Action.ID.Function.value() == 0 || Action.ID.Instruction.value() == 0)
+ return tooling_test::fail("instruction selection Actions must use stable nonzero IDs");
+ }
+
+ auto MultiFunction = makeMultiFunctionInput();
+ if (!MultiFunction)
+ return tooling_test::fail(MultiFunction.error().message());
+ auto OneThread = runDeterminismCase(*MultiFunction, *Target, 1);
+ auto TwoThreads = runDeterminismCase(*MultiFunction, *Target, 2);
+ auto FourThreads = runDeterminismCase(*MultiFunction, *Target, 4);
+ if (!OneThread || !TwoThreads || !FourThreads)
+ return tooling_test::fail("parallel determinism pipeline failed");
+ if (OneThread->FinalMIR != TwoThreads->FinalMIR || OneThread->FinalMIR != FourThreads->FinalMIR ||
+ OneThread->Assembly != TwoThreads->Assembly || OneThread->Assembly != FourThreads->Assembly ||
+ OneThread->Object != TwoThreads->Object || OneThread->Object != FourThreads->Object ||
+ OneThread->Actions != TwoThreads->Actions || OneThread->Actions != FourThreads->Actions ||
+ OneThread->ExecutedPasses != TwoThreads->ExecutedPasses ||
+ OneThread->ExecutedPasses != FourThreads->ExecutedPasses) {
+ return tooling_test::fail("1/2/4-thread execution must produce stable output and event order");
+ }
+
+ return 0;
+}
diff --git a/backend/unittests/Tooling/ToolingTestSupport.h b/backend/unittests/Tooling/ToolingTestSupport.h
new file mode 100644
index 0000000..bff0ebb
--- /dev/null
+++ b/backend/unittests/Tooling/ToolingTestSupport.h
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// The private header has no stable include-root-relative guard name.
+// NOLINTNEXTLINE(llvm-header-guard)
+#ifndef RCC_UNITTESTS_TOOLING_TOOLINGTESTSUPPORT_H
+#define RCC_UNITTESTS_TOOLING_TOOLINGTESTSUPPORT_H
+
+#include "rcc/Support/Error.h"
+#include "rcc/Tooling/ArtifactStore.h"
+#include "rcc/Tooling/BackendInvocation.h"
+
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <span>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+
+#ifndef RCC_BACKEND_TOOLING_TEST_INPUT_DIR
+#error "RCC_BACKEND_TOOLING_TEST_INPUT_DIR must be defined"
+#endif
+
+namespace tooling_test {
+
+inline int fail(std::string_view Message) {
+ std::cerr << Message << '\n';
+ return 1;
+}
+
+[[nodiscard]] inline rcc::Expected<std::string> readText(const std::filesystem::path &Path) {
+ std::ifstream Input(Path, std::ios::binary);
+ if (!Input) {
+ return rcc::makeUnexpected(rcc::ErrorCode::IoError,
+ "cannot open test input '" + Path.string() + "'");
+ }
+ std::string Text((std::istreambuf_iterator<char>(Input)), std::istreambuf_iterator<char>());
+ if (Input.bad()) {
+ return rcc::makeUnexpected(rcc::ErrorCode::IoError,
+ "cannot read test input '" + Path.string() + "'");
+ }
+ return Text;
+}
+
+[[nodiscard]] inline rcc::Expected<void> writeText(const std::filesystem::path &Path,
+ std::string_view Text) {
+ std::ofstream Output(Path, std::ios::binary | std::ios::trunc);
+ if (!Output)
+ return rcc::makeUnexpected(rcc::ErrorCode::IoError, "cannot create test file");
+ Output.write(Text.data(), static_cast<std::streamsize>(Text.size()));
+ Output.close();
+ if (!Output)
+ return rcc::makeUnexpected(rcc::ErrorCode::IoError, "cannot write test file");
+ return {};
+}
+
+[[nodiscard]] inline rcc::Expected<void> writeBytes(const std::filesystem::path &Path,
+ std::span<const std::uint8_t> Bytes) {
+ if (Bytes.empty())
+ return writeText(Path, {});
+ const auto *Data = reinterpret_cast<const char *>(Bytes.data());
+ return writeText(Path, std::string_view(Data, Bytes.size()));
+}
+
+[[nodiscard]] inline rcc::Expected<std::string> readInput(std::string_view Name) {
+ return readText(std::filesystem::path(RCC_BACKEND_TOOLING_TEST_INPUT_DIR) / Name);
+}
+
+[[nodiscard]] inline rcc::Expected<rcc::tooling::CanonicalMIR>
+loadCanonicalInput(std::string_view Name, const rcc::TargetDescription &Target,
+ const rcc::tooling::BackendInvocation &Invocation) {
+ auto Source = readInput(Name);
+ if (!Source)
+ return std::unexpected(Source.error());
+ return rcc::tooling::parseAndCanonicalizeMIR(std::string(Name), std::move(*Source), Target,
+ Invocation);
+}
+
+class TemporaryDirectory final {
+public:
+ TemporaryDirectory(const TemporaryDirectory &) = delete;
+ TemporaryDirectory &operator=(const TemporaryDirectory &) = delete;
+
+ TemporaryDirectory(TemporaryDirectory &&Other) noexcept : Path(std::move(Other.Path)) {
+ Other.Path.clear();
+ }
+ TemporaryDirectory &operator=(TemporaryDirectory &&) = delete;
+
+ ~TemporaryDirectory() {
+ if (Path.empty())
+ return;
+ std::error_code Error;
+ std::filesystem::remove_all(Path, Error);
+ }
+
+ [[nodiscard]] static rcc::Expected<TemporaryDirectory> create(std::string_view Prefix) {
+ std::error_code Error;
+ const std::filesystem::path Root = std::filesystem::temp_directory_path(Error);
+ if (Error)
+ return rcc::makeUnexpected(rcc::ErrorCode::IoError, "cannot locate test temporary directory");
+ const auto Stamp = std::chrono::steady_clock::now().time_since_epoch().count();
+ for (std::size_t Attempt = 0; Attempt < 1024; ++Attempt) {
+ const std::filesystem::path Candidate =
+ Root /
+ (std::string(Prefix) + "-" + std::to_string(Stamp) + "-" + std::to_string(Attempt));
+ Error.clear();
+ if (std::filesystem::create_directory(Candidate, Error))
+ return TemporaryDirectory(Candidate);
+ }
+ return rcc::makeUnexpected(rcc::ErrorCode::IoError,
+ "cannot create a unique test temporary directory");
+ }
+
+ [[nodiscard]] const std::filesystem::path &path() const noexcept {
+ return Path;
+ }
+
+private:
+ explicit TemporaryDirectory(std::filesystem::path Path) : Path(std::move(Path)) {}
+
+ std::filesystem::path Path;
+};
+
+} // namespace tooling_test
+
+#endif // RCC_UNITTESTS_TOOLING_TOOLINGTESTSUPPORT_H