[Mlir-commits] [mlir] [MLIR][Python] Add python-side adaptor class codegen in mlir-tblgen (PR #176640)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sat Jan 24 05:37:50 PST 2026


https://github.com/PragmaTwice updated https://github.com/llvm/llvm-project/pull/176640

>From 36e3ff81cd631d2ba07b536426a32622d6c842cd Mon Sep 17 00:00:00 2001
From: PragmaTwice <twice at apache.org>
Date: Sun, 18 Jan 2026 18:59:35 +0800
Subject: [PATCH 1/5] [MLIR][Python] Add python-side adaptor class codegen in
 mlir-tblgen

---
 mlir/python/mlir/dialects/_ods_common.py      |  6 ++
 mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp | 71 ++++++++++++-------
 2 files changed, 53 insertions(+), 24 deletions(-)

diff --git a/mlir/python/mlir/dialects/_ods_common.py b/mlir/python/mlir/dialects/_ods_common.py
index 10abd06ff266e..e8b7aa81ef920 100644
--- a/mlir/python/mlir/dialects/_ods_common.py
+++ b/mlir/python/mlir/dialects/_ods_common.py
@@ -305,3 +305,9 @@ def _get_int_array_array_attr(
 
     # Turn the outer list into an ArrayAttr.
     return ArrayAttr.get(values)
+
+
+class OpAdaptor:
+    def __init__(self, operands, attributes) -> None:
+        self.operands = operands
+        self.attributes = attributes
diff --git a/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp b/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
index 6545559ff1b10..6571db1796010 100644
--- a/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
@@ -40,6 +40,7 @@ from ._ods_common import (
     get_default_loc_context as _ods_get_default_loc_context,
     get_op_results_or_values as _get_op_results_or_values,
     segmented_accessor as _ods_segmented_accessor,
+    OpAdaptor as _ods_OpAdaptor,
 )
 _ods_ir = _ods_cext.ir
 _ods_cext.globals.register_traceback_file_exclusion(__file__)
@@ -69,6 +70,15 @@ constexpr const char *opClassTemplate = R"Py(
 @_ods_cext.register_operation(_Dialect)
 class {0}(_ods_ir.OpView):{2}
   OPERATION_NAME = "{1}"
+  Adaptor = {0}Adaptor
+)Py";
+
+/// Template for operation class:
+///   {0} is the Python class name;
+///   {1} is the operation name。
+constexpr const char *opAdaptorClassTemplate = R"Py(
+class {0}Adaptor(_ods_OpAdaptor):
+  OPERATION_NAME = "{1}"
 )Py";
 
 /// Template for class level declarations of operand and result
@@ -99,7 +109,7 @@ constexpr const char *opClassRegionSpecTemplate = R"Py(
 constexpr const char *opSingleTemplate = R"Py(
   @builtins.property
   def {0}(self) -> {3}:
-    return self.operation.{1}s[{2}]
+    return self.{1}s[{2}]
 )Py";
 
 /// Template for single-element accessor after a variable-length group:
@@ -113,8 +123,8 @@ constexpr const char *opSingleTemplate = R"Py(
 constexpr const char *opSingleAfterVariableTemplate = R"Py(
   @builtins.property
   def {0}(self) -> {4}:
-    _ods_variadic_group_length = len(self.operation.{1}s) - {2} + 1
-    return self.operation.{1}s[{3} + _ods_variadic_group_length - 1]
+    _ods_variadic_group_length = len(self.{1}s) - {2} + 1
+    return self.{1}s[{3} + _ods_variadic_group_length - 1]
 )Py";
 
 /// Template for an optional element accessor:
@@ -129,7 +139,7 @@ constexpr const char *opSingleAfterVariableTemplate = R"Py(
 constexpr const char *opOneOptionalTemplate = R"Py(
   @builtins.property
   def {0}(self) -> _Optional[{4}]:
-    return None if len(self.operation.{1}s) < {2} else self.operation.{1}s[{3}]
+    return None if len(self.{1}s) < {2} else self.{1}s[{3}]
 )Py";
 
 /// Template for the variadic group accessor in the single variadic group case:
@@ -141,8 +151,8 @@ constexpr const char *opOneOptionalTemplate = R"Py(
 constexpr const char *opOneVariadicTemplate = R"Py(
   @builtins.property
   def {0}(self) -> {4}:
-    _ods_variadic_group_length = len(self.operation.{1}s) - {2} + 1
-    return self.operation.{1}s[{3}:{3} + _ods_variadic_group_length]
+    _ods_variadic_group_length = len(self.{1}s) - {2} + 1
+    return self.{1}s[{3}:{3} + _ods_variadic_group_length]
 )Py";
 
 /// First part of the template for equally-sized variadic group accessor:
@@ -156,20 +166,20 @@ constexpr const char *opOneVariadicTemplate = R"Py(
 constexpr const char *opVariadicEqualPrefixTemplate = R"Py(
   @builtins.property
   def {0}(self) -> {6}:
-    start, elements_per_group = _ods_equally_sized_accessor(self.operation.{1}s, {2}, {3}, {4}, {5}))Py";
+    start, elements_per_group = _ods_equally_sized_accessor(self.{1}s, {2}, {3}, {4}, {5}))Py";
 
 /// Second part of the template for equally-sized case, accessing a single
 /// element:
 ///   {0} is either 'operand' or 'result'.
 constexpr const char *opVariadicEqualSimpleTemplate = R"Py(
-    return self.operation.{0}s[start]
+    return self.{0}s[start]
 )Py";
 
 /// Second part of the template for equally-sized case, accessing a variadic
 /// group:
 ///   {0} is either 'operand' or 'result'.
 constexpr const char *opVariadicEqualVariadicTemplate = R"Py(
-    return self.operation.{0}s[start:start + elements_per_group]
+    return self.{0}s[start:start + elements_per_group]
 )Py";
 
 /// Template for an attribute-sized group accessor:
@@ -177,14 +187,15 @@ constexpr const char *opVariadicEqualVariadicTemplate = R"Py(
 ///   {1} is either 'operand' or 'result';
 ///   {2} is the position of the group in the group list;
 ///   {3} is a return suffix (expected [0] for single-element, empty for
