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()) {
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) {
135 registerAnchorKind<CFGEdge>();
139 LDBG() <<
"Initializing DeadCodeAnalysis for top-level op: "
148 LDBG() <<
"Marked entry block live for region in op: "
154 initializeSymbolCallables(top);
156 return initializeRecursively(top);
159 void DeadCodeAnalysis::initializeSymbolCallables(
Operation *top) {
160 LDBG() <<
"[init] Entering initializeSymbolCallables for top-level op: "
164 auto walkFn = [&](
Operation *symTable,
bool allUsesVisible) {
165 LDBG() <<
"[init] Processing symbol table op: "
168 Block *symbolTableBlock = &symbolTableRegion.
front();
170 bool foundSymbolCallable =
false;
171 for (
auto callable : symbolTableBlock->
getOps<CallableOpInterface>()) {
172 LDBG() <<
"[init] Found CallableOpInterface: "
175 Region *callableRegion = callable.getCallableRegion();
178 auto symbol = dyn_cast<SymbolOpInterface>(callable.getOperation());
184 if (symbol.isPublic() || (!allUsesVisible && symbol.isNested())) {
188 LDBG() <<
"[init] Marked callable as having unknown predecessors: "
192 foundSymbolCallable =
true;
196 if (!foundSymbolCallable)
200 std::optional<SymbolTable::UseRange> uses =
205 LDBG() <<
"[init] Could not gather symbol uses, conservatively marking "
206 "all nested callables as having unknown predecessors";
207 return top->
walk([&](CallableOpInterface callable) {
211 LDBG() <<
"[init] Marked nested callable as "
212 "having unknown predecessors: "
219 if (isa<CallOpInterface>(use.getUser()))
228 LDBG() <<
"[init] Found non-call use for symbol, "
229 "marked as having unknown predecessors: "
235 LDBG() <<
"[init] Finished initializeSymbolCallables for top-level op: "
243 isa<RegionBranchOpInterface, CallableOpInterface>(op->
getParentOp()) &&
247 LogicalResult DeadCodeAnalysis::initializeRecursively(
Operation *op) {
248 LDBG() <<
"[init] Entering initializeRecursively for op: "
253 LDBG() <<
"[init] Visiting op with control-flow semantics: "
259 ->blockContentSubscribe(
this);
269 bool savedHasSymbolTable = hasSymbolTable;
270 auto restoreHasSymbolTable =
271 llvm::make_scope_exit([&]() { hasSymbolTable = savedHasSymbolTable; });
273 hasSymbolTable =
true;
276 LDBG() <<
"[init] Recursing into region of op: "
278 for (
Operation &nestedOp : region.getOps()) {
279 LDBG() <<
"[init] Recursing into nested op: "
281 if (
failed(initializeRecursively(&nestedOp)))
286 LDBG() <<
"[init] Finished initializeRecursively for op: "
291 void DeadCodeAnalysis::markEdgeLive(
Block *from,
Block *to) {
292 LDBG() <<
"Marking edge live from block " << from <<
" to block " << to;
296 getOrCreate<Executable>(getLatticeAnchor<CFGEdge>(from, to));
300 void DeadCodeAnalysis::markEntryBlocksLive(
Operation *op) {
301 LDBG() <<
"Marking entry blocks live for op: "
309 LDBG() <<
"Marked entry block live for region in op: "
315 LDBG() <<
"Visiting program point: " << *point;
319 LDBG() <<
"Visiting operation: "
326 LDBG() <<
"Parent block not live, skipping op: "
332 if (
auto call = dyn_cast<CallOpInterface>(op)) {
333 LDBG() <<
"Visiting call operation: "
335 visitCallOperation(call);
341 if (
auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
342 LDBG() <<
"Visiting region branch operation: "
344 visitRegionBranchOperation(branch);
347 }
else if (
auto callable = dyn_cast<CallableOpInterface>(op)) {
348 LDBG() <<
"Visiting callable operation: "
350 const auto *callsites = getOrCreateFor<PredecessorState>(
355 if (!callsites->allPredecessorsKnown() ||
356 !callsites->getKnownPredecessors().empty())
357 markEntryBlocksLive(callable);
361 LDBG() <<
"Marking all entry blocks live for op: "
363 markEntryBlocksLive(op);
368 if (
auto branch = dyn_cast<RegionBranchOpInterface>(op->
getParentOp())) {
369 LDBG() <<
"Visiting region terminator: "
372 visitRegionTerminator(op, branch);
373 }
else if (
auto callable =
374 dyn_cast<CallableOpInterface>(op->
getParentOp())) {
375 LDBG() <<
"Visiting callable terminator: "
378 visitCallableTerminator(op, callable);
384 if (
auto branch = dyn_cast<BranchOpInterface>(op)) {
385 LDBG() <<
"Visiting branch operation: "
387 visitBranchOperation(branch);
391 LDBG() <<
"Marking all successors live for op: "
394 markEdgeLive(op->
getBlock(), successor);
401 void DeadCodeAnalysis::visitCallOperation(CallOpInterface call) {
402 LDBG() <<
"visitCallOperation: "
407 callableOp = call.resolveCallableInTable(&symbolTable);
410 <<
"No symbol table present in analysis scope, can't resolve callable";
413 const auto isExternalCallable = [
this](
Operation *op) {
418 if (
auto callable = dyn_cast<CallableOpInterface>(op))
419 return !callable.getCallableRegion();
426 if (isa_and_nonnull<SymbolOpInterface>(callableOp) &&
427 !isExternalCallable(callableOp)) {
432 LDBG() <<
"Added callsite as predecessor for callable: "
439 LDBG() <<
"Marked call op's predecessors as unknown for: "
447 std::optional<SmallVector<Attribute>>
448 DeadCodeAnalysis::getOperandValues(
Operation *op) {
455 if (cv->
getValue().isUninitialized())
457 operands.push_back(cv->
getValue().getConstantValue());
462 void DeadCodeAnalysis::visitBranchOperation(BranchOpInterface branch) {
463 LDBG() <<
"visitBranchOperation: "
466 std::optional<SmallVector<Attribute>> operands = getOperandValues(branch);
470 if (
Block *successor = branch.getSuccessorForOperands(*operands)) {
471 markEdgeLive(branch->getBlock(), successor);
472 LDBG() <<
"Branch has single successor: " << successor;
476 markEdgeLive(branch->getBlock(), successor);
477 LDBG() <<
"Branch has multiple/all successors live";
481 void DeadCodeAnalysis::visitRegionBranchOperation(
482 RegionBranchOpInterface branch) {
483 LDBG() <<
"visitRegionBranchOperation: "
486 std::optional<SmallVector<Attribute>> operands = getOperandValues(branch);
491 branch.getEntrySuccessorRegions(*operands, successors);
493 visitRegionBranchEdges(branch, branch.getOperation(), successors);
496 void DeadCodeAnalysis::visitRegionTerminator(
Operation *op,
497 RegionBranchOpInterface branch) {
498 LDBG() <<
"visitRegionTerminator: " << *op;
499 std::optional<SmallVector<Attribute>> operands = getOperandValues(op);
504 if (
auto terminator = dyn_cast<RegionBranchTerminatorOpInterface>(op))
505 terminator.getSuccessorRegions(*operands, successors);
509 visitRegionBranchEdges(branch, op, successors);
512 void DeadCodeAnalysis::visitRegionBranchEdges(
513 RegionBranchOpInterface regionBranchOp,
Operation *predecessorOp,
518 successor.getSuccessor()
523 auto *state = getOrCreate<Executable>(point);
525 LDBG() <<
"Marked region successor live: " << point;
528 auto *predecessors = getOrCreate<PredecessorState>(point);
531 predecessors->join(predecessorOp, successor.getSuccessorInputs()));
532 LDBG() <<
"Added region branch as predecessor for successor: " << point;
536 void DeadCodeAnalysis::visitCallableTerminator(
Operation *op,
537 CallableOpInterface callable) {
538 LDBG() <<
"visitCallableTerminator: " << *op;
540 auto *callsites = getOrCreateFor<PredecessorState>(
543 for (
Operation *predecessor : callsites->getKnownPredecessors()) {
544 assert(isa<CallOpInterface>(predecessor));
549 LDBG() <<
"Added callable terminator as predecessor for callsite: "
555 predecessors->setHasUnknownPredecessors());
556 LDBG() <<
"Could not resolve callable terminator for callsite: "
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.
LatticeAnchor anchor
The lattice anchor to which the state belongs.
Block represents an ordered list of Operations.
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
SuccessorRange getSuccessors()
Operation * getTerminator()
Get the terminator operation of this block.
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'.
Base class for all data-flow analyses.
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to a state if it changed.
ProgramPoint * getProgramPointAfter(Operation *op)
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
The general data-flow analysis solver.
void enqueue(WorkItem item)
Push a work item onto the worklist.
ProgramPoint * getProgramPointAfter(Operation *op)
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
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.
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
unsigned getNumSuccessors()
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),...
unsigned getNumRegions()
Returns the number of regions held by this operation.
unsigned getNumOperands()
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Block * getBlock()
Returns the operation block that contains this operation.
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
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.
SuccessorRange getSuccessors()
Region * getParentRegion()
Returns the region to which the instruction belongs.
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.
Location getLoc()
Return a location for this region.
virtual 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.
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.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
void useDefSubscribe(DataFlowAnalysis *analysis)
Subscribe an analysis to updates of the lattice.
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 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.
This class represents a lattice holding a specific value of type ValueT.
ValueT & getValue()
Return the value held by this lattice.
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.
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".
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.