[Mlir-commits] [mlir] [mlir] Use wouldOpBeTriviallyDead in LivenessAnalysis (PR #174362)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sun Jan 4 22:44:42 PST 2026


https://github.com/neildhar updated https://github.com/llvm/llvm-project/pull/174362

>From 77712a97cef4b074cd672bcd34486a66dd90a1fb Mon Sep 17 00:00:00 2001
From: Neil Dhar <neildhar at meta.com>
Date: Sun, 4 Jan 2026 20:43:36 -0800
Subject: [PATCH 1/2] [mlir] Simplify LivenessAnalysis::visitBranchOperand

Simplify each of the three cases we need to handle.

**BranchOpInterface**
We always assume that the operand is live, so just mark it as live and
move on, there is no need to then call `visitOperation` on it.

**RegionBranchOpInterface**
Instead of separately checking for live results, collecting the blocks,
looking for memory effects, and then calling `visitOperation`, skip
directly to calling `visitOperation`. It already checks for live
results, and recursively checks for memory side effects.

Note that we already implicitly rely on the `visitOperation` call to
catch these cases, because if an operation has a non-zero number of
results, but they are all dead, we rely on `visitOperation` to check if
the operation has memory side effects. (since we don't collect the
blocks if an operation has non-zero results)

**RegionBranchTerminatorOpInterface**
We just need to check if the enclosing `RegionBranchOp` is live.
---
 .../Analysis/DataFlow/LivenessAnalysis.cpp    | 122 +++---------------
 1 file changed, 17 insertions(+), 105 deletions(-)

diff --git a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
index ec7fec86f0c51..49be771039127 100644
--- a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
@@ -131,126 +131,38 @@ void LivenessAnalysis::visitBranchOperand(OpOperand &operand) {
   // the forwarded branch operands or the non-branch operands. Thus they need
   // to be handled separately. This is where we handle them.
 
-  // This marks values of type (1.b/1.c) liveness as "live". A non-forwarded
-  // branch operand will be live if a block where its op could take the control
-  // has an op with memory effects or could result in different results.
-  // Populating such blocks in `blocks`.
-  bool mayLive = false;
-  SmallVector<Block *, 4> blocks;
-  if (auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
-    if (op->getNumResults() != 0) {
-      // This mark value of type 1.c liveness as may live, because the region
-      // branch operation has a return value, and the non-forwarded operand can
-      // determine the region to jump to, it can thereby control the result of
-      // the region branch operation.
-      // Therefore, if the result value is live, we conservatively consider the
-      // non-forwarded operand of the region branch operation with result may
-      // live and record all result.
-      for (auto [resultIndex, result] : llvm::enumerate(op->getResults())) {
-        if (getLatticeElement(result)->isLive) {
-          mayLive = true;
-          LDBG() << "[visitBranchOperand] Non-forwarded branch operand may be "
-                    "live due to live result #"
-                 << resultIndex << ": "
-                 << OpWithFlags(op, OpPrintingFlags().skipRegions());
-          break;
-        }
-      }
-    } else {
-      // When the op is a `RegionBranchOpInterface`, like an `scf.for` or an
-      // `scf.index_switch` op, its branch operand controls the flow into this
-      // op's regions.
-      for (Region &region : op->getRegions()) {
-        for (Block &block : region)
-          blocks.push_back(&block);
-      }
-    }
-  } else if (isa<BranchOpInterface>(op)) {
+  if (isa<BranchOpInterface>(op)) {
     // We cannot track all successor blocks of the branch operation(More
     // specifically, it's the successor's successor). Additionally, different
     // blocks might also lead to the different block argument described in 1.c.
     // Therefore, we conservatively consider the non-forwarded operand of the
     // branch operation may live.
-    mayLive = true;
     LDBG() << "[visitBranchOperand] Non-forwarded branch operand may "
-              "be live due to branch op interface";
-  } else {
-    Operation *parentOp = op->getParentOp();
-    assert(isa<RegionBranchOpInterface>(parentOp) &&
-           "expected parent op to implement `RegionBranchOpInterface`");
-    if (parentOp->getNumResults() != 0) {
-      // This mark value of type 1.c liveness as may live, because the region
-      // branch operation has a return value, and the non-forwarded operand can
-      // determine the region to jump to, it can thereby control the result of
-      // the region branch operation.
-      // Therefore, if the result value is live, we conservatively consider the
-      // non-forwarded operand of the region branch operation with result may
-      // live and record all result.
-      for (Value result : parentOp->getResults()) {
-        if (getLatticeElement(result)->isLive) {
-          mayLive = true;
-          LDBG() << "[visitBranchOperand] Non-forwarded branch "
-                    "operand may be live due to parent live result: "
-                 << result;
-          break;
-        }
-      }
-    } else {
-      // When the op is a `RegionBranchTerminatorOpInterface`, like an
-      // `scf.condition` op or return-like, like an `scf.yield` op, its branch
-      // operand controls the flow into this op's parent's (which is a
-      // `RegionBranchOpInterface`'s) regions.
-      for (Region &region : parentOp->getRegions()) {
-        for (Block &block : region)
-          blocks.push_back(&block);
-      }
-    }
-  }
-  for (Block *block : blocks) {
-    if (mayLive)
-      break;
-    for (Operation &nestedOp : *block) {
-      if (!isMemoryEffectFree(&nestedOp)) {
-        mayLive = true;
-        LDBG() << "Non-forwarded branch operand may be "
-                  "live due to memory effect in block: "
-               << block;
-        break;
-      }
-    }
-  }
-
-  if (mayLive) {
+              "be live due to branch op interface"
+           << operand.get();
     Liveness *operandLiveness = getLatticeElement(operand.get());
-    LDBG() << "Marking branch operand live: " << operand.get();
     propagateIfChanged(operandLiveness, operandLiveness->markLive());
+    return;
   }
 
-  // Now that we have checked for memory-effecting ops in the blocks of concern,
-  // we will simply visit the op with this non-forwarded operand to potentially
-  // mark it "live" due to type (1.a/3) liveness.
+  // For RegionBranchOpInterface, we can simply visit it as a normal operation
+  // with this operand. The operand is live if the results of the op are used,
+  // or if it has any recursive memory side effects (which visitOperation will
+  // check). For RegionBranchOpTerminatorInterface, the operand is live if the
+  // surrounding RegionBranchOp is live, so we call visitOperation on the
+  // surrounding op, but with the operand that we are looking at.
+  Operation *rbiOp = op;
+  if (isa<RegionBranchTerminatorOpInterface>(op))
+    rbiOp = op->getParentOp();
+  assert(isa<RegionBranchOpInterface>(rbiOp));
   SmallVector<Liveness *, 4> operandLiveness;
   operandLiveness.push_back(getLatticeElement(operand.get()));
   SmallVector<const Liveness *, 4> resultsLiveness;
-  for (const Value result : op->getResults())
+  for (const Value result : rbiOp->getResults())
     resultsLiveness.push_back(getLatticeElement(result));
   LDBG() << "Visiting operation for non-forwarded branch operand: "
-         << OpWithFlags(op, OpPrintingFlags().skipRegions());
-  (void)visitOperation(op, operandLiveness, resultsLiveness);
-
-  // We also visit the parent op with the parent's results and this operand if
-  // `op` is a `RegionBranchTerminatorOpInterface` because its non-forwarded
-  // operand depends on not only its memory effects/results but also on those of
-  // its parent's.
-  if (!isa<RegionBranchTerminatorOpInterface>(op))
-    return;
-  Operation *parentOp = op->getParentOp();
-  SmallVector<const Liveness *, 4> parentResultsLiveness;
-  for (const Value parentResult : parentOp->getResults())
-    parentResultsLiveness.push_back(getLatticeElement(parentResult));
-  LDBG() << "Visiting parent operation for non-forwarded branch operand: "
-         << *parentOp;
-  (void)visitOperation(parentOp, operandLiveness, parentResultsLiveness);
+         << OpWithFlags(rbiOp, OpPrintingFlags().skipRegions());
+  (void)visitOperation(rbiOp, operandLiveness, resultsLiveness);
 }
 
 void LivenessAnalysis::visitCallOperand(OpOperand &operand) {

>From 80c9e71a6f00d4b80dabbf8e9a8665a169fe26a7 Mon Sep 17 00:00:00 2001
From: Neil Dhar <neildhar at meta.com>
Date: Sun, 4 Jan 2026 22:07:09 -0800
Subject: [PATCH 2/2] [mlir] Use wouldOpBeTriviallyDead in LivenessAnalysis

`wouldOpBeTriviallyDead` is closer to the intended purpose here, and
aligns the code with how the greedy pattern rewriter identifies dead
operations.

Importantly, it allows us to identify unused read-only memory
operations, which would previously have failed the `isMemoryEffectFree`
check.
---
 .../Analysis/DataFlow/LivenessAnalysis.cpp    |  2 +-
 .../DataFlow/test-liveness-analysis.mlir      | 26 ++++++++++++++++---
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
index 49be771039127..0145d71e6ed74 100644
--- a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
@@ -82,7 +82,7 @@ LivenessAnalysis::visitOperation(Operation *op, ArrayRef<Liveness *> operands,
   LDBG() << "[visitOperation] Enter: "
          << OpWithFlags(op, OpPrintingFlags().skipRegions());
   // This marks values of type (1.a) and (4) liveness as "live".
-  if (!isMemoryEffectFree(op) || op->hasTrait<OpTrait::ReturnLike>()) {
+  if (!wouldOpBeTriviallyDead(op)) {
     LDBG() << "[visitOperation] Operation has memory effects or is "
               "return-like, marking operands live";
     for (auto *operand : operands) {
diff --git a/mlir/test/Analysis/DataFlow/test-liveness-analysis.mlir b/mlir/test/Analysis/DataFlow/test-liveness-analysis.mlir
index 171a35fdeafb9..a3cd10f785b1d 100644
--- a/mlir/test/Analysis/DataFlow/test-liveness-analysis.mlir
+++ b/mlir/test/Analysis/DataFlow/test-liveness-analysis.mlir
@@ -336,7 +336,7 @@ func.func @affine_loop_no_use_iv_has_side_effect_op() {
 // CHECK-NEXT: region: #0:
 // CHECK-NEXT:   argument: #0: not live
 func.func @affine_loop_no_use_iv() {
-  affine.for %arg0 = 0 to 79 { 
+  affine.for %arg0 = 0 to 79 {
   } {tag = "for"}
   return
 }
@@ -351,9 +351,29 @@ func.func @affine_loop_no_use_iv() {
 func.func @forall_no_use_iv_has_side_effect_op(%idx1: index, %idx2: index) {
   scf.parallel (%i) = (%idx1) to (%idx2) step (%idx2) {
     %r = memref.alloca() : memref<10xf32>
+    %cst = arith.constant 0.0 : f32
     scf.forall (%e2) in (%idx2) {
-      %a = memref.load %r[%idx2] : memref<10xf32>
+      memref.store %cst, %r[%idx2] : memref<10xf32>
     } {tag = "forall"}
-  } 
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: test_tag: for:
+// CHECK-NEXT:   operand #0: not live
+// CHECK-NEXT:   operand #1: not live
+// CHECK-NEXT:   operand #2: not live
+// CHECK-NEXT:   operand #3: not live
+
+func.func @test_for_loop_read_only(%arg0: memref<10xindex>) {
+  %c0 = arith.constant 0 : index
+  %c10 = arith.constant 10 : index
+  %c1 = arith.constant 1 : index
+  %0 = scf.for %iv = %c0 to %c10 step %c1 iter_args(%idx = %c0) -> (index) {
+    %loaded = memref.load %arg0[%idx] : memref<10xindex>
+    scf.yield %loaded : index
+  } {tag = "for"}
   return
 }



More information about the Mlir-commits mailing list