Skip to content

Conversation

@rolfmorel
Copy link
Contributor

@rolfmorel rolfmorel commented Dec 10, 2025

Friendlier wrapper for transform.foreach.

To facilitate that friendliness, makes it so that OpResult.owner returns the relevant OpView instead of Operation. For good measure, also changes Value.owner to return OpView instead of Operation, thereby ensuring consistency. That is, makes it is so that all op-returning .owner accessors return OpView (and thereby give access to all goodies available on registered OpViews.)

Friendlier wrapper and makes it so that OpResult.owner returns the
relevant OpView instead of Operation (Like OpResultList etc).
@llvmbot
Copy link
Member

llvmbot commented Dec 10, 2025

@llvm/pr-subscribers-mlir

Author: Rolf Morel (rolfmorel)

Changes

Friendlier wrapper and makes it so that OpResult.owner returns the relevant OpView instead of Operation (Like OpResultList etc).


Full diff: https://github.com/llvm/llvm-project/pull/171544.diff

4 Files Affected:

  • (modified) mlir/lib/Bindings/Python/IRCore.cpp (+2-2)
  • (modified) mlir/python/mlir/dialects/memref.py (+4-2)
  • (modified) mlir/python/mlir/dialects/transform/init.py (+50)
  • (modified) mlir/test/python/dialects/transform.py (+52)
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 2e0c2b895216f..eed12fe4380bb 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -1519,12 +1519,12 @@ class PyOpResult : public PyConcreteValue<PyOpResult> {
   static void bindDerived(ClassTy &c) {
     c.def_prop_ro(
         "owner",
-        [](PyOpResult &self) -> nb::typed<nb::object, PyOperation> {
+        [](PyOpResult &self) -> nb::typed<nb::object, PyOpView> {
           assert(mlirOperationEqual(self.getParentOperation()->get(),
                                     mlirOpResultGetOwner(self.get())) &&
                  "expected the owner of the value in Python to match that in "
                  "the IR");
-          return self.getParentOperation().getObject();
+          return self.getParentOperation()->createOpView();
         },
         "Returns the operation that produces this result.");
     c.def_prop_ro(
diff --git a/mlir/python/mlir/dialects/memref.py b/mlir/python/mlir/dialects/memref.py
index bc9a3a52728ad..91185b37a5b5f 100644
--- a/mlir/python/mlir/dialects/memref.py
+++ b/mlir/python/mlir/dialects/memref.py
@@ -14,8 +14,10 @@
 def _is_constant_int_like(i):
     return (
         isinstance(i, Value)
-        and isinstance(i.owner, Operation)
-        and isinstance(i.owner.opview, ConstantOp)
+        and (
+            (isinstance(i.owner, Operation) and isinstance(i.owner.opview, ConstantOp))
+            or isinstance(i.owner, ConstantOp)
+        )
         and _is_integer_like_type(i.type)
     )
 
diff --git a/mlir/python/mlir/dialects/transform/__init__.py b/mlir/python/mlir/dialects/transform/__init__.py
index b3dd79c7dbd79..fbe4078782997 100644
--- a/mlir/python/mlir/dialects/transform/__init__.py
+++ b/mlir/python/mlir/dialects/transform/__init__.py
@@ -310,6 +310,8 @@ def __init__(
             sym_visibility=sym_visibility,
             arg_attrs=arg_attrs,
             res_attrs=res_attrs,
+            loc=loc,
+            ip=ip,
         )
         self.regions[0].blocks.append(*input_types)
 
@@ -468,6 +470,54 @@ def apply_registered_pass(
     ).result
 
 
+@_ods_cext.register_operation(_Dialect, replace=True)
+class ForeachOp(ForeachOp):
+    def __init__(
+        self,
+        results: Sequence[Type],
+        targets: Sequence[Union[Operation, Value, OpView]],
+        *,
+        with_zip_shortest: Optional[bool] = False,
+        loc=None,
+        ip=None,
+    ):
+        targets = [_get_op_result_or_value(target) for target in targets]
+        super().__init__(
+            results_=results,
+            targets=targets,
+            with_zip_shortest=with_zip_shortest,
+            loc=loc,
+            ip=ip,
+        )
+        self.regions[0].blocks.append(*[target.type for target in targets])
+
+    @property
+    def body(self) -> Block:
+        return self.regions[0].blocks[0]
+
+    @property
+    def bodyTargets(self) -> BlockArgumentList:
+        return self.regions[0].blocks[0].arguments
+
+
+def foreach(
+    results: Sequence[Type],
+    targets: Sequence[Union[Operation, Value, OpView]],
+    *,
+    with_zip_shortest: Optional[bool] = False,
+    loc=None,
+    ip=None,
+) -> Union[OpResult, OpResultList, ForeachOp]:
+    results = ForeachOp(
+        results=results,
+        targets=targets,
+        with_zip_shortest=with_zip_shortest,
+        loc=loc,
+        ip=ip,
+    ).results
+    return results if len(results) > 1 else (results[0] if len(results) == 1 else op)
+
+
 AnyOpTypeT = NewType("AnyOpType", AnyOpType)
 
 
diff --git a/mlir/test/python/dialects/transform.py b/mlir/test/python/dialects/transform.py
index f58442d04fc66..dfcc890b83ffc 100644
--- a/mlir/test/python/dialects/transform.py
+++ b/mlir/test/python/dialects/transform.py
@@ -401,3 +401,55 @@ def testApplyRegisteredPassOp(module: Module):
             options={"exclude": (symbol_a, symbol_b)},
         )
         transform.YieldOp()
+
+
+# CHECK-LABEL: TEST: testForeachOp
+@run
+def testForeachOp(module: Module):
+    # CHECK: transform.sequence
+    sequence = transform.SequenceOp(
+        transform.FailurePropagationMode.Propagate,
+        [transform.AnyOpType.get()],
+        transform.AnyOpType.get(),
+    )
+    with InsertionPoint(sequence.body):
+        # CHECK: {{.*}} = foreach %{{.*}} : !transform.any_op -> !transform.any_op
+        foreach1 = transform.ForeachOp(
+            (transform.AnyOpType.get(),), (sequence.bodyTarget,)
+        )
+        with InsertionPoint(foreach1.body):
+            # CHECK: transform.yield {{.*}} : !transform.any_op
+            transform.yield_(foreach1.bodyTargets)
+
+        a_val = transform.get_operand(
+            transform.AnyValueType.get(), foreach1.result, [0]
+        )
+        a_param = transform.param_constant(
+            transform.AnyParamType.get(), StringAttr.get("a_param")
+        )
+
+        # CHECK: {{.*}} = foreach %{{.*}}, %{{.*}}, %{{.*}} : !transform.any_op, !transform.any_value, !transform.any_param -> !transform.any_value, !transform.any_param
+        foreach2 = transform.foreach(
+            (transform.AnyValueType.get(), transform.AnyParamType.get()),
+            (sequence.bodyTarget, a_val, a_param),
+        )
+        with InsertionPoint(foreach2.owner.body):
+            # CHECK: transform.yield {{.*}} : !transform.any_value, !transform.any_param
+            transform.yield_(foreach2.owner.bodyTargets[1:3])
+
+        another_param = transform.param_constant(
+            transform.AnyParamType.get(), StringAttr.get("another_param")
+        )
+        params = transform.merge_handles([a_param, another_param])
+
+        # CHECK: {{.*}} = foreach %{{.*}}, %{{.*}}, %{{.*}} with_zip_shortest : !transform.any_op, !transform.any_param, !transform.any_param -> !transform.any_op
+        foreach3 = transform.foreach(
+            (transform.AnyOpType.get(),),
+            (foreach1.result, foreach2[1], params),
+            with_zip_shortest=True,
+        )
+        with InsertionPoint(foreach3.owner.body):
+            # CHECK: transform.yield {{.*}} : !transform.any_op
+            transform.yield_((foreach3.owner.bodyTargets[0],))
+
+        transform.yield_((foreach3,))

@rolfmorel rolfmorel changed the title [MLIR][Transform][Python] Wrapper for transform.foreach [MLIR][Transform][Python] transform.foreach wrapper and .owner OpViews Dec 13, 2025
Copy link
Contributor

@makslevental makslevental left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@rolfmorel rolfmorel merged commit 4cdec92 into llvm:main Dec 14, 2025
10 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Dec 14, 2025

LLVM Buildbot has detected a new failure on builder mlir-nvidia-gcc7 running on mlir-nvidia while building mlir at step 7 "test-build-check-mlir-build-only-check-mlir".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/116/builds/22238

Here is the relevant piece of the build log for the reference
Step 7 (test-build-check-mlir-build-only-check-mlir) failure: test (failure)
******************** TEST 'MLIR :: python/integration/dialects/pdl.py' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/usr/bin/python3.10 /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/python/integration/dialects/pdl.py 2>&1 | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/FileCheck /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/python/integration/dialects/pdl.py
# executed command: /usr/bin/python3.10 /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/python/integration/dialects/pdl.py
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.obj/bin/FileCheck /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/python/integration/dialects/pdl.py
# .---command stderr------------
# | /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/python/integration/dialects/pdl.py:221:10: error: CHECK: expected string not found in input
# | # CHECK: return %arg0 : i32
# |          ^
# | <stdin>:33:44: note: scanning from here
# | TEST: test_pdl_register_function_constraint
# |                                            ^
# | <stdin>:41:2: note: possible intended match here
# |  return %4 : i32
# |  ^
# | 
# | Input file: <stdin>
# | Check file: /vol/worker/mlir-nvidia/mlir-nvidia-gcc7/llvm.src/mlir/test/python/integration/dialects/pdl.py
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |              .
# |              .
# |              .
# |             28:  %2 = "myint.constant"() {value = 5 : i32} : () -> i32 
# |             29:  %3 = "myint.constant"() {value = 8 : i32} : () -> i32 
# |             30: } 
# |             31:  
# |             32:  
# |             33: TEST: test_pdl_register_function_constraint 
# | check:221'0                                                X error: no match found
# |             34: module { 
# | check:221'0     ~~~~~~~~~
# |             35:  func.func @f(%arg0: i32) -> i32 { 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             36:  %0 = "myint.constant"() {value = 1 : i64} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             37:  %1 = "myint.constant"() {value = -1 : i64} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             38:  %2 = "myint.constant"() {value = 0 : i32} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             39:  %3 = "myint.add"(%2, %arg0) : (i32, i32) -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             40:  %4 = "myint.add"(%3, %2) : (i32, i32) -> i32 
...

@llvm-ci
Copy link
Collaborator

llvm-ci commented Dec 14, 2025

LLVM Buildbot has detected a new failure on builder mlir-rocm-mi200 running on mi200-buildbot while building mlir at step 7 "test-build-check-mlir-build-only-check-mlir".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/177/builds/25891

Here is the relevant piece of the build log for the reference
Step 7 (test-build-check-mlir-build-only-check-mlir) failure: test (failure)
******************** TEST 'MLIR :: python/integration/dialects/pdl.py' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/usr/bin/python3.10 /vol/worker/mi200-buildbot/mlir-rocm-mi200/llvm-project/mlir/test/python/integration/dialects/pdl.py 2>&1 | /vol/worker/mi200-buildbot/mlir-rocm-mi200/build/bin/FileCheck /vol/worker/mi200-buildbot/mlir-rocm-mi200/llvm-project/mlir/test/python/integration/dialects/pdl.py
# executed command: /usr/bin/python3.10 /vol/worker/mi200-buildbot/mlir-rocm-mi200/llvm-project/mlir/test/python/integration/dialects/pdl.py
# executed command: /vol/worker/mi200-buildbot/mlir-rocm-mi200/build/bin/FileCheck /vol/worker/mi200-buildbot/mlir-rocm-mi200/llvm-project/mlir/test/python/integration/dialects/pdl.py
# .---command stderr------------
# | /vol/worker/mi200-buildbot/mlir-rocm-mi200/llvm-project/mlir/test/python/integration/dialects/pdl.py:221:10: error: CHECK: expected string not found in input
# | # CHECK: return %arg0 : i32
# |          ^
# | <stdin>:33:44: note: scanning from here
# | TEST: test_pdl_register_function_constraint
# |                                            ^
# | <stdin>:41:2: note: possible intended match here
# |  return %4 : i32
# |  ^
# | 
# | Input file: <stdin>
# | Check file: /vol/worker/mi200-buildbot/mlir-rocm-mi200/llvm-project/mlir/test/python/integration/dialects/pdl.py
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |              .
# |              .
# |              .
# |             28:  %2 = "myint.constant"() {value = 5 : i32} : () -> i32 
# |             29:  %3 = "myint.constant"() {value = 8 : i32} : () -> i32 
# |             30: } 
# |             31:  
# |             32:  
# |             33: TEST: test_pdl_register_function_constraint 
# | check:221'0                                                X error: no match found
# |             34: module { 
# | check:221'0     ~~~~~~~~~
# |             35:  func.func @f(%arg0: i32) -> i32 { 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             36:  %0 = "myint.constant"() {value = 1 : i64} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             37:  %1 = "myint.constant"() {value = -1 : i64} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             38:  %2 = "myint.constant"() {value = 0 : i32} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             39:  %3 = "myint.add"(%2, %arg0) : (i32, i32) -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             40:  %4 = "myint.add"(%3, %2) : (i32, i32) -> i32 
...

@llvm-ci
Copy link
Collaborator

llvm-ci commented Dec 14, 2025

LLVM Buildbot has detected a new failure on builder mlir-nvidia running on mlir-nvidia while building mlir at step 7 "test-build-check-mlir-build-only-check-mlir".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/138/builds/23132

Here is the relevant piece of the build log for the reference
Step 7 (test-build-check-mlir-build-only-check-mlir) failure: test (failure)
******************** TEST 'MLIR :: python/integration/dialects/pdl.py' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/usr/bin/python3.10 /vol/worker/mlir-nvidia/mlir-nvidia/llvm.src/mlir/test/python/integration/dialects/pdl.py 2>&1 | /vol/worker/mlir-nvidia/mlir-nvidia/llvm.obj/bin/FileCheck /vol/worker/mlir-nvidia/mlir-nvidia/llvm.src/mlir/test/python/integration/dialects/pdl.py
# executed command: /usr/bin/python3.10 /vol/worker/mlir-nvidia/mlir-nvidia/llvm.src/mlir/test/python/integration/dialects/pdl.py
# executed command: /vol/worker/mlir-nvidia/mlir-nvidia/llvm.obj/bin/FileCheck /vol/worker/mlir-nvidia/mlir-nvidia/llvm.src/mlir/test/python/integration/dialects/pdl.py
# .---command stderr------------
# | /vol/worker/mlir-nvidia/mlir-nvidia/llvm.src/mlir/test/python/integration/dialects/pdl.py:221:10: error: CHECK: expected string not found in input
# | # CHECK: return %arg0 : i32
# |          ^
# | <stdin>:33:44: note: scanning from here
# | TEST: test_pdl_register_function_constraint
# |                                            ^
# | <stdin>:41:2: note: possible intended match here
# |  return %4 : i32
# |  ^
# | 
# | Input file: <stdin>
# | Check file: /vol/worker/mlir-nvidia/mlir-nvidia/llvm.src/mlir/test/python/integration/dialects/pdl.py
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |              .
# |              .
# |              .
# |             28:  %2 = "myint.constant"() {value = 5 : i32} : () -> i32 
# |             29:  %3 = "myint.constant"() {value = 8 : i32} : () -> i32 
# |             30: } 
# |             31:  
# |             32:  
# |             33: TEST: test_pdl_register_function_constraint 
# | check:221'0                                                X error: no match found
# |             34: module { 
# | check:221'0     ~~~~~~~~~
# |             35:  func.func @f(%arg0: i32) -> i32 { 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             36:  %0 = "myint.constant"() {value = 1 : i64} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             37:  %1 = "myint.constant"() {value = -1 : i64} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             38:  %2 = "myint.constant"() {value = 0 : i32} : () -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             39:  %3 = "myint.add"(%2, %arg0) : (i32, i32) -> i32 
# | check:221'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             40:  %4 = "myint.add"(%3, %2) : (i32, i32) -> i32 
...

@llvm-ci
Copy link
Collaborator

llvm-ci commented Dec 14, 2025

LLVM Buildbot has detected a new failure on builder ppc64le-mlir-rhel-clang running on ppc64le-mlir-rhel-test while building mlir at step 3 "clean-build-dir".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/129/builds/34985

Here is the relevant piece of the build log for the reference
Step 3 (clean-build-dir) failure: Delete failed. (failure) (timed out)
Step 4 (cmake-configure) failure: cmake (failure) (timed out)
-- The C compiler identification is Clang 19.1.7
command timed out: 1200 seconds without output running [b'cmake', b'-DLLVM_TARGETS_TO_BUILD=PowerPC', b'-DLLVM_INSTALL_UTILS=ON', b'-DCMAKE_CXX_STANDARD=17', b'-DLLVM_ENABLE_PROJECTS=mlir', b'-DLLVM_LIT_ARGS=-vj 256', b'-DCMAKE_C_COMPILER_LAUNCHER=ccache', b'-DCMAKE_CXX_COMPILER_LAUNCHER=ccache', b'-DCMAKE_BUILD_TYPE=Release', b'-DLLVM_ENABLE_ASSERTIONS=ON', b'-GNinja', b'../llvm-project/llvm'], attempting to kill
process killed by signal 9
program finished with exit code -1
elapsedTime=1409.497886

@joker-eph
Copy link
Collaborator

Failures seem legit, reverting in #172225 for now to unbreak CI, feel free to reland with a fix!

joker-eph added a commit that referenced this pull request Dec 14, 2025
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Dec 14, 2025
rolfmorel added a commit that referenced this pull request Dec 14, 2025
#172228)

Friendlier wrapper for transform.foreach.

To facilitate that friendliness, makes it so that OpResult.owner returns
the relevant OpView instead of Operation. For good measure, also changes
Value.owner to return OpView instead of Operation, thereby ensuring
consistency. That is, makes it is so that all op-returning .owner
accessors return OpView (and thereby give access to all goodies
available on registered OpViews.)

Reland of #171544 due to fixup for integration test.
anonymouspc pushed a commit to anonymouspc/llvm that referenced this pull request Dec 15, 2025
llvm#171544)

Friendlier wrapper for `transform.foreach`.

To facilitate that friendliness, makes it so that `OpResult.owner`
returns the relevant `OpView` instead of `Operation`. For good measure,
also changes `Value.owner` to return `OpView` instead of `Operation`,
thereby ensuring consistency. That is, makes it is so that all
op-returning `.owner` accessors return `OpView` (and thereby give access
to all goodies available on registered `OpView`s.)
anonymouspc pushed a commit to anonymouspc/llvm that referenced this pull request Dec 15, 2025
anonymouspc pushed a commit to anonymouspc/llvm that referenced this pull request Dec 15, 2025
llvm#172228)

Friendlier wrapper for transform.foreach.

To facilitate that friendliness, makes it so that OpResult.owner returns
the relevant OpView instead of Operation. For good measure, also changes
Value.owner to return OpView instead of Operation, thereby ensuring
consistency. That is, makes it is so that all op-returning .owner
accessors return OpView (and thereby give access to all goodies
available on registered OpViews.)

Reland of llvm#171544 due to fixup for integration test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mlir:python MLIR Python bindings mlir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants