Files
gps/tools/local/circt/include/mlir/Analysis/DataFlow/SparseAnalysis.h
T
2026-07-12 11:01:51 +03:00

599 lines
26 KiB
C++

//===- SparseAnalysis.h - Sparse data-flow analysis -----------------------===//
//
// 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 implements sparse data-flow analysis using the data-flow analysis
// framework. The analysis is forward and conditional and uses the results of
// dead code analysis to prune dead code during the analysis.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_ANALYSIS_DATAFLOW_SPARSEANALYSIS_H
#define MLIR_ANALYSIS_DATAFLOW_SPARSEANALYSIS_H
#include "mlir/Analysis/DataFlowFramework.h"
#include "mlir/IR/SymbolTable.h"
#include "mlir/Interfaces/CallInterfaces.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "llvm/ADT/SmallPtrSet.h"
namespace mlir {
namespace dataflow {
//===----------------------------------------------------------------------===//
// AbstractSparseLattice
//===----------------------------------------------------------------------===//
/// This class represents an abstract lattice. A lattice contains information
/// about an SSA value and is what's propagated across the IR by sparse
/// data-flow analysis.
class AbstractSparseLattice : public AnalysisState {
public:
/// Lattices can only be created for values.
AbstractSparseLattice(Value value) : AnalysisState(value) {}
/// Return the value this lattice is located at.
Value getAnchor() const { return cast<Value>(AnalysisState::getAnchor()); }
/// Join the information contained in 'rhs' into this lattice. Returns
/// if the value of the lattice changed.
virtual ChangeResult join(const AbstractSparseLattice &rhs) {
return ChangeResult::NoChange;
}
/// Meet (intersect) the information in this lattice with 'rhs'. Returns
/// if the value of the lattice changed.
virtual ChangeResult meet(const AbstractSparseLattice &rhs) {
return ChangeResult::NoChange;
}
/// When the lattice gets updated, propagate an update to users of the value
/// using its use-def chain to subscribed analyses.
void onUpdate(DataFlowSolver *solver) const override;
/// Subscribe an analysis to updates of the lattice. When the lattice changes,
/// subscribed analyses are re-invoked on all users of the value. This is
/// more efficient than relying on the dependency map.
void useDefSubscribe(DataFlowAnalysis *analysis) {
useDefSubscribers.insert(analysis);
}
private:
/// A set of analyses that should be updated when this lattice changes.
SetVector<DataFlowAnalysis *, SmallVector<DataFlowAnalysis *, 4>,
SmallPtrSet<DataFlowAnalysis *, 4>>
useDefSubscribers;
};
//===----------------------------------------------------------------------===//
// Lattice
//===----------------------------------------------------------------------===//
/// 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.
/// * bool operator==(const ValueT &rhs) const;
///
template <typename ValueT>
class Lattice : public AbstractSparseLattice {
public:
using AbstractSparseLattice::AbstractSparseLattice;
/// Return the value this lattice is located at.
Value getAnchor() const { return cast<Value>(anchor); }
/// Return the value held by this lattice. This requires that the value is
/// initialized.
ValueT &getValue() { return value; }
const ValueT &getValue() const {
return const_cast<Lattice<ValueT> *>(this)->getValue();
}
using LatticeT = Lattice<ValueT>;
/// Join the information contained in the 'rhs' lattice into this
/// lattice. Returns if the state of the current lattice changed.
ChangeResult join(const AbstractSparseLattice &rhs) override {
return join(static_cast<const LatticeT &>(rhs).getValue());
}
/// Meet (intersect) the information contained in the 'rhs' lattice with
/// this lattice. Returns if the state of the current lattice changed.
ChangeResult meet(const AbstractSparseLattice &rhs) override {
return meet(static_cast<const LatticeT &>(rhs).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) {
// Otherwise, join rhs with the current optimistic value.
ValueT newValue = ValueT::join(value, rhs);
assert(ValueT::join(newValue, value) == 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 == value)
return ChangeResult::NoChange;
value = newValue;
return ChangeResult::Change;
}
/// Trait to check if `T` provides a `meet` method. Needed since for forward
/// analysis, lattices will only have a `join`, no `meet`, but we want to use
/// the same `Lattice` class for both directions.
template <typename T, typename... Args>
using has_meet = decltype(&T::meet);
template <typename T>
using lattice_has_meet = llvm::is_detected<has_meet, T>;
/// Meet (intersect) the information contained in the 'rhs' value with this
/// lattice. Returns if the state of the current lattice changed. If the
/// lattice elements don't have a `meet` method, this is a no-op.
template <typename VT>
ChangeResult meet(const VT &rhs) {
if constexpr (!lattice_has_meet<VT>::value) {
return ChangeResult::NoChange;
} else {
ValueT newValue = ValueT::meet(value, rhs);
assert(ValueT::meet(newValue, value) == newValue &&
"expected `meet` to be monotonic");
assert(ValueT::meet(newValue, rhs) == newValue &&
"expected `meet` to be monotonic");
// Update the current optimistic value if something changed.
if (newValue == value)
return ChangeResult::NoChange;
value = newValue;
return ChangeResult::Change;
}
}
/// Print the lattice element.
void print(raw_ostream &os) const override { value.print(os); }
private:
/// The currently computed value that is optimistically assumed to be true.
ValueT value;
};
//===----------------------------------------------------------------------===//
// AbstractSparseForwardDataFlowAnalysis
//===----------------------------------------------------------------------===//
/// Base class for sparse forward data-flow analyses. A sparse analysis
/// implements a transfer function on operations from the lattices of the
/// operands to the lattices of the results. This analysis will propagate
/// lattices across control-flow edges and the callgraph using liveness
/// information.
///
/// Visit a program point in sparse forward data-flow analysis will invoke the
/// transfer function of the operation preceding the program point iterator.
/// Visit a program point at the begining of block will visit the block itself.
class AbstractSparseForwardDataFlowAnalysis : public DataFlowAnalysis {
public:
/// Initialize the analysis by visiting every owner of an SSA value: all
/// operations and blocks.
LogicalResult initialize(Operation *top) override;
/// Visit a program point. If this is at beginning of block and all
/// control-flow predecessors or callsites are known, then the arguments
/// lattices are propagated from them. If this is after call operation or an
/// operation with region control-flow, then its result lattices are set
/// accordingly. Otherwise, the operation transfer function is invoked.
LogicalResult visit(ProgramPoint *point) override;
protected:
explicit AbstractSparseForwardDataFlowAnalysis(DataFlowSolver &solver);
/// The operation transfer function. Given the operand lattices, this
/// function is expected to set the result lattices.
virtual LogicalResult
visitOperationImpl(Operation *op,
ArrayRef<const AbstractSparseLattice *> operandLattices,
ArrayRef<AbstractSparseLattice *> resultLattices) = 0;
/// The transfer function for calls to external functions.
virtual void visitExternalCallImpl(
CallOpInterface call,
ArrayRef<const AbstractSparseLattice *> argumentLattices,
ArrayRef<AbstractSparseLattice *> resultLattices) = 0;
/// Given an operation with region control-flow, the lattices of the operands,
/// and a region successor, compute the lattice values for block arguments
/// that are not accounted for by the branching control flow (ex. the bounds
/// of loops).
virtual void visitNonControlFlowArgumentsImpl(
Operation *op, const RegionSuccessor &successor,
ValueRange nonSuccessorInputs,
ArrayRef<AbstractSparseLattice *> nonSuccessorInputLattices) = 0;
/// Get the lattice element of a value.
virtual AbstractSparseLattice *getLatticeElement(Value value) = 0;
/// Get a read-only lattice element for a value and add it as a dependency to
/// a program point.
const AbstractSparseLattice *getLatticeElementFor(ProgramPoint *point,
Value value);
/// Set the given lattice element(s) at control flow entry point(s).
virtual void setToEntryState(AbstractSparseLattice *lattice) = 0;
void setAllToEntryStates(ArrayRef<AbstractSparseLattice *> lattices);
/// Join the lattice element and propagate and update if it changed.
void join(AbstractSparseLattice *lhs, const AbstractSparseLattice &rhs);
/// Visits a call operation. Given the operand lattices, sets the result
/// lattices. Performs interprocedural data flow as follows: if the call
/// operation targets an external function, or if the solver is not
/// interprocedural, attempts to infer the results from the call arguments
/// using the user-provided `visitExternalCallImpl`. Otherwise, computes the
/// result lattices from the return sites if all return sites are known;
/// otherwise, conservatively marks the result lattices as having reached
/// their pessimistic fixpoints.
/// This method can be overridden to, for example, be less conservative and
/// propagate the information even if some return sites are unknown.
virtual LogicalResult
visitCallOperation(CallOpInterface call,
ArrayRef<const AbstractSparseLattice *> operandLattices,
ArrayRef<AbstractSparseLattice *> resultLattices);
/// Visits a callable operation. Computes the argument lattices from call
/// sites if all call sites are known; otherwise, conservatively marks them
/// as having reached their pessimistic fixpoints.
/// This method can be overridden to, for example, be less conservative and
/// propagate the information even if some call sites are unknown.
virtual void
visitCallableOperation(CallableOpInterface callable,
ArrayRef<AbstractSparseLattice *> argLattices);
private:
/// Recursively initialize the analysis on nested operations and blocks.
LogicalResult initializeRecursively(Operation *op);
/// Visit an operation. If this is a call operation or an operation with
/// region control-flow, then its result lattices are set accordingly.
/// Otherwise, the operation transfer function is invoked.
LogicalResult visitOperation(Operation *op);
/// Visit a block to compute the lattice values of its arguments. If this is
/// an entry block, then the argument values are determined from the block's
/// "predecessors" as set by `PredecessorState`. The predecessors can be
/// region terminators or callable callsites. Otherwise, the values are
/// determined from block predecessors.
void visitBlock(Block *block);
/// Visit a program point `point` with predecessors within a region branch
/// operation `branch`, which can either be the entry block of one of the
/// regions or the parent operation itself, and set either the argument or
/// parent result lattices.
/// This method can be overridden to control precisely how the region
/// successors of `branch` are visited. For example in order to precisely
/// control the order in which predecessor operand lattices are propagated
/// from. An override is responsible for visiting all the known predecessors
/// and propagating therefrom.
virtual void
visitRegionSuccessors(ProgramPoint *point, RegionBranchOpInterface branch,
RegionSuccessor successor,
ArrayRef<AbstractSparseLattice *> lattices);
};
//===----------------------------------------------------------------------===//
// SparseForwardDataFlowAnalysis
//===----------------------------------------------------------------------===//
/// A sparse forward data-flow analysis for propagating SSA value lattices
/// across the IR by implementing transfer functions for operations.
///
/// `StateT` is expected to be a subclass of `AbstractSparseLattice`.
template <typename StateT>
class SparseForwardDataFlowAnalysis
: public AbstractSparseForwardDataFlowAnalysis {
static_assert(
std::is_base_of<AbstractSparseLattice, StateT>::value,
"analysis state class expected to subclass AbstractSparseLattice");
public:
explicit SparseForwardDataFlowAnalysis(DataFlowSolver &solver)
: AbstractSparseForwardDataFlowAnalysis(solver) {}
/// Visit an operation with the lattices of its operands. This function is
/// expected to set the lattices of the operation's results.
virtual LogicalResult visitOperation(Operation *op,
ArrayRef<const StateT *> operands,
ArrayRef<StateT *> results) = 0;
/// Visit a call operation to an externally defined function given the
/// lattices of its arguments.
virtual void visitExternalCall(CallOpInterface call,
ArrayRef<const StateT *> argumentLattices,
ArrayRef<StateT *> resultLattices) {
setAllToEntryStates(resultLattices);
}
/// Given an operation with possible region control-flow, the lattices of the
/// operands, and a region successor, compute the lattice values for
/// non-successor-inputs (ex. loop induction variables) of a given region
/// successor. By default, this method marks all lattice elements as having
/// reached a pessimistic fixpoint.
virtual void
visitNonControlFlowArguments(Operation *op, const RegionSuccessor &successor,
ValueRange nonSuccessorInputs,
ArrayRef<StateT *> nonSuccessorInputLattices) {
assert(nonSuccessorInputs.size() == nonSuccessorInputLattices.size() &&
"size mismatch");
setAllToEntryStates(nonSuccessorInputLattices);
}
protected:
/// Get the lattice element for a value.
StateT *getLatticeElement(Value value) override {
return getOrCreate<StateT>(value);
}
/// Get the lattice element for a value and create a dependency on the
/// provided program point.
const StateT *getLatticeElementFor(ProgramPoint *point, Value value) {
return static_cast<const StateT *>(
AbstractSparseForwardDataFlowAnalysis::getLatticeElementFor(point,
value));
}
/// Set the given lattice element(s) at control flow entry point(s).
virtual void setToEntryState(StateT *lattice) = 0;
void setAllToEntryStates(ArrayRef<StateT *> lattices) {
AbstractSparseForwardDataFlowAnalysis::setAllToEntryStates(
{reinterpret_cast<AbstractSparseLattice *const *>(lattices.begin()),
lattices.size()});
}
private:
/// Type-erased wrappers that convert the abstract lattice operands to derived
/// lattices and invoke the virtual hooks operating on the derived lattices.
LogicalResult visitOperationImpl(
Operation *op, ArrayRef<const AbstractSparseLattice *> operandLattices,
ArrayRef<AbstractSparseLattice *> resultLattices) override {
return visitOperation(
op,
{reinterpret_cast<const StateT *const *>(operandLattices.begin()),
operandLattices.size()},
{reinterpret_cast<StateT *const *>(resultLattices.begin()),
resultLattices.size()});
}
void visitExternalCallImpl(
CallOpInterface call,
ArrayRef<const AbstractSparseLattice *> argumentLattices,
ArrayRef<AbstractSparseLattice *> resultLattices) override {
visitExternalCall(
call,
{reinterpret_cast<const StateT *const *>(argumentLattices.begin()),
argumentLattices.size()},
{reinterpret_cast<StateT *const *>(resultLattices.begin()),
resultLattices.size()});
}
void visitNonControlFlowArgumentsImpl(
Operation *op, const RegionSuccessor &successor,
ValueRange nonSuccessorInputs,
ArrayRef<AbstractSparseLattice *> nonSuccessorInputLattices) override {
visitNonControlFlowArguments(
op, successor, nonSuccessorInputs,
{reinterpret_cast<StateT *const *>(nonSuccessorInputLattices.begin()),
nonSuccessorInputLattices.size()});
}
void setToEntryState(AbstractSparseLattice *lattice) override {
return setToEntryState(reinterpret_cast<StateT *>(lattice));
}
};
//===----------------------------------------------------------------------===//
// AbstractSparseBackwardDataFlowAnalysis
//===----------------------------------------------------------------------===//
/// Base class for sparse backward data-flow analyses. Similar to
/// AbstractSparseForwardDataFlowAnalysis, but walks bottom to top.
class AbstractSparseBackwardDataFlowAnalysis : public DataFlowAnalysis {
public:
/// Initialize the analysis by visiting the operation and everything nested
/// under it.
LogicalResult initialize(Operation *top) override;
/// Visit a program point. If it is after call operation or an operation with
/// block or region control-flow, then operand lattices are set accordingly.
/// Otherwise, invokes the operation transfer function (`visitOperationImpl`).
LogicalResult visit(ProgramPoint *point) override;
protected:
explicit AbstractSparseBackwardDataFlowAnalysis(
DataFlowSolver &solver, SymbolTableCollection &symbolTable);
/// The operation transfer function. Given the result lattices, this
/// function is expected to set the operand lattices.
virtual LogicalResult visitOperationImpl(
Operation *op, ArrayRef<AbstractSparseLattice *> operandLattices,
ArrayRef<const AbstractSparseLattice *> resultLattices) = 0;
/// The transfer function for calls to external functions.
virtual void visitExternalCallImpl(
CallOpInterface call, ArrayRef<AbstractSparseLattice *> operandLattices,
ArrayRef<const AbstractSparseLattice *> resultLattices) = 0;
// Visit operands on branch instructions that are not forwarded.
virtual void visitBranchOperand(OpOperand &operand) = 0;
// Visit the non-forwarded arguments of a region, such as the
// induction variables of a loop.
virtual void
visitNonControlFlowArguments(RegionSuccessor &successor,
ArrayRef<BlockArgument> arguments) = 0;
// Visit operands on call instructions that are not forwarded.
virtual void visitCallOperand(OpOperand &operand) = 0;
/// Set the given lattice element(s) at control flow exit point(s) and
/// propagate the update if it chaned.
virtual void setToExitState(AbstractSparseLattice *lattice) = 0;
/// Set the given lattice element(s) at control flow exit point(s) and
/// propagate the update if it chaned.
void setAllToExitStates(ArrayRef<AbstractSparseLattice *> lattices);
/// Get the lattice element for a value.
virtual AbstractSparseLattice *getLatticeElement(Value value) = 0;
/// Get the lattice elements for a range of values.
SmallVector<AbstractSparseLattice *> getLatticeElements(ValueRange values);
/// Join the lattice element and propagate and update if it changed.
void meet(AbstractSparseLattice *lhs, const AbstractSparseLattice &rhs);
/// Visits a callable operation. If all the call sites are known computes the
/// operand lattices of `op` from the result lattices of all the call sites;
/// otherwise, conservatively marks them as having reached their pessimistic
/// fixpoints.
/// This method can be overridden to, for example, be less conservative and
/// propagate the information even if some call sites are unknown.
virtual LogicalResult
visitCallableOperation(Operation *op, CallableOpInterface callable,
ArrayRef<AbstractSparseLattice *> operandLattices);
private:
/// Recursively initialize the analysis on nested operations and blocks.
LogicalResult initializeRecursively(Operation *op);
/// Visit an operation. If this is a call operation or an operation with
/// region control-flow, then its operand lattices are set accordingly.
/// Otherwise, the operation transfer function is invoked.
LogicalResult visitOperation(Operation *op);
/// Visit a block.
void visitBlock(Block *block);
/// Visit an op with regions (like e.g. `scf.while`)
void visitRegionSuccessors(RegionBranchOpInterface branch,
ArrayRef<AbstractSparseLattice *> operands);
/// Visit a `RegionBranchTerminatorOpInterface` to compute the lattice values
/// of its operands, given its parent op `branch`. The lattice value of an
/// operand is determined based on the corresponding arguments in
/// `terminator`'s region successor(s).
void visitRegionSuccessorsFromTerminator(
RegionBranchTerminatorOpInterface terminator,
RegionBranchOpInterface branch);
/// Get the lattice element for a value, and also set up
/// dependencies so that the analysis on the given ProgramPoint is re-invoked
/// if the value changes.
const AbstractSparseLattice *getLatticeElementFor(ProgramPoint *point,
Value value);
/// Get the lattice elements for a range of values, and also set up
/// dependencies so that the analysis on the given ProgramPoint is re-invoked
/// if any of the values change.
SmallVector<const AbstractSparseLattice *>
getLatticeElementsFor(ProgramPoint *point, ValueRange values);
SymbolTableCollection &symbolTable;
};
//===----------------------------------------------------------------------===//
// SparseBackwardDataFlowAnalysis
//===----------------------------------------------------------------------===//
/// A sparse (backward) data-flow analysis for propagating SSA value lattices
/// backwards across the IR by implementing transfer functions for operations.
///
/// `StateT` is expected to be a subclass of `AbstractSparseLattice`.
///
/// Visit a program point in sparse backward data-flow analysis will invoke the
/// transfer function of the operation preceding the program point iterator.
/// Visit a program point at the begining of block will visit the block itself.
template <typename StateT>
class SparseBackwardDataFlowAnalysis
: public AbstractSparseBackwardDataFlowAnalysis {
static_assert(
std::is_base_of<AbstractSparseLattice, StateT>::value,
"analysis state class expected to subclass AbstractSparseLattice");
public:
explicit SparseBackwardDataFlowAnalysis(DataFlowSolver &solver,
SymbolTableCollection &symbolTable)
: AbstractSparseBackwardDataFlowAnalysis(solver, symbolTable) {}
/// Visit an operation with the lattices of its results. This function is
/// expected to set the lattices of the operation's operands.
virtual LogicalResult visitOperation(Operation *op,
ArrayRef<StateT *> operands,
ArrayRef<const StateT *> results) = 0;
/// Visit a call to an external function. This function is expected to set
/// lattice values of the call operands. By default, calls `visitCallOperand`
/// for all operands.
virtual void visitExternalCall(CallOpInterface call,
ArrayRef<StateT *> argumentLattices,
ArrayRef<const StateT *> resultLattices) {
(void)argumentLattices;
(void)resultLattices;
for (OpOperand &operand : call->getOpOperands()) {
visitCallOperand(operand);
}
};
protected:
/// Get the lattice element for a value.
StateT *getLatticeElement(Value value) override {
return getOrCreate<StateT>(value);
}
/// Set the given lattice element(s) at control flow exit point(s).
virtual void setToExitState(StateT *lattice) = 0;
void setToExitState(AbstractSparseLattice *lattice) override {
return setToExitState(reinterpret_cast<StateT *>(lattice));
}
void setAllToExitStates(ArrayRef<StateT *> lattices) {
AbstractSparseBackwardDataFlowAnalysis::setAllToExitStates(
{reinterpret_cast<AbstractSparseLattice *const *>(lattices.begin()),
lattices.size()});
}
private:
/// Type-erased wrappers that convert the abstract lattice operands to derived
/// lattices and invoke the virtual hooks operating on the derived lattices.
LogicalResult visitOperationImpl(
Operation *op, ArrayRef<AbstractSparseLattice *> operandLattices,
ArrayRef<const AbstractSparseLattice *> resultLattices) override {
return visitOperation(
op,
{reinterpret_cast<StateT *const *>(operandLattices.begin()),
operandLattices.size()},
{reinterpret_cast<const StateT *const *>(resultLattices.begin()),
resultLattices.size()});
}
void visitExternalCallImpl(
CallOpInterface call, ArrayRef<AbstractSparseLattice *> operandLattices,
ArrayRef<const AbstractSparseLattice *> resultLattices) override {
visitExternalCall(
call,
{reinterpret_cast<StateT *const *>(operandLattices.begin()),
operandLattices.size()},
{reinterpret_cast<const StateT *const *>(resultLattices.begin()),
resultLattices.size()});
}
};
} // end namespace dataflow
} // end namespace mlir
#endif // MLIR_ANALYSIS_DATAFLOW_SPARSEANALYSIS_H