[Mlir-commits] [mlir] [MLIR] Add HasAncestor / AncestorOneOf op traits (PR #197904)
Berke Ates
llvmlistbot at llvm.org
Fri May 15 03:42:20 PDT 2026
https://github.com/Berke-Ates created https://github.com/llvm/llvm-project/pull/197904
Added new structural op traits `HasAncestor<op>` / `AncestorOneOf<ops>`, analogous to `HasParent` / `ParentOneOf` but walking the full parent chain instead of just checking the immediate parent.
Adopted on two ops whose `verify()` was just an ancestor-existence check:
- `omp.workshare.loop_wrapper`
- `emitc.get_field`
>From 4eaec3f5e498cccf9c6da6bcf31ae700bd58c554 Mon Sep 17 00:00:00 2001
From: Berke-Ates <berke at ates.ch>
Date: Fri, 15 May 2026 11:47:26 +0200
Subject: [PATCH 1/3] [MLIR][Traits] Add HasAncestor and AncestorOneOf op
traits
Introduces HasAncestor and AncestorOneOf as structural operation traits
that verify an op has at least one ancestor of the given type(s), walking
the full parent chain. These complement the existing HasParent/ParentOneOf
traits, which only check the immediate parent.
---
mlir/include/mlir/IR/OpBase.td | 9 ++++
mlir/include/mlir/IR/OpDefinition.h | 30 +++++++++++
mlir/test/IR/traits.mlir | 77 +++++++++++++++++++++++++++
mlir/test/lib/Dialect/Test/TestOps.td | 16 ++++++
4 files changed, 132 insertions(+)
diff --git a/mlir/include/mlir/IR/OpBase.td b/mlir/include/mlir/IR/OpBase.td
index 7f36e6c74c7f7..77b6b0ae849d0 100644
--- a/mlir/include/mlir/IR/OpBase.td
+++ b/mlir/include/mlir/IR/OpBase.td
@@ -141,6 +141,15 @@ class ParentOneOf<list<string> ops>
: ParamNativeOpTrait<"HasParent", !interleave(ops, ", ")>,
StructuralOpTrait;
+// Op has at least one ancestor (transitive parent) of the provided type.
+class HasAncestor<string op>
+ : ParamNativeOpTrait<"HasAncestor", op>, StructuralOpTrait;
+
+// Op has at least one ancestor of one of the provided types.
+class AncestorOneOf<list<string> ops>
+ : ParamNativeOpTrait<"HasAncestor", !interleave(ops, ", ")>,
+ StructuralOpTrait;
+
// Op result type is derived from the first attribute. If the attribute is an
// subclass of `TypeAttrBase`, its value is used, otherwise, the type of the
// attribute content is used.
diff --git a/mlir/include/mlir/IR/OpDefinition.h b/mlir/include/mlir/IR/OpDefinition.h
index be92fe0a6c7e3..3612535df3160 100644
--- a/mlir/include/mlir/IR/OpDefinition.h
+++ b/mlir/include/mlir/IR/OpDefinition.h
@@ -1323,6 +1323,36 @@ struct HasParent {
};
};
+/// This class provides a verifier for ops that are expecting at least one
+/// transitive parent (i.e. some ancestor) to be one of the given parent ops.
+/// The check matches the immediate parent as well as any deeper ancestor.
+template <typename... ParentOpTypes>
+struct HasAncestor {
+ template <typename ConcreteType>
+ class Impl : public TraitBase<ConcreteType, Impl> {
+ public:
+ static LogicalResult verifyTrait(Operation *op) {
+ Operation *cur = op->getParentOp();
+ while (cur) {
+ if (llvm::isa<ParentOpTypes...>(cur))
+ return success();
+ cur = cur->getParentOp();
+ }
+ return op->emitOpError()
+ << "expects ancestor op "
+ << (sizeof...(ParentOpTypes) != 1 ? "to be one of '" : "'")
+ << llvm::ArrayRef({ParentOpTypes::getOperationName()...}) << "'";
+ }
+
+ template <typename ParentOpType =
+ std::tuple_element_t<0, std::tuple<ParentOpTypes...>>>
+ std::enable_if_t<sizeof...(ParentOpTypes) == 1, ParentOpType>
+ getAncestorOp() {
+ return this->getOperation()->template getParentOfType<ParentOpType>();
+ }
+ };
+};
+
/// A trait for operations that have an attribute specifying operand segments.
///
/// Certain operations can have multiple variadic operands and their size
diff --git a/mlir/test/IR/traits.mlir b/mlir/test/IR/traits.mlir
index 49cfd7e496746..ed0f9630f1799 100644
--- a/mlir/test/IR/traits.mlir
+++ b/mlir/test/IR/traits.mlir
@@ -303,6 +303,83 @@ func.func @failedParentOneOf_wrong_parent1() {
}) : () -> ()
}
+// -----
+
+// CHECK: succeededHasAncestor_direct_child
+func.func @succeededHasAncestor_direct_child() {
+ "test.ancestor"() ({
+ "test.descendant"() : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ return
+}
+
+// -----
+
+// CHECK: succeededHasAncestor_deeply_nested
+func.func @succeededHasAncestor_deeply_nested() {
+ "test.ancestor"() ({
+ "test.intermediate"() ({
+ "test.intermediate"() ({
+ "test.descendant"() : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ return
+}
+
+// -----
+
+func.func @failedHasAncestor_no_matching_ancestor() {
+ "test.intermediate"() ({
+ // expected-error at +1 {{'test.descendant' op expects ancestor op 'test.ancestor'}}
+ "test.descendant"() : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ return
+}
+
+// -----
+
+// CHECK: succeededAncestorOneOf_first_type
+func.func @succeededAncestorOneOf_first_type() {
+ "test.ancestor"() ({
+ "test.intermediate"() ({
+ "test.descendant_with_ancestor_one_of"() : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ return
+}
+
+// -----
+
+// CHECK: succeededAncestorOneOf_second_type
+func.func @succeededAncestorOneOf_second_type() {
+ "test.ancestor1"() ({
+ "test.intermediate"() ({
+ "test.descendant_with_ancestor_one_of"() : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ return
+}
+
+// -----
+
+func.func @failedAncestorOneOf_no_matching_ancestor() {
+ "test.intermediate"() ({
+ // expected-error at +1 {{'test.descendant_with_ancestor_one_of' op expects ancestor op to be one of 'test.ancestor, test.ancestor1'}}
+ "test.descendant_with_ancestor_one_of"() : () -> ()
+ "test.finish"() : () -> ()
+ }) : () -> ()
+ return
+}
// -----
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index 02bac016eeed1..75b3b9a05ac4e 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -876,6 +876,22 @@ def ParentOp1 : TEST_Op<"parent1"> {
def ChildWithParentOneOf : TEST_Op<"child_with_parent_one_of",
[ParentOneOf<["ParentOp", "ParentOp1"]>]>;
+// HasAncestor trait
+def AncestorOp : TEST_Op<"ancestor"> {
+ let regions = (region AnyRegion);
+}
+def AncestorOp1 : TEST_Op<"ancestor1"> {
+ let regions = (region AnyRegion);
+}
+def IntermediateOp : TEST_Op<"intermediate"> {
+ let regions = (region AnyRegion);
+}
+def DescendantOp : TEST_Op<"descendant", [HasAncestor<"AncestorOp">]>;
+
+// AncestorOneOf trait
+def DescendantWithAncestorOneOf : TEST_Op<"descendant_with_ancestor_one_of",
+ [AncestorOneOf<["AncestorOp", "AncestorOp1"]>]>;
+
def TerminatorOp : TEST_Op<"finish", [Terminator]>;
def SingleBlockImplicitTerminatorOp : TEST_Op<"SingleBlockImplicitTerminator",
[SingleBlockImplicitTerminator<"TerminatorOp">]> {
>From 20e8ec3cdc35f0d1b9f0c0586f259b52c2619e25 Mon Sep 17 00:00:00 2001
From: Berke-Ates <berke at ates.ch>
Date: Fri, 15 May 2026 12:04:27 +0200
Subject: [PATCH 2/3] [MLIR][OpenMP] Use HasAncestor trait on
omp.workshare.loop_wrapper
Replace the hand-written verify() with HasAncestor<"WorkshareOp">.
Update invalid.mlir to match the new diagnostic wording.
---
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 5 ++---
mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 6 ------
mlir/test/Dialect/OpenMP/invalid.mlir | 2 +-
3 files changed, 3 insertions(+), 10 deletions(-)
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 88c8ab4f6f949..8f35d80e2ff55 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -604,8 +604,8 @@ def WorkshareOp : OpenMP_Op<"workshare", traits = [
}
def WorkshareLoopWrapperOp : OpenMP_Op<"workshare.loop_wrapper", traits = [
- DeclareOpInterfaceMethods<LoopWrapperInterface>, NoTerminator,
- RecursiveMemoryEffects, SingleBlock
+ DeclareOpInterfaceMethods<LoopWrapperInterface>, HasAncestor<"WorkshareOp">,
+ NoTerminator, RecursiveMemoryEffects, SingleBlock
], singleRegion = true> {
let summary = "contains loop nests to be parallelized by workshare";
let description = [{
@@ -617,7 +617,6 @@ def WorkshareLoopWrapperOp : OpenMP_Op<"workshare.loop_wrapper", traits = [
OpBuilder<(ins), [{ build($_builder, $_state, {}); }]>
];
let assemblyFormat = "$region attr-dict";
- let hasVerifier = 1;
let hasRegionVerifier = 1;
}
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index f388b7ccb591b..447b900638a47 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -2818,12 +2818,6 @@ void WorkshareOp::build(OpBuilder &builder, OperationState &state,
// WorkshareLoopWrapperOp
//===----------------------------------------------------------------------===//
-LogicalResult WorkshareLoopWrapperOp::verify() {
- if (!(*this)->getParentOfType<WorkshareOp>())
- return emitOpError() << "must be nested in an omp.workshare";
- return success();
-}
-
LogicalResult WorkshareLoopWrapperOp::verifyRegions() {
if (isa_and_nonnull<LoopWrapperInterface>((*this)->getParentOp()) ||
getNestedWrapper())
diff --git a/mlir/test/Dialect/OpenMP/invalid.mlir b/mlir/test/Dialect/OpenMP/invalid.mlir
index bc508d66fbd5f..31a6b0f1c7dca 100644
--- a/mlir/test/Dialect/OpenMP/invalid.mlir
+++ b/mlir/test/Dialect/OpenMP/invalid.mlir
@@ -3008,7 +3008,7 @@ func.func @not_wrapper() {
// -----
func.func @missing_workshare(%idx : index) {
- // expected-error @below {{must be nested in an omp.workshare}}
+ // expected-error @below {{'omp.workshare.loop_wrapper' op expects ancestor op 'omp.workshare'}}
omp.workshare.loop_wrapper {
omp.loop_nest (%iv) : index = (%idx) to (%idx) step (%idx) {
omp.yield
>From feb6ea265b70880241f8c75305c29451db92d266 Mon Sep 17 00:00:00 2001
From: Berke-Ates <berke at ates.ch>
Date: Fri, 15 May 2026 12:21:14 +0200
Subject: [PATCH 3/3] [MLIR][EmitC] Use HasAncestor trait on emitc.get_field
Replaces the hand-written verify() check (nested-within-emitc.class)
with HasAncestor<"ClassOp">. Updates expected-error in invalid_ops.mlir
to match the new diagnostic wording. emitc.field is left alone.
---
mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 4 ++--
mlir/lib/Dialect/EmitC/IR/EmitC.cpp | 8 --------
mlir/test/Dialect/EmitC/invalid_ops.mlir | 4 ++--
3 files changed, 4 insertions(+), 12 deletions(-)
diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 7d9bb8907eb8b..aa8ce7e75bfd6 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -1761,7 +1761,8 @@ def EmitC_FieldOp : EmitC_Op<"field", [Symbol]> {
}
def EmitC_GetFieldOp
- : EmitC_Op<"get_field", [Pure, DeclareOpInterfaceMethods<
+ : EmitC_Op<"get_field", [Pure, HasAncestor<"ClassOp">,
+ DeclareOpInterfaceMethods<
SymbolUserOpInterface>]> {
let summary = "Obtain access to a field within a class instance";
let description = [{
@@ -1778,7 +1779,6 @@ def EmitC_GetFieldOp
let arguments = (ins FlatSymbolRefAttr:$field_name);
let results = (outs EmitCType:$result);
let assemblyFormat = "$field_name `:` type($result) attr-dict";
- let hasVerifier = 1;
}
def EmitC_DoOp : EmitC_Op<"do",
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 6979f34c1e047..29f1055ff0b57 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -1666,14 +1666,6 @@ LogicalResult FieldOp::verify() {
// GetFieldOp
//===----------------------------------------------------------------------===//
-LogicalResult GetFieldOp::verify() {
- auto parentClassOp = getOperation()->getParentOfType<emitc::ClassOp>();
- if (!parentClassOp.getOperation())
- return emitOpError(" must be nested within an emitc.class operation");
-
- return success();
-}
-
LogicalResult GetFieldOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
mlir::FlatSymbolRefAttr fieldNameAttr = getFieldNameAttr();
FieldOp fieldOp =
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index 0d878e90cdf0c..331f282294bae 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -718,14 +718,14 @@ emitc.field @testField : !emitc.array<1xf32>
// -----
-// expected-error @+1 {{'emitc.get_field' op must be nested within an emitc.class operation}}
+// expected-error @+1 {{'emitc.get_field' op expects ancestor op 'emitc.class'}}
%1 = emitc.get_field @testField : !emitc.array<1xf32>
// -----
emitc.func @testMethod() {
%0 = "emitc.constant"() <{value = 0 : index}> : () -> !emitc.size_t
- // expected-error @+1 {{'emitc.get_field' op must be nested within an emitc.class operation}}
+ // expected-error @+1 {{'emitc.get_field' op expects ancestor op 'emitc.class'}}
%1 = get_field @testField : !emitc.array<1xf32>
%2 = subscript %1[%0] : (!emitc.array<1xf32>, !emitc.size_t) -> !emitc.lvalue<f32>
return
More information about the Mlir-commits
mailing list