54#include "llvm/ADT/STLExtras.h"
55#include "llvm/Support/Debug.h"
56#include "llvm/Support/DebugLog.h"
63#define DEBUG_TYPE "remove-dead-values"
66#define GEN_PASS_DEF_REMOVEDEADVALUES
67#include "mlir/Transforms/Passes.h.inc"
83struct FunctionToCleanUp {
84 FunctionOpInterface funcOp;
85 BitVector nonLiveArgs;
86 BitVector nonLiveRets;
89struct OperationToCleanup {
96struct BlockArgsToCleanup {
98 BitVector nonLiveArgs;
101struct SuccessorOperandsToCleanup {
102 BranchOpInterface branch;
103 unsigned successorIndex;
104 BitVector nonLiveOperands;
107struct RDVFinalCleanupList {
108 SmallVector<Operation *> operations;
109 SmallVector<Value> values;
110 SmallVector<FunctionToCleanUp> functions;
111 SmallVector<OperationToCleanup> operands;
112 SmallVector<OperationToCleanup> results;
113 SmallVector<BlockArgsToCleanup> blocks;
114 SmallVector<SuccessorOperandsToCleanup> successorOperands;
123 for (
Value value : values) {
124 if (nonLiveSet.contains(value)) {
125 LDBG() <<
"Value " << value <<
" is already marked non-live (dead)";
131 LDBG() <<
"Value " << value
132 <<
" has no liveness info, conservatively considered live";
136 LDBG() <<
"Value " << value <<
" is live according to liveness analysis";
139 LDBG() <<
"Value " << value <<
" is dead according to liveness analysis";
149 BitVector lives(values.size(),
true);
151 for (
auto [
index, value] : llvm::enumerate(values)) {
152 if (nonLiveSet.contains(value)) {
154 LDBG() <<
"Value " << value
155 <<
" is already marked non-live (dead) at index " <<
index;
167 LDBG() <<
"Value " << value <<
" at index " <<
index
168 <<
" has no liveness info, conservatively considered live";
173 LDBG() <<
"Value " << value <<
" at index " <<
index
174 <<
" is dead according to liveness analysis";
176 LDBG() <<
"Value " << value <<
" at index " <<
index
177 <<
" is live according to liveness analysis";
188 const BitVector &nonLive) {
189 for (
auto [
index,
result] : llvm::enumerate(range)) {
192 nonLiveSet.insert(
result);
193 LDBG() <<
"Marking value " <<
result <<
" as non-live (dead) at index "
200static void dropUsesAndEraseResults(
Operation *op, BitVector toErase) {
202 "expected the number of results in `op` and the size of `toErase` to "
205 std::vector<Type> newResultTypes;
207 if (!toErase[
result.getResultNumber()])
208 newResultTypes.push_back(
result.getType());
210 builder.setInsertionPointAfter(op);
216 for (
const auto &[
index, region] : llvm::enumerate(op->
getRegions())) {
221 while (!region.empty())
222 region.front().moveBefore(temp);
226 unsigned indexOfNextNewCallOpResultToReplace = 0;
228 assert(
result &&
"expected result to be non-null");
229 if (toErase[
index]) {
232 result.replaceAllUsesWith(
233 newOp->
getResult(indexOfNextNewCallOpResultToReplace++));
243 for (
unsigned i = 0, e = operands.size(); i < e; i++)
244 opOperands.push_back(&values[i]);
263 RDVFinalCleanupList &cl) {
267 bool hasDeadOperand =
268 markLives(op->
getOperands(), nonLiveSet, la).flip().any();
269 if (hasDeadOperand) {
270 LDBG() <<
"Simple op has dead operands, so the op must be dead: "
272 assert(!hasLive(op->
getResults(), nonLiveSet, la) &&
273 "expected the op to have no live results");
274 cl.operations.push_back(op);
275 collectNonLiveValues(nonLiveSet, op->
getResults(),
281 LDBG() <<
"Simple op is not memory effect free or has live results, "
288 <<
"Simple op has all dead results and is memory effect free, scheduling "
291 cl.operations.push_back(op);
292 collectNonLiveValues(nonLiveSet, op->
getResults(),
306static void processFuncOp(FunctionOpInterface funcOp,
Operation *module,
308 RDVFinalCleanupList &cl) {
309 LDBG() <<
"Processing function op: "
311 if (funcOp.isPublic() || funcOp.isExternal()) {
312 LDBG() <<
"Function is public or external, skipping: "
313 << funcOp.getOperation()->getName();
319 BitVector nonLiveArgs = markLives(arguments, nonLiveSet, la);
320 nonLiveArgs = nonLiveArgs.flip();
323 for (
auto [
index, arg] : llvm::enumerate(arguments))
324 if (arg && nonLiveArgs[
index]) {
325 cl.values.push_back(arg);
326 nonLiveSet.insert(arg);
335 assert(isa<CallOpInterface>(callOp) &&
"expected a call-like user");
340 cl.operands.push_back({callOp, BitVector(callOp->
getNumOperands(),
false),
341 funcOp.getOperation()});
367 size_t numReturns = funcOp.getNumResults();
368 BitVector nonLiveRets(numReturns,
true);
371 assert(isa<CallOpInterface>(callOp) &&
"expected a call-like user");
372 BitVector liveCallRets = markLives(callOp->
getResults(), nonLiveSet, la);
373 nonLiveRets &= liveCallRets.flip();
379 for (
Block &block : funcOp.getBlocks()) {
380 Operation *returnOp = block.getTerminator();
384 cl.operands.push_back({returnOp, nonLiveRets});
388 cl.functions.push_back({funcOp, nonLiveArgs, nonLiveRets});
395 assert(isa<CallOpInterface>(callOp) &&
"expected a call-like user");
396 cl.results.push_back({callOp, nonLiveRets});
397 collectNonLiveValues(nonLiveSet, callOp->
getResults(), nonLiveRets);
430static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
433 RDVFinalCleanupList &cl) {
434 LDBG() <<
"Processing region branch op: "
437 auto markLiveResults = [&](BitVector &liveResults) {
438 liveResults = markLives(regionBranchOp->getResults(), nonLiveSet, la);
443 for (
Region ®ion : regionBranchOp->getRegions()) {
447 BitVector regionLiveArgs = markLives(arguments, nonLiveSet, la);
448 liveArgs[®ion] = regionLiveArgs;
456 regionBranchOp.getSuccessorRegions(point, successors);
466 terminator ? cast<RegionBranchTerminatorOpInterface>(terminator)
467 .getSuccessorOperands(successor)
468 : regionBranchOp.getEntrySuccessorOperands(successor);
475 auto markNonForwardedOperands = [&](BitVector &nonForwardedOperands) {
476 nonForwardedOperands.resize(regionBranchOp->getNumOperands(),
true);
479 for (
OpOperand *opOperand : getForwardedOpOperands(successor))
480 nonForwardedOperands.reset(opOperand->getOperandNumber());
486 auto markNonForwardedReturnValues =
488 for (
Region ®ion : regionBranchOp->getRegions()) {
492 Operation *terminator = region.front().getTerminator();
493 nonForwardedRets[terminator] =
497 cast<RegionBranchTerminatorOpInterface>(terminator)))) {
499 getForwardedOpOperands(successor, terminator))
500 nonForwardedRets[terminator].reset(opOperand->getOperandNumber());
509 auto updateOperandsOrTerminatorOperandsToKeep =
510 [&](BitVector &valuesToKeep, BitVector &resultsToKeep,
513 region ? region->front().getTerminator() :
nullptr;
517 cast<RegionBranchTerminatorOpInterface>(terminator))
522 for (
auto [opOperand, input] :
523 llvm::zip(getForwardedOpOperands(successor, terminator),
525 size_t operandNum = opOperand->getOperandNumber();
528 ? argsToKeep[successorRegion]
529 [cast<BlockArgument>(input).getArgNumber()]
530 : resultsToKeep[cast<OpResult>(input).getResultNumber()];
531 valuesToKeep[operandNum] = valuesToKeep[operandNum] | updateBasedOn;
539 auto recomputeResultsAndArgsToKeep =
541 BitVector &operandsToKeep,
543 bool &resultsOrArgsToKeepChanged) {
544 resultsOrArgsToKeepChanged =
false;
550 for (
auto [opOperand, input] :
551 llvm::zip(getForwardedOpOperands(successor),
553 bool recomputeBasedOn =
554 operandsToKeep[opOperand->getOperandNumber()];
557 ? argsToKeep[successorRegion]
558 [cast<BlockArgument>(input).getArgNumber()]
559 : resultsToKeep[cast<OpResult>(input).getResultNumber()];
560 if (!toRecompute && recomputeBasedOn)
561 resultsOrArgsToKeepChanged =
true;
562 if (successorRegion) {
563 argsToKeep[successorRegion][cast<BlockArgument>(input)
565 argsToKeep[successorRegion]
566 [cast<BlockArgument>(input).getArgNumber()] |
569 resultsToKeep[cast<OpResult>(input).getResultNumber()] =
570 resultsToKeep[cast<OpResult>(input).getResultNumber()] |
578 for (
Region ®ion : regionBranchOp->getRegions()) {
581 Operation *terminator = region.front().getTerminator();
584 cast<RegionBranchTerminatorOpInterface>(terminator)))) {
586 for (
auto [opOperand, input] :
587 llvm::zip(getForwardedOpOperands(successor, terminator),
589 bool recomputeBasedOn =
590 terminatorOperandsToKeep[region.back().getTerminator()]
591 [opOperand->getOperandNumber()];
594 ? argsToKeep[successorRegion]
595 [cast<BlockArgument>(input).getArgNumber()]
596 : resultsToKeep[cast<OpResult>(input).getResultNumber()];
597 if (!toRecompute && recomputeBasedOn)
598 resultsOrArgsToKeepChanged =
true;
599 if (successorRegion) {
600 argsToKeep[successorRegion][cast<BlockArgument>(input)
602 argsToKeep[successorRegion]
603 [cast<BlockArgument>(input).getArgNumber()] |
606 resultsToKeep[cast<OpResult>(input).getResultNumber()] =
607 resultsToKeep[cast<OpResult>(input).getResultNumber()] |
617 auto markValuesToKeep =
619 BitVector &operandsToKeep,
621 bool resultsOrArgsToKeepChanged =
true;
624 while (resultsOrArgsToKeepChanged) {
626 updateOperandsOrTerminatorOperandsToKeep(operandsToKeep,
627 resultsToKeep, argsToKeep);
630 for (
Region ®ion : regionBranchOp->getRegions()) {
633 updateOperandsOrTerminatorOperandsToKeep(
634 terminatorOperandsToKeep[region.back().getTerminator()],
635 resultsToKeep, argsToKeep, ®ion);
639 recomputeResultsAndArgsToKeep(
640 resultsToKeep, argsToKeep, operandsToKeep,
641 terminatorOperandsToKeep, resultsOrArgsToKeepChanged);
652 !hasLive(regionBranchOp->getResults(), nonLiveSet, la)) {
653 cl.operations.push_back(regionBranchOp.getOperation());
662 BitVector resultsToKeep;
667 BitVector operandsToKeep;
675 markLiveResults(resultsToKeep);
678 markLiveArgs(argsToKeep);
682 markNonForwardedOperands(operandsToKeep);
685 markNonForwardedReturnValues(terminatorOperandsToKeep);
689 markValuesToKeep(resultsToKeep, argsToKeep, operandsToKeep,
690 terminatorOperandsToKeep);
693 cl.operands.push_back({regionBranchOp, operandsToKeep.flip()});
696 for (
Region ®ion : regionBranchOp->getRegions()) {
699 BitVector argsToRemove = argsToKeep[®ion].flip();
700 cl.blocks.push_back({®ion.front(), argsToRemove});
701 collectNonLiveValues(nonLiveSet, region.front().getArguments(),
706 for (
Region ®ion : regionBranchOp->getRegions()) {
709 Operation *terminator = region.front().getTerminator();
710 cl.operands.push_back(
711 {terminator, terminatorOperandsToKeep[terminator].flip()});
715 BitVector resultsToRemove = resultsToKeep.flip();
716 collectNonLiveValues(nonLiveSet, regionBranchOp.getOperation()->getResults(),
718 cl.results.push_back({regionBranchOp.getOperation(), resultsToRemove});
735 RDVFinalCleanupList &cl) {
736 LDBG() <<
"Processing branch op: " << *branchOp;
739 BitVector deadNonForwardedOperands =
740 markLives(branchOp->getOperands(), nonLiveSet, la).flip();
741 unsigned numSuccessors = branchOp->getNumSuccessors();
742 for (
unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
744 branchOp.getSuccessorOperands(succIdx);
747 deadNonForwardedOperands[opOperand.getOperandNumber()] =
false;
749 if (deadNonForwardedOperands.any()) {
750 cl.operations.push_back(branchOp.getOperation());
754 for (
unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
759 branchOp.getSuccessorOperands(succIdx);
761 for (
unsigned operandIdx = 0; operandIdx < successorOperands.
size();
763 operandValues.push_back(successorOperands[operandIdx]);
767 BitVector successorNonLive =
768 markLives(operandValues, nonLiveSet, la).flip();
769 collectNonLiveValues(nonLiveSet, successorBlock->
getArguments(),
773 cl.blocks.push_back({successorBlock, successorNonLive});
774 cl.successorOperands.push_back({branchOp, succIdx, successorNonLive});
780static void cleanUpDeadVals(RDVFinalCleanupList &list) {
781 LDBG() <<
"Starting cleanup of dead values...";
785 LDBG() <<
"Cleaning up " << list.blocks.size() <<
" block argument lists";
786 for (
auto &
b : list.blocks) {
788 if (
b.b->getNumArguments() !=
b.nonLiveArgs.size())
790 LDBG() <<
"Erasing " <<
b.nonLiveArgs.count()
791 <<
" non-live arguments from block: " <<
b.b;
793 for (
int i =
b.nonLiveArgs.size() - 1; i >= 0; --i) {
794 if (!
b.nonLiveArgs[i])
796 LDBG() <<
" Erasing block argument " << i <<
": " <<
b.b->getArgument(i);
797 b.b->getArgument(i).dropAllUses();
798 b.b->eraseArgument(i);
803 LDBG() <<
"Cleaning up " << list.successorOperands.size()
804 <<
" successor operand lists";
805 for (
auto &op : list.successorOperands) {
807 op.branch.getSuccessorOperands(op.successorIndex);
809 if (successorOperands.
size() != op.nonLiveOperands.size())
811 LDBG() <<
"Erasing " << op.nonLiveOperands.count()
812 <<
" non-live successor operands from successor "
813 << op.successorIndex <<
" of branch: "
816 for (
int i = successorOperands.
size() - 1; i >= 0; --i) {
817 if (!op.nonLiveOperands[i])
819 LDBG() <<
" Erasing successor operand " << i <<
": "
820 << successorOperands[i];
821 successorOperands.
erase(i);
826 LDBG() <<
"Cleaning up " << list.operations.size() <<
" operations";
828 LDBG() <<
"Erasing operation: "
833 ub::UnreachableOp::create(
b, op->
getLoc());
840 LDBG() <<
"Cleaning up " << list.values.size() <<
" values";
841 for (
auto &v : list.values) {
842 LDBG() <<
"Dropping all uses of value: " << v;
847 LDBG() <<
"Cleaning up " << list.functions.size() <<
" functions";
852 for (
auto &f : list.functions) {
853 LDBG() <<
"Cleaning up function: " << f.funcOp.getOperation()->getName();
854 LDBG() <<
" Erasing " << f.nonLiveArgs.count() <<
" non-live arguments";
855 LDBG() <<
" Erasing " << f.nonLiveRets.count()
856 <<
" non-live return values";
860 if (succeeded(f.funcOp.eraseArguments(f.nonLiveArgs))) {
862 if (f.nonLiveArgs.any())
863 erasedFuncArgs.try_emplace(f.funcOp.getOperation(), f.nonLiveArgs);
865 (
void)f.funcOp.eraseResults(f.nonLiveRets);
869 LDBG() <<
"Cleaning up " << list.operands.size() <<
" operand lists";
870 for (OperationToCleanup &o : list.operands) {
874 bool handledAsCall =
false;
875 if (o.callee && isa<CallOpInterface>(o.op)) {
876 auto call = cast<CallOpInterface>(o.op);
877 auto it = erasedFuncArgs.find(o.callee);
878 if (it != erasedFuncArgs.end()) {
879 const BitVector &deadArgIdxs = it->second;
883 for (
unsigned argIdx : llvm::reverse(deadArgIdxs.set_bits()))
889 if (o.nonLive.any()) {
891 int operandOffset = call.getArgOperands().getBeginOperandIndex();
892 for (
int argIdx : deadArgIdxs.set_bits()) {
893 int operandNumber = operandOffset + argIdx;
894 if (operandNumber <
static_cast<int>(o.nonLive.size()))
895 o.nonLive.reset(operandNumber);
898 handledAsCall =
true;
905 if (!handledAsCall && o.nonLive.any()) {
911 LDBG() <<
"Cleaning up " << list.results.size() <<
" result lists";
912 for (
auto &r : list.results) {
913 LDBG() <<
"Erasing " << r.nonLive.count()
914 <<
" non-live results from operation: "
916 dropUsesAndEraseResults(r.op, r.nonLive);
918 LDBG() <<
"Finished cleanup of dead values";
922 void runOnOperation()
override;
926void RemoveDeadValues::runOnOperation() {
927 auto &la = getAnalysis<RunLivenessAnalysis>();
928 Operation *module = getOperation();
936 RDVFinalCleanupList finalCleanupList;
938 module->walk([&](Operation *op) {
939 if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
940 processFuncOp(funcOp, module, la, deadVals, finalCleanupList);
941 }
else if (
auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
942 processRegionBranchOp(regionBranchOp, la, deadVals, finalCleanupList);
943 }
else if (
auto branchOp = dyn_cast<BranchOpInterface>(op)) {
944 processBranchOp(branchOp, la, deadVals, finalCleanupList);
945 }
else if (op->
hasTrait<::mlir::OpTrait::IsTerminator>()) {
948 }
else if (isa<CallOpInterface>(op)) {
952 processSimpleOp(op, la, deadVals, finalCleanupList);
956 cleanUpDeadVals(finalCleanupList);
960 return std::make_unique<RemoveDeadValues>();
static MutableArrayRef< OpOperand > operandsToOpOperands(OperandRange &operands)
Block represents an ordered list of Operations.
void erase()
Unlink this Block from its parent region and delete it.
BlockArgListType getArguments()
Block * getSuccessor(unsigned i)
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.
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 is a value defined by a result of an operation.
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...
This class implements the operand iterators for the Operation class.
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
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.
void dropAllUses()
Drop all uses of results of this operation.
void eraseOperands(unsigned idx, unsigned length=1)
Erase the operands starting at position idx and ending at position 'idx'+'length'.
ArrayRef< NamedAttribute > getAttrs()
Return all of the attributes on this operation.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
unsigned getNumRegions()
Returns the number of regions held by this operation.
Location getLoc()
The source location the operation was defined or derived from.
unsigned getNumOperands()
static Operation * create(Location location, OperationName name, TypeRange resultTypes, ValueRange operands, NamedAttrList &&attributes, OpaqueProperties properties, BlockRange successors, unsigned numRegions)
Create a new Operation with the specific fields.
OperationName getName()
The name of an operation is the key identifier for it.
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
operand_range getOperands()
Returns an iterator on the underlying Value's.
result_range getResults()
void erase()
Remove this operation from its parent block and delete it.
unsigned getNumResults()
Return the number of results held by this operation.
This class represents a point being branched from in the methods of the RegionBranchOpInterface.
static constexpr RegionBranchPoint parent()
Returns an instance of RegionBranchPoint representing the parent operation.
This class represents a successor of a region.
ValueRange getSuccessorInputs() const
Return the inputs to the successor that are remapped by the exit values of the current region.
Region * getSuccessor() const
Return the given region successor.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
void push_back(Block *block)
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 specific symbol use.
This class implements a range of SymbolRef uses.
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...
Include the generated interface declarations.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
std::unique_ptr< Pass > createRemoveDeadValuesPass()
Creates an optimization pass to remove dead values.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
This trait indicates that a terminator operation is "return-like".
This represents an operation in an abstracted form, suitable for use with the builder APIs.
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)