//===- 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 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 &rhsLattice = static_cast &>(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 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 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 operands, SmallVectorImpl &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 sourceIndex, ArrayRef operands, SmallVectorImpl &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 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 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 *lookupLatticeElement(Value value) { return static_cast *>( 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 &getLatticeElement(Value value) { return static_cast &>( 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 *> 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 *> operands, SmallVectorImpl &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 sourceIndex, ArrayRef *> operands, SmallVectorImpl &successors) { SmallVector 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 operands) final { LatticeElement *const *derivedOperandBase = reinterpret_cast *const *>(operands.data()); return visitOperation( op, llvm::makeArrayRef(derivedOperandBase, operands.size())); } LogicalResult getSuccessorsForOperands(BranchOpInterface branch, ArrayRef operands, SmallVectorImpl &successors) final { LatticeElement *const *derivedOperandBase = reinterpret_cast *const *>(operands.data()); return getSuccessorsForOperands( branch, llvm::makeArrayRef(derivedOperandBase, operands.size()), successors); } void getSuccessorsForOperands(RegionBranchOpInterface branch, Optional sourceIndex, ArrayRef operands, SmallVectorImpl &successors) final { LatticeElement *const *derivedOperandBase = reinterpret_cast *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(knownValue); } /// An allocator used for new lattice elements. llvm::SpecificBumpPtrAllocator> allocator; /// The MLIRContext of this solver. MLIRContext *context; }; } // end namespace mlir #endif // MLIR_ANALYSIS_DATAFLOWANALYSIS_H