49#include "llvm/ADT/STLExtras.h"
50#include "llvm/Support/Debug.h"
51#include "llvm/Support/DebugLog.h"
58#define DEBUG_TYPE "remove-dead-values"
61#define GEN_PASS_DEF_REMOVEDEADVALUESPASS
62#include "mlir/Transforms/Passes.h.inc"
78struct FunctionToCleanUp {
79 FunctionOpInterface funcOp;
80 BitVector nonLiveArgs;
81 BitVector nonLiveRets;
84struct ResultsToCleanup {
89struct OperandsToCleanup {
93 Operation *callee =
nullptr;
96 bool replaceWithPoison =
false;
99struct BlockArgsToCleanup {
101 BitVector nonLiveArgs;
104struct SuccessorOperandsToCleanup {
105 BranchOpInterface branch;
106 unsigned successorIndex;
107 BitVector nonLiveOperands;
110struct RDVFinalCleanupList {
111 SmallVector<Operation *> operations;
112 SmallVector<FunctionToCleanUp> functions;
113 SmallVector<OperandsToCleanup> operands;
114 SmallVector<ResultsToCleanup> results;
115 SmallVector<BlockArgsToCleanup> blocks;
116 SmallVector<SuccessorOperandsToCleanup> successorOperands;
125 for (
Value value : values) {
126 if (nonLiveSet.contains(value)) {
127 LDBG() <<
"Value " << value <<
" is already marked non-live (dead)";
133 LDBG() <<
"Value " << value
134 <<
" has no liveness info, conservatively considered live";
138 LDBG() <<
"Value " << value <<
" is live according to liveness analysis";
141 LDBG() <<
"Value " << value <<
" is dead according to liveness analysis";
150 BitVector lives(values.size(),
true);
152 for (
auto [
index, value] : llvm::enumerate(values)) {
153 if (nonLiveSet.contains(value)) {
155 LDBG() <<
"Value " << value
156 <<
" is already marked non-live (dead) at index " <<
index;
168 LDBG() <<
"Value " << value <<
" at index " <<
index
169 <<
" has no liveness info, conservatively considered live";
174 LDBG() <<
"Value " << value <<
" at index " <<
index
175 <<
" is dead according to liveness analysis";
177 LDBG() <<
"Value " << value <<
" at index " <<
index
178 <<
" is live according to liveness analysis";
189 const BitVector &nonLive) {
190 for (
auto [
index,
result] : llvm::enumerate(range)) {
193 nonLiveSet.insert(
result);
194 LDBG() <<
"Marking value " <<
result <<
" as non-live (dead) at index "
204 "expected the number of results in `op` and the size of `toErase` to "
206 for (
auto idx : toErase.set_bits())
226 RDVFinalCleanupList &cl) {
230 bool hasDeadOperand =
231 markLives(op->
getOperands(), nonLiveSet, la).flip().any();
232 if (hasDeadOperand) {
233 LDBG() <<
"Simple op has dead operands, so the op must be dead: "
236 assert(!hasLive(op->
getResults(), nonLiveSet, la) &&
237 "expected the op to have no live results");
238 cl.operations.push_back(op);
239 collectNonLiveValues(nonLiveSet, op->
getResults(),
245 LDBG() <<
"Simple op is not memory effect free or has live results, "
253 <<
"Simple op has all dead results and is memory effect free, scheduling "
256 cl.operations.push_back(op);
257 collectNonLiveValues(nonLiveSet, op->
getResults(),
271static void processFuncOp(FunctionOpInterface funcOp,
274 RDVFinalCleanupList &cl) {
275 LDBG() <<
"Processing function op: "
278 if (funcOp.isPublic() || funcOp.isExternal()) {
279 LDBG() <<
"Function is public or external, skipping: "
280 << funcOp.getOperation()->getName();
284 if (!llvm::all_of(users, llvm::IsaPred<CallOpInterface>)) {
292 BitVector nonLiveArgs = markLives(arguments, nonLiveSet, la);
293 nonLiveArgs = nonLiveArgs.flip();
296 for (
auto [
index, arg] : llvm::enumerate(arguments))
297 if (arg && nonLiveArgs[
index])
298 nonLiveSet.insert(arg);
308 cl.operands.push_back({callOp, BitVector(callOp->getNumOperands(),
false),
309 funcOp.getOperation()});
335 size_t numReturns = funcOp.getNumResults();
336 BitVector nonLiveRets(numReturns,
true);
338 assert(isa<CallOpInterface>(callOp) &&
"expected a call-like user");
339 BitVector liveCallRets = markLives(callOp->getResults(), nonLiveSet, la);
340 nonLiveRets &= liveCallRets.flip();
346 for (
Block &block : funcOp.getBlocks()) {
347 Operation *returnOp = block.getTerminator();
351 cl.operands.push_back({returnOp, nonLiveRets});
355 cl.functions.push_back({funcOp, nonLiveArgs, nonLiveRets});
361 assert(isa<CallOpInterface>(callOp) &&
"expected a call-like user");
362 cl.results.push_back({callOp, nonLiveRets});
363 collectNonLiveValues(nonLiveSet, callOp->getResults(), nonLiveRets);
386static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
389 RDVFinalCleanupList &cl) {
390 LDBG() <<
"Processing region branch op: "
400 !hasLive(regionBranchOp->getResults(), nonLiveSet, la)) {
401 cl.operations.push_back(regionBranchOp.getOperation());
425 regionBranchOp.getSuccessorOperandInputMapping(operandToSuccessorInputs);
428 for (
auto [opOperand, successorInputs] : operandToSuccessorInputs) {
431 auto markOperandDead = [&opOperand = opOperand, &deadOperandsPerOp]() {
436 BitVector &deadOperands =
438 .try_emplace(opOperand->getOwner(),
439 opOperand->getOwner()->getNumOperands(),
false)
441 deadOperands.set(opOperand->getOperandNumber());
445 if (!hasLive(opOperand->get(), nonLiveSet, la)) {
452 if (!hasLive(successorInputs, nonLiveSet, la))
456 for (
auto [op, deadOperands] : deadOperandsPerOp) {
457 cl.operands.push_back(
458 {op, deadOperands,
nullptr,
true});
476 RDVFinalCleanupList &cl) {
477 LDBG() <<
"Processing branch op: " << *branchOp;
480 BitVector deadNonForwardedOperands =
481 markLives(branchOp->getOperands(), nonLiveSet, la).flip();
482 unsigned numSuccessors = branchOp->getNumSuccessors();
483 for (
unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
485 branchOp.getSuccessorOperands(succIdx);
488 deadNonForwardedOperands[opOperand.getOperandNumber()] =
false;
490 if (deadNonForwardedOperands.any()) {
491 cl.operations.push_back(branchOp.getOperation());
495 for (
unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
500 branchOp.getSuccessorOperands(succIdx);
502 for (
unsigned operandIdx = 0; operandIdx < successorOperands.
size();
504 operandValues.push_back(successorOperands[operandIdx]);
508 BitVector successorNonLive =
509 markLives(operandValues, nonLiveSet, la).flip();
510 collectNonLiveValues(nonLiveSet, successorBlock->
getArguments(),
514 cl.blocks.push_back({successorBlock, successorNonLive});
515 cl.successorOperands.push_back({branchOp, succIdx, successorNonLive});
524 return ub::PoisonOp::create(
b, value.
getLoc(), value.
getType()).getResult();
530 void notifyOperationErased(Operation *op)
override {
531 if (
auto poisonOp = dyn_cast<ub::PoisonOp>(op))
532 poisonOps.erase(poisonOp);
534 void notifyOperationInserted(Operation *op,
535 OpBuilder::InsertPoint previous)
override {
536 if (
auto poisonOp = dyn_cast<ub::PoisonOp>(op))
537 poisonOps.insert(poisonOp);
545static void cleanUpDeadVals(
MLIRContext *ctx, RDVFinalCleanupList &list) {
546 LDBG() <<
"Starting cleanup of dead values...";
551 TrackingListener listener;
557 LDBG() <<
"Replacing dead operands with poison in " << list.operands.size()
559 for (OperandsToCleanup &o : list.operands) {
560 if (!o.replaceWithPoison || !o.nonLive.any())
563 os <<
"Replacing non-live operands [";
564 llvm::interleaveComma(o.nonLive.set_bits(), os);
565 os <<
"] with poison in operation: "
570 for (
auto deadIdx : o.nonLive.set_bits()) {
572 assert(operand &&
"expected non-null operand for poison replacement");
573 o.op->
setOperand(deadIdx, createPoisonedValue(rewriter, operand));
579 LDBG() <<
"Cleaning up " << list.blocks.size() <<
" block argument lists";
580 for (
auto &
b : list.blocks) {
582 if (
b.b->getNumArguments() !=
b.nonLiveArgs.size())
585 os <<
"Erasing non-live arguments [";
586 llvm::interleaveComma(
b.nonLiveArgs.set_bits(), os);
587 os <<
"] from block #" <<
b.b->computeBlockNumber() <<
" in region #"
588 <<
b.b->getParent()->getRegionNumber() <<
" of operation "
594 for (
int i =
b.nonLiveArgs.size() - 1; i >= 0; --i) {
595 if (!
b.nonLiveArgs[i])
597 b.b->getArgument(i).dropAllUses();
598 b.b->eraseArgument(i);
603 LDBG() <<
"Cleaning up " << list.successorOperands.size()
604 <<
" successor operand lists";
605 for (
auto &op : list.successorOperands) {
607 op.branch.getSuccessorOperands(op.successorIndex);
609 if (successorOperands.
size() != op.nonLiveOperands.size())
612 os <<
"Erasing non-live successor operands [";
613 llvm::interleaveComma(op.nonLiveOperands.set_bits(), os);
614 os <<
"] from successor " << op.successorIndex <<
" of branch: "
619 for (
int i = successorOperands.
size() - 1; i >= 0; --i) {
620 if (!op.nonLiveOperands[i])
622 successorOperands.
erase(i);
627 LDBG() <<
"Cleaning up " << list.functions.size() <<
" functions";
632 for (
auto &f : list.functions) {
633 LDBG() <<
"Cleaning up function: " << f.funcOp.getName() <<
" ("
634 << f.funcOp.getOperation() <<
")";
636 os <<
" Erasing non-live arguments [";
637 llvm::interleaveComma(f.nonLiveArgs.set_bits(), os);
639 os <<
" Erasing non-live return values [";
640 llvm::interleaveComma(f.nonLiveRets.set_bits(), os);
644 for (
auto deadIdx : f.nonLiveArgs.set_bits())
645 f.funcOp.getArgument(deadIdx).dropAllUses();
649 if (succeeded(f.funcOp.eraseArguments(f.nonLiveArgs))) {
651 if (f.nonLiveArgs.any())
652 erasedFuncArgs.try_emplace(f.funcOp.getOperation(), f.nonLiveArgs);
654 LDBG() <<
"Failed to erase arguments for function: "
655 << f.funcOp.getName();
657 (
void)f.funcOp.eraseResults(f.nonLiveRets);
661 LDBG() <<
"Cleaning up " << list.operands.size() <<
" operand lists";
662 for (OperandsToCleanup &o : list.operands) {
663 if (o.replaceWithPoison)
668 bool handledAsCall =
false;
669 if (o.callee && isa<CallOpInterface>(o.op)) {
670 auto call = cast<CallOpInterface>(o.op);
671 auto it = erasedFuncArgs.find(o.callee);
672 if (it != erasedFuncArgs.end()) {
673 const BitVector &deadArgIdxs = it->second;
677 for (
unsigned argIdx : llvm::reverse(deadArgIdxs.set_bits()))
683 if (o.nonLive.any()) {
685 int operandOffset = call.getArgOperands().getBeginOperandIndex();
686 for (
int argIdx : deadArgIdxs.set_bits()) {
687 int operandNumber = operandOffset + argIdx;
688 if (operandNumber <
static_cast<int>(o.nonLive.size()))
689 o.nonLive.reset(operandNumber);
692 handledAsCall =
true;
699 if (!handledAsCall && o.nonLive.any()) {
701 os <<
"Erasing non-live operands [";
702 llvm::interleaveComma(o.nonLive.set_bits(), os);
703 os <<
"] from operation: "
712 LDBG() <<
"Cleaning up " << list.results.size() <<
" result lists";
713 for (
auto &r : list.results) {
715 os <<
"Erasing non-live results [";
716 llvm::interleaveComma(r.nonLive.set_bits(), os);
717 os <<
"] from operation: "
721 dropUsesAndEraseResults(rewriter, r.op, r.nonLive);
725 LDBG() <<
"Cleaning up " << list.operations.size() <<
" operations";
727 LDBG() <<
"Erasing operation: "
733 ub::UnreachableOp::create(rewriter, op->
getLoc());
746 for (
Value opResult : opResults) {
749 if (opResult.use_empty())
753 Value poisonedValue = createPoisonedValue(rewriter, opResult);
762 for (ub::PoisonOp poisonOp : listener.poisonOps) {
763 if (poisonOp.use_empty())
767 LDBG() <<
"Finished cleanup of dead values";
770struct RemoveDeadValues
772 using impl::RemoveDeadValuesPassBase<
773 RemoveDeadValues>::RemoveDeadValuesPassBase;
774 void runOnOperation()
override;
778void RemoveDeadValues::runOnOperation() {
779 auto &la = getAnalysis<RunLivenessAnalysis>();
780 Operation *module = getOperation();
786 SymbolTableCollection symbolTableCollection;
787 SymbolUserMap symbolUserMap(symbolTableCollection, module);
795 RDVFinalCleanupList finalCleanupList;
797 module->walk([&](Operation *op) {
798 if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
799 processFuncOp(funcOp, symbolUserMap, la, deadVals, finalCleanupList);
800 }
else if (
auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
801 processRegionBranchOp(regionBranchOp, la, deadVals, finalCleanupList);
802 }
else if (
auto branchOp = dyn_cast<BranchOpInterface>(op)) {
803 processBranchOp(branchOp, la, deadVals, finalCleanupList);
804 }
else if (op->
hasTrait<::mlir::OpTrait::IsTerminator>()) {
807 }
else if (isa<CallOpInterface>(op)) {
811 processSimpleOp(op, la, deadVals, finalCleanupList);
815 MLIRContext *context =
module->getContext();
816 cleanUpDeadVals(context, finalCleanupList);
822 SmallVector<Operation *> opsToCanonicalize;
823 module->walk([&](RegionBranchOpInterface regionBranchOp) {
824 opsToCanonicalize.push_back(regionBranchOp.getOperation());
827 RewritePatternSet owningPatterns(context);
829 for (Operation *op : opsToCanonicalize)
831 if (populatedPatterns.insert(*info).second)
832 info->getCanonicalizationPatterns(owningPatterns, context);
834 std::move(owningPatterns)))) {
835 module->emitError("greedy pattern rewrite failed to converge");
Block represents an ordered list of Operations.
BlockArgListType getArguments()
Block * getSuccessor(unsigned i)
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
MLIRContext is the top-level object for a collection of MLIR operations.
This class provides a mutable adaptor for a range of operands.
void erase(unsigned subStart, unsigned subLen=1)
Erase the operands within the given sub-range.
This class helps build Operations.
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
This class represents an operand of an operation.
Set of flags used to control the behavior of the various IR print methods (e.g.
This class provides the API for ops that are known to be terminators.
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.
Value getOperand(unsigned idx)
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
void dropAllUses()
Drop all uses of results of this operation.
void setOperand(unsigned idx, Value value)
void eraseOperands(unsigned idx, unsigned length=1)
Erase the operands starting at position idx and ending at position 'idx'+'length'.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Location getLoc()
The source location the operation was defined or derived from.
std::optional< RegisteredOperationName > getRegisteredInfo()
If this operation has a registered operation description, return it.
unsigned getNumOperands()
operand_range getOperands()
Returns an iterator on the underlying Value's.
result_range getResults()
unsigned getNumResults()
Return the number of results held by this operation.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
Operation * eraseOpResults(Operation *op, const BitVector &eraseIndices)
Erase the specified results of the given operation.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
This class models how operands are forwarded to block arguments in control flow.
void erase(unsigned subStart, unsigned subLen=1)
Erase operands forwarded to the successor.
MutableOperandRange getMutableForwardedOperands() const
Get the range of operands that are simply forwarded to the successor.
unsigned size() const
Returns the amount of operands passed to the successor.
This class represents a map of symbols to users, and provides efficient implementations of symbol que...
ArrayRef< Operation * > getUsers(Operation *symbol) const
Return the users of the provided symbol 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...
bool use_empty() const
Returns true if this value has no uses.
void dropAllUses()
Drop all uses of this object from their respective owners.
Type getType() const
Return the type of this value.
Location getLoc() const
Return the location of this value.
Include the generated interface declarations.
DenseMap< OpOperand *, SmallVector< Value > > RegionBranchSuccessorMapping
A mapping from successor operands to successor inputs.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
LogicalResult applyOpPatternsGreedily(ArrayRef< Operation * > ops, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr, bool *allErased=nullptr)
Rewrite the specified ops by repeatedly applying the highest benefit patterns in a greedy worklist dr...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
This trait indicates that a terminator operation is "return-like".
This lattice represents, for a given value, whether or not it is "live".
Runs liveness analysis on the IR defined by op.
const Liveness * getLiveness(Value val)