//===- AnalysisManager.h - Analysis Management Infrastructure ---*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef MLIR_PASS_ANALYSISMANAGER_H #define MLIR_PASS_ANALYSISMANAGER_H #include "mlir/IR/Operation.h" #include "mlir/Pass/PassInstrumentation.h" #include "mlir/Support/LLVM.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/Support/TypeName.h" #include namespace mlir { class AnalysisManager; //===----------------------------------------------------------------------===// // Analysis Preservation and Concept Modeling //===----------------------------------------------------------------------===// namespace detail { /// A utility class to represent the analyses that are known to be preserved. class PreservedAnalyses { /// A type used to represent all potential analyses. struct AllAnalysesType {}; public: /// Mark all analyses as preserved. void preserveAll() { preservedIDs.insert(TypeID::get()); } /// Returns true if all analyses were marked preserved. bool isAll() const { return preservedIDs.count(TypeID::get()); } /// Returns true if no analyses were marked preserved. bool isNone() const { return preservedIDs.empty(); } /// Preserve the given analyses. template void preserve() { preserve(TypeID::get()); } template void preserve() { preserve(); preserve(); } void preserve(TypeID id) { preservedIDs.insert(id); } /// Returns true if the given analysis has been marked as preserved. Note that /// this simply checks for the presence of a given analysis ID and should not /// be used as a general preservation checker. template bool isPreserved() const { return isPreserved(TypeID::get()); } bool isPreserved(TypeID id) const { return preservedIDs.count(id); } private: /// Remove the analysis from preserved set. template void unpreserve() { preservedIDs.erase(TypeID::get()); } /// AnalysisModel need access to unpreserve(). template friend struct AnalysisModel; /// The set of analyses that are known to be preserved. SmallPtrSet preservedIDs; }; namespace analysis_impl { /// Trait to check if T provides a static 'isInvalidated' method. template using has_is_invalidated = decltype(std::declval().isInvalidated( std::declval())); template bool isInvalidated(AnalysisT &analysis, const PreservedAnalyses &pa) { if constexpr (llvm::is_detected::value) /// Implementation of 'isInvalidated' if the analysis provides a definition. return analysis.isInvalidated(pa); else /// Default implementation of 'isInvalidated'. return !pa.isPreserved(); } } // namespace analysis_impl /// The abstract polymorphic base class representing an analysis. struct AnalysisConcept { virtual ~AnalysisConcept() = default; /// A hook used to query analyses for invalidation. Given a preserved analysis /// set, returns true if it should truly be invalidated. This allows for more /// fine-tuned invalidation in cases where an analysis wasn't explicitly /// marked preserved, but may be preserved(or invalidated) based upon other /// properties such as analyses sets. Invalidated analyses must also be /// removed from pa. virtual bool invalidate(PreservedAnalyses &pa) = 0; }; /// A derived analysis model used to hold a specific analysis object. template struct AnalysisModel : public AnalysisConcept { template explicit AnalysisModel(Args &&...args) : analysis(std::forward(args)...) {} /// A hook used to query analyses for invalidation. Removes invalidated /// analyses from pa. bool invalidate(PreservedAnalyses &pa) final { bool result = analysis_impl::isInvalidated(analysis, pa); if (result) pa.unpreserve(); return result; } /// The actual analysis object. AnalysisT analysis; }; /// This class represents a cache of analyses for a single operation. All /// computation, caching, and invalidation of analyses takes place here. class AnalysisMap { /// A mapping between an analysis id and an existing analysis instance. using ConceptMap = llvm::MapVector>; /// Utility to return the name of the given analysis class. template static StringRef getAnalysisName() { StringRef name = llvm::getTypeName(); if (!name.consume_front("mlir::")) name.consume_front("(anonymous namespace)::"); return name; } public: explicit AnalysisMap(Operation *ir) : ir(ir) {} /// Get an analysis for the current IR unit, computing it if necessary. template AnalysisT &getAnalysis(PassInstrumentor *pi, AnalysisManager &am) { return getAnalysisImpl(pi, ir, am); } /// Get an analysis for the current IR unit assuming it's of specific derived /// operation type. template std::enable_if_t< std::is_constructible::value || std::is_constructible::value, AnalysisT &> getAnalysis(PassInstrumentor *pi, AnalysisManager &am) { return getAnalysisImpl(pi, cast(ir), am); } /// Get a cached analysis instance if one exists, otherwise return null. template std::optional> getCachedAnalysis() const { auto res = analyses.find(TypeID::get()); if (res == analyses.end()) return std::nullopt; return {static_cast &>(*res->second).analysis}; } /// Returns the operation that this analysis map represents. Operation *getOperation() const { return ir; } /// Clear any held analyses. void clear() { analyses.clear(); } /// Invalidate any cached analyses based upon the given set of preserved /// analyses. void invalidate(const PreservedAnalyses &pa) { PreservedAnalyses paCopy(pa); // Remove any analyses that were invalidated. // As we are using MapVector, order of insertion is preserved and // dependencies always go before users, so we need only one iteration. analyses.remove_if( [&](auto &val) { return val.second->invalidate(paCopy); }); } private: template AnalysisT &getAnalysisImpl(PassInstrumentor *pi, OpT op, AnalysisManager &am) { TypeID id = TypeID::get(); auto it = analyses.find(id); // If we don't have a cached analysis for this operation, compute it // directly and add it to the cache. if (analyses.end() == it) { if (pi) pi->runBeforeAnalysis(getAnalysisName(), id, ir); bool wasInserted; std::tie(it, wasInserted) = analyses.insert({id, constructAnalysis(am, op)}); assert(wasInserted); if (pi) pi->runAfterAnalysis(getAnalysisName(), id, ir); } return static_cast &>(*it->second).analysis; } /// Construct analysis using two arguments constructor (OpT, AnalysisManager) template ::value> * = nullptr> static auto constructAnalysis(AnalysisManager &am, OpT op) { return std::make_unique>(op, am); } /// Construct analysis using single argument constructor (OpT) template ::value> * = nullptr> static auto constructAnalysis(AnalysisManager &, OpT op) { return std::make_unique>(op); } Operation *ir; ConceptMap analyses; }; /// An analysis map that contains a map for the current operation, and a set of /// maps for any child operations. struct NestedAnalysisMap { NestedAnalysisMap(Operation *op, PassInstrumentor *instrumentor) : analyses(op), parentOrInstrumentor(instrumentor) {} NestedAnalysisMap(Operation *op, NestedAnalysisMap *parent) : analyses(op), parentOrInstrumentor(parent) {} /// Get the operation for this analysis map. Operation *getOperation() const { return analyses.getOperation(); } /// Invalidate any non preserved analyses. void invalidate(const PreservedAnalyses &pa); /// Returns the parent analysis map for this analysis map, or null if this is /// the top-level map. const NestedAnalysisMap *getParent() const { return llvm::dyn_cast_if_present(parentOrInstrumentor); } /// Returns a pass instrumentation object for the current operation. This /// value may be null. PassInstrumentor *getPassInstrumentor() const { if (auto *parent = getParent()) return parent->getPassInstrumentor(); return cast(parentOrInstrumentor); } /// The cached analyses for nested operations. DenseMap> childAnalyses; /// The analyses for the owning operation. detail::AnalysisMap analyses; /// This value has three possible states: /// NestedAnalysisMap*: A pointer to the parent analysis map. /// PassInstrumentor*: This analysis map is the top-level map, and this /// pointer is the optional pass instrumentor for the /// current compilation. /// nullptr: This analysis map is the top-level map, and there is nop pass /// instrumentor. PointerUnion parentOrInstrumentor; }; } // namespace detail //===----------------------------------------------------------------------===// // Analysis Management //===----------------------------------------------------------------------===// class ModuleAnalysisManager; /// This class represents an analysis manager for a particular operation /// instance. It is used to manage and cache analyses on the operation as well /// as those for child operations, via nested AnalysisManager instances /// accessible via 'slice'. This class is intended to be passed around by value, /// and cannot be constructed directly. class AnalysisManager { using ParentPointerT = PointerUnion; public: using PreservedAnalyses = detail::PreservedAnalyses; /// Query for a cached analysis on the given parent operation. The analysis /// may not exist and if it does it may be out-of-date. template std::optional> getCachedParentAnalysis(Operation *parentOp) const { const detail::NestedAnalysisMap *curParent = impl; while (auto *parentAM = curParent->getParent()) { if (parentAM->getOperation() == parentOp) return parentAM->analyses.getCachedAnalysis(); curParent = parentAM; } return std::nullopt; } /// Query for the given analysis for the current operation. template AnalysisT &getAnalysis() { return impl->analyses.getAnalysis(getPassInstrumentor(), *this); } /// Query for the given analysis for the current operation of a specific /// derived operation type. template AnalysisT &getAnalysis() { return impl->analyses.getAnalysis(getPassInstrumentor(), *this); } /// Query for a cached entry of the given analysis on the current operation. template std::optional> getCachedAnalysis() const { return impl->analyses.getCachedAnalysis(); } /// Query for an analysis of a child operation, constructing it if necessary. template AnalysisT &getChildAnalysis(Operation *op) { return nest(op).template getAnalysis(); } /// Query for an analysis of a child operation of a specific derived operation /// type, constructing it if necessary. template AnalysisT &getChildAnalysis(OpT child) { return nest(child).template getAnalysis(); } /// Query for a cached analysis of a child operation, or return null. template std::optional> getCachedChildAnalysis(Operation *op) const { assert(op->getParentOp() == impl->getOperation()); auto it = impl->childAnalyses.find(op); if (it == impl->childAnalyses.end()) return std::nullopt; return it->second->analyses.getCachedAnalysis(); } /// Get an analysis manager for the given operation, which must be a proper /// descendant of the current operation represented by this analysis manager. AnalysisManager nest(Operation *op); /// Invalidate any non preserved analyses, void invalidate(const PreservedAnalyses &pa) { impl->invalidate(pa); } /// Clear any held analyses. void clear() { impl->analyses.clear(); impl->childAnalyses.clear(); } /// Returns a pass instrumentation object for the current operation. This /// value may be null. PassInstrumentor *getPassInstrumentor() const { return impl->getPassInstrumentor(); } private: AnalysisManager(detail::NestedAnalysisMap *impl) : impl(impl) {} /// Get an analysis manager for the given immediately nested child operation. AnalysisManager nestImmediate(Operation *op); /// A reference to the impl analysis map within the parent analysis manager. detail::NestedAnalysisMap *impl; /// Allow access to the constructor. friend class ModuleAnalysisManager; }; /// An analysis manager class specifically for the top-level operation. This /// class contains the memory allocations for all nested analysis managers, and /// provides an anchor point. This is necessary because AnalysisManager is /// designed to be a thin wrapper around an existing analysis map instance. class ModuleAnalysisManager { public: ModuleAnalysisManager(Operation *op, PassInstrumentor *passInstrumentor) : analyses(op, passInstrumentor) {} ModuleAnalysisManager(const ModuleAnalysisManager &) = delete; ModuleAnalysisManager &operator=(const ModuleAnalysisManager &) = delete; /// Returns an analysis manager for the current top-level module. operator AnalysisManager() { return AnalysisManager(&analyses); } private: /// The analyses for the owning module. detail::NestedAnalysisMap analyses; }; } // namespace mlir #endif // MLIR_PASS_ANALYSISMANAGER_H