[Mlir-commits] [mlir] [mlir] transform dialect; add pre/post-condition type (PR #191813)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Apr 13 06:37:14 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir

Author: Oleksandr "Alex" Zinenko (ftynse)

<details>
<summary>Changes</summary>

Add a transform dialect type denoting additional invariants on payload
IR usable for pre/post-conditions of a transformation. The invariants
are defined as a list of attributes in the type parameter, where the
attribute implements the interface for invariant-checking. This allows
clients to factor out, explicify and deduplicate precondition
verification logic.

This required adding support for Transform dialect extensions injecting
attributes into the dialects similarly to how they already do this for
operations and types.

Co-authored-by: Tim Gymnich <tim@<!-- -->gymni.ch>
Co-authored-by: Martin Lücke <martin.luecke@<!-- -->amd.com>
Assisted-by: Claude Opus 4.3 / Cursor


---

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


16 Files Affected:

- (modified) mlir/include/mlir/Dialect/Transform/IR/TransformDialect.h (+33) 
- (modified) mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td (+46-3) 
- (modified) mlir/include/mlir/Dialect/Transform/IR/TransformTypes.td (+17) 
- (modified) mlir/include/mlir/Dialect/Transform/Interfaces/CMakeLists.txt (+5) 
- (modified) mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h (+13) 
- (modified) mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.td (+34) 
- (modified) mlir/lib/Dialect/Transform/IR/TransformAttrs.cpp (+26) 
- (modified) mlir/lib/Dialect/Transform/IR/TransformDialect.cpp (+33-7) 
- (modified) mlir/lib/Dialect/Transform/IR/TransformTypes.cpp (+40) 
- (modified) mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp (+1) 
- (added) mlir/test/Dialect/Transform/normal-forms.mlir (+80) 
- (modified) mlir/test/Dialect/Transform/ops-invalid.mlir (+11) 
- (modified) mlir/test/lib/Dialect/Transform/CMakeLists.txt (+2) 
- (modified) mlir/test/lib/Dialect/Transform/TestTransformDialectExtension.cpp (+48) 
- (modified) mlir/test/lib/Dialect/Transform/TestTransformDialectExtension.h (+3) 
- (modified) mlir/test/lib/Dialect/Transform/TestTransformDialectExtension.td (+13) 


``````````diff
diff --git a/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.h b/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.h
index 8e6ac364473af..8fe94ebcf658f 100644
--- a/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.h
+++ b/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.h
@@ -195,6 +195,16 @@ class TransformDialectExtension
     });
   }
 
+  /// Injects the attributes into the Transform dialect. The attributes must
+  /// provide a `getMnemonic` static method returning an object convertible to
+  /// `StringRef` that is unique across all injected attributes.
+  template <typename... AttrTys>
+  void registerAttributes() {
+    initializers.push_back([](TransformDialect *transformDialect) {
+      transformDialect->addAttributesChecked<AttrTys...>();
+    });
+  }
+
   /// Injects the types into the Transform dialect. The types must implement
   /// the TransformHandleTypeInterface and the implementation must be already
   /// available when the type is injected. Furthermore, the types must provide
@@ -269,6 +279,29 @@ void TransformDialect::addOperationIfNotRegistered() {
   reportDuplicateOpRegistration(OpTy::getOperationName());
 }
 
