MLIR 22.0.0git
DeadCodeAnalysis.h
Go to the documentation of this file.
1//===- DeadCodeAnalysis.h - Dead code analysis ----------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements dead code analysis using the data-flow analysis
10// framework. This analysis uses the results of constant propagation to
11// determine live blocks, control-flow edges, and control-flow predecessors.
12//
13//===----------------------------------------------------------------------===//
14
15#ifndef MLIR_ANALYSIS_DATAFLOW_DEADCODEANALYSIS_H
16#define MLIR_ANALYSIS_DATAFLOW_DEADCODEANALYSIS_H
17
19#include "mlir/IR/SymbolTable.h"
21#include "llvm/ADT/SmallPtrSet.h"
22#include <optional>
23
24namespace mlir {
25
26class CallOpInterface;
27class CallableOpInterface;
28class BranchOpInterface;
29class RegionBranchOpInterface;
30class RegionBranchTerminatorOpInterface;
31
32namespace dataflow {
33
34//===----------------------------------------------------------------------===//
35// Executable
36//===----------------------------------------------------------------------===//
37
38/// This is a simple analysis state that represents whether the associated
39/// lattice anchor (either a block or a control-flow edge) is live.
40class Executable : public AnalysisState {
41public:
43
44 /// Set the state of the lattice anchor to live.
46
47 /// Get whether the lattice anchor is live.
48 bool isLive() const { return live; }
49
50 /// Print the liveness.
51 void print(raw_ostream &os) const override;
52
53 /// When the state of the lattice anchor is changed to live, re-invoke
54 /// subscribed analyses on the operations in the block and on the block
55 /// itself.
56 void onUpdate(DataFlowSolver *solver) const override;
57
58 /// Subscribe an analysis to changes to the liveness.
60 subscribers.insert(analysis);
61 }
62
63private:
64 /// Whether the lattice anchor is live. Optimistically assume that the lattice
65 /// anchor is dead.
66 bool live = false;
67
68 /// A set of analyses that should be updated when this state changes.
71 subscribers;
72};
73
74//===----------------------------------------------------------------------===//
75// PredecessorState
76//===----------------------------------------------------------------------===//
77
78/// This analysis state represents a set of live control-flow "predecessors" of
79/// a program point (either an operation or a block), which are the last
80/// operations along all execution paths that pass through this point.
81///
82/// For example, in dead-code analysis, an operation with region control-flow
83/// can be the predecessor of a region's entry block or itself, the exiting
84/// terminator of a region can be the predecessor of the parent operation or
85/// another region's entry block, the callsite of a callable operation can be
86/// the predecessor to its entry block, and the exiting terminator or a callable
87/// operation can be the predecessor of the call operation.
88///
89/// The state can optionally contain information about which values are
90/// propagated from each predecessor to the successor point.
91///
92/// The state can indicate that it is underdefined, meaning that not all live
93/// control-flow predecessors can be known.
95public:
97
98 /// Print the known predecessors.
99 void print(raw_ostream &os) const override;
100
101 /// Returns true if all predecessors are known.
102 bool allPredecessorsKnown() const { return allKnown; }
103
104 /// Indicate that there are potentially unknown predecessors.
106 return std::exchange(allKnown, false) ? ChangeResult::Change
108 }
109
110 /// Get the known predecessors.
112 return knownPredecessors.getArrayRef();
113 }
114
115 /// Get the successor inputs from a predecessor.
117 return successorInputs.lookup(predecessor);
118 }
119
120 /// Add a known predecessor.
121 ChangeResult join(Operation *predecessor);
122
123 /// Add a known predecessor with successor inputs.
124 ChangeResult join(Operation *predecessor, ValueRange inputs);
125
126private:
127 /// Whether all predecessors are known. Optimistically assume that we know
128 /// all predecessors.
129 bool allKnown = true;
130
131 /// The known control-flow predecessors of this program point.
134 knownPredecessors;
135
136 /// The successor inputs when branching from a given predecessor.
137 DenseMap<Operation *, ValueRange> successorInputs;
138};
139
140//===----------------------------------------------------------------------===//
141// CFGEdge
142//===----------------------------------------------------------------------===//
143
144/// This lattice anchor represents a control-flow edge between a block and one
145/// of its successors.
147 : public GenericLatticeAnchorBase<CFGEdge, std::pair<Block *, Block *>> {
148public:
149 using Base::Base;
150
151 /// Get the block from which the edge originates.
152 Block *getFrom() const { return getValue().first; }
153 /// Get the target block.
154 Block *getTo() const { return getValue().second; }
155
156 /// Print the blocks between the control-flow edge.
157 void print(raw_ostream &os) const override;
158 /// Get a fused location of both blocks.
159 Location getLoc() const override;
160};
161
162//===----------------------------------------------------------------------===//
163// DeadCodeAnalysis
164//===----------------------------------------------------------------------===//
165
166/// Dead code analysis analyzes control-flow, as understood by
167/// `RegionBranchOpInterface` and `BranchOpInterface`, and the callgraph, as
168/// understood by `CallableOpInterface` and `CallOpInterface`.
169///
170/// This analysis uses known constant values of operands to determine the
171/// liveness of each block and each edge between a block and its predecessors.
172/// For region control-flow, this analysis determines the predecessor operations
173/// for region entry blocks and region control-flow operations. For the
174/// callgraph, this analysis determines the callsites and live returns of every
175/// function.
177public:
178 explicit DeadCodeAnalysis(DataFlowSolver &solver);
179
180 /// Initialize the analysis by visiting every operation with potential
181 /// control-flow semantics.
182 LogicalResult initialize(Operation *top) override;
183
184 /// Visit an operation with control-flow semantics and deduce which of its
185 /// successors are live.
186 LogicalResult visit(ProgramPoint *point) override;
187
188private:
189 /// Find and mark symbol callables with potentially unknown callsites as
190 /// having overdefined predecessors. `top` is the top-level operation that the
191 /// analysis is operating on.
192 void initializeSymbolCallables(Operation *top);
193
194 /// Recursively Initialize the analysis on nested regions.
195 LogicalResult initializeRecursively(Operation *op);
196
197 /// Visit the given call operation and compute any necessary lattice state.
198 void visitCallOperation(CallOpInterface call);
199
200 /// Visit the given branch operation with successors and try to determine
201 /// which are live from the current block.
202 void visitBranchOperation(BranchOpInterface branch);
203
204 /// Visit region branch edges from `predecessorOp` to a list of successors.
205 /// For each edge, mark the successor program point as executable, and record
206 /// the predecessor information in its `PredecessorState`.
207 void visitRegionBranchEdges(RegionBranchOpInterface regionBranchOp,
208 Operation *predecessorOp,
209 const SmallVector<RegionSuccessor> &successors);
210
211 /// Visit the given region branch operation, which defines regions, and
212 /// compute any necessary lattice state. This also resolves the lattice state
213 /// of both the operation results and any nested regions.
214 void visitRegionBranchOperation(RegionBranchOpInterface branch);
215
216 /// Visit the given terminator operation that exits a region under an
217 /// operation with control-flow semantics. These are terminators with no CFG
218 /// successors.
219 void visitRegionTerminator(Operation *op, RegionBranchOpInterface branch);
220
221 /// Visit the given terminator operation that exits a callable region. These
222 /// are terminators with no CFG successors.
223 void visitCallableTerminator(Operation *op, CallableOpInterface callable);
224
225 /// Mark the edge between `from` and `to` as executable.
226 void markEdgeLive(Block *from, Block *to);
227
228 /// Mark the entry blocks of the operation as executable.
229 void markEntryBlocksLive(Operation *op);
230
231 /// Get the constant values of the operands of the operation. Returns
232 /// std::nullopt if any of the operand lattices are uninitialized.
233 std::optional<SmallVector<Attribute>> getOperandValues(Operation *op);
234
235 /// The top-level operation the analysis is running on. This is used to detect
236 /// if a callable is outside the scope of the analysis and thus must be
237 /// considered an external callable.
238 Operation *analysisScope;
239
240 /// Whether the analysis scope has a symbol table. This is used to avoid
241 /// resolving callables outside the analysis scope.
242 /// It is updated when recursing into a region in case where the top-level
243 /// operation does not have a symbol table, but one is encountered in a nested
244 /// region.
245 bool hasSymbolTable = false;
246
247 /// A symbol table used for O(1) symbol lookups during simplification.
248 SymbolTableCollection symbolTable;
249};
250
251} // end namespace dataflow
252} // end namespace mlir
253
254#endif // MLIR_ANALYSIS_DATAFLOW_DEADCODEANALYSIS_H
AnalysisState(LatticeAnchor anchor)
Create the analysis state on the given lattice anchor.
Block represents an ordered list of Operations.
Definition Block.h:33
Base class for all data-flow analyses.
DataFlowAnalysis(DataFlowSolver &solver)
Create an analysis with a reference to the parent solver.
friend class DataFlowSolver
Allow the data-flow solver to access the internals of this class.
The general data-flow analysis solver.
GenericLatticeAnchorBase< CFGEdge, std::pair< Block *, Block * > > Base
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
This class represents a collection of SymbolTables.
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:387
This lattice anchor represents a control-flow edge between a block and one of its successors.
Location getLoc() const override
Get a fused location of both blocks.
Block * getTo() const
Get the target block.
Block * getFrom() const
Get the block from which the edge originates.
void print(raw_ostream &os) const override
Print the blocks between the control-flow edge.
DeadCodeAnalysis(DataFlowSolver &solver)
LogicalResult visit(ProgramPoint *point) override
Visit an operation with control-flow semantics and deduce which of its successors are live.
LogicalResult initialize(Operation *top) override
Initialize the analysis by visiting every operation with potential control-flow semantics.
This is a simple analysis state that represents whether the associated lattice anchor (either a block...
bool isLive() const
Get whether the lattice anchor is live.
void onUpdate(DataFlowSolver *solver) const override
When the state of the lattice anchor is changed to live, re-invoke subscribed analyses on the operati...
ChangeResult setToLive()
Set the state of the lattice anchor to live.
void print(raw_ostream &os) const override
Print the liveness.
AnalysisState(LatticeAnchor anchor)
Create the analysis state on the given lattice anchor.
void blockContentSubscribe(DataFlowAnalysis *analysis)
Subscribe an analysis to changes to the liveness.
This analysis state represents a set of live control-flow "predecessors" of a program point (either a...
ChangeResult setHasUnknownPredecessors()
Indicate that there are potentially unknown predecessors.
ValueRange getSuccessorInputs(Operation *predecessor) const
Get the successor inputs from a predecessor.
ArrayRef< Operation * > getKnownPredecessors() const
Get the known predecessors.
bool allPredecessorsKnown() const
Returns true if all predecessors are known.
AnalysisState(LatticeAnchor anchor)
Create the analysis state on the given lattice anchor.
void print(raw_ostream &os) const override
Print the known predecessors.
ChangeResult join(Operation *predecessor)
Add a known predecessor.
Include the generated interface declarations.
ChangeResult
A result type used to indicate if a change happened.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:131
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:126
Program point represents a specific location in the execution of a program.