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

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-tirx.git


The following commit(s) were added to refs/heads/main by this push:
     new 00163899f [FIX][CUDA] Register void result types for several functions 
(#184)
00163899f is described below

commit 00163899f872f413a0fd6678755fb4147f7b7372
Author: Linzhang Li <[email protected]>
AuthorDate: Tue Nov 3 20:57:25 2026 -0500

    [FIX][CUDA] Register void result types for several functions (#184)
    
    ## Summary
    
    This PR registers a void result type for six CUDA and NVSHMEM statement
    intrinsics. Without it,
    calling them from Python under an IRBuilder without the parser silently
    drops the statement.
    
    | Op | Same-file op that already registers void |
    | --- | --- |
    | `tirx.cuda.wait_until` | `tirx.nvshmem.wait_until` |
    | `tirx.cuda.timer_init` | `tirx.cuda.cluster_sync` |
    | `tirx.cuda.timer_start` | `tirx.cuda.cluster_sync` |
    | `tirx.cuda.timer_end` | `tirx.cuda.cluster_sync` |
    | `tirx.cuda.timer_finalize` | `tirx.cuda.cluster_sync` |
    | `tirx.nvshmem.getmem_nbi_block` | `getmem_nbi`, `getmem_nbi_warp` |
    
    ### Why the statements were dropped
    
    An op with no `TFixedReturnType` and no `FInferType` builds a Call whose
    type is `Missing`.
    `MaybeEmitVoidCall` (`src/script/ir_builder/ir.cc`) emits a Call as an
    `Evaluate` only when its
    type is void, and otherwise returns the Call to the caller. The
    TVMScript parser wraps expression
    statements in `Evaluate` itself, so parsed functions were unaffected.
    Python code that calls the
    script namespace directly under an IRBuilder (for example TIRx-lite in
    tirx-kernels) discards the
    returned Call, so the statement never reaches the IR or the generated
    CUDA.
    
    For `wait_until` this removes a cross-CTA wait: kernels that use it to
    wait for other CTAs (a
    grid barrier, a publish flag) were compiled without the wait.
---
 src/backend/cuda/op/target_builtin.cc             |  6 ++
 tests/python/backend/cuda/test_op_result_types.py | 82 +++++++++++++++++++++++
 2 files changed, 88 insertions(+)

diff --git a/src/backend/cuda/op/target_builtin.cc 
b/src/backend/cuda/op/target_builtin.cc
index f5a1ba310..8c2be2eeb 100644
--- a/src/backend/cuda/op/target_builtin.cc
+++ b/src/backend/cuda/op/target_builtin.cc
@@ -137,6 +137,7 @@ void RegisterCudaTargetBuiltins() {
                  sig::arg<IntExpr>("num_groups", "The number of groups."),
                  sig::arg<IntExpr>("group_id", "The group identifier."))
       .set_attr(tvm::op_attr::kCallEffectKind, CallEffectKind::kOpaque)
+      .set_attr(tvm::op_attr::kFixedReturnType, PrimType::Void())
       .expose();
 
   OpDef("tirx.cuda.timer_start")
@@ -147,6 +148,7 @@ void RegisterCudaTargetBuiltins() {
                  sig::arg<IntExpr>("profiler_write_stride", "The profiler 
write stride."),
                  sig::arg("leader_cond", "The leader condition."))
       .set_attr(tvm::op_attr::kCallEffectKind, CallEffectKind::kOpaque)
+      .set_attr(tvm::op_attr::kFixedReturnType, PrimType::Void())
       .expose();
 
   OpDef("tirx.cuda.timer_end")
@@ -157,6 +159,7 @@ void RegisterCudaTargetBuiltins() {
                  sig::arg<IntExpr>("profiler_write_stride", "The profiler 
write stride."),
                  sig::arg("leader_cond", "The leader condition."))
       .set_attr(tvm::op_attr::kCallEffectKind, CallEffectKind::kOpaque)
+      .set_attr(tvm::op_attr::kFixedReturnType, PrimType::Void())
       .expose();
 
   OpDef("tirx.cuda.timer_finalize")
@@ -166,6 +169,7 @@ void RegisterCudaTargetBuiltins() {
                  sig::arg<IntExpr>("profiler_write_stride", "The profiler 
write stride."),
                  sig::arg("leader_cond", "The leader condition."))
       .set_attr(tvm::op_attr::kCallEffectKind, CallEffectKind::kOpaque)
+      .set_attr(tvm::op_attr::kFixedReturnType, PrimType::Void())
       .expose();
 
   RegisterDeviceIntrinsics();
@@ -213,6 +217,7 @@ void RegisterDeviceIntrinsics() {
   RegisterDeviceIntrinsic(OpDef("tirx.cuda.wait_until"), 
CallEffectKind::kOpaque, sig::arg("dst"),
                           sig::arg("ptr"), sig::arg("condition"), 
sig::arg("scope"),
                           sig::arg("space"), sig::arg("ptx_type"), 
sig::arg<IntExpr>("backoff_ns"))
+      .set_attr(tvm::op_attr::kFixedReturnType, PrimType::Void())
       .expose();
   RegisterDeviceIntrinsic(OpDef("tirx.cuda.ballot_sync"), 
CallEffectKind::kOpaque,
                           sig::arg<IntExpr>("mask"), sig::arg("pred"))
@@ -415,6 +420,7 @@ void RegisterDeviceIntrinsics() {
   RegisterDeviceIntrinsic(OpDef("tirx.nvshmem.getmem_nbi_block"), 
CallEffectKind::kOpaque,
                           sig::arg("dst"), sig::arg("src"), 
sig::arg<IntExpr>("nelems"),
                           sig::arg<IntExpr>("pe"))
+      .set_attr(tvm::op_attr::kFixedReturnType, PrimType::Void())
       .expose();
   RegisterDeviceIntrinsic(OpDef("tirx.nvshmem.getmem_nbi_warp"), 
CallEffectKind::kOpaque,
                           sig::arg("dst"), sig::arg("src"), 
sig::arg<IntExpr>("nelems"),
diff --git a/tests/python/backend/cuda/test_op_result_types.py 
b/tests/python/backend/cuda/test_op_result_types.py
new file mode 100644
index 000000000..2d35aca16
--- /dev/null
+++ b/tests/python/backend/cuda/test_op_result_types.py
@@ -0,0 +1,82 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Result types of the CUDA and NVSHMEM intrinsics.
+
+A builder emits a Call as a statement only when its type is void, so a 
statement
+intrinsic registered without a result type is silently dropped when Python code
+calls it under an IRBuilder without the parser.
+"""
+
+import pytest
+from tvm_ffi import structural_equal, structural_walk
+
+from tvm_tirx import ir
+from tvm_tirx.ir.op import Op
+from tvm_tirx.ir.type import PrimType
+from tvm_tirx.script import tirx as T
+from tvm_tirx.script.ir_builder import AlreadyEmitted, IRBuilder
+from tvm_tirx.tirx.script import ir_builder as I
+
+STATEMENT_INTRINSICS = [
+    "tirx.cuda.wait_until",
+    "tirx.cuda.timer_init",
+    "tirx.cuda.timer_start",
+    "tirx.cuda.timer_end",
+    "tirx.cuda.timer_finalize",
+    "tirx.nvshmem.getmem_nbi_block",
+]
+
+# These constructors require the caller to pass the result type.
+CALLER_TYPED = {"tirx.cuda.mma_fill", "tirx.cuda.mma_store"}
+
+
[email protected]("name", STATEMENT_INTRINSICS)
+def test_statement_intrinsic_registers_void(name):
+    fixed = Op.get_attr_map("TFixedReturnType")
+    op = Op.get(name)
+    assert op in fixed and structural_equal(fixed[op], PrimType("void"))
+
+
+def test_every_intrinsic_has_a_result_type():
+    fixed = Op.get_attr_map("TFixedReturnType")
+    infer = Op.get_attr_map("FInferType")
+    untyped = {
+        name
+        for name in Op.list_op_names()
+        if name.startswith(("tirx.cuda.", "tirx.nvshmem."))
+        and Op.get(name) not in fixed
+        and Op.get(name) not in infer
+    }
+    assert untyped == CALLER_TYPED
+
+
+def test_statement_intrinsics_emit_under_a_builder():
+    with IRBuilder() as builder:
+        with I.function_():
+            flag = I.arg_("flag", T.Tensor((1,), "int32"))
+            spin = T.bind_(T.alloc_local((1,), "int32"))[0]
+            ptr = flag.ptr_to([0])
+            receipts = [
+                T.cuda.wait_until(spin, ptr, spin >= 1, scope="gpu", 
ptx_type="b32"),
+                T.cuda.timer_init(ptr, 0, 0, 1, 0),
+                T.nvshmem.getmem_nbi.block(ptr, ptr, 1, 0),
+            ]
+    assert all(isinstance(receipt, AlreadyEmitted) for receipt in receipts)
+    names = []
+    structural_walk(builder.get(), (ir.Call, lambda call: 
names.append(call.op.name)))
+    for name in ("tirx.cuda.wait_until", "tirx.cuda.timer_init", 
"tirx.nvshmem.getmem_nbi_block"):
+        assert names.count(name) == 1

Reply via email to