| //===- 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" |
| |
| 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<AllAnalysesType>()); } |
| |
| /// Returns true if all analyses were marked preserved. |
| bool isAll() const { |
| return preservedIDs.count(TypeID::get<AllAnalysesType>()); |
| } |
| |
| /// Returns true if no analyses were marked preserved. |
| bool isNone() const { return preservedIDs.empty(); } |
| |
| /// Preserve the given analyses. |
| template <typename AnalysisT> void preserve() { |
| preserve(TypeID::get<AnalysisT>()); |
| } |
| template <typename AnalysisT, typename AnalysisT2, typename... OtherAnalysesT> |
| void preserve() { |
| preserve<AnalysisT>(); |
| preserve<AnalysisT2, OtherAnalysesT...>(); |
| } |
| 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 <typename AnalysisT> bool isPreserved() const { |
| return isPreserved(TypeID::get<AnalysisT>()); |
| } |
| bool isPreserved(TypeID id) const { return preservedIDs.count(id); } |
| |
| private: |
| /// Remove the analysis from preserved set. |
| template <typename AnalysisT> |
| void unpreserve() { |
| preservedIDs.erase(TypeID::get<AnalysisT>()); |
| } |
| |
| /// AnalysisModel need access to unpreserve(). |
| template <typename> |
| friend struct AnalysisModel; |
| |
| /// The set of analyses that are known to be preserved. |
| SmallPtrSet<TypeID, 2> preservedIDs; |
| }; |
| |
| namespace analysis_impl { |
| /// Trait to check if T provides a static 'isInvalidated' method. |
| template <typename T, typename... Args> |
| using has_is_invalidated = decltype(std::declval<T &>().isInvalidated( |
| std::declval<const PreservedAnalyses &>())); |
| |
| /// Implementation of 'isInvalidated' if the analysis provides a definition. |
| template <typename AnalysisT> |
| std::enable_if_t<llvm::is_detected<has_is_invalidated, AnalysisT>::value, bool> |
| isInvalidated(AnalysisT &analysis, const PreservedAnalyses &pa) { |
| return analysis.isInvalidated(pa); |
| } |
| /// Default implementation of 'isInvalidated'. |
| template <typename AnalysisT> |
| std::enable_if_t<!llvm::is_detected<has_is_invalidated, AnalysisT>::value, bool> |
| isInvalidated(AnalysisT &analysis, const PreservedAnalyses &pa) { |
| return !pa.isPreserved<AnalysisT>(); |
| } |
| } // end 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 <typename AnalysisT> struct AnalysisModel : public AnalysisConcept { |
| template <typename... Args> |
| explicit AnalysisModel(Args &&...args) |
| : analysis(std::forward<Args>(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<AnalysisT>(); |
| 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<TypeID, std::unique_ptr<AnalysisConcept>>; |
| |
| /// Utility to return the name of the given analysis class. |
| template <typename AnalysisT> static StringRef getAnalysisName() { |
| StringRef name = llvm::getTypeName<AnalysisT>(); |
| 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 <typename AnalysisT> |
| AnalysisT &getAnalysis(PassInstrumentor *pi, AnalysisManager &am) { |
| return getAnalysisImpl<AnalysisT, Operation *>(pi, ir, am); |
| } |
| |
| /// Get an analysis for the current IR unit assuming it's of specific derived |
| /// operation type. |
| template <typename AnalysisT, typename OpT> |
| std::enable_if_t< |
| std::is_constructible<AnalysisT, OpT>::value || |
| std::is_constructible<AnalysisT, OpT, AnalysisManager &>::value, |
| AnalysisT &> |
| getAnalysis(PassInstrumentor *pi, AnalysisManager &am) { |
| return getAnalysisImpl<AnalysisT, OpT>(pi, cast<OpT>(ir), am); |
| } |
| |
| /// Get a cached analysis instance if one exists, otherwise return null. |
| template <typename AnalysisT> |
| Optional<std::reference_wrapper<AnalysisT>> getCachedAnalysis() const { |
| auto res = analyses.find(TypeID::get<AnalysisT>()); |
| if (res == analyses.end()) |
| return llvm::None; |
| return {static_cast<AnalysisModel<AnalysisT> &>(*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 <typename AnalysisT, typename OpT> |
| AnalysisT &getAnalysisImpl(PassInstrumentor *pi, OpT op, |
| AnalysisManager &am) { |
| TypeID id = TypeID::get<AnalysisT>(); |
| |
| 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<AnalysisT>(), id, ir); |
| |
| bool wasInserted; |
| std::tie(it, wasInserted) = |
| analyses.insert({id, constructAnalysis<AnalysisT>(am, op)}); |
| assert(wasInserted); |
| |
| if (pi) |
| pi->runAfterAnalysis(getAnalysisName<AnalysisT>(), id, ir); |
| } |
| return static_cast<AnalysisModel<AnalysisT> &>(*it->second).analysis; |
| } |
| |
| /// Construct analysis using two arguments constructor (OpT, AnalysisManager) |
| template <typename AnalysisT, typename OpT, |
| std::enable_if_t<std::is_constructible< |
| AnalysisT, OpT, AnalysisManager &>::value> * = nullptr> |
| static auto constructAnalysis(AnalysisManager &am, OpT op) { |
| return std::make_unique<AnalysisModel<AnalysisT>>(op, am); |
| } |
| |
| /// Construct analysis using single argument constructor (OpT) |
| template <typename AnalysisT, typename OpT, |
| std::enable_if_t<!std::is_constructible< |
| AnalysisT, OpT, AnalysisManager &>::value> * = nullptr> |
| static auto constructAnalysis(AnalysisManager &, OpT op) { |
| return std::make_unique<AnalysisModel<AnalysisT>>(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 parentOrInstrumentor.dyn_cast<NestedAnalysisMap *>(); |
| } |
| |
| /// 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 parentOrInstrumentor.get<PassInstrumentor *>(); |
| } |
| |
| /// The cached analyses for nested operations. |
| DenseMap<Operation *, std::unique_ptr<NestedAnalysisMap>> 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<NestedAnalysisMap *, PassInstrumentor *> 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<const ModuleAnalysisManager *, const AnalysisManager *>; |
| |
| 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 <typename AnalysisT> |
| Optional<std::reference_wrapper<AnalysisT>> |
| getCachedParentAnalysis(Operation *parentOp) const { |
| const detail::NestedAnalysisMap *curParent = impl; |
| while (auto *parentAM = curParent->getParent()) { |
| if (parentAM->getOperation() == parentOp) |
| return parentAM->analyses.getCachedAnalysis<AnalysisT>(); |
| curParent = parentAM; |
| } |
| return None; |
| } |
| |
| /// Query for the given analysis for the current operation. |
| template <typename AnalysisT> AnalysisT &getAnalysis() { |
| return impl->analyses.getAnalysis<AnalysisT>(getPassInstrumentor(), *this); |
| } |
| |
| /// Query for the given analysis for the current operation of a specific |
| /// derived operation type. |
| template <typename AnalysisT, typename OpT> |
| AnalysisT &getAnalysis() { |
| return impl->analyses.getAnalysis<AnalysisT, OpT>(getPassInstrumentor(), |
| *this); |
| } |
| |
| /// Query for a cached entry of the given analysis on the current operation. |
| template <typename AnalysisT> |
| Optional<std::reference_wrapper<AnalysisT>> getCachedAnalysis() const { |
| return impl->analyses.getCachedAnalysis<AnalysisT>(); |
| } |
| |
| /// Query for an analysis of a child operation, constructing it if necessary. |
| template <typename AnalysisT> AnalysisT &getChildAnalysis(Operation *op) { |
| return nest(op).template getAnalysis<AnalysisT>(); |
| } |
| |
| /// Query for an analysis of a child operation of a specific derived operation |
| /// type, constructing it if necessary. |
| template <typename AnalysisT, typename OpT> |
| AnalysisT &getChildAnalysis(OpT child) { |
| return nest(child).template getAnalysis<AnalysisT, OpT>(); |
| } |
| |
| /// Query for a cached analysis of a child operation, or return null. |
| template <typename AnalysisT> |
| Optional<std::reference_wrapper<AnalysisT>> |
| getCachedChildAnalysis(Operation *op) const { |
| assert(op->getParentOp() == impl->getOperation()); |
| auto it = impl->childAnalyses.find(op); |
| if (it == impl->childAnalyses.end()) |
| return llvm::None; |
| return it->second->analyses.getCachedAnalysis<AnalysisT>(); |
| } |
| |
| /// 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; |
| }; |
| |
| } // end namespace mlir |
| |
| #endif // MLIR_PASS_ANALYSISMANAGER_H |