MLIR  20.0.0git
DeadCodeAnalysis.cpp
Go to the documentation of this file.
1 //===- DeadCodeAnalysis.cpp - 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 
13 #include "mlir/IR/Attributes.h"
14 #include "mlir/IR/Block.h"
15 #include "mlir/IR/Diagnostics.h"
16 #include "mlir/IR/Location.h"
17 #include "mlir/IR/Operation.h"
18 #include "mlir/IR/SymbolTable.h"
19 #include "mlir/IR/Value.h"
20 #include "mlir/IR/ValueRange.h"
23 #include "mlir/Support/LLVM.h"
24 #include "llvm/Support/Casting.h"
25 #include <cassert>
26 #include <optional>
27 
28 using namespace mlir;
29 using namespace mlir::dataflow;
30 
31 //===----------------------------------------------------------------------===//
32 // Executable
33 //===----------------------------------------------------------------------===//
34 
36  if (live)
38  live = true;
39  return ChangeResult::Change;
40 }
41 
42 void Executable::print(raw_ostream &os) const {
43  os << (live ? "live" : "dead");
44 }
45 
46 void Executable::onUpdate(DataFlowSolver *solver) const {
48 
49  if (auto *block = llvm::dyn_cast_if_present<Block *>(point)) {
50  // Re-invoke the analyses on the block itself.
51  for (DataFlowAnalysis *analysis : subscribers)
52  solver->enqueue({block, analysis});
53  // Re-invoke the analyses on all operations in the block.
54  for (DataFlowAnalysis *analysis : subscribers)
55  for (Operation &op : *block)
56  solver->enqueue({&op, analysis});
57  } else if (auto *programPoint = llvm::dyn_cast_if_present<GenericProgramPoint *>(point)) {
58  // Re-invoke the analysis on the successor block.
59  if (auto *edge = dyn_cast<CFGEdge>(programPoint)) {
60  for (DataFlowAnalysis *analysis : subscribers)
61  solver->enqueue({edge->getTo(), analysis});
62  }
63  }
64 }
65 
66 //===----------------------------------------------------------------------===//
67 // PredecessorState
68 //===----------------------------------------------------------------------===//
69 
70 void PredecessorState::print(raw_ostream &os) const {
72  os << "(all) ";
73  os << "predecessors:\n";
74  for (Operation *op : getKnownPredecessors())
75  os << " " << *op << "\n";
76 }
77 
79  return knownPredecessors.insert(predecessor) ? ChangeResult::Change
81 }
82 
84  ChangeResult result = join(predecessor);
85  if (!inputs.empty()) {
86  ValueRange &curInputs = successorInputs[predecessor];
87  if (curInputs != inputs) {
88  curInputs = inputs;
89  result |= ChangeResult::Change;
90  }
91  }
92  return result;
93 }
94 
95 //===----------------------------------------------------------------------===//
96 // CFGEdge
97 //===----------------------------------------------------------------------===//
98 
100  return FusedLoc::get(
101  getFrom()->getParent()->getContext(),
102  {getFrom()->getParent()->getLoc(), getTo()->getParent()->getLoc()});
103 }
104 
105 void CFGEdge::print(raw_ostream &os) const {
106  getFrom()->print(os);
107  os << "\n -> \n";
108  getTo()->print(os);
109 }
110 
111 //===----------------------------------------------------------------------===//
112 // DeadCodeAnalysis
113 //===----------------------------------------------------------------------===//
114 
116  : DataFlowAnalysis(solver) {
117  registerPointKind<CFGEdge>();
118 }
119 
121  // Mark the top-level blocks as executable.
122  for (Region &region : top->getRegions()) {
123  if (region.empty())
124  continue;
125  auto *state = getOrCreate<Executable>(&region.front());
126  propagateIfChanged(state, state->setToLive());
127  }
128 
129  // Mark as overdefined the predecessors of symbol callables with potentially
130  // unknown predecessors.
131  initializeSymbolCallables(top);
132 
133  return initializeRecursively(top);
134 }
135 
136 void DeadCodeAnalysis::initializeSymbolCallables(Operation *top) {
137  analysisScope = top;
138  auto walkFn = [&](Operation *symTable, bool allUsesVisible) {
139  Region &symbolTableRegion = symTable->getRegion(0);
140  Block *symbolTableBlock = &symbolTableRegion.front();
141 
142  bool foundSymbolCallable = false;
143  for (auto callable : symbolTableBlock->getOps<CallableOpInterface>()) {
144  Region *callableRegion = callable.getCallableRegion();
145  if (!callableRegion)
146  continue;
147  auto symbol = dyn_cast<SymbolOpInterface>(callable.getOperation());
148  if (!symbol)
149  continue;
150 
151  // Public symbol callables or those for which we can't see all uses have
152  // potentially unknown callsites.
153  if (symbol.isPublic() || (!allUsesVisible && symbol.isNested())) {
154  auto *state = getOrCreate<PredecessorState>(callable);
155  propagateIfChanged(state, state->setHasUnknownPredecessors());
156  }
157  foundSymbolCallable = true;
158  }
159 
160  // Exit early if no eligible symbol callables were found in the table.
161  if (!foundSymbolCallable)
162  return;
163 
164  // Walk the symbol table to check for non-call uses of symbols.
165  std::optional<SymbolTable::UseRange> uses =
166  SymbolTable::getSymbolUses(&symbolTableRegion);
167  if (!uses) {
168  // If we couldn't gather the symbol uses, conservatively assume that
169  // we can't track information for any nested symbols.
170  return top->walk([&](CallableOpInterface callable) {
171  auto *state = getOrCreate<PredecessorState>(callable);
172  propagateIfChanged(state, state->setHasUnknownPredecessors());
173  });
174  }
175 
176  for (const SymbolTable::SymbolUse &use : *uses) {
177  if (isa<CallOpInterface>(use.getUser()))
178  continue;
179  // If a callable symbol has a non-call use, then we can't be guaranteed to
180  // know all callsites.
181  Operation *symbol = symbolTable.lookupSymbolIn(top, use.getSymbolRef());
182  auto *state = getOrCreate<PredecessorState>(symbol);
183  propagateIfChanged(state, state->setHasUnknownPredecessors());
184  }
185  };
186  SymbolTable::walkSymbolTables(top, /*allSymUsesVisible=*/!top->getBlock(),
187  walkFn);
188 }
189 
190 /// Returns true if the operation is a returning terminator in region
191 /// control-flow or the terminator of a callable region.
193  return !op->getNumSuccessors() &&
194  isa<RegionBranchOpInterface, CallableOpInterface>(op->getParentOp()) &&
195  op->getBlock()->getTerminator() == op;
196 }
197 
198 LogicalResult DeadCodeAnalysis::initializeRecursively(Operation *op) {
199  // Initialize the analysis by visiting every op with control-flow semantics.
200  if (op->getNumRegions() || op->getNumSuccessors() ||
201  isRegionOrCallableReturn(op) || isa<CallOpInterface>(op)) {
202  // When the liveness of the parent block changes, make sure to re-invoke the
203  // analysis on the op.
204  if (op->getBlock())
205  getOrCreate<Executable>(op->getBlock())->blockContentSubscribe(this);
206  // Visit the op.
207  if (failed(visit(op)))
208  return failure();
209  }
210  // Recurse on nested operations.
211  for (Region &region : op->getRegions())
212  for (Operation &op : region.getOps())
213  if (failed(initializeRecursively(&op)))
214  return failure();
215  return success();
216 }
217 
218 void DeadCodeAnalysis::markEdgeLive(Block *from, Block *to) {
219  auto *state = getOrCreate<Executable>(to);
220  propagateIfChanged(state, state->setToLive());
221  auto *edgeState = getOrCreate<Executable>(getProgramPoint<CFGEdge>(from, to));
222  propagateIfChanged(edgeState, edgeState->setToLive());
223 }
224 
225 void DeadCodeAnalysis::markEntryBlocksLive(Operation *op) {
226  for (Region &region : op->getRegions()) {
227  if (region.empty())
228  continue;
229  auto *state = getOrCreate<Executable>(&region.front());
230  propagateIfChanged(state, state->setToLive());
231  }
232 }
233 
234 LogicalResult DeadCodeAnalysis::visit(ProgramPoint point) {
235  if (point.is<Block *>())
236  return success();
237  auto *op = llvm::dyn_cast_if_present<Operation *>(point);
238  if (!op)
239  return emitError(point.getLoc(), "unknown program point kind");
240 
241  // If the parent block is not executable, there is nothing to do.
242  if (!getOrCreate<Executable>(op->getBlock())->isLive())
243  return success();
244 
245  // We have a live call op. Add this as a live predecessor of the callee.
246  if (auto call = dyn_cast<CallOpInterface>(op))
247  visitCallOperation(call);
248 
249  // Visit the regions.
250  if (op->getNumRegions()) {
251  // Check if we can reason about the region control-flow.
252  if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
253  visitRegionBranchOperation(branch);
254 
255  // Check if this is a callable operation.
256  } else if (auto callable = dyn_cast<CallableOpInterface>(op)) {
257  const auto *callsites = getOrCreateFor<PredecessorState>(op, callable);
258 
259  // If the callsites could not be resolved or are known to be non-empty,
260  // mark the callable as executable.
261  if (!callsites->allPredecessorsKnown() ||
262  !callsites->getKnownPredecessors().empty())
263  markEntryBlocksLive(callable);
264 
265  // Otherwise, conservatively mark all entry blocks as executable.
266  } else {
267  markEntryBlocksLive(op);
268  }
269  }
270 
271  if (isRegionOrCallableReturn(op)) {
272  if (auto branch = dyn_cast<RegionBranchOpInterface>(op->getParentOp())) {
273  // Visit the exiting terminator of a region.
274  visitRegionTerminator(op, branch);
275  } else if (auto callable =
276  dyn_cast<CallableOpInterface>(op->getParentOp())) {
277  // Visit the exiting terminator of a callable.
278  visitCallableTerminator(op, callable);
279  }
280  }
281  // Visit the successors.
282  if (op->getNumSuccessors()) {
283  // Check if we can reason about the control-flow.
284  if (auto branch = dyn_cast<BranchOpInterface>(op)) {
285  visitBranchOperation(branch);
286 
287  // Otherwise, conservatively mark all successors as exectuable.
288  } else {
289  for (Block *successor : op->getSuccessors())
290  markEdgeLive(op->getBlock(), successor);
291  }
292  }
293 
294  return success();
295 }
296 
297 void DeadCodeAnalysis::visitCallOperation(CallOpInterface call) {
298  Operation *callableOp = call.resolveCallable(&symbolTable);
299 
300  // A call to a externally-defined callable has unknown predecessors.
301  const auto isExternalCallable = [this](Operation *op) {
302  // A callable outside the analysis scope is an external callable.
303  if (!analysisScope->isAncestor(op))
304  return true;
305  // Otherwise, check if the callable region is defined.
306  if (auto callable = dyn_cast<CallableOpInterface>(op))
307  return !callable.getCallableRegion();
308  return false;
309  };
310 
311  // TODO: Add support for non-symbol callables when necessary. If the
312  // callable has non-call uses we would mark as having reached pessimistic
313  // fixpoint, otherwise allow for propagating the return values out.
314  if (isa_and_nonnull<SymbolOpInterface>(callableOp) &&
315  !isExternalCallable(callableOp)) {
316  // Add the live callsite.
317  auto *callsites = getOrCreate<PredecessorState>(callableOp);
318  propagateIfChanged(callsites, callsites->join(call));
319  } else {
320  // Mark this call op's predecessors as overdefined.
321  auto *predecessors = getOrCreate<PredecessorState>(call);
322  propagateIfChanged(predecessors, predecessors->setHasUnknownPredecessors());
323  }
324 }
325 
326 /// Get the constant values of the operands of an operation. If any of the
327 /// constant value lattices are uninitialized, return std::nullopt to indicate
328 /// the analysis should bail out.
329 static std::optional<SmallVector<Attribute>> getOperandValuesImpl(
330  Operation *op,
331  function_ref<const Lattice<ConstantValue> *(Value)> getLattice) {
332  SmallVector<Attribute> operands;
333  operands.reserve(op->getNumOperands());
334  for (Value operand : op->getOperands()) {
335  const Lattice<ConstantValue> *cv = getLattice(operand);
336  // If any of the operands' values are uninitialized, bail out.
337  if (cv->getValue().isUninitialized())
338  return {};
339  operands.push_back(cv->getValue().getConstantValue());
340  }
341  return operands;
342 }
343 
344 std::optional<SmallVector<Attribute>>
345 DeadCodeAnalysis::getOperandValues(Operation *op) {
346  return getOperandValuesImpl(op, [&](Value value) {
347  auto *lattice = getOrCreate<Lattice<ConstantValue>>(value);
348  lattice->useDefSubscribe(this);
349  return lattice;
350  });
351 }
352 
353 void DeadCodeAnalysis::visitBranchOperation(BranchOpInterface branch) {
354  // Try to deduce a single successor for the branch.
355  std::optional<SmallVector<Attribute>> operands = getOperandValues(branch);
356  if (!operands)
357  return;
358 
359  if (Block *successor = branch.getSuccessorForOperands(*operands)) {
360  markEdgeLive(branch->getBlock(), successor);
361  } else {
362  // Otherwise, mark all successors as executable and outgoing edges.
363  for (Block *successor : branch->getSuccessors())
364  markEdgeLive(branch->getBlock(), successor);
365  }
366 }
367 
368 void DeadCodeAnalysis::visitRegionBranchOperation(
369  RegionBranchOpInterface branch) {
370  // Try to deduce which regions are executable.
371  std::optional<SmallVector<Attribute>> operands = getOperandValues(branch);
372  if (!operands)
373  return;
374 
375  SmallVector<RegionSuccessor> successors;
376  branch.getEntrySuccessorRegions(*operands, successors);
377  for (const RegionSuccessor &successor : successors) {
378  // The successor can be either an entry block or the parent operation.
379  ProgramPoint point = successor.getSuccessor()
380  ? &successor.getSuccessor()->front()
381  : ProgramPoint(branch);
382  // Mark the entry block as executable.
383  auto *state = getOrCreate<Executable>(point);
384  propagateIfChanged(state, state->setToLive());
385  // Add the parent op as a predecessor.
386  auto *predecessors = getOrCreate<PredecessorState>(point);
388  predecessors,
389  predecessors->join(branch, successor.getSuccessorInputs()));
390  }
391 }
392 
393 void DeadCodeAnalysis::visitRegionTerminator(Operation *op,
394  RegionBranchOpInterface branch) {
395  std::optional<SmallVector<Attribute>> operands = getOperandValues(op);
396  if (!operands)
397  return;
398 
399  SmallVector<RegionSuccessor> successors;
400  if (auto terminator = dyn_cast<RegionBranchTerminatorOpInterface>(op))
401  terminator.getSuccessorRegions(*operands, successors);
402  else
403  branch.getSuccessorRegions(op->getParentRegion(), successors);
404 
405  // Mark successor region entry blocks as executable and add this op to the
406  // list of predecessors.
407  for (const RegionSuccessor &successor : successors) {
408  PredecessorState *predecessors;
409  if (Region *region = successor.getSuccessor()) {
410  auto *state = getOrCreate<Executable>(&region->front());
411  propagateIfChanged(state, state->setToLive());
412  predecessors = getOrCreate<PredecessorState>(&region->front());
413  } else {
414  // Add this terminator as a predecessor to the parent op.
415  predecessors = getOrCreate<PredecessorState>(branch);
416  }
417  propagateIfChanged(predecessors,
418  predecessors->join(op, successor.getSuccessorInputs()));
419  }
420 }
421 
422 void DeadCodeAnalysis::visitCallableTerminator(Operation *op,
423  CallableOpInterface callable) {
424  // Add as predecessors to all callsites this return op.
425  auto *callsites = getOrCreateFor<PredecessorState>(op, callable);
426  bool canResolve = op->hasTrait<OpTrait::ReturnLike>();
427  for (Operation *predecessor : callsites->getKnownPredecessors()) {
428  assert(isa<CallOpInterface>(predecessor));
429  auto *predecessors = getOrCreate<PredecessorState>(predecessor);
430  if (canResolve) {
431  propagateIfChanged(predecessors, predecessors->join(op));
432  } else {
433  // If the terminator is not a return-like, then conservatively assume we
434  // can't resolve the predecessor.
435  propagateIfChanged(predecessors,
436  predecessors->setHasUnknownPredecessors());
437  }
438  }
439 }
static std::optional< SmallVector< Attribute > > getOperandValuesImpl(Operation *op, function_ref< const Lattice< ConstantValue > *(Value)> getLattice)
Get the constant values of the operands of an operation.
static bool isRegionOrCallableReturn(Operation *op)
Returns true if the operation is a returning terminator in region control-flow or the terminator of a...
static MLIRContext * getContext(OpFoldResult val)
virtual void onUpdate(DataFlowSolver *solver) const
This function is called by the solver when the analysis state is updated to enqueue more work items.
ProgramPoint point
The program point to which the state belongs.
Block represents an ordered list of Operations.
Definition: Block.h:31
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition: Block.cpp:26
SuccessorRange getSuccessors()
Definition: Block.h:265
Operation * getTerminator()
Get the terminator operation of this block.
Definition: Block.cpp:243
void print(raw_ostream &os)
iterator_range< op_iterator< OpT > > getOps()
Return an iterator range over the operations within this block that are of 'OpT'.
Definition: Block.h:191
Base class for all data-flow analyses.
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to a state if it changed.
The general data-flow analysis solver.
void enqueue(WorkItem item)
Push a work item onto the worklist.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:63
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition: Operation.h:745
unsigned getNumSuccessors()
Definition: Operation.h:702
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition: Operation.h:793
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition: Operation.h:669
unsigned getNumOperands()
Definition: Operation.h:341
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition: Operation.h:234
Block * getBlock()
Returns the operation block that contains this operation.
Definition: Operation.h:213
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition: Operation.h:682
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition: Operation.h:672
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition: Operation.h:373
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
Definition: Operation.h:263
SuccessorRange getSuccessors()
Definition: Operation.h:699
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition: Operation.h:230
This class represents a successor of a region.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition: Region.h:26
Location getLoc()
Return a location for this region.
Definition: Region.cpp:31
Block & front()
Definition: Region.h:65
Operation * lookupSymbolIn(Operation *symbolTableOp, StringAttr symbol)
Look up a symbol with the specified name within the specified symbol table operation,...
This class represents a specific symbol use.
Definition: SymbolTable.h:183
static void walkSymbolTables(Operation *op, bool allSymUsesVisible, function_ref< void(Operation *, bool)> callback)
Walks all symbol table operations nested within, and including, op.
static std::optional< UseRange > getSymbolUses(Operation *from)
Get an iterator range for all of the uses, for any symbol, that are nested within the given operation...
This class provides an abstraction over the different types of ranges over Values.
Definition: ValueRange.h:381
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
Block * getTo() const
Get the target block.
Block * getFrom() const
Get the block from which the edge originates.
Location getLoc() const override
Get a fused location of both blocks.
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.
void onUpdate(DataFlowSolver *solver) const override
When the state of the program point is changed to live, re-invoke subscribed analyses on the operatio...
ChangeResult setToLive()
Set the state of the program point to live.
void print(raw_ostream &os) const override
Print the liveness.
This class represents a lattice holding a specific value of type ValueT.
ValueT & getValue()
Return the value held by this lattice.
This analysis state represents a set of live control-flow "predecessors" of a program point (either a...
bool allPredecessorsKnown() const
Returns true if all predecessors are known.
void print(raw_ostream &os) const override
Print the known predecessors.
ChangeResult join(Operation *predecessor)
Add a known predecessor.
ArrayRef< Operation * > getKnownPredecessors() const
Get the known predecessors.
Include the generated interface declarations.
ChangeResult
A result type used to indicate if a change happened.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
This trait indicates that a terminator operation is "return-like".
Fundamental IR components are supported as first-class program points.
Location getLoc() const
Get the source location of the program point.