[Mlir-commits] [mlir] [mlir] Optimize `isOpTriviallyDead` greedy rewriter checks for deeply nested IR (PR #231423)

Ivan Butygin llvmlistbot at llvm.org
Tue Oct 13 02:32:52 PDT 2026


https://github.com/Hardcode84 created https://github.com/llvm/llvm-project/pull/231423

See the discussion https://github.com/llvm/llvm-project/pull/230961#issuecomment-6130021318

Driver checks `isOpTriviallyDead` after each IR modifications and `isOpTriviallyDead` walked the entire nested chain for `HasRecursiveMemoryEffects`.

Cache op deadness in driver and invalidate on IR change.

Also, `Operation *dumpRootOp = getDumpRootOp(op);` only needed for debug printing was called unconditionally and was also noticeable in profiler. Call it only if debug printing is actually enabled.

Added synthetic 1000-nested IR test, generated by python. Test doesn't actually check execution times, but I hope someone will notice if it suddenly start to take 30s.

Times for 1000-nested `scf.for` test on my machine:

| 1,000-nested case | Before | After | Speedup |
|---|---:|---:|---:|
| PR loop-range reproduction | 22.806 s | 0.271 s | 84.1× |
| Normal canonicalization stress test | 21.223 s | 1.058 s | 20.1× |

>From 023da155bd2e1c9c923a9cc7c338987d070f0e29 Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Tue, 13 Oct 2026 10:39:45 +0200
Subject: [PATCH] [mlir] Cache recursive deadness in the greedy rewrite driver

Repeated deadness queries scan nested regions after each rewrite. Cache
recursive effect summaries and invalidate them through rewriter hooks.
Check uses on each query and traverse uncached regions with an explicit
worklist to handle deeply nested IR.

Find the pattern dump root only when level-two debug logging is enabled.
Add cache invalidation unit tests, debug logging coverage, and a generated
1000-loop canonicalization stress test.
---
 .../mlir/Interfaces/SideEffectInterfaces.h    |  19 ++
 mlir/lib/Interfaces/SideEffectInterfaces.cpp  | 143 +++++++++---
 mlir/lib/Rewrite/PatternApplicator.cpp        |   6 +-
 .../Utils/GreedyPatternRewriteDriver.cpp      |  17 +-
 mlir/test/IR/pattern-application-debug.mlir   |  16 ++
 .../Transforms/canonicalize-deeply-nested.py  |  28 +++
 mlir/unittests/Transforms/CMakeLists.txt      |   4 +
 .../Transforms/GreedyPatternRewriteDriver.cpp | 211 ++++++++++++++++++
 8 files changed, 407 insertions(+), 37 deletions(-)
 create mode 100644 mlir/test/IR/pattern-application-debug.mlir
 create mode 100644 mlir/test/Transforms/canonicalize-deeply-nested.py
 create mode 100644 mlir/unittests/Transforms/GreedyPatternRewriteDriver.cpp

diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
index dfffbcaf1bbab..f75259e37beff 100644
--- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
+++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
@@ -15,7 +15,9 @@
 #define MLIR_INTERFACES_SIDEEFFECTINTERFACES_H
 
 #include "mlir/IR/OpDefinition.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/Twine.h"
+#include <cstdint>
 
 namespace mlir {
 namespace SideEffects {
@@ -546,6 +548,23 @@ bool isOpTriviallyDead(Operation *op);
 /// Note: Terminators and symbols are never considered to be trivially dead.
 bool wouldOpBeTriviallyDead(Operation *op);
 
+/// A cache for repeated deadness queries on nested operations. All changes to
+/// an operation or its nested IR must invalidate that operation. Moves must
+/// also invalidate the old parent. Invalidate operations before erasing them.
+class DeadnessCache {
+public:
+  /// Return true if the operation is unused and can be erased. Terminators and
+  /// symbols cannot be erased. Uses are checked on every query.
+  bool isOpTriviallyDead(Operation *op);
+
+  /// Invalidate the operation, its descendants, and its ancestors.
+  void invalidate(Operation *op);
+
+private:
+  enum class State : uint8_t { Alive, Dead };
+  DenseMap<Operation *, State> cache;
+};
+
 /// Returns true if the given operation is free of memory effects.
 ///
 /// An operation is free of memory effects if its implementation of
diff --git a/mlir/lib/Interfaces/SideEffectInterfaces.cpp b/mlir/lib/Interfaces/SideEffectInterfaces.cpp
index b5a6888e5e1a4..8ed3dd27b64c2 100644
--- a/mlir/lib/Interfaces/SideEffectInterfaces.cpp
+++ b/mlir/lib/Interfaces/SideEffectInterfaces.cpp
@@ -36,6 +36,35 @@ bool mlir::isOpTriviallyDead(Operation *op) {
   return op->use_empty() && wouldOpBeTriviallyDead(op);
 }
 
+static bool hasOnlyDeadEffects(Operation *op, bool hasRecursiveEffects) {
+  // If the op has memory effects, characterize them to see if the op can be
+  // trivially dead.
+  if (auto effectInterface = dyn_cast<MemoryEffectOpInterface>(op)) {
+    // Check if this op has no effects, or only allocates/reads memory.
+    SmallVector<MemoryEffects::EffectInstance, 1> effects;
+    effectInterface.getEffects(effects);
+
+    // Gather all results of this op that are allocated.
+    SmallPtrSet<Value, 4> allocResults;
+    for (const MemoryEffects::EffectInstance &effect : effects)
+      if (isa<MemoryEffects::Allocate>(effect.getEffect()) &&
+          effect.getValue() && effect.getValue().getDefiningOp() == op)
+        allocResults.insert(effect.getValue());
+    return llvm::all_of(effects,
+                        [&](const MemoryEffects::EffectInstance &effect) {
+                          // We can drop effects on values allocated by this
+                          // operation and returned as results. Otherwise, the
+                          // effect must be a read.
+                          return allocResults.contains(effect.getValue()) ||
+                                 isa<MemoryEffects::Read>(effect.getEffect());
+                        });
+  }
+  // If the op only has recursive side effects, treat the operation itself as
+  // having no effects. The caller must also check its children. Without an
+  // effect interface or recursive effects, conservatively assume live effects.
+  return hasRecursiveEffects;
+}
+
 /// Internal implementation of `mlir::wouldOpBeTriviallyDead` that also
 /// considers terminator operations as dead if they have no side effects. This
 /// allows for marking region operations as trivially dead without always being
@@ -66,49 +95,93 @@ static bool wouldOpBeTriviallyDeadImpl(Operation *rootOp) {
       }
     }
 
-    // If the op has memory effects, try to characterize them to see if the op
-    // is trivially dead here.
-    if (auto effectInterface = dyn_cast<MemoryEffectOpInterface>(op)) {
-      // Check to see if this op either has no effects, or only allocates/reads
-      // memory.
-      SmallVector<MemoryEffects::EffectInstance, 1> effects;
-      effectInterface.getEffects(effects);
+    if (!hasOnlyDeadEffects(op, hasRecursiveEffects))
+      return false;
+  }
 
-      // Gather all results of this op that are allocated.
-      SmallPtrSet<Value, 4> allocResults;
-      for (const MemoryEffects::EffectInstance &it : effects)
-        if (isa<MemoryEffects::Allocate>(it.getEffect()) && it.getValue() &&
-            it.getValue().getDefiningOp() == op)
-          allocResults.insert(it.getValue());
-
-      if (!llvm::all_of(effects, [&allocResults](
-                                     const MemoryEffects::EffectInstance &it) {
-            // We can drop effects if the value is an allocation and is a result
-            // of the operation.
-            if (allocResults.contains(it.getValue()))
-              return true;
-            // Otherwise, the effect must be a read.
-            return isa<MemoryEffects::Read>(it.getEffect());
-          })) {
-        return false;
-      }
+  // If we get here, none of the operations had effects that prevented marking
+  // 'op' as dead.
+  return true;
+}
+
+bool DeadnessCache::isOpTriviallyDead(Operation *root) {
+  // Cache only effects. Uses can change without a change to the defining op.
+  // Nested operations may have internal uses or be terminators even when the
+  // whole root can be erased, so these checks apply only to the root.
+  if (!root->use_empty() || root->mightHaveTrait<OpTrait::IsTerminator>() ||
+      isa<SymbolOpInterface>(root))
+    return false;
+
+  auto cachedRoot = cache.find(root);
+  if (cachedRoot != cache.end())
+    return cachedRoot->second == State::Dead;
+
+  // Repeated queries on nested operations would otherwise scan the same
+  // subtrees. Use a postorder traversal to cache each subtree's effects and
+  // skip it on later queries. Keep the traversal on an explicit worklist so
+  // deeply nested IR does not consume the C++ call stack.
+  struct WorkItem {
+    Operation *complete;
+    // The unvisited range [current, end) lets a block resume after a nested
+    // operation, without adding each sibling to the worklist. The initial
+    // range contains only the root, not the rest of its block. Completion
+    // markers do not use these iterators.
+    Block::iterator current;
+    Block::iterator end;
+  };
+  SmallVector<WorkItem, 8> worklist = {
+      {nullptr, Block::iterator(root), ++Block::iterator(root)}};
+  while (!worklist.empty()) {
+    WorkItem &item = worklist.back();
+    if (item.complete) {
+      // All nested operations have been checked without finding live effects.
+      cache[item.complete] = State::Dead;
+      worklist.pop_back();
       continue;
     }
-    // Otherwise, if the op only has recursive side effects we can treat the
-    // operation itself as having no effects. We will visit its children next.
-    if (hasRecursiveEffects)
+    if (item.current == item.end) {
+      worklist.pop_back();
       continue;
-
-    // If there were no effect interfaces, we treat this op as conservatively
-    // having effects.
-    return false;
+    }
+    Operation *op = &*item.current++;
+    auto cached = cache.find(op);
+    if (cached != cache.end() && cached->second == State::Dead)
+      continue;
+    bool recursive = op->hasTrait<OpTrait::HasRecursiveMemoryEffects>();
+    if (cached != cache.end() || !hasOnlyDeadEffects(op, recursive)) {
+      // One live effect keeps every ancestor up to the root live. Cache this
+      // result and stop; the remaining subtrees cannot change the answer.
+      for (Operation *ancestor = op;; ancestor = ancestor->getParentOp()) {
+        cache[ancestor] = State::Alive;
+        if (ancestor == root)
+          break;
+      }
+      return false;
+    }
+    if (!recursive) {
+      cache[op] = State::Dead;
+      continue;
+    }
+    // Process the child ranges before this completion marker. The op can be
+    // cached as dead only after all of its recursive effects are checked.
+    worklist.push_back({op, {}, {}});
+    for (Region &region : op->getRegions())
+      for (Block &block : region)
+        worklist.push_back({nullptr, block.begin(), block.end()});
   }
-
-  // If we get here, none of the operations had effects that prevented marking
-  // 'op' as dead.
   return true;
 }
 
+void DeadnessCache::invalidate(Operation *op) {
+  op->walk([&](Operation *nested) { cache.erase(nested); });
+  // An ancestor may report nested effects through its own effect interface,
+  // without querying the intermediate cache entries. A missing entry cannot
+  // stop invalidation of its ancestors.
+  for (Operation *parent = op->getParentOp(); parent;
+       parent = parent->getParentOp())
+    cache.erase(parent);
+}
+
 template <typename EffectTy>
 bool mlir::hasSingleEffect(Operation *op) {
   auto memOp = dyn_cast<MemoryEffectOpInterface>(op);
diff --git a/mlir/lib/Rewrite/PatternApplicator.cpp b/mlir/lib/Rewrite/PatternApplicator.cpp
index e1b56fd6efda0..4b6ca9bac0a0b 100644
--- a/mlir/lib/Rewrite/PatternApplicator.cpp
+++ b/mlir/lib/Rewrite/PatternApplicator.cpp
@@ -200,7 +200,11 @@ LogicalResult PatternApplicator::matchAndRewrite(
 #ifndef NDEBUG
           // Operation `op` may be invalidated after applying the rewrite
           // pattern.
-          Operation *dumpRootOp = getDumpRootOp(op);
+          Operation *dumpRootOp = nullptr;
+          // LLVM_DEBUG enables level 1, but IR dumps require level 2. Avoid
+          // walking ancestors to find the dump root when no IR will be printed.
+          LLVM_DEBUG(if (llvm::isCurrentDebugType(DEBUG_TYPE, 2)) dumpRootOp =
+                         getDumpRootOp(op));
 #endif
           if (pdlMatch) {
             result =
diff --git a/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp b/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp
index b71194e783ba0..f13e9d493aff2 100644
--- a/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp
+++ b/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp
@@ -408,6 +408,8 @@ class GreedyPatternRewriteDriver : public RewriterBase::Listener {
   /// The low-level pattern applicator.
   PatternApplicator matcher;
 
+  DeadnessCache deadnessCache;
+
 #if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
   ExpensiveChecks expensiveChecks;
 #endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
@@ -483,7 +485,7 @@ bool GreedyPatternRewriteDriver::processWorklist() {
     });
 
     // If the operation is trivially dead - remove it.
-    if (isOpTriviallyDead(op)) {
+    if (deadnessCache.isOpTriviallyDead(op)) {
       rewriter.eraseOp(op);
       changed = true;
 
@@ -665,17 +667,28 @@ void GreedyPatternRewriteDriver::addSingleOpToWorklist(Operation *op) {
 
 void GreedyPatternRewriteDriver::notifyBlockInserted(
     Block *block, Region *previous, Region::iterator previousIt) {
+  if (previous)
+    if (Operation *parent = previous->getParentOp())
+      deadnessCache.invalidate(parent);
+  if (Operation *parent = block->getParentOp())
+    deadnessCache.invalidate(parent);
   if (RewriterBase::Listener *listener = config.getListener())
     listener->notifyBlockInserted(block, previous, previousIt);
 }
 
 void GreedyPatternRewriteDriver::notifyBlockErased(Block *block) {
+  if (Operation *parent = block->getParentOp())
+    deadnessCache.invalidate(parent);
   if (RewriterBase::Listener *listener = config.getListener())
     listener->notifyBlockErased(block);
 }
 
 void GreedyPatternRewriteDriver::notifyOperationInserted(
     Operation *op, OpBuilder::InsertPoint previous) {
+  if (previous.isSet())
+    if (Operation *parent = previous.getBlock()->getParentOp())
+      deadnessCache.invalidate(parent);
+  deadnessCache.invalidate(op);
   LLVM_DEBUG({
     logger.startLine() << "** Insert  : '" << op->getName() << "'(" << op
                        << ")\n";
@@ -688,6 +701,7 @@ void GreedyPatternRewriteDriver::notifyOperationInserted(
 }
 
 void GreedyPatternRewriteDriver::notifyOperationModified(Operation *op) {
+  deadnessCache.invalidate(op);
   LLVM_DEBUG({
     logger.startLine() << "** Modified: '" << op->getName() << "'(" << op
                        << ")\n";
@@ -731,6 +745,7 @@ void GreedyPatternRewriteDriver::addOperandsToWorklist(Operation *op) {
 }
 
 void GreedyPatternRewriteDriver::notifyOperationErased(Operation *op) {
+  deadnessCache.invalidate(op);
   LLVM_DEBUG({
     logger.startLine() << "** Erase   : '" << op->getName() << "'(" << op
                        << ")\n";
diff --git a/mlir/test/IR/pattern-application-debug.mlir b/mlir/test/IR/pattern-application-debug.mlir
new file mode 100644
index 0000000000000..28e4da51b8d49
--- /dev/null
+++ b/mlir/test/IR/pattern-application-debug.mlir
@@ -0,0 +1,16 @@
+// REQUIRES: asserts
+// RUN: mlir-opt %s --test-walk-pattern-rewrite-driver --debug-only=pattern-application:1 -o /dev/null 2>&1 | FileCheck %s --check-prefix=LEVEL1 --implicit-check-not="IR Dump After Pattern Application"
+// RUN: mlir-opt %s --test-walk-pattern-rewrite-driver --debug-only=pattern-application:2 -o /dev/null 2>&1 | FileCheck %s --check-prefix=LEVEL2
+
+// The pattern erases its root. The dump must still show the enclosing function.
+// LEVEL1: matchAndRewrite successful
+// LEVEL2: IR Dump After Pattern Application
+// LEVEL2: func.func @replace_with_new_op()
+// LEVEL2: "test.new_op"
+// LEVEL2-NOT: "test.replace_with_new_op"
+// LEVEL2: return
+func.func @replace_with_new_op() -> i32 {
+  %a = "test.replace_with_new_op"() : () -> i32
+  %res = arith.addi %a, %a : i32
+  return %res : i32
+}
diff --git a/mlir/test/Transforms/canonicalize-deeply-nested.py b/mlir/test/Transforms/canonicalize-deeply-nested.py
new file mode 100644
index 0000000000000..12b2c6d6f16bd
--- /dev/null
+++ b/mlir/test/Transforms/canonicalize-deeply-nested.py
@@ -0,0 +1,28 @@
+# RUN: %PYTHON %s | mlir-opt -canonicalize | FileCheck %s --implicit-check-not=arith.addi
+
+# Identity folds change values used in nested regions. The store keeps
+# all loops live, so repeated deadness queries must account for nested effects.
+# Generate the input to keep the test small. Do not check elapsed time.
+
+# CHECK-LABEL: func.func @deeply_nested(
+# CHECK-SAME: %[[VALUE:[^ ,]+]]: i32, %[[OUT:[^ ,]+]]: memref<i32>
+# CHECK-COUNT-1000: scf.for
+# CHECK-NEXT: memref.store %[[VALUE]], %[[OUT]][] : memref<i32>
+# CHECK-NOT: scf.for
+
+depth = 1000
+print("func.func @deeply_nested(%value: i32, %out: memref<i32>) {")
+print("%c0 = arith.constant 0 : index")
+print("%c1 = arith.constant 1 : index")
+print("%c4 = arith.constant 4 : index")
+print("%zero = arith.constant 0 : i32")
+value = "%value"
+for i in range(depth):
+    print(f"scf.for %i{i} = %c0 to %c4 step %c1 {{")
+    print(f"%v{i} = arith.addi {value}, %zero : i32")
+    value = f"%v{i}"
+print(f"memref.store {value}, %out[] : memref<i32>")
+for _ in range(depth):
+    print("}")
+print("return")
+print("}")
diff --git a/mlir/unittests/Transforms/CMakeLists.txt b/mlir/unittests/Transforms/CMakeLists.txt
index dc5920087b505..d26971492219d 100644
--- a/mlir/unittests/Transforms/CMakeLists.txt
+++ b/mlir/unittests/Transforms/CMakeLists.txt
@@ -1,8 +1,12 @@
 add_mlir_unittest(MLIRTransformsTests
   Canonicalizer.cpp
   DialectConversion.cpp
+  GreedyPatternRewriteDriver.cpp
 )
 mlir_target_link_libraries(MLIRTransformsTests
   PRIVATE
   MLIRParser
   MLIRTransforms)
+
+target_include_directories(MLIRTransformsTests PRIVATE "${MLIR_BINARY_DIR}/test/lib/Dialect/Test")
+target_link_libraries(MLIRTransformsTests PRIVATE MLIRTestDialect)
diff --git a/mlir/unittests/Transforms/GreedyPatternRewriteDriver.cpp b/mlir/unittests/Transforms/GreedyPatternRewriteDriver.cpp
new file mode 100644
index 0000000000000..7b2d939f78b8f
--- /dev/null
+++ b/mlir/unittests/Transforms/GreedyPatternRewriteDriver.cpp
@@ -0,0 +1,211 @@
+//===- GreedyPatternRewriteDriver.cpp - Greedy rewrite tests -------------===//
+//
+// 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/Transforms/GreedyPatternRewriteDriver.h"
+#include "mlir/IR/PatternMatch.h"
+#include "mlir/IR/Verifier.h"
+#include "mlir/Interfaces/SideEffectInterfaces.h"
+#include "mlir/Parser/Parser.h"
+#include "gtest/gtest.h"
+
+#include "../../test/lib/Dialect/Test/TestDialect.h"
+
+using namespace mlir;
+
+namespace {
+static std::string region(StringRef body) {
+  return ("%r = \"test.one_region_with_recursive_memory_effects\"() ({\n" +
+          body + "\n\"test.return\"() : () -> ()\n}) : () -> i32\n")
+      .str();
+}
+
+constexpr StringLiteral effect =
+    "%e = \"test.side_effect_op\"() {effects = [{effect = \"write\"}]} "
+    ": () -> i32\n";
+
+class RewriteRoot : public RewritePattern {
+public:
+  RewriteRoot(MLIRContext *context, Operation *root,
+              function_ref<void(PatternRewriter &)> rewrite)
+      : RewritePattern(root->getName().getStringRef(), 1, context), root(root),
+        rewrite(rewrite) {}
+
+  LogicalResult matchAndRewrite(Operation *op,
+                                PatternRewriter &rewriter) const override {
+    if (op != root || applied)
+      return failure();
+    applied = true;
+    rewrite(rewriter);
+    return success();
+  }
+
+private:
+  Operation *root;
+  function_ref<void(PatternRewriter &)> rewrite;
+  mutable bool applied = false;
+};
+
+class GreedyDeadnessTest : public testing::Test {
+protected:
+  GreedyDeadnessTest() { context.loadDialect<test::TestDialect>(); }
+
+  void run(ModuleOp module, function_ref<void(PatternRewriter &)> rewrite) {
+    RewritePatternSet patterns(&context);
+    patterns.add<RewriteRoot>(&context, &module.getBody()->front(), rewrite);
+    GreedyRewriteConfig config;
+    config.setUseTopDownTraversal(true)
+        .enableFolding(false)
+        .enableConstantCSE(false)
+        .setRegionSimplificationLevel(GreedySimplifyRegionLevel::Disabled);
+    ASSERT_TRUE(
+        succeeded(applyPatternsGreedily(module, std::move(patterns), config)));
+    EXPECT_TRUE(succeeded(verify(module)));
+  }
+
+  MLIRContext context;
+};
+
+TEST_F(GreedyDeadnessTest, EraseLastEffect) {
+  auto module = parseSourceString<ModuleOp>(region(region(effect)), &context);
+  ASSERT_TRUE(module);
+  Operation *outer = &module->getBody()->front();
+  Operation *inner = &outer->getRegion(0).front().front();
+  Operation *write = &inner->getRegion(0).front().front();
+  run(*module, [&](PatternRewriter &rewriter) { rewriter.eraseOp(write); });
+  EXPECT_TRUE(module->getBody()->empty());
+}
+
+TEST_F(GreedyDeadnessTest, ModifyLastEffect) {
+  auto module = parseSourceString<ModuleOp>(region(region(effect)), &context);
+  ASSERT_TRUE(module);
+  Operation *outer = &module->getBody()->front();
+  Operation *inner = &outer->getRegion(0).front().front();
+  Operation *write = &inner->getRegion(0).front().front();
+  run(*module, [&](PatternRewriter &rewriter) {
+    rewriter.modifyOpInPlace(
+        write, [&] { write->setAttr("effects", rewriter.getArrayAttr({})); });
+  });
+  EXPECT_TRUE(module->getBody()->empty());
+}
+
+TEST_F(GreedyDeadnessTest, UsedInnerResultDoesNotPreventOuterDeletion) {
+  auto module = parseSourceString<ModuleOp>(R"mlir(
+    %outer = "test.one_region_with_recursive_memory_effects"() ({
+      %inner = "test.one_region_with_recursive_memory_effects"() ({
+        %v = "test.side_effect_op"() {effects = [{effect = "write"}]} : () -> i32
+        "test.return"(%v) : (i32) -> ()
+      }) : () -> i32
+      "test.return"(%inner) : (i32) -> ()
+    }) : () -> i32
+  )mlir",
+                                            &context);
+  ASSERT_TRUE(module);
+  Operation *outer = &module->getBody()->front();
+  Operation *inner = &outer->getRegion(0).front().front();
+  Operation *write = &inner->getRegion(0).front().front();
+  ASSERT_FALSE(inner->use_empty());
+  run(*module, [&](PatternRewriter &rewriter) {
+    rewriter.modifyOpInPlace(
+        write, [&] { write->setAttr("effects", rewriter.getArrayAttr({})); });
+  });
+  EXPECT_TRUE(module->getBody()->empty());
+}
+
+TEST_F(GreedyDeadnessTest, InsertEffectIntoDeadRegion) {
+  auto module =
+      parseSourceString<ModuleOp>(region(region("") + effect.str()), &context);
+  ASSERT_TRUE(module);
+  Operation *outer = &module->getBody()->front();
+  Operation *inner = &outer->getRegion(0).front().front();
+  Operation *write = inner->getNextNode();
+  run(*module, [&](PatternRewriter &rewriter) {
+    rewriter.setInsertionPointToStart(&inner->getRegion(0).front());
+    rewriter.clone(*write);
+    rewriter.eraseOp(write);
+  });
+  ASSERT_FALSE(module->getBody()->empty());
+  ASSERT_FALSE(outer->getRegion(0).front().empty());
+  EXPECT_EQ(&outer->getRegion(0).front().front(), inner);
+  EXPECT_EQ(inner->getRegion(0).front().front().getName().getStringRef(),
+            "test.side_effect_op");
+}
+
+TEST_F(GreedyDeadnessTest, MoveEffectBetweenRegions) {
+  std::string empty = region("");
+  std::string live = region(effect);
+  live.replace(0, 2, "%s");
+  auto module = parseSourceString<ModuleOp>(region(empty + live), &context);
+  ASSERT_TRUE(module);
+  Operation *outer = &module->getBody()->front();
+  Operation *target = &outer->getRegion(0).front().front();
+  Operation *source = target->getNextNode();
+  Operation *write = &source->getRegion(0).front().front();
+  run(*module, [&](PatternRewriter &rewriter) {
+    rewriter.moveOpBefore(write, &target->getRegion(0).front(),
+                          target->getRegion(0).front().begin());
+  });
+  ASSERT_FALSE(module->getBody()->empty());
+  EXPECT_EQ(outer->getRegion(0).front().getOperations().size(), 2u);
+  EXPECT_EQ(&outer->getRegion(0).front().front(), target);
+  EXPECT_EQ(&target->getRegion(0).front().front(), write);
+}
+
+TEST_F(GreedyDeadnessTest, MoveBlocksBetweenRegions) {
+  std::string empty = region("");
+  std::string live = region(effect);
+  live.replace(0, 2, "%s");
+  auto module = parseSourceString<ModuleOp>(region(empty + live), &context);
+  ASSERT_TRUE(module);
+  Operation *outer = &module->getBody()->front();
+  Operation *target = &outer->getRegion(0).front().front();
+  Operation *source = target->getNextNode();
+  Block *emptyBlock = &target->getRegion(0).front();
+  Block *liveBlock = &source->getRegion(0).front();
+  run(*module, [&](PatternRewriter &rewriter) {
+    rewriter.moveBlockBefore(liveBlock, &target->getRegion(0),
+                             target->getRegion(0).begin());
+    rewriter.moveBlockBefore(emptyBlock, &source->getRegion(0),
+                             source->getRegion(0).begin());
+  });
+  ASSERT_FALSE(module->getBody()->empty());
+  EXPECT_EQ(outer->getRegion(0).front().getOperations().size(), 2u);
+  EXPECT_EQ(&outer->getRegion(0).front().front(), target);
+  EXPECT_EQ(&target->getRegion(0).front(), liveBlock);
+}
+
+TEST_F(GreedyDeadnessTest, UsesAreNotCached) {
+  auto module = parseSourceString<ModuleOp>(R"mlir(
+    %v = "test.side_effect_op"() : () -> i32
+    "test.use"(%v) : (i32) -> ()
+  )mlir",
+                                            &context);
+  ASSERT_TRUE(module);
+  Operation *value = &module->getBody()->front();
+  DeadnessCache cache;
+  EXPECT_FALSE(cache.isOpTriviallyDead(value));
+  value->getNextNode()->erase();
+  EXPECT_TRUE(cache.isOpTriviallyDead(value));
+  OpBuilder builder(&context);
+  builder.setInsertionPointAfter(value);
+  OperationState state(builder.getUnknownLoc(), "test.use");
+  state.addOperands(value->getResult(0));
+  builder.create(state);
+  EXPECT_FALSE(cache.isOpTriviallyDead(value));
+}
+
+TEST_F(GreedyDeadnessTest, TerminatorsRemainLiveAfterRecursiveQuery) {
+  auto module = parseSourceString<ModuleOp>(region(""), &context);
+  ASSERT_TRUE(module);
+  Operation *outer = &module->getBody()->front();
+  Operation *terminator = &outer->getRegion(0).front().back();
+  DeadnessCache cache;
+  EXPECT_TRUE(cache.isOpTriviallyDead(outer));
+  EXPECT_FALSE(cache.isOpTriviallyDead(terminator));
+  EXPECT_FALSE(cache.isOpTriviallyDead(*module));
+}
+} // namespace



More information about the Mlir-commits mailing list