#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
#include "mlir/Analysis/DataFlow/LivenessAnalysis.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/Dialect.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/OperationSupport.h"
#include "mlir/IR/SymbolTable.h"
#include "mlir/IR/Value.h"
#include "mlir/IR/ValueRange.h"
#include "mlir/IR/Visitors.h"
#include "mlir/Interfaces/CallInterfaces.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/FunctionInterfaces.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Support/LLVM.h"
#include "mlir/Transforms/FoldUtils.h"
#include "mlir/Transforms/Passes.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/DebugLog.h"
#include <cassert>
#include <cstddef>
#include <memory>
#include <optional>
#include <vector>
#define DEBUG_TYPE "remove-dead-values"
namespace mlir {
#define GEN_PASS_DEF_REMOVEDEADVALUES
#include "mlir/Transforms/Passes.h.inc"
}
using namespace mlir;
using namespace mlir::dataflow;
namespace {
struct FunctionToCleanUp {
FunctionOpInterface funcOp;
BitVector nonLiveArgs;
BitVector nonLiveRets;
};
struct OperationToCleanup {
Operation *op;
BitVector nonLive;
Operation *callee =
nullptr;
};
struct BlockArgsToCleanup {
Block *b;
BitVector nonLiveArgs;
};
struct SuccessorOperandsToCleanup {
BranchOpInterface branch;
unsigned successorIndex;
BitVector nonLiveOperands;
};
struct RDVFinalCleanupList {
SmallVector<Operation *> operations;
SmallVector<Value> values;
SmallVector<FunctionToCleanUp> functions;
SmallVector<OperationToCleanup> operands;
SmallVector<OperationToCleanup> results;
SmallVector<BlockArgsToCleanup> blocks;
SmallVector<SuccessorOperandsToCleanup> successorOperands;
};
static bool hasLive(ValueRange values, const DenseSet<Value> &nonLiveSet,
RunLivenessAnalysis &la) {
for (Value value : values) {
if (nonLiveSet.contains(value)) {
LDBG() << "Value " << value << " is already marked non-live (dead)";
continue;
}
const Liveness *liveness = la.getLiveness(value);
if (!liveness) {
LDBG() << "Value " << value
<< " has no liveness info, conservatively considered live";
return true;
}
if (liveness->isLive) {
LDBG() << "Value " << value << " is live according to liveness analysis";
return true;
} else {
LDBG() << "Value " << value << " is dead according to liveness analysis";
}
}
return false;
}
static BitVector markLives(ValueRange values, const DenseSet<Value> &nonLiveSet,
RunLivenessAnalysis &la) {
BitVector lives(values.size(), true);
for (auto [index, value] : llvm::enumerate(values)) {
if (nonLiveSet.contains(value)) {
lives.reset(index);
LDBG() << "Value " << value
<< " is already marked non-live (dead) at index " << index;
continue;
}
const Liveness *liveness = la.getLiveness(value);
if (!liveness) {
LDBG() << "Value " << value << " at index " << index
<< " has no liveness info, conservatively considered live";
continue;
}
if (!liveness->isLive) {
lives.reset(index);
LDBG() << "Value " << value << " at index " << index
<< " is dead according to liveness analysis";
} else {
LDBG() << "Value " << value << " at index " << index
<< " is live according to liveness analysis";
}
}
return lives;
}
static void collectNonLiveValues(DenseSet<Value> &nonLiveSet, ValueRange range,
const BitVector &nonLive) {
for (auto [index, result] : llvm::enumerate(range)) {
if (!nonLive[index])
continue;
nonLiveSet.insert(result);
LDBG() << "Marking value " << result << " as non-live (dead) at index "
<< index;
}
}
static void dropUsesAndEraseResults(Operation *op, BitVector toErase) {
assert(op->getNumResults() == toErase.size() &&
"expected the number of results in `op` and the size of `toErase` to "
"be the same");
std::vector<Type> newResultTypes;
for (OpResult result : op->getResults())
if (!toErase[result.getResultNumber()])
newResultTypes.push_back(result.getType());
OpBuilder builder(op);
builder.setInsertionPointAfter(op);
OperationState state(op->getLoc(), op->getName().getStringRef(),
op->getOperands(), newResultTypes, op->getAttrs());
for (unsigned i = 0, e = op->getNumRegions(); i < e; ++i)
state.addRegion();
Operation *newOp = builder.create(state);
for (const auto &[index, region] : llvm::enumerate(op->getRegions())) {
Region &newRegion = newOp->getRegion(index);
Block *temp = new Block();
newRegion.push_back(temp);
while (!region.empty())
region.front().moveBefore(temp);
temp->erase();
}
unsigned indexOfNextNewCallOpResultToReplace = 0;
for (auto [index, result] : llvm::enumerate(op->getResults())) {
assert(result && "expected result to be non-null");
if (toErase[index]) {
result.dropAllUses();
} else {
result.replaceAllUsesWith(
newOp->getResult(indexOfNextNewCallOpResultToReplace++));
}
}
op->erase();
}
static SmallVector<OpOperand *> operandsToOpOperands(OperandRange operands) {
OpOperand *values = operands.getBase();
SmallVector<OpOperand *> opOperands;
for (unsigned i = 0, e = operands.size(); i < e; i++)
opOperands.push_back(&values[i]);
return opOperands;
}
static void processSimpleOp(Operation *op, RunLivenessAnalysis &la,
DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
if (!isMemoryEffectFree(op) || hasLive(op->getResults(), nonLiveSet, la)) {
LDBG() << "Simple op is not memory effect free or has live results, "
"preserving it: "
<< OpWithFlags(op, OpPrintingFlags().skipRegions());
return;
}
LDBG()
<< "Simple op has all dead results and is memory effect free, scheduling "
"for removal: "
<< OpWithFlags(op, OpPrintingFlags().skipRegions());
cl.operations.push_back(op);
collectNonLiveValues(nonLiveSet, op->getResults(),
BitVector(op->getNumResults(), true));
}
static void processFuncOp(FunctionOpInterface funcOp, Operation *module,
RunLivenessAnalysis &la, DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
LDBG() << "Processing function op: "
<< OpWithFlags(funcOp, OpPrintingFlags().skipRegions());
if (funcOp.isPublic() || funcOp.isExternal()) {
LDBG() << "Function is public or external, skipping: "
<< funcOp.getOperation()->getName();
return;
}
SmallVector<Value> arguments(funcOp.getArguments());
BitVector nonLiveArgs = markLives(arguments, nonLiveSet, la);
nonLiveArgs = nonLiveArgs.flip();
for (auto [index, arg] : llvm::enumerate(arguments))
if (arg && nonLiveArgs[index]) {
cl.values.push_back(arg);
nonLiveSet.insert(arg);
}
SymbolTable::UseRange uses = *funcOp.getSymbolUses(module);
for (SymbolTable::SymbolUse use : uses) {
Operation *callOp = use.getUser();
assert(isa<CallOpInterface>(callOp) && "expected a call-like user");
cl.operands.push_back({callOp, BitVector(callOp->getNumOperands(), false),
funcOp.getOperation()});
}
size_t numReturns = funcOp.getNumResults();
BitVector nonLiveRets(numReturns, true);
for (SymbolTable::SymbolUse use : uses) {
Operation *callOp = use.getUser();
assert(isa<CallOpInterface>(callOp) && "expected a call-like user");
BitVector liveCallRets = markLives(callOp->getResults(), nonLiveSet, la);
nonLiveRets &= liveCallRets.flip();
}
for (Block &block : funcOp.getBlocks()) {
Operation *returnOp = block.getTerminator();
if (returnOp && returnOp->getNumOperands() == numReturns)
cl.operands.push_back({returnOp, nonLiveRets});
}
cl.functions.push_back({funcOp, nonLiveArgs, nonLiveRets});
if (numReturns == 0)
return;
for (SymbolTable::SymbolUse use : uses) {
Operation *callOp = use.getUser();
assert(isa<CallOpInterface>(callOp) && "expected a call-like user");
cl.results.push_back({callOp, nonLiveRets});
collectNonLiveValues(nonLiveSet, callOp->getResults(), nonLiveRets);
}
}
static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
RunLivenessAnalysis &la,
DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
LDBG() << "Processing region branch op: "
<< OpWithFlags(regionBranchOp, OpPrintingFlags().skipRegions());
auto markLiveResults = [&](BitVector &liveResults) {
liveResults = markLives(regionBranchOp->getResults(), nonLiveSet, la);
};
auto markLiveArgs = [&](DenseMap<Region *, BitVector> &liveArgs) {
for (Region ®ion : regionBranchOp->getRegions()) {
if (region.empty())
continue;
SmallVector<Value> arguments(region.front().getArguments());
BitVector regionLiveArgs = markLives(arguments, nonLiveSet, la);
liveArgs[®ion] = regionLiveArgs;
}
};
auto getSuccessors = [&](RegionBranchPoint point) {
SmallVector<RegionSuccessor> successors;
regionBranchOp.getSuccessorRegions(point, successors);
return successors;
};
auto getForwardedOpOperands = [&](const RegionSuccessor &successor,
Operation *terminator = nullptr) {
OperandRange operands =
terminator ? cast<RegionBranchTerminatorOpInterface>(terminator)
.getSuccessorOperands(successor)
: regionBranchOp.getEntrySuccessorOperands(successor);
SmallVector<OpOperand *> opOperands = operandsToOpOperands(operands);
return opOperands;
};
auto markNonForwardedOperands = [&](BitVector &nonForwardedOperands) {
nonForwardedOperands.resize(regionBranchOp->getNumOperands(), true);
for (const RegionSuccessor &successor :
getSuccessors(RegionBranchPoint::parent())) {
for (OpOperand *opOperand : getForwardedOpOperands(successor))
nonForwardedOperands.reset(opOperand->getOperandNumber());
}
};
auto markNonForwardedReturnValues =
[&](DenseMap<Operation *, BitVector> &nonForwardedRets) {
for (Region ®ion : regionBranchOp->getRegions()) {
if (region.empty())
continue;
Operation *terminator = region.front().getTerminator();
nonForwardedRets[terminator] =
BitVector(terminator->getNumOperands(), true);
for (const RegionSuccessor &successor :
getSuccessors(RegionBranchPoint(
cast<RegionBranchTerminatorOpInterface>(terminator)))) {
for (OpOperand *opOperand :
getForwardedOpOperands(successor, terminator))
nonForwardedRets[terminator].reset(opOperand->getOperandNumber());
}
}
};
auto updateOperandsOrTerminatorOperandsToKeep =
[&](BitVector &valuesToKeep, BitVector &resultsToKeep,
DenseMap<Region *, BitVector> &argsToKeep, Region *region = nullptr) {
Operation *terminator =
region ? region->front().getTerminator() : nullptr;
RegionBranchPoint point =
terminator
? RegionBranchPoint(
cast<RegionBranchTerminatorOpInterface>(terminator))
: RegionBranchPoint::parent();
for (const RegionSuccessor &successor : getSuccessors(point)) {
Region *successorRegion = successor.getSuccessor();
for (auto [opOperand, input] :
llvm::zip(getForwardedOpOperands(successor, terminator),
successor.getSuccessorInputs())) {
size_t operandNum = opOperand->getOperandNumber();
bool updateBasedOn =
successorRegion
? argsToKeep[successorRegion]
[cast<BlockArgument>(input).getArgNumber()]
: resultsToKeep[cast<OpResult>(input).getResultNumber()];
valuesToKeep[operandNum] = valuesToKeep[operandNum] | updateBasedOn;
}
}
};
auto recomputeResultsAndArgsToKeep =
[&](BitVector &resultsToKeep, DenseMap<Region *, BitVector> &argsToKeep,
BitVector &operandsToKeep,
DenseMap<Operation *, BitVector> &terminatorOperandsToKeep,
bool &resultsOrArgsToKeepChanged) {
resultsOrArgsToKeepChanged = false;
for (const RegionSuccessor &successor :
getSuccessors(RegionBranchPoint::parent())) {
Region *successorRegion = successor.getSuccessor();
for (auto [opOperand, input] :
llvm::zip(getForwardedOpOperands(successor),
successor.getSuccessorInputs())) {
bool recomputeBasedOn =
operandsToKeep[opOperand->getOperandNumber()];
bool toRecompute =
successorRegion
? argsToKeep[successorRegion]
[cast<BlockArgument>(input).getArgNumber()]
: resultsToKeep[cast<OpResult>(input).getResultNumber()];
if (!toRecompute && recomputeBasedOn)
resultsOrArgsToKeepChanged = true;
if (successorRegion) {
argsToKeep[successorRegion][cast<BlockArgument>(input)
.getArgNumber()] =
argsToKeep[successorRegion]
[cast<BlockArgument>(input).getArgNumber()] |
recomputeBasedOn;
} else {
resultsToKeep[cast<OpResult>(input).getResultNumber()] =
resultsToKeep[cast<OpResult>(input).getResultNumber()] |
recomputeBasedOn;
}
}
}
for (Region ®ion : regionBranchOp->getRegions()) {
if (region.empty())
continue;
Operation *terminator = region.front().getTerminator();
for (const RegionSuccessor &successor :
getSuccessors(RegionBranchPoint(
cast<RegionBranchTerminatorOpInterface>(terminator)))) {
Region *successorRegion = successor.getSuccessor();
for (auto [opOperand, input] :
llvm::zip(getForwardedOpOperands(successor, terminator),
successor.getSuccessorInputs())) {
bool recomputeBasedOn =
terminatorOperandsToKeep[region.back().getTerminator()]
[opOperand->getOperandNumber()];
bool toRecompute =
successorRegion
? argsToKeep[successorRegion]
[cast<BlockArgument>(input).getArgNumber()]
: resultsToKeep[cast<OpResult>(input).getResultNumber()];
if (!toRecompute && recomputeBasedOn)
resultsOrArgsToKeepChanged = true;
if (successorRegion) {
argsToKeep[successorRegion][cast<BlockArgument>(input)
.getArgNumber()] =
argsToKeep[successorRegion]
[cast<BlockArgument>(input).getArgNumber()] |
recomputeBasedOn;
} else {
resultsToKeep[cast<OpResult>(input).getResultNumber()] =
resultsToKeep[cast<OpResult>(input).getResultNumber()] |
recomputeBasedOn;
}
}
}
}
};
auto markValuesToKeep =
[&](BitVector &resultsToKeep, DenseMap<Region *, BitVector> &argsToKeep,
BitVector &operandsToKeep,
DenseMap<Operation *, BitVector> &terminatorOperandsToKeep) {
bool resultsOrArgsToKeepChanged = true;
while (resultsOrArgsToKeepChanged) {
updateOperandsOrTerminatorOperandsToKeep(operandsToKeep,
resultsToKeep, argsToKeep);
for (Region ®ion : regionBranchOp->getRegions()) {
if (region.empty())
continue;
updateOperandsOrTerminatorOperandsToKeep(
terminatorOperandsToKeep[region.back().getTerminator()],
resultsToKeep, argsToKeep, ®ion);
}
recomputeResultsAndArgsToKeep(
resultsToKeep, argsToKeep, operandsToKeep,
terminatorOperandsToKeep, resultsOrArgsToKeepChanged);
}
};
if (isMemoryEffectFree(regionBranchOp.getOperation()) &&
!hasLive(regionBranchOp->getResults(), nonLiveSet, la)) {
cl.operations.push_back(regionBranchOp.getOperation());
return;
}
BitVector resultsToKeep;
DenseMap<Region *, BitVector> argsToKeep;
BitVector operandsToKeep;
DenseMap<Operation *, BitVector> terminatorOperandsToKeep;
markLiveResults(resultsToKeep);
markLiveArgs(argsToKeep);
markNonForwardedOperands(operandsToKeep);
markNonForwardedReturnValues(terminatorOperandsToKeep);
markValuesToKeep(resultsToKeep, argsToKeep, operandsToKeep,
terminatorOperandsToKeep);
cl.operands.push_back({regionBranchOp, operandsToKeep.flip()});
for (Region ®ion : regionBranchOp->getRegions()) {
if (region.empty())
continue;
BitVector argsToRemove = argsToKeep[®ion].flip();
cl.blocks.push_back({®ion.front(), argsToRemove});
collectNonLiveValues(nonLiveSet, region.front().getArguments(),
argsToRemove);
}
for (Region ®ion : regionBranchOp->getRegions()) {
if (region.empty())
continue;
Operation *terminator = region.front().getTerminator();
cl.operands.push_back(
{terminator, terminatorOperandsToKeep[terminator].flip()});
}
BitVector resultsToRemove = resultsToKeep.flip();
collectNonLiveValues(nonLiveSet, regionBranchOp.getOperation()->getResults(),
resultsToRemove);
cl.results.push_back({regionBranchOp.getOperation(), resultsToRemove});
}
static void processBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la,
DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
LDBG() << "Processing branch op: " << *branchOp;
unsigned numSuccessors = branchOp->getNumSuccessors();
for (unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
Block *successorBlock = branchOp->getSuccessor(succIdx);
SuccessorOperands successorOperands =
branchOp.getSuccessorOperands(succIdx);
SmallVector<Value> operandValues;
for (unsigned operandIdx = 0; operandIdx < successorOperands.size();
++operandIdx) {
operandValues.push_back(successorOperands[operandIdx]);
}
BitVector successorNonLive =
markLives(operandValues, nonLiveSet, la).flip();
collectNonLiveValues(nonLiveSet, successorBlock->getArguments(),
successorNonLive);
cl.blocks.push_back({successorBlock, successorNonLive});
cl.successorOperands.push_back({branchOp, succIdx, successorNonLive});
}
}
static void cleanUpDeadVals(RDVFinalCleanupList &list) {
LDBG() << "Starting cleanup of dead values...";
LDBG() << "Cleaning up " << list.blocks.size() << " block argument lists";
for (auto &b : list.blocks) {
if (b.b->getNumArguments() != b.nonLiveArgs.size())
continue;
LDBG() << "Erasing " << b.nonLiveArgs.count()
<< " non-live arguments from block: " << b.b;
for (int i = b.nonLiveArgs.size() - 1; i >= 0; --i) {
if (!b.nonLiveArgs[i])
continue;
LDBG() << " Erasing block argument " << i << ": " << b.b->getArgument(i);
b.b->getArgument(i).dropAllUses();
b.b->eraseArgument(i);
}
}
LDBG() << "Cleaning up " << list.successorOperands.size()
<< " successor operand lists";
for (auto &op : list.successorOperands) {
SuccessorOperands successorOperands =
op.branch.getSuccessorOperands(op.successorIndex);
if (successorOperands.size() != op.nonLiveOperands.size())
continue;
LDBG() << "Erasing " << op.nonLiveOperands.count()
<< " non-live successor operands from successor "
<< op.successorIndex << " of branch: "
<< OpWithFlags(op.branch, OpPrintingFlags().skipRegions());
for (int i = successorOperands.size() - 1; i >= 0; --i) {
if (!op.nonLiveOperands[i])
continue;
LDBG() << " Erasing successor operand " << i << ": "
<< successorOperands[i];
successorOperands.erase(i);
}
}
LDBG() << "Cleaning up " << list.operations.size() << " operations";
for (auto &op : list.operations) {
LDBG() << "Erasing operation: "
<< OpWithFlags(op, OpPrintingFlags().skipRegions());
op->dropAllUses();
op->erase();
}
LDBG() << "Cleaning up " << list.values.size() << " values";
for (auto &v : list.values) {
LDBG() << "Dropping all uses of value: " << v;
v.dropAllUses();
}
LDBG() << "Cleaning up " << list.functions.size() << " functions";
DenseMap<Operation *, BitVector> erasedFuncArgs;
for (auto &f : list.functions) {
LDBG() << "Cleaning up function: " << f.funcOp.getOperation()->getName();
LDBG() << " Erasing " << f.nonLiveArgs.count() << " non-live arguments";
LDBG() << " Erasing " << f.nonLiveRets.count()
<< " non-live return values";
if (succeeded(f.funcOp.eraseArguments(f.nonLiveArgs))) {
if (f.nonLiveArgs.any())
erasedFuncArgs.try_emplace(f.funcOp.getOperation(), f.nonLiveArgs);
}
(void)f.funcOp.eraseResults(f.nonLiveRets);
}
LDBG() << "Cleaning up " << list.operands.size() << " operand lists";
for (OperationToCleanup &o : list.operands) {
bool handledAsCall = false;
if (o.callee && isa<CallOpInterface>(o.op)) {
auto call = cast<CallOpInterface>(o.op);
auto it = erasedFuncArgs.find(o.callee);
if (it != erasedFuncArgs.end()) {
const BitVector &deadArgIdxs = it->second;
MutableOperandRange args = call.getArgOperandsMutable();
for (unsigned argIdx : llvm::reverse(deadArgIdxs.set_bits()))
args.erase(argIdx);
if (o.nonLive.any()) {
int operandOffset = call.getArgOperands().getBeginOperandIndex();
for (int argIdx : deadArgIdxs.set_bits()) {
int operandNumber = operandOffset + argIdx;
if (operandNumber < static_cast<int>(o.nonLive.size()))
o.nonLive.reset(operandNumber);
}
}
handledAsCall = true;
}
}
if (!handledAsCall && o.nonLive.any()) {
o.op->eraseOperands(o.nonLive);
}
}
LDBG() << "Cleaning up " << list.results.size() << " result lists";
for (auto &r : list.results) {
LDBG() << "Erasing " << r.nonLive.count()
<< " non-live results from operation: "
<< OpWithFlags(r.op, OpPrintingFlags().skipRegions());
dropUsesAndEraseResults(r.op, r.nonLive);
}
LDBG() << "Finished cleanup of dead values";
}
struct RemoveDeadValues : public impl::RemoveDeadValuesBase<RemoveDeadValues> {
void runOnOperation() override;
};
}
void RemoveDeadValues::runOnOperation() {
auto &la = getAnalysis<RunLivenessAnalysis>();
Operation *module = getOperation();
DenseSet<Value> deadVals;
RDVFinalCleanupList finalCleanupList;
module->walk([&](Operation *op) {
if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
processFuncOp(funcOp, module, la, deadVals, finalCleanupList);
} else if (auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
processRegionBranchOp(regionBranchOp, la, deadVals, finalCleanupList);
} else if (auto branchOp = dyn_cast<BranchOpInterface>(op)) {
processBranchOp(branchOp, la, deadVals, finalCleanupList);
} else if (op->hasTrait<::mlir::OpTrait::IsTerminator>()) {
} else if (isa<CallOpInterface>(op)) {
} else {
processSimpleOp(op, la, deadVals, finalCleanupList);
}
});
cleanUpDeadVals(finalCleanupList);
}
std::unique_ptr<Pass> mlir::createRemoveDeadValuesPass() {
return std::make_unique<RemoveDeadValues>();
}