diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-10 20:36:50 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-10 20:36:50 +0800 |
| commit | adcf8c9b1bd1b47e00bf859693112cea1f7e8c9f (patch) | |
| tree | 59067aa71befab31678946dbf5e19f57131dd6f2 /backend | |
| parent | 3a57627e59ba18b6f9f9aae7bb60c390c0d3575a (diff) | |
| download | rcc-project-adcf8c9b1bd1b47e00bf859693112cea1f7e8c9f.tar.gz rcc-project-adcf8c9b1bd1b47e00bf859693112cea1f7e8c9f.tar.xz | |
[Tooling] Complete backend tooling and validation
Diffstat (limited to 'backend')
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 |
