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 94613dc701 [REFACTOR][TIRx] Replace legacy specialization with 
structural mapping (#20787)
94613dc701 is described below

commit 94613dc701c1a4f6b5caa40b573f42dddb0577f8
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Oct 18 13:20:18 2026 -0400

    [REFACTOR][TIRx] Replace legacy specialization with structural mapping 
(#20787)
---
 .../specialize_function_based_on_callsite.cc       |  62 ++-
 tirx/include/tvm/tirx/function.h                   |  42 +-
 tirx/python/tvm_tirx/tirx/function.py              |  60 +--
 tirx/src/tirx/ir/function.cc                       |   7 +-
 tirx/src/tirx/ir/specialize.cc                     | 466 ---------------------
 tirx/tests/python/tirx/base/test_specialize.py     | 410 ------------------
 tirx/tests/python/tirx/ir/__init__.py              |  16 +
 tirx/tests/python/tirx/ir/test_function.py         |  65 +++
 8 files changed, 146 insertions(+), 982 deletions(-)

diff --git a/src/relax/transform/specialize_function_based_on_callsite.cc 
b/src/relax/transform/specialize_function_based_on_callsite.cc
index 457a3d72ce..4c7776769c 100644
--- a/src/relax/transform/specialize_function_based_on_callsite.cc
+++ b/src/relax/transform/specialize_function_based_on_callsite.cc
@@ -21,6 +21,7 @@
  * \brief Update tirx::Function tensors based on updated scope (or structure) 
info.
  */
 
+#include <tvm/ffi/extra/structural_mutate.h>
 #include <tvm/relax/expr.h>
 #include <tvm/relax/expr_functor.h>
 #include <tvm/relax/nested_msg.h>
@@ -28,6 +29,7 @@
 #include <tvm/relax/op_attr.h>
 #include <tvm/relax/transform.h>
 #include <tvm/tirx/index_map.h>
+#include <tvm/tirx/op/memory.h>
 
 #include <tuple>
 
@@ -82,7 +84,7 @@ class SpecializeTIRCallArgs : ExprMutator {
     auto gv = call->args[0].as_or_throw<GlobalVar>();
     auto pfunc = mod_->Lookup(gv).as_or_throw<tirx::Function>();
     auto args = call->args[1].as_or_throw<Tuple>()->fields;
-    ffi::Map<tvm::Var, ffi::Variant<TensorVar, Expr>> param_map;
+    ffi::Map<tvm::Var, TensorVar> param_map;
 
     for (size_t i = 0; i < args.size(); ++i) {
       auto ty = GetType(args[i]);
@@ -131,6 +133,7 @@ class SpecializeTIRCallArgs : ExprMutator {
                "output structinfo, but got "
             << si;
         auto ty = si.as_or_throw<TensorType>();
+        scope = runtime::MemoryScope();
         if (ty->vdevice.has_value()) {
           scope = ty->vdevice.value()->memory_scope;
         }
@@ -143,7 +146,62 @@ class SpecializeTIRCallArgs : ExprMutator {
       }
     }
 
-    auto new_pfunc = Specialize(pfunc, param_map);
+    // call_tir supplies dense tensor descriptors. Bind their symbolic 
dimensions
+    // explicitly, retaining each parameter's access contract and storage 
scope.
+    sym::Analyzer analyzer;
+    ffi::Map<tvm::Var, Expr> replacements;
+    auto bind = [&](const PrimExpr& symbolic, const PrimExpr& concrete) {
+      if (analyzer->CanProveEqual(symbolic, concrete)) return;
+      auto var = symbolic.as<PrimVar>();
+      TVM_FFI_CHECK(var.has_value() && !replacements.count(var.value()), 
ValueError)
+          << "Inconsistent call_tir tensor dimension: " << symbolic << " vs. " 
<< concrete;
+      replacements.Set(var.value(), concrete);
+      analyzer->Bind(var.value(), concrete);
+    };
+    for (const auto& param : pfunc->params) {
+      auto descriptor = param_map.Get(param);
+      if (!descriptor) continue;
+      auto tensor = param.as_or_throw<TensorVar>();
+      auto actual = descriptor.value();
+      TVM_FFI_CHECK_EQ(tensor.shape().size(), actual.shape().size(), 
ValueError);
+      TVM_FFI_CHECK(tensor.ty()->dtype == actual.ty()->dtype, ValueError);
+      for (size_t i = 0; i < tensor.shape().size(); ++i) {
+        bind(tensor.shape()[i], actual.shape()[i]);
+      }
+      auto source = tirx::MatchShapeStridePattern(tensor.ty());
+      auto target = tirx::MatchShapeStridePattern(actual.ty());
+      TVM_FFI_CHECK(source.has_value() && target.has_value(), ValueError)
+          << "call_tir requires shape-aligned strided tensor layouts";
+      for (size_t i = 0; i < source.value().get<1>().size(); ++i) {
+        bind(source.value().get<1>()[i], target.value().get<1>()[i]);
+      }
+      bind(source.value().get<2>(), target.value().get<2>());
+      // Preserve layout and alignment; the Relax descriptor only chooses 
scope.
+      auto ty = tensor.ty();
+      replacements.Set(param, TensorVar(param->name,
+                                        tirx::TensorType(ty->shape, ty->dtype, 
actual.scope(),
+                                                         ty->data_alignment, 
ty->layout, ty->loc),
+                                        param->loc));
+    }
+    ffi::Array<tvm::Var> params;
+    for (const auto& param : pfunc->params) {
+      if (!param.as<PrimVar>() || !replacements.count(param)) 
params.push_back(param);
+    }
+    auto signature = tirx::Function(params, pfunc->body, pfunc->ret_type, 
pfunc->attrs, pfunc->loc);
+    auto mapped = ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(
+        signature,
+        [&](const tvm::Var& var) -> Expr { return 
replacements.Get(var).value_or(var); });
+    auto new_pfunc = ffi::StructuralMap<ffi::WalkOrder::kPostOrder>(
+                         mapped,
+                         [](const tvm::Call& call) {
+                           if (!call->op.same_as(tirx::tensor_data_ptr_op())) 
return call;
+                           // Pointer projections inherit the newly bound 
tensor storage scope.
+                           auto copy = 
ffi::make_object<tvm::CallNode>(*call.get());
+                           copy->ty = tvm::Call::ReinferType(copy.get());
+                           return tvm::Call(std::move(copy));
+                         },
+                         [&](const PrimExpr& expr) { return 
analyzer->Simplify(expr); })
+                         .as_or_throw<tirx::Function>();
     auto new_function =
         WithAttr(new_pfunc, tvm::relax::attr::kScoped.name, 
static_cast<int64_t>(1));
     updates_->Add(gv, new_function);
diff --git a/tirx/include/tvm/tirx/function.h b/tirx/include/tvm/tirx/function.h
index 411412c23f..75d751276f 100644
--- a/tirx/include/tvm/tirx/function.h
+++ b/tirx/include/tvm/tirx/function.h
@@ -25,7 +25,6 @@
 #define TVM_TIRX_FUNCTION_H_
 
 #include <tvm/ffi/container/map.h>
-#include <tvm/ffi/container/variant.h>
 #include <tvm/ffi/extra/structural_equal.h>
 #include <tvm/ffi/extra/structural_hash.h>
 #include <tvm/ir/cow.h>
@@ -70,9 +69,7 @@ class FunctionNode : public BaseFuncNode {
 
   bool SEqual(const FunctionNode* other,
               ffi::TypedFunction<bool(AnyView, AnyView, bool, AnyView)> equal) 
const {
-    // `ty` is derived from the fields below.  Function transformations update
-    // those source fields without maintaining this redundant cache eagerly.
-    // Remove this exception once all Function mutation paths recompute `ty`.
+    // `ty` is derived from the signature; compare its source fields directly.
     return equal(attrs, other->attrs, false, "attrs") &&
            equal(params, other->params, true, "params") &&
            equal(ret_type, other->ret_type, false, "ret_type") &&
@@ -140,43 +137,6 @@ class Function : public BaseFunc {
  */
 TVM_TIRX_DLL Function RenewDef(Function func);
 
-/*!
- * \brief Specialize parameters of Function.
- * \param func The Function to be specialized.
- * \param param_map The mapping from function params to the instance.
- * \return The new function with parameter specialized.
- * \note We can define a TIRx function with symbolic shape:
- *
- * \code{.py}
- *  from __future__ import annotations
- *
- *  @T.function
- *  def mem_copy(A: T.Tensor((m, n), "float32"), B: T.Tensor((m, n), 
"float32"),
- *               m: T.int32, n: T.int32) -> None:
- *      for i, j in T.grid(m, n):
- *          B[i, j] = A[i, j]
- * \endcode
- *
- * Then we can make it specialized with given shapes or tensors.
- *
- * \code{.py}
- *  a, _, m, n = mem_copy.params
- *  func = mem_copy.specialize({a: tirx.decl_tensor((16, 16))})
- *  # or
- *  func = mem_copy.specialize({n: 16, m: 16})
- * \endcode
- *
- * \code{.py}
- *  @T.function
- *  def mem_copy_16_16(A: T.Tensor((16, 16), "float32"),
- *                     B: T.Tensor((16, 16), "float32")) -> None:
- *      for i, j in T.grid(16, 16):
- *          B[i, j] = A[i, j]
- * \endcode
- */
-TVM_TIRX_DLL Function Specialize(Function func,
-                                 const ffi::Map<Var, ffi::Variant<TensorVar, 
Expr>>& param_map);
-
 /*!
  * \brief Function specific attribute names.
  *
diff --git a/tirx/python/tvm_tirx/tirx/function.py 
b/tirx/python/tvm_tirx/tirx/function.py
index 9ce3bd1487..8b06d4678d 100644
--- a/tirx/python/tvm_tirx/tirx/function.py
+++ b/tirx/python/tvm_tirx/tirx/function.py
@@ -17,7 +17,7 @@
 # pylint: disable=unrecognized-inline-option
 """Function data types."""
 
-from collections.abc import Mapping, Sequence
+from collections.abc import Sequence
 
 import tvm_ffi
 
@@ -27,7 +27,6 @@ from tvm_tirx.ir import BaseFunc, Scriptable, SeqStmt, Stmt, 
Var
 from tvm_tirx.ir.location import UNKNOWN_LOC, Location
 
 from . import _ffi_api
-from .expr import Expr
 
 
 @tvm_ffi.register_object("tirx.Function")
@@ -107,63 +106,6 @@ class Function(BaseFunc, Scriptable):
             loc=loc,
         )
 
-    def specialize(self, param_map: Mapping[Var, Expr | Var]):
-        """Specialize parameters of Function
-
-        Parameters
-        ----------
-
-        param_map : Mapping[Var, Union[Expr, Var]]
-            The mapping from function params to the instance
-
-        Examples
-        --------
-        We can define a TIRX function with symbolic shape:
-
-        .. code-block:: python
-
-            from __future__ import annotations
-
-            @T.function
-            def mem_copy(
-                A: T.Tensor((m, n), "float32"),
-                B: T.Tensor((m, n), "float32"),
-                m: T.int32,
-                n: T.int32,
-            ) -> None:
-
-                for i, j in T.grid(m, n):
-                    B[i, j] = A[i, j]
-
-        Then we can make it specialized with given shapes or tensors.
-
-        .. code-block:: python
-
-            a, _, m, n = mem_copy.params
-            tensor = tirx.Var("tensor", tirx.TensorType((16, 16), "float32"))
-            func = mem_copy.specialize({a: tensor})
-            # or
-            func = mem_copy.specialize({n: 16, m: 16})
-
-        The specialized function:
-
-        .. code-block:: python
-
-            @T.function
-            def mem_copy_16_16(
-                A: T.Tensor((16, 16), "float32"), B: T.Tensor((16, 16), 
"float32")
-            ) -> None:
-
-                for i, j in T.grid(16, 16):
-                    B[i, j] = A[i, j]
-
-        Returns
-        -------
-        func : Function
-            The new function with parameter specialized
-        """
-        return _ffi_api.Specialize(self, param_map)  # type: ignore
-
 
 def renew_def(func: Function) -> Function:
     """Renew variable definitions and remap their uses in a function.
diff --git a/tirx/src/tirx/ir/function.cc b/tirx/src/tirx/ir/function.cc
index 2e425aff1e..e7b225d9c1 100644
--- a/tirx/src/tirx/ir/function.cc
+++ b/tirx/src/tirx/ir/function.cc
@@ -70,6 +70,7 @@ TVM_FFI_INLINE ffi::Expected<ffi::UnchangedOr<ffi::Any>> 
FunctionMutate(
                      ? 
ffi::Optional<SeqStmt>(SeqStmt(std::move(replacement).value()))
                      : std::nullopt;
   }
+  copy->ty = copy->func_type_annotation();
   return ffi::Any(std::move(copy));
 }
 
@@ -100,6 +101,8 @@ TVM_FFI_INLINE ffi::Expected<ffi::UnchangedOr<ffi::Any>> 
FunctionMaybeInplaceMut
                      ? 
ffi::Optional<SeqStmt>(SeqStmt(std::move(replacement).value()))
                      : std::nullopt;
   }
+  // A nested signature node may change in place without replacing params or 
ret_type.
+  self->ty = self->func_type_annotation();
   return ffi::Unchanged();
 }
 
@@ -143,10 +146,6 @@ Function RenewDef(Function func) {
                Var fresh(var->name, var->ty, var->loc);
                remap.Set(var, fresh);
                return fresh;
-             },
-             [](const Function& mapped) {
-               return Function(mapped->params, mapped->body, mapped->ret_type, 
mapped->attrs,
-                               mapped->loc);
              })
       .as_or_throw<Function>();
 }
diff --git a/tirx/src/tirx/ir/specialize.cc b/tirx/src/tirx/ir/specialize.cc
deleted file mode 100644
index 7abf56b4e3..0000000000
--- a/tirx/src/tirx/ir/specialize.cc
+++ /dev/null
@@ -1,466 +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 src/tirx/ir/specialize.cc
- * \brief Specialize parameters of Function.
- */
-#include <tvm/ffi/cast.h>
-#include <tvm/ffi/extra/structural_equal.h>
-#include <tvm/ffi/function.h>
-#include <tvm/ffi/reflection/registry.h>
-#include <tvm/ir/prim/expr.h>
-#include <tvm/ir/prim/op.h>
-#include <tvm/tirx/function.h>
-#include <tvm/tirx/layout.h>
-#include <tvm/tirx/op/memory.h>
-#include <tvm/tirx/stmt_functor.h>
-
-#include <functional>
-#include <unordered_set>
-
-#include "../transform/ir_utils.h"
-
-namespace tvm {
-namespace tirx {
-
-using VarMap = std::unordered_map<Var, Expr>;
-
-/**************** Helper functions ****************/
-
-/*! \brief Helper function to check whether the given var is in function 
parameter list. */
-inline bool IsParam(const Function& func, const Var& param) {
-  return std::any_of(func->params.begin(), func->params.end(),
-                     [&](const Var& var) { return var.same_as(param); });
-}
-
-/**************** Specializer ****************/
-
-// Try fold constants if op's child get specialized to constant.
-#define DEFINE_SPECIALIZER_BINARY_OP_MUTATE(BinaryNode, BinaryFunc)            
         \
-  UnchangedOr<PrimExpr> Mutate_(const BinaryNode* op, InplaceMode 
inplace_mode) final { \
-    auto a_result = Mutate(op->a, inplace_mode);                               
         \
-    bool a_unchanged = a_result.UnchangedOrSameAs(op->a);                      
         \
-    PrimExpr a = std::move(a_result).ValueOrUnchanged(op->a);                  
         \
-    auto b_result = Mutate(op->b, inplace_mode);                               
         \
-    bool b_unchanged = b_result.UnchangedOrSameAs(op->b);                      
         \
-    PrimExpr b = std::move(b_result).ValueOrUnchanged(op->b);                  
         \
-    if (a_unchanged && b_unchanged) {                                          
         \
-      return ffi::Unchanged();                                                 
         \
-    } else {                                                                   
         \
-      return BinaryFunc(a, b, op->loc);                                        
         \
-    }                                                                          
         \
-  }
-#define DEFINE_SPECIALIZER_UNARY_OP_MUTATE(UnaryNode, UnaryFunc)               
        \
-  UnchangedOr<PrimExpr> Mutate_(const UnaryNode* op, InplaceMode inplace_mode) 
final { \
-    auto a_result = Mutate(op->a, inplace_mode);                               
        \
-    bool a_unchanged = a_result.UnchangedOrSameAs(op->a);                      
        \
-    PrimExpr a = std::move(a_result).ValueOrUnchanged(op->a);                  
        \
-    if (a_unchanged) {                                                         
        \
-      return ffi::Unchanged();                                                 
        \
-    } else {                                                                   
        \
-      return UnaryFunc(a, op->loc);                                            
        \
-    }                                                                          
        \
-  }
-
-/*! \brief Mutator to specialize function and remove const parameters */
-class FunctionSpecializer : public StmtExprMutator {
- public:
-  using StmtExprMutator::Mutate;
-  using StmtExprMutator::Mutate_;
-  explicit FunctionSpecializer(const VarMap& var_map) {
-    for (const auto& [var, value] : var_map) {
-      if (!var.as<TensorVar>()) VarRemapSet(var, value);
-    }
-  }
-
-  static Function Specialize(Function f, const VarMap& var_map) {
-    auto specializer = ffi::make_object<FunctionSpecializer>(var_map);
-    for (const Var& param : f->params) {
-      auto tensor = param.as<TensorVar>();
-      auto replacement = var_map.find(param);
-      if (!tensor || replacement == var_map.end()) {
-        continue;
-      }
-      if (auto replacement_var = replacement->second.as<Var>()) {
-        if (auto replacement_tensor = replacement_var.value().as<TensorVar>()) 
{
-          if (IsParam(f, replacement_var.value())) {
-            specializer->VarRemapSet(tensor.value(), 
replacement_tensor.value());
-          } else {
-            specializer->constrained_tensor_params_.insert(param.get());
-            specializer->tensor_storage_scopes_.emplace(tensor.value(),
-                                                        
replacement_tensor.value().scope());
-          }
-        }
-      }
-    }
-
-    // Updating parameters
-    ffi::Array<Var> params;
-    bool param_updated = false;
-    for (const auto& var : f->params) {
-      Var new_var = var;
-      if (auto tensor = var.as<TensorVar>()) {
-        TensorVar new_tensor = specializer->MutateTensor(tensor.value());
-        new_var = new_tensor;
-        if (!new_tensor.same_as(tensor.value())) {
-          param_updated = true;
-          specializer->VarRemapSet(tensor.value(), new_tensor);
-          specializer->defined_tensors_.insert(tensor.value().get());
-        }
-      }
-      // Remove parmeters which has been specialized.
-      if (var_map.find(var) == var_map.end() ||
-          specializer->constrained_tensor_params_.count(var.get())) {
-        params.push_back(new_var);
-      } else {
-        param_updated = true;
-      }
-    }
-
-    auto planner = ffi::make_object<TensorPlanner>(specializer.get());
-    planner->Visit(f->body);
-
-    auto body_result =
-        specializer->Mutate(f->body, f.unique() ? InplaceMode::kAllow : 
InplaceMode::kDisallow);
-    bool body_unchanged = body_result.UnchangedOrSameAs(f->body);
-    auto body = std::move(body_result).ValueOrUnchanged(f->body);
-
-    if (param_updated || !body_unchanged) {
-      return Function(params, body, f->ret_type, f->attrs, f->loc);
-    } else {
-      return f;
-    }
-  }
-
- private:
-  class TensorPlanner : public StmtExprVisitor {
-   public:
-    using StmtExprVisitor::Visit_;
-
-    explicit TensorPlanner(FunctionSpecializer* specializer) : 
specializer_(specializer) {}
-
-   private:
-    ffi::Optional<VisitInterrupt> Visit_(const VarNode* op) final {
-      if (op->ty.as<TensorTypeNode>()) {
-        if (def_region_kind() == kTVMFFIDefRegionKindSimple) {
-          const TensorVar tensor = 
ffi::GetRef<Var>(op).as_or_throw<TensorVar>();
-          specializer_->MutateAllocTensor(tensor);
-        } else {
-          
specializer_->ValidateTensorUse(ffi::GetRef<Var>(op).as_or_throw<TensorVar>());
-        }
-      }
-      return StmtExprVisitor::Visit_(op);
-    }
-
-    ffi::Optional<VisitInterrupt> Visit_(const BindNode* op) final {
-      if (const auto* call = op->value.as<CallNode>();
-          call &&
-          (call->op.same_as(tirx::alloc_tensor_op()) || 
call->op.same_as(tirx::decl_tensor_op()))) {
-        TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(this->WithDefRegionKind(
-            kTVMFFIDefRegionKindSimple, [&]() { return this->Visit(op->var); 
}));
-        // The definition above owns traversal of the complete tensor type.
-        // Value operands include declaration data or allocation placement.
-        return Visit(call->args);
-      }
-      return StmtExprVisitor::Visit_(op);
-    }
-
-    FunctionSpecializer* specializer_;
-  };
-
-  UnchangedOr<Expr> Mutate_(const CallNode* op, InplaceMode inplace_mode) 
final {
-    auto result = StmtExprMutator::Mutate_(op, inplace_mode);
-    if (!result.IsUnchanged()) {
-      op = ffi::AnyView(result).as<CallNode>();
-      if (!op->unique()) inplace_mode = InplaceMode::kDisallow;
-    }
-    if (!op->op.same_as(tirx::tensor_data_ptr_op()) || op->args.size() != 1) 
return result;
-    PtrType type = op->args[0].as_or_throw<TensorVar>().ty()->DataPtrType();
-    if (ffi::StructuralEqual()(op->ty, type)) return result;
-    if (inplace_mode == InplaceMode::kAllow) {
-      const_cast<CallNode*>(op)->ty = std::move(type);
-      return result;
-    }
-    auto copy = ffi::make_object<CallNode>(*op);
-    copy->ty = std::move(type);
-    return Expr(std::move(copy));
-  }
-
-  UnchangedOr<PrimExpr> Mutate_(const TensorLoadNode* op, InplaceMode 
inplace_mode) final {
-    auto result = StmtExprMutator::Mutate_(op, InplaceMode::kDisallow);
-    if (result.UnchangedOrSameAs(ffi::GetRef<PrimExpr>(op))) return 
ffi::Unchanged();
-    auto load = std::move(result).ValueUnchecked().as_or_throw<TensorLoad>();
-    if (auto tensor = load->source.as<TensorVar>()) {
-      return TensorLoad(tensor.value(), load->indices, load->loc);
-    }
-    return load;
-  }
-
-  UnchangedOr<Expr> Mutate_(const TensorRegionNode* op, InplaceMode 
inplace_mode) final {
-    auto result = StmtExprMutator::Mutate_(op, InplaceMode::kDisallow);
-    if (result.UnchangedOrSameAs(ffi::GetRef<Expr>(op))) return 
ffi::Unchanged();
-    auto region = 
std::move(result).ValueUnchecked().as_or_throw<TensorRegion>();
-    if (auto tensor = region->source.as<TensorVar>()) {
-      return TensorRegion(tensor.value(), region->region, TensorRegionType(), 
region->loc);
-    }
-    return region;
-  }
-
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::AddNode, add);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::SubNode, sub);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::MulNode, mul);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::DivNode, div);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::ModNode, truncmod);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::FloorDivNode, floordiv);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::FloorModNode, floormod);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::MaxNode, max);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::MinNode, min);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::EQNode, equal);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::NENode, not_equal);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::LTNode, less);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::LENode, less_equal);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::GTNode, greater);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::GENode, greater_equal);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::AndNode, logical_and);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::OrNode, logical_or);
-  DEFINE_SPECIALIZER_UNARY_OP_MUTATE(prim::NotNode, logical_not);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::LShiftNode, left_shift);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::RShiftNode, right_shift);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::BitwiseAndNode, bitwise_and);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::BitwiseOrNode, bitwise_or);
-  DEFINE_SPECIALIZER_BINARY_OP_MUTATE(prim::BitwiseXorNode, bitwise_xor);
-  DEFINE_SPECIALIZER_UNARY_OP_MUTATE(prim::BitwiseNotNode, prim::BitwiseNot);
-  TensorVar MutateTensor(const TensorVar& tensor) {
-    ffi::Any mapped = VarRemapGet(tensor);
-    if (mapped.type_index() != ffi::TypeIndex::kTVMFFINone) {
-      return 
std::move(mapped).as_or_throw<UnchangedOr<TensorVar>>().ValueOrUnchanged(tensor);
-    }
-
-    ffi::Optional<runtime::MemoryScope> specialized_storage_scope;
-    if (auto it = tensor_storage_scopes_.find(tensor); it != 
tensor_storage_scopes_.end()) {
-      specialized_storage_scope = it->second;
-    }
-
-    ffi::Array<PrimExpr> shape =
-        tensor.shape().Map([this](const PrimExpr& e) { return 
Mutate(e).ValueOrUnchanged(e); });
-    ffi::Optional<Layout> layout = tensor.ty()->layout;
-    auto mapped_layout = Mutate(ffi::AnyView(layout), InplaceMode::kDisallow);
-    bool layout_changed = !mapped_layout.UnchangedOrSameAs(layout);
-    layout = 
std::move(mapped_layout).ValueOrUnchanged(layout).cast<ffi::Optional<Layout>>();
-
-    bool storage_scope_changed = specialized_storage_scope.has_value() &&
-                                 specialized_storage_scope.value() != 
tensor.scope();
-    if (tensor.shape().same_as(shape) && !layout_changed && 
!storage_scope_changed) {
-      return tensor;
-    } else {
-      auto n = ffi::make_object<TensorTypeNode>(*tensor.ty().get());
-      n->shape = std::move(shape);
-      if (layout_changed) {
-        n->layout = std::move(layout);
-      }
-      if (storage_scope_changed) {
-        n->scope = specialized_storage_scope.value();
-      }
-      return TensorVar(tensor->name, TensorType(std::move(n)), tensor->loc);
-    }
-  }
-
-  void MutateAllocTensor(const TensorVar& alloc_buf) {
-    TVM_FFI_ICHECK(defined_tensors_.insert(alloc_buf.get()).second)
-        << "Multiple points of definition found for tensor " << alloc_buf;
-    VarRemapSet(alloc_buf, MutateTensor(alloc_buf));
-  }
-
-  void ValidateTensorUse(const TensorVar& old_tensor) {
-    if (VarRemapGet(old_tensor).type_index() != ffi::TypeIndex::kTVMFFINone) 
return;
-
-    auto mutated = MutateTensor(old_tensor);
-    TVM_FFI_ICHECK(mutated.same_as(old_tensor))
-        << "TensorVar " << old_tensor << " (shape = " << old_tensor.shape() << 
")"
-        << " was used without a declaration, "
-        << "and would be specialized into " << mutated << " (shape = " << 
mutated.shape() << ").  "
-        << "While usage of an undeclared tensor is currently allowed in TIR, "
-        << "mutation must occur at the tensor's point of definition "
-        << "(see discussion on https://github.com/apache/tvm/pull/14565 for 
more details).  "
-        << "Please add a definition for this tensor, "
-        << "either as a TensorType-annotated Function parameter, "
-        << "in a block's tensor allocations, "
-        << "or in a DeclTensor statement.";
-  }
-
-  /*! \brief Definition identities used only to validate declaration order. */
-  std::unordered_set<const VarNode*> defined_tensors_;
-  /*! \brief Storage constraints supplied by concrete, non-parameter tensors. 
*/
-  std::unordered_map<TensorVar, runtime::MemoryScope, ffi::ObjectPtrHash, 
ffi::ObjectPtrEqual>
-      tensor_storage_scopes_;
-  /*! \brief Tensor parameters constrained by a concrete, non-parameter 
tensor. */
-  std::unordered_set<const VarNode*> constrained_tensor_params_;
-};
-
-/*!
- * \brief Update Specialize var map with tensor matching.
- * \param func The function to be specialized.
- * \param param The given function parameter
- * \param specific_buf The matching tensor.
- * \param var_map The var mapping to be updated.
- * \note This function will match target tensor's shape, strides and 
element_offset
- *   For example, we define a tensor in Function:
- *   A: T.Tensor([m, n])
- *
- *   Then we match it with a tensor B =  Var("tensor", TensorType((8, 16), 
"float32"))
- *
- *   It means we have two var mappings here: m = 8 and n = 16
- *
- *   If the tensor signature is not a Var, the mapping will fail.
- *   e.g. A: T.Tensor([m * 2, n + 1])
- */
-void UpdateSpecializeVarMap(const Function& func, const Var& param, const 
TensorVar& specific_buf,
-                            VarMap* var_map) {
-  // preliminaries
-  prim::ExprDeepEqual equal;
-
-  auto opt_tensor = param.as<TensorVar>();
-  TVM_FFI_CHECK(opt_tensor, ValueError)
-      << "specialize expects param to have a TensorType annotation";
-  const TensorVar& buf_to_specialize = opt_tensor.value();
-
-  // build var mapping using specific_buf's parameters
-  auto build_var_mapping = [&](const Expr& new_expr, const Expr& old_expr) {
-    auto expr_equal = [&](const Expr& lhs, const Expr& rhs) {
-      if (auto lhs_prim = lhs.as<PrimExpr>()) {
-        auto rhs_prim = rhs.as<PrimExpr>();
-        return rhs_prim && equal(lhs_prim.value(), rhs_prim.value());
-      }
-      return lhs.same_as(rhs);
-    };
-    Expr resolved = old_expr;
-    if (auto prim_expr = old_expr.as<PrimExpr>()) {
-      auto mutator = ffi::make_object<FunctionSpecializer>(*var_map);
-      resolved = 
mutator->Mutate(prim_expr.value()).ValueOrUnchanged(prim_expr.value());
-    }
-    if (!expr_equal(new_expr, resolved)) {
-      auto maybe_var = old_expr.as<Var>();
-      TVM_FFI_CHECK(maybe_var, TypeError)
-          << "The signature of target tensor exprected an independent Var, but 
got " << old_expr
-          << ".";
-      const Var& var = maybe_var.value();
-      auto it = var_map->find(var);
-      if (it != var_map->end()) {
-        TVM_FFI_CHECK(expr_equal(it->second, new_expr), ValueError)
-            << "The assigned value of var " << var << " mismatched. " << 
it->second << " vs. "
-            << new_expr << ".";
-      } else {
-        var_map->insert_or_assign(var, new_expr);
-      }
-    }
-  };
-
-  // Check tensor dimensions
-  TVM_FFI_CHECK(specific_buf.shape().size() == 
buf_to_specialize.shape().size(), ValueError)
-      << "The tensor dimensions mismatched" << 
buf_to_specialize.shape().size() << " vs. "
-      << specific_buf.shape().size() << ".";
-
-  for (size_t i = 0; i < specific_buf.shape().size(); ++i) {
-    build_var_mapping(specific_buf.shape()[i], buf_to_specialize.shape()[i]);
-  }
-  auto specific = MatchShapeStridePattern(specific_buf.ty());
-  auto original = MatchShapeStridePattern(buf_to_specialize.ty());
-  TVM_FFI_CHECK(specific.has_value() && original.has_value(), ValueError)
-      << "Tensor specialization requires shape-aligned strided memory layouts";
-  auto new_strides = specific.value().get<1>();
-  auto old_strides = original.value().get<1>();
-  for (size_t i = 0; i < old_strides.size(); ++i) {
-    build_var_mapping(new_strides[i], old_strides[i]);
-  }
-  build_var_mapping(specific.value().get<2>(), original.value().get<2>());
-  // The specializer distinguishes a concrete tensor constraint (which keeps
-  // the parameter) from another function parameter (which aliases it).
-  build_var_mapping(specific_buf, buf_to_specialize);
-
-  // Check data_alignment.
-  // These two signatures are int, so we do not need map them.
-  TVM_FFI_CHECK_EQ(specific_buf.ty()->data_alignment, 
buf_to_specialize.ty()->data_alignment,
-                   ValueError)
-      << "The tensor data_alignment mismatched" << 
buf_to_specialize.ty()->data_alignment << " vs. "
-      << specific_buf.ty()->data_alignment << ".";
-}
-
-/*!
- * \brief Update Specialize var map with parameter value.
- * \param func The function to be specialized.
- * \param param The given function parameter
- * \param specific_expr The parameter value.
- * \param var_map The var mapping to be updated.
- */
-void UpdateSpecializeVarMap(const Function& func, const Var& param, const 
Expr& specific_expr,
-                            VarMap* var_map) {
-  // check param is in Function's parameters
-  TVM_FFI_CHECK(IsParam(func, param), ValueError)
-      << "Specialize expects param to be in Function's params";
-  // Specialize a scalar parameter rather than a tensor parameter.
-  TVM_FFI_CHECK(!param.as<TensorVar>(), ValueError)
-      << "Specialize expects param to not have a TensorType annotation";
-  // build var mapping using specific_expr
-  var_map->insert_or_assign(param, specific_expr);
-}
-
-/**************** Implementation ****************/
-
-Function Specialize(Function func, const ffi::Map<Var, ffi::Variant<TensorVar, 
Expr>>& param_map) {
-  VarMap var_map;
-  for (const auto& kv : param_map) {
-    const Var& param = kv.first;
-    const ffi::Variant<TensorVar, Expr>& instance = kv.second;
-    if (auto opt_tensor = instance.as<TensorVar>()) {
-      UpdateSpecializeVarMap(func, param, opt_tensor.value(), &var_map);
-    } else if (auto opt_expr = instance.as<Expr>()) {
-      UpdateSpecializeVarMap(func, param, opt_expr.value(), &var_map);
-    } else {
-      TVM_FFI_THROW(TypeError) << "specialize expected instance to be 
TensorVar or Expr";
-    }
-  }
-  return FunctionSpecializer::Specialize(func, std::move(var_map));
-}
-
-/**************** FFI ****************/
-
-TVM_FFI_STATIC_INIT_BLOCK() {
-  namespace refl = tvm::ffi::reflection;
-  refl::GlobalDef().def("tirx.Specialize", [](Function func,
-                                              const ffi::Map<Var, ffi::Any>& 
param_map) {
-    VarMap var_map;
-    for (const auto& [param, instance] : param_map) {
-      if (auto tensor = instance.as<TensorVar>()) {
-        UpdateSpecializeVarMap(func, param, tensor.value(), &var_map);
-      } else if (const ExprNode* expr = instance.as<ExprNode>()) {
-        UpdateSpecializeVarMap(func, param, ffi::GetRef<Expr>(expr), &var_map);
-      } else if (instance.type_index() < ffi::TypeIndex::kTVMFFISmallStr) {
-        UpdateSpecializeVarMap(func, param, instance.cast<PrimExpr>(), 
&var_map);
-      } else {
-        TVM_FFI_THROW(TypeError) << "specialize expected instance to be 
TensorVar or Expr, but got "
-                                 << instance.GetTypeKey();
-      }
-    }
-    return FunctionSpecializer::Specialize(func, std::move(var_map));
-  });
-}
-
-}  // namespace tirx
-}  // namespace tvm
diff --git a/tirx/tests/python/tirx/base/test_specialize.py 
b/tirx/tests/python/tirx/base/test_specialize.py
deleted file mode 100644
index 8bdc8685b2..0000000000
--- a/tirx/tests/python/tirx/base/test_specialize.py
+++ /dev/null
@@ -1,410 +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.
-# pylint: disable=missing-function-docstring, missing-module-docstring
-# ruff: noqa: F401
-
-from __future__ import annotations
-
-import pytest
-
-import tvm_tirx
-from tvm_tirx.script import tirx as T
-
-m = T.dynamic("m", "int32")
-
-
-def assert_structural_equal_ignore_global_symbol(lhs, rhs):
-    tvm_tirx.ir.assert_structural_equal(
-        lhs.without_attr("global_symbol"), rhs.without_attr("global_symbol")
-    )
-
-
[email protected]
-def matmul(
-    A: T.Tensor([m, n], "float32"),
-    B: T.Tensor([m, n], "float32"),
-    C: T.Tensor([m, m], "float32"),
-    n: T.int32,
-) -> None:
-    for i, j, k in T.grid(m, m, n):
-        if k == 0:
-            C[i, j] = 0.0
-        C[i, j] = C[i, j] + A[i, k] * B[j, k]
-
-
[email protected]
-def matmul_128(
-    A: T.Tensor([128, 128], "float32"),
-    B: T.Tensor([128, 128], "float32"),
-    C: T.Tensor([128, 128], "float32"),
-) -> None:
-    for i, j, k in T.grid(128, 128, 128):
-        if k == 0:
-            C[i, j] = 0.0
-        C[i, j] = C[i, j] + A[i, k] * B[j, k]
-
-
-m = T.dynamic("m", "int32")
-
-
[email protected]
-def matmul_m_128(
-    A: T.Tensor([m, 128], "float32"),
-    B: T.Tensor([m, 128], "float32"),
-    C: T.Tensor([m, m], "float32"),
-) -> None:
-    for i, j, k in T.grid(m, m, 128):
-        if k == 0:
-            C[i, j] = 0.0
-        C[i, j] = C[i, j] + A[i, k] * B[j, k]
-
-
-# x is considered undefined because it appears as part of x*8,
-# but not on its own
-x = T.dynamic("x", "int32")
-m = T.dynamic("m", "int32")
-
-
[email protected](check_well_formed=False)
-def matmul_m_8x(
-    A: T.Tensor([m, x * 8], "float32"),
-    B: T.Tensor([m, x * 8], "float32"),
-    C: T.Tensor([m, m], "float32"),
-) -> None:
-    for i, j, k in T.grid(m, m, x * 8):
-        if k == 0:
-            C[i, j] = 0.0
-        C[i, j] = C[i, j] + A[i, k] * B[j, k]
-
-
-m = T.dynamic("m", "int32")
-n = T.dynamic("n", "int32")
-
-
[email protected]
-def element_wise(A: T.Tensor((m, n), "float32"), C: T.Tensor((m, n), 
"float32")) -> None:
-    B = T.alloc_tensor(ty_args=[T.Tensor((m, n), "float32")])
-
-    for i, j in T.grid(m, n):
-        B[i, j] = A[i, j] * 2.0
-
-    for i, j in T.grid(m, n):
-        C[i, j] = B[i, j] + 1.0
-
-
[email protected]
-def element_wise_128_64(
-    A: T.Tensor((128, 64), "float32"), C: T.Tensor((128, 64), "float32")
-) -> None:
-    B = T.alloc_tensor(ty_args=[T.Tensor((128, 64), "float32")])
-
-    for i, j in T.grid(128, 64):
-        B[i, j] = A[i, j] * 2.0
-
-    for i, j in T.grid(128, 64):
-        C[i, j] = B[i, j] + 1.0
-
-
-n = T.dynamic("n", "int32")
-
-
[email protected]
-def element_wise_128_n(A: T.Tensor((128, n), "float32"), C: T.Tensor((128, n), 
"float32")) -> None:
-    B = T.alloc_tensor(ty_args=[T.Tensor((128, n), "float32")])
-
-    for i, j in T.grid(128, n):
-        B[i, j] = A[i, j] * 2.0
-
-    for i, j in T.grid(128, n):
-        C[i, j] = B[i, j] + 1.0
-
-
-mem_copy_m = T.dynamic("mem_copy_m", "int32")
-
-mem_copy_n = T.dynamic("mem_copy_n", "int32")
-
-
[email protected]
-def mem_copy(
-    A: T.Tensor(
-        (mem_copy_m, mem_copy_n),
-        "float32",
-        layout=T.TileLayout(T.S[(mem_copy_m, mem_copy_n) : [p, 1]] + (q)),  # 
noqa: F821
-    ),
-    B: T.Tensor(
-        (mem_copy_m, mem_copy_n),
-        "float32",
-        layout=T.TileLayout(T.S[(mem_copy_m, mem_copy_n) : [p, 1]] + (q)),  # 
noqa: F821
-    ),
-    m: mem_copy_m,
-    n: mem_copy_n,
-    p: T.int32,
-    q: T.int32,
-) -> None:
-    for i, j in T.grid(m, n):
-        B[i, j] = A[i, j]
-
-
[email protected]
-def mem_copy_16_16_8_4(
-    A: T.Tensor((16, 16), "float32", layout=T.TileLayout(T.S[(16, 16) : [8, 
1]] + (T.int32(4)))),
-    B: T.Tensor((16, 16), "float32", layout=T.TileLayout(T.S[(16, 16) : [8, 
1]] + (T.int32(4)))),
-) -> None:
-    for i, j in T.grid(16, 16):
-        B[i, j] = A[i, j]
-
-
-mem_copy_m_n_p_n_m = T.dynamic("mem_copy_m_n_p_n_m", "int32")
-
-mem_copy_m_n_p_n_n = T.dynamic("mem_copy_m_n_p_n_n", "int32")
-
-
[email protected]
-def mem_copy_m_n_p_n(
-    A: T.Tensor(
-        (mem_copy_m_n_p_n_m, mem_copy_m_n_p_n_n),
-        "float32",
-        layout=T.TileLayout(
-            T.S[(mem_copy_m_n_p_n_m, mem_copy_m_n_p_n_n) : [p, 1]] + 
(mem_copy_m_n_p_n_n)  # noqa: F821
-        ),
-    ),
-    B: T.Tensor(
-        (mem_copy_m_n_p_n_m, mem_copy_m_n_p_n_n),
-        "float32",
-        layout=T.TileLayout(
-            T.S[(mem_copy_m_n_p_n_m, mem_copy_m_n_p_n_n) : [p, 1]] + 
(mem_copy_m_n_p_n_n)  # noqa: F821
-        ),
-    ),
-    m: mem_copy_m_n_p_n_m,
-    n: mem_copy_m_n_p_n_n,
-    p: T.int32,
-) -> None:
-    for i, j in T.grid(m, n):
-        B[i, j] = A[i, j]
-
-
-def test_specialize_nothing():
-    func = matmul.specialize({})
-    assert func.same_as(matmul)  # Pointer the same
-
-
-def test_specialize_matmul():
-    a, _, _, n = matmul.params
-    # fully specialized
-    func = matmul.specialize(
-        {a: tvm_tirx.tirx.Var("tensor", tvm_tirx.tirx.TensorType((128, 128), 
"float32"))}
-    )
-    assert_structural_equal_ignore_global_symbol(func, matmul_128)
-    # partially specialized
-    func = matmul.specialize({n: 128})
-    assert_structural_equal_ignore_global_symbol(func, matmul_m_128)
-    # symbolic specialized
-    func = matmul.specialize({n: tvm_tirx.tirx.Var("x", "int32") * 8})
-    assert_structural_equal_ignore_global_symbol(func, matmul_m_8x)
-
-
-def test_specialize_elemwise():
-    a, c = element_wise.params
-    C = c
-    # fully specialized
-    func = element_wise.specialize(
-        {a: tvm_tirx.tirx.Var("tensor", tvm_tirx.tirx.TensorType((128, 64), 
"float32"))}
-    )
-    assert_structural_equal_ignore_global_symbol(func, element_wise_128_64)
-    # partially specialized
-    func = element_wise.specialize(
-        {c: tvm_tirx.tirx.Var("tensor", tvm_tirx.tirx.TensorType((128, 
C.ty.shape[1]), "float32"))}
-    )
-    assert_structural_equal_ignore_global_symbol(func, element_wise_128_n)
-
-
-def test_specialize_mem_copy():
-    a, _, m, n, p, q = mem_copy.params
-    # fully specialized
-    func = mem_copy.specialize(
-        {
-            a: tvm_tirx.tirx.Var(
-                "tensor",
-                tvm_tirx.tirx.TensorType(
-                    (16, 16),
-                    "float32",
-                    layout=tvm_tirx.tirx.layout.TileLayout(
-                        tvm_tirx.tirx.layout.S[(16, 16) : [8, 1]] + 
(T.int32(4))
-                    ),
-                ),
-            )
-        }
-    )
-    assert_structural_equal_ignore_global_symbol(func, mem_copy_16_16_8_4)
-    func = mem_copy.specialize({n: 16, m: 16, p: 8, q: 4})
-    assert_structural_equal_ignore_global_symbol(func, mem_copy_16_16_8_4)
-    # partially specialized
-    func = mem_copy.specialize({q: n})
-    assert_structural_equal_ignore_global_symbol(func, mem_copy_m_n_p_n)
-
-
-def test_specialize_recursive_load():
-    # TODO(Siyuan): add recursive Load testcase, e.g. A[C[i]]
-    pass
-
-
-def test_specialize_with_const_folding():
-    n = T.dynamic("n", "int32")
-
-    @T.function
-    def before(A: T.Tensor([n // 8, 8], "int32"), B: T.Tensor([n], "int32")):
-        for i in range(n - 1):
-            B[i] = A[i // 8, i % 8] + (n + 1) * 42
-
-    @T.function
-    def expected(A: T.Tensor([2, 8], "int32"), B: T.Tensor([16], "int32")):
-        for i in range(15):
-            B[i] = A[i // 8, i % 8] + 714
-
-    b = before.params[1]
-    after = before.specialize(
-        {b: tvm_tirx.tirx.Var("tensor", tvm_tirx.tirx.TensorType([16], 
"int32"))}
-    )
-    assert_structural_equal_ignore_global_symbol(expected, after)
-
-
-def test_specialize_decl_tensor():
-    """Tensors occurring in a DeclTensor statement should be updated"""
-
-    @T.function(private=True)
-    def before(A_data: T.handle("float32"), A_size: T.int32):
-        A_buf = T.decl_tensor(A_data, ty_args=[T.Tensor(A_size, "float32", 
data=A_data)])
-        for i in range(A_size):
-            A_buf[i] = A_buf[i] * 2.0
-
-    @T.function(private=True)
-    def expected(A_data: T.handle("float32")):
-        A_buf = T.decl_tensor(A_data, ty_args=[T.Tensor(16, "float32", 
data=A_data)])
-        for i in range(16):
-            A_buf[i] = A_buf[i] * 2.0
-
-    param_map = {before.params[1]: T.int32(16)}
-    after = before.specialize(param_map)
-
-    tvm_tirx.ir.assert_structural_equal(expected, after)
-
-
-def test_specialize_preserves_decl_tensor_alias():
-    before_n = T.dynamic("before_n", "int32")
-
-    @T.function(private=True)
-    def before(A: T.Tensor((before_n,), "int32"), n: before_n):
-        A_flat = T.decl_tensor(A.data, ty_args=[T.Tensor((n,), "int32", 
data=A.data)])
-        A_flat[n - 1] = 42
-
-    @T.function(private=True)
-    def expected(A: T.Tensor((8,), "int32")):
-        A_flat = T.decl_tensor(A.data, ty_args=[T.Tensor((8,), "int32", 
data=A.data)])
-        A_flat[7] = 42
-
-    after = before.specialize({before.params[1]: 8})
-
-    tvm_tirx.ir.assert_structural_equal(expected, after)
-
-
-def test_specialize_tensor_var_to_var():
-    """A tensor var may be remapped by specialization
-
-    If a tensor variable is replaced by a specialization, then other
-    tensors using the same tensor var should also be updated.
-    """
-
-    @T.function(private=True)
-    def before(A: T.Tensor([16, 16], "float32"), B: T.Tensor([16, 16], 
"float32")):
-        A_flat = T.decl_tensor(A.data, ty_args=[T.Tensor([256], "float32", 
data=A.data)])
-        B_flat = T.decl_tensor(B.data, ty_args=[T.Tensor([256], "float32", 
data=B.data)])
-        for i in range(256):
-            B_flat[i] = A_flat[i] * 2.0
-
-    @T.function(private=True)
-    def expected(A: T.Tensor([16, 16], "float32")):
-        A_flat = T.decl_tensor(A.data, ty_args=[T.Tensor([256], "float32", 
data=A.data)])
-        B_flat = T.decl_tensor(A.data, ty_args=[T.Tensor([256], "float32", 
data=A.data)])
-        for i in range(256):
-            B_flat[i] = A_flat[i] * 2.0
-
-    A = before.params[0]
-    B_handle = before.params[1]
-    param_map = {B_handle: A}
-    after = before.specialize(param_map)
-
-    tvm_tirx.ir.assert_structural_equal(expected, after)
-
-
-def test_specialize_tensor_var_to_expr():
-    """A DeclTensor source expression may be specialized directly."""
-
-    @T.function(private=True)
-    def before(A_data: T.handle("float32"), B_data: T.handle("float32")):
-        A_buf = T.decl_tensor(A_data, ty_args=[T.Tensor(32, "float32", 
data=A_data)])
-        B_buf = T.decl_tensor(B_data, ty_args=[T.Tensor(16, "float32", 
data=B_data)])
-        for i in range(16):
-            B_buf[i] = A_buf[i] * 2.0
-
-    @T.function(private=True)
-    def expected(A_data: T.handle("float32")):
-        A_buf = T.decl_tensor(A_data, ty_args=[T.Tensor(32, "float32", 
data=A_data)])
-        B_buf = T.decl_tensor(
-            T.address_of(A_buf[16]), ty_args=[T.Tensor(16, "float32", 
data=T.address_of(A_buf[16]))]
-        )
-        for i in range(16):
-            B_buf[i] = A_buf[i] * 2.0
-
-    B_data = before.params[1]
-    # body is a SeqStmt; the first statement is DeclTensor for A_buf
-    A_buf = before.body[0].var
-    param_map = {B_data: tvm_tirx.tirx.address_of(A_buf[16])}
-    after = before.specialize(param_map)
-
-    tvm_tirx.ir.assert_structural_equal(expected, after)
-
-
-def test_specialization_updates_ty():
-    """Update type in specialization
-
-    A Function's native function type must reflect its specialized parameters.
-    """
-
-    @T.function(private=True)
-    def before(n: T.int32) -> T.int32:
-        return n * 10
-
-    @T.function(private=True)
-    def expected() -> T.int32:
-        return 50
-
-    ty_before = tvm_tirx.ir.FuncType([tvm_tirx.ir.PrimType("int32")], 
tvm_tirx.ir.PrimType("int32"))
-    tvm_tirx.ir.assert_structural_equal(before.ty, ty_before)
-
-    ty_expected = tvm_tirx.ir.FuncType([], tvm_tirx.ir.PrimType("int32"))
-    tvm_tirx.ir.assert_structural_equal(expected.ty, ty_expected)
-
-    n = before.params[0]
-    param_map = {n: 5}
-    after = before.specialize(param_map)
-
-    tvm_tirx.ir.assert_structural_equal(after, expected)
-    tvm_tirx.ir.assert_structural_equal(after.ty, ty_expected)
-
-
-if __name__ == "__main__":
-    tvm_tirx.testing.main()
diff --git a/tirx/tests/python/tirx/ir/__init__.py 
b/tirx/tests/python/tirx/ir/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/tirx/tests/python/tirx/ir/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/tirx/tests/python/tirx/ir/test_function.py 
b/tirx/tests/python/tirx/ir/test_function.py
new file mode 100644
index 0000000000..5ddc966332
--- /dev/null
+++ b/tirx/tests/python/tirx/ir/test_function.py
@@ -0,0 +1,65 @@
+# 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.
+"""Test TIRx function structural mutation."""
+
+import tvm_ffi
+
+
+def test_structural_map_specializes_function_signature():
+    from tvm_tirx.ir import Evaluate, FuncType, IntImm, Return, Var, 
assert_structural_equal
+    from tvm_tirx.tirx import Function, TensorType
+
+    n = Var("n", "int32")
+    scale = Var("scale", "int32")
+    tensor = Var("tensor", TensorType((n,), "float32"))
+    original = Function([tensor, scale], [Evaluate(scale), Return(tensor)], 
tensor.ty)
+    bindings = {n: IntImm("int32", 16), scale: IntImm("int32", 2)}
+
+    # Removing runtime parameters is explicit. structural_map handles the shape
+    # definitions embedded in tensor types and remaps the retained tensor's 
uses.
+    signature = Function([tensor], original.body, original.ret_type)
+    specialized = tvm_ffi.structural_map(
+        signature, (Var, lambda var: bindings.get(var, var)), order="pre"
+    )
+    expected_tensor = Var("tensor", TensorType((16,), "float32"))
+    expected = Function(
+        [expected_tensor],
+        [Evaluate(IntImm("int32", 2)), Return(expected_tensor)],
+        expected_tensor.ty,
+    )
+    assert_structural_equal(specialized, expected)
+    assert specialized.body.seq[1].value.same_as(specialized.params[0])
+    assert_structural_equal(
+        specialized.ty, FuncType([p.ty for p in specialized.params], 
specialized.ret_type)
+    )
+    assert original.params[0].ty.shape[0].same_as(n)
+    assert original.ty.arg_types[0].shape[0].same_as(n)
+
+    # A unique declaration allows the parameter array and its Var to remain in
+    # place while a nested TensorType changes. The cached FuncType must follow.
+    def declaration():
+        return Function(
+            [Var("tensor", TensorType((n,), "float32"))], None, 
TensorType((n,), "float32")
+        )
+
+    inplace = declaration()
+    inplace = tvm_ffi.structural_map(
+        inplace._move(), (Var, lambda var: bindings.get(var, var)), order="pre"
+    )
+    assert int(inplace.params[0].ty.shape[0]) == 16
+    assert int(inplace.ret_type.shape[0]) == 16
+    assert_structural_equal(inplace.ty, FuncType([inplace.params[0].ty], 
inplace.ret_type))

Reply via email to