-///       variadic, and opVariadicSegmentOptionalTrailingTemplate for optional).
-///   {4} is the type hint.
+///       variadic, and opVariadicSegmentOptionalTrailingTemplate for optional);
+///   {4} is the type hint;
+///   {5} is the instance variable name in python.
 constexpr const char *opVariadicSegmentTemplate = R"Py(
   @builtins.property
   def {0}(self) -> {4}:
     {1}_range = _ods_segmented_accessor(
-         self.operation.{1}s,
-         self.operation.attributes["{1}SegmentSizes"], {2})
+         self.{5}s,
+         self.attributes["{1}SegmentSizes"], {2})
     return {1}_range{3}
 )Py";
 
@@ -364,7 +375,8 @@ static void emitElementAccessors(
     const Operator &op, raw_ostream &os, const char *kind,
     unsigned numVariadicGroups, unsigned numElements,
     llvm::function_ref<const NamedTypeConstraint &(const Operator &, int)>
-        getElement) {
+        getElement,
+    bool isAdaptor = false) {
   assert(llvm::is_contained(SmallVector<StringRef, 2>{"operand", "result"},
                             kind) &&
          "unsupported kind");
@@ -375,6 +387,8 @@ static void emitElementAccessors(
                                       StringRef(kind).drop_front());
   std::string attrSizedTrait = attrSizedTraitForKind(kind);
 
+  std::string pyAttrName = isAdaptor ? kind : std::string("operation.") + kind;
+
   // If there is only one variable-length element group, its size can be
   // inferred from the total number of elements. If there are none, the
   // generation is straightforward.
@@ -393,20 +407,20 @@ static void emitElementAccessors(
         type = llvm::formatv("{0}[{1}]", type, pythonType);
       if (element.isVariableLength()) {
         if (element.isOptional()) {
-          os << formatv(opOneOptionalTemplate, sanitizeName(element.name), kind,
-                        numElements, i, type);
+          os << formatv(opOneOptionalTemplate, sanitizeName(element.name),
+                        pyAttrName, numElements, i, type);
         } else {
           type = std::strcmp(kind, "operand") == 0 ? "_ods_ir.OpOperandList"
                                                    : "_ods_ir.OpResultList";
-          os << formatv(opOneVariadicTemplate, sanitizeName(element.name), kind,
-                        numElements, i, type);
+          os << formatv(opOneVariadicTemplate, sanitizeName(element.name),
+                        pyAttrName, numElements, i, type);
         }
       } else if (seenVariableLength) {
         os << formatv(opSingleAfterVariableTemplate, sanitizeName(element.name),
-                      kind, numElements, i, type);
+                      pyAttrName, numElements, i, type);
       } else {
-        os << formatv(opSingleTemplate, sanitizeName(element.name), kind, i,
-                      type);
+        os << formatv(opSingleTemplate, sanitizeName(element.name), pyAttrName,
+                      i, type);
       }
     }
     return;
@@ -444,12 +458,12 @@ static void emitElementAccessors(
             type += "[" + pythonType.str() + "]";
         }
         os << formatv(opVariadicEqualPrefixTemplate, sanitizeName(element.name),
-                      kind, numSimpleLength, numVariadicGroups,
+                      pyAttrName, numSimpleLength, numVariadicGroups,
                       numPrecedingSimple, numPrecedingVariadic, type);
         os << formatv(element.isVariableLength()
                           ? opVariadicEqualVariadicTemplate
                           : opVariadicEqualSimpleTemplate,
-                      kind);
+                      pyAttrName);
       }
       if (element.isVariableLength())
         ++numPrecedingVariadic;
@@ -490,7 +504,7 @@ static void emitElementAccessors(
       }
 
       os << formatv(opVariadicSegmentTemplate, sanitizeName(element.name), kind,
-                    i, trailing, type);
+                    i, trailing, type, pyAttrName);
     }
     return;
   }
@@ -1193,8 +1207,17 @@ static std::string makeDocStringForOp(const Operator &op) {
   return docString;
 }
 
