From 236082a0fe4dcd0362c5c74923a93e3db6a01291 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Thu, 13 Aug 2026 05:45:37 +0800 Subject: [CodeGen] Align pass manager ownership --- backend/unittests/CodeGen/PassManagerTest.cpp | 260 ++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 backend/unittests/CodeGen/PassManagerTest.cpp (limited to 'backend/unittests/CodeGen/PassManagerTest.cpp') diff --git a/backend/unittests/CodeGen/PassManagerTest.cpp b/backend/unittests/CodeGen/PassManagerTest.cpp new file mode 100644 index 0000000..56c30f3 --- /dev/null +++ b/backend/unittests/CodeGen/PassManagerTest.cpp @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "Target/TestTarget.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineFunctionAnalysisManager.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +class TestContext final { +public: + void expect(bool Condition, std::string_view Message) { + if (Condition) + return; + std::cerr << Message << '\n'; + ++Failures; + } + + [[nodiscard]] int result() const noexcept { + return Failures == 0 ? 0 : 1; + } + +private: + int Failures = 0; +}; + +[[nodiscard]] rcc::Expected createTarget() { + std::vector Descriptors; + std::vector Predicates; + auto Appended = rcc::appendGenericInstructionInfo(Descriptors, Predicates); + if (!Appended) + return std::unexpected(Appended.error()); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors), std::move(Predicates)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::test::createTestTargetDescription(std::move(*Instructions)); +} + +class BlockCountAnalysis final { +public: + struct Result final { + std::size_t Count = 0; + }; + + [[nodiscard]] static constexpr std::string_view name() noexcept { + return "BlockCount"; + } + + [[nodiscard]] static rcc::Expected run(rcc::MachineFunction &Function, + const rcc::TargetDescription &, + rcc::MachineFunctionAnalysisManager &) { + ++Runs; + return Result{Function.blockCount()}; + } + + static inline std::atomic Runs = 0; +}; + +class DependentAnalysis final { +public: + struct Result final { + std::size_t Count = 0; + }; + + [[nodiscard]] static constexpr std::string_view name() noexcept { + return "Dependent"; + } + + [[nodiscard]] static rcc::Expected run(rcc::MachineFunction &Function, + const rcc::TargetDescription &Target, + rcc::MachineFunctionAnalysisManager &Analyses) { + auto Blocks = Analyses.getResult(Function, Target); + if (!Blocks) + return std::unexpected(Blocks.error()); + return Result{(*Blocks)->Count}; + } +}; + +class StickyAnalysis final { +public: + struct Result final { + [[nodiscard]] bool invalidate(const rcc::MachineFunction &, + const rcc::PreservedAnalyses &) noexcept { + return false; + } + }; + + [[nodiscard]] static constexpr std::string_view name() noexcept { + return "Sticky"; + } + + [[nodiscard]] static rcc::Expected run(rcc::MachineFunction &, + const rcc::TargetDescription &, + rcc::MachineFunctionAnalysisManager &) { + return Result{}; + } +}; + +class ExpiringAnalysis final { +public: + struct Result final { + [[nodiscard]] bool invalidate(const rcc::MachineFunction &, + const rcc::PreservedAnalyses &) noexcept { + return true; + } + }; + + [[nodiscard]] static constexpr std::string_view name() noexcept { + return "Expiring"; + } + + [[nodiscard]] static rcc::Expected run(rcc::MachineFunction &, + const rcc::TargetDescription &, + rcc::MachineFunctionAnalysisManager &) { + return Result{}; + } +}; + +class CycleA final { +public: + struct Result final {}; + + [[nodiscard]] static constexpr std::string_view name() noexcept { + return "CycleA"; + } + + [[nodiscard]] static rcc::Expected run(rcc::MachineFunction &, + const rcc::TargetDescription &, + rcc::MachineFunctionAnalysisManager &) { + return Result{}; + } +}; + +class CycleB final { +public: + struct Result final {}; + + [[nodiscard]] static constexpr std::string_view name() noexcept { + return "CycleB"; + } + + [[nodiscard]] static rcc::Expected run(rcc::MachineFunction &, + const rcc::TargetDescription &, + rcc::MachineFunctionAnalysisManager &) { + return Result{}; + } +}; + +void testAnalysisManager(TestContext &Context, const rcc::TargetDescription &Target) { + auto Function = + rcc::MachineFunction::create("analysis", rcc::MachineFunctionStableID::fromValue(1)); + Context.expect(Function.has_value(), "analysis function creation must succeed"); + if (!Function) + return; + + rcc::MachineFunctionAnalysisManager Analyses; + Context.expect(Analyses + .registerAnalysis({ + .Name = "BlockCount", + .Dependencies = {}, + .InvalidatedBy = {rcc::MachinePassChange::CFG}, + }) + .has_value(), + "base analysis registration must succeed"); + Context.expect(Analyses + .registerAnalysis({ + .Name = "Dependent", + .Dependencies = + { + rcc::MachineFunctionAnalysisManager::id(), + }, + .InvalidatedBy = {rcc::MachinePassChange::Operands}, + }) + .has_value(), + "dependent analysis registration must succeed"); + + BlockCountAnalysis::Runs = 0; + auto First = Analyses.getResult(**Function, Target); + auto Second = Analyses.getResult(**Function, Target); + Context.expect(First && Second && *First == *Second && BlockCountAnalysis::Runs == 1, + "analysis dependencies must materialize once and then use the cache"); + + Analyses.invalidate(**Function, rcc::PreservedAnalyses::none(), + {rcc::MachinePassChange::Registers}); + Context.expect(Analyses.cachedResultCount(**Function) == 2, + "unrelated changes must preserve cached analyses precisely"); + Analyses.invalidate(**Function, rcc::PreservedAnalyses::none(), {rcc::MachinePassChange::CFG}); + Context.expect(Analyses.cachedResultCount(**Function) == 0, + "invalidating a dependency must invalidate its dependent analysis"); + + rcc::MachineFunctionAnalysisManager Cyclic; + Context.expect( + Cyclic.registerAnalysis({ + .Name = "CycleA", + .Dependencies = + { + rcc::MachineFunctionAnalysisManager::id(), + }, + }) + .has_value() && + Cyclic + .registerAnalysis({ + .Name = "CycleB", + .Dependencies = + { + rcc::MachineFunctionAnalysisManager::id(), + }, + }) + .has_value(), + "cyclic analysis registrations must be accepted until requested"); + Context.expect(!Cyclic.getResult(**Function, Target), + "analysis dependency cycles must produce a structured error"); + + rcc::MachineFunctionAnalysisManager CustomInvalidation; + Context.expect(CustomInvalidation + .registerAnalysis({ + .Name = "Sticky", + .Dependencies = {}, + .InvalidatedBy = {rcc::MachinePassChange::CFG}, + }) + .has_value() && + CustomInvalidation + .registerAnalysis({ + .Name = "Expiring", + .Dependencies = {}, + .InvalidatedBy = {rcc::MachinePassChange::CFG}, + }) + .has_value() && + CustomInvalidation.getResult(**Function, Target) && + CustomInvalidation.getResult(**Function, Target), + "custom invalidation analyses must materialize"); + CustomInvalidation.invalidate(**Function, rcc::PreservedAnalyses::none(), + {rcc::MachinePassChange::CFG}); + Context.expect(CustomInvalidation.hasCached( + **Function, rcc::MachineFunctionAnalysisManager::id()) && + !CustomInvalidation.hasCached( + **Function, rcc::MachineFunctionAnalysisManager::id()), + "analysis invalidate() must return false to preserve and true to invalidate"); +} + +} // namespace + +int main() { + TestContext Context; + auto Target = createTarget(); + Context.expect(Target.has_value(), "pass manager tests require a valid target description"); + if (!Target) + return Context.result(); + + testAnalysisManager(Context, *Target); + return Context.result(); +} -- cgit v1.2.3