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 40c2f54908 [TIR] Ignore None-valued pragma annotations (#20265)
40c2f54908 is described below

commit 40c2f54908e96875fa19e92db83f123854d97991
Author: T1Gang <[email protected]>
AuthorDate: Wed Sep 9 11:46:27 2026 +0800

    [TIR] Ignore None-valued pragma annotations (#20265)
    
    Fixes #18393.
    
    None-valued pragma annotations represent an unspecified pragma and
    should not be lowered into `AttrStmt` nodes with undefined `PrimExpr`
    values.
    
    Previously, `pragma_unroll_explicit=None` was lowered into an `AttrStmt`
    with an undefined value. During `FlattenBuffer`, `StmtExprMutator`
    attempted to visit that value and triggered a segmentation fault.
    
    This patch skips `None`-valued pragma annotations during opaque lowering
    while preserving the existing behavior for valid pragma values.
    
    For `pragma_unroll`, the existing upstream behavior is preserved:
    non-null values remain attached to the loop, while null values are
    ignored.
    
    Tests:
    - `tests/python/tirx-transform/test_tir_transform_flatten_buffer.py`: 11
    passed
    -
    `tests/python/s_tir/transform/test_s_tir_transform_lower_opaque_block.py`:
    13 passed
    - `git diff --check upstream/main..HEAD`: passed
---
 src/s_tir/transform/lower_opaque_block.cc          |  8 +++---
 src/tirx/transform/lower_tirx_opaque.cc            | 14 ++++++----
 tests/python/relax/test_frontend_onnx.py           |  7 ++---
 .../test_s_tir_transform_lower_opaque_block.py     | 20 ++++++++++++++
 .../test_tir_transform_flatten_buffer.py           | 20 ++++++++++++++
 .../tirx/transform/test_transform_lower_tirx.py    | 31 ++++++++++++++++++++++
 6 files changed, 87 insertions(+), 13 deletions(-)

diff --git a/src/s_tir/transform/lower_opaque_block.cc 
b/src/s_tir/transform/lower_opaque_block.cc
index 586eb727b7..d3dcf2c3a9 100644
--- a/src/s_tir/transform/lower_opaque_block.cc
+++ b/src/s_tir/transform/lower_opaque_block.cc
@@ -158,9 +158,7 @@ class OpaqueBlockLower : public StmtExprMutator {
 
   /*! \brief Convert attr value from annotation map into PrimExpr. */
   PrimExpr ConvertAttrValue(const ffi::String& key, const Any& obj) {
-    if (obj == nullptr) {
-      return PrimExpr();
-    } else if (auto expr = obj.try_cast<PrimExpr>()) {
+    if (auto expr = obj.try_cast<PrimExpr>()) {
       return expr.value();
     } else if (auto str = obj.try_cast<ffi::String>()) {
       return std::move(StringImm(str.value()));
@@ -187,6 +185,10 @@ class OpaqueBlockLower : public StmtExprMutator {
     for (const auto& kv : annotations) {
       const ffi::String& key = kv.first;
       if (tirx::attr::IsPragmaKey(key)) {
+        if (kv.second == nullptr) {
+          continue;
+        }
+
         pragma_attrs->emplace_back(key, ConvertAttrValue(key, kv.second));
       } else if (!is_block) {
         // the loop annotation is preserved
diff --git a/src/tirx/transform/lower_tirx_opaque.cc 
b/src/tirx/transform/lower_tirx_opaque.cc
index 7c6ba2b2c9..99fcd7793e 100644
--- a/src/tirx/transform/lower_tirx_opaque.cc
+++ b/src/tirx/transform/lower_tirx_opaque.cc
@@ -120,9 +120,7 @@ class TIRxOpaqueLower : public StmtExprMutator {
 
   /*! \brief Convert attr value from annotation map into PrimExpr. */
   PrimExpr ConvertAttrValue(const ffi::String& key, const Any& obj) {
-    if (obj == nullptr) {
-      return PrimExpr();
-    } else if (auto expr = obj.try_cast<PrimExpr>()) {
+    if (auto expr = obj.try_cast<PrimExpr>()) {
       return expr.value();
     } else if (auto str = obj.try_cast<ffi::String>()) {
       return std::move(prim::StringImm(str.value()));
@@ -149,11 +147,17 @@ class TIRxOpaqueLower : public StmtExprMutator {
     for (const auto& kv : annotations) {
       const ffi::String& key = kv.first;
       if (key == "pragma_unroll") {
-        preserved_annotations.Set(key, kv.second);
+        if (kv.second != nullptr) {
+          preserved_annotations.Set(key, kv.second);
+        }
       } else if (tirx::attr::IsPragmaKey(key)) {
+        if (kv.second == nullptr) {
+          continue;
+        }
+
         pragma_attrs->emplace_back(key, ConvertAttrValue(key, kv.second));
       } else {
-        // loop annotations are always preserved (no SBlock annotation 
dropping here)
+        // Loop annotations are always preserved
         preserved_annotations.Set(key, kv.second);
       }
     }
diff --git a/tests/python/relax/test_frontend_onnx.py 
b/tests/python/relax/test_frontend_onnx.py
index 335694ed76..e2de5e02a3 100644
--- a/tests/python/relax/test_frontend_onnx.py
+++ b/tests/python/relax/test_frontend_onnx.py
@@ -1230,7 +1230,7 @@ def test_multi_input_unknown_static_shape(op_name, 
num_inputs):
         "Slice", ["x", "starts", "ends", "axes"], ["sliced"], name="slice0"
     )
     other_names = [f"y{i}" for i in range(num_inputs - 1)]
-    op_node = helper.make_node(op_name, ["sliced"] + other_names, ["output"], 
name="op0")
+    op_node = helper.make_node(op_name, ["sliced", *other_names], ["output"], 
name="op0")
 
     graph = helper.make_graph(
         [slice_node, op_node],
@@ -1241,10 +1241,7 @@ def test_multi_input_unknown_static_shape(op_name, 
num_inputs):
             helper.make_tensor_value_info("ends", TensorProto.INT64, [1]),
             helper.make_tensor_value_info("axes", TensorProto.INT64, [1]),
         ]
-        + [
-            helper.make_tensor_value_info(name, TensorProto.FLOAT, [2, 3])
-            for name in other_names
-        ],
+        + [helper.make_tensor_value_info(name, TensorProto.FLOAT, [2, 3]) for 
name in other_names],
         outputs=[helper.make_tensor_value_info("output", TensorProto.FLOAT, 
[2, 3])],
     )
     model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 
13)])
diff --git 
a/tests/python/s_tir/transform/test_s_tir_transform_lower_opaque_block.py 
b/tests/python/s_tir/transform/test_s_tir_transform_lower_opaque_block.py
index 441074128e..273fa0bdc5 100644
--- a/tests/python/s_tir/transform/test_s_tir_transform_lower_opaque_block.py
+++ b/tests/python/s_tir/transform/test_s_tir_transform_lower_opaque_block.py
@@ -421,6 +421,26 @@ def test_preserved_annotations():
     tvm.ir.assert_structural_equal(mod["main"], 
after.with_attr("global_symbol", "main"))
 
 
+def test_none_pragma_annotation():
+    @T.prim_func(s_tir=True)
+    def before(A: T.Buffer(8, "float32"), B: T.Buffer(8, "float32")):
+        for i in T.serial(8, annotations={"pragma_unroll_explicit": None}):
+            with T.sblock("block"):
+                B[i] = A[i] + 1.0
+
+    @T.prim_func(s_tir=True)
+    def after(A: T.Buffer(8, "float32"), B: T.Buffer(8, "float32")):
+        for i in T.serial(8):
+            B[i] = A[i] + 1.0
+
+    mod = tvm.IRModule.from_expr(before.with_attr("global_symbol", "main"))
+    mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
+    tvm.ir.assert_structural_equal(
+        mod["main"],
+        after.with_attr("global_symbol", "main"),
+    )
+
+
 def test_boolean_handling():
     _check(boolean_handling_before, boolean_handling_after)
 
diff --git a/tests/python/tirx-transform/test_tir_transform_flatten_buffer.py 
b/tests/python/tirx-transform/test_tir_transform_flatten_buffer.py
index d01400d3a5..7c2943879c 100644
--- a/tests/python/tirx-transform/test_tir_transform_flatten_buffer.py
+++ b/tests/python/tirx-transform/test_tir_transform_flatten_buffer.py
@@ -368,5 +368,25 @@ def test_flatten_inside_block():
     tvm.ir.assert_structural_equal(After, Expected)
 
 
+def test_build_with_optional_pragma_unroll_explicit():
+    def check(value):
+        @I.ir_module(s_tir=True)
+        class Module:
+            @T.prim_func(s_tir=True)
+            def main(A: T.Buffer((4, 5, 6), "int16"), B: T.Buffer((4, 5, 6), 
"int16")):
+                for ax0 in T.serial(4, annotations={"pragma_unroll_explicit": 
value}):
+                    for ax1, ax2 in T.grid(5, 6):
+                        with T.sblock("copy"):
+                            v0, v1, v2 = T.axis.remap("SSS", [ax0, ax1, ax2])
+                            T.reads(A[v0, v1, v2])
+                            T.writes(B[v0, v1, v2])
+                            B[v0, v1, v2] = A[v0, v1, v2]
+
+        tvm.build(Module, target="llvm")
+
+    for value in [None, True, False]:
+        check(value)
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/tests/python/tirx/transform/test_transform_lower_tirx.py 
b/tests/python/tirx/transform/test_transform_lower_tirx.py
index 72909c77bb..d9a571942d 100644
--- a/tests/python/tirx/transform/test_transform_lower_tirx.py
+++ b/tests/python/tirx/transform/test_transform_lower_tirx.py
@@ -62,6 +62,37 @@ def _launch_thread_extents(func):
 L_LANE = T.TileLayout(T.S[32 : 1 @ laneid])
 
 
+def test_lower_tirx_opaque_optional_pragma_annotations():
+    @T.prim_func(private=True)
+    def before(A: T.Buffer(8, "float32"), B: T.Buffer(8, "float32")):
+        for i in T.serial(8, annotations={"pragma_unroll": None}):
+            B[i] = A[i] + 1.0
+        for i in T.serial(8, annotations={"pragma_unroll_explicit": None}):
+            B[i] = A[i] + 2.0
+        for i in T.serial(8, annotations={"pragma_unroll": False}):
+            B[i] = A[i] + 3.0
+        for i in T.serial(8, annotations={"pragma_unroll_explicit": 0}):
+            B[i] = A[i] + 4.0
+
+    @T.prim_func(private=True)
+    def after(A: T.Buffer(8, "float32"), B: T.Buffer(8, "float32")):
+        for i in T.serial(8):
+            B[i] = A[i] + 1.0
+        for i in T.serial(8):
+            B[i] = A[i] + 2.0
+        for i in T.serial(8, annotations={"pragma_unroll": False}):
+            B[i] = A[i] + 3.0
+        for i in T.serial(8):
+            B[i] = A[i] + 4.0
+
+    # The pragma refers to the variable bound by the loop inside its body.
+    loop = after.body.seq[3]
+    pragma = tvm.tirx.AttrStmt(loop.loop_var, "pragma_unroll_explicit", 0, 
loop)
+    after = after.with_body(tvm.tirx.SeqStmt([*after.body.seq[:3], pragma]))
+    lowered = tvm.tirx.transform.LowerTIRxOpaque()(tvm.IRModule({"main": 
before}))
+    tvm.ir.assert_structural_equal(lowered["main"], after, map_free_vars=True)
+
+
 def test_lower_view_get():
     @T.prim_func(private=True)
     def before1(in_buf: T.Buffer(64, "float32"), out: T.Buffer(64, "float32")) 
-> None:

Reply via email to