blob: 7566c4d21b8269afcca9decd24fd0242759ecff1 [file] [log] [blame]
//===- 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