[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 ®ion : 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(®ion);
+ 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