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);
340 BitVector liveCallRets = markLives(
341 cast<CallOpInterface>(callOp).getForwardedResults(), nonLiveSet, la);
342 nonLiveRets &= liveCallRets.flip();
348 for (
Block &block : funcOp.getBlocks()) {
349 Operation *returnOp = block.getTerminator();
353 cl.operands.push_back({returnOp, nonLiveRets});
357 cl.functions.push_back({funcOp, nonLiveArgs, nonLiveRets});
367 cast<CallOpInterface>(callOp).getForwardedResults();
368 BitVector nonLiveCallResults(callOp->getNumResults(),
false);
369 for (
int index : nonLiveRets.set_bits())
370 nonLiveCallResults.set(forwardedResults[
index].getResultNumber());
371 cl.results.push_back({callOp, nonLiveCallResults});
372 collectNonLiveValues(nonLiveSet, callOp->getResults(), nonLiveCallResults);
395static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
398 RDVFinalCleanupList &cl) {
399 LDBG() <<
"Processing region branch op: "
409 !hasLive(regionBranchOp->getResults(), nonLiveSet, la)) {
410 cl.operations.push_back(regionBranchOp.getOperation());
434 regionBranchOp.getSuccessorOperandInputMapping(operandToSuccessorInputs);
437 for (
auto [opOperand, successorInputs] : operandToSuccessorInputs) {
440 auto markOperandDead = [&opOperand = opOperand, &deadOperandsPerOp]() {
445 BitVector &deadOperands =
447 .try_emplace(opOperand->getOwner(),
448 opOperand->getOwner()->getNumOperands(),
false)
450 deadOperands.set(opOperand->getOperandNumber());
454 if (!hasLive(opOperand->get(), nonLiveSet, la)) {
461 if (!hasLive(successorInputs, nonLiveSet, la))
465 for (
auto [op, deadOperands] : deadOperandsPerOp) {
466 cl.operands.push_back(
467 {op, deadOperands,
nullptr,
true});
485 RDVFinalCleanupList &cl) {
486 LDBG() <<
"Processing branch op: " << *branchOp;
489 BitVector deadNonForwardedOperands =
490 markLives(branchOp->getOperands(), nonLiveSet, la).flip();
491 unsigned numSuccessors = branchOp->getNumSuccessors();
492 for (
unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
494 branchOp.getSuccessorOperands(succIdx);
497 deadNonForwardedOperands[opOperand.getOperandNumber()] =
false;
499 if (deadNonForwardedOperands.any()) {
500 cl.operations.push_back(branchOp.getOperation());
504 for (
unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
509 branchOp.getSuccessorOperands(succIdx);
511 for (
unsigned operandIdx = 0; operandIdx < successorOperands.
size();
513 operandValues.push_back(successorOperands[operandIdx]);
517 BitVector successorNonLive =
518 markLives(operandValues, nonLiveSet, la).flip();
519 collectNonLiveValues(nonLiveSet, successorBlock->
getArguments(),
523 cl.blocks.push_back({successorBlock, successorNonLive});
524 cl.successorOperands.push_back({branchOp, succIdx, successorNonLive});
533 return ub::PoisonOp::create(
b, value.
getLoc(), value.
getType()).getResult();
539 void notifyOperationErased(Operation *op)
override {
540 if (
auto poisonOp = dyn_cast<ub::PoisonOp>(op))
541 poisonOps.erase(poisonOp);
543 void notifyOperationInserted(Operation *op,
544 OpBuilder::InsertPoint previous)
override {
545 if (
auto poisonOp = dyn_cast<ub::PoisonOp>(op))
546 poisonOps.insert(poisonOp);
554static void cleanUpDeadVals(
MLIRContext *ctx, RDVFinalCleanupList &list) {
555 LDBG() <<
"Starting cleanup of dead values...";
560 TrackingListener listener;
566 LDBG() <<
"Replacing dead operands with poison in " << list.operands.size()
568 for (OperandsToCleanup &o : list.operands) {
569 if (!o.replaceWithPoison || !o.nonLive.any())
572 os <<
"Replacing non-live operands [";
573 llvm::interleaveComma(o.nonLive.set_bits(), os);
574 os <<
"] with poison in operation: "
579 for (
auto deadIdx : o.nonLive.set_bits()) {
581 assert(operand &&
"expected non-null operand for poison replacement");
582 o.op->
setOperand(deadIdx, createPoisonedValue(rewriter, operand));
588 LDBG() <<
"Cleaning up " << list.blocks.size() <<
" block argument lists";
589 for (
auto &
b : list.blocks) {
591 if (
b.b->getNumArguments() !=
b.nonLiveArgs.size())
594 os <<
"Erasing non-live arguments [";
595 llvm::interleaveComma(
b.nonLiveArgs.set_bits(), os);
596 os <<
"] from block #" <<
b.b->computeBlockNumber() <<
" in region #"
597 <<
b.b->getParent()->getRegionNumber() <<
" of operation "
603 for (
int i =
b.nonLiveArgs.size() - 1; i >= 0; --i) {
604 if (!
b.nonLiveArgs[i])
606 b.b->getArgument(i).dropAllUses();
607 b.b->eraseArgument(i);
612 LDBG() <<
"Cleaning up " << list.successorOperands.size()
613 <<
" successor operand lists";
614 for (
auto &op : list.successorOperands) {
616 op.branch.getSuccessorOperands(op.successorIndex);
618 if (successorOperands.
size() != op.nonLiveOperands.size())
621 os <<
"Erasing non-live successor operands [";
622 llvm::interleaveComma(op.nonLiveOperands.set_bits(), os);
623 os <<
"] from successor " << op.successorIndex <<
" of branch: "
628 for (
int i = successorOperands.
size() - 1; i >= 0; --i) {
629 if (!op.nonLiveOperands[i])
631 successorOperands.
erase(i);
636 LDBG() <<
"Cleaning up " << list.functions.size() <<
" functions";
641 for (
auto &f : list.functions) {
642 LDBG() <<
"Cleaning up function: " << f.funcOp.getName() <<
" ("
643 << f.funcOp.getOperation() <<
")";
645 os <<
" Erasing non-live arguments [";
646 llvm::interleaveComma(f.nonLiveArgs.set_bits(), os);
648 os <<
" Erasing non-live return values [";
649 llvm::interleaveComma(f.nonLiveRets.set_bits(), os);
653 for (
auto deadIdx : f.nonLiveArgs.set_bits())
654 f.funcOp.getArgument(deadIdx).dropAllUses();
658 if (succeeded(f.funcOp.eraseArguments(f.nonLiveArgs))) {
660 if (f.nonLiveArgs.any())
661 erasedFuncArgs.try_emplace(f.funcOp.getOperation(), f.nonLiveArgs);
663 LDBG() <<
"Failed to erase arguments for function: "
664 << f.funcOp.getName();
666 (
void)f.funcOp.eraseResults(f.nonLiveRets);
670 LDBG() <<
"Cleaning up " << list.operands.size() <<
" operand lists";
671 for (OperandsToCleanup &o : list.operands) {
672 if (o.replaceWithPoison)
677 bool handledAsCall =
false;
678 if (o.callee && isa<CallOpInterface>(o.op)) {
679 auto call = cast<CallOpInterface>(o.op);
680 auto it = erasedFuncArgs.find(o.callee);
681 if (it != erasedFuncArgs.end()) {
682 const BitVector &deadArgIdxs = it->second;
686 for (
unsigned argIdx : llvm::reverse(deadArgIdxs.set_bits()))
692 if (o.nonLive.any()) {
694 int operandOffset = call.getArgOperands().getBeginOperandIndex();
695 for (
int argIdx : deadArgIdxs.set_bits()) {
696 int operandNumber = operandOffset + argIdx;
697 if (operandNumber <
static_cast<int>(o.nonLive.size()))
698 o.nonLive.reset(operandNumber);
701 handledAsCall =
true;
708 if (!handledAsCall && o.nonLive.any()) {
710 os <<
"Erasing non-live operands [";
711 llvm::interleaveComma(o.nonLive.set_bits(), os);
712 os <<
"] from operation: "
721 LDBG() <<
"Cleaning up " << list.results.size() <<
" result lists";
722 for (
auto &r : list.results) {
724 os <<
"Erasing non-live results [";
725 llvm::interleaveComma(r.nonLive.set_bits(), os);
726 os <<
"] from operation: "
730 dropUsesAndEraseResults(rewriter, r.op, r.nonLive);
734 LDBG() <<
"Cleaning up " << list.operations.size() <<
" operations";
736 LDBG() <<
"Erasing operation: "
742 ub::UnreachableOp::create(rewriter, op->
getLoc());
755 for (
Value opResult : opResults) {
758 if (opResult.use_empty())
762 Value poisonedValue = createPoisonedValue(rewriter, opResult);
771 for (ub::PoisonOp poisonOp : listener.poisonOps) {
772 if (poisonOp.use_empty())
776 LDBG() <<
"Finished cleanup of dead values";
779struct RemoveDeadValues
781 using impl::RemoveDeadValuesPassBase<
782 RemoveDeadValues>::RemoveDeadValuesPassBase;
783 void runOnOperation()
override;
787void RemoveDeadValues::runOnOperation() {
788 auto &la = getAnalysis<RunLivenessAnalysis>();
789 Operation *module = getOperation();
795 SymbolTableCollection symbolTableCollection;
796 SymbolUserMap symbolUserMap(symbolTableCollection, module);
804 RDVFinalCleanupList finalCleanupList;
806 module->walk([&](Operation *op) {
807 if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
808 processFuncOp(funcOp, symbolUserMap, la, deadVals, finalCleanupList);
809 }
else if (
auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
810 processRegionBranchOp(regionBranchOp, la, deadVals, finalCleanupList);
811 }
else if (
auto branchOp = dyn_cast<BranchOpInterface>(op)) {
812 processBranchOp(branchOp, la, deadVals, finalCleanupList);
813 }
else if (op->
hasTrait<::mlir::OpTrait::IsTerminator>()) {
816 }
else if (isa<CallOpInterface>(op)) {
820 processSimpleOp(op, la, deadVals, finalCleanupList);
824 MLIRContext *context =
module->getContext();
825 cleanUpDeadVals(context, finalCleanupList);
831 SmallVector<Operation *> opsToCanonicalize;
832 module->walk([&](RegionBranchOpInterface regionBranchOp) {
833 opsToCanonicalize.push_back(regionBranchOp.getOperation());
836 RewritePatternSet owningPatterns(context);
838 for (Operation *op : opsToCanonicalize)
840 if (populatedPatterns.insert(*info).second)
841 info->getCanonicalizationPatterns(owningPatterns, context);
843 std::move(owningPatterns)))) {
844 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 implements the result iterators for the Operation class.
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)