[Mlir-commits] [mlir] [mlir][transform] Fix handling of transitive include in interpreter. (PR #67560)

Ingo Müller llvmlistbot at llvm.org
Wed Sep 27 07:24:39 PDT 2023


https://github.com/ingomueller-net created https://github.com/llvm/llvm-project/pull/67560

Until now, the interpreter would only load those symbols from the provided library files that were declared in the main transform module. However, sequences in the library may include other sequences on their own. Until now, if such sequences were not *also* declared in the main transform module, the interpreter would fail to resolve them. Forward declaring all of them is undesirable as it defeats the purpose of encapsulation into library modules.

This PR implements a kind of linker for transform scripts to solve this problem. The linker merges all symbols of the library module into the main module before interpreting the latter. Symbols whose names collide are handled as follows: (1) if they are both functions (in the sense of `FunctionOpInterface`) with compatible signatures, one is external, and the other one is public, then they are merged; (2) of one of them is private, that one is renamed; and (3) an error is raised otherwise.

>From 8156631c60f258299f6fd4fc64c9dd9af081a33b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ingo=20M=C3=BCller?= <ingomueller at google.com>
Date: Fri, 22 Sep 2023 15:10:30 +0000
Subject: [PATCH] [mlir][transform] Fix handling of transitive include in
 interpreter.

Until now, the interpreter would only load those symbols from the
provided library files that were declared in the main transform module.
However, sequences in the library may include other sequences on their
own. Until now, if such sequences were not *also* declared in the main
transform module, the interpreter would fail to resolve them. Forward
declaring all of them is undesirable as it defeats the purpose of
encapsulation into library modules.

This PR implements a kind of linker for transform scripts to solve this
problem. The linker merges all symbols of the library module into the
main module before interpreting the latter. Symbols whose names collide
are handled as follows: (1) if they are both functions (in the sense of
`FunctionOpInterface`) with compatible signatures, one is external, and
the other one is public, then they are merged; (2) of one of them is
private, that one is renamed; and (3) an error is raised otherwise.
---
 .../TransformInterpreterPassBase.cpp          | 284 ++++++++++++++----
 ...reter-external-symbol-decl-transitive.mlir |  27 ++
 ...test-interpreter-external-symbol-decl.mlir |  56 +++-
 .../test-interpreter-external-symbol-def.mlir |  44 ++-
 4 files changed, 339 insertions(+), 72 deletions(-)
 create mode 100644 mlir/test/Dialect/Transform/test-interpreter-external-symbol-decl-transitive.mlir

diff --git a/mlir/lib/Dialect/Transform/Transforms/TransformInterpreterPassBase.cpp b/mlir/lib/Dialect/Transform/Transforms/TransformInterpreterPassBase.cpp
index d5c65b23e3a2134..78d48865b8f0e33 100644
--- a/mlir/lib/Dialect/Transform/Transforms/TransformInterpreterPassBase.cpp
+++ b/mlir/lib/Dialect/Transform/Transforms/TransformInterpreterPassBase.cpp
@@ -302,77 +302,237 @@ static void performOptionalDebugActions(
     transform->removeAttr(kTransformDialectTagAttrName);
 }
 
-/// Replaces external symbols in `block` with their (non-external) definitions
-/// from the given module.
-static LogicalResult defineDeclaredSymbols(Block &block, ModuleOp definitions) {
-  MLIRContext &ctx = *definitions->getContext();
-  auto consumedName =
-      StringAttr::get(&ctx, transform::TransformDialect::kArgConsumedAttrName);
-  auto readOnlyName =
-      StringAttr::get(&ctx, transform::TransformDialect::kArgReadOnlyAttrName);
-
-  for (Operation &op : llvm::make_early_inc_range(block)) {
-    LLVM_DEBUG(DBGS() << op << "\n");
-    auto symbol = dyn_cast<SymbolOpInterface>(op);
-    if (!symbol)
-      continue;
-    if (symbol->getNumRegions() == 1 && !symbol->getRegion(0).empty())
-      continue;
-
-    LLVM_DEBUG(DBGS() << "looking for definition of symbol "
-                      << symbol.getNameAttr() << ":");
-    SymbolTable symbolTable(definitions);
-    Operation *externalSymbol = symbolTable.lookup(symbol.getNameAttr());
-    if (!externalSymbol || externalSymbol->getNumRegions() != 1 ||
-        externalSymbol->getRegion(0).empty()) {
-      LLVM_DEBUG(llvm::dbgs() << "not found\n");
-      continue;
-    }
+/// Merge all symbols from `other` into `target`. Both ops need to implement the
+/// `SymbolTable` trait. Operations are moved from `other`, i.e., `other` may be
+/// modified by this function. Upon merging, private symbols may be renamed in
+/// order to avoid collisions in the result. Public symbols may not collide,
+/// with the exception of `SymbolInterfaceOp`s, where collisions are allowed if
+/// at least one of the two is external, in which case the other op preserved
+/// (or one of the two if both are external). The `target` op might not verify
+/// after this function returns.
+// XXX: Make `other` argument an `OwningOpRef`?
+static LogicalResult mergeSymbolsInto(Operation *target, Operation *other) {
+  assert(target->hasTrait<OpTrait::SymbolTable>() &&
+         "requires target to implement the 'SymbolTable' trait");
+  assert(other->hasTrait<OpTrait::SymbolTable>() &&
+         "requires target to implement the 'SymbolTable' trait");
+
+  MLIRContext *context = other->getContext();
+  auto consumedName = StringAttr::get(
+      context, transform::TransformDialect::kArgConsumedAttrName);
+  auto readOnlyName = StringAttr::get(
+      context, transform::TransformDialect::kArgReadOnlyAttrName);
+
+  int uniqueId = 0;
+
+  auto canBeMerged = [](FunctionOpInterface func1, FunctionOpInterface func2) {
+    return func1.isExternal() && (func2.isPublic() || func2.isExternal());
+    ;
+  };
+
+  // Rename private symbols in both ops in order to resolve conflicts that can
+  // be resolved that way.
+  // XXX: Actually, I think that the reverse check is not necessary.
+  LLVM_DEBUG(DBGS() << "renaming private symbols to resolve conflicts:\n");
+  for (auto [symbolTableOp, otherSymbolTableOp] :
+       llvm::zip(SmallVector<Operation *>{target, other},
+                 SmallVector<Operation *>{other, target})) {
+    SymbolTable symbolTable(symbolTableOp); // XXX: build only once
+    SymbolTable otherSymbolTable(otherSymbolTableOp);
+    for (Operation &op : symbolTableOp->getRegion(0).front()) {
+      auto symbolOp = dyn_cast<SymbolOpInterface>(op);
+      if (!symbolOp)
+        continue;
+      StringAttr name = symbolOp.getNameAttr();
+      LLVM_DEBUG(DBGS() << "  found @" << name.getValue() << "\n");
+
+      // Check if there is a colliding op in the other module.
+      auto collidingOp =
+          cast_or_null<SymbolOpInterface>(otherSymbolTable.lookup(name));
+      if (!collidingOp)
+        continue;
+
+      LLVM_DEBUG(DBGS() << "    collision found for @" << name.getValue());
+
+      // Collisions are fine if both opt are functions and can be merged.
+      if (auto funcOp = dyn_cast<FunctionOpInterface>(op),
+          collidingFuncOp =
+              dyn_cast<FunctionOpInterface>(collidingOp.getOperation());
+          funcOp && collidingFuncOp) {
+        if (canBeMerged(funcOp, collidingFuncOp) ||
+            canBeMerged(collidingFuncOp, funcOp)) {
+          LLVM_DEBUG(llvm::dbgs() << " but both ops are functions and "
+                                     "will be merged\n");
+          continue;
+        }
+
+        // If they can't be merged, proceed like any other collision.
+        LLVM_DEBUG(llvm::dbgs() << " and both ops are function definitions");
+      }
+
+      /// Rename `op` inside `symbolTableOp` with symbol table `symbolTable`
+      /// to avoid a collision with `otherOp`.
+      auto renameToUnique =
+          [&uniqueId =
+               uniqueId](SymbolOpInterface op, SymbolOpInterface otherOp,
+                         Operation *symbolTableOp, SymbolTable &symbolTable,
+                         SymbolTable &otherSymbolTable) -> LogicalResult {
+        assert(SymbolTable::getNearestSymbolTable(op) == symbolTableOp &&
+               "expected 'op' to be inside of 'symbolTableOp'");
+        MLIRContext *context = op->getContext();
+
+        // Determine new name that is unique in both symbol tables.
+        StringAttr newName;
+        {
+          SmallString<64> prefix = op.getNameAttr().getValue();
+          prefix.push_back('_');
+          while (true) {
+            newName = StringAttr::get(context, prefix + Twine(uniqueId++));
+            if (!symbolTable.lookup(newName) &&
+                !otherSymbolTable.lookup(newName)) {
+              break;
+            }
+          }
+        }
+
+        // Apply renaming.
+        LLVM_DEBUG(llvm::dbgs()
+                   << ", renaming to @" << newName.getValue() << "\n");
+        if (failed(SymbolTable::replaceAllSymbolUses(op, newName,
+                                                     symbolTableOp))) {
+          InFlightDiagnostic diag =
+              emitError(op->getLoc(), Twine("failed to rename symbol to @") +
+                                          newName.getValue());
+          diag.attachNote(otherOp->getLoc())
+              << "renaming due to collision with this op";
+          return diag;
+        }
+        op.setName(newName); // XXX: Why is this necessary? Why does
+                             // SymbolTable::renameAllSymbolUses not do it?
+        return success();
+      };
+
+      // Collision can be resolved if one of the ops is private.
+      if (symbolOp.isPrivate()) {
+        if (failed(renameToUnique(symbolOp, collidingOp, symbolTableOp,
+                                  symbolTable, otherSymbolTable)))
+          return failure();
+        continue;
+      }
+      if (collidingOp.isPrivate()) {
+        if (failed(renameToUnique(collidingOp, symbolOp, otherSymbolTableOp,
+                                  symbolTable, otherSymbolTable)))
+          return failure();
+        continue;
+      }
 
-    auto symbolFunc = dyn_cast<FunctionOpInterface>(op);
-    auto externalSymbolFunc = dyn_cast<FunctionOpInterface>(externalSymbol);
-    if (!symbolFunc || !externalSymbolFunc) {
-      LLVM_DEBUG(llvm::dbgs() << "cannot compare types\n");
-      continue;
+      LLVM_DEBUG(llvm::dbgs() << ", emitting error\n");
+      InFlightDiagnostic diag =
+          emitError(symbolOp->getLoc(),
+                    Twine("doubly defined symbol @") + name.getValue());
+      diag.attachNote(collidingOp->getLoc()) << "previously defined here";
+      return diag;
     }
+  }
+
+  for (auto *op : SmallVector<Operation *>{target, other}) {
+    if (failed(mlir::verify(op)))
+      return emitError(op->getLoc(),
+                       "failed to verify input op after renaming");
+  }
 
-    LLVM_DEBUG(llvm::dbgs() << "found @" << externalSymbol << "\n");
-    if (symbolFunc.getFunctionType() != externalSymbolFunc.getFunctionType()) {
-      return symbolFunc.emitError()
-             << "external definition has a mismatching signature ("
-             << externalSymbolFunc.getFunctionType() << ")";
+  LLVM_DEBUG(DBGS() << "moving all symbols into target\n");
+  {
+    SmallVector<SymbolOpInterface> opsToMove;
+    for (Operation &op : other->getRegion(0).front()) {
+      if (auto symbol = dyn_cast<SymbolOpInterface>(op))
+        opsToMove.push_back(symbol);
     }
 
-    for (unsigned i = 0, e = symbolFunc.getNumArguments(); i < e; ++i) {
-      bool isExternalConsumed =
-          externalSymbolFunc.getArgAttr(i, consumedName) != nullptr;
-      bool isExternalReadonly =
-          externalSymbolFunc.getArgAttr(i, readOnlyName) != nullptr;
-      bool isConsumed = symbolFunc.getArgAttr(i, consumedName) != nullptr;
-      bool isReadonly = symbolFunc.getArgAttr(i, readOnlyName) != nullptr;
-      if (!isExternalConsumed && !isExternalReadonly) {
-        if (isConsumed)
-          externalSymbolFunc.setArgAttr(i, consumedName, UnitAttr::get(&ctx));
-        else if (isReadonly)
-          externalSymbolFunc.setArgAttr(i, readOnlyName, UnitAttr::get(&ctx));
+    SymbolTable symbolTable(target);
+    for (SymbolOpInterface op : opsToMove) {
+      // Remember potentially colliding op in the target module.
+      auto collidingOp =
+          cast_or_null<SymbolOpInterface>(symbolTable.lookup(op.getNameAttr()));
+
+      // Move op even if we get a collision.
+      LLVM_DEBUG(DBGS() << "  moving @" << op.getName());
+      op->moveAfter(&target->getRegion(0).front(),
+                    target->getRegion(0).front().begin());
+
+      // If there is no collision, we are done.
+      if (!collidingOp) {
+        LLVM_DEBUG(llvm::dbgs() << " without collision\n");
         continue;
       }
 
-      if ((isExternalConsumed && !isConsumed) ||
-          (isExternalReadonly && !isReadonly)) {
+      // We now have a collision that we resolve through merging. The merging
+      // may bring the symbol table out of date but we don't need to access the
+      // table for that symbol anymore.
+
+      // The two colliding ops must bot be functions because we have already
+      // emitted errors otherwise earlier.
+      auto symbolFunc = cast<FunctionOpInterface>(op.getOperation());
+      auto externalSymbolFunc =
+          cast<FunctionOpInterface>(collidingOp.getOperation());
+
+      // Both ops are in the target module now and can be treated symmetrically,
+      // so w.l.o.g. we can reduce to merging `funcOp` into `collidingFuncOp`.
+      if (!canBeMerged(symbolFunc, externalSymbolFunc))
+        std::swap(symbolFunc, externalSymbolFunc);
+      assert(canBeMerged(symbolFunc, externalSymbolFunc));
+
+      LLVM_DEBUG(llvm::dbgs() << " with collision, trying to keep op at "
+                              << externalSymbolFunc.getLoc() << ":\n"
+                              << externalSymbolFunc << "\n");
+
+      // Check that function signatures match.
+      // XXX: Do that check earlier?
+      if (symbolFunc.getFunctionType() !=
+          externalSymbolFunc.getFunctionType()) {
         return symbolFunc.emitError()
-               << "external definition has mismatching consumption annotations "
-                  "for argument #"
-               << i;
+               << "external definition has a mismatching signature ("
+               << externalSymbolFunc.getFunctionType() << ")";
       }
-    }
 
-    OpBuilder builder(&op);
-    builder.setInsertionPoint(&op);
-    builder.clone(*externalSymbol);
-    symbol->erase();
+      // Check and merge argument attributes.
+      for (unsigned i = 0, e = symbolFunc.getNumArguments(); i < e; ++i) {
+        bool isExternalConsumed =
+            externalSymbolFunc.getArgAttr(i, consumedName) != nullptr;
+        bool isExternalReadonly =
+            externalSymbolFunc.getArgAttr(i, readOnlyName) != nullptr;
+        bool isConsumed = symbolFunc.getArgAttr(i, consumedName) != nullptr;
+        bool isReadonly = symbolFunc.getArgAttr(i, readOnlyName) != nullptr;
+        if (!isExternalConsumed && !isExternalReadonly) {
+          if (isConsumed)
+            externalSymbolFunc.setArgAttr(i, consumedName,
+                                          UnitAttr::get(context));
+          else if (isReadonly)
+            externalSymbolFunc.setArgAttr(i, readOnlyName,
+                                          UnitAttr::get(context));
+          continue;
+        }
+
+        if ((isExternalConsumed && !isConsumed) ||
+            (isExternalReadonly && !isReadonly)) {
+          return symbolFunc.emitError()
+                 << "external definition has mismatching consumption "
+                    "annotations for argument #"
+                 << i;
+        }
+      }
+
+      // `funcOp` is the external one, so we can remove it.
+      assert(symbolFunc.isExternal());
+      symbolFunc->erase();
+    }
   }
 
+  if (failed(mlir::verify(target)))
+    return emitError(target->getLoc(),
+                     "failed to verify target op after merging symbols");
+
+  LLVM_DEBUG(DBGS() << "done merging ops\n");
   return success();
 }
 
@@ -438,8 +598,9 @@ LogicalResult transform::detail::interpreterBaseRunOnOperationImpl(
       diag.attachNote(target->getLoc()) << "pass anchor op";
       return diag;
     }
-    if (failed(defineDeclaredSymbols(*transformRoot->getBlock(),
-                                     libraryModule->get())))
+    if (failed(
+            mergeSymbolsInto(SymbolTable::getNearestSymbolTable(transformRoot),
+                             libraryModule->get())))
       return failure();
   }
 
@@ -499,8 +660,7 @@ LogicalResult transform::detail::interpreterBaseInitializeImpl(
     return success();
 
   if (module && *module) {
-    if (failed(defineDeclaredSymbols(*module->get().getBody(),
-                                     parsedLibrary.get())))
+    if (failed(mergeSymbolsInto(module->get(), parsedLibrary.get())))
       return failure();
   } else {
     libraryModule =
diff --git a/mlir/test/Dialect/Transform/test-interpreter-external-symbol-decl-transitive.mlir b/mlir/test/Dialect/Transform/test-interpreter-external-symbol-decl-transitive.mlir
new file mode 100644
index 000000000000000..0e9fa7c59bc4155
--- /dev/null
+++ b/mlir/test/Dialect/Transform/test-interpreter-external-symbol-decl-transitive.mlir
@@ -0,0 +1,27 @@
+// RUN: mlir-opt %s --pass-pipeline="builtin.module(test-transform-dialect-interpreter{transform-library-file-name=%p/test-interpreter-external-symbol-def.mlir})" \
+// RUN:             --verify-diagnostics --split-input-file | FileCheck %s
+
+// RUN: mlir-opt %s --pass-pipeline="builtin.module(test-transform-dialect-interpreter{transform-library-file-name=%p/test-interpreter-external-symbol-def.mlir}, test-transform-dialect-interpreter)" \
+// RUN:             --verify-diagnostics --split-input-file | FileCheck %s
+
+// RUN: mlir-opt %s --pass-pipeline="builtin.module(test-transform-dialect-interpreter{transform-library-file-name=%p/test-interpreter-external-symbol-def.mlir}, test-transform-dialect-interpreter{transform-library-file-name=%p/test-interpreter-external-symbol-def.mlir})" \
+// RUN:             --verify-diagnostics --split-input-file | FileCheck %s
+
+// The definition of the @bar named sequence is provided in another file. It
+// will be included because of the pass option. That sequence uses another named
+// sequence @foo, which should be made available here. Repeated application of
+// the same pass, with or without the library option, should not be a problem.
+// Note that the same diagnostic produced twice at the same location only
+// needs to be matched once.
+
+// expected-remark @below {{message}}
+module attributes {transform.with_named_sequence} {
+  // CHECK-DAG: transform.named_sequence @foo
+  // CHECK-DAG: transform.named_sequence @bar
+  transform.named_sequence private @bar(!transform.any_op {transform.readonly})
+
+  transform.sequence failures(propagate) {
+  ^bb0(%arg0: !transform.any_op):
+    include @bar failures(propagate) (%arg0) : (!transform.any_op) -> ()
+  }
+}
diff --git a/mlir/test/Dialect/Transform/test-interpreter-external-symbol-decl.mlir b/mlir/test/Dialect/Transform/test-interpreter-external-symbol-decl.mlir
index 04b6c5a02e0adf1..48cca02590d6cd8 100644
--- a/mlir/test/Dialect/Transform/test-interpreter-external-symbol-decl.mlir
+++ b/mlir/test/Dialect/Transform/test-interpreter-external-symbol-decl.mlir
@@ -7,26 +7,64 @@
 // RUN: mlir-opt %s --pass-pipeline="builtin.module(test-transform-dialect-interpreter{transform-library-file-name=%p/test-interpreter-external-symbol-def.mlir}, test-transform-dialect-interpreter{transform-library-file-name=%p/test-interpreter-external-symbol-def.mlir})" \
 // RUN:             --verify-diagnostics --split-input-file | FileCheck %s
 
-// The definition of the @foo named sequence is provided in another file. It
-// will be included because of the pass option. Repeated application of the
-// same pass, with or without the library option, should not be a problem.
+// The definition of the @print_message named sequence is provided in another
+// file. It will be included because of the pass option. Repeated application of
+// the same pass, with or without the library option, should not be a problem.
 // Note that the same diagnostic produced twice at the same location only
 // needs to be matched once.
 
 // expected-remark @below {{message}}
 // expected-remark @below {{unannotated}}
+// expected-remark @+6 {{internal colliding (without suffix)}}
+// expected-remark @+5 {{internal colliding_0}}
+// expected-remark @+4 {{internal colliding_1}}
+// expected-remark @+3 {{internal colliding_3}}
+// expected-remark @+2 {{internal colliding_4}}
+// expected-remark @+1 {{internal colliding_5}}
 module attributes {transform.with_named_sequence} {
-  // CHECK: transform.named_sequence @foo
-  // CHECK: test_print_remark_at_operand %{{.*}}, "message"
-  transform.named_sequence private @foo(!transform.any_op {transform.readonly})
+  // CHECK: transform.named_sequence @print_message(
+  // CHECK: transform.include @private_helper
+  transform.named_sequence private @print_message(!transform.any_op {transform.readonly})
 
-  // CHECK: transform.named_sequence @unannotated
+  // These ops collide with ops from the other module before or after renaming.
+  transform.named_sequence private @colliding(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "internal colliding (without suffix)" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence private @colliding_0(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "internal colliding_0" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence private @colliding_1(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "internal colliding_1" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence private @colliding_3(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "internal colliding_3" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence @colliding_4(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "internal colliding_4" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence private @colliding_5(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "internal colliding_5" : !transform.any_op
+    transform.yield
+  }
+
+  // CHECK: transform.named_sequence @unannotated(
   // CHECK: test_print_remark_at_operand %{{.*}}, "unannotated"
-  transform.named_sequence private @unannotated(!transform.any_op {transform.readonly})
+  transform.named_sequence @unannotated(!transform.any_op {transform.readonly})
 
   transform.sequence failures(propagate) {
   ^bb0(%arg0: !transform.any_op):
-    include @foo failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    include @print_message failures(propagate) (%arg0) : (!transform.any_op) -> ()
     include @unannotated failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    include @colliding failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    include @colliding_0 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    include @colliding_1 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    include @colliding_3 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    include @colliding_4 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    include @colliding_5 failures(propagate) (%arg0) : (!transform.any_op) -> ()
   }
 }
diff --git a/mlir/test/Dialect/Transform/test-interpreter-external-symbol-def.mlir b/mlir/test/Dialect/Transform/test-interpreter-external-symbol-def.mlir
index 1149bda98ab8527..d6abf3cd1ae1868 100644
--- a/mlir/test/Dialect/Transform/test-interpreter-external-symbol-def.mlir
+++ b/mlir/test/Dialect/Transform/test-interpreter-external-symbol-def.mlir
@@ -1,11 +1,43 @@
 // RUN: mlir-opt %s
 
 module attributes {transform.with_named_sequence} {
-  transform.named_sequence @foo(%arg0: !transform.any_op {transform.readonly}) {
+  transform.named_sequence private @private_helper(%arg0: !transform.any_op {transform.readonly}) {
     transform.test_print_remark_at_operand %arg0, "message" : !transform.any_op
     transform.yield
   }
 
+  // These ops collide with ops from the other module before or after renaming.
+  transform.named_sequence private @colliding(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "external colliding (without suffix)" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence private @colliding_0(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "external colliding_0" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence private @colliding_2(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "external colliding_2" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence private @colliding_3(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "external colliding_3" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence private @colliding_4(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "external colliding_4" : !transform.any_op
+    transform.yield
+  }
+  transform.named_sequence @colliding_5(%arg0: !transform.any_op {transform.readonly}) {
+    transform.test_print_remark_at_operand %arg0, "external colliding_5" : !transform.any_op
+    transform.yield
+  }
+
+
+  transform.named_sequence @print_message(%arg0: !transform.any_op {transform.readonly}) {
+    transform.include @private_helper failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    transform.yield
+  }
+
   transform.named_sequence @consuming(%arg0: !transform.any_op {transform.consumed}) {
     transform.test_consume_operand %arg0 : !transform.any_op
     transform.yield
@@ -15,4 +47,14 @@ module attributes {transform.with_named_sequence} {
     transform.test_print_remark_at_operand %arg0, "unannotated" : !transform.any_op
     transform.yield
   }
+
+  transform.named_sequence @symbol_user(%arg0: !transform.any_op {transform.readonly}) {
+    transform.include @colliding failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    transform.include @colliding_0 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    transform.include @colliding_2 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    transform.include @colliding_3 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    transform.include @colliding_4 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    transform.include @colliding_5 failures(propagate) (%arg0) : (!transform.any_op) -> ()
+    transform.yield
+  }
 }



More information about the Mlir-commits mailing list