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.git


The following commit(s) were added to refs/heads/main by this push:
     new 7e913b5826 [REFACTOR] Localize and simplify internal TIRx helpers 
(#20683)
7e913b5826 is described below

commit 7e913b58268d88ee36428c9e22c58830b0eb9269
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 12 19:11:54 2026 -0400

    [REFACTOR] Localize and simplify internal TIRx helpers (#20683)
    
    Move expression replacement and tensor allocation-shape handling into
    S-TIR, and keep WMMA fragment parsing with SPIR-V code generation.
    
    Replace trivial ABI construction, pointer access, storage-scope and
    statement-prefix helpers at their use sites, and remove unused
    utilities. This reduces private TIRx helper dependencies while
    preserving existing traversal, lowering and code-generation behavior.
---
 src/backend/vulkan/codegen/codegen_spirv.cc        |  33 +++-
 src/s_tir/schedule/primitive/cache_index.cc        |   2 +-
 src/s_tir/transform/inject_ptx_async_copy.cc       |  15 +-
 .../transform/merge_shared_memory_allocations.cc   |  23 ++-
 .../transform/replace_selected_expr.cc             |   8 +-
 .../transform/replace_selected_expr.h              |  18 +--
 src/s_tir/transform/storage_access.cc              |   6 +-
 src/s_tir/transform/tensorcore_infer_fragment.cc   |   8 +-
 src/target/llvm/codegen_llvm.cc                    |   5 +-
 src/target/source/codegen_c.cc                     |   9 +-
 src/tirx/ir/pointer_type.h                         |  57 -------
 src/tirx/transform/ir_utils.cc                     | 100 ------------
 src/tirx/transform/ir_utils.h                      | 169 ---------------------
 src/tirx/transform/lower_tvm_builtin.cc            | 153 +++++++++++++------
 src/tirx/transform/make_packed_api.cc              |  13 +-
 src/tirx/transform/storage_rewrite.cc              |  38 +++--
 src/tirx/transform/tvm_ffi_binder.cc               |  55 ++++---
 17 files changed, 265 insertions(+), 447 deletions(-)

diff --git a/src/backend/vulkan/codegen/codegen_spirv.cc 
b/src/backend/vulkan/codegen/codegen_spirv.cc
index 6b3823d544..70674d4292 100644
--- a/src/backend/vulkan/codegen/codegen_spirv.cc
+++ b/src/backend/vulkan/codegen/codegen_spirv.cc
@@ -32,10 +32,11 @@
 #include <tvm/tirx/op/region.h>
 #include <tvm/tirx/stmt.h>
 
+#include <stdexcept>
 #include <string>
+#include <utility>
 
 #include "../../../runtime/pack_args.h"
-#include "../../../tirx/transform/ir_utils.h"
 #include "../runtime/vulkan_common.h"
 
 namespace tvm {
@@ -44,6 +45,36 @@ using namespace tvm::prim;
 
 namespace {
 
+int Stoi(const std::string& str) {
+  try {
+    return std::stoi(str);
+  } catch (std::invalid_argument& e) {
+    TVM_FFI_THROW(InternalError) << "Cannot convert \"" << str << "\" to int";
+    throw;
+  }
+}
+
+std::pair<int32_t, int32_t> GetWmmaFragmentDimSize(const std::string& 
shape_str,
+                                                   const std::string& scope) {
+  size_t m, n, k;
+  size_t last_pos = 0, pos = 0;
+  pos = shape_str.find(", ", last_pos);
+  m = Stoi(shape_str.substr(last_pos, pos - last_pos));
+  last_pos = pos + 2;
+  pos = shape_str.find(", ", last_pos);
+  n = Stoi(shape_str.substr(last_pos, pos - last_pos));
+  last_pos = pos + 2;
+  k = Stoi(shape_str.substr(last_pos, shape_str.length() - last_pos));
+  if (scope == "wmma.matrix_a") {
+    return std::pair<int32_t, int32_t>(m, k);
+  } else if (scope == "wmma.matrix_b") {
+    return std::pair<int32_t, int32_t>(k, n);
+  } else if (scope == "wmma.accumulator") {
+    return std::pair<int32_t, int32_t>(m, n);
+  }
+  return std::pair<int32_t, int32_t>(0, 0);
+}
+
 const IntImmNode* AsIntImmNode(const Expr& expr) {
   const IntImmNode* node = expr.as<IntImmNode>();
   TVM_FFI_ICHECK(node);
diff --git a/src/s_tir/schedule/primitive/cache_index.cc 
b/src/s_tir/schedule/primitive/cache_index.cc
index 3f0f7e308d..4e3db2e8ec 100644
--- a/src/s_tir/schedule/primitive/cache_index.cc
+++ b/src/s_tir/schedule/primitive/cache_index.cc
@@ -22,7 +22,7 @@
 #include <tvm/s_tir/stmt.h>
 #include <tvm/sym/int_set.h>
 
-#include "../../../tirx/transform/replace_selected_expr.h"
+#include "../../transform/replace_selected_expr.h"
 #include "../utils.h"
 #include "cache_index_helpers.h"
 
diff --git a/src/s_tir/transform/inject_ptx_async_copy.cc 
b/src/s_tir/transform/inject_ptx_async_copy.cc
index 05171cb68a..99c904c8b5 100644
--- a/src/s_tir/transform/inject_ptx_async_copy.cc
+++ b/src/s_tir/transform/inject_ptx_async_copy.cc
@@ -31,7 +31,6 @@
 #include <tvm/tirx/analysis.h>
 #include <tvm/tirx/op/memory.h>
 
-#include "../../tirx/ir/pointer_type.h"
 #include "storage_access.h"
 #include "tvm/s_tir/stmt.h"
 #include "tvm/tirx/stmt.h"
@@ -73,25 +72,21 @@ class PTXAsyncCopyInjector : public StmtExprMutator {
           
static_cast<int>(load->source.as_or_throw<tvm::tirx::TensorVar>()->dtype.StorageBytes());
 
       if (bytes == 4 || bytes == 8 || bytes == 16) {
-        auto dst_elem_type =
-            
GetPtrElementType(store->dest.as_or_throw<TensorVar>().type()->DataPtrType());
-        auto src_elem_type = GetPtrElementType(
-            
load->source.as_or_throw<tvm::tirx::TensorVar>().type()->DataPtrType());
-        TVM_FFI_ICHECK(dst_elem_type.has_value() && src_elem_type.has_value())
-            << "Both store and load tensor should have a pointer type 
annotation.";
+        PrimType dst_elem_type = store->dest.as_or_throw<TensorVar>()->dtype;
+        PrimType src_elem_type = load->source.as_or_throw<TensorVar>()->dtype;
 
         int index_factor = 1;
-        if (dst_elem_type.value() != src_elem_type.value()) {
+        if (dst_elem_type != src_elem_type) {
           // The only case where src and dst have different dtypes is when the 
dst shared memory
           // is a byte tensor generated by merging dynamic shared memory.
           TVM_FFI_ICHECK(store->dest.as_or_throw<TensorVar>().scope() == 
"shared.dyn");
-          TVM_FFI_ICHECK((dst_elem_type.value() == PrimType::UInt(8)));
+          TVM_FFI_ICHECK((dst_elem_type == PrimType::UInt(8)));
           // TensorStore/Load have the "pointer reinterpret" semantics 
according to their
           // "value" dtype. Their "indices" are supposed to be applied after 
such pointer cast,
           // for example: ((*float16)(byte_buffer))[tensor->indices] = 
fp16_value;
           // To replace TensorStore/Load with cp.async, we need to multiply 
the store index by
           // the byte size of the "value" dtype, to get the correct offset 
into the byte tensor.
-          index_factor = 
static_cast<int>(src_elem_type.value().StorageBytes());
+          index_factor = static_cast<int>(src_elem_type.StorageBytes());
         }
 
         if (indices_lanes == 1) {
diff --git a/src/s_tir/transform/merge_shared_memory_allocations.cc 
b/src/s_tir/transform/merge_shared_memory_allocations.cc
index 35282988ef..4777468db6 100644
--- a/src/s_tir/transform/merge_shared_memory_allocations.cc
+++ b/src/s_tir/transform/merge_shared_memory_allocations.cc
@@ -34,6 +34,7 @@
 #include <tvm/s_tir/stmt.h>
 #include <tvm/s_tir/stmt_functor.h>
 #include <tvm/s_tir/transform.h>
+#include <tvm/sym/analyzer.h>
 #include <tvm/tirx/op/memory.h>
 #include <tvm/tirx/op/region.h>
 #include <tvm/tirx/stmt.h>
@@ -45,7 +46,6 @@
 
 #include "../../runtime/thread_storage_scope.h"
 #include "../../support/arena.h"
-#include "../../tirx/transform/ir_utils.h"
 
 namespace tvm {
 namespace s_tir {
@@ -56,6 +56,19 @@ using runtime::StorageScope;
 
 namespace {
 
+ffi::Array<PrimExpr> GetTensorAllocationShape(const TensorVar& tensor) {
+  ffi::Array<PrimExpr> alloc_shape = tensor->shape;
+  if (tensor->strides.size()) {
+    TVM_FFI_ICHECK_EQ(tensor->shape.size(), tensor->strides.size());
+    for (size_t i = tensor->strides.size() - 1; i > 0; --i) {
+      TVM_FFI_ICHECK(
+          sym::Analyzer()->CanProveEqual(floormod(tensor->strides[i - 1], 
tensor->strides[i]), 0));
+      alloc_shape.Set(i, tensor->strides[i - 1] / tensor->strides[i]);
+    }
+  }
+  return alloc_shape;
+}
+
 ffi::Optional<Var> GetBufferDataVar(const ffi::Any& data) {
   if (auto var = data.as<Var>()) {
     return var;
@@ -70,12 +83,16 @@ ffi::Optional<Var> GetBufferDataVar(const ffi::Any& data) {
 }  // namespace
 
 bool IsDynamicSharedMemory(Var buffer_var) {
-  StorageScope storage_scope = 
runtime::StorageScope::Create(GetPtrStorageScope(buffer_var));
+  StorageScope storage_scope = runtime::StorageScope::Create(
+      (buffer_var->ty.as<TensorTypeNode>() ? 
buffer_var->ty.as<TensorTypeNode>()->storage_scope
+                                           : 
buffer_var->ty.as_or_throw<PtrType>()->storage_scope));
   return storage_scope.rank == runtime::StorageRank::kShared && 
storage_scope.tag == ".dyn";
 }
 
 bool IsStaticSharedMemory(Var buffer_var) {
-  StorageScope storage_scope = 
runtime::StorageScope::Create(GetPtrStorageScope(buffer_var));
+  StorageScope storage_scope = runtime::StorageScope::Create(
+      (buffer_var->ty.as<TensorTypeNode>() ? 
buffer_var->ty.as<TensorTypeNode>()->storage_scope
+                                           : 
buffer_var->ty.as_or_throw<PtrType>()->storage_scope));
   return storage_scope.rank == runtime::StorageRank::kShared && 
storage_scope.tag == "";
 }
 
diff --git a/src/tirx/transform/replace_selected_expr.cc 
b/src/s_tir/transform/replace_selected_expr.cc
similarity index 96%
rename from src/tirx/transform/replace_selected_expr.cc
rename to src/s_tir/transform/replace_selected_expr.cc
index 93e96481de..178ecb9b3d 100644
--- a/src/tirx/transform/replace_selected_expr.cc
+++ b/src/s_tir/transform/replace_selected_expr.cc
@@ -35,7 +35,7 @@
 #include <tvm/tirx/transform.h>  // For the declaration of the pass
 
 namespace tvm {
-namespace tirx {
+namespace s_tir {
 
 /*!
  * \brief Toplevel (static) function that replace in an expression
@@ -87,7 +87,7 @@ 
ReplaceSelectedExpr::ReplaceSelectedExpr(std::function<bool(const PrimExpr&)> pr
       can_replace_inside_(can_replace_inside) {}
 
 /*!
- * \brief The method which overrides the generic dispatcher of StmtExprMutator
+ * \brief The method which overrides the generic dispatcher of 
tirx::StmtExprMutator
  * \param expr The expression to mutate
  */
 UnchangedOr<ffi::Any> ReplaceSelectedExpr::Mutate(ffi::AnyView input, 
InplaceMode inplace_mode) {
@@ -95,8 +95,8 @@ UnchangedOr<ffi::Any> 
ReplaceSelectedExpr::Mutate(ffi::AnyView input, InplaceMod
     if (predicate_selector_(prim_expr.value())) return new_expr_;
     if (!can_replace_inside_(prim_expr.value())) return ffi::Unchanged();
   }
-  return StmtExprMutator::Mutate(input, inplace_mode);
+  return tirx::StmtExprMutator::Mutate(input, inplace_mode);
 }
 
-}  // namespace tirx
+}  // namespace s_tir
 }  // namespace tvm
diff --git a/src/tirx/transform/replace_selected_expr.h 
b/src/s_tir/transform/replace_selected_expr.h
similarity index 86%
rename from src/tirx/transform/replace_selected_expr.h
rename to src/s_tir/transform/replace_selected_expr.h
index efa95e6edb..0b79ad12d4 100644
--- a/src/tirx/transform/replace_selected_expr.h
+++ b/src/s_tir/transform/replace_selected_expr.h
@@ -24,26 +24,26 @@
            with a predicate by another expression.
  */
 
-#ifndef TVM_TIR_TRANSFORM_REPLACE_SELECTED_EXPR_H_
-#define TVM_TIR_TRANSFORM_REPLACE_SELECTED_EXPR_H_
+#ifndef TVM_S_TIR_TRANSFORM_REPLACE_SELECTED_EXPR_H_
+#define TVM_S_TIR_TRANSFORM_REPLACE_SELECTED_EXPR_H_
 
 #include <tvm/ir/expr_functor.h>
 #include <tvm/ir/prim/expr.h>
 #include <tvm/tirx/stmt.h>
-#include <tvm/tirx/stmt_functor.h>  // For the class StmtExprMutator
+#include <tvm/tirx/stmt_functor.h>  // For the class tirx::StmtExprMutator
 
 namespace tvm {
-namespace tirx {
+namespace s_tir {
 
 /*!
  * \brief Mutator for replacing the expressions selected by a predicate in a 
statement and/or
           in an expression, which only replace inside of nodes in which it is 
allowed to perform
           replacecements (given by a second predicate)
  */
-class ReplaceSelectedExpr : public StmtExprMutator {
+class ReplaceSelectedExpr : public tirx::StmtExprMutator {
  public:
-  using StmtExprMutator::Mutate;
-  using StmtExprMutator::Mutate_;
+  using tirx::StmtExprMutator::Mutate;
+  using tirx::StmtExprMutator::Mutate_;
   // Toplevel (static) functions
   static PrimExpr ReplaceSelectedExprInExpr(
       const PrimExpr& expr, std::function<bool(const PrimExpr&)> 
predicate_selector,
@@ -69,7 +69,7 @@ class ReplaceSelectedExpr : public StmtExprMutator {
   std::function<bool(const PrimExpr&)> can_replace_inside_;
 };
 
-}  // namespace tirx
+}  // namespace s_tir
 }  // namespace tvm
 
-#endif  // TVM_TIR_TRANSFORM_REPLACE_SELECTED_EXPR_H_
+#endif  // TVM_S_TIR_TRANSFORM_REPLACE_SELECTED_EXPR_H_
diff --git a/src/s_tir/transform/storage_access.cc 
b/src/s_tir/transform/storage_access.cc
index 2632b4f9b1..3d894b0dd5 100644
--- a/src/s_tir/transform/storage_access.cc
+++ b/src/s_tir/transform/storage_access.cc
@@ -32,8 +32,6 @@
 #include <string>
 #include <utility>
 
-#include "../../tirx/transform/ir_utils.h"
-
 namespace tvm {
 namespace s_tir {
 using namespace tvm::tirx;
@@ -410,8 +408,8 @@ StorageScope StorageAccessVisitor::GetScope(Var buffer_var) 
const {
   if (auto tensor_type = buffer_var->ty.as<TensorType>()) {
     return StorageScope::Create(tensor_type.value()->storage_scope);
   }
-  if (buffer_var->ty.as<PtrTypeNode>()) {
-    return StorageScope::Create(GetPtrStorageScope(buffer_var));
+  if (const auto* pointer_type = buffer_var->ty.as<PtrTypeNode>()) {
+    return StorageScope::Create(pointer_type->storage_scope);
   }
   return StorageScope();  // global by default
 }
diff --git a/src/s_tir/transform/tensorcore_infer_fragment.cc 
b/src/s_tir/transform/tensorcore_infer_fragment.cc
index 8b6d24ba9f..477c793e16 100644
--- a/src/s_tir/transform/tensorcore_infer_fragment.cc
+++ b/src/s_tir/transform/tensorcore_infer_fragment.cc
@@ -79,7 +79,9 @@ class FragmentGetter : public s_tir::StmtExprVisitor {
       TVM_FFI_ICHECK(k);
       TVM_FFI_ICHECK(layout);
 
-      std::string scope = GetPtrStorageScope(ffi::GetRef<Var>(buffer_var));
+      std::string scope = (buffer_var->ty.as<TensorTypeNode>()
+                               ? 
buffer_var->ty.as<TensorTypeNode>()->storage_scope
+                               : 
buffer_var->ty.as_or_throw<PtrType>()->storage_scope);
       if (fragments.count(buffer_var)) {
         // check if the fragment has met before
         FragmentInfo info = fragments[buffer_var];
@@ -114,7 +116,9 @@ class FragmentGetter : public s_tir::StmtExprVisitor {
       TVM_FFI_ICHECK(n);
       TVM_FFI_ICHECK(k);
 
-      std::string scope = GetPtrStorageScope(ffi::GetRef<Var>(buffer_var));
+      std::string scope = (buffer_var->ty.as<TensorTypeNode>()
+                               ? 
buffer_var->ty.as<TensorTypeNode>()->storage_scope
+                               : 
buffer_var->ty.as_or_throw<PtrType>()->storage_scope);
       if (fragments.count(buffer_var)) {
         FragmentInfo info = fragments[buffer_var];
         TVM_FFI_ICHECK_EQ(m->value, info.m);
diff --git a/src/target/llvm/codegen_llvm.cc b/src/target/llvm/codegen_llvm.cc
index 658c3aa178..c341747acb 100644
--- a/src/target/llvm/codegen_llvm.cc
+++ b/src/target/llvm/codegen_llvm.cc
@@ -712,8 +712,9 @@ void CodeGenLLVM::GetAlignment(PrimType t, const VarNode* 
buf_var, const PrimExp
   auto it = alloc_storage_info_.find(buf_var);
   if (it != alloc_storage_info_.end()) {
     const StorageInfo& info = it->second;
-    *p_native_bits = NativeVectorBits(
-        
runtime::StorageScope::Create(GetPtrStorageScope(ffi::GetRef<Var>(buf_var))));
+    *p_native_bits = NativeVectorBits(runtime::StorageScope::Create(
+        (buf_var->ty.as<TensorTypeNode>() ? 
buf_var->ty.as<TensorTypeNode>()->storage_scope
+                                          : 
buf_var->ty.as_or_throw<PtrType>()->storage_scope)));
     max_align_bits = info.alignment * 8;
   } else {
     *p_native_bits = native_vector_bits_;
diff --git a/src/target/source/codegen_c.cc b/src/target/source/codegen_c.cc
index 294735afc7..75639ffe5f 100644
--- a/src/target/source/codegen_c.cc
+++ b/src/target/source/codegen_c.cc
@@ -41,7 +41,6 @@
 #include <limits>
 
 #include "../../sym/pattern_match.h"
-#include "../../tirx/ir/pointer_type.h"
 #include "codegen_params.h"
 
 namespace tvm {
@@ -436,13 +435,15 @@ void CodeGenC::RegisterHandleType(const VarNode* buf_var, 
const PrimType& t) {
 
 void CodeGenC::RegisterHandleTypeFromPointer(const tvm::Var& var, const Expr* 
value) {
   if (value == nullptr) return;
-  std::optional<PrimType> value_dtype = tirx::GetPtrElementType((*value)->ty);
-  if (!value_dtype.has_value()) return;
+  const auto* pointer_type = (*value)->ty.as<PtrTypeNode>();
+  if (!pointer_type) return;
+  const auto* value_dtype = pointer_type->element_type.as<PrimTypeNode>();
+  if (!value_dtype) return;
   auto* call = value->as<CallNode>();
   if (call != nullptr && call->op.same_as(tirx::ptr_byte_offset_op())) {
     pointer_offset_vars_.insert(var.get());
   }
-  RegisterHandleType(var.get(), value_dtype.value());
+  RegisterHandleType(var.get(), ffi::GetRef<PrimType>(value_dtype));
 }
 
 void CodeGenC::PrintVecElemLoad(const std::string& vec, const PrimType& t, int 
i,
diff --git a/src/tirx/ir/pointer_type.h b/src/tirx/ir/pointer_type.h
deleted file mode 100644
index c78c74c3a0..0000000000
--- a/src/tirx/ir/pointer_type.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.
- */
-/*!
- * \file tirx/ir/pointer_type.h
- * \brief Primitive pointer type utility
- */
-#ifndef TVM_TIR_IR_POINTER_TYPE_H_
-#define TVM_TIR_IR_POINTER_TYPE_H_
-
-#include <tvm/ffi/dtype.h>
-#include <tvm/ir/type.h>
-
-#include <optional>
-
-namespace tvm {
-namespace tirx {
-
-/*!
- * \brief Returns the type of object pointed to.
- *
- * \param type The type to be checked.
- *
- * \return An std::optional<PrimType> object. If the type is a pointer
- * to a primitive type, the object has a value which is the pointed-to
- * type. Otherwise the object is nullopt.
- */
-inline std::optional<PrimType> GetPtrElementType(const Type& type) {
-  if (!type.as<MissingType>().has_value()) {
-    if (auto* ptr_type = type.as<PtrTypeNode>()) {
-      if (auto* prim_type = ptr_type->element_type.as<PrimTypeNode>()) {
-        return ffi::GetRef<PrimType>(prim_type);
-      }
-    }
-  }
-
-  return std::nullopt;
-}
-
-}  // namespace tirx
-}  // namespace tvm
-#endif  // TVM_TIR_IR_POINTER_TYPE_H_
diff --git a/src/tirx/transform/ir_utils.cc b/src/tirx/transform/ir_utils.cc
index 8e7b15201f..536eb6da8e 100644
--- a/src/tirx/transform/ir_utils.cc
+++ b/src/tirx/transform/ir_utils.cc
@@ -43,43 +43,6 @@ namespace tvm {
 namespace tirx {
 using namespace tvm::prim;
 
-Stmt MergeNest(const std::vector<Stmt>& nest, Stmt body) {
-  // use reverse iteration
-  for (auto ri = nest.rbegin(); ri != nest.rend(); ++ri) {
-    Stmt s = *ri;
-    if (const auto* for_ = s.as<ForNode>()) {
-      auto n = ffi::make_object<ForNode>(*for_);
-      TVM_FFI_ICHECK(tirx::IsNoOp(n->body));
-      n->body = body;
-      body = Stmt(n);
-    } else if (const auto* bind = s.as<BindNode>()) {
-      // Bind has no body -- prepend it before the accumulated body in a 
SeqStmt.
-      body = SeqStmt({ffi::GetRef<Stmt>(bind), body});
-    } else if (const auto* ite = s.as<IfNode>()) {
-      auto n = ffi::make_object<IfNode>(*ite);
-      TVM_FFI_ICHECK(tirx::IsNoOp(n->then_case));
-      TVM_FFI_ICHECK(!n->else_case);
-      n->then_case = body;
-      body = Stmt(n);
-    } else if (const auto* seq = s.as<SeqStmtNode>()) {
-      // A sequence nest is a prefix, followed by the accumulated body.
-      body = SeqStmt({s, body}, seq->loc);
-    } else if (s.as<AssertStmtNode>()) {
-      body = SeqStmt({s, body});
-    } else {
-      TVM_FFI_THROW(InternalError) << "not supported nest type";
-    }
-  }
-  return body;
-}
-
-Stmt MergeNest(const std::vector<std::vector<Stmt>>& nest, Stmt body) {
-  for (auto ri = nest.rbegin(); ri != nest.rend(); ++ri) {
-    body = MergeNest(*ri, body);
-  }
-  return body;
-}
-
 Function IRConvertSSA::VisitFunction(Function func) {
   std::unordered_set<const VarNode*> parameter_symbols;
   // Define explicit parameters before the symbolic values in their types.
@@ -296,69 +259,6 @@ Stmt ConvertSSA(Stmt stmt) {
   return ffi::make_object<IRConvertSSA>()->Mutate(stmt, 
InplaceMode::kAllow).ValueOrUnchanged(stmt);
 }
 
-ffi::String GetPtrStorageScope(Var buffer_var) {
-  if (const auto* tensor_type = buffer_var->ty.as<TensorTypeNode>()) {
-    return tensor_type->storage_scope;
-  }
-  const auto* ptr_type = buffer_var->ty.as<PtrTypeNode>();
-  TVM_FFI_ICHECK(ptr_type)
-      << "The provided variable is neither a pointer nor a tensor-typed 
variable";
-  return ptr_type->storage_scope;
-}
-
-ffi::Array<PrimExpr> GetTensorAllocationShape(const TensorVar& tensor) {
-  ffi::Array<PrimExpr> alloc_shape = tensor->shape;
-  if (tensor->strides.size()) {
-    TVM_FFI_ICHECK_EQ(tensor->shape.size(), tensor->strides.size());
-    for (size_t i = tensor->strides.size() - 1; i > 0; --i) {
-      TVM_FFI_ICHECK(
-          sym::Analyzer()->CanProveEqual(floormod(tensor->strides[i - 1], 
tensor->strides[i]), 0));
-      alloc_shape.Set(i, tensor->strides[i - 1] / tensor->strides[i]);
-    }
-  }
-  return alloc_shape;
-}
-
-int Stoi(const std::string& str) {
-  try {
-    return std::stoi(str);
-  } catch (std::invalid_argument& e) {
-    TVM_FFI_THROW(InternalError) << "Cannot convert \"" << str << "\" to int";
-    throw;
-  }
-}
-
-std::pair<int32_t, int32_t> GetWmmaFragmentDimSize(const std::string& 
shape_str,
-                                                   const std::string& scope) {
-  size_t m, n, k;
-  size_t last_pos = 0, pos = 0;
-  pos = shape_str.find(", ", last_pos);
-  m = Stoi(shape_str.substr(last_pos, pos - last_pos));
-  last_pos = pos + 2;
-  pos = shape_str.find(", ", last_pos);
-  n = Stoi(shape_str.substr(last_pos, pos - last_pos));
-  last_pos = pos + 2;
-  k = Stoi(shape_str.substr(last_pos, shape_str.length() - last_pos));
-  if (scope == "wmma.matrix_a") {
-    return std::pair<int32_t, int32_t>(m, k);
-  } else if (scope == "wmma.matrix_b") {
-    return std::pair<int32_t, int32_t>(k, n);
-  } else if (scope == "wmma.accumulator") {
-    return std::pair<int32_t, int32_t>(m, n);
-  }
-  return std::pair<int32_t, int32_t>(0, 0);
-}
-
-std::optional<bool> IsHostFunc(const Function& func) {
-  if (func->HasNonzeroAttr(tvm::tirx::attr::kIsHostFunc)) {
-    return true;
-  } else if (auto target = func->GetAttr<Target>(tvm::attr::kTarget)) {
-    return target.value()->HasKey("cpu");
-  } else {
-    return std::nullopt;
-  }
-}
-
 IRModule IRConvertSSA::VisitIRModule(IRModule mod) {
   ffi::Map<GlobalVar, BaseFunc> functions;
   bool made_change = false;
diff --git a/src/tirx/transform/ir_utils.h b/src/tirx/transform/ir_utils.h
index c9f5e45911..614fe5e7e9 100644
--- a/src/tirx/transform/ir_utils.h
+++ b/src/tirx/transform/ir_utils.h
@@ -38,7 +38,6 @@
 #include <tvm/tirx/stmt_functor.h>
 
 #include <functional>
-#include <limits>
 #include <optional>
 #include <string>
 #include <unordered_map>
@@ -49,118 +48,6 @@
 namespace tvm {
 namespace tirx {
 
-/*!
- * \brief Fill empty loop/conditional bodies and prepend statement prefixes.
- * \param nest Loops, conditionals with empty bodies, or Bind/Assert/SeqStmt 
prefixes.
- * \param body body
- * \return The combined Stmt
- */
-Stmt MergeNest(const std::vector<Stmt>& nest, Stmt body);
-
-/*!
- * \brief Fill empty loop/conditional bodies and prepend statement prefixes.
- * \param nest Groups of loops, conditionals with empty bodies, or statement 
prefixes.
- * \param body body
- * \return The combined Stmt
- */
-Stmt MergeNest(const std::vector<std::vector<Stmt>>& nest, Stmt body);
-
-/*!
- * \brief update array with an unary function
- * \param arr array
- * \param fupdate an unary function
- * \tparam T type of array element
- * \tparam F type of the unary function
- * \return if update happens, return the new array, else return the
- *  original array
- */
-template <typename T, typename F>
-inline ffi::Array<T> UpdateArray(ffi::Array<T> arr, F fupdate) {
-  std::vector<T> new_arr(arr.size());
-  bool changed = false;
-  for (size_t i = 0; i < arr.size(); ++i) {
-    T old_elem = arr[i];
-    T new_elem = fupdate(old_elem);
-    if (!new_elem.same_as(old_elem)) changed = true;
-    new_arr[i] = new_elem;
-  }
-  if (!changed) {
-    return arr;
-  } else {
-    return ffi::Array<T>(new_arr);
-  }
-}
-
-/*!
- * \brief Get construct from struct
- * \param dtype The data type.
- * \param handle the struct handle.
- * \param index the offset index.
- * \param kind The data kind.
- * \return the get expression.
- */
-inline Expr TVMStructGet(Type type, Var handle, int index, 
tirx::TVMStructFieldKind kind) {
-  ffi::Array<Expr> args = {handle, IntImm::Int32(index), 
IntImm::Int32(static_cast<int>(kind))};
-  return Call(std::move(type), tirx::abi_field_get_op(), args);
-}
-
-inline PrimExpr TVMStructGet(PrimType type, Var handle, int index, 
tirx::TVMStructFieldKind kind) {
-  return TVMStructGet(Type(type), std::move(handle), index, 
kind).as_or_throw<PrimExpr>();
-}
-
-/*!
- * \brief Address of handle + offset
- * \param handle the array handle.
- * \param dtype The data type.
- * \param offset the offset index.
- */
-inline Call AddressOffset(Var handle, PrimType dtype, int offset) {
-  PrimExpr offset_expr = IntImm::Int32(offset * dtype.lanes());
-  ffi::Array<PrimExpr> shape = {offset_expr + 1};
-  auto pointer_type = handle->ty.as_or_throw<PtrType>();
-  TensorVar dummy_buf(handle->name,
-                      TensorType(pointer_type->storage_scope, dtype, shape, 
{}, 0, 0, 0));
-  TensorLoad buf_load = MakeTensorLoad(dummy_buf, {offset_expr});
-
-  return Call(handle->ty, tirx::address_of_op(), {buf_load});
-}
-
-/*!
- * \brief Address of handle + offset
- * \param handle the array handle.
- * \param dtype The data type.
- * \param offset the offset index.
- */
-inline Call AddressOffset(Var handle, PrimType dtype, PrimExpr offset) {
-  if (dtype.lanes() != 1) {
-    PrimType offset_ty = offset.ty();
-    offset = offset * IntImm(offset_ty, dtype.lanes());
-    offset = prim::Ramp(offset, IntImm(offset_ty, 1), dtype.lanes());
-  }
-
-  ffi::Array<PrimExpr> shape = {offset + 1};
-  auto pointer_type = handle->ty.as_or_throw<PtrType>();
-  TensorVar dummy_buf(handle->name, TensorType(pointer_type->storage_scope, 
dtype.WithLanes(1),
-                                               shape, {}, 0, 0, 0));
-  TensorLoad buf_load = MakeTensorLoad(dummy_buf, {offset});
-
-  return Call(handle->ty, tirx::address_of_op(), {buf_load});
-}
-
-/*!
- * \brief Set value into struct.
- * \param handle the struct handle.
- * \param index the offset index.
- * \param kind The data kind.
- * \param value The value to be set.
- * \return the set stmt.
- */
-inline Stmt TVMStructSet(Var handle, int index, tirx::TVMStructFieldKind kind, 
Expr value) {
-  ffi::Array<Expr> args = {handle, IntImm::Int32(index), 
IntImm::Int32(static_cast<int>(kind)),
-                           value};
-  return Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(), 
args).as_or_throw<PrimExpr>());
-}
-
 /*!
  * \brief Get the type that is passed around TVM ffi::Function API.
  * \param t The original type.
@@ -198,28 +85,6 @@ inline int GetTempAllocaAlignment(const PrimType& type, 
int64_t const_size) {
   return align;
 }
 
-/*!
- * \brief Create an int32 constant
- * \param index the value of the constant
- * \return the PrimExpr that represents the constant
- */
-inline PrimExpr ConstInt32(size_t index) {
-  TVM_FFI_ICHECK_LE(index, std::numeric_limits<int>::max());
-  return IntImm::Int32(static_cast<int>(index));
-}
-
-/*!
- * \brief Allocate TVMValues on the stack
- * \param ret_type exact pointer type returned by the allocation
- * \param type type of allocation
- * \param num number of TVMValues to allocate
- * \return Call representing the allocated pointer
- */
-inline Call StackAlloca(Type ret_type, std::string type, size_t num) {
-  ffi::Array<Expr> args = {StringImm(type), ConstInt32(num)};
-  return Call(std::move(ret_type), tirx::stack_alloca_op(), args);
-}
-
 /*!
  * \brief Convert a IR node to be SSA form.
  * \param stmt The source statement to be converted.
@@ -309,40 +174,6 @@ class IRConvertSSA : public StmtExprMutator {
   ScopeStack<ScopeLevel> scope_;
 };
 
-/*!
- * \brief Return the storage scope associated with a buffer variable.
- * \param buffer_var The input buffer variable.
- * \return A string representing the storage scope of this buffer variable.
- */
-ffi::String GetPtrStorageScope(Var buffer_var);
-
-/*!
- * \brief Get stride aware tensor allocation shape from tensor.
- * \param tensor The tensor object.
- * \return shape The shape considering tensor strides.
- */
-ffi::Array<PrimExpr> GetTensorAllocationShape(const TensorVar& tensor);
-
-/*!
- * \brief Split string separated by "," to get wmma fragment dimension size.
- * \param  shape_str The string to split.
- * \param  scope The scope to match.
- * \return The result pair of fragment dimension size.
- */
-std::pair<int32_t, int32_t> GetWmmaFragmentDimSize(const std::string& 
shape_str,
-                                                   const std::string& scope);
-
-/*! \brief Check if a Function is a host function
- *
- * \param func The function to be inspected
- *
- * \return True if the function is known to run on the host, false if
- * the function is known to run on the device.  If it cannot be
- * determined (e.g. a function without a tvm::attr::kTarget
- * attribute), returns std::nullopt.
- */
-std::optional<bool> IsHostFunc(const Function& func);
-
 }  // namespace tirx
 }  // namespace tvm
 #endif  // TVM_TIR_TRANSFORM_IR_UTILS_H_
diff --git a/src/tirx/transform/lower_tvm_builtin.cc 
b/src/tirx/transform/lower_tvm_builtin.cc
index 386437bf75..ecea4e5a77 100644
--- a/src/tirx/transform/lower_tvm_builtin.cc
+++ b/src/tirx/transform/lower_tvm_builtin.cc
@@ -184,23 +184,28 @@ class BuiltinLower : public StmtExprMutator {
       if (scope.max_sizes.arg_stack != 0) {
         alloca_stmts.push_back(
             Bind(scope.stack_ffi_any,
-                 StackAlloca(scope.stack_ffi_any->ty, "tvm_ffi_any", 
scope.max_sizes.arg_stack)));
+                 Call(scope.stack_ffi_any->ty, tirx::stack_alloca_op(),
+                      {StringImm("tvm_ffi_any"), 
IntImm::Int32(scope.max_sizes.arg_stack)})));
       }
 
       if (scope.max_sizes.array_stack != 0) {
-        alloca_stmts.push_back(Bind(scope.stack_array, 
StackAlloca(scope.stack_array->ty, "array",
-                                                                   
scope.max_sizes.array_stack)));
+        alloca_stmts.push_back(
+            Bind(scope.stack_array,
+                 Call(scope.stack_array->ty, tirx::stack_alloca_op(),
+                      {StringImm("array"), 
IntImm::Int32(scope.max_sizes.array_stack)})));
       }
 
       if (scope.max_sizes.shape_stack != -1) {
         scope.stack_shape = 
decl_tensor({IntImm::Int64(scope.max_sizes.shape_stack)},
                                         PrimType::Int(64), "stack_shape");
-        stmt = SeqStmt({Bind(scope.stack_shape.value(),
-                             Call(scope.stack_shape.value().type(), 
decl_tensor_op(),
-                                  
{StackAlloca(scope.stack_shape.value().type()->DataPtrType(),
-                                               "shape", 
scope.max_sizes.shape_stack)},
-                                  {}, {scope.stack_shape.value().type()})),
-                        stmt});
+        stmt = SeqStmt(
+            {Bind(scope.stack_shape.value(),
+                  Call(scope.stack_shape.value().type(), decl_tensor_op(),
+                       {Call(scope.stack_shape.value().type()->DataPtrType(),
+                             tirx::stack_alloca_op(),
+                             {StringImm("shape"), 
IntImm::Int32(scope.max_sizes.shape_stack)})},
+                       {}, {scope.stack_shape.value().type()})),
+             stmt});
       }
 
       if (!alloca_stmts.empty()) {
@@ -491,10 +496,10 @@ class BuiltinLower : public StmtExprMutator {
     // no need to perform any store for a scalar shape
     for (size_t i = 0; i < op->args.size(); ++i) {
       prep_seq.emplace_back(
-          TensorStore(scope.stack_shape.value(), {ConstInt32(stack_begin + i)},
+          TensorStore(scope.stack_shape.value(), {IntImm::Int32(stack_begin + 
i)},
                       prim::cast(PrimType::Int(64), 
op->args[i].as_or_throw<PrimExpr>())));
     }
-    PrimExpr offset = ConstInt32(stack_begin);
+    PrimExpr offset = IntImm::Int32(stack_begin);
     TensorLoad load = MakeTensorLoad(scope.stack_shape.value(), {offset});
     return Call(scope.stack_shape.value().type()->DataPtrType(), 
address_of_op(), {load});
   }
@@ -510,22 +515,43 @@ class BuiltinLower : public StmtExprMutator {
                     .ValueOrUnchanged(ffi::GetRef<Expr>(op));
     op = expr.as<CallNode>();
 
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, kDLTensorData, 
op->args[0]));
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, kDLTensorShape, 
op->args[1]));
+    prep_seq.emplace_back(Evaluate(
+        Call(PrimType::Int(32), tirx::abi_field_set_op(),
+             {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorData), op->args[0]})
+            .as_or_throw<PrimExpr>()));
+    prep_seq.emplace_back(Evaluate(
+        Call(PrimType::Int(32), tirx::abi_field_set_op(),
+             {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorShape), op->args[1]})
+            .as_or_throw<PrimExpr>()));
     Expr strides = op->args[2];
     if (auto prim_strides = strides.as<PrimExpr>(); prim_strides && 
IsZero(prim_strides.value())) {
       strides = tirx::ConstHandle(0);
     }
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, 
kDLTensorStrides, strides));
+    prep_seq.emplace_back(Evaluate(
+        Call(PrimType::Int(32), tirx::abi_field_set_op(),
+             {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorStrides), strides})
+            .as_or_throw<PrimExpr>()));
     prep_seq.emplace_back(
-        TVMStructSet(scope.stack_array, idx, kDLTensorNDim, 
op->args[3].as_or_throw<PrimExpr>()));
+        Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorNDim),
+                       op->args[3].as_or_throw<PrimExpr>()})
+                     .as_or_throw<PrimExpr>()));
     PrimType dtype = op->args[4].as_or_throw<PrimExpr>().ty();
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, 
kDLTensorTypeCode,
-                                       IntImm(PrimType::UInt(8), 
static_cast<int>(dtype.code()))));
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, 
kDLTensorTypeBits,
-                                       IntImm(PrimType::UInt(8), 
dtype.bits())));
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, 
kDLTensorTypeLanes,
-                                       IntImm(PrimType::UInt(16), 
dtype.lanes())));
+    prep_seq.emplace_back(
+        Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorTypeCode),
+                       IntImm(PrimType::UInt(8), 
static_cast<int>(dtype.code()))})
+                     .as_or_throw<PrimExpr>()));
+    prep_seq.emplace_back(
+        Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorTypeBits),
+                       IntImm(PrimType::UInt(8), dtype.bits())})
+                     .as_or_throw<PrimExpr>()));
+    prep_seq.emplace_back(
+        Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorTypeLanes),
+                       IntImm(PrimType::UInt(16), dtype.lanes())})
+                     .as_or_throw<PrimExpr>()));
     // set byte offset
     int data_bytes = GetVectorBytes(dtype);
     PrimExpr elem_offset = op->args[5].as_or_throw<PrimExpr>();
@@ -535,15 +561,25 @@ class BuiltinLower : public StmtExprMutator {
     } else {
       byte_offset = elem_offset;
     }
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, 
kDLTensorByteOffset,
-                                       prim::cast(PrimType::UInt(64), 
byte_offset)));
+    prep_seq.emplace_back(
+        Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorByteOffset),
+                       prim::cast(PrimType::UInt(64), byte_offset)})
+                     .as_or_throw<PrimExpr>()));
     TVM_FFI_ICHECK(device_type_) << "Unknown device type in current IR";
     TVM_FFI_ICHECK(device_id_) << "Unknown device id in current IR";
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, 
kDLTensorDeviceId,
-                                       prim::cast(PrimType::Int(32), 
device_id_.value())));
-    prep_seq.emplace_back(TVMStructSet(scope.stack_array, idx, 
kDLTensorDeviceType,
-                                       prim::cast(PrimType::Int(32), 
device_type_.value())));
-    return TVMStructGet(PtrType::VoidPtrType(), scope.stack_array, idx, 
kDLTensorAddr);
+    prep_seq.emplace_back(
+        Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorDeviceId),
+                       prim::cast(PrimType::Int(32), device_id_.value())})
+                     .as_or_throw<PrimExpr>()));
+    prep_seq.emplace_back(
+        Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorDeviceType),
+                       prim::cast(PrimType::Int(32), device_type_.value())})
+                     .as_or_throw<PrimExpr>()));
+    return Call(PtrType::VoidPtrType(), tirx::abi_field_get_op(),
+                {scope.stack_array, IntImm::Int32(idx), 
IntImm::Int32(kDLTensorAddr)});
   }
 
   void SetPackedArg(Expr arg, const Var& args_stack, size_t stack_offset,
@@ -577,21 +613,35 @@ class BuiltinLower : public StmtExprMutator {
     if (arg_type_index == ffi::TypeIndex::kTVMFFIOpaquePtr) {
       prep_seq->emplace_back(
           If(Call(PrimType::Bool(), isnullptr_op(), 
{arg}).as_or_throw<PrimExpr>(),
-             TVMStructSet(args_stack, stack_offset, kTVMFFIAnyTypeIndex,
-                          ConstInt32(ffi::TypeIndex::kTVMFFINone)),
-             SeqStmt(TVMStructSet(args_stack, stack_offset, 
kTVMFFIAnyTypeIndex,
-                                  
ConstInt32(ffi::TypeIndex::kTVMFFIOpaquePtr)))));
+             Evaluate(
+                 Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {args_stack, IntImm::Int32(stack_offset), 
IntImm::Int32(kTVMFFIAnyTypeIndex),
+                       IntImm::Int32(ffi::TypeIndex::kTVMFFINone)})
+                     .as_or_throw<PrimExpr>()),
+             SeqStmt(Evaluate(
+                 Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                      {args_stack, IntImm::Int32(stack_offset), 
IntImm::Int32(kTVMFFIAnyTypeIndex),
+                       IntImm::Int32(ffi::TypeIndex::kTVMFFIOpaquePtr)})
+                     .as_or_throw<PrimExpr>()))));
     } else {
       prep_seq->emplace_back(
-          TVMStructSet(args_stack, stack_offset, kTVMFFIAnyTypeIndex, 
ConstInt32(arg_type_index)));
+          Evaluate(Call(PrimType::Int(32), tirx::abi_field_set_op(),
+                        {args_stack, IntImm::Int32(stack_offset),
+                         IntImm::Int32(kTVMFFIAnyTypeIndex), 
IntImm::Int32(arg_type_index)})
+                       .as_or_throw<PrimExpr>()));
     }
     // set zero padding to ensure compatibility with FFI convention
