255 lines
9.9 KiB
C++
255 lines
9.9 KiB
C++
//===- DeadCodeAnalysis.h - Dead code 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 dead code analysis using the data-flow analysis
|
|
// framework. This analysis uses the results of constant propagation to
|
|
// determine live blocks, control-flow edges, and control-flow predecessors.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef MLIR_ANALYSIS_DATAFLOW_DEADCODEANALYSIS_H
|
|
#define MLIR_ANALYSIS_DATAFLOW_DEADCODEANALYSIS_H
|
|
|
|
#include "mlir/Analysis/DataFlowFramework.h"
|
|
#include "mlir/IR/SymbolTable.h"
|
|
#include "mlir/Interfaces/ControlFlowInterfaces.h"
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
#include <optional>
|
|
|
|
namespace mlir {
|
|
|
|
class CallOpInterface;
|
|
class CallableOpInterface;
|
|
class BranchOpInterface;
|
|
class RegionBranchOpInterface;
|
|
class RegionBranchTerminatorOpInterface;
|
|
|
|
namespace dataflow {
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Executable
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// This is a simple analysis state that represents whether the associated
|
|
/// lattice anchor (either a block or a control-flow edge) is live.
|
|
class Executable : public AnalysisState {
|
|
public:
|
|
using AnalysisState::AnalysisState;
|
|
|
|
/// Set the state of the lattice anchor to live.
|
|
ChangeResult setToLive();
|
|
|
|
/// Get whether the lattice anchor is live.
|
|
bool isLive() const { return live; }
|
|
|
|
/// Print the liveness.
|
|
void print(raw_ostream &os) const override;
|
|
|
|
/// When the state of the lattice anchor is changed to live, re-invoke
|
|
/// subscribed analyses on the operations in the block and on the block
|
|
/// itself.
|
|
void onUpdate(DataFlowSolver *solver) const override;
|
|
|
|
/// Subscribe an analysis to changes to the liveness.
|
|
void blockContentSubscribe(DataFlowAnalysis *analysis) {
|
|
subscribers.insert(analysis);
|
|
}
|
|
|
|
private:
|
|
/// Whether the lattice anchor is live. Optimistically assume that the lattice
|
|
/// anchor is dead.
|
|
bool live = false;
|
|
|
|
/// A set of analyses that should be updated when this state changes.
|
|
SetVector<DataFlowAnalysis *, SmallVector<DataFlowAnalysis *, 4>,
|
|
SmallPtrSet<DataFlowAnalysis *, 4>>
|
|
subscribers;
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// PredecessorState
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// This analysis state represents a set of live control-flow "predecessors" of
|
|
/// a program point (either an operation or a block), which are the last
|
|
/// operations along all execution paths that pass through this point.
|
|
///
|
|
/// For example, in dead-code analysis, an operation with region control-flow
|
|
/// can be the predecessor of a region's entry block or itself, the exiting
|
|
/// terminator of a region can be the predecessor of the parent operation or
|
|
/// another region's entry block, the callsite of a callable operation can be
|
|
/// the predecessor to its entry block, and the exiting terminator or a callable
|
|
/// operation can be the predecessor of the call operation.
|
|
///
|
|
/// The state can optionally contain information about which values are
|
|
/// propagated from each predecessor to the successor point.
|
|
///
|
|
/// The state can indicate that it is underdefined, meaning that not all live
|
|
/// control-flow predecessors can be known.
|
|
class PredecessorState : public AnalysisState {
|
|
public:
|
|
using AnalysisState::AnalysisState;
|
|
|
|
/// Print the known predecessors.
|
|
void print(raw_ostream &os) const override;
|
|
|
|
/// Returns true if all predecessors are known.
|
|
bool allPredecessorsKnown() const { return allKnown; }
|
|
|
|
/// Indicate that there are potentially unknown predecessors.
|
|
ChangeResult setHasUnknownPredecessors() {
|
|
return std::exchange(allKnown, false) ? ChangeResult::Change
|
|
: ChangeResult::NoChange;
|
|
}
|
|
|
|
/// Get the known predecessors.
|
|
ArrayRef<Operation *> getKnownPredecessors() const {
|
|
return knownPredecessors.getArrayRef();
|
|
}
|
|
|
|
/// Get the successor inputs from a predecessor.
|
|
ValueRange getSuccessorInputs(Operation *predecessor) const {
|
|
return successorInputs.lookup(predecessor);
|
|
}
|
|
|
|
/// Add a known predecessor.
|
|
ChangeResult join(Operation *predecessor);
|
|
|
|
/// Add a known predecessor with successor inputs.
|
|
ChangeResult join(Operation *predecessor, ValueRange inputs);
|
|
|
|
private:
|
|
/// Whether all predecessors are known. Optimistically assume that we know
|
|
/// all predecessors.
|
|
bool allKnown = true;
|
|
|
|
/// The known control-flow predecessors of this program point.
|
|
SetVector<Operation *, SmallVector<Operation *, 4>,
|
|
SmallPtrSet<Operation *, 4>>
|
|
knownPredecessors;
|
|
|
|
/// The successor inputs when branching from a given predecessor.
|
|
DenseMap<Operation *, ValueRange> successorInputs;
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CFGEdge
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// This lattice anchor represents a control-flow edge between a block and one
|
|
/// of its successors.
|
|
class CFGEdge
|
|
: public GenericLatticeAnchorBase<CFGEdge, std::pair<Block *, Block *>> {
|
|
public:
|
|
using Base::Base;
|
|
|
|
/// Get the block from which the edge originates.
|
|
Block *getFrom() const { return getValue().first; }
|
|
/// Get the target block.
|
|
Block *getTo() const { return getValue().second; }
|
|
|
|
/// Print the blocks between the control-flow edge.
|
|
void print(raw_ostream &os) const override;
|
|
/// Get a fused location of both blocks.
|
|
Location getLoc() const override;
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// DeadCodeAnalysis
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Dead code analysis analyzes control-flow, as understood by
|
|
/// `RegionBranchOpInterface` and `BranchOpInterface`, and the callgraph, as
|
|
/// understood by `CallableOpInterface` and `CallOpInterface`.
|
|
///
|
|
/// This analysis uses known constant values of operands to determine the
|
|
/// liveness of each block and each edge between a block and its predecessors.
|
|
/// For region control-flow, this analysis determines the predecessor operations
|
|
/// for region entry blocks and region control-flow operations. For the
|
|
/// callgraph, this analysis determines the callsites and live returns of every
|
|
/// function.
|
|
class DeadCodeAnalysis : public DataFlowAnalysis {
|
|
public:
|
|
explicit DeadCodeAnalysis(DataFlowSolver &solver);
|
|
|
|
/// Initialize the analysis by visiting every operation with potential
|
|
/// control-flow semantics.
|
|
LogicalResult initialize(Operation *top) override;
|
|
|
|
/// Visit an operation with control-flow semantics and deduce which of its
|
|
/// successors are live.
|
|
LogicalResult visit(ProgramPoint *point) override;
|
|
|
|
private:
|
|
/// Find and mark symbol callables with potentially unknown callsites as
|
|
/// having overdefined predecessors. `top` is the top-level operation that the
|
|
/// analysis is operating on.
|
|
void initializeSymbolCallables(Operation *top);
|
|
|
|
/// Recursively Initialize the analysis on nested regions.
|
|
LogicalResult initializeRecursively(Operation *op);
|
|
|
|
/// Visit the given call operation and compute any necessary lattice state.
|
|
void visitCallOperation(CallOpInterface call);
|
|
|
|
/// Visit the given branch operation with successors and try to determine
|
|
/// which are live from the current block.
|
|
void visitBranchOperation(BranchOpInterface branch);
|
|
|
|
/// Visit region branch edges from `predecessorOp` to a list of successors.
|
|
/// For each edge, mark the successor program point as executable, and record
|
|
/// the predecessor information in its `PredecessorState`.
|
|
void visitRegionBranchEdges(RegionBranchOpInterface regionBranchOp,
|
|
Operation *predecessorOp,
|
|
const SmallVector<RegionSuccessor> &successors);
|
|
|
|
/// Visit the given region branch operation, which defines regions, and
|
|
/// compute any necessary lattice state. This also resolves the lattice state
|
|
/// of both the operation results and any nested regions.
|
|
void visitRegionBranchOperation(RegionBranchOpInterface branch);
|
|
|
|
/// Visit the given terminator operation that exits a region under an
|
|
/// operation with control-flow semantics. These are terminators with no CFG
|
|
/// successors.
|
|
void visitRegionTerminator(Operation *op, RegionBranchOpInterface branch);
|
|
|
|
/// Visit the given terminator operation that exits a callable region. These
|
|
/// are terminators with no CFG successors.
|
|
void visitCallableTerminator(Operation *op, CallableOpInterface callable);
|
|
|
|
/// Mark the edge between `from` and `to` as executable.
|
|
void markEdgeLive(Block *from, Block *to);
|
|
|
|
/// Mark the entry blocks of the operation as executable.
|
|
void markEntryBlocksLive(Operation *op);
|
|
|
|
/// Get the constant values of the operands of the operation. Returns
|
|
/// std::nullopt if any of the operand lattices are uninitialized.
|
|
std::optional<SmallVector<Attribute>> getOperandValues(Operation *op);
|
|
|
|
/// The top-level operation the analysis is running on. This is used to detect
|
|
/// if a callable is outside the scope of the analysis and thus must be
|
|
/// considered an external callable.
|
|
Operation *analysisScope;
|
|
|
|
/// Whether the analysis scope has a symbol table. This is used to avoid
|
|
/// resolving callables outside the analysis scope.
|
|
/// It is updated when recursing into a region in case where the top-level
|
|
/// operation does not have a symbol table, but one is encountered in a nested
|
|
/// region.
|
|
bool hasSymbolTable = false;
|
|
|
|
/// A symbol table used for O(1) symbol lookups during simplification.
|
|
SymbolTableCollection symbolTable;
|
|
};
|
|
|
|
} // end namespace dataflow
|
|
} // end namespace mlir
|
|
|
|
#endif // MLIR_ANALYSIS_DATAFLOW_DEADCODEANALYSIS_H
|