+template <typename AttrTy>
+void TransformDialect::addAttributeIfNotRegistered() {
+  // Use the address of the parse method as a proxy for identifying whether we
+  // are registering the same type class for the same mnemonic.
+  StringRef mnemonic = AttrTy::getMnemonic();
+  auto [it, inserted] =
+      attributeParsingHooks.try_emplace(mnemonic, AttrTy::parse);
+  if (!inserted) {
+    const ExtensionAttributeParsingHook &parsingHook = it->getValue();
+    if (parsingHook != &AttrTy::parse)
+      reportDuplicateAttributeRegistration(mnemonic);
+    else
+      return;
+  }
+  attributePrintingHooks.try_emplace(
+      TypeID::get<AttrTy>(),
+      +[](mlir::Attribute attribute, AsmPrinter &printer) {
+        printer << AttrTy::getMnemonic();
+        cast<AttrTy>(attribute).print(printer);
+      });
+  addAttributes<AttrTy>();
+}
+
 template <typename Type>
 void TransformDialect::addTypeIfNotRegistered() {
   // Use the address of the parse method as a proxy for identifying whether we
diff --git a/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td b/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td
index c7ea5ade72ace..ce0ad30ad2c8c 100644
--- a/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td
+++ b/mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td
@@ -19,7 +19,6 @@ def Transform_Dialect : Dialect {
   let cppNamespace = "::mlir::transform";
 
   let hasOperationAttrVerify = 1;
-  let useDefaultAttributePrinterParser = 1;
   let extraClassDeclaration = [{
     /// Symbol name for the default entry point "named sequence".
     constexpr const static ::llvm::StringLiteral
@@ -57,11 +56,29 @@ def Transform_Dialect : Dialect {
           extraData.at(::mlir::TypeID::get<DataTy>()).get());
     }
 
+    /// Parses an attribute registered by this dialect or one of its extensions.
+    ::mlir::Attribute parseAttribute(::mlir::DialectAsmParser &parser,
+        ::mlir::Type type) const override;
+
+    /// Prints an attribute registered by this dialect or one of its extensions.
+    void printAttribute(::mlir::Attribute attribute,
+        ::mlir::DialectAsmPrinter &printer) const override;
+
+    /// Parser callback for an individual attribute registered by this dialect or
+    /// its extensions.
+    using ExtensionAttributeParsingHook =
+        ::mlir::Attribute (*)(::mlir::AsmParser &, ::mlir::Type);
+
+    /// Printer callback for an individual attribute registered by this dialect or
+    /// its extensions.
+    using ExtensionAttributePrintingHook =
+        std::function<void(::mlir::Attribute, ::mlir::AsmPrinter &)>;
+
     /// Parses a type registered by this dialect or one of its extensions.
-    ::mlir::Type parseType(::mlir::DialectAsmParser & parser) const override;
+    ::mlir::Type parseType(::mlir::DialectAsmParser &parser) const override;
 
     /// Prints a type registered by this dialect or one of its extensions.
-    void printType(::mlir::Type type, ::mlir::DialectAsmPrinter & printer)
+    void printType(::mlir::Type type, ::mlir::DialectAsmPrinter &printer)
         const override;
 
     /// Parser callback for an individual type registered by this dialect or
@@ -102,6 +119,21 @@ def Transform_Dialect : Dialect {
     /// Reports a repeated registration error of an op with the given name.
     [[noreturn]] void reportDuplicateOpRegistration(StringRef opName);
 
+    /// Registers attributes specified as template parameters with the Transform
+    /// dialect. Checks that they meet the requirements for Transform IR attributes.
+    template <typename... AttrTys>
+    void addAttributesChecked() {
+      (addAttributeIfNotRegistered<AttrTys>(), ...);
+    }
+    template <typename AttrTy>
+    void addAttributeIfNotRegistered();
+
+    /// Reports a repeated registration error of an attribute with the given name.
+    [[noreturn]] void reportDuplicateAttributeRegistration(StringRef attrName);
+
+    /// Registers dialect attributes with the context.
+    void initializeAttributes();
+
     /// Registers types specified as template parameters with the Transform
     /// dialect. Checks that they meet the requirements for Transform IR types.
     template <typename... TypeTys>
@@ -148,6 +180,17 @@ def Transform_Dialect : Dialect {
     ::llvm::DenseMap<::mlir::TypeID, ExtensionTypePrintingHook>
         typePrintingHooks;
 
+    /// A map from attribute TypeID to its parsing function for the remainder of
+    /// the syntax. The parser has access to the TypeID, so it is used for
+    /// further dispatch.
+    ::llvm::StringMap<ExtensionAttributeParsingHook>
+        attributeParsingHooks;
+
+    /// A map from attribute TypeID to its printing function. No need to do string
+    /// lookups when the attribute is fully constructed.
+    ::llvm::DenseMap<::mlir::TypeID, ExtensionAttributePrintingHook>
+        attributePrintingHooks;
+
     /// Module containing symbols, e.g. named sequences, that will be resolved
     /// by the interpreter when used.
     ::mlir::OwningOpRef<::mlir::ModuleOp> libraryModule;
diff --git a/mlir/include/mlir/Dialect/Transform/IR/TransformTypes.td b/mlir/include/mlir/Dialect/Transform/IR/TransformTypes.td
index 3e3fff4c63d2b..ac3e8c21ffb81 100644
--- a/mlir/include/mlir/Dialect/Transform/IR/TransformTypes.td
+++ b/mlir/include/mlir/Dialect/Transform/IR/TransformTypes.td
@@ -65,6 +65,23 @@ def Transform_AnyParamType : TypeDef<Transform_Dialect, "AnyParam",
   let assemblyFormat = "";
 }
 
+def Transform_NormalizedOpType
+    : TypeDef<Transform_Dialect, "NormalizedOp",
+              [DeclareTypeInterfaceMethods<TransformHandleTypeInterface>]> {
+  let description = [{
+    Transform IR handle to operations that satisfy additional constraints
+    required by normal form attributes parameterizing this type. When used for
+    operands, serves as a guarantee of transform preconditions being satisfied.
+    When used for results, serves as a guarantee of transform postcondition.
+  }];
+  let mnemonic = "normalized_op";
+  let parameters = (ins ArrayRefParameter<
+      "::mlir::transform::NormalFormAttrInterface",
+      "Normal forms satisfied by the associated payload">:$normal_forms);
+  let assemblyFormat = "`<` $normal_forms `>`";
+  let genVerifyDecl = 1;
+}
+
 def Transform_ParamType : TypeDef<Transform_Dialect, "Param",
     [DeclareTypeInterfaceMethods<TransformParamTypeInterface>]> {
   let description = [{
diff --git a/mlir/include/mlir/Dialect/Transform/Interfaces/CMakeLists.txt b/mlir/include/mlir/Dialect/Transform/Interfaces/CMakeLists.txt
index 195f4d97c4d56..75e174941160b 100644
--- a/mlir/include/mlir/Dialect/Transform/Interfaces/CMakeLists.txt
+++ b/mlir/include/mlir/Dialect/Transform/Interfaces/CMakeLists.txt
@@ -9,6 +9,11 @@ mlir_tablegen(TransformTypeInterfaces.cpp.inc -gen-type-interface-defs)
 add_mlir_dialect_tablegen_target(MLIRTransformDialectTypeInterfacesIncGen)
 add_mlir_doc(TransformInterfaces TransformTypeInterfaces Dialects/ -gen-type-interface-docs)
 
+mlir_tablegen(TransformAttrInterfaces.h.inc -gen-attr-interface-decls)
+mlir_tablegen(TransformAttrInterfaces.cpp.inc -gen-attr-interface-defs)
+add_mlir_dialect_tablegen_target(MLIRTransformDialectAttrInterfacesIncGen)
+add_mlir_doc(TransformInterfaces TransformAttrInterfaces Dialects/ -gen-attr-interface-docs)
+
 add_mlir_interface(MatchInterfaces)
 add_dependencies(MLIRMatchInterfacesIncGen MLIRTransformInterfacesIncGen)
 add_dependencies(mlir-headers MLIRMatchInterfacesIncGen)
diff --git a/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h b/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h
index c32d4a81a63ab..d5499fa2f3fc0 100644
--- a/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h
+++ b/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h
@@ -16,6 +16,7 @@
 #include "mlir/Interfaces/SideEffectInterfaces.h"
 #include "mlir/Transforms/DialectConversion.h"
 
+#include "mlir/Dialect/Transform/Interfaces/TransformAttrInterfaces.h.inc"
 #include "mlir/Dialect/Transform/Interfaces/TransformTypeInterfaces.h.inc"
 
 namespace mlir {
@@ -1624,4 +1625,16 @@ mlir::transform::TransformEachOpTrait<OpTy>::verifyTrait(Operation *op) {
   return success();
 }
 
+namespace llvm {
+template <>
+struct PointerLikeTypeTraits<mlir::transform::NormalFormAttrInterface>
+    : public PointerLikeTypeTraits<mlir::Attribute> {
+  static inline mlir::transform::NormalFormAttrInterface
+  getFromVoidPointer(void *p) {
+    return cast<mlir::transform::NormalFormAttrInterface>(
+        mlir::Attribute::getFromOpaquePointer(p));
+  }
+};
+} // namespace llvm
+
 #endif // DIALECT_TRANSFORM_INTERFACES_TRANSFORMINTERFACES_H
diff --git a/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.td b/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.td
index 9439104a700b0..60474a370be8f 100644
--- a/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.td
+++ b/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.td
@@ -117,6 +117,40 @@ def TransformOpInterface : OpInterface<"TransformOpInterface"> {
   }];
 }
 
+def NormalFormAttrInterface : AttrInterface<"NormalFormAttrInterface"> {
+  let description = [{
+    Interface for attributes that define normal form constraints on IR.
+
+    A normal form attribute specifies additional invariants beyond those
+    enforced by the verifiers that types, attributes, and operations may satisfy
+    to enable certain transformations. This can be seen as a transformation
+    pre/post-conditions.
+
+    Implementers should override one or more of `verifyType`, `verifyAttribute`,
+    or `verifyOperation` to define custom verification logic. The default
+    implementations return success.
+  }];
+  let cppNamespace = "::mlir::transform";
+
+  // Note: we can also consider dedicated methods for attributes and types.
+  let methods = [InterfaceMethod<
+                     /*desc=*/[{
+       Check that the given operation satisfies the constraints defined by this
+       normal form attribute.
+
+       Produce a silenceable error at the operation's location if constraints are not
+       satisfied.
+      }],
+                     /*returnType=*/"::mlir::DiagnosedSilenceableFailure",
+                     /*name=*/"checkOperation",
+                     /*arguments=*/(ins "::mlir::Operation*":$op),
+                     /*methodBody=*/"",
+                     /*defaultImplementation=*/[{
+        return ::mlir::DiagnosedSilenceableFailure::success();
+      }]>,
+  ];
+}
+
 class TransformTypeInterfaceBase<string cppClass, string cppObjectType>
     : TypeInterface<cppClass> {
   let cppNamespace = "::mlir::transform";
diff --git a/mlir/lib/Dialect/Transform/IR/TransformAttrs.cpp b/mlir/lib/Dialect/Transform/IR/TransformAttrs.cpp
index 1549055e38ad9..359447feed83c 100644
--- a/mlir/lib/Dialect/Transform/IR/TransformAttrs.cpp
+++ b/mlir/lib/Dialect/Transform/IR/TransformAttrs.cpp
@@ -7,6 +7,32 @@
 //===----------------------------------------------------------------------===//
 
 #include "mlir/Dialect/Transform/IR/TransformAttrs.h"
+#include "mlir/Dialect/Transform/IR/TransformDialect.h"
+#include "mlir/IR/Builders.h"
 #include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/DialectImplementation.h"
+#include "mlir/IR/OpImplementation.h"
+
+#include "llvm/ADT/TypeSwitch.h"
+
+using namespace mlir;
+
+// These are automatically generated by ODS but are not used as the Transform
+// dialect uses a different dispatch mechanism to support dialect extensions.
+[[maybe_unused]] static OptionalParseResult
+generatedAttributeParser(AsmParser &parser, StringRef *mnemonic, Type type,
+                         Attribute &value);
+[[maybe_unused]] static LogicalResult
+generatedAttributePrinter(Attribute def, AsmPrinter &printer);
+
+#define GET_ATTRDEF_CLASSES
+#include "mlir/Dialect/Transform/IR/TransformAttrs.cpp.inc"
 
 #include "mlir/Dialect/Transform/IR/TransformDialectEnums.cpp.inc"
+
+void transform::TransformDialect::initializeAttributes() {
+  addAttributesChecked<
+#define GET_ATTRDEF_LIST
+#include "mlir/Dialect/Transform/IR/TransformAttrs.cpp.inc"
+      >();
+}
\ No newline at end of file
diff --git a/mlir/lib/Dialect/Transform/IR/TransformDialect.cpp b/mlir/lib/Dialect/Transform/IR/TransformDialect.cpp
index 165908d88360d..0448ed194217d 100644
--- a/mlir/lib/Dialect/Transform/IR/TransformDialect.cpp
+++ b/mlir/lib/Dialect/Transform/IR/TransformDialect.cpp
@@ -21,9 +21,6 @@ using namespace mlir;
 
 #include "mlir/Dialect/Transform/IR/TransformDialect.cpp.inc"
 
-#define GET_ATTRDEF_CLASSES
-#include "mlir/Dialect/Transform/IR/TransformAttrs.cpp.inc"
-
 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
 void transform::detail::checkImplementsTransformOpInterface(
     StringRef name, MLIRContext *context) {
@@ -70,14 +67,34 @@ void transform::TransformDialect::initialize() {
 #define GET_OP_LIST
 #include "mlir/Dialect/Transform/IR/TransformOps.cpp.inc"
       >();
+  initializeAttributes();
   initializeTypes();
-  addAttributes<
-#define GET_ATTRDEF_LIST
-#include "mlir/Dialect/Transform/IR/TransformAttrs.cpp.inc"
-      >();
   initializeLibraryModule();
 }
 
+Attribute transform::TransformDialect::parseAttribute(DialectAsmParser &parser,
+                                                      Type type) const {
+  StringRef keyword;
+  SMLoc loc = parser.getCurrentLocation();
+  if (failed(parser.parseKeyword(&keyword)))
+    return nullptr;
+
+  auto it = attributeParsingHooks.find(keyword);
+  if (it == attributeParsingHooks.end()) {
+    parser.emitError(loc) << "unknown attribute mnemonic: " << keyword;
+    return nullptr;
+  }
+
+  return it->getValue()(parser, type);
+}
+
+void transform::TransformDialect::printAttribute(
+    Attribute attribute, DialectAsmPrinter &printer) const {
+  auto it = attributePrintingHooks.find(attribute.getTypeID());
+  assert(it != attributePrintingHooks.end() && "printing unknown attribute");
+  it->getSecond()(attribute, printer);
+}
+
 Type transform::TransformDialect::parseType(DialectAsmParser &parser) const {
   StringRef keyword;
   SMLoc loc = parser.getCurrentLocation();
@@ -114,6 +131,15 @@ void transform::TransformDialect::initializeLibraryModule() {
                                UnitAttr::get(context));
 }
 
+void transform::TransformDialect::reportDuplicateAttributeRegistration(
+    StringRef attrName) {
+  std::string buffer;
+  llvm::raw_string_ostream msg(buffer);
+  msg << "extensible dialect attribute '" << attrName
+      << "' is already registered with a different implementation";
+  llvm::report_fatal_error(StringRef(buffer));
+}
+
 void transform::TransformDialect::reportDuplicateTypeRegistration(
     StringRef mnemonic) {
   std::string buffer;
diff --git a/mlir/lib/Dialect/Transform/IR/TransformTypes.cpp b/mlir/lib/Dialect/Transform/IR/TransformTypes.cpp
index a2cff6acecd5a..4aa2aa23b9025 100644
--- a/mlir/lib/Dialect/Transform/IR/TransformTypes.cpp
+++ b/mlir/lib/Dialect/Transform/IR/TransformTypes.cpp
@@ -104,6 +104,46 @@ transform::AnyParamType::checkPayload(Location loc,
   return DiagnosedSilenceableFailure::success();
 }
 
+//===----------------------------------------------------------------------===//
+// transform::NormalizedOpType
+//===----------------------------------------------------------------------===//
+
+DiagnosedSilenceableFailure
+transform::NormalizedOpType::checkPayload(Location loc,
+                                          ArrayRef<Operation *> payload) const {
+  // Return any definite failure or the first silenceable failure.
+  DiagnosedSilenceableFailure overallResult =
+      DiagnosedSilenceableFailure::success();
+  for (Operation *op : payload) {
+    for (NormalFormAttrInterface normalForm : getNormalForms()) {
+      DiagnosedSilenceableFailure result = normalForm.checkOperation(op);
+      if (result.isDefiniteFailure())
+        return result;
+      if (result.isSilenceableFailure() && overallResult.succeeded())
+        overallResult = std::move(result);
+    }
+  }
+  return overallResult;
+}
+
+LogicalResult transform::NormalizedOpType::verify(
+    function_ref<InFlightDiagnostic()> emitError,
+    ArrayRef<NormalFormAttrInterface> normalForms) {
+  llvm::DenseMap<TypeID, NormalFormAttrInterface> seen;
+  for (NormalFormAttrInterface normalForm : normalForms) {
+    auto [previous, inserted] =
+        seen.try_emplace(normalForm.getTypeID(), normalForm);
+    if (!inserted) {
+      InFlightDiagnostic diag = emitError()
+                                << "duplicate normal form: " << normalForm;
+      diag.attachNote() << "previous instance: " << previous->second;
+      return diag;
+    }
+  }
+
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // transform::ParamType
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp b/mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp
index da9ffe53d7fd5..09225f14dea1d 100644
--- a/mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp
+++ b/mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp
@@ -2023,5 +2023,6 @@ LogicalResult transform::applyTransforms(
 // Generated interface implementation.
 //===----------------------------------------------------------------------===//
 
+#include "mlir/Dialect/Transform/Interfaces/TransformAttrInterfaces.cpp.inc"
 #include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.cpp.inc"
 #include "mlir/Dialect/Transform/Interfaces/TransformTypeInterfaces.cpp.inc"
diff --git a/mlir/test/Dialect/Transform/normal-forms.mlir b/mlir/test/Dialect/Transform/normal-forms.mlir
new file mode 100644
index 0000000000000..49cf149f62739
--- /dev/null
+++ b/mlir/test/Dialect/Transform/normal-forms.mlir
@@ -0,0 +1,80 @@
+// expected-error @below {{normal form test_single_block_normal_form requires payload operations to have a single region}}
+func.func private @empty()
+
+module attributes {transform.with_named_sequence} {
+  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
+    transform.structured.match attributes {sym_name = "empty"} in %arg0 : (!transform.any_op) -> !transform.normalized_op<#transform.test_single_block_normal_form<nested false>>
+    transform.yield
+  }
+}
+
+// -----
+
+// expected-remark @below {{found}}
+func.func private @single() {
+  return
+}
+
+module attributes {transform.with_named_sequence} {
+  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
+    %0 = transform.structured.match attributes {sym_name = "single"} in %arg0 : (!transform.any_op) -> !transform.normalized_op<#transform.test_single_block_normal_form<nested false>>
+    transform.debug.emit_remark_at %0, "found" : !transform.normalized_op<#transform.test_single_block_normal_form<nested false>>
+    transform.yield
+  }
+}
+
+// -----
+
+// expected-error @below {{normal form test_single_block_normal_form requires payload operations to have a single region}}
+func.func private @branchy() {
+  cf.br ^bb1
+^bb1:
+  return
+}
+
+module attributes {transform.with_named_sequence} {
+  transform.named_sequence @__transform_main(%arg0: !transform....
[truncated]

``````````

</details>


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


More information about the Mlir-commits mailing list