This is an automated email from the ASF dual-hosted git repository.
spectrometerHBH pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new e0bbe2c102 [FIX][TIRx] Traverse pointer expressions in tile calls
(#20089)
e0bbe2c102 is described below
commit e0bbe2c102e485b6e947be06a3d6cc1431824747
Author: Hongyi Jin <[email protected]>
AuthorDate: Wed Aug 5 19:37:32 2026 -0400
[FIX][TIRx] Traverse pointer expressions in tile calls (#20089)
## Motivation
`TilePrimitiveCall` arguments and config values can contain any
`tvm.ir.Expr`, not only `PrimExpr`. In particular, `T.address_of(...)`
produces a pointer expression.
The Python `StmtVisitor` and `StmtMutator` only traversed values
accepted by `tvm.ir.is_prim_expr`. Pointer-valued operands were
therefore skipped entirely. Analyses could miss buffer uses below the
pointer expression, and rewrites could leave stale buffer references
behind.
## Example
A tile call can pass an mbarrier address through its config:
```python
@T.prim_func
def copy_async(
A: T.Buffer((8,), "float16"),
B: T.Buffer((8,), "float16"),
mbar: T.Buffer((1,), "uint64"),
):
Tx.copy_async(
B[:],
A[:],
dispatch="tma_auto",
mbar=T.address_of(mbar[0]),
)
```
Before this PR, a `StmtExprVisitor` did not reach the `BufferLoad` for
`mbar[0]`, and a `StmtExprMutator` could not replace it. After this PR,
both visitor and mutator traverse the pointer expression normally.
## What changed
- Treat every `tvm.ir.Expr` in `TilePrimitiveCall` arguments and config
values as traversable.
- Add a regression test that verifies both visiting and replacing the
buffer load beneath `T.address_of`.
## Tests
- `python -m pytest tests/python/tirx/transform/test_stmt_functor.py -q`
(20 passed)
- `pre-commit run --files python/tvm/tirx/stmt_functor.py
tests/python/tirx/transform/test_stmt_functor.py`
Ported from
[mlc-ai/tvm@98b7dec](https://github.com/mlc-ai/tvm/commit/98b7decf5a741d9d47210f7d204085d8acba81b3).
---
python/tvm/tirx/stmt_functor.py | 8 ++---
tests/python/tirx/transform/test_stmt_functor.py | 43 ++++++++++++++++++++++++
2 files changed, 47 insertions(+), 4 deletions(-)
diff --git a/python/tvm/tirx/stmt_functor.py b/python/tvm/tirx/stmt_functor.py
index db418f18b6..1f9755b22a 100644
--- a/python/tvm/tirx/stmt_functor.py
+++ b/python/tvm/tirx/stmt_functor.py
@@ -363,14 +363,14 @@ class StmtVisitor(StmtFunctor):
def visit_op_call_(self, op):
"""Visitor implementation for TilePrimitiveCall."""
for arg in op.args:
- if tvm.ir.is_prim_expr(arg):
+ if isinstance(arg, tvm.ir.Expr):
self.visit_expr(arg)
elif isinstance(arg, tvm.tirx.Stmt):
self.visit_stmt(arg)
elif isinstance(arg, tvm.tirx.BufferRegion):
self.visit_buffer_region_(arg)
for value in op.config.values():
- if tvm.ir.is_prim_expr(value):
+ if isinstance(value, tvm.ir.Expr):
self.visit_expr(value)
elif isinstance(value, tvm.tirx.Stmt):
self.visit_stmt(value)
@@ -842,7 +842,7 @@ class StmtMutator(StmtFunctor):
args_changed = False
for arg in op.args:
- if tvm.ir.is_prim_expr(arg):
+ if isinstance(arg, tvm.ir.Expr):
new_arg = self.visit_expr(arg)
elif isinstance(arg, tvm.tirx.Stmt):
new_arg = self.visit_stmt(arg)
@@ -859,7 +859,7 @@ class StmtMutator(StmtFunctor):
new_config = {}
config_changed = False
for key, value in op.config.items():
- if tvm.ir.is_prim_expr(value):
+ if isinstance(value, tvm.ir.Expr):
new_value = self.visit_expr(value)
elif isinstance(value, tvm.tirx.Stmt):
new_value = self.visit_stmt(value)
diff --git a/tests/python/tirx/transform/test_stmt_functor.py
b/tests/python/tirx/transform/test_stmt_functor.py
index ae7e1aa8fa..3da352a18c 100644
--- a/tests/python/tirx/transform/test_stmt_functor.py
+++ b/tests/python/tirx/transform/test_stmt_functor.py
@@ -1183,6 +1183,49 @@ def test_op_call_config_mutated():
)
+def test_op_call_pointer_config_visited_and_mutated():
+ """Pointer-valued config expressions participate in Python traversal."""
+
+ @T.prim_func
+ def copy_async(
+ A: T.Buffer((8,), "float16"),
+ B: T.Buffer((8,), "float16"),
+ mbar: T.Buffer((1,), "uint64"),
+ ):
+ Tx.copy_async(B[:], A[:], dispatch="tma_auto",
mbar=T.address_of(mbar[0]))
+
+ op_call = copy_async.body
+ assert isinstance(op_call, tir.TilePrimitiveCall)
+ mbar_buffer = copy_async.buffer_map[copy_async.params[2]]
+
+ class LoadCollector(StmtExprVisitor):
+ def __init__(self):
+ super().__init__()
+ self.buffers = []
+
+ def visit_buffer_load_(self, op):
+ self.buffers.append(op.buffer)
+ return super().visit_buffer_load_(op)
+
+ collector = LoadCollector()
+ collector.visit_stmt(op_call)
+ assert any(buffer.same_as(mbar_buffer) for buffer in collector.buffers)
+
+ replacement = tir.decl_buffer((1,), "uint64", name="replacement")
+
+ class ReplaceMbarLoad(StmtExprMutator):
+ def visit_buffer_load_(self, op):
+ new_op = super().visit_buffer_load_(op)
+ if op.buffer.same_as(mbar_buffer):
+ return tir.BufferLoad(replacement, new_op.indices,
new_op.predicate)
+ return new_op
+
+ updated = ReplaceMbarLoad().visit_stmt(op_call)
+ mbar_load = updated.config["mbar"].args[0]
+ assert isinstance(mbar_load, tir.BufferLoad)
+ assert mbar_load.buffer.same_as(replacement)
+
+
def test_op_call_nested_config_visited_and_substituted():
"""Nested selector arrays participate in the core visitor and mutator."""
from tvm.tirx.stmt_functor import post_order_visit, substitute