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 23fc37c4cf [Fix][TIRx] Handle vector access pointer addresses in C 
codegen (#20058)
23fc37c4cf is described below

commit 23fc37c4cf95569d417e8e657dc67d2d0f171f89
Author: Shushi Hong <[email protected]>
AuthorDate: Tue Jul 28 19:29:13 2026 -0400

    [Fix][TIRx] Handle vector access pointer addresses in C codegen (#20058)
    
    This PR fixes invalid pointer arithmetic emitted by C-family codegen for
    vector-typed `tvm_access_ptr`.
    
    A vector access pointer is lowered to `address_of(BufferLoad(...))` with
    a `Ramp` index describing its lane indices. For example, `Ramp(4, 1, 2)`
    represents scalar elements `[4, 5]`, so its address should be the
    address of the first lane, `&A[4]`.
    
    LLVM codegen already extracts `Ramp::base` in this case. However,
    `CodeGenC` previously passed the complete ramp to pointer arithmetic,
    which could generate invalid CUDA code such as:
    
    ```cpp
    (float*)A + make_int2(4, 5)
    ```
    
    This PR makes `CodeGenC` use `Ramp::base` when generating the address of
    a vector `BufferLoad`. The normalized index is applied to both the
    direct pointer-offset path and the general `GetBufferRef` path.
    
    The existing scalar-buffer plus `Ramp` lowering is preserved. This
    avoids regressions for padded vector types such as `float32x3` and
    packed vector types such as `int4x4`, while making C-family codegen
    consistent with LLVM codegen.
    
    Regression tests cover:
    
    - `float32x2` C codegen.
    - Padded `float32x3` LLVM codegen.
    - Packed `int4x4` CUDA codegen.
---
 src/target/source/codegen_c.cc                     | 11 ++++--
 tests/python/codegen/test_target_codegen_c_host.py | 13 +++++++
 .../test_tir_transform_lower_intrin.py             | 40 ++++++++++++++++++++++
 tests/python/tirx/codegen/test_codegen_cuda.py     | 20 +++++++++++
 4 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/src/target/source/codegen_c.cc b/src/target/source/codegen_c.cc
index 6f9c25c592..8d0dadb55e 100644
--- a/src/target/source/codegen_c.cc
+++ b/src/target/source/codegen_c.cc
@@ -758,15 +758,20 @@ void CodeGenC::VisitExpr_(const CallNode* op, 
std::ostream& os) {  // NOLINT(*)
       if (load) {
         TVM_FFI_ICHECK_EQ(load->indices.size(), 1)
             << "CodeGenC only supports flat memory allocations.";
+        PrimExpr index = load->indices[0];
+        // A vector BufferLoad uses a Ramp to describe its lane indices.  The
+        // address of that load is the address of its first lane.
+        if (const RampNode* ramp = index.as<RampNode>()) {
+          index = ramp->base;
+        }
         const VarNode* data = load->buffer->data.get();
         if (pointer_offset_vars_.count(data) && HandleTypeMatch(data, 
load->buffer->dtype) &&
             !IsVolatile(data)) {
           os << "(" << GetVarID(data) << " + ";
-          this->PrintExpr(load->indices[0], os);
+          this->PrintExpr(index, os);
           os << ")";
         } else {
-          os << "(&("
-             << GetBufferRef(load->ty.as_or_throw<PrimType>(), 
load->buffer.get(), load->indices[0])
+          os << "(&(" << GetBufferRef(load->ty.as_or_throw<PrimType>(), 
load->buffer.get(), index)
              << "))";
         }
       } else {
diff --git a/tests/python/codegen/test_target_codegen_c_host.py 
b/tests/python/codegen/test_target_codegen_c_host.py
index 8af6be0b79..f33eb92210 100644
--- a/tests/python/codegen/test_target_codegen_c_host.py
+++ b/tests/python/codegen/test_target_codegen_c_host.py
@@ -245,5 +245,18 @@ def test_workspace_allocation_cast():
     built.export_library(temp.relpath("workspace.so"))
 
 
+def test_vector_access_ptr_address_uses_ramp_base():
+    buffer = tvm.tirx.decl_buffer((8,), "float32x2", name="A")
+    access_ptr = buffer.access_ptr(access_mask=3, offset=2, extent=4)
+    body = tvm.tirx.Evaluate(tvm.tirx.call_extern("void", "consume", 
access_ptr))
+    func = tvm.tirx.PrimFunc([buffer], body).with_attr("global_symbol", "main")
+
+    source = tvm.tirx.build(tvm.IRModule.from_expr(func), 
target="c").inspect_source()
+    call = next(line.strip() for line in source.splitlines() if 
line.strip().startswith("consume("))
+    assert "int32_t2" not in call
+    assert "float2*" in call
+    assert " + 4" in call
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/tests/python/tirx-transform/test_tir_transform_lower_intrin.py 
b/tests/python/tirx-transform/test_tir_transform_lower_intrin.py
index bde693fbce..c5378cfcdb 100644
--- a/tests/python/tirx-transform/test_tir_transform_lower_intrin.py
+++ b/tests/python/tirx-transform/test_tir_transform_lower_intrin.py
@@ -114,6 +114,46 @@ def test_lower_nested_access_ptr():
     assert int(tvm.arith.Analyzer().simplify(load.indices[0])) == 5
 
 
+def test_lower_vector_access_ptr():
+    buffer = tvm.tirx.decl_buffer((8,), "float32x2", name="A")
+    access_ptr = buffer.access_ptr(access_mask=3, offset=2, extent=4)
+
+    assert access_ptr.op.name == "tirx.tvm_access_ptr"
+    assert int(access_ptr.args[2]) == 2
+    assert int(access_ptr.args[3]) == 4
+    assert int(access_ptr.args[4]) == 3
+
+    mod = tvm.IRModule.from_expr(
+        tvm.tirx.PrimFunc([buffer], tvm.tirx.Evaluate(access_ptr)).with_attr(
+            "target", tvm.target.Target("llvm")
+        )
+    )
+    lowered = tvm.tirx.transform.LowerIntrin()(mod)["main"].body.value
+    assert lowered.op.name == "tirx.address_of"
+    assert lowered.ty == access_ptr.ty
+
+    load = lowered.args[0]
+    assert isinstance(load, tvm.tirx.BufferLoad)
+    assert load.buffer.data.same_as(buffer.data)
+    assert load.buffer.dtype == tvm.ir.PrimType("float32")
+    assert len(load.indices) == 1
+    ramp = load.indices[0]
+    assert isinstance(ramp, tvm.tirx.Ramp)
+    assert int(ramp.base) == 4
+    assert int(ramp.stride) == 1
+    assert ramp.lanes == 2
+
+
[email protected](not env.has_llvm(), reason="need llvm")
+def test_lower_vector_access_ptr_with_padded_vector_dtype():
+    buffer = tvm.tirx.decl_buffer((8,), "float32x3", name="A")
+    access_ptr = buffer.access_ptr(access_mask=1, offset=2, extent=4)
+    body = tvm.tirx.Evaluate(tvm.tirx.call_extern("void", "consume", 
access_ptr))
+    func = tvm.tirx.PrimFunc([buffer], body).with_attr("global_symbol", "main")
+
+    tvm.tirx.build(tvm.IRModule.from_expr(func), target="llvm")
+
+
 def get_ref_data():
     """Get reference data for every pairs"""
     import itertools
diff --git a/tests/python/tirx/codegen/test_codegen_cuda.py 
b/tests/python/tirx/codegen/test_codegen_cuda.py
index f5fc621c69..745d10d928 100644
--- a/tests/python/tirx/codegen/test_codegen_cuda.py
+++ b/tests/python/tirx/codegen/test_codegen_cuda.py
@@ -43,6 +43,26 @@ def _helper_source(src: str, helper_name: str) -> str:
     return src[start:next_helper]
 
 
+def test_vector_access_ptr_preserves_packed_offset(monkeypatch):
+    buffer = tvm.tirx.decl_buffer((8,), "int4x4", name="A")
+    access_ptr = buffer.access_ptr(access_mask=3, offset=2, extent=4)
+    body = tvm.tirx.Evaluate(tvm.tirx.call_extern("void", "consume", 
access_ptr))
+    target = tvm.target.Target({"kind": "cuda", "arch": "sm_80"})
+    func = (
+        tvm.tirx.PrimFunc([buffer.data], body)
+        .with_attr("global_symbol", "main")
+        .with_attr("target", target)
+    )
+    lowered = tvm.tirx.transform.LowerIntrin()(tvm.IRModule.from_expr(func))
+
+    monkeypatch.setenv("TVM_COMPILE_FORCE_FALLBACK", "1")
+    source = tvm.get_global_func("target.build.cuda")(lowered, 
target).inspect_source()
+    call = next(line.strip() for line in source.splitlines() if 
line.strip().startswith("consume("))
+
+    assert "make_int4" not in call
+    assert " + 8 / 4" in call
+
+
 def test_tirx_launch_bounds_omits_min_blocks_without_persistent_schedule():
     @T.prim_func
     def main(A: T.Buffer((4,), "int32")):

Reply via email to