+static void emitAdaptorOperandAccessors(const Operator &op, raw_ostream &os) {
+  emitElementAccessors(op, os, "operand", op.getNumVariableLengthOperands(),
+                       getNumOperands(op), getOperand, /*isAdaptor=*/true);
+}
+
 /// Emits bindings for a specific Op to the given output stream.
 static void emitOpBindings(const Operator &op, raw_ostream &os) {
+  os << formatv(opAdaptorClassTemplate, op.getCppClassName(),
+                op.getOperationName());
+  emitAdaptorOperandAccessors(op, os);
+
   os << formatv(opClassTemplate, op.getCppClassName(), op.getOperationName(),
                 makeDocStringForOp(op));
 

>From c7039afdd7c04000b838982b6b1751e0d160368b Mon Sep 17 00:00:00 2001
From: PragmaTwice <twice at apache.org>
Date: Sun, 18 Jan 2026 22:15:34 +0800
Subject: [PATCH 2/5] fix test case

---
 mlir/test/mlir-tblgen/op-python-bindings.td   | 76 +++++++++----------
 mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp |  8 +-
 2 files changed, 43 insertions(+), 41 deletions(-)

diff --git a/mlir/test/mlir-tblgen/op-python-bindings.td b/mlir/test/mlir-tblgen/op-python-bindings.td
index 929851724ba71..a4cb5fdacbe30 100644
--- a/mlir/test/mlir-tblgen/op-python-bindings.td
+++ b/mlir/test/mlir-tblgen/op-python-bindings.td
@@ -16,8 +16,8 @@ class TestOp<string mnemonic, list<Trait> traits = []> :
     Op<Test_Dialect, mnemonic, traits>;
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class AttrSizedOperandsOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.attr_sized_operands"
+// CHECK-LABEL: class AttrSizedOperandsOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.attr_sized_operands"
 // CHECK: _ODS_OPERAND_SEGMENTS = [-1,1,0,]
 def AttrSizedOperandsOp : TestOp<"attr_sized_operands",
                                  [AttrSizedOperandSegments]> {
@@ -64,8 +64,8 @@ def AttrSizedOperandsOp : TestOp<"attr_sized_operands",
 // CHECK:   return AttrSizedOperandsOp(variadic1=variadic1, non_variadic=non_variadic, variadic2=variadic2, loc=loc, ip=ip)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class AttrSizedResultsOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.attr_sized_results"
+// CHECK-LABEL: class AttrSizedResultsOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.attr_sized_results"
 // CHECK: _ODS_RESULT_SEGMENTS = [0,1,-1,]
 def AttrSizedResultsOp : TestOp<"attr_sized_results",
                                [AttrSizedResultSegments]> {
@@ -114,8 +114,8 @@ def AttrSizedResultsOp : TestOp<"attr_sized_results",
 
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class AttributedOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.attributed_op"
+// CHECK-LABEL: class AttributedOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.attributed_op"
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def AttributedOp : TestOp<"attributed_op"> {
@@ -164,8 +164,8 @@ def AttributedOp : TestOp<"attributed_op"> {
 // CHECK:     return AttributedOp(i32attr=i32attr, in_=in_, optionalF32Attr=optional_f32_attr, unitAttr=unit_attr, loc=loc, ip=ip)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class AttributedOpWithOperands(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.attributed_op_with_operands"
+// CHECK-LABEL: class AttributedOpWithOperands(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.attributed_op_with_operands"
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def AttributedOpWithOperands : TestOp<"attributed_op_with_operands"> {
@@ -201,8 +201,8 @@ def AttributedOpWithOperands : TestOp<"attributed_op_with_operands"> {
 // CHECK:   return AttributedOpWithOperands(_gen_arg_0=_gen_arg_0, _gen_arg_2=_gen_arg_2, in_=in_, is_=is_, loc=loc, ip=ip)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class DefaultValuedAttrsOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.default_valued_attrs"
+// CHECK-LABEL: class DefaultValuedAttrsOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.default_valued_attrs"
 def DefaultValuedAttrsOp : TestOp<"default_valued_attrs"> {
   // CHECK: def __init__(self, *, arr=None, unsupported=None, loc=None, ip=None):
   // CHECK:   operands = []
@@ -283,8 +283,8 @@ def DescriptionOp : TestOp<"description"> {
 }
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class EmptyOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.empty"
+// CHECK-LABEL: class EmptyOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.empty"
 def EmptyOp : TestOp<"empty">;
   // CHECK: def __init__(self, *, loc=None, ip=None):
   // CHECK:   operands = []
@@ -329,8 +329,8 @@ def InferResultTypesOp : TestOp<"infer_result_types_op", [InferTypeOpInterface]>
 // CHECK:   return InferResultTypesOp(results=results, loc=loc, ip=ip).results
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class MissingNamesOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.missing_names"
+// CHECK-LABEL: class MissingNamesOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.missing_names"
 def MissingNamesOp : TestOp<"missing_names"> {
   // CHECK: def __init__(self, i32, _gen_res_1, i64, _gen_arg_0, f32, _gen_arg_2, *, loc=None, ip=None):
   // CHECK:   operands = []
@@ -368,8 +368,8 @@ def MissingNamesOp : TestOp<"missing_names"> {
 // CHECK:   return MissingNamesOp(i32=i32, _gen_res_1=_gen_res_1, i64=i64, _gen_arg_0=_gen_arg_0, f32=f32, _gen_arg_2=_gen_arg_2, loc=loc, ip=ip).results
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class OneOptionalOperandOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.one_optional_operand"
+// CHECK-LABEL: class OneOptionalOperandOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.one_optional_operand"
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def OneOptionalOperandOp : TestOp<"one_optional_operand"> {
@@ -400,8 +400,8 @@ def OneOptionalOperandOp : TestOp<"one_optional_operand"> {
 // CHECK:   return OneOptionalOperandOp(non_optional=non_optional, optional=optional, loc=loc, ip=ip)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class OneVariadicOperandOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.one_variadic_operand"
+// CHECK-LABEL: class OneVariadicOperandOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.one_variadic_operand"
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def OneVariadicOperandOp : TestOp<"one_variadic_operand"> {
@@ -433,8 +433,8 @@ def OneVariadicOperandOp : TestOp<"one_variadic_operand"> {
 // CHECK:   return OneVariadicOperandOp(non_variadic=non_variadic, variadic=variadic, loc=loc, ip=ip)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class OneVariadicResultOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.one_variadic_result"
+// CHECK-LABEL: class OneVariadicResultOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.one_variadic_result"
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def OneVariadicResultOp : TestOp<"one_variadic_result"> {
@@ -468,8 +468,8 @@ def OneVariadicResultOp : TestOp<"one_variadic_result"> {
 // CHECK:   return results if len(results) > 1 else (results[0] if len(results) == 1 else op)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class PythonKeywordOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.python_keyword"
+// CHECK-LABEL: class PythonKeywordOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.python_keyword"
 def PythonKeywordOp : TestOp<"python_keyword"> {
   // CHECK: def __init__(self, in_, *, loc=None, ip=None):
   // CHECK:   operands = []
@@ -518,8 +518,8 @@ def SameResultsVariadicOp : TestOp<"same_results_variadic", [SameOperandsAndResu
 
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class SameVariadicOperandSizeOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.same_variadic_operand"
+// CHECK-LABEL: class SameVariadicOperandSizeOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.same_variadic_operand"
 def SameVariadicOperandSizeOp : TestOp<"same_variadic_operand",
                                        [SameVariadicOperandSize]> {
   // CHECK: @builtins.property
@@ -544,8 +544,8 @@ def SameVariadicOperandSizeOp : TestOp<"same_variadic_operand",
 // CHECK:   return SameVariadicOperandSizeOp(variadic1=variadic1, non_variadic=non_variadic, variadic2=variadic2, loc=loc, ip=ip)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class SameVariadicResultSizeOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.same_variadic_result"
+// CHECK-LABEL: class SameVariadicResultSizeOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.same_variadic_result"
 def SameVariadicResultSizeOp : TestOp<"same_variadic_result",
                                       [SameVariadicResultSize]> {
   // CHECK: @builtins.property
@@ -571,8 +571,8 @@ def SameVariadicResultSizeOp : TestOp<"same_variadic_result",
 // CHECK:   return results if len(results) > 1 else (results[0] if len(results) == 1 else op)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class SimpleOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.simple"
+// CHECK-LABEL: class SimpleOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.simple"
 def SimpleOp : TestOp<"simple"> {
   // CHECK: def __init__(self, i64, f64, i32, f32, *, loc=None, ip=None):
   // CHECK:   operands = []
@@ -611,8 +611,8 @@ def SimpleOp : TestOp<"simple"> {
 // CHECK: def simple(i64, f64, i32, f32, *, loc=None, ip=None) -> _ods_ir.OpResultList:
 // CHECK:   return SimpleOp(i64=i64, f64=f64, i32=i32, f32=f32, loc=loc, ip=ip).results
 
-// CHECK: class VariadicAndNormalRegionOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.variadic_and_normal_region"
+// CHECK-LABEL: class VariadicAndNormalRegionOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.variadic_and_normal_region"
 def VariadicAndNormalRegionOp : TestOp<"variadic_and_normal_region"> {
   // CHECK:  def __init__(self, num_variadic, *, loc=None, ip=None):
   // CHECK:    operands = []
@@ -639,8 +639,8 @@ def VariadicAndNormalRegionOp : TestOp<"variadic_and_normal_region"> {
 // CHECK: def variadic_and_normal_region(num_variadic, *, loc=None, ip=None) -> VariadicAndNormalRegionOp:
 // CHECK:   return VariadicAndNormalRegionOp(num_variadic=num_variadic, loc=loc, ip=ip)
 
-// CHECK: class VariadicRegionOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.variadic_region"
+// CHECK-LABEL: class VariadicRegionOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.variadic_region"
 def VariadicRegionOp : TestOp<"variadic_region"> {
   // CHECK:  def __init__(self, num_variadic, *, loc=None, ip=None):
   // CHECK:    operands = []
@@ -664,8 +664,8 @@ def VariadicRegionOp : TestOp<"variadic_region"> {
 // CHECK:   return VariadicRegionOp(num_variadic=num_variadic, loc=loc, ip=ip)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class WithSpecialCharactersOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.123with--special.characters"
+// CHECK-LABEL: class WithSpecialCharactersOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.123with--special.characters"
 def WithSpecialCharactersOp : TestOp<"123with--special.characters"> {
 }
 
@@ -673,8 +673,8 @@ def WithSpecialCharactersOp : TestOp<"123with--special.characters"> {
 // CHECK:   return WithSpecialCharactersOp(loc=loc, ip=ip)
 
 // CHECK: @_ods_cext.register_operation(_Dialect)
-// CHECK: class WithSuccessorsOp(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.with_successors"
+// CHECK-LABEL: class WithSuccessorsOp(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.with_successors"
 def WithSuccessorsOp : TestOp<"with_successors"> {
   // CHECK-NOT:  _ods_successors = None
   // CHECK:      _ods_successors = []
@@ -687,8 +687,8 @@ def WithSuccessorsOp : TestOp<"with_successors"> {
 // CHECK: def with_successors(successor, successors, *, loc=None, ip=None) -> WithSuccessorsOp:
 // CHECK:   return WithSuccessorsOp(successor=successor, successors=successors, loc=loc, ip=ip)
 
-// CHECK: class snake_case(_ods_ir.OpView):
-// CHECK-LABEL: OPERATION_NAME = "test.snake_case"
+// CHECK-LABEL: class snake_case(_ods_ir.OpView):
+// CHECK: OPERATION_NAME = "test.snake_case"
 def already_snake_case : TestOp<"snake_case"> {}
 // CHECK: def snake_case_(*, loc=None, ip=None) -> snake_case:
 // CHECK:   return snake_case(loc=loc, ip=ip)
diff --git a/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp b/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
index 6571db1796010..6eed815591751 100644
--- a/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
@@ -189,13 +189,14 @@ constexpr const char *opVariadicEqualVariadicTemplate = R"Py(
 ///   {3} is a return suffix (expected [0] for single-element, empty for
 ///       variadic, and opVariadicSegmentOptionalTrailingTemplate for optional);
 ///   {4} is the type hint;
-///   {5} is the instance variable name in python.
+///   {5} is the instance variable name in python;
+///   {6} is the instance variable name for attributes in python.
 constexpr const char *opVariadicSegmentTemplate = R"Py(
   @builtins.property
   def {0}(self) -> {4}:
     {1}_range = _ods_segmented_accessor(
          self.{5}s,
-         self.attributes["{1}SegmentSizes"], {2})
+         self.{6}["{1}SegmentSizes"], {2})
     return {1}_range{3}
 )Py";
 
@@ -504,7 +505,8 @@ static void emitElementAccessors(
       }
 
       os << formatv(opVariadicSegmentTemplate, sanitizeName(element.name), kind,
-                    i, trailing, type, pyAttrName);
+                    i, trailing, type, pyAttrName,
+                    isAdaptor ? "attributes" : "operation.attributes");
     }
     return;
   }

>From 0b28512ccd441638c8fdf97a1aa4c52cb4d7c279 Mon Sep 17 00:00:00 2001
From: PragmaTwice <twice at apache.org>
Date: Mon, 19 Jan 2026 23:23:29 +0800
Subject: [PATCH 3/5] add attr getters

---
 mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp | 62 +++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp b/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
index 6eed815591751..74f6d7edea4c2 100644
--- a/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
@@ -228,6 +228,28 @@ constexpr const char *optionalAttributeGetterTemplate = R"Py(
     return self.operation.attributes["{1}"]
 )Py";
 
+/// Template for an operation attribute getter for adaptors:
+///   {0} is the name of the attribute sanitized for Python;
+///   {1} is the original name of the attribute.
+///   {2} is the type hint.
+constexpr const char *adaptorAttributeGetterTemplate = R"Py(
+  @builtins.property
+  def {0}(self) -> {2}:
+    return self.attributes["{1}"]
+)Py";
+
+/// Template for an optional operation attribute getter for adaptors:
+///   {0} is the name of the attribute sanitized for Python;
+///   {1} is the original name of the attribute.
+///   {2} is the type hint.
+constexpr const char *adaptorOptionalAttributeGetterTemplate = R"Py(
+  @builtins.property
+  def {0}(self) -> _Optional[{2}]:
+    if "{1}" not in self.attributes:
+      return None
+    return self.attributes["{1}"]
+)Py";
+
 /// Template for a getter of a unit operation attribute, returns True of the
 /// unit attribute is present, False otherwise (unit attributes have meaning
 /// by mere presence):
@@ -239,6 +261,17 @@ constexpr const char *unitAttributeGetterTemplate = R"Py(
     return "{1}" in self.operation.attributes
 )Py";
 
+/// Template for a getter of a unit operation attribute for adaptors, returns
+/// True of the unit attribute is present, False otherwise (unit attributes have
+/// meaning by mere presence):
+///    {0} is the name of the attribute sanitized for Python,
+///    {1} is the original name of the attribute.
+constexpr const char *adaptorUnitAttributeGetterTemplate = R"Py(
+  @builtins.property
+  def {0}(self) -> bool:
+    return "{1}" in self.attributes
+)Py";
+
 /// Template for an operation attribute setter:
 ///    {0} is the name of the attribute sanitized for Python;
 ///    {1} is the original name of the attribute.
@@ -640,6 +673,34 @@ static void emitAttributeAccessors(const Operator &op, raw_ostream &os) {
   }
 }
 
+/// Emits accessors to Op attributes for adaptors.
+static void emitAdaptorAttributeAccessors(const Operator &op, raw_ostream &os) {
+  for (const auto &namedAttr : op.getAttributes()) {
+    // Skip "derived" attributes because they are just C++ functions that we
+    // don't currently expose.
+    if (namedAttr.attr.isDerivedAttr())
+      continue;
+
+    if (namedAttr.name.empty())
+      continue;
+
+    std::string sanitizedName = sanitizeName(namedAttr.name);
+
+    // Unit attributes are handled specially.
+    if (namedAttr.attr.getStorageType().trim() == "::mlir::UnitAttr") {
+      os << formatv(adaptorUnitAttributeGetterTemplate, sanitizedName,
+                    namedAttr.name);
+      continue;
+    }
+
+    std::string type = "_ods_ir." + getPythonAttrName(namedAttr.attr);
+    os << formatv(namedAttr.attr.isOptional()
+                      ? adaptorOptionalAttributeGetterTemplate
+                      : adaptorAttributeGetterTemplate,
+                  sanitizedName, namedAttr.name, type);
+  }
+}
+
 /// Template for the default auto-generated builder.
 ///   {0} is a comma-separated list of builder arguments, including the trailing
 ///       `loc` and `ip`;
@@ -1219,6 +1280,7 @@ static void emitOpBindings(const Operator &op, raw_ostream &os) {
   os << formatv(opAdaptorClassTemplate, op.getCppClassName(),
                 op.getOperationName());
   emitAdaptorOperandAccessors(op, os);
+  emitAdaptorAttributeAccessors(op, os);
 
   os << formatv(opClassTemplate, op.getCppClassName(), op.getOperationName(),
                 makeDocStringForOp(op));

>From 694e813bd00ae46eae1f8e818e501ade6daa8263 Mon Sep 17 00:00:00 2001
From: PragmaTwice <twice at apache.org>
Date: Mon, 19 Jan 2026 23:46:41 +0800
Subject: [PATCH 4/5] add test

---
 mlir/test/mlir-tblgen/op-python-bindings.td | 37 ++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/mlir/test/mlir-tblgen/op-python-bindings.td b/mlir/test/mlir-tblgen/op-python-bindings.td
index a4cb5fdacbe30..2d44824c9d8fd 100644
--- a/mlir/test/mlir-tblgen/op-python-bindings.td
+++ b/mlir/test/mlir-tblgen/op-python-bindings.td
@@ -112,10 +112,26 @@ def AttrSizedResultsOp : TestOp<"attr_sized_results",
 // CHECK:   op = AttrSizedResultsOp(variadic1=variadic1, non_variadic=non_variadic, variadic2=variadic2, loc=loc, ip=ip); results = op.results
 // CHECK:   return results if len(results) > 1 else (results[0] if len(results) == 1 else op)
 
-
+// CHECK-LABEL: class AttributedOpAdaptor(_ods_OpAdaptor):
+// CHECK:   OPERATION_NAME = "test.attributed_op"
+// CHECK:   @builtins.property
+// CHECK:   def i32attr(self) -> _ods_ir.IntegerAttr:
+// CHECK:     return self.attributes["i32attr"]
+// CHECK:   @builtins.property
+// CHECK:   def optionalF32Attr(self) -> _Optional[_ods_ir.FloatAttr]:
+// CHECK:     if "optionalF32Attr" not in self.attributes:
+// CHECK:       return None
+// CHECK:     return self.attributes["optionalF32Attr"]
+// CHECK:   @builtins.property
+// CHECK:   def unitAttr(self) -> bool:
+// CHECK:     return "unitAttr" in self.attributes
+// CHECK:   @builtins.property
+// CHECK:   def in_(self) -> _ods_ir.IntegerAttr:
+// CHECK:     return self.attributes["in"]
 // CHECK: @_ods_cext.register_operation(_Dialect)
 // CHECK-LABEL: class AttributedOp(_ods_ir.OpView):
 // CHECK: OPERATION_NAME = "test.attributed_op"
+// CHECK: Adaptor = AttributedOpAdaptor
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def AttributedOp : TestOp<"attributed_op"> {
@@ -367,9 +383,18 @@ def MissingNamesOp : TestOp<"missing_names"> {
 // CHECK: def missing_names(i32, _gen_res_1, i64, _gen_arg_0, f32, _gen_arg_2, *, loc=None, ip=None) -> _ods_ir.OpResultList:
 // CHECK:   return MissingNamesOp(i32=i32, _gen_res_1=_gen_res_1, i64=i64, _gen_arg_0=_gen_arg_0, f32=f32, _gen_arg_2=_gen_arg_2, loc=loc, ip=ip).results
 
+// CHECK-LABEL: class OneOptionalOperandOpAdaptor(_ods_OpAdaptor):
+// CHECK:   OPERATION_NAME = "test.one_optional_operand"
+// CHECK:   @builtins.property
+// CHECK:   def non_optional(self) -> _ods_ir.Value:
+// CHECK:     return self.operands[0]
+// CHECK:   @builtins.property
+// CHECK:   def optional(self) -> _Optional[_ods_ir.Value]:
+// CHECK:     return None if len(self.operands) < 2 else self.operands[1]
 // CHECK: @_ods_cext.register_operation(_Dialect)
 // CHECK-LABEL: class OneOptionalOperandOp(_ods_ir.OpView):
 // CHECK: OPERATION_NAME = "test.one_optional_operand"
+// CHECK: Adaptor = OneOptionalOperandOpAdaptor
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def OneOptionalOperandOp : TestOp<"one_optional_operand"> {
@@ -399,9 +424,19 @@ def OneOptionalOperandOp : TestOp<"one_optional_operand"> {
 // CHECK: def one_optional_operand(non_optional, *, optional=None, loc=None, ip=None) -> OneOptionalOperandOp:
 // CHECK:   return OneOptionalOperandOp(non_optional=non_optional, optional=optional, loc=loc, ip=ip)
 
+// CHECK-LABEL: class OneVariadicOperandOpAdaptor(_ods_OpAdaptor):
+// CHECK:   OPERATION_NAME = "test.one_variadic_operand"
+// CHECK:   @builtins.property
+// CHECK:   def non_variadic(self) -> _ods_ir.Value:
+// CHECK:     return self.operands[0]
+// CHECK:   @builtins.property
+// CHECK:   def variadic(self) -> _ods_ir.OpOperandList:
+// CHECK:     _ods_variadic_group_length = len(self.operands) - 2 + 1
+// CHECK:     return self.operands[1:1 + _ods_variadic_group_length]
 // CHECK: @_ods_cext.register_operation(_Dialect)
 // CHECK-LABEL: class OneVariadicOperandOp(_ods_ir.OpView):
 // CHECK: OPERATION_NAME = "test.one_variadic_operand"
+// CHECK: Adaptor = OneVariadicOperandOpAdaptor
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def OneVariadicOperandOp : TestOp<"one_variadic_operand"> {

>From 5b89f1e80d62918b2cf87de782a77896ed9f50cb Mon Sep 17 00:00:00 2001
From: PragmaTwice <twice at apache.org>
Date: Sat, 24 Jan 2026 21:37:27 +0800
Subject: [PATCH 5/5] switch to register

---
 mlir/include/mlir/Bindings/Python/Globals.h   | 14 ++++
 mlir/lib/Bindings/Python/Globals.cpp          | 30 ++++++++
 mlir/lib/Bindings/Python/IRCore.cpp           | 22 ++++++
 mlir/test/mlir-tblgen/op-python-bindings.td   | 72 +++++++++----------
 mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp | 13 ++--
 5 files changed, 109 insertions(+), 42 deletions(-)

diff --git a/mlir/include/mlir/Bindings/Python/Globals.h b/mlir/include/mlir/Bindings/Python/Globals.h
index 5548a716cbe21..ad58c9374f766 100644
--- a/mlir/include/mlir/Bindings/Python/Globals.h
+++ b/mlir/include/mlir/Bindings/Python/Globals.h
@@ -94,6 +94,12 @@ class MLIR_PYTHON_API_EXPORTED PyGlobals {
   void registerOperationImpl(const std::string &operationName,
                              nanobind::object pyClass, bool replace = false);
 
+  /// Adds a operation adaptor class.
+  /// Raises an exception if the mapping already exists and replace == false.
+  /// This is intended to be called by implementation code.
+  void registerOpAdaptorImpl(const std::string &operationName,
+                             nanobind::object pyClass, bool replace = false);
+
   /// Returns the custom Attribute builder for Attribute kind.
   std::optional<nanobind::callable>
   lookupAttributeBuilder(const std::string &attributeKind);
@@ -117,6 +123,12 @@ class MLIR_PYTHON_API_EXPORTED PyGlobals {
   std::optional<nanobind::object>
   lookupOperationClass(llvm::StringRef operationName);
 
+  /// Looks up a registered operation adaptor class by operation
+  /// name. Note that this may trigger a load of the dialect, which can
+  /// arbitrarily re-enter.
+  std::optional<nanobind::object>
+  lookupOpAdaptorClass(llvm::StringRef operationName);
+
   class MLIR_PYTHON_API_EXPORTED TracebackLoc {
   public:
     bool locTracebacksEnabled();
@@ -184,6 +196,8 @@ class MLIR_PYTHON_API_EXPORTED PyGlobals {
   llvm::StringMap<nanobind::object> dialectClassMap;
   /// Map of full operation name to external operation class object.
   llvm::StringMap<nanobind::object> operationClassMap;
+  /// Map of full operation name to external operation adaptor class object.
+  llvm::StringMap<nanobind::object> opAdaptorClassMap;
   /// Map of attribute ODS name to custom builder.
   llvm::StringMap<nanobind::callable> attributeBuilderMap;
   /// Map of MlirTypeID to custom type caster.
diff --git a/mlir/lib/Bindings/Python/Globals.cpp b/mlir/lib/Bindings/Python/Globals.cpp
index e2e8693ba45f3..3d7ee3d30656e 100644
--- a/mlir/lib/Bindings/Python/Globals.cpp
+++ b/mlir/lib/Bindings/Python/Globals.cpp
@@ -137,6 +137,18 @@ void PyGlobals::registerOperationImpl(const std::string &operationName,
   found = std::move(pyClass);
 }
 
+void PyGlobals::registerOpAdaptorImpl(const std::string &operationName,
+                                      nb::object pyClass, bool replace) {
+  nb::ft_lock_guard lock(mutex);
+  nb::object &found = opAdaptorClassMap[operationName];
+  if (found && !replace) {
+    throw std::runtime_error((llvm::Twine("Operation adaptor of '") +
+                              operationName + "' is already registered.")
+                                 .str());
+  }
+  found = std::move(pyClass);
+}
+
 std::optional<nb::callable>
 PyGlobals::lookupAttributeBuilder(const std::string &attributeKind) {
   nb::ft_lock_guard lock(mutex);
@@ -207,6 +219,24 @@ PyGlobals::lookupOperationClass(llvm::StringRef operationName) {
   return std::nullopt;
 }
 
+std::optional<nb::object>
+PyGlobals::lookupOpAdaptorClass(llvm::StringRef operationName) {
+  // Make sure dialect module is loaded.
+  auto split = operationName.split('.');
+  llvm::StringRef dialectNamespace = split.first;
+  if (!loadDialectModule(dialectNamespace))
+    return std::nullopt;
+
+  nb::ft_lock_guard lock(mutex);
+  auto foundIt = opAdaptorClassMap.find(operationName);
+  if (foundIt != opAdaptorClassMap.end()) {
+    assert(foundIt->second && "OpAdaptor is defined");
+    return foundIt->second;
+  }
+  // Not found and loading did not yield a registration.
+  return std::nullopt;
+}
+
 bool PyGlobals::TracebackLoc::locTracebacksEnabled() {
   nanobind::ft_lock_guard lock(mutex);
   return locTracebackEnabled_;
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index eb00363a54034..c8693dc7624d6 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -2774,6 +2774,28 @@ void populateRoot(nb::module_ &m) {
       "dialect_class"_a, nb::kw_only(), "replace"_a = false,
       "Produce a class decorator for registering an Operation class as part of "
       "a dialect");
+  m.def(
+      "register_op_adaptor",
+      [](const nb::type_object &opClass, bool replace) -> nb::object {
+        return nb::cpp_function(
+            [opClass,
+             replace](nb::type_object adaptorClass) -> nb::type_object {
+              std::string operationName =
+                  nb::cast<std::string>(adaptorClass.attr("OPERATION_NAME"));
+              PyGlobals::get().registerOpAdaptorImpl(operationName,
+                                                     adaptorClass, replace);
+              // Dict-stuff the new adaptorClass by name onto the opClass.
+              opClass.attr("Adaptor") = adaptorClass;
+              return adaptorClass;
+            });
+      },
+      // clang-format off
+      nb::sig("def register_op_adaptor(op_class: type, *, replace: bool = False) "
+        "-> typing.Callable[[type[T]], type[T]]"),
+      // clang-format on
+      "op_class"_a, nb::kw_only(), "replace"_a = false,
+      "Produce a class decorator for registering an OpAdaptor class for an "
+      "operation.");
   m.def(
       MLIR_PYTHON_CAPI_TYPE_CASTER_REGISTER_ATTR,
       [](PyTypeID mlirTypeID, bool replace) -> nb::object {
diff --git a/mlir/test/mlir-tblgen/op-python-bindings.td b/mlir/test/mlir-tblgen/op-python-bindings.td
index 2d44824c9d8fd..7d62b7cc943c6 100644
--- a/mlir/test/mlir-tblgen/op-python-bindings.td
+++ b/mlir/test/mlir-tblgen/op-python-bindings.td
@@ -112,26 +112,9 @@ def AttrSizedResultsOp : TestOp<"attr_sized_results",
 // CHECK:   op = AttrSizedResultsOp(variadic1=variadic1, non_variadic=non_variadic, variadic2=variadic2, loc=loc, ip=ip); results = op.results
 // CHECK:   return results if len(results) > 1 else (results[0] if len(results) == 1 else op)
 
-// CHECK-LABEL: class AttributedOpAdaptor(_ods_OpAdaptor):
-// CHECK:   OPERATION_NAME = "test.attributed_op"
-// CHECK:   @builtins.property
-// CHECK:   def i32attr(self) -> _ods_ir.IntegerAttr:
-// CHECK:     return self.attributes["i32attr"]
-// CHECK:   @builtins.property
-// CHECK:   def optionalF32Attr(self) -> _Optional[_ods_ir.FloatAttr]:
-// CHECK:     if "optionalF32Attr" not in self.attributes:
-// CHECK:       return None
-// CHECK:     return self.attributes["optionalF32Attr"]
-// CHECK:   @builtins.property
-// CHECK:   def unitAttr(self) -> bool:
-// CHECK:     return "unitAttr" in self.attributes
-// CHECK:   @builtins.property
-// CHECK:   def in_(self) -> _ods_ir.IntegerAttr:
-// CHECK:     return self.attributes["in"]
 // CHECK: @_ods_cext.register_operation(_Dialect)
 // CHECK-LABEL: class AttributedOp(_ods_ir.OpView):
 // CHECK: OPERATION_NAME = "test.attributed_op"
-// CHECK: Adaptor = AttributedOpAdaptor
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def AttributedOp : TestOp<"attributed_op"> {
@@ -175,6 +158,23 @@ def AttributedOp : TestOp<"attributed_op"> {
   let arguments = (ins I32Attr:$i32attr, OptionalAttr<F32Attr>:$optionalF32Attr,
                    UnitAttr:$unitAttr, I32Attr:$in);
 }
+// CHECK: @_ods_cext.register_op_adaptor(AttributedOp)
+// CHECK-LABEL: class AttributedOpAdaptor(_ods_OpAdaptor):
+// CHECK:   OPERATION_NAME = "test.attributed_op"
+// CHECK:   @builtins.property
+// CHECK:   def i32attr(self) -> _ods_ir.IntegerAttr:
+// CHECK:     return self.attributes["i32attr"]
+// CHECK:   @builtins.property
+// CHECK:   def optionalF32Attr(self) -> _Optional[_ods_ir.FloatAttr]:
+// CHECK:     if "optionalF32Attr" not in self.attributes:
+// CHECK:       return None
+// CHECK:     return self.attributes["optionalF32Attr"]
+// CHECK:   @builtins.property
+// CHECK:   def unitAttr(self) -> bool:
+// CHECK:     return "unitAttr" in self.attributes
+// CHECK:   @builtins.property
+// CHECK:   def in_(self) -> _ods_ir.IntegerAttr:
+// CHECK:     return self.attributes["in"]
 
 // CHECK: def attributed_op(i32attr, in_, *, optional_f32_attr=None, unit_attr=None, loc=None, ip=None) -> AttributedOp:
 // CHECK:     return AttributedOp(i32attr=i32attr, in_=in_, optionalF32Attr=optional_f32_attr, unitAttr=unit_attr, loc=loc, ip=ip)
@@ -383,18 +383,9 @@ def MissingNamesOp : TestOp<"missing_names"> {
 // CHECK: def missing_names(i32, _gen_res_1, i64, _gen_arg_0, f32, _gen_arg_2, *, loc=None, ip=None) -> _ods_ir.OpResultList:
 // CHECK:   return MissingNamesOp(i32=i32, _gen_res_1=_gen_res_1, i64=i64, _gen_arg_0=_gen_arg_0, f32=f32, _gen_arg_2=_gen_arg_2, loc=loc, ip=ip).results
 
-// CHECK-LABEL: class OneOptionalOperandOpAdaptor(_ods_OpAdaptor):
-// CHECK:   OPERATION_NAME = "test.one_optional_operand"
-// CHECK:   @builtins.property
-// CHECK:   def non_optional(self) -> _ods_ir.Value:
-// CHECK:     return self.operands[0]
-// CHECK:   @builtins.property
-// CHECK:   def optional(self) -> _Optional[_ods_ir.Value]:
-// CHECK:     return None if len(self.operands) < 2 else self.operands[1]
 // CHECK: @_ods_cext.register_operation(_Dialect)
 // CHECK-LABEL: class OneOptionalOperandOp(_ods_ir.OpView):
 // CHECK: OPERATION_NAME = "test.one_optional_operand"
-// CHECK: Adaptor = OneOptionalOperandOpAdaptor
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def OneOptionalOperandOp : TestOp<"one_optional_operand"> {
@@ -420,23 +411,22 @@ def OneOptionalOperandOp : TestOp<"one_optional_operand"> {
   // CHECK: def optional(self) -> _Optional[_ods_ir.Value]:
   // CHECK:   return None if len(self.operation.operands) < 2 else self.operation.operands[1]
 }
+// CHECK: @_ods_cext.register_op_adaptor(OneOptionalOperandOp)
+// CHECK-LABEL: class OneOptionalOperandOpAdaptor(_ods_OpAdaptor):
+// CHECK:   OPERATION_NAME = "test.one_optional_operand"
+// CHECK:   @builtins.property
+// CHECK:   def non_optional(self) -> _ods_ir.Value:
+// CHECK:     return self.operands[0]
+// CHECK:   @builtins.property
+// CHECK:   def optional(self) -> _Optional[_ods_ir.Value]:
+// CHECK:     return None if len(self.operands) < 2 else self.operands[1]
 
 // CHECK: def one_optional_operand(non_optional, *, optional=None, loc=None, ip=None) -> OneOptionalOperandOp:
 // CHECK:   return OneOptionalOperandOp(non_optional=non_optional, optional=optional, loc=loc, ip=ip)
 
-// CHECK-LABEL: class OneVariadicOperandOpAdaptor(_ods_OpAdaptor):
-// CHECK:   OPERATION_NAME = "test.one_variadic_operand"
-// CHECK:   @builtins.property
-// CHECK:   def non_variadic(self) -> _ods_ir.Value:
-// CHECK:     return self.operands[0]
-// CHECK:   @builtins.property
-// CHECK:   def variadic(self) -> _ods_ir.OpOperandList:
-// CHECK:     _ods_variadic_group_length = len(self.operands) - 2 + 1
-// CHECK:     return self.operands[1:1 + _ods_variadic_group_length]
 // CHECK: @_ods_cext.register_operation(_Dialect)
 // CHECK-LABEL: class OneVariadicOperandOp(_ods_ir.OpView):
 // CHECK: OPERATION_NAME = "test.one_variadic_operand"
-// CHECK: Adaptor = OneVariadicOperandOpAdaptor
 // CHECK-NOT: _ODS_OPERAND_SEGMENTS
 // CHECK-NOT: _ODS_RESULT_SEGMENTS
 def OneVariadicOperandOp : TestOp<"one_variadic_operand"> {
@@ -463,6 +453,16 @@ def OneVariadicOperandOp : TestOp<"one_variadic_operand"> {
   // CHECK:   return self.operation.operands[1:1 + _ods_variadic_group_length]
   let arguments = (ins AnyType:$non_variadic, Variadic<AnyType>:$variadic);
 }
+// CHECK: @_ods_cext.register_op_adaptor(OneVariadicOperandOp)
+// CHECK-LABEL: class OneVariadicOperandOpAdaptor(_ods_OpAdaptor):
+// CHECK:   OPERATION_NAME = "test.one_variadic_operand"
+// CHECK:   @builtins.property
+// CHECK:   def non_variadic(self) -> _ods_ir.Value:
+// CHECK:     return self.operands[0]
+// CHECK:   @builtins.property
+// CHECK:   def variadic(self) -> _ods_ir.OpOperandList:
+// CHECK:     _ods_variadic_group_length = len(self.operands) - 2 + 1
+// CHECK:     return self.operands[1:1 + _ods_variadic_group_length]
 
 // CHECK: def one_variadic_operand(non_variadic, variadic, *, loc=None, ip=None) -> OneVariadicOperandOp:
 // CHECK:   return OneVariadicOperandOp(non_variadic=non_variadic, variadic=variadic, loc=loc, ip=ip)
diff --git a/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp b/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
index 74f6d7edea4c2..6391842567617 100644
--- a/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpPythonBindingGen.cpp
@@ -70,13 +70,13 @@ constexpr const char *opClassTemplate = R"Py(
 @_ods_cext.register_operation(_Dialect)
 class {0}(_ods_ir.OpView):{2}
   OPERATION_NAME = "{1}"
-  Adaptor = {0}Adaptor
 )Py";
 
 /// Template for operation class:
 ///   {0} is the Python class name;
 ///   {1} is the operation name。
 constexpr const char *opAdaptorClassTemplate = R"Py(
+ at _ods_cext.register_op_adaptor({0})
 class {0}Adaptor(_ods_OpAdaptor):
   OPERATION_NAME = "{1}"
 )Py";
@@ -1277,11 +1277,6 @@ static void emitAdaptorOperandAccessors(const Operator &op, raw_ostream &os) {
 
 /// Emits bindings for a specific Op to the given output stream.
 static void emitOpBindings(const Operator &op, raw_ostream &os) {
-  os << formatv(opAdaptorClassTemplate, op.getCppClassName(),
-                op.getOperationName());
-  emitAdaptorOperandAccessors(op, os);
-  emitAdaptorAttributeAccessors(op, os);
-
   os << formatv(opClassTemplate, op.getCppClassName(), op.getOperationName(),
                 makeDocStringForOp(op));
 
@@ -1299,6 +1294,12 @@ static void emitOpBindings(const Operator &op, raw_ostream &os) {
   emitAttributeAccessors(op, os);
   emitResultAccessors(op, os);
   emitRegionAccessors(op, os);
+
+  os << formatv(opAdaptorClassTemplate, op.getCppClassName(),
+                op.getOperationName());
+  emitAdaptorOperandAccessors(op, os);
+  emitAdaptorAttributeAccessors(op, os);
+
   emitValueBuilder(op, functionArgs, os);
 }
 



More information about the Mlir-commits mailing list