[Mlir-commits] [mlir] [MLIR][Python] Add python-side adaptor class codegen in mlir-tblgen (PR #176640)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Mon Jan 19 07:23:54 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/3] [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/3] 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/3] 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));
More information about the Mlir-commits
mailing list