[Mlir-commits] [mlir] [mlir] Refactor transform.apply_dce into a eliminateTriviallyDeadOps region-utils helper (PR #194041)

Mehdi Amini llvmlistbot at llvm.org
Wed Apr 29 04:31:18 PDT 2026


https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/194041

>From 42959ac576800a3a5d83139460e98fbbab1106b9 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Fri, 24 Apr 2026 12:56:45 -0700
Subject: [PATCH] [mlir] Refactor transform.apply_dce into
 eliminateTriviallyDeadOps

Introduce a lightweight, targeted dead-op eliminator that complements the
existing liveness-based runRegionDCE. The algorithm is O(ops):
step 1 walks each op in reverse program order, erases it outright if
trivially dead, or recurses into nested regions when recursive cleanup is
enabled; step 2 drives a per-region worklist that only ever holds ops
already verified dead, propagating new deadness by dropping each operand's
use before re-checking isOpTriviallyDead on the defining op.

Unlike runRegionDCE, this does not touch dead block arguments, dead
successor operands, or dead use-def cycles. Use runRegionDCE when those
are required. An includeNestedRegions parameter lets callers restrict
simplification to the top-level region.

Switch transform.apply_dce to use the new helper, dropping a custom
worklist that had subtle invariants: SetVector dedup, linear search on
every erase, and pre-order walk with skip.

In general it is bad practice to implement complex custom logic in
Transform ops apply methods: this should be an adaptor exposing MLIR
transformations to the transform dialect; non-trivial logic deserves a
public API for reusability and proper layering.

Introduce a TrivialDeadCodeEliminationPass exposed as -trivial-dce. The pass
removes trivially dead operations and, when removeBlocks is enabled,
unreachable blocks. It does not run liveness analysis and does not remove
dead use-def cycles. Add recursive and removeBlocks options, both defaulting
to true, to control whether nested regions are visited and whether
unreachable blocks are erased.

Add tests for nested-region cleanup, unreachable blocks, graph regions,
dead cycles, non-recursive mode, and preserving unreachable blocks with
remove-blocks=false.

Assisted-by: Claude Code
---
 mlir/include/mlir/Transforms/Passes.h         |   1 +
 mlir/include/mlir/Transforms/Passes.td        |  24 +++
 mlir/include/mlir/Transforms/RegionUtils.h    |  18 +-
 .../lib/Dialect/Transform/IR/TransformOps.cpp |  44 +----
 mlir/lib/Transforms/CMakeLists.txt            |   1 +
 .../Transforms/TrivialDeadCodeElimination.cpp |  37 ++++
 mlir/lib/Transforms/Utils/RegionUtils.cpp     |  84 ++++++++-
 .../Transforms/dead-code-elimination.mlir     | 170 ++++++++++++++++++
 8 files changed, 328 insertions(+), 51 deletions(-)
 create mode 100644 mlir/lib/Transforms/TrivialDeadCodeElimination.cpp
 create mode 100644 mlir/test/Transforms/dead-code-elimination.mlir

diff --git a/mlir/include/mlir/Transforms/Passes.h b/mlir/include/mlir/Transforms/Passes.h
index 4804b023a8f79..313755fde3479 100644
--- a/mlir/include/mlir/Transforms/Passes.h
+++ b/mlir/include/mlir/Transforms/Passes.h
@@ -33,6 +33,7 @@ class GreedyRewriteConfig;
 
 #define GEN_PASS_DECL_BUBBLEDOWNMEMORYSPACECASTS
 #define GEN_PASS_DECL_CSEPASS
+#define GEN_PASS_DECL_TRIVIALDEADCODEELIMINATIONPASS
 #define GEN_PASS_DECL_CANONICALIZERPASS
 #define GEN_PASS_DECL_COMPOSITEFIXEDPOINTPASS
 #define GEN_PASS_DECL_CONTROLFLOWSINKPASS
diff --git a/mlir/include/mlir/Transforms/Passes.td b/mlir/include/mlir/Transforms/Passes.td
index 1b08ec98baf06..74ac370ea950b 100644
--- a/mlir/include/mlir/Transforms/Passes.td
+++ b/mlir/include/mlir/Transforms/Passes.td
@@ -100,6 +100,30 @@ def CSEPass : Pass<"cse"> {
   ];
 }
 
