Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mlir/include/mlir/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ def RemoveDeadValues : Pass<"remove-dead-values"> {
```
}];
let constructor = "mlir::createRemoveDeadValuesPass()";
let dependentDialects = ["ub::UBDialect"];
}

def PrintIRPass : Pass<"print-ir"> {
Expand Down
1 change: 1 addition & 0 deletions mlir/lib/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ add_mlir_library(MLIRTransforms
MLIRSideEffectInterfaces
MLIRSupport
MLIRTransformUtils
MLIRUBDialect
)
47 changes: 45 additions & 2 deletions mlir/lib/Transforms/RemoveDeadValues.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
#include "mlir/Analysis/DataFlow/LivenessAnalysis.h"
#include "mlir/Dialect/UB/IR/UBOps.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/Dialect.h"
Expand Down Expand Up @@ -260,6 +261,22 @@ static SmallVector<OpOperand *> operandsToOpOperands(OperandRange operands) {
static void processSimpleOp(Operation *op, RunLivenessAnalysis &la,
DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
// Operations that have dead operands can be erased regardless of their
// side effects. The liveness analysis would not have marked an SSA value as
// "dead" if it had a side-effecting user that is reachable.
bool hasDeadOperand =
markLives(op->getOperands(), nonLiveSet, la).flip().any();
if (hasDeadOperand) {
LDBG() << "Simple op has dead operands, so the op must be dead: "
<< OpWithFlags(op, OpPrintingFlags().skipRegions());
assert(!hasLive(op->getResults(), nonLiveSet, la) &&
"expected the op to have no live results");
cl.operations.push_back(op);
collectNonLiveValues(nonLiveSet, op->getResults(),
BitVector(op->getNumResults(), true));
return;
}

if (!isMemoryEffectFree(op) || hasLive(op->getResults(), nonLiveSet, la)) {
LDBG() << "Simple op is not memory effect free or has live results, "
"preserving it: "
Expand Down Expand Up @@ -361,6 +378,8 @@ static void processFuncOp(FunctionOpInterface funcOp, Operation *module,
// block other than the entry block, because every block has a terminator.
for (Block &block : funcOp.getBlocks()) {
Operation *returnOp = block.getTerminator();
if (!returnOp->hasTrait<OpTrait::ReturnLike>())
continue;
if (returnOp && returnOp->getNumOperands() == numReturns)
cl.operands.push_back({returnOp, nonLiveRets});
}
Expand Down Expand Up @@ -700,7 +719,11 @@ static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
}

/// Steps to process a `BranchOpInterface` operation:
/// Iterate through each successor block of `branchOp`.
///
/// When a non-forwarded operand is dead (e.g., the condition value of a
/// conditional branch op), the entire operation is dead.
///
/// Otherwise, iterate through each successor block of `branchOp`.
/// (1) For each successor block, gather all operands from all successors.
/// (2) Fetch their associated liveness analysis data and collect for future
/// removal.
Expand All @@ -711,7 +734,22 @@ static void processBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la,
DenseSet<Value> &nonLiveSet,
RDVFinalCleanupList &cl) {
LDBG() << "Processing branch op: " << *branchOp;

// Check for dead non-forwarded operands.
BitVector deadNonForwardedOperands =
markLives(branchOp->getOperands(), nonLiveSet, la).flip();
unsigned numSuccessors = branchOp->getNumSuccessors();
for (unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
SuccessorOperands successorOperands =
branchOp.getSuccessorOperands(succIdx);
// Remove all non-forwarded operands from the bit vector.
for (OpOperand &opOperand : successorOperands.getMutableForwardedOperands())
deadNonForwardedOperands[opOperand.getOperandNumber()] = false;
}
if (deadNonForwardedOperands.any()) {
cl.operations.push_back(branchOp.getOperation());
return;
}

for (unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
Block *successorBlock = branchOp->getSuccessor(succIdx);
Expand Down Expand Up @@ -786,9 +824,14 @@ static void cleanUpDeadVals(RDVFinalCleanupList &list) {

// 3. Operations
LDBG() << "Cleaning up " << list.operations.size() << " operations";
for (auto &op : list.operations) {
for (Operation *op : list.operations) {
LDBG() << "Erasing operation: "
<< OpWithFlags(op, OpPrintingFlags().skipRegions());
if (op->hasTrait<OpTrait::IsTerminator>()) {
// When erasing a terminator, insert an unreachable op in its place.
OpBuilder b(op);
ub::UnreachableOp::create(b, op->getLoc());
}
Comment on lines +830 to +834
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (op->hasTrait<OpTrait::IsTerminator>()) {
// When erasing a terminator, insert an unreachable op in its place.
OpBuilder b(op);
ub::UnreachableOp::create(b, op->getLoc());
}
// When erasing a terminator, insert an unreachable op in its place.
if (op->hasTrait<OpTrait::IsTerminator>())
ub::UnreachableOp::create(OpBuilder{op}, op->getLoc());

Nit: simplifying a bit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is giving me a compilation error:

llvm-project/mlir/lib/Transforms/RemoveDeadValues.cpp:832:7: error: no matching function for call to 'create'
  832 |       ub::UnreachableOp::create(OpBuilder{op}, op->getLoc());
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~
llvm-project/build/tools/mlir/include/mlir/Dialect/UB/IR/UBOps.h.inc:377:24: note: candidate function not viable: expects an lvalue for 1st argument
  377 |   static UnreachableOp create(::mlir::OpBuilder &builder, ::mlir::Location location);
      |                        ^      ~~~~~~~~~~~~~~~~~~~~~~~~~~

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's annoying, we should make it so that it is possible.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not a blocker here)

op->dropAllUses();
op->erase();
}
Expand Down
27 changes: 27 additions & 0 deletions mlir/test/Transforms/remove-dead-values.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ func.func @main(%arg0 : i32) {

// -----

// CHECK-LABEL: func.func private @clean_func_op_remove_side_effecting_op() {
// CHECK-NEXT: return
// CHECK-NEXT: }
func.func private @clean_func_op_remove_side_effecting_op(%arg0: i32) -> (i32) {
// vector.print has a side effect but the op is dead.
vector.print %arg0 : i32
return %arg0 : i32
}

// -----

// %arg0 is not live because it is never used. %arg1 is not live because its
// user `arith.addi` doesn't have any uses and the value that it is forwarded to
// (%non_live_0) also doesn't have any uses.
Expand Down Expand Up @@ -687,3 +698,19 @@ func.func @op_block_have_dead_arg(%arg0: index, %arg1: index, %arg2: i1) {
// CHECK-NEXT: return
return
}

// -----

// CHECK-LABEL: func private @remove_dead_branch_op()
// CHECK-NEXT: ub.unreachable
// CHECK-NEXT: ^{{.*}}:
// CHECK-NEXT: return
// CHECK-NEXT: ^{{.*}}:
// CHECK-NEXT: return
func.func private @remove_dead_branch_op(%c: i1, %arg0: i64, %arg1: i64) -> (i64) {
cf.cond_br %c, ^bb1, ^bb2
^bb1:
return %arg0 : i64
^bb2:
return %arg1 : i64
}