[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 ®ion,
+ 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 ®ion : 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 ®ion : 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 ®ion : op.getRegions())
- worklist.push_back(®ion);
+ if (recurse)
+ for (Operation &op : region->front())
+ for (Region ®ion : op.getRegions())
+ worklist.push_back(®ion);
continue;
}
@@ -243,9 +246,10 @@ LogicalResult mlir::eraseUnreachableBlocks(RewriterBase &rewriter,
}
// Walk any regions within this block.
- for (Operation &op : block)
- for (Region ®ion : op.getRegions())
- worklist.push_back(®ion);
+ if (recurse)
+ for (Operation &op : block)
+ for (Region ®ion : op.getRegions())
+ worklist.push_back(®ion);
}
}
@@ -506,6 +510,72 @@ LogicalResult mlir::runRegionDCE(RewriterBase &rewriter,
return deleteDeadness(rewriter, regions, liveMap);
}
+bool mlir::eliminateTriviallyDeadOps(RewriterBase &rewriter, Region ®ion,
+ 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() != ®ion || 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