| //===- DataFlowAnalysis.h - General DataFlow Analysis Utilities -*- 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file has several utilities and algorithms that perform abstract dataflow |
| // analysis over the IR. These allow for users to hook into various analysis |
| // propagation algorithms without needing to reinvent the traversal over the |
| // different types of control structures present within MLIR, such as regions, |
| // the callgraph, etc. A few of the main entry points are detailed below: |
| // |
| // FowardDataFlowAnalysis: |
| // This class provides support for defining dataflow algorithms that are |
| // forward, sparse, pessimistic (except along unreached backedges) and |
| // context-insensitive for the interprocedural aspects. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef MLIR_ANALYSIS_DATAFLOWANALYSIS_H |
| #define MLIR_ANALYSIS_DATAFLOWANALYSIS_H |
| |
| #include "mlir/IR/Value.h" |
| #include "mlir/Interfaces/ControlFlowInterfaces.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/Support/Allocator.h" |
| |
| namespace mlir { |
| //===----------------------------------------------------------------------===// |
| // ChangeResult |
| //===----------------------------------------------------------------------===// |
| |
| /// A result type used to indicate if a change happened. Boolean operations on |
| /// ChangeResult behave as though `Change` is truthy. |
| enum class ChangeResult { |
| NoChange, |
| Change, |
| }; |
| inline ChangeResult operator|(ChangeResult lhs, ChangeResult rhs) { |
| return lhs == ChangeResult::Change ? lhs : rhs; |
| } |
| inline ChangeResult &operator|=(ChangeResult &lhs, ChangeResult rhs) { |
| lhs = lhs | rhs; |
| return lhs; |
| } |
| inline ChangeResult operator&(ChangeResult lhs, ChangeResult rhs) { |
| return lhs == ChangeResult::NoChange ? lhs : rhs; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // AbstractLatticeElement |
| //===----------------------------------------------------------------------===// |
| |
| namespace detail { |
| /// This class represents an abstract lattice. A lattice is what gets propagated |
| /// across the IR, and contains the information for a specific Value. |
| class AbstractLatticeElement { |
| public: |
| virtual ~AbstractLatticeElement(); |
| |
| /// Returns true if the value of this lattice is uninitialized, meaning that |
| /// it hasn't yet been initialized. |
| virtual bool isUninitialized() const = 0; |
| |
| /// Join the information contained in 'rhs' into this lattice. Returns |
| /// if the value of the lattice changed. |
| virtual ChangeResult join(const AbstractLatticeElement &rhs) = 0; |
| |
| /// Mark the lattice element as having reached a pessimistic fixpoint. This |
| /// means that the lattice may potentially have conflicting value states, and |
| /// only the most conservative value should be relied on. |
| virtual ChangeResult markPessimisticFixpoint() = 0; |
| |
| /// Mark the lattice element as having reached an optimistic fixpoint. This |
| /// means that we optimistically assume the current value is the true state. |
| virtual void markOptimisticFixpoint() = 0; |
| |
| /// Returns true if the lattice has reached a fixpoint. A fixpoint is when the |
| /// information optimistically assumed to be true is the same as the |
| /// information known to be true. |
| virtual bool isAtFixpoint() const = 0; |
| }; |
| } // namespace detail |
| |
| //===----------------------------------------------------------------------===// |
| // LatticeElement |
| //===----------------------------------------------------------------------===// |
| |
| /// This class represents a lattice holding a specific value of type `ValueT`. |
| /// Lattice values (`ValueT`) are required to adhere to the following: |
| /// * static ValueT join(const ValueT &lhs, const ValueT &rhs); |
| /// - This method conservatively joins the information held by `lhs` |
| /// and `rhs` into a new value. This method is required to be monotonic. |
| /// * static ValueT getPessimisticValueState(MLIRContext *context); |
| /// - This method computes a pessimistic/conservative value state assuming |
| /// no information about the state of the IR. |
| /// * static ValueT getPessimisticValueState(Value value); |
| /// - This method computes a pessimistic/conservative value state for |
| /// `value` assuming only information present in the current IR. |
| /// * bool operator==(const ValueT &rhs) const; |
| /// |
| template <typename ValueT> |
| class LatticeElement final : public detail::AbstractLatticeElement { |
| public: |
| LatticeElement() = delete; |
| LatticeElement(const ValueT &knownValue) : knownValue(knownValue) {} |
| |
| /// Return the value held by this lattice. This requires that the value is |
| /// initialized. |
| ValueT &getValue() { |
| assert(!isUninitialized() && "expected known lattice element"); |
| return *optimisticValue; |
| } |
| const ValueT &getValue() const { |
| assert(!isUninitialized() && "expected known lattice element"); |
| return *optimisticValue; |
| } |
| |
| /// Returns true if the value of this lattice hasn't yet been initialized. |
| bool isUninitialized() const final { return !optimisticValue.hasValue(); } |
| |
| /// Join the information contained in the 'rhs' lattice into this |
| /// lattice. Returns if the state of the current lattice changed. |
| ChangeResult join(const detail::AbstractLatticeElement &rhs) final { |
| const LatticeElement<ValueT> &rhsLattice = |
| static_cast<const LatticeElement<ValueT> &>(rhs); |
| |
| // If we are at a fixpoint, or rhs is uninitialized, there is nothing to do. |
| if (isAtFixpoint() || rhsLattice.isUninitialized()) |
| return ChangeResult::NoChange; |
| |
| // Join the rhs value into this lattice. |
| return join(rhsLattice.getValue()); |
| } |
| |
| /// Join the information contained in the 'rhs' value into this |
| /// lattice. Returns if the state of the current lattice changed. |
| ChangeResult join(const ValueT &rhs) { |
| // If the current lattice is uninitialized, copy the rhs value. |
| if (isUninitialized()) { |
| optimisticValue = rhs; |
| return ChangeResult::Change; |
| } |
| |
| // Otherwise, join rhs with the current optimistic value. |
| ValueT newValue = ValueT::join(*optimisticValue, rhs); |
| assert(ValueT::join(newValue, *optimisticValue) == newValue && |
| "expected `join` to be monotonic"); |
| assert(ValueT::join(newValue, rhs) == newValue && |
| "expected `join` to be monotonic"); |
| |
| // Update the current optimistic value if something changed. |
| if (newValue == optimisticValue) |
| return ChangeResult::NoChange; |
| |
| optimisticValue = newValue; |
| return ChangeResult::Change; |
| } |
| |
| /// Mark the lattice element as having reached a pessimistic fixpoint. This |
| /// means that the lattice may potentially have conflicting value states, and |
| /// only the conservatively known value state should be relied on. |
| ChangeResult markPessimisticFixpoint() final { |
| if (isAtFixpoint()) |
| return ChangeResult::NoChange; |
| |
| // For this fixed point, we take whatever we knew to be true and set that to |
| // our optimistic value. |
| optimisticValue = knownValue; |
| return ChangeResult::Change; |
| } |
| |
| /// Mark the lattice element as having reached an optimistic fixpoint. This |
| /// means that we optimistically assume the current value is the true state. |
| void markOptimisticFixpoint() final { |
| assert(!isUninitialized() && "expected an initialized value"); |
| knownValue = *optimisticValue; |
| } |
| |
| /// Returns true if the lattice has reached a fixpoint. A fixpoint is when the |
| /// information optimistically assumed to be true is the same as the |
| /// information known to be true. |
| bool isAtFixpoint() const final { return optimisticValue == knownValue; } |
| |
| private: |
| /// The value that is conservatively known to be true. |
| ValueT knownValue; |
| /// The currently computed value that is optimistically assumed to be true, or |
| /// None if the lattice element is uninitialized. |
| Optional<ValueT> optimisticValue; |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // ForwardDataFlowAnalysisBase |
| //===----------------------------------------------------------------------===// |
| |
| namespace detail { |
| /// This class is the non-templated virtual base class for the |
| /// ForwardDataFlowAnalysis. This class provides opaque hooks to the main |
| /// algorithm. |
| class ForwardDataFlowAnalysisBase { |
| public: |
| virtual ~ForwardDataFlowAnalysisBase(); |
| |
| /// Initialize and compute the analysis on operations rooted under the given |
| /// top-level operation. Note that the top-level operation is not visited. |
| void run(Operation *topLevelOp); |
| |
| /// Return the lattice element attached to the given value. If a lattice has |
| /// not been added for the given value, a new 'uninitialized' value is |
| /// inserted and returned. |
| AbstractLatticeElement &getLatticeElement(Value value); |
| |
| /// Return the lattice element attached to the given value, or nullptr if no |
| /// lattice for the value has yet been created. |
| AbstractLatticeElement *lookupLatticeElement(Value value); |
| |
| /// Visit the given operation, and join any necessary analysis state |
| /// into the lattices for the results and block arguments owned by this |
| /// operation using the provided set of operand lattice elements (all pointer |
| /// values are guaranteed to be non-null). Returns if any result or block |
| /// argument value lattices changed during the visit. The lattice for a result |
| /// or block argument value can be obtained and join'ed into by using |
| /// `getLatticeElement`. |
| virtual ChangeResult |
| visitOperation(Operation *op, |
| ArrayRef<AbstractLatticeElement *> operands) = 0; |
| |
| /// Given a BranchOpInterface, and the current lattice elements that |
| /// correspond to the branch operands (all pointer values are guaranteed to be |
| /// non-null), try to compute a specific set of successors that would be |
| /// selected for the branch. Returns failure if not computable, or if all of |
| /// the successors would be chosen. If a subset of successors can be selected, |
| /// `successors` is populated. |
| virtual LogicalResult |
| getSuccessorsForOperands(BranchOpInterface branch, |
| ArrayRef<AbstractLatticeElement *> operands, |
| SmallVectorImpl<Block *> &successors) = 0; |
| |
| /// Given a RegionBranchOpInterface, and the current lattice elements that |
| /// correspond to the branch operands (all pointer values are guaranteed to be |
| /// non-null), compute a specific set of region successors that would be |
| /// selected. |
| virtual void |
| getSuccessorsForOperands(RegionBranchOpInterface branch, |
| Optional<unsigned> sourceIndex, |
| ArrayRef<AbstractLatticeElement *> operands, |
| SmallVectorImpl<RegionSuccessor> &successors) = 0; |
| |
| /// Create a new uninitialized lattice element. An optional value is provided |
| /// which, if valid, should be used to initialize the known conservative state |
| /// of the lattice. |
| virtual AbstractLatticeElement *createLatticeElement(Value value = {}) = 0; |
| |
| private: |
| /// A map from SSA value to lattice element. |
| DenseMap<Value, AbstractLatticeElement *> latticeValues; |
| }; |
| } // namespace detail |
| |
| //===----------------------------------------------------------------------===// |
| // ForwardDataFlowAnalysis |
| //===----------------------------------------------------------------------===// |
| |
| /// This class provides a general forward dataflow analysis driver |
| /// utilizing the lattice classes defined above, to enable the easy definition |
| /// of dataflow analysis algorithms. More specifically this driver is useful for |
| /// defining analyses that are forward, sparse, pessimistic (except along |
| /// unreached backedges) and context-insensitive for the interprocedural |
| /// aspects. |
| template <typename ValueT> |
| class ForwardDataFlowAnalysis : public detail::ForwardDataFlowAnalysisBase { |
| public: |
| ForwardDataFlowAnalysis(MLIRContext *context) : context(context) {} |
| |
| /// Return the MLIR context used when constructing this analysis. |
| MLIRContext *getContext() { return context; } |
| |
| /// Compute the analysis on operations rooted under the given top-level |
| /// operation. Note that the top-level operation is not visited. |
| void run(Operation *topLevelOp) { |
| detail::ForwardDataFlowAnalysisBase::run(topLevelOp); |
| } |
| |
| /// Return the lattice element attached to the given value, or nullptr if no |
| /// lattice for the value has yet been created. |
| LatticeElement<ValueT> *lookupLatticeElement(Value value) { |
| return static_cast<LatticeElement<ValueT> *>( |
| detail::ForwardDataFlowAnalysisBase::lookupLatticeElement(value)); |
| } |
| |
| protected: |
| /// Return the lattice element attached to the given value. If a lattice has |
| /// not been added for the given value, a new 'uninitialized' value is |
| /// inserted and returned. |
| LatticeElement<ValueT> &getLatticeElement(Value value) { |
| return static_cast<LatticeElement<ValueT> &>( |
| detail::ForwardDataFlowAnalysisBase::getLatticeElement(value)); |
| } |
| |
| /// Mark all of the lattices for the given range of Values as having reached a |
| /// pessimistic fixpoint. |
| ChangeResult markAllPessimisticFixpoint(ValueRange values) { |
| ChangeResult result = ChangeResult::NoChange; |
| for (Value value : values) |
| result |= getLatticeElement(value).markPessimisticFixpoint(); |
| return result; |
| } |
| |
| /// Visit the given operation, and join any necessary analysis state |
| /// into the lattices for the results and block arguments owned by this |
| /// operation using the provided set of operand lattice elements (all pointer |
| /// values are guaranteed to be non-null). Returns if any result or block |
| /// argument value lattices changed during the visit. The lattice for a result |
| /// or block argument value can be obtained by using |
| /// `getLatticeElement`. |
| virtual ChangeResult |
| visitOperation(Operation *op, |
| ArrayRef<LatticeElement<ValueT> *> operands) = 0; |
| |
| /// Given a BranchOpInterface, and the current lattice elements that |
| /// correspond to the branch operands (all pointer values are guaranteed to be |
| /// non-null), try to compute a specific set of successors that would be |
| /// selected for the branch. Returns failure if not computable, or if all of |
| /// the successors would be chosen. If a subset of successors can be selected, |
| /// `successors` is populated. |
| virtual LogicalResult |
| getSuccessorsForOperands(BranchOpInterface branch, |
| ArrayRef<LatticeElement<ValueT> *> operands, |
| SmallVectorImpl<Block *> &successors) { |
| return failure(); |
| } |
| |
| /// Given a RegionBranchOpInterface, and the current lattice elements that |
| /// correspond to the branch operands (all pointer values are guaranteed to be |
| /// non-null), compute a specific set of region successors that would be |
| /// selected. |
| virtual void |
| getSuccessorsForOperands(RegionBranchOpInterface branch, |
| Optional<unsigned> sourceIndex, |
| ArrayRef<LatticeElement<ValueT> *> operands, |
| SmallVectorImpl<RegionSuccessor> &successors) { |
| SmallVector<Attribute> constantOperands(operands.size()); |
| branch.getSuccessorRegions(sourceIndex, constantOperands, successors); |
| } |
| |
| private: |
| /// Type-erased wrappers that convert the abstract lattice operands to derived |
| /// lattices and invoke the virtual hooks operating on the derived lattices. |
| ChangeResult |
| visitOperation(Operation *op, |
| ArrayRef<detail::AbstractLatticeElement *> operands) final { |
| LatticeElement<ValueT> *const *derivedOperandBase = |
| reinterpret_cast<LatticeElement<ValueT> *const *>(operands.data()); |
| return visitOperation( |
| op, llvm::makeArrayRef(derivedOperandBase, operands.size())); |
| } |
| LogicalResult |
| getSuccessorsForOperands(BranchOpInterface branch, |
| ArrayRef<detail::AbstractLatticeElement *> operands, |
| SmallVectorImpl<Block *> &successors) final { |
| LatticeElement<ValueT> *const *derivedOperandBase = |
| reinterpret_cast<LatticeElement<ValueT> *const *>(operands.data()); |
| return getSuccessorsForOperands( |
| branch, llvm::makeArrayRef(derivedOperandBase, operands.size()), |
| successors); |
| } |
| void |
| getSuccessorsForOperands(RegionBranchOpInterface branch, |
| Optional<unsigned> sourceIndex, |
| ArrayRef<detail::AbstractLatticeElement *> operands, |
| SmallVectorImpl<RegionSuccessor> &successors) final { |
| LatticeElement<ValueT> *const *derivedOperandBase = |
| reinterpret_cast<LatticeElement<ValueT> *const *>(operands.data()); |
| getSuccessorsForOperands( |
| branch, sourceIndex, |
| llvm::makeArrayRef(derivedOperandBase, operands.size()), successors); |
| } |
| |
| /// Create a new uninitialized lattice element. An optional value is provided, |
| /// which if valid, should be used to initialize the known conservative state |
| /// of the lattice. |
| detail::AbstractLatticeElement *createLatticeElement(Value value) final { |
| ValueT knownValue = value ? ValueT::getPessimisticValueState(value) |
| : ValueT::getPessimisticValueState(context); |
| return new (allocator.Allocate()) LatticeElement<ValueT>(knownValue); |
| } |
| |
| /// An allocator used for new lattice elements. |
| llvm::SpecificBumpPtrAllocator<LatticeElement<ValueT>> allocator; |
| |
| /// The MLIRContext of this solver. |
| MLIRContext *context; |
| }; |
| |
| } // end namespace mlir |
| |
| #endif // MLIR_ANALYSIS_DATAFLOWANALYSIS_H |