25#include "llvm/ADT/ScopeExit.h"
26#include "llvm/Support/Casting.h"
27#include "llvm/Support/Debug.h"
28#include "llvm/Support/DebugLog.h"
32#define DEBUG_TYPE "dead-code-analysis"
49 os << (live ?
"live" :
"dead");
56 if (pp->isBlockStart()) {
59 solver->
enqueue({pp, analysis});
65 }
else if (
auto *latticeAnchor =
66 llvm::dyn_cast_if_present<GenericLatticeAnchor *>(
anchor)) {
68 if (
auto *edge = dyn_cast<CFGEdge>(latticeAnchor)) {
83 os <<
"predecessors:";
103 if (!inputs.empty()) {
104 ValueRange &curInputs = successorInputs[predecessor];
105 if (curInputs != inputs) {
118 return FusedLoc::get(
139 LDBG() <<
"Initializing DeadCodeAnalysis for top-level op: "
148 LDBG() <<
"Marked entry block live for region in op: "
153 if (isa<CallableOpInterface>(top)) {
156 LDBG() <<
"[init] Marked callable root as having unknown predecessors: "
162 initializeSymbolCallables(top);
164 return initializeRecursively(top);
167void DeadCodeAnalysis::initializeSymbolCallables(
Operation *top) {
168 LDBG() <<
"[init] Entering initializeSymbolCallables for top-level op: "
172 auto walkFn = [&](
Operation *symTable,
bool allUsesVisible) {
173 LDBG() <<
"[init] Processing symbol table op: "
176 Block *symbolTableBlock = &symbolTableRegion.
front();
178 bool foundSymbolCallable =
false;
179 for (
auto callable : symbolTableBlock->
getOps<CallableOpInterface>()) {
180 LDBG() <<
"[init] Found CallableOpInterface: "
183 Region *callableRegion = callable.getCallableRegion();
186 auto symbol = dyn_cast<SymbolOpInterface>(callable.getOperation());
192 if (symbol.isPublic() || (!allUsesVisible && symbol.isNested())) {
196 LDBG() <<
"[init] Marked callable as having unknown predecessors: "
200 foundSymbolCallable =
true;
204 if (!foundSymbolCallable)
208 std::optional<SymbolTable::UseRange> uses =
213 LDBG() <<
"[init] Could not gather symbol uses, conservatively marking "
214 "all nested callables as having unknown predecessors";
215 return top->
walk([&](CallableOpInterface callable) {
219 LDBG() <<
"[init] Marked nested callable as "
220 "having unknown predecessors: "
226 for (
const SymbolTable::SymbolUse &use : *uses) {
227 if (isa<CallOpInterface>(use.getUser()))
231 Operation *symbol = symbolTable.lookupSymbolIn(top, use.getSymbolRef());
236 LDBG() <<
"[init] Found non-call use for symbol, "
237 "marked as having unknown predecessors: "
238 << OpWithFlags(symbol, OpPrintingFlags().skipRegions());
243 LDBG() <<
"[init] Finished initializeSymbolCallables for top-level op: "
244 << OpWithFlags(top, OpPrintingFlags().skipRegions());
251 isa<RegionBranchOpInterface, CallableOpInterface>(op->
getParentOp()) &&
256LogicalResult DeadCodeAnalysis::initializeRecursively(Operation *op) {
257 LDBG() <<
"[init] Entering initializeRecursively for op: "
258 << OpWithFlags(op, OpPrintingFlags().skipRegions());
262 LDBG() <<
"[init] Visiting op with control-flow semantics: "
263 << OpWithFlags(op, OpPrintingFlags().skipRegions());
268 ->blockContentSubscribe(
this);
278 bool savedHasSymbolTable = hasSymbolTable;
279 llvm::scope_exit restoreHasSymbolTable(
280 [&]() { hasSymbolTable = savedHasSymbolTable; });
281 if (!hasSymbolTable && op->
hasTrait<OpTrait::SymbolTable>())
282 hasSymbolTable =
true;
285 LDBG() <<
"[init] Recursing into region of op: "
286 << OpWithFlags(op, OpPrintingFlags().skipRegions());
287 for (Operation &nestedOp : region.getOps()) {
288 LDBG() <<
"[init] Recursing into nested op: "
289 << OpWithFlags(&nestedOp, OpPrintingFlags().skipRegions());
290 if (
failed(initializeRecursively(&nestedOp)))
295 LDBG() <<
"[init] Finished initializeRecursively for op: "
296 << OpWithFlags(op, OpPrintingFlags().skipRegions());
300void DeadCodeAnalysis::markEdgeLive(
Block *from,
Block *to) {
301 LDBG() <<
"Marking edge live from block " << from <<
" to block " << to;
309void DeadCodeAnalysis::markEntryBlocksLive(Operation *op) {
310 LDBG() <<
"Marking entry blocks live for op: "
311 << OpWithFlags(op, OpPrintingFlags().skipRegions());
318 LDBG() <<
"Marked entry block live for region in op: "
319 << OpWithFlags(op, OpPrintingFlags().skipRegions());
324 LDBG() <<
"Visiting program point: " << *point;
328 LDBG() <<
"Visiting operation: "
335 LDBG() <<
"Parent block not live, skipping op: "
341 if (
auto call = dyn_cast<CallOpInterface>(op)) {
342 LDBG() <<
"Visiting call operation: "
344 visitCallOperation(call);
350 if (
auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
351 LDBG() <<
"Visiting region branch operation: "
353 visitRegionBranchOperation(branch);
356 }
else if (
auto callable = dyn_cast<CallableOpInterface>(op)) {
357 LDBG() <<
"Visiting callable operation: "
364 if (!callsites->allPredecessorsKnown() ||
365 !callsites->getKnownPredecessors().empty())
366 markEntryBlocksLive(callable);
370 LDBG() <<
"Marking all entry blocks live for op: "
372 markEntryBlocksLive(op);
377 if (
auto branch = dyn_cast<RegionBranchOpInterface>(op->
getParentOp())) {
378 LDBG() <<
"Visiting region terminator: "
381 visitRegionTerminator(op, branch);
382 }
else if (
auto callable =
383 dyn_cast<CallableOpInterface>(op->
getParentOp())) {
384 LDBG() <<
"Visiting callable terminator: "
387 visitCallableTerminator(op, callable);
393 if (
auto branch = dyn_cast<BranchOpInterface>(op)) {
394 LDBG() <<
"Visiting branch operation: "
396 visitBranchOperation(branch);
400 LDBG() <<
"Marking all successors live for op: "
403 markEdgeLive(op->
getBlock(), successor);
410void DeadCodeAnalysis::visitCallOperation(CallOpInterface call) {
411 LDBG() <<
"visitCallOperation: "
416 callableOp = call.resolveCallableInTable(&symbolTable);
419 <<
"No symbol table present in analysis scope, can't resolve callable";
422 const auto isExternalCallable = [
this](
Operation *op) {
427 if (
auto callable = dyn_cast<CallableOpInterface>(op))
428 return !callable.getCallableRegion();
435 if (isa_and_nonnull<SymbolOpInterface>(callableOp) &&
436 !isExternalCallable(callableOp)) {
441 LDBG() <<
"Added callsite as predecessor for callable: "
448 LDBG() <<
"Marked call op's predecessors as unknown for: "
456std::optional<SmallVector<Attribute>>
457DeadCodeAnalysis::getOperandValues(Operation *op) {
458 SmallVector<Attribute> operands;
464 if (cv->
getValue().isUninitialized())
466 operands.push_back(cv->
getValue().getConstantValue());
471void DeadCodeAnalysis::visitBranchOperation(BranchOpInterface branch) {
472 LDBG() <<
"visitBranchOperation: "
473 << OpWithFlags(branch.getOperation(), OpPrintingFlags().skipRegions());
475 std::optional<SmallVector<Attribute>> operands = getOperandValues(branch);
479 if (
Block *successor = branch.getSuccessorForOperands(*operands)) {
480 markEdgeLive(branch->getBlock(), successor);
481 LDBG() <<
"Branch has single successor: " << successor;
484 for (
Block *successor : branch->getSuccessors())
485 markEdgeLive(branch->getBlock(), successor);
486 LDBG() <<
"Branch has multiple/all successors live";
490void DeadCodeAnalysis::visitRegionBranchOperation(
491 RegionBranchOpInterface branch) {
492 LDBG() <<
"visitRegionBranchOperation: "
493 << OpWithFlags(branch.getOperation(), OpPrintingFlags().skipRegions());
495 std::optional<SmallVector<Attribute>> operands = getOperandValues(branch);
499 SmallVector<RegionSuccessor> successors;
500 branch.getEntrySuccessorRegions(*operands, successors);
502 visitRegionBranchEdges(branch, branch.getOperation(), successors);
505void DeadCodeAnalysis::visitRegionTerminator(Operation *op,
506 RegionBranchOpInterface branch) {
507 LDBG() <<
"visitRegionTerminator: " << *op;
508 std::optional<SmallVector<Attribute>> operands = getOperandValues(op);
512 SmallVector<RegionSuccessor> successors;
513 auto terminator = dyn_cast<RegionBranchTerminatorOpInterface>(op);
516 terminator.getSuccessorRegions(*operands, successors);
517 visitRegionBranchEdges(branch, op, successors);
520void DeadCodeAnalysis::visitRegionBranchEdges(
521 RegionBranchOpInterface regionBranchOp, Operation *predecessorOp,
522 const SmallVector<RegionSuccessor> &successors) {
523 for (
const RegionSuccessor &successor : successors) {
526 if (!successor.isParent() && successor.getSuccessor()->empty())
528 ProgramPoint *point =
536 LDBG() <<
"Marked region successor live: " << *point;
542 predecessors->join(predecessorOp,
543 regionBranchOp.getSuccessorInputs(successor)));
544 LDBG() <<
"Added region branch as predecessor for successor: " << *point;
548void DeadCodeAnalysis::visitCallableTerminator(Operation *op,
549 CallableOpInterface callable) {
550 LDBG() <<
"visitCallableTerminator: " << *op;
554 bool canResolve = op->
hasTrait<OpTrait::ReturnLike>();
555 for (Operation *predecessor : callsites->getKnownPredecessors()) {
556 assert(isa<CallOpInterface>(predecessor));
561 LDBG() <<
"Added callable terminator as predecessor for callsite: "
562 << OpWithFlags(predecessor, OpPrintingFlags().skipRegions());
567 predecessors->setHasUnknownPredecessors());
568 LDBG() <<
"Could not resolve callable terminator for callsite: "
569 << OpWithFlags(predecessor, OpPrintingFlags().skipRegions());
static bool isRegionOrCallableReturn(Operation *op)
Returns true if the operation is a returning terminator in region control-flow or the terminator of a...
virtual void onUpdate(DataFlowSolver *solver) const
This function is called by the solver when the analysis state is updated to enqueue more work items.
LatticeAnchor anchor
The lattice anchor to which the state belongs.
friend class DataFlowSolver
Allow the framework to access the dependents.
Block represents an ordered list of Operations.
iterator_range< op_iterator< OpT > > getOps()
Return an iterator range over the operations within this block that are of 'OpT'.
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Operation * getTerminator()
Get the terminator operation of this block.
void print(raw_ostream &os)
bool mightHaveTerminator()
Return "true" if this block might have a terminator.
Base class for all data-flow analyses.
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to a state if it changed.
StateT * getOrCreate(AnchorT anchor)
Get the analysis state associated with the lattice anchor.
ProgramPoint * getProgramPointAfter(Operation *op)
DataFlowAnalysis(DataFlowSolver &solver)
Create an analysis with a reference to the parent solver.
AnchorT * getLatticeAnchor(Args &&...args)
Get or create a custom lattice anchor.
void registerAnchorKind()
Register a custom lattice anchor class.
friend class DataFlowSolver
Allow the data-flow solver to access the internals of this class.
const StateT * getOrCreateFor(ProgramPoint *dependent, AnchorT anchor)
Get a read-only analysis state for the given point and create a dependency on dependent.
void enqueue(WorkItem item)
Push a work item onto the worklist.
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
ProgramPoint * getProgramPointAfter(Operation *op)
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Set of flags used to control the behavior of the various IR print methods (e.g.
A trait used to provide symbol table functionalities to a region operation.
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Operation is the basic unit of execution within MLIR.
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
unsigned getNumSuccessors()
Block * getBlock()
Returns the operation block that contains this operation.
unsigned getNumRegions()
Returns the number of regions held by this operation.
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
unsigned getNumOperands()
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
operand_range getOperands()
Returns an iterator on the underlying Value's.
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
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),...
SuccessorRange getSuccessors()
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Location getLoc()
Return a location for this region.
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.
void useDefSubscribe(DataFlowAnalysis *analysis)
Subscribe an analysis to updates of the lattice.
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.
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.
ValueT & getValue()
Return the value held by this lattice.
ArrayRef< Operation * > getKnownPredecessors() const
Get the known predecessors.
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.
Include the generated interface declarations.
ChangeResult
A result type used to indicate if a change happened.
Program point represents a specific location in the execution of a program.
bool isBlockStart() const
Operation * getPrevOp() const
Get the previous operation of this program point.