[Mlir-commits] [mlir] [MLIR] `remove-dead-values` and RegionBranchOp canononicalizations fixes around fixes around the `IsolatedFromAbove` ops (PR #224831)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sat Sep 19 04:49:32 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir

Author: Ivan Butygin (Hardcode84)

<details>
<summary>Changes</summary>

I have a downstream op which is both `RegionBranchOpInterface` and `IsolatedFromAbove`, trying use `remove-dead-values` on it and especially trying to use this op as pass root uncovered various issues.

* `LivenessAnalysis` were treating yielded values as dead
* `RegionBranchOpInterface` canonicalization were just asserting on `IsolatedFromAbove`
* When rooting on `func.func` it would leave callers invalid.
* Subsequent canonicalization could reach outside the scope.

---

Patch is 24.56 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/224831.diff


9 Files Affected:

- (modified) mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h (+2) 
- (modified) mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp (+9) 
- (modified) mlir/lib/Interfaces/ControlFlowInterfaces.cpp (+15-12) 
- (modified) mlir/lib/Transforms/RemoveDeadValues.cpp (+33-16) 
- (added) mlir/test/Transforms/isolated-region-branch-canonicalize.mlir (+156) 
- (added) mlir/test/Transforms/remove-dead-values-function-root.mlir (+17) 
- (added) mlir/test/Transforms/remove-dead-values-nested-root.mlir (+122) 
- (modified) mlir/test/lib/Dialect/Test/TestOpDefs.cpp (+37) 
- (modified) mlir/test/lib/Dialect/Test/TestOps.td (+17) 


``````````diff
diff --git a/mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h b/mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h
index 90947d06e7999..f27188e2cc75a 100644
--- a/mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h
+++ b/mlir/include/mlir/Analysis/DataFlow/LivenessAnalysis.h
@@ -79,6 +79,8 @@ class LivenessAnalysis : public SparseBackwardDataFlowAnalysis<Liveness> {
 public:
   using SparseBackwardDataFlowAnalysis::SparseBackwardDataFlowAnalysis;
 
+  LogicalResult initialize(Operation *top) override;
+
   LogicalResult visitOperation(Operation *op, ArrayRef<Liveness *> operands,
                                ArrayRef<const Liveness *> results) override;
 
diff --git a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
index 07e3c98e0bb60..706f89dcd7c5d 100644
--- a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
@@ -48,6 +48,15 @@ ChangeResult Liveness::meet(const AbstractSparseLattice &other) {
 // LivenessAnalysis
 //===----------------------------------------------------------------------===//
 
+LogicalResult LivenessAnalysis::initialize(Operation *top) {
+  // Users outside the analysis root are not visited. Mark all root results live
+  // so that this missing information does not make their producers dead. For a
+  // region branch root, this keeps the yielded values and their producers live.
+  for (Value result : top->getResults())
+    setToExitState(getLatticeElement(result));
+  return SparseBackwardDataFlowAnalysis<Liveness>::initialize(top);
+}
+
 /// For every value, liveness analysis determines whether or not it is "live".
 ///
 /// A value is considered "live" iff it:
diff --git a/mlir/lib/Interfaces/ControlFlowInterfaces.cpp b/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
index d505ff0772a9c..dc9068aa7a8d8 100644
--- a/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
+++ b/mlir/lib/Interfaces/ControlFlowInterfaces.cpp
@@ -722,9 +722,6 @@ struct MakeRegionBranchOpSuccessorInputsDead : public RewritePattern {
 
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
-    assert(!op->hasTrait<OpTrait::IsIsolatedFromAbove>() &&
-           "isolated-from-above ops are not supported");
-
     // Compute the mapping of successor inputs to successor operands.
     auto regionBranchOp = cast<RegionBranchOpInterface>(op);
     RegionBranchInverseSuccessorMapping inputToOperands;
@@ -732,6 +729,7 @@ struct MakeRegionBranchOpSuccessorInputsDead : public RewritePattern {
 
     // Try to replace the uses of each successor input one-by-one.
     bool changed = false;
+    const bool isIsolated = op->hasTrait<OpTrait::IsIsolatedFromAbove>();
     for (Value value : inputToOperands.keys()) {
       // Nothing to do for successor inputs that are already dead.
       if (value.use_empty())
@@ -744,8 +742,19 @@ struct MakeRegionBranchOpSuccessorInputsDead : public RewritePattern {
               /*maxReachableValues=*/1)) ||
           reachableValues.empty())
         continue;
-      assert(*reachableValues.begin() != value &&
+      Value replacement = *reachableValues.begin();
+      assert(replacement != value &&
              "successor inputs are supposed to be excluded");
+      // A value inside an isolated region cannot be replaced with a value from
+      // another region, even if the replacement dominates its uses. Op results
+      // are in the parent region and do not cross this isolation boundary.
+      // Successor inputs are direct region arguments or results of this op.
+      // Valid input IR already prevents captures across nested isolation
+      // boundaries, so only this op's boundary needs an additional check.
+      Region *valueRegion = value.getParentRegion();
+      if (isIsolated && valueRegion->getParentOp() == op &&
+          replacement.getParentRegion() != valueRegion)
+        continue;
       // Do not replace `value` with the found reachable value if doing so
       // would violate dominance. Example:
       // %r = scf.execute_region ... {
@@ -755,9 +764,9 @@ struct MakeRegionBranchOpSuccessorInputsDead : public RewritePattern {
       // use(%r)
       // In the above example, reachableValues(%r) = {%a}, but %a cannot be
       // used as a replacement for %r due to dominance / scope.
-      if (!isDefinedBefore(regionBranchOp, *reachableValues.begin(), value))
+      if (!isDefinedBefore(regionBranchOp, replacement, value))
         continue;
-      rewriter.replaceAllUsesWith(value, *reachableValues.begin());
+      rewriter.replaceAllUsesWith(value, replacement);
       changed = true;
     }
     return success(changed);
@@ -845,9 +854,6 @@ struct RemoveDeadRegionBranchOpSuccessorInputs : public RewritePattern {
 
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
-    assert(!op->hasTrait<OpTrait::IsIsolatedFromAbove>() &&
-           "isolated-from-above ops are not supported");
-
     // Compute tied values: values that must come as a set. If you remove one,
     // you must remove all. If a successor op operand is forwarded to two
     // successor inputs %a and %b, both %a and %b are in the same set.
@@ -995,9 +1001,6 @@ struct RemoveDuplicateSuccessorInputUses : public RewritePattern {
 
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
-    assert(!op->hasTrait<OpTrait::IsIsolatedFromAbove>() &&
-           "isolated-from-above ops are not supported");
-
     // Collect all successor inputs and sort them. When dropping the uses of a
     // successor input, we'd like to also drop the uses of the same tied
     // successor inputs. Otherwise, a set of tied successor inputs may not
diff --git a/mlir/lib/Transforms/RemoveDeadValues.cpp b/mlir/lib/Transforms/RemoveDeadValues.cpp
index a4c2aa59b4421..fe7eed7ce54c6 100644
--- a/mlir/lib/Transforms/RemoveDeadValues.cpp
+++ b/mlir/lib/Transforms/RemoveDeadValues.cpp
@@ -786,14 +786,14 @@ struct RemoveDeadValues
 
 void RemoveDeadValues::runOnOperation() {
   auto &la = getAnalysis<RunLivenessAnalysis>();
-  Operation *module = getOperation();
+  Operation *root = getOperation();
 
   // Build a symbol user map once up front so that processFuncOp can look up the
   // callers of each function in O(1). Otherwise, each call would walk the
-  // entire module to find the callers, making the pass O(numFunctions *
+  // entire root to find the callers, making the pass O(numFunctions *
   // numOperations).
   SymbolTableCollection symbolTableCollection;
-  SymbolUserMap symbolUserMap(symbolTableCollection, module);
+  SymbolUserMap symbolUserMap(symbolTableCollection, root);
 
   // Tracks values eligible for erasure - complements liveness analysis to
   // identify "droppable" values.
@@ -803,7 +803,12 @@ void RemoveDeadValues::runOnOperation() {
   // end of this pass.
   RDVFinalCleanupList finalCleanupList;
 
-  module->walk([&](Operation *op) {
+  root->walk([&](Operation *op) {
+    // Do not erase the pass root or change its operands and results. In
+    // particular, the symbol user map cannot see callers outside the root, so
+    // changing a root function's signature would leave those calls invalid.
+    if (op == root)
+      return;
     if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
       processFuncOp(funcOp, symbolUserMap, la, deadVals, finalCleanupList);
     } else if (auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
@@ -821,27 +826,39 @@ void RemoveDeadValues::runOnOperation() {
     }
   });
 
-  MLIRContext *context = module->getContext();
+  MLIRContext *context = root->getContext();
   cleanUpDeadVals(context, finalCleanupList);
 
   if (!canonicalize)
     return;
 
-  // Canonicalize all region branch ops.
-  SmallVector<Operation *> opsToCanonicalize;
-  module->walk([&](RegionBranchOpInterface regionBranchOp) {
-    opsToCanonicalize.push_back(regionBranchOp.getOperation());
-  });
-  // Collect all canonicalization patterns for region branch ops.
+  // Collect and freeze the patterns once for all root regions.
   RewritePatternSet owningPatterns(context);
   DenseSet<RegisteredOperationName> populatedPatterns;
-  for (Operation *op : opsToCanonicalize)
+  root->walk([&](RegionBranchOpInterface regionBranchOp) {
+    Operation *op = regionBranchOp.getOperation();
+    if (op == root)
+      return;
     if (std::optional<RegisteredOperationName> info = op->getRegisteredInfo())
       if (populatedPatterns.insert(*info).second)
         info->getCanonicalizationPatterns(owningPatterns, context);
-  if (failed(applyOpPatternsGreedily(opsToCanonicalize,
-                                     std::move(owningPatterns)))) {
-    module->emitError("greedy pattern rewrite failed to converge");
-    signalPassFailure();
+  });
+  FrozenRewritePatternSet patterns(std::move(owningPatterns));
+
+  // Process each root region separately. For operations in different root
+  // regions, the inferred common scope can contain the root itself.
+  for (Region &region : root->getRegions()) {
+    SmallVector<Operation *> opsToCanonicalize;
+    region.walk([&](RegionBranchOpInterface regionBranchOp) {
+      opsToCanonicalize.push_back(regionBranchOp.getOperation());
+    });
+    // The greedy driver can add ancestors to its worklist. Set an explicit
+    // scope so that it cannot rewrite the root or operations outside the root.
+    GreedyRewriteConfig config;
+    config.setScope(&region);
+    if (failed(applyOpPatternsGreedily(opsToCanonicalize, patterns, config))) {
+      root->emitError("greedy pattern rewrite failed to converge");
+      return signalPassFailure();
+    }
   }
 }
diff --git a/mlir/test/Transforms/isolated-region-branch-canonicalize.mlir b/mlir/test/Transforms/isolated-region-branch-canonicalize.mlir
new file mode 100644
index 0000000000000..50ed4495d848a
--- /dev/null
+++ b/mlir/test/Transforms/isolated-region-branch-canonicalize.mlir
@@ -0,0 +1,156 @@
+// RUN: mlir-opt %s --canonicalize | FileCheck %s
+// RUN: mlir-opt %s --remove-dead-values --canonicalize | FileCheck %s
+
+func.func private @sink(i32)
+
+// CHECK-LABEL: func.func @dead_input
+// CHECK: "test.isolated_region_branch"(%{{.*}}) ({
+// CHECK: ^bb0(%[[ARG:.*]]: i32):
+// CHECK: call @sink(%[[ARG]])
+func.func @dead_input(%x: i32, %y: i32) -> i32 {
+  %r = "test.isolated_region_branch"(%x, %y) ({
+  ^bb0(%arg: i32, %dead: i32):
+    call @sink(%arg) : (i32) -> ()
+    %sum = arith.addi %arg, %arg : i32
+    "test.isolated_region_yield"(%sum) : (i32) -> ()
+  }) : (i32, i32) -> i32
+  return %r : i32
+}
+
+// CHECK-LABEL: func.func @duplicate_inputs
+// CHECK: "test.isolated_region_branch"(%{{.*}}) ({
+// CHECK: ^bb0(%[[ARG:.*]]: i32):
+// CHECK: call @sink(%[[ARG]])
+// CHECK: call @sink(%[[ARG]])
+// CHECK: arith.addi %[[ARG]], %[[ARG]]
+func.func @duplicate_inputs(%x: i32) -> i32 {
+  %r = "test.isolated_region_branch"(%x, %x) ({
+  ^bb0(%a: i32, %b: i32):
+    call @sink(%a) : (i32) -> ()
+    call @sink(%b) : (i32) -> ()
+    %sum = arith.addi %a, %b : i32
+    "test.isolated_region_yield"(%sum) : (i32) -> ()
+  }) : (i32, i32) -> i32
+  return %r : i32
+}
+
+// CHECK-LABEL: func.func @duplicate_results
+// CHECK: %[[R:.*]] = "test.isolated_region_branch"
+// CHECK: "test.isolated_region_yield"(%{{.*}}) : (i32) -> ()
+// CHECK: return %[[R]], %[[R]] : i32, i32
+func.func @duplicate_results(%x: i32) -> (i32, i32) {
+  %r:2 = "test.isolated_region_branch"(%x) ({
+  ^bb0(%arg: i32):
+    %sum = arith.addi %arg, %arg : i32
+    "test.isolated_region_yield"(%sum, %sum) : (i32, i32) -> ()
+  }) : (i32) -> (i32, i32)
+  return %r#0, %r#1 : i32, i32
+}
+
+// CHECK-LABEL: func.func @forwarded_result
+// CHECK-SAME: %[[X:.*]]: i32
+// CHECK: "test.isolated_region_branch"(%[[X]]) ({
+// CHECK: ^bb0(%[[ARG:.*]]: i32):
+// CHECK: call @sink(%[[ARG]])
+// CHECK: "test.isolated_region_yield"() : () -> ()
+// CHECK: return %[[X]] : i32
+func.func @forwarded_result(%x: i32) -> i32 {
+  %r = "test.isolated_region_branch"(%x) ({
+  ^bb0(%arg: i32):
+    call @sink(%arg) : (i32) -> ()
+    "test.isolated_region_yield"(%arg) : (i32) -> ()
+  }) : (i32) -> i32
+  return %r : i32
+}
+
+// An op result can be replaced with an input from an ancestor region. Keep the
+// region argument in the isolated body so that its uses do not capture %x.
+// CHECK-LABEL: func.func @forwarded_ancestor_result
+// CHECK-SAME: %{{.*}}: i1, %[[X:.*]]: i32
+// CHECK: %[[SELECTED:.*]] = arith.select %{{.*}}, %[[X]], %{{.*}} : i32
+// CHECK: scf.if
+// CHECK: "test.isolated_region_branch"(%[[X]]) ({
+// CHECK: ^bb0(%[[ARG:.*]]: i32):
+// CHECK: call @sink(%[[ARG]])
+// CHECK: "test.isolated_region_yield"() : () -> ()
+// CHECK: }) : (i32) -> ()
+// CHECK: return %[[SELECTED]] : i32
+func.func @forwarded_ancestor_result(%cond: i1, %x: i32) -> i32 {
+  %r = scf.if %cond -> i32 {
+    %forwarded = "test.isolated_region_branch"(%x) ({
+    ^bb0(%arg: i32):
+      func.call @sink(%arg) : (i32) -> ()
+      "test.isolated_region_yield"(%arg) : (i32) -> ()
+    }) : (i32) -> i32
+    scf.yield %forwarded : i32
+  } else {
+    %zero = arith.constant 0 : i32
+    scf.yield %zero : i32
+  }
+  return %r : i32
+}
+
+// Keep both isolation boundaries when an inner isolated op is nested in an if.
+// CHECK-LABEL: func.func @nested_isolation
+// CHECK-SAME: %[[COND:.*]]: i1, %[[X:.*]]: i32
+// CHECK: "test.isolated_region_branch"(%[[COND]], %[[X]]) ({
+// CHECK: ^bb0(%[[C:.*]]: i1, %[[A:.*]]: i32):
+// CHECK: scf.if %[[C]]
+// CHECK: "test.isolated_region_branch"(%[[A]]) ({
+// CHECK: ^bb0(%[[B:.*]]: i32):
+// CHECK: call @sink(%[[B]])
+// CHECK: "test.isolated_region_yield"() : () -> ()
+// CHECK: }) : (i32) -> ()
+// CHECK: "test.isolated_region_yield"() : () -> ()
+// CHECK: }) : (i1, i32) -> ()
+// CHECK: return %[[X]] : i32
+func.func @nested_isolation(%cond: i1, %x: i32) -> i32 {
+  %r = "test.isolated_region_branch"(%cond, %x) ({
+  ^bb0(%c: i1, %a: i32):
+    %selected = scf.if %c -> i32 {
+      %inner = "test.isolated_region_branch"(%a) ({
+      ^bb0(%b: i32):
+        func.call @sink(%b) : (i32) -> ()
+        "test.isolated_region_yield"(%b) : (i32) -> ()
+      }) : (i32) -> i32
+      scf.yield %inner : i32
+    } else {
+      scf.yield %a : i32
+    }
+    "test.isolated_region_yield"(%selected) : (i32) -> ()
+  }) : (i1, i32) -> i32
+  return %r : i32
+}
+
+// Keep replacement arguments in their own regions. Remove a shared input only
+// when its arguments are dead in all regions.
+// CHECK-LABEL: func.func @multiple_regions
+// CHECK-SAME: %[[X:.*]]: i32,
+// CHECK: %[[R:.*]] = "test.isolated_region_branch"(%[[X]]) ({
+// CHECK: ^bb0(%[[A:.*]]: i32):
+// CHECK: call @sink(%[[A]])
+// CHECK: call @sink(%[[A]])
+// CHECK: %[[SUM:.*]] = arith.addi %[[A]], %[[A]]
+// CHECK: "test.isolated_region_yield"(%[[SUM]]) : (i32) -> ()
+// CHECK: ^bb0(%[[B:.*]]: i32):
+// CHECK: call @sink(%[[B]])
+// CHECK: call @sink(%[[B]])
+// CHECK: %[[PRODUCT:.*]] = arith.muli %[[B]], %[[B]]
+// CHECK: "test.isolated_region_yield"(%[[PRODUCT]]) : (i32) -> ()
+// CHECK: return %[[R]], %[[R]] : i32, i32
+func.func @multiple_regions(%x: i32, %y: i32) -> (i32, i32) {
+  %r:2 = "test.isolated_region_branch"(%x, %x, %y) ({
+  ^bb0(%a: i32, %b: i32, %dead: i32):
+    call @sink(%a) : (i32) -> ()
+    call @sink(%b) : (i32) -> ()
+    %sum = arith.addi %a, %b : i32
+    "test.isolated_region_yield"(%sum, %sum) : (i32, i32) -> ()
+  }, {
+  ^bb0(%a: i32, %b: i32, %dead: i32):
+    call @sink(%a) : (i32) -> ()
+    call @sink(%b) : (i32) -> ()
+    %product = arith.muli %a, %b : i32
+    "test.isolated_region_yield"(%product, %product) : (i32, i32) -> ()
+  }) : (i32, i32, i32) -> (i32, i32)
+  return %r#0, %r#1 : i32, i32
+}
diff --git a/mlir/test/Transforms/remove-dead-values-function-root.mlir b/mlir/test/Transforms/remove-dead-values-function-root.mlir
new file mode 100644
index 0000000000000..2cec260eb9640
--- /dev/null
+++ b/mlir/test/Transforms/remove-dead-values-function-root.mlir
@@ -0,0 +1,17 @@
+// RUN: mlir-opt %s --pass-pipeline='builtin.module(func.func(remove-dead-values))' | FileCheck %s
+
+// CHECK-LABEL: func.func private @callee
+// CHECK-SAME: %[[ARG:.*]]: i32, %{{.*}}: i32) -> i32
+// CHECK-NEXT: %[[SUM:.*]] = arith.addi %[[ARG]], %[[ARG]] : i32
+// CHECK-NEXT: return %[[SUM]] : i32
+func.func private @callee(%arg: i32, %unused: i32) -> i32 {
+  %sum = arith.addi %arg, %arg : i32
+  return %sum : i32
+}
+
+// CHECK-LABEL: func.func @caller
+// CHECK: call @callee(%{{.*}}, %{{.*}}) : (i32, i32) -> i32
+func.func @caller(%x: i32) -> i32 {
+  %r = call @callee(%x, %x) : (i32, i32) -> i32
+  return %r : i32
+}
diff --git a/mlir/test/Transforms/remove-dead-values-nested-root.mlir b/mlir/test/Transforms/remove-dead-values-nested-root.mlir
new file mode 100644
index 0000000000000..20b6b4cc99657
--- /dev/null
+++ b/mlir/test/Transforms/remove-dead-values-nested-root.mlir
@@ -0,0 +1,122 @@
+// RUN: mlir-opt %s --pass-pipeline='builtin.module(func.func(test.isolated_region_branch(remove-dead-values)))' > %t.parallel
+// RUN: FileCheck %s --check-prefixes=CHECK,CANON < %t.parallel
+// RUN: mlir-opt %s --mlir-disable-threading --pass-pipeline='builtin.module(func.func(test.isolated_region_branch(remove-dead-values)))' > %t.serial
+// RUN: diff %t.parallel %t.serial
+// RUN: mlir-opt %s --pass-pipeline='builtin.module(func.func(test.isolated_region_branch(remove-dead-values{canonicalize=false})))' | FileCheck %s
+
+// CHECK-LABEL: func.func @live_result
+// CHECK-SAME: %[[X:.*]]: i32
+// CHECK: %[[ROOT:.*]] = "test.isolated_region_branch"(%[[X]])
+// CHECK: ^bb0(%[[ARG:.*]]: i32):
+// CHECK-NEXT: %[[SUM:.*]] = arith.addi %[[ARG]], %[[ARG]] : i32
+// CHECK-NEXT: "test.isolated_region_yield"(%[[SUM]])
+// CHECK: return %[[ROOT]] : i32
+func.func @live_result(%x: i32) -> i32 {
+  %r = "test.isolated_region_branch"(%x) ({
+  ^bb0(%arg: i32):
+    %dead = arith.muli %arg, %arg : i32
+    %sum = arith.addi %arg, %arg : i32
+    "test.isolated_region_yield"(%sum) : (i32) -> ()
+  }) : (i32) -> i32
+  return %r : i32
+}
+
+// Unused root results and inputs are outside this pass's rewrite scope.
+// CHECK-LABEL: func.func @unused_result
+// CHECK-SAME: %[[X:.*]]: i32
+// CHECK: "test.isolated_region_branch"(%[[X]], %[[X]])
+// CHECK: ^bb0(%[[A:.*]]: i32, %[[UNUSED:.*]]: i32):
+// CHECK-NEXT: %[[SUM:.*]] = arith.addi %[[A]], %[[A]] : i32
+// CHECK-NEXT: "test.isolated_region_yield"(%[[SUM]])
+// CHECK: "test.isolated_region_branch"(%[[X]], %[[X]])
+// CHECK: "test.isolated_region_yield"
+// CHECK: return
+func.func @unused_result(%x: i32) {
+  %r = "test.isolated_region_branch"(%x, %x) ({
+  ^bb0(%arg: i32, %unused: i32):
+    %sum = arith.addi %arg, %arg : i32
+    "test.isolated_region_yield"(%sum) : (i32) -> ()
+  }) : (i32, i32) -> i32
+  %other = "test.isolated_region_branch"(%x, %x) ({
+  ^bb0(%arg: i32, %unused: i32):
+    "test.isolated_region_yield"(%arg) : (i32) -> ()
+  }) : (i32, i32) -> i32
+  return
+}
+
+// CHECK-LABEL: func.func @multiple_regions
+// CHECK: "test.isolated_region_branch"()
+// CHECK-NEXT: "test.isolated_region_yield"()
+// CHECK-NEXT: }, {
+// CHECK-NEXT: "test.isolated_region_yield"()
+// CHECK: return
+func.func @multiple_regions() {
+  "test.isolated_region_branch"() ({
+    %dead = arith.constant 1 : i32
+    "test.isolated_region_yield"() : () -> ()
+  }, {
+    %dead = arith.constant 2 : i32
+    "test.isolated_region_yield"() : () -> ()
+  }) : () -> ()
+  return
+}
+
+// CHECK-LABEL: func.func @effects
+// CHECK: "test.isolated_region_branch"
+// CHECK: ^bb0(%[[MEM:.*]]: memref<i32>, %[[VAL:.*]]: i32):
+// CHECK-NEXT: memref.store %[[VAL]], %[[MEM]][] : memref<i32>
+// CHECK-NEXT: "test.isolated_region_yield"()
+func.func @effects(%mem: memref<i32>, %x: i32) {
+  "test.isolated_region_branch"(%mem, %x) ({
+  ^bb0(%arg: memref<i32>, %val: i32):
+    %dead = arith.muli %val, %val : i32
+    memref.store %val, %arg[] : memref<i32>
+    "test.isolated_region_yield"() : () -> ()
+  }) : (memref<i32>, i32) -> ()
+  return
+}
+
+// Remove dead loop results in both root regions with the shared patterns.
+// CANON-LABEL: func.func @dead_carry
+// CANON: "test.isolated_region_branch"
+// CANON: scf.for {{.*}} iter_args(%[[CARRY:.*]] = %{{.*}}) -> (i32)
+// CANON: scf.yield %{{.*}} : i32
+...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/224831


More information about the Mlir-commits mailing list