This is an automated email from the ASF dual-hosted git repository.

tlopex 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 7eaa8eb899 [Fix][Relax] Preserve match-cast storage liveness (#20220)
7eaa8eb899 is described below

commit 7eaa8eb89901d510ffebbefb613294bfd56be66d
Author: Zupeng Wang <[email protected]>
AuthorDate: Sun Aug 30 11:03:08 2026 +0800

    [Fix][Relax] Preserve match-cast storage liveness (#20220)
    
    Fixes #20190.
    Fixes #20207.
    
    `StaticPlanBlockMemory` propagates storage tokens through ordinary
    variable
    bindings and lowered reshape/view operations, but not through
    `MatchCast`.
    A `MatchCast` refines type information without allocating or copying
    storage,
    so losing the token can make the planner release and reuse the source
    allocation
    while the alias is still live. This causes silent wrong-code in the
    default
    Relax pipeline.
    
    This change propagates storage tokens through `MatchCast` bindings and
    adds a
    regression test showing that the aliased allocation remains live until
    its
    downstream use.
    
    Tests:
    
    - `python -m pytest -q
    tests/python/relax/test_transform_static_plan_block_memory.py`
      (`29 passed`)
    - `pre-commit run --files
    src/relax/transform/static_plan_block_memory.cc
    tests/python/relax/test_transform_static_plan_block_memory.py`
    - End-to-end compiled-VM reproducers for #20190 and #20207, comparing
    NumPy
      references before and after the patch
    
    Signed-off-by: Zupeng Wang <[email protected]>
---
 src/relax/transform/static_plan_block_memory.cc    |  6 ++++
 .../test_transform_static_plan_block_memory.py     | 40 ++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/src/relax/transform/static_plan_block_memory.cc 
b/src/relax/transform/static_plan_block_memory.cc
index b4782c7ca4..ffb0851f83 100644
--- a/src/relax/transform/static_plan_block_memory.cc
+++ b/src/relax/transform/static_plan_block_memory.cc
@@ -361,6 +361,12 @@ class StorageAllocatorBaseVisitor : public ExprVisitor {
     SetTokens(binding->var.get(), token_map_[binding->value.get()]);
   }
 
+  void VisitBinding_(const MatchCastNode* binding) override {
+    ExprVisitor::VisitBinding_(binding);
+    // MatchCast refines the type without changing the underlying storage.
+    SetTokens(binding->var.get(), token_map_[binding->value.get()]);
+  }
+
   void VisitBindingBlock_(const DataflowBlockNode* block) override {
     // We maintain a block stack for token allocation-site and use-site check.
     block_stack_.push_back(block);
diff --git a/tests/python/relax/test_transform_static_plan_block_memory.py 
b/tests/python/relax/test_transform_static_plan_block_memory.py
index 2bcf1adf49..a7583eb5bd 100644
--- a/tests/python/relax/test_transform_static_plan_block_memory.py
+++ b/tests/python/relax/test_transform_static_plan_block_memory.py
@@ -1735,6 +1735,46 @@ def test_view():
     tvm.ir.assert_structural_equal(after, Expected)
 
 
+def test_match_cast_preserves_storage_liveness():
+    @I.ir_module
+    class Before:
+        @T.prim_func(s_tir=True)
+        def copy(A: T.Buffer((16,), "float32"), B: T.Buffer((16,), "float32")):
+            T.evaluate(0)
+
+        @T.prim_func(s_tir=True)
+        def add(
+            A: T.Buffer((16,), "float32"),
+            B: T.Buffer((16,), "float32"),
+            C: T.Buffer((16,), "float32"),
+        ):
+            T.evaluate(0)
+
+        @R.function
+        def main(x: R.Tensor((16,), "float32")) -> R.Tensor((16,), "float32"):
+            R.func_attr({"relax.force_pure": True})
+            cls = Before
+            alloc = R.builtin.alloc_tensor(R.shape([16]), "float32", 0)
+            cls.copy(x, alloc)
+            checked = R.match_cast(alloc, R.Tensor((16,), "float32"))
+            alloc1 = R.builtin.alloc_tensor(R.shape([16]), "float32", 0)
+            cls.copy(x, alloc1)
+            alloc2 = R.builtin.alloc_tensor(R.shape([16]), "float32", 0)
+            cls.add(checked, alloc1, alloc2)
+            return alloc2
+
+    after = relax.transform.StaticPlanBlockMemory()(Before)
+    alloc_storage_op = tvm.ir.Op.get("relax.memory.alloc_storage")
+    storage_allocations = []
+
+    def collect_storage_allocations(expr):
+        if isinstance(expr, relax.Call) and expr.op.same_as(alloc_storage_op):
+            storage_allocations.append(expr)
+
+    relax.analysis.post_order_visit(after["main"], collect_storage_allocations)
+    assert len(storage_allocations) == 2
+
+
 def test_builtin_reshape_preserves_storage_liveness():
     @I.ir_module
     class Before:

Reply via email to