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

Mehdi Amini llvmlistbot at llvm.org
Thu Apr 30 01:12:21 PDT 2026


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

>From bf24c25c424a05d62b8e4763d3bd189a22d2e79e 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.

When erasing a worklist operation, account for the operands of every nested operation that is implicitly erased with it. This lets deadness propagate through producers that are used only by nested operations inside an erased parent 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 LDBG tracing for the helper, using higher verbosity levels for region, worklist, and operand-level details.

Add tests for nested-region cleanup, unreachable blocks, graph regions, dead cycles, non-recursive mode, preserving unreachable blocks with remove-blocks=false, and propagation through operands of nested operations erased with their parent.

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     | 155 +++++++++++++-
 .../Transforms/dead-code-elimination.mlir     | 200 ++++++++++++++++++
 8 files changed, 429 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..cee48b0b6b126 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,143 @@ LogicalResult mlir::runRegionDCE(RewriterBase &rewriter,
   return deleteDeadness(rewriter, regions, liveMap);
 }
 
+bool mlir::eliminateTriviallyDeadOps(RewriterBase &rewriter, Region &region,
+                                     bool includeNestedRegions) {
+  LDBG() << "Starting eliminateTriviallyDeadOps with "
+         << region.getBlocks().size()
+         << " blocks, includeNestedRegions=" << includeNestedRegions;
+  if (Operation *parentOp = region.getParentOp())
+    LDBG(2) << " -> parent operation: "
+            << OpWithFlags(parentOp, OpPrintingFlags().skipRegions());
+
+  bool changed = false;
+  unsigned erasedOps = 0;
+  unsigned seededOps = 0;
+  unsigned enqueuedDefs = 0;
+
+  // 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)) {
+    LDBG(2) << "Scanning block " << &block << " with "
+            << block.getOperations().size() << " operations";
+    for (Operation &op :
+         llvm::make_early_inc_range(llvm::reverse(block.getOperations()))) {
+      LDBG(3) << "Visiting operation: "
+              << OpWithFlags(&op, OpPrintingFlags().skipRegions());
+      if (isOpTriviallyDead(&op)) {
+        LDBG() << "Erasing trivially dead operation: "
+               << OpWithFlags(&op, OpPrintingFlags().skipRegions());
+        rewriter.eraseOp(&op);
+        changed = true;
+        ++erasedOps;
+        continue;
+      }
+      if (includeNestedRegions) {
+        unsigned regionIdx = 0;
+        for (Region &nested : op.getRegions()) {
+          LDBG(2) << "Recursing into nested region #" << regionIdx
+                  << " of operation " << op.getName();
+          bool nestedChanged =
+              eliminateTriviallyDeadOps(rewriter, nested, includeNestedRegions);
+          LDBG(2) << "Finished nested region #" << regionIdx << " of operation "
+                  << op.getName() << ", changed=" << nestedChanged;
+          changed |= nestedChanged;
+          ++regionIdx;
+        }
+      }
+    }
+  }
+
+  // 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;
+
+  LDBG(2) << "Stage 2: Seeding trivially dead operation worklist";
+  for (Operation &op : region.getOps()) {
+    if (isOpTriviallyDead(&op) && visited.insert(&op).second) {
+      LDBG(2) << "Seeded worklist with operation: "
+              << OpWithFlags(&op, OpPrintingFlags().skipRegions());
+      worklist.push_back(&op);
+      changed = true;
+      ++seededOps;
+    }
+  }
+  LDBG(2) << "Initial worklist size: " << worklist.size();
+
+  while (!worklist.empty()) {
+    Operation *op = worklist.pop_back_val();
+    LDBG(2) << "Popped operation from worklist: "
+            << OpWithFlags(op, OpPrintingFlags().skipRegions());
+    /// 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.
+    ///
+    /// Walk nested operations as well because erasing `op` also implicitly
+    /// erases every operation nested under it and therefore drops their operand
+    /// uses.
+    op->walk([&](Operation *erasedOp) {
+      LDBG(3) << "Processing operands of operation erased: "
+              << OpWithFlags(erasedOp, OpPrintingFlags().skipRegions());
+      for (OpOperand &opOperand : erasedOp->getOpOperands()) {
+        Operation *defOp = opOperand.get().getDefiningOp();
+        if (!defOp) {
+          LDBG(4) << "Skipping operand #" << opOperand.getOperandNumber()
+                  << ": value has no defining operation";
+          continue;
+        }
+        if (defOp->getParentRegion() != &region) {
+          LDBG(4) << "Skipping operand #" << opOperand.getOperandNumber()
+                  << ": defining operation is outside the current region";
+          continue;
+        }
+        if (visited.count(defOp)) {
+          LDBG(4) << "Skipping operand #" << opOperand.getOperandNumber()
+                  << ": defining operation was already visited";
+          continue;
+        }
+        LDBG(4) << "Dropping operand #" << opOperand.getOperandNumber()
+                << " from defining operation: "
+                << OpWithFlags(defOp, OpPrintingFlags().skipRegions());
+        opOperand.drop();
+        if (isOpTriviallyDead(defOp)) {
+          LDBG(2) << "Enqueued newly trivially dead defining operation: "
+                  << OpWithFlags(defOp, OpPrintingFlags().skipRegions());
+          worklist.push_back(defOp);
+          ++enqueuedDefs;
+        } else {
+          LDBG(4) << "Defining operation is still not trivially dead: "
+                  << OpWithFlags(defOp, OpPrintingFlags().skipRegions());
+        }
+      }
+    });
+    LDBG() << "Erasing trivially dead worklist operation: "
+           << OpWithFlags(op, OpPrintingFlags().skipRegions());
+    rewriter.eraseOp(op);
+    ++erasedOps;
+  }
+  LDBG() << "Finished eliminateTriviallyDeadOps, erased " << erasedOps
+         << " operations, seeded " << seededOps << " operations, enqueued "
+         << enqueuedDefs << " defining operations, changed=" << changed;
+  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..fa277c8a38b64
--- /dev/null
+++ b/mlir/test/Transforms/dead-code-elimination.mlir
@@ -0,0 +1,200 @@
+// 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: func @miss_nested_operand_of_erased_op(
+//  CHECK-NOT:    scf.if
+//  CHECK-NOT:    arith.addi
+//  CHECK:        return
+func.func @miss_nested_operand_of_erased_op(%arg0: i16, %cond: i1) {
+  cf.br ^producer
+
+^user:
+  %dead_user = arith.addi %if_result, %if_result : i16
+  cf.br ^exit
+
+^if_block:
+  %if_result = scf.if %cond -> i16 {
+    %nested_use = arith.addi %producer_value, %producer_value : i16
+    scf.yield %nested_use : i16
+  } else {
+    scf.yield %producer_value : i16
+  }
+  cf.br ^user
+
+^producer:
+  %producer_value = arith.addi %arg0, %arg0 : i16
+  cf.br ^if_block
+
+^exit:
+  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