+def TrivialDeadCodeEliminationPass : Pass<"trivial-dce"> {
+  let summary = "Remove trivially dead operations and blocks";
+  let description = [{
+    This pass eliminates only trivially dead operations; that is,
+    side-effect-free operations with no users. By default, it also removes
+    trivially dead blocks; that is, blocks that are unreachable from their
+    region entry block. The `remove-blocks` option can be disabled to preserve
+    unreachable blocks.
+
+    This pass does not run a liveness analysis and does not remove dead
+    use-def cycles.
+
+    By default, this pass recursively visits nested regions. The `recursive`
+    option can be disabled to restrict the pass to only the immediate regions
+    nested under the current operation.
+  }];
+  let options = [
+    Option<"recursive", "recursive", "bool", /*default=*/"true",
+           "Recursively visit nested regions">,
+    Option<"removeBlocks", "remove-blocks", "bool", /*default=*/"true",
+           "Remove unreachable blocks">
+  ];
+}
+
 def RemoveDeadValuesPass : Pass<"remove-dead-values"> {
   let summary = "Remove dead values";
   let description = [{
diff --git a/mlir/include/mlir/Transforms/RegionUtils.h b/mlir/include/mlir/Transforms/RegionUtils.h
index f8db72aabefe3..ea0b9ba9614a4 100644
--- a/mlir/include/mlir/Transforms/RegionUtils.h
+++ b/mlir/include/mlir/Transforms/RegionUtils.h
@@ -109,6 +109,16 @@ LogicalResult moveValueDefinitions(RewriterBase &rewriter, ValueRange values,
 LogicalResult moveValueDefinitions(RewriterBase &rewriter, ValueRange values,
                                    Operation *insertionPoint);
 
+/// Remove trivially dead operations from \p region. An operation is trivially
+/// dead when it has no users and is side-effect-free. Operand-defining ops are
+/// re-evaluated after each erasure, so chains of dead ops are eliminated in a
+/// single pass. When \p includeNestedRegions is true (the default), the pass
+/// descends into nested regions bottom-up before simplifying \p region itself;
+/// when false, only ops directly in \p region are considered. Returns true if
+/// any op was removed.
+bool eliminateTriviallyDeadOps(RewriterBase &rewriter, Region &region,
+                               bool includeNestedRegions = true);
+
 /// Run a set of structural simplifications over the given regions. This
 /// includes transformations like unreachable block elimination, dead argument
 /// elimination, as well as some other DCE. This function returns success if any
@@ -120,10 +130,12 @@ LogicalResult simplifyRegions(RewriterBase &rewriter,
                               MutableArrayRef<Region> regions,
                               bool mergeBlocks = true);
 
-/// Erase the unreachable blocks within the provided regions. Returns success
-/// if any blocks were erased, failure otherwise.
+/// Erase the unreachable blocks within the provided regions. If \p recurse is
+/// true, also visit regions nested under live operations. Returns success if
+/// any blocks were erased, failure otherwise.
 LogicalResult eraseUnreachableBlocks(RewriterBase &rewriter,
-                                     MutableArrayRef<Region> regions);
+                                     MutableArrayRef<Region> regions,
+                                     bool recurse = true);
 
 /// This function returns success if any operations or arguments were deleted,
 /// failure otherwise.
diff --git a/mlir/lib/Dialect/Transform/IR/TransformOps.cpp b/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
index 1a27e6cb5de58..3235ae1171a73 100644
--- a/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
+++ b/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
@@ -31,6 +31,7 @@
 #include "mlir/Transforms/DialectConversion.h"
 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
 #include "mlir/Transforms/LoopInvariantCodeMotionUtils.h"
+#include "mlir/Transforms/RegionUtils.h"
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/ScopeExit.h"
@@ -324,47 +325,8 @@ DiagnosedSilenceableFailure transform::ApplyDeadCodeEliminationOp::applyToOne(
   if (!payloadCheck.succeeded())
     return payloadCheck;
 
-  // Maintain a worklist of potentially dead ops.
-  SetVector<Operation *> worklist;
-
-  // Helper function that adds all defining ops of used values (operands and
-  // operands of nested ops).
-  auto addDefiningOpsToWorklist = [&](Operation *op) {
-    op->walk([&](Operation *op) {
-      for (Value v : op->getOperands())
-        if (Operation *defOp = v.getDefiningOp())
-          if (target->isProperAncestor(defOp))
-            worklist.insert(defOp);
-    });
-  };
-
-  // Helper function that erases an op.
-  auto eraseOp = [&](Operation *op) {
-    // Remove op and nested ops from the worklist.
-    op->walk([&](Operation *op) {
-      const auto *it = llvm::find(worklist, op);
-      if (it != worklist.end())
-        worklist.erase(it);
-    });
-    rewriter.eraseOp(op);
-  };
-
-  // Initial walk over the IR.
-  target->walk<WalkOrder::PostOrder>([&](Operation *op) {
-    if (op != target && isOpTriviallyDead(op)) {
-      addDefiningOpsToWorklist(op);
-      eraseOp(op);
-    }
-  });
-
-  // Erase all ops that have become dead.
-  while (!worklist.empty()) {
-    Operation *op = worklist.pop_back_val();
-    if (!isOpTriviallyDead(op))
-      continue;
-    addDefiningOpsToWorklist(op);
-    eraseOp(op);
-  }
+  for (Region &region : target->getRegions())
+    eliminateTriviallyDeadOps(rewriter, region);
 
   return DiagnosedSilenceableFailure::success();
 }
diff --git a/mlir/lib/Transforms/CMakeLists.txt b/mlir/lib/Transforms/CMakeLists.txt
index 8907724627386..66b39f53c91df 100644
--- a/mlir/lib/Transforms/CMakeLists.txt
+++ b/mlir/lib/Transforms/CMakeLists.txt
@@ -5,6 +5,7 @@ add_mlir_library(MLIRTransforms
   CompositePass.cpp
   ControlFlowSink.cpp
   CSE.cpp
+  TrivialDeadCodeElimination.cpp
   GenerateRuntimeVerification.cpp
   BubbleDownMemorySpaceCasts.cpp
   InlinerPass.cpp
diff --git a/mlir/lib/Transforms/TrivialDeadCodeElimination.cpp b/mlir/lib/Transforms/TrivialDeadCodeElimination.cpp
new file mode 100644
index 0000000000000..5cb9c47410983
--- /dev/null
+++ b/mlir/lib/Transforms/TrivialDeadCodeElimination.cpp
@@ -0,0 +1,37 @@
+//===- TrivialDeadCodeElimination.cpp - Trivial DCE -----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/Operation.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Transforms/Passes.h"
+#include "mlir/Transforms/RegionUtils.h"
+
+namespace mlir {
+#define GEN_PASS_DEF_TRIVIALDEADCODEELIMINATIONPASS
+#include "mlir/Transforms/Passes.h.inc"
+} // namespace mlir
+
+using namespace mlir;
+
+namespace {
+struct TrivialDeadCodeElimination
+    : public impl::TrivialDeadCodeEliminationPassBase<
+          TrivialDeadCodeElimination> {
+  using impl::TrivialDeadCodeEliminationPassBase<
+      TrivialDeadCodeElimination>::TrivialDeadCodeEliminationPassBase;
+
+  void runOnOperation() override {
+    Operation *target = getOperation();
+    IRRewriter rewriter(target->getContext());
+    if (removeBlocks)
+      (void)eraseUnreachableBlocks(rewriter, target->getRegions(), recursive);
+    for (Region &region : target->getRegions())
+      eliminateTriviallyDeadOps(rewriter, region, recursive);
+  }
+};
+} // namespace
diff --git a/mlir/lib/Transforms/Utils/RegionUtils.cpp b/mlir/lib/Transforms/Utils/RegionUtils.cpp
index a87cb15625130..eb61159682ed5 100644
--- a/mlir/lib/Transforms/Utils/RegionUtils.cpp
+++ b/mlir/lib/Transforms/Utils/RegionUtils.cpp
@@ -19,6 +19,7 @@
 #include "mlir/Interfaces/ControlFlowInterfaces.h"
 #include "mlir/Interfaces/SideEffectInterfaces.h"
 #include "mlir/Support/LogicalResult.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/DepthFirstIterator.h"
 #include "llvm/ADT/PostOrderIterator.h"
 #include "llvm/ADT/STLExtras.h"
@@ -185,7 +186,8 @@ SmallVector<Value> mlir::makeRegionIsolatedFromAbove(
 /// if any blocks were erased, failure otherwise.
 // TODO: We could likely merge this with the DCE algorithm below.
 LogicalResult mlir::eraseUnreachableBlocks(RewriterBase &rewriter,
-                                           MutableArrayRef<Region> regions) {
+                                           MutableArrayRef<Region> regions,
+                                           bool recurse) {
   LDBG() << "Starting eraseUnreachableBlocks with " << regions.size()
          << " regions";
 
@@ -217,9 +219,10 @@ LogicalResult mlir::eraseUnreachableBlocks(RewriterBase &rewriter,
 
     // If this is a single block region, just collect the nested regions.
     if (region->hasOneBlock()) {
-      for (Operation &op : region->front())
-        for (Region &region : op.getRegions())
-          worklist.push_back(&region);
+      if (recurse)
+        for (Operation &op : region->front())
+          for (Region &region : op.getRegions())
+            worklist.push_back(&region);
       continue;
     }
 
@@ -243,9 +246,10 @@ LogicalResult mlir::eraseUnreachableBlocks(RewriterBase &rewriter,
       }
 
       // Walk any regions within this block.
-      for (Operation &op : block)
-        for (Region &region : op.getRegions())
-          worklist.push_back(&region);
+      if (recurse)
+        for (Operation &op : block)
+          for (Region &region : op.getRegions())
+            worklist.push_back(&region);
     }
   }
 
@@ -506,6 +510,72 @@ LogicalResult mlir::runRegionDCE(RewriterBase &rewriter,
   return deleteDeadness(rewriter, regions, liveMap);
 }
 
+bool mlir::eliminateTriviallyDeadOps(RewriterBase &rewriter, Region &region,
+                                     bool includeNestedRegions) {
+  bool changed = false;
+
+  // Step 1: walk each op in reverse program order. If the op is already
+  // trivially dead, erase it outright — there's no point recursing into
+  // regions that will be destroyed with it. Otherwise, if
+  // `includeNestedRegions` is set, recurse into its nested regions so values
+  // defined in `region` may lose their last user and show up as dead in
+  // step 2's seed. Reverse iteration lets dead chains propagate within this
+  // single pass.
+  for (Block &block : llvm::reverse(region)) {
+    for (Operation &op :
+         llvm::make_early_inc_range(llvm::reverse(block.getOperations()))) {
+      if (isOpTriviallyDead(&op)) {
+        rewriter.eraseOp(&op);
+        changed = true;
+        continue;
+      }
+      if (includeNestedRegions)
+        for (Region &nested : op.getRegions())
+          changed |= eliminateTriviallyDeadOps(rewriter, nested);
+    }
+  }
+
+  // Step 2: worklist over ops in this region only.
+  //
+  // Worklist invariant: an op is pushed *only* once we have verified it is
+  // trivially dead. No speculative enqueues — every op on the worklist will
+  // be erased when popped. Two things enforce this:
+  //   - the initial seed below calls isOpTriviallyDead before enqueueing,
+  //   - the propagation inside the loop drops the erasing op's use of
+  //     `defOp` *before* re-checking isOpTriviallyDead(defOp), so the check
+  //     sees the post-erase use count and only enqueues when actually dead.
+  // Deadness is monotonic within this pass (we never add users, only remove
+  // them), so an op that was dead at enqueue time is still dead at pop time.
+  // The `visited` set is just for dedup; no re-check is needed on pop.
+  SmallVector<Operation *> worklist;
+  DenseSet<Operation *> visited;
+
+  for (Operation &op : region.getOps()) {
+    if (isOpTriviallyDead(&op) && visited.insert(&op).second) {
+      worklist.push_back(&op);
+      changed = true;
+    }
+  }
+
+  while (!worklist.empty()) {
+    Operation *op = worklist.pop_back_val();
+    /// Erase each operand to drop its use count before checking its defining
+    /// op: by the time we call isOpTriviallyDead on defOp, the
+    /// about-to-be-erased `op` is no longer counted as a user. Only
+    /// actually-dead ops enter the worklist.
+    for (OpOperand &opOperand : op->getOpOperands()) {
+      Operation *defOp = opOperand.get().getDefiningOp();
+      if (!defOp || defOp->getParentRegion() != &region || visited.count(defOp))
+        continue;
+      opOperand.drop();
+      if (isOpTriviallyDead(defOp))
+        worklist.push_back(defOp);
+    }
+    rewriter.eraseOp(op);
+  }
+  return changed;
+}
+
 //===----------------------------------------------------------------------===//
 // Block Merging
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Transforms/dead-code-elimination.mlir b/mlir/test/Transforms/dead-code-elimination.mlir
new file mode 100644
index 0000000000000..8df577caea929
--- /dev/null
+++ b/mlir/test/Transforms/dead-code-elimination.mlir
@@ -0,0 +1,170 @@
+// RUN: mlir-opt -trivial-dce -split-input-file %s | FileCheck %s
+// RUN: mlir-opt -pass-pipeline="builtin.module(func.func(trivial-dce{recursive=false}))" -split-input-file %s | FileCheck %s --check-prefix=NONREC
+// RUN: mlir-opt -pass-pipeline="builtin.module(func.func(trivial-dce{remove-blocks=false}))" -split-input-file %s | FileCheck %s --check-prefix=NOBLOCKS
+
+// CHECK-LABEL: func @simple_test(
+//  CHECK-SAME:     %[[arg0:.*]]: i16)
+//  CHECK-NEXT:   %[[c5:.*]] = arith.constant 5 : i16
+//  CHECK-NEXT:   %[[add:.*]] = arith.addi %[[c5]], %[[arg0]]
+//  CHECK-NEXT:   return %[[add]]
+// NONREC-LABEL: func @simple_test(
+//  NONREC-SAME:     %[[arg0:.*]]: i16)
+//  NONREC-NEXT:   %[[c5:.*]] = arith.constant 5 : i16
+//  NONREC-NEXT:   %[[add:.*]] = arith.addi %[[c5]], %[[arg0]]
+//  NONREC-NEXT:   return %[[add]]
+func.func @simple_test(%arg0: i16) -> i16 {
+  %0 = arith.constant 5 : i16
+  %1 = arith.addi %0, %arg0 : i16
+  %2 = arith.addi %1, %1 : i16
+  %3 = arith.addi %2, %1 : i16
+  return %1 : i16
+}
+
+// -----
+
+// CHECK-LABEL: func @eliminate_from_region
+//  CHECK-NEXT:   scf.for {{.*}} {
+//  CHECK-NEXT:     arith.constant
+//  CHECK-NEXT:     "test.print"
+//  CHECK-NEXT:   }
+//  CHECK-NEXT:   return
+// NONREC-LABEL: func @eliminate_from_region
+//  NONREC:      scf.for {{.*}} {
+//  NONREC-NEXT:   arith.constant 5
+//  NONREC-NEXT:   arith.constant 10
+//  NONREC-NEXT:   "test.print"
+func.func @eliminate_from_region(%lb: index, %ub: index, %step: index) {
+  scf.for %iv = %lb to %ub step %step {
+    %0 = arith.constant 5 : i16
+    %1 = arith.constant 10 : i16
+    "test.print"(%0) : (i16) -> ()
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func @eliminate_op_with_region
+//  CHECK-NEXT:   return
+func.func @eliminate_op_with_region(%lb: index, %ub: index, %step: index) {
+  %c0 = arith.constant 0 : i16
+  %0 = scf.for %iv = %lb to %ub step %step iter_args(%iter = %c0) -> i16 {
+    %0 = arith.constant 5 : i16
+    %added = arith.addi %iter, %0 : i16
+    scf.yield %added : i16
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func @unstructured_control_flow(
+//  CHECK-SAME:     %[[arg0:.*]]: i16)
+//  CHECK-NEXT:   %[[c5:.*]] = arith.constant 5 : i16
+//  CHECK-NEXT:   cf.br ^[[bb2:.*]]
+//  CHECK-NEXT: ^[[bb1:.*]]:  // pred
+//  CHECK-NEXT:   cf.br ^[[bb3:.*]]
+//  CHECK-NEXT: ^[[bb2]]:
+//  CHECK-NEXT:   %[[add:.*]] = arith.addi %[[c5]], %[[arg0]]
+//  CHECK-NEXT:   cf.br ^[[bb1]]
+//  CHECK-NEXT: ^[[bb3]]:
+//  CHECK-NEXT:   return %[[add]]
+func.func @unstructured_control_flow(%arg0: i16) -> i16 {
+  %0 = arith.constant 5 : i16
+  cf.br ^bb2
+^bb1:
+  %3 = arith.addi %1, %1 : i16
+  %4 = arith.addi %3, %2 : i16
+  cf.br ^bb3
+^bb2:
+  %1 = arith.addi %0, %arg0 : i16
+  %2 = arith.subi %0, %arg0 : i16
+  cf.br ^bb1
+^bb3:
+  return %1 : i16
+}
+
+// -----
+
+// CHECK-LABEL: func @remove_dead_block()
+//  CHECK-NEXT:   cf.br ^[[bb2:.*]]
+//  CHECK-NEXT: ^[[bb2]]:
+//  CHECK-NEXT:   return
+// NONREC-LABEL: func @remove_dead_block()
+//  NONREC-NEXT:   cf.br ^[[bb2:.*]]
+//  NONREC-NEXT: ^[[bb2]]:
+//  NONREC-NEXT:   return
+// NOBLOCKS-LABEL: func @remove_dead_block()
+//  NOBLOCKS-NEXT:   cf.br ^[[bb2:.*]]
+//  NOBLOCKS-NEXT: ^[[bb1:.*]]:  // no predecessors
+//  NOBLOCKS-NEXT:   cf.br ^[[bb2]]
+//  NOBLOCKS-NEXT: ^[[bb2]]:
+//  NOBLOCKS-NEXT:   return
+func.func @remove_dead_block() {
+  cf.br ^bb2
+^bb1:
+  %0 = arith.constant 0 : i16
+  cf.br ^bb2
+^bb2:
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func @potentially_side_effecting_op()
+//  CHECK-NEXT:   "test.print"
+//  CHECK-NEXT:   return
+func.func @potentially_side_effecting_op() {
+  "test.print"() : () -> ()
+  return
+}
+
+// -----
+
+// CHECK-LABEL: test.graph_region {
+//  CHECK-NEXT:   "test.baz"
+//  CHECK-NEXT: }
+test.graph_region {
+  %1 = arith.addi %0, %0 : i16
+  %0 = arith.constant 5 : i16
+  "test.baz"() : () -> i32
+}
+
+// -----
+
+// CHECK-LABEL: test.graph_region attributes {cycle} {
+//  CHECK-NEXT:   %[[a:.*]] = arith.addi %[[b:.*]], %[[b]] : i16
+//  CHECK-NEXT:   %[[b]] = arith.addi %[[a]], %[[a]] : i16
+//  CHECK-NEXT:   "test.baz"
+//  CHECK-NEXT: }
+test.graph_region attributes {cycle} {
+  %0 = arith.addi %1, %1 : i16
+  %1 = arith.addi %0, %0 : i16
+  "test.baz"() : () -> i32
+}
+
+// -----
+
+// CHECK-LABEL: dead_blocks()
+//  CHECK-NEXT:   cf.br ^[[bb3:.*]]
+//  CHECK-NEXT: ^[[bb3]]:
+//  CHECK-NEXT:   return
+// NOBLOCKS-LABEL: dead_blocks()
+//  NOBLOCKS-NEXT:   cf.br ^[[bb3:.*]]
+//  NOBLOCKS-NEXT: ^[[bb1:.*]]:  // pred
+//  NOBLOCKS-NEXT:   "test.print"
+//  NOBLOCKS-NEXT:   cf.br ^[[bb2:.*]]
+//  NOBLOCKS-NEXT: ^[[bb2]]:  // pred
+//  NOBLOCKS-NEXT:   cf.br ^[[bb1]]
+//  NOBLOCKS-NEXT: ^[[bb3]]:
+//  NOBLOCKS-NEXT:   return
+func.func @dead_blocks() {
+  cf.br ^bb3
+^bb1:
+  "test.print"() : () -> ()
+  cf.br ^bb2
+^bb2:
+  cf.br ^bb1
+^bb3:
+  return
+}



More information about the Mlir-commits mailing list