-    prep_seq->emplace_back(
-        TVMStructSet(args_stack, stack_offset, kTVMFFIAnyZeroPadding, 
ConstInt32(0)));
+    prep_seq->emplace_back(Evaluate(Call(PrimType::Int(32), 
tirx::abi_field_set_op(),
+                                         {args_stack, 
IntImm::Int32(stack_offset),
+                                          
IntImm::Int32(kTVMFFIAnyZeroPadding), IntImm::Int32(0)})
+                                        .as_or_throw<PrimExpr>()));
     // handle arg value
     // NOTE: the intrinsic codegen will handle padding value clear for 32bit
     // types or types that are smaller than 64 bits.
-    prep_seq->emplace_back(TVMStructSet(args_stack, stack_offset, 
kTVMFFIAnyUnionValue, arg));
+    prep_seq->emplace_back(Evaluate(
+        Call(PrimType::Int(32), tirx::abi_field_set_op(),
+             {args_stack, IntImm::Int32(stack_offset), 
IntImm::Int32(kTVMFFIAnyUnionValue), arg})
+            .as_or_throw<PrimExpr>()));
   }
 
   /*!
@@ -631,12 +681,19 @@ class BuiltinLower : public StmtExprMutator {
       this->SetPackedArg(arg, scope.stack_ffi_any, arg_stack_begin + i, 
&prep_seq);
     }
     // explicitly set return value to None to avoid bad state interpretation
-    prep_seq.emplace_back(TVMStructSet(scope.stack_ffi_any, num_args, 
kTVMFFIAnyTypeIndex,
-                                       
ConstInt32(ffi::TypeIndex::kTVMFFINone)));
-    prep_seq.emplace_back(
-        TVMStructSet(scope.stack_ffi_any, num_args, kTVMFFIAnyZeroPadding, 
ConstInt32(0)));
-    prep_seq.emplace_back(
-        TVMStructSet(scope.stack_ffi_any, num_args, kTVMFFIAnyUnionValue, 
IntImm::Int64(0)));
+    prep_seq.emplace_back(Evaluate(
+        Call(PrimType::Int(32), tirx::abi_field_set_op(),
+             {scope.stack_ffi_any, IntImm::Int32(num_args), 
IntImm::Int32(kTVMFFIAnyTypeIndex),
+              IntImm::Int32(ffi::TypeIndex::kTVMFFINone)})
+            .as_or_throw<PrimExpr>()));
+    prep_seq.emplace_back(Evaluate(Call(PrimType::Int(32), 
tirx::abi_field_set_op(),
+                                        {scope.stack_ffi_any, 
IntImm::Int32(num_args),
+                                         IntImm::Int32(kTVMFFIAnyZeroPadding), 
IntImm::Int32(0)})
+                                       .as_or_throw<PrimExpr>()));
+    prep_seq.emplace_back(Evaluate(Call(PrimType::Int(32), 
tirx::abi_field_set_op(),
+                                        {scope.stack_ffi_any, 
IntImm::Int32(num_args),
+                                         IntImm::Int32(kTVMFFIAnyUnionValue), 
IntImm::Int64(0)})
+                                       .as_or_throw<PrimExpr>()));
     // Verify stack size matches earlier value.
     if (is_precheck_) {
       scope.UpdateMax();
@@ -647,8 +704,8 @@ class BuiltinLower : public StmtExprMutator {
     scope.run_sizes.array_stack = restore_array_stack;
     scope.run_sizes.arg_stack = arg_stack_begin;
     ffi::Array<Expr> packed_args = {op->args[name_offset], scope.stack_ffi_any,
-                                    ConstInt32(arg_stack_begin),
-                                    ConstInt32(arg_stack_begin + num_args)};
+                                    IntImm::Int32(arg_stack_begin),
+                                    IntImm::Int32(arg_stack_begin + num_args)};
     return Call(op->ty, lowered_packed_op, packed_args);
   }
 
@@ -753,7 +810,13 @@ namespace transform {
 
 Pass LowerTVMBuiltin() {
   auto pass_func = [](Function func, IRModule m, PassContext ctx) {
-    if (IsHostFunc(func).value_or(false)) {
+    bool is_host = func->HasNonzeroAttr(tvm::tirx::attr::kIsHostFunc);
+    if (!is_host) {
+      if (auto target = func->GetAttr<Target>(tvm::attr::kTarget)) {
+        is_host = target.value()->HasKey("cpu");
+      }
+    }
+    if (is_host) {
       func = BuiltinLower::Build(func);
       VLOG(2) << "LowerTVMBuiltin: " << func;
     }
diff --git a/src/tirx/transform/make_packed_api.cc 
b/src/tirx/transform/make_packed_api.cc
index 0cca17b74d..3fbfd66e58 100644
--- a/src/tirx/transform/make_packed_api.cc
+++ b/src/tirx/transform/make_packed_api.cc
@@ -294,15 +294,20 @@ Function MakePackedAPI(Function func) {
   // Return error code of zero on success
   body = SeqStmt({body, Return(IntImm::Int32(0))});
 
-  // Tensor declarations and alignment assumptions are ordinary statements,
-  // not scopes with a body hole for MergeNest to fill.
+  // Prepend tensor declarations and alignment assumptions.
   result.decl_tensors.push_back(body);
   body = SeqStmt(result.decl_tensors);
-  body = MergeNest(std::move(result.asserts), body);
+  if (!result.asserts.empty()) {
+    result.asserts.push_back(body);
+    body = SeqStmt(result.asserts);
+  }
   if (need_set_device) {
     body = RegionStmt(tirx::device_context_op(), {device_type, device_id}, {}, 
DictAttrs(), body);
   }
-  body = MergeNest(std::move(result.init_nest), body);
+  if (!result.init_nest.empty()) {
+    result.init_nest.push_back(body);
+    body = SeqStmt(result.init_nest);
+  }
   func_ptr->body = body;
   func_ptr->params = args;
 
diff --git a/src/tirx/transform/storage_rewrite.cc 
b/src/tirx/transform/storage_rewrite.cc
index 4094f7e84d..5b62aacd0d 100644
--- a/src/tirx/transform/storage_rewrite.cc
+++ b/src/tirx/transform/storage_rewrite.cc
@@ -47,7 +47,6 @@
 
 #include "../../runtime/thread_storage_scope.h"
 #include "../../sym/int_operator.h"
-#include "../ir/pointer_type.h"
 #include "ir_utils.h"
 
 namespace tvm {
@@ -825,7 +824,11 @@ class StoragePlanRewriter : public StmtExprMutator {
 
   Stmt MakeAttach(const std::vector<StorageEntry*>& svec, Stmt body) {
     for (auto it = svec.rbegin(); it != svec.rend(); it++) {
-      body = MergeNest((*it)->alloc_nest, body);
+      if (!(*it)->alloc_nest.empty()) {
+        ffi::Array<Stmt> prefix((*it)->alloc_nest);
+        prefix.push_back(body);
+        body = SeqStmt(prefix);
+      }
     }
     return body;
   }
@@ -1450,12 +1453,15 @@ class VectorTypeAccessChecker : public StmtExprVisitor {
                            BufferVarInfo::kFunctionTensorParam);
         continue;
       }
-      auto pointer_type = GetPtrElementType(buffer_var->ty);
-      if (pointer_type.has_value() && !pointer_type.value().IsVoid()) {
-        PrimType dtype = pointer_type.value();
+      const auto* pointer_type = buffer_var->ty.as<PtrTypeNode>();
+      if (!pointer_type) continue;
+      const auto* element_type = pointer_type->element_type.as<PrimTypeNode>();
+      if (!element_type) continue;
+      PrimType dtype = ffi::GetRef<PrimType>(element_type);
+      if (!dtype.IsVoid()) {
         PrimExpr extent = 0;
         OnArrayDeclaration(buffer_var, dtype, extent, 
BufferVarInfo::kFunctionPointerParam);
-      } else if (pointer_type.has_value() && allow_untyped_pointers_) {
+      } else if (allow_untyped_pointers_) {
         OnArrayDeclaration(buffer_var, PrimType::Void(), 0, 
BufferVarInfo::kFunctionPointerParam);
       }
     }
@@ -1536,14 +1542,15 @@ class VectorTypeAccessChecker : public StmtExprVisitor {
   }
 
   void HandleLetNode(Var let_var) {
-    auto pointer_type = GetPtrElementType(let_var->ty);
-    if (pointer_type.has_value()) {
-      if (!pointer_type.value().IsVoid()) {
-        OnArrayDeclaration(let_var, pointer_type.value(), 0, 
BufferVarInfo::kLetNode);
-      } else if (allow_untyped_pointers_) {
-        OnArrayDeclaration(let_var, PrimType::Void(), 0, 
BufferVarInfo::kLetNode);
-      }
-      return;
+    const auto* pointer_type = let_var->ty.as<PtrTypeNode>();
+    if (!pointer_type) return;
+    const auto* element_type = pointer_type->element_type.as<PrimTypeNode>();
+    if (!element_type) return;
+    PrimType dtype = ffi::GetRef<PrimType>(element_type);
+    if (!dtype.IsVoid()) {
+      OnArrayDeclaration(let_var, dtype, 0, BufferVarInfo::kLetNode);
+    } else if (allow_untyped_pointers_) {
+      OnArrayDeclaration(let_var, PrimType::Void(), 0, 
BufferVarInfo::kLetNode);
     }
   }
 
@@ -1792,7 +1799,8 @@ class VectorTypeRewriter : public StmtExprMutator {
             type->layout = std::nullopt;
             return RebuildTensorVar(old_tensor, std::move(type)).var();
           }
-          return Var(old_tensor_var->name, PtrType(preferred, 
GetPtrStorageScope(old_tensor_var)),
+          return Var(old_tensor_var->name,
+                     PtrType(preferred, 
old_tensor_var->ty.as_or_throw<PtrType>()->storage_scope),
                      old_tensor_var->loc);
         }();
 
diff --git a/src/tirx/transform/tvm_ffi_binder.cc 
b/src/tirx/transform/tvm_ffi_binder.cc
index 017967b713..c978d0c748 100644
--- a/src/tirx/transform/tvm_ffi_binder.cc
+++ b/src/tirx/transform/tvm_ffi_binder.cc
@@ -650,9 +650,8 @@ Var TVMFFIABIBuilder::DLTensorGetFieldPtr(const Var& 
handle, int field_kind,
                                           const std::string& var_name) {
   Type pointer_type = PtrType(DefaultIndexPrimType());
   Var ptr(var_name, pointer_type);
-  init_nest_.emplace_back(Bind(
-      ptr,
-      TVMStructGet(pointer_type, handle, 0, 
static_cast<tirx::TVMStructFieldKind>(field_kind))));
+  init_nest_.emplace_back(Bind(ptr, Call(pointer_type, 
tirx::abi_field_get_op(),
+                                         {handle, IntImm::Int32(0), 
IntImm::Int32(field_kind)})));
   return ptr;
 }
 
@@ -661,7 +660,9 @@ Var TVMFFIABIBuilder::DLTensorGetFieldPtr(const Var& 
handle, int field_kind,
 // ============================================================
 
 PrimExpr TVMFFIABIBuilder::LoadInt64ArrayElem(const Var& ptr, int index) {
-  return TVMStructGet(DefaultIndexPrimType(), ptr, index, 
tirx::kInt64ArrayElem);
+  return Call(DefaultIndexPrimType(), tirx::abi_field_get_op(),
+              {ptr, IntImm::Int32(index), 
IntImm::Int32(tirx::kInt64ArrayElem)})
+      .as_or_throw<PrimExpr>();
 }
 
 // ============================================================
@@ -730,7 +731,9 @@ Expr TVMFFIABIBuilder::DecodeParamDLTensor(const TensorVar& 
tensor, const PrimEx
       "Tensor");
 
   // ── Section: ndim ────────────────────────────────────────────
-  PrimExpr v_ndim = TVMStructGet(tvm_ndim_type, handle, 0, 
tirx::kDLTensorNDim);
+  PrimExpr v_ndim = Call(tvm_ndim_type, tirx::abi_field_get_op(),
+                         {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorNDim)})
+                        .as_or_throw<PrimExpr>();
   PrimExpr a_ndim = IntImm(tvm_ndim_type, 
static_cast<int64_t>(tensor->shape.size()));
   EmitAssert(a_ndim == v_ndim, "ValueError",  //
              "Mismatched ", buf_name, ".ndim on argument #", 
std::to_string(param_index),
@@ -738,13 +741,18 @@ Expr TVMFFIABIBuilder::DecodeParamDLTensor(const 
TensorVar& tensor, const PrimEx
 
   // ── Section: dtype ───────────────────────────────────────────
   {
-    PrimExpr code_matches = TVMStructGet(PrimType::UInt(8), handle, 0, 
tirx::kDLTensorTypeCode) ==
-                            IntImm(PrimType::UInt(8), tensor->dtype.code());
-    PrimExpr bits_matches = TVMStructGet(PrimType::UInt(8), handle, 0, 
tirx::kDLTensorTypeBits) ==
-                            IntImm(PrimType::UInt(8), tensor->dtype.bits());
+    PrimExpr code_matches =
+        Call(PrimType::UInt(8), tirx::abi_field_get_op(),
+             {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorTypeCode)})
+            .as_or_throw<PrimExpr>() == IntImm(PrimType::UInt(8), 
tensor->dtype.code());
+    PrimExpr bits_matches =
+        Call(PrimType::UInt(8), tirx::abi_field_get_op(),
+             {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorTypeBits)})
+            .as_or_throw<PrimExpr>() == IntImm(PrimType::UInt(8), 
tensor->dtype.bits());
     PrimExpr lanes_matches =
-        TVMStructGet(PrimType::UInt(16), handle, 0, tirx::kDLTensorTypeLanes) 
==
-        IntImm(PrimType::UInt(16), tensor->dtype.lanes());
+        Call(PrimType::UInt(16), tirx::abi_field_get_op(),
+             {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorTypeLanes)})
+            .as_or_throw<PrimExpr>() == IntImm(PrimType::UInt(16), 
tensor->dtype.lanes());
     PrimExpr cond = code_matches && bits_matches && lanes_matches;
     if (!(tensor->dtype == PrimType::Int(1) || tensor->dtype == 
PrimType::Int(4) ||
           tensor->dtype == PrimType::UInt(4))) {
@@ -783,16 +791,23 @@ Expr TVMFFIABIBuilder::DecodeParamDLTensor(const 
TensorVar& tensor, const PrimEx
   ffi::reflection::AccessPath byte_offset_path = 
param_path->Attr(ffi::String("byte_offset"));
   if (const auto* const_offset = tensor->elem_offset.as<IntImmNode>()) {
     BindScalar(IntImm(PrimType::UInt(64), const_offset->value * data_bytes),
-               TVMStructGet(PrimType::UInt(64), handle, 0, 
tirx::kDLTensorByteOffset),
+               Call(PrimType::UInt(64), tirx::abi_field_get_op(),
+                    {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorByteOffset)})
+                   .as_or_throw<PrimExpr>(),
                byte_offset_path, true);
   } else {
-    PrimExpr byte_offset = TVMStructGet(PrimType::UInt(64), handle, 0, 
tirx::kDLTensorByteOffset);
+    PrimExpr byte_offset =
+        Call(PrimType::UInt(64), tirx::abi_field_get_op(),
+             {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorByteOffset)})
+            .as_or_throw<PrimExpr>();
     EmitAssert(truncmod(byte_offset, MakeConst(PrimType::UInt(64), 
data_bytes)) ==
                    MakeConst(PrimType::UInt(64), 0),
                "ValueError", "Tensor byte offset must be divisible by its 
element size");
     if (BindScalar(tensor->elem_offset,
                    cast(tensor->elem_offset.ty(),
-                        (TVMStructGet(PrimType::UInt(64), handle, 0, 
tirx::kDLTensorByteOffset) /
+                        (Call(PrimType::UInt(64), tirx::abi_field_get_op(),
+                              {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorByteOffset)})
+                             .as_or_throw<PrimExpr>() /
                          MakeConst(PrimType::UInt(64), data_bytes))),
                    byte_offset_path, true)) {
       if (tensor->offset_factor > 1) {
@@ -817,7 +832,9 @@ Expr TVMFFIABIBuilder::DecodeParamDLTensor(const TensorVar& 
tensor, const PrimEx
   // ── Section: device ──────────────────────────────────────────
   {
     PrimExpr actual_device_type =
-        TVMStructGet(PrimType::Int(32), handle, 0, tirx::kDLTensorDeviceType);
+        Call(PrimType::Int(32), tirx::abi_field_get_op(),
+             {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorDeviceType)})
+            .as_or_throw<PrimExpr>();
     // Use custom assertion for device_type to show human-readable device name
     if (const auto* const_dt = device_type_.as<IntImmNode>()) {
       PrimExpr cond = analyzer_->Simplify(IntImm::Int32(const_dt->value) == 
actual_device_type);
@@ -833,14 +850,18 @@ Expr TVMFFIABIBuilder::DecodeParamDLTensor(const 
TensorVar& tensor, const PrimEx
       BindScalar(device_type_, actual_device_type, device_type_path, true);
     }
     ffi::reflection::AccessPath device_id_path = 
param_path->Attr(ffi::String("device_id"));
-    BindScalar(device_id_, TVMStructGet(PrimType::Int(32), handle, 0, 
tirx::kDLTensorDeviceId),
+    BindScalar(device_id_,
+               Call(PrimType::Int(32), tirx::abi_field_get_op(),
+                    {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorDeviceId)})
+                   .as_or_throw<PrimExpr>(),
                device_id_path, true);
   }
 
   // ── Section: data pointer ────────────────────────────────────
   {
     ffi::reflection::AccessPath data_path = 
param_path->Attr(ffi::String("data"));
-    Expr raw_data = TVMStructGet(PtrType::VoidPtrType(), handle, 0, 
tirx::kDLTensorData);
+    Expr raw_data = Call(PtrType::VoidPtrType(), tirx::abi_field_get_op(),
+                         {handle, IntImm::Int32(0), 
IntImm::Int32(tirx::kDLTensorData)});
     Expr typed_data = Call(tensor.type()->DataPtrType(), 
tirx::reinterpret_op(), {raw_data});
     {
       Expr vptr = typed_data;

Reply via email to