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 e11e355ed3 [REFACTOR][TIRX] Migrate StmtFunctor dispatch and organize 
analyzer helpers (#20370)
e11e355ed3 is described below

commit e11e355ed3dd015b80b3ee618a580dab70a57fc4
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Sep 17 07:14:18 2026 -0400

    [REFACTOR][TIRX] Migrate StmtFunctor dispatch and organize analyzer helpers 
(#20370)
    
    Give TIRx statement functors the shared `Dispatch` API and extensible,
    finalized dispatch tables. Update path visitors, code generators, FLOP
    estimation, and tensorization comparators while preserving their
    explicit traversal and backend algorithms.
    
    Move analyzer-aware visitor and mutator helpers into `src/tirx/ir`
    alongside the other IR utilities.
---
 include/tvm/tirx/stmt_functor.h                    | 204 +++++++++++++--------
 src/backend/cuda/codegen/codegen_cuda.cc           |  26 +--
 src/backend/cuda/codegen/codegen_cuda.h            |  12 +-
 src/backend/cuda/codegen/llvm/codegen_nvptx.cc     |   2 +-
 .../hexagon/codegen/llvm/codegen_hexagon.cc        |   2 +-
 src/backend/metal/codegen/codegen_metal.cc         |   6 +-
 src/backend/metal/codegen/codegen_metal.h          |   4 +-
 src/backend/opencl/codegen/codegen_opencl.cc       |   4 +-
 src/backend/opencl/codegen/codegen_opencl.h        |   2 +-
 src/backend/rocm/codegen/llvm/codegen_amdgpu.cc    |   2 +-
 src/backend/trn/codegen/codegen_trn.cc             |  16 +-
 src/backend/trn/codegen/codegen_trn.h              |  15 +-
 src/backend/trn/transform/lower_trainium_layout.cc |   2 +-
 src/backend/vulkan/codegen/codegen_spirv.cc        |  38 ++--
 src/backend/vulkan/codegen/codegen_spirv.h         |  25 +--
 src/backend/webgpu/codegen/codegen_webgpu.cc       |  16 +-
 src/backend/webgpu/codegen/codegen_webgpu.h        |  16 +-
 src/relax/transform/split_call_tir_by_pattern.cc   |  30 +--
 src/s_tir/analysis/domain_touched.cc               |   2 +-
 src/s_tir/analysis/estimate_flops.cc               |  50 ++---
 src/s_tir/analysis/identify_memcpy.cc              |   2 +-
 src/s_tir/analysis/is_pure_function.cc             |   8 +-
 src/s_tir/analysis/oob_checker.cc                  |   2 +-
 src/s_tir/backend/adreno/inject_texture_alloc.cc   |   2 +-
 src/s_tir/backend/adreno/texture_flatten.cc        |   2 +-
 src/s_tir/schedule/analysis/analysis.cc            |   2 +-
 src/s_tir/schedule/ir_comparator.cc                |  36 ++--
 src/s_tir/schedule/ir_comparator.h                 |  18 +-
 src/s_tir/schedule/primitive/blockize_tensorize.cc |   2 +-
 .../schedule/primitive/layout_transformation.cc    |   2 +-
 src/s_tir/schedule/transform.h                     |   2 +-
 src/s_tir/transform/hoist_expression.cc            |   2 +-
 src/s_tir/transform/inject_permuted_layout.cc      |   2 +-
 src/s_tir/transform/inject_virtual_thread.cc       |   2 +-
 src/s_tir/transform/lower_async_dma.cc             |   2 +-
 src/s_tir/transform/renormalize_split_pattern.cc   |   2 +-
 .../transform/using_assume_to_reduce_branches.cc   |   2 +-
 src/target/llvm/codegen_aarch64.cc                 |  10 +-
 src/target/llvm/codegen_cpu.cc                     |  24 +--
 src/target/llvm/codegen_cpu.h                      |   7 +-
 src/target/llvm/codegen_llvm.cc                    |  40 ++--
 src/target/llvm/codegen_llvm.h                     |  27 +--
 src/target/source/codegen_c.cc                     |  28 +--
 src/target/source/codegen_c.h                      |  33 ++--
 src/target/source/codegen_c_host.cc                |   2 +-
 src/target/source/codegen_c_host.h                 |   2 +-
 src/tirx/analysis/verify_tirx_well_formed.cc       |  29 ++-
 src/tirx/{ => ir}/ir_mutator_with_analyzer.cc      |   4 +-
 src/tirx/{ => ir}/ir_mutator_with_analyzer.h       |  10 +-
 src/tirx/{ => ir}/ir_visitor_with_analyzer.cc      |   2 +-
 src/tirx/{ => ir}/ir_visitor_with_analyzer.h       |   8 +-
 src/tirx/ir/tir_visitor_with_path.cc               |  36 ++--
 src/tirx/ir/tir_visitor_with_path.h                |  42 ++---
 src/tirx/transform/flatten_buffer.cc               |   2 +-
 src/tirx/transform/lower_intrin.cc                 |   2 +-
 src/tirx/transform/lower_tirx_cleanup.cc           |   2 +-
 src/tirx/transform/remove_no_op.cc                 |   2 +-
 src/tirx/transform/stmt_simplify.cc                |   2 +-
 src/tirx/transform/tile_primitive_dispatch.cc      |   4 +-
 tests/cpp/ir_functor_test.cc                       |   6 +-
 tests/cpp/stmt_functor_test.cc                     | 131 +++++++++++++
 61 files changed, 606 insertions(+), 413 deletions(-)

diff --git a/include/tvm/tirx/stmt_functor.h b/include/tvm/tirx/stmt_functor.h
index 61c17cf574..c1147f5f80 100644
--- a/include/tvm/tirx/stmt_functor.h
+++ b/include/tvm/tirx/stmt_functor.h
@@ -40,105 +40,149 @@
 namespace tvm {
 namespace tirx {
 /*!
- * \brief Same as ExprFunctor except it is applied on statements
+ * \brief Type-dispatched statement functor with a caller-selected signature.
+ *
+ * Override Dispatch_ for a node type or DispatchDefault_ for default behavior.
+ * This functor does not traverse children automatically. Dispatch may use a
+ * registered ancestor. Derived extensions can initialize a fresh inherited
+ * table with InitVTable and register additional hooks with SetDispatch.
  * \tparam FType The function signature.
  * \sa ExprFunctor
  */
 template <typename FType>
 class StmtFunctor;
 
-#define STMT_FUNCTOR_DEFAULT                                   \
-  {                                                            \
-    return VisitStmtDefault_(op, std::forward<Args>(args)...); \
-  }
-
-#define IR_STMT_FUNCTOR_DISPATCH(OP)                                           
            \
-  vtable.template SetDispatch<OP>([](const ffi::ObjectRef& n, TSelf* self, 
Args... args) { \
-    return self->VisitStmt_(static_cast<const OP*>(n.get()), 
std::forward<Args>(args)...); \
-  });
-
 template <typename R, typename... Args>
-class StmtFunctor<R(const Stmt& n, Args... args)> {
+class StmtFunctor<R(const Stmt&, Args...)> {
  private:
-  using TSelf = StmtFunctor<R(const Stmt& n, Args... args)>;
-  using FType = ObjectFunctor<R(const ffi::ObjectRef& n, TSelf* self, Args... 
args)>;
+  using TSelf = StmtFunctor<R(const Stmt&, Args...)>;
 
  public:
-  /*! \brief the result type of this functor */
+  /*! \brief The result type of this functor. */
   using result_type = R;
-  /*! \brief virtual destructor */
-  virtual ~StmtFunctor() {}
-  /*!
-   * \brief Same as call.
-   * \param n The stmt node.
-   * \param args Additional arguments.
-   * \return The result of the call
-   */
-  R operator()(const Stmt& n, Args... args) { return VisitStmt(n, 
std::forward<Args>(args)...); }
-  /*!
-   * \brief The functor call.
-   * \param n The stmt node.
-   * \param args Additional arguments.
-   * \return The result of the call
-   */
-  virtual R VisitStmt(const Stmt& n, Args... args) {
-    static FType vtable = InitVTable();
-    return vtable(n, this, std::forward<Args>(args)...);
+  /*! \brief Construct a functor with the TIRx statement hooks. */
+  StmtFunctor() : StmtFunctor(GlobalVTable()) {}
+  /*! \brief Destroy through the statement functor base. */
+  virtual ~StmtFunctor() = default;
+  /*! \brief Dispatch a statement, forwarding additional arguments to its 
hook. */
+  TVM_FFI_INLINE R operator()(const Stmt& node, Args... args) {
+    return Dispatch(node, std::forward<Args>(args)...);
+  }
+  /*! \brief Dispatch to a node hook, including registered ancestor hooks. */
+  TVM_FFI_INLINE virtual R Dispatch(const Stmt& node, Args... args) {
+    TVM_FFI_ICHECK(node.defined()) << "Cannot dispatch a null statement";
+    return (*vtable_)(node, this, std::forward<Args>(args)...);
+  }
+
+  virtual R Dispatch_(const BindNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const AttrStmtNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const IfThenElseNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const ForNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const WhileNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const ReturnNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const BreakNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const ContinueNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
   }
-  // Functions that can be overriden by subclass
-  virtual R VisitStmt_(const BindNode* op, Args... args) STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const AttrStmtNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const IfThenElseNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const ForNode* op, Args... args) STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const WhileNode* op, Args... args) STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const ReturnNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const BreakNode* op, Args... args) STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const ContinueNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const AllocBufferNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const DeclBufferNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const BufferStoreNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const AssertStmtNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const SeqStmtNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const EvaluateNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const SBlockNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const SBlockRealizeNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const ScopeIdDefStmtNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmt_(const tirx::TilePrimitiveCallNode* op, Args... args) 
STMT_FUNCTOR_DEFAULT;
-  virtual R VisitStmtDefault_(const ffi::Object* op, Args...) {
-    TVM_FFI_THROW(InternalError) << "Do not have a default for " << 
op->GetTypeKey();
+  virtual R Dispatch_(const AllocBufferNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const DeclBufferNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const BufferStoreNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const AssertStmtNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const SeqStmtNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const EvaluateNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const SBlockNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const SBlockRealizeNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const ScopeIdDefStmtNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  virtual R Dispatch_(const tirx::TilePrimitiveCallNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
+  /*! \brief Default behavior for statement hooks not overridden by a 
subclass. */
+  virtual R DispatchDefault_(const ffi::Object* node, Args...) {
+    TVM_FFI_THROW(InternalError) << "Do not have a default for " << 
node->GetTypeKey();
     TVM_FFI_UNREACHABLE();
   }
 
+ protected:
+  /*! \brief Dispatch table shared by this signature and its subclasses. */
+  using VTable = ObjectFunctor<R(const ffi::ObjectRef&, TSelf*, Args...)>;
+  /*! \brief Construct with a finalized table that outlives the functor. */
+  explicit StmtFunctor(const VTable* vtable) : vtable_(vtable) {}
+  /*! \brief Register statement hooks in a fresh mutable table. */
+  static void InitVTable(VTable* vtable) {
+    SetDispatch<TSelf, BindNode>(vtable);
+    SetDispatch<TSelf, AttrStmtNode>(vtable);
+    SetDispatch<TSelf, IfThenElseNode>(vtable);
+    SetDispatch<TSelf, ForNode>(vtable);
+    SetDispatch<TSelf, WhileNode>(vtable);
+    SetDispatch<TSelf, ReturnNode>(vtable);
+    SetDispatch<TSelf, BreakNode>(vtable);
+    SetDispatch<TSelf, ContinueNode>(vtable);
+    SetDispatch<TSelf, AllocBufferNode>(vtable);
+    SetDispatch<TSelf, DeclBufferNode>(vtable);
+    SetDispatch<TSelf, BufferStoreNode>(vtable);
+    SetDispatch<TSelf, AssertStmtNode>(vtable);
+    SetDispatch<TSelf, SeqStmtNode>(vtable);
+    SetDispatch<TSelf, EvaluateNode>(vtable);
+    SetDispatch<TSelf, SBlockNode>(vtable);
+    SetDispatch<TSelf, SBlockRealizeNode>(vtable);
+    SetDispatch<TSelf, ScopeIdDefStmtNode>(vtable);
+    SetDispatch<TSelf, tirx::TilePrimitiveCallNode>(vtable);
+  }
+  /*! \brief Register an additional node hook implemented by Self. */
+  template <typename Self, typename Node>
+  static void SetDispatch(VTable* vtable) {
+    vtable->template SetDispatch<Node>(
+        [](const ffi::ObjectRef& node, TSelf* self, Args... args) -> R {
+          return static_cast<Self*>(self)->Dispatch_(static_cast<const 
Node*>(node.get()),
+                                                     
std::forward<Args>(args)...);
+        });
+  }
+
  private:
-  // initialize the vtable.
-  static FType InitVTable() {
-    FType vtable;
-    IR_STMT_FUNCTOR_DISPATCH(BindNode);
-    IR_STMT_FUNCTOR_DISPATCH(AttrStmtNode);
-    IR_STMT_FUNCTOR_DISPATCH(IfThenElseNode);
-    IR_STMT_FUNCTOR_DISPATCH(ForNode);
-    IR_STMT_FUNCTOR_DISPATCH(WhileNode);
-    IR_STMT_FUNCTOR_DISPATCH(ReturnNode);
-    IR_STMT_FUNCTOR_DISPATCH(BreakNode);
-    IR_STMT_FUNCTOR_DISPATCH(ContinueNode);
-    IR_STMT_FUNCTOR_DISPATCH(AllocBufferNode);
-    IR_STMT_FUNCTOR_DISPATCH(DeclBufferNode);
-    IR_STMT_FUNCTOR_DISPATCH(AssertStmtNode);
-    IR_STMT_FUNCTOR_DISPATCH(SeqStmtNode);
-    IR_STMT_FUNCTOR_DISPATCH(EvaluateNode);
-    IR_STMT_FUNCTOR_DISPATCH(BufferStoreNode);
-    IR_STMT_FUNCTOR_DISPATCH(SBlockNode);
-    IR_STMT_FUNCTOR_DISPATCH(SBlockRealizeNode);
-    IR_STMT_FUNCTOR_DISPATCH(ScopeIdDefStmtNode);
-    IR_STMT_FUNCTOR_DISPATCH(tirx::TilePrimitiveCallNode);
-    vtable.Finalize();
-    return vtable;
+  static const VTable* GlobalVTable() {
+    static const VTable table = [] {
+      VTable table;
+      InitVTable(&table);
+      table.Finalize();
+      return table;
+    }();
+    return &table;
   }
+  const VTable* vtable_;
 };
 
-#undef IR_STMT_FUNCTOR_DISPATCH
-#undef STMT_FUNCTOR_DEFAULT
-
 /*!
  * \brief Native visitor for TIRx statements and their expression operands.
  *
diff --git a/src/backend/cuda/codegen/codegen_cuda.cc 
b/src/backend/cuda/codegen/codegen_cuda.cc
index d60eea8d29..c35e4d1a6d 100644
--- a/src/backend/cuda/codegen/codegen_cuda.cc
+++ b/src/backend/cuda/codegen/codegen_cuda.cc
@@ -305,10 +305,10 @@ void CodeGenCUDA::PrintExtraAttrs(const PrimFunc& f, 
std::ostream& os) {
   }
 }
 
-void CodeGenCUDA::VisitStmt_(const ReturnNode* op) {
+void CodeGenCUDA::Dispatch_(const ReturnNode* op) {
   if (!in_kernel_launch_) {
     // __device__ subroutines return real values.
-    CodeGenC::VisitStmt_(op);
+    CodeGenC::Dispatch_(op);
     return;
   }
   const auto* value = op->value.as<IntImmNode>();
@@ -336,7 +336,7 @@ std::string CodeGenCUDA::Finish() {
   return CodeGenC::Finish();
 }
 
-void CodeGenCUDA::VisitStmt_(const tirx::ForNode* op) {
+void CodeGenCUDA::Dispatch_(const tirx::ForNode* op) {
   // Materialize the loop bounds before emitting an unroll pragma.  PrintExpr
   // may introduce temporaries (for example, for a Select expression).  CUDA
   // requires #pragma unroll to immediately precede the loop it controls; if
@@ -380,7 +380,7 @@ void CodeGenCUDA::VisitStmt_(const tirx::ForNode* op) {
   stream << "}\n";
 }
 
-void CodeGenCUDA::VisitStmt_(const WhileNode* op) {
+void CodeGenCUDA::Dispatch_(const WhileNode* op) {
   PrintIndent();
   // Match CodeGenC: dynamic-trip-count loops must not be unrolled.
   stream << "#pragma unroll 1\n";
@@ -1612,7 +1612,7 @@ void CodeGenCUDA::Dispatch_(const CallNode* op, 
std::ostream& os) {
   }
 }
 
-void CodeGenCUDA::VisitStmt_(const AttrStmtNode* op) {
+void CodeGenCUDA::Dispatch_(const AttrStmtNode* op) {
   if (op->attr_key == s_tir::attr::fragment_shape) {
     const VarNode* buffer = op->node.as<VarNode>();
     const prim::StringImmNode* shape_str = op->value.as<prim::StringImmNode>();
@@ -1625,7 +1625,7 @@ void CodeGenCUDA::VisitStmt_(const AttrStmtNode* op) {
     const IntImmNode* queue_id = op->value.as<IntImmNode>();
     TVM_FFI_ICHECK(queue_id && queue_id->value == 0)
         << "For CUDA, the index of an async queue must be 0.";
-    this->VisitStmt(op->body);
+    this->Dispatch(op->body);
     static const Op& ptx_cp_async_commit_group_op = 
Op::Get("tirx.ptx.cp_async_commit_group");
     // ptx Call layout: [operands...] [slot tokens] [pred marker ""].
     auto commit_group =
@@ -1655,12 +1655,12 @@ void CodeGenCUDA::VisitStmt_(const AttrStmtNode* op) {
     this->stream << ";\n";
     auto inner = op->body.as<AttrStmtNode>();
     TVM_FFI_ICHECK(inner);
-    this->VisitStmt(inner->body);
+    this->Dispatch(inner->body);
     return;
   } else if (op->attr_key == "disable_unroll") {
     PrintIndent();
     stream << "#pragma unroll 1\n";
-    this->VisitStmt(op->body);
+    this->Dispatch(op->body);
     return;
   } else if (op->attr_key == "pragma_unroll") {
     PrintIndent();
@@ -1669,14 +1669,14 @@ void CodeGenCUDA::VisitStmt_(const AttrStmtNode* op) {
       stream << " " << count->value;
     }
     stream << "\n";
-    this->VisitStmt(op->body);
+    this->Dispatch(op->body);
     return;
   } else if (op->attr_key == tirx::attr::thread_extent) {
   }
-  CodeGenC::VisitStmt_(op);
+  CodeGenC::Dispatch_(op);
 }
 
-void CodeGenCUDA::VisitStmt_(const AllocBufferNode* op) {
+void CodeGenCUDA::Dispatch_(const AllocBufferNode* op) {
   TVM_FFI_ICHECK(op->buffer.defined());
   std::string vid = AllocVarID(op->buffer.get(), op->buffer.name() + "_ptr");
 
@@ -1748,7 +1748,7 @@ void CodeGenCUDA::VisitStmt_(const AllocBufferNode* op) {
   }
 }
 
-void CodeGenCUDA::VisitStmt_(const EvaluateNode* op) {
+void CodeGenCUDA::Dispatch_(const EvaluateNode* op) {
   if (auto value = op->value.as<PrimExpr>(); value && 
is_const_int(value.value())) return;
   const CallNode* call = op->value.as<CallNode>();
   if (call && call->op.same_as(tirx::builtin::tvm_global_barrier_kinit())) {
@@ -1761,7 +1761,7 @@ void CodeGenCUDA::VisitStmt_(const EvaluateNode* op) {
     PrintIndent();
     stream << "}\n";
   } else {
-    CodeGenC::VisitStmt_(op);
+    CodeGenC::Dispatch_(op);
   }
 }
 
diff --git a/src/backend/cuda/codegen/codegen_cuda.h 
b/src/backend/cuda/codegen/codegen_cuda.h
index fa69f4b20f..c7703f9e0b 100644
--- a/src/backend/cuda/codegen/codegen_cuda.h
+++ b/src/backend/cuda/codegen/codegen_cuda.h
@@ -52,8 +52,8 @@ class CodeGenCUDA final : public CodeGenC {
   void PrintFunctionSignature(const ffi::String& function_name, const 
PrimFunc& func,
                               std::ostream& os) final;
   void PrintExtraAttrs(const PrimFunc& f, std::ostream& os) final;  // 
NOLINT(*)
-  void VisitStmt_(const ForNode* op) final;
-  void VisitStmt_(const WhileNode* op) final;
+  void Dispatch_(const ForNode* op) final;
+  void Dispatch_(const WhileNode* op) final;
   void PrintStorageSync(const CallNode* op) final;
   void PrintStorageScope(const std::string& scope, std::ostream& os) final;  
// NOLINT(*)
   using CodeGenC::PrintType;
@@ -77,10 +77,10 @@ class CodeGenCUDA final : public CodeGenC {
   void Dispatch_(const FloatImmNode* op, std::ostream& os) final;
   void Dispatch_(const CallNode* op, std::ostream& os) final;
   void Dispatch_(const prim::CastNode* op, std::ostream& os) final;
-  void VisitStmt_(const EvaluateNode* op) final;
-  void VisitStmt_(const ReturnNode* op) final;
-  void VisitStmt_(const AllocBufferNode* op) final;
-  void VisitStmt_(const AttrStmtNode* op) final;
+  void Dispatch_(const EvaluateNode* op) final;
+  void Dispatch_(const ReturnNode* op) final;
+  void Dispatch_(const AllocBufferNode* op) final;
+  void Dispatch_(const AttrStmtNode* op) final;
 
   // Target
   Target target;
diff --git a/src/backend/cuda/codegen/llvm/codegen_nvptx.cc 
b/src/backend/cuda/codegen/llvm/codegen_nvptx.cc
index e248fed902..6d2bdd0b63 100644
--- a/src/backend/cuda/codegen/llvm/codegen_nvptx.cc
+++ b/src/backend/cuda/codegen/llvm/codegen_nvptx.cc
@@ -78,7 +78,7 @@ class CodeGenNVPTX : public CodeGenLLVM {
                    llvm::ValueAsMetadata::get(ConstInt32(1))}));
   }
 
-  void VisitStmt_(const AllocBufferNode* op) final {
+  void Dispatch_(const AllocBufferNode* op) final {
     llvm::Value* buf = nullptr;
     StorageInfo& info = alloc_storage_info_[op->buffer.get()];
     // maximum necessary alignment in the NV devices
diff --git a/src/backend/hexagon/codegen/llvm/codegen_hexagon.cc 
b/src/backend/hexagon/codegen/llvm/codegen_hexagon.cc
index 61173f52b8..97083d33f0 100644
--- a/src/backend/hexagon/codegen/llvm/codegen_hexagon.cc
+++ b/src/backend/hexagon/codegen/llvm/codegen_hexagon.cc
@@ -79,7 +79,7 @@ class CodeGenHexagon final : public CodeGenCPU {
             bool target_c_runtime) override;
   void InitTarget() final;
 
-  using CodeGenCPU::VisitStmt_;
+  using CodeGenCPU::Dispatch_;
   llvm::Value* Dispatch_(const TensorLoadNode* op) override;
   llvm::Value* CreateIntrinsic(const CallNode* op) override;
 
diff --git a/src/backend/metal/codegen/codegen_metal.cc 
b/src/backend/metal/codegen/codegen_metal.cc
index e25d33db26..a40b188c37 100644
--- a/src/backend/metal/codegen/codegen_metal.cc
+++ b/src/backend/metal/codegen/codegen_metal.cc
@@ -330,10 +330,10 @@ void CodeGenMetal::PrintStorageScope(const std::string& 
scope, std::ostream& os)
   }
 }
 
-void CodeGenMetal::VisitStmt_(const BindNode* op) {
+void CodeGenMetal::Dispatch_(const BindNode* op) {
   const auto* pointer_type = op->var->ty.as<PointerTypeNode>();
   if (pointer_type == nullptr || pointer_type->storage_scope.empty()) {
-    return CodeGenC::VisitStmt_(op);
+    return CodeGenC::Dispatch_(op);
   }
 
   const std::string& storage_scope = pointer_type->storage_scope;
@@ -355,7 +355,7 @@ void CodeGenMetal::VisitStmt_(const BindNode* op) {
   stream << "*)" << value << ";\n";
 }
 
-void CodeGenMetal::VisitStmt_(const AllocBufferNode* op) {
+void CodeGenMetal::Dispatch_(const AllocBufferNode* op) {
   TVM_FFI_ICHECK(op->buffer.defined());
   std::string vid = AllocVarID(op->buffer.get());
 
diff --git a/src/backend/metal/codegen/codegen_metal.h 
b/src/backend/metal/codegen/codegen_metal.h
index 23991df89d..7fad4eb398 100644
--- a/src/backend/metal/codegen/codegen_metal.h
+++ b/src/backend/metal/codegen/codegen_metal.h
@@ -52,8 +52,8 @@ class CodeGenMetal final : public CodeGenC {
   void PrintVecElemStore(const std::string& vec, const PrimType& t, int i,
                          const std::string& value) final;
   // overload visitor
-  void VisitStmt_(const BindNode* op) final;                              // 
NOLINT(*)
-  void VisitStmt_(const AllocBufferNode* op) final;                       // 
NOLINT(*)
+  void Dispatch_(const BindNode* op) final;                               // 
NOLINT(*)
+  void Dispatch_(const AllocBufferNode* op) final;                        // 
NOLINT(*)
   void Dispatch_(const prim::SelectNode* op, std::ostream& os) final;     // 
NOLINT(*)
   void Dispatch_(const prim::BroadcastNode* op, std::ostream& os) final;  // 
NOLINT(*)
   void Dispatch_(const CallNode* op, std::ostream& os) final;             // 
NOLINT(*)
diff --git a/src/backend/opencl/codegen/codegen_opencl.cc 
b/src/backend/opencl/codegen/codegen_opencl.cc
index 692b7cd0a0..8aecd841a5 100644
--- a/src/backend/opencl/codegen/codegen_opencl.cc
+++ b/src/backend/opencl/codegen/codegen_opencl.cc
@@ -445,7 +445,7 @@ std::string CodeGenOpenCL::CastTo(std::string value, const 
PrimType& target) {
   }
 }
 
-void CodeGenOpenCL::VisitStmt_(const AllocBufferNode* op) {
+void CodeGenOpenCL::Dispatch_(const AllocBufferNode* op) {
   // Compute constant_size from buffer shape
   size_t constant_size = 1;
   for (const auto& dim : op->buffer->shape) {
@@ -454,7 +454,7 @@ void CodeGenOpenCL::VisitStmt_(const AllocBufferNode* op) {
     constant_size *= dim_imm->value;
   }
   allocation_size_.insert({op->buffer.get(), constant_size * 
op->buffer->dtype.lanes()});
-  CodeGenC::VisitStmt_(op);
+  CodeGenC::Dispatch_(op);
 }
 
 void CodeGenOpenCL::Dispatch_(const CallNode* op, std::ostream& os) {
diff --git a/src/backend/opencl/codegen/codegen_opencl.h 
b/src/backend/opencl/codegen/codegen_opencl.h
index b3b20ea8b4..6de95420f7 100644
--- a/src/backend/opencl/codegen/codegen_opencl.h
+++ b/src/backend/opencl/codegen/codegen_opencl.h
@@ -64,7 +64,7 @@ class CodeGenOpenCL final : public CodeGenC {
   void SetTextureScope(const std::unordered_map<const VarNode*, 
std::string>&);  // NOLINT(*)
 
   // overload visitor
-  void VisitStmt_(const AllocBufferNode* op) final;                       // 
NOLINT(*)
+  void Dispatch_(const AllocBufferNode* op) final;                        // 
NOLINT(*)
   void Dispatch_(const prim::BroadcastNode* op, std::ostream& os) final;  // 
NOLINT(*)
   void Dispatch_(const prim::RampNode* op, std::ostream& os) final;       // 
NOLINT(*)
   void Dispatch_(const CallNode* op, std::ostream& os) final;             // 
NOLINT(*)
diff --git a/src/backend/rocm/codegen/llvm/codegen_amdgpu.cc 
b/src/backend/rocm/codegen/llvm/codegen_amdgpu.cc
index 3272835f33..1e91582e56 100644
--- a/src/backend/rocm/codegen/llvm/codegen_amdgpu.cc
+++ b/src/backend/rocm/codegen/llvm/codegen_amdgpu.cc
@@ -96,7 +96,7 @@ class CodeGenAMDGPU : public CodeGenLLVM {
     function_->addFnAttr("amdgpu-flat-work-group-size", attr.str());
   }
 
-  void VisitStmt_(const AllocBufferNode* op) final {
+  void Dispatch_(const AllocBufferNode* op) final {
     llvm::Value* buf = nullptr;
     StorageInfo& info = alloc_storage_info_[op->buffer.get()];
     auto storage_scope = 
runtime::StorageScope::Create(GetPtrStorageScope(op->buffer.var()));
diff --git a/src/backend/trn/codegen/codegen_trn.cc 
b/src/backend/trn/codegen/codegen_trn.cc
index bf36859bf6..ee8dbe5f6c 100644
--- a/src/backend/trn/codegen/codegen_trn.cc
+++ b/src/backend/trn/codegen/codegen_trn.cc
@@ -214,7 +214,7 @@ std::string CodeGenTrainium::GetStorageScopeStr(const 
std::string& scope) {  //
   }
 }
 
-void CodeGenTrainium::VisitStmt_(const AllocBufferNode* op) {
+void CodeGenTrainium::Dispatch_(const AllocBufferNode* op) {
   TVM_FFI_ICHECK(op->buffer.defined());
   std::string vid = AllocVarID(op->buffer.get(), op->buffer.name() + "_ptr");
 
@@ -262,7 +262,7 @@ void CodeGenTrainium::VisitStmt_(const AllocBufferNode* op) 
{
   }
 }
 
-void CodeGenTrainium::VisitStmt_(const AttrStmtNode* op) {
+void CodeGenTrainium::Dispatch_(const AttrStmtNode* op) {
   if (op->attr_key == tirx::attr::tensorized_nki_instruction) {
     ctx_.tensorizing = true;
     ctx_.mask = PrimExpr(nullptr);
@@ -275,7 +275,7 @@ void CodeGenTrainium::VisitStmt_(const AttrStmtNode* op) {
   }
 }
 
-void CodeGenTrainium::VisitStmt_(const ForNode* op) {
+void CodeGenTrainium::Dispatch_(const ForNode* op) {
   bool is_outermost_loop = is_outermost_loop_;
   is_outermost_loop_ = false;
   std::string extent = PrintExpr(op->extent);
@@ -335,11 +335,11 @@ std::string CodeGenTrainium::PrintIndices(const 
Array<PrimExpr>& indices) {
   return os.str();
 }
 
-void CodeGenTrainium::VisitStmt_(const BufferStoreNode* op) {
+void CodeGenTrainium::Dispatch_(const BufferStoreNode* op) {
   LOG(FATAL) << "Trainium codegen does not support buffer store";
 }
 
-void CodeGenTrainium::VisitStmt_(const EvaluateNode* op) {
+void CodeGenTrainium::Dispatch_(const EvaluateNode* op) {
   if (auto value = op->value.as<PrimExpr>(); value && 
is_const_int(value.value())) return;
   std::string vid = this->PrintExpr(op->value);
   if (vid != "") {
@@ -614,7 +614,7 @@ void CodeGenTrainium::Dispatch_(const prim::FloorModNode* 
op, std::ostream& os)
   os << PrintExpr(op->a) << " % " << PrintExpr(op->b);
 }
 
-void CodeGenTrainium::VisitStmt_(const DeclBufferNode* op) {
+void CodeGenTrainium::Dispatch_(const DeclBufferNode* op) {
   if (op->buffer.scope() == "trn.psum" || op->buffer.scope() == "trn.sbuf") {
     return;
   }
@@ -684,12 +684,12 @@ ffi::Module BuildTrainium(IRModule mod, Target target) {
   return codegen::DeviceSourceModuleCreate(source_maker.str(), fmt, 
ExtractFuncInfo(mod), "nki");
 }
 
-void CodeGenTrainium::VisitStmt_(const IfThenElseNode* op) {
+void CodeGenTrainium::Dispatch_(const IfThenElseNode* op) {
   if (ctx_.tensorizing) {
     TVM_FFI_ICHECK(!op->else_case.has_value()) << "Else not allowed in 
tensorized instruction";
     TVM_FFI_ICHECK(!ctx_.mask.defined()) << "Only one if stmt allowed in 
tensorized instruction";
     ctx_.mask = op->condition;
-    VisitStmt(op->then_case);
+    Dispatch(op->then_case);
     return;
   }
   std::string cond = PrintExpr(op->condition);
diff --git a/src/backend/trn/codegen/codegen_trn.h 
b/src/backend/trn/codegen/codegen_trn.h
index 429c5b9fb5..7e216f96c3 100644
--- a/src/backend/trn/codegen/codegen_trn.h
+++ b/src/backend/trn/codegen/codegen_trn.h
@@ -51,7 +51,6 @@ class CodeGenTrainium final : public CodeGenC {
   explicit CodeGenTrainium(Target target);
   using CodeGenC::Dispatch_;
   using CodeGenC::PrintType;
-  using CodeGenC::VisitStmt_;
   // override print thread tag.
   void PrintArgUnionDecl();
   void AddFunction(const GlobalVar& gvar, const PrimFunc& func) final;
@@ -59,11 +58,11 @@ class CodeGenTrainium final : public CodeGenC {
   std::string GetStorageScopeStr(const std::string& scope);              // 
NOLINT(*)
   void Dispatch_(const VarNode* op, std::ostream& os) final;             // 
NOLINT(*)
   void PrintType(const PrimType& t, std::ostream& os) final;             // 
NOLINT(*)
-  void VisitStmt_(const AllocBufferNode* op) final;                      // 
NOLINT(*)
-  void VisitStmt_(const AttrStmtNode* op) final;                         // 
NOLINT(*)
-  void VisitStmt_(const ForNode* op) final;                              // 
NOLINT(*)
-  void VisitStmt_(const BufferStoreNode* op) final;                      // 
NOLINT(*)=
-  void VisitStmt_(const EvaluateNode* op) final;                         // 
NOLINT(*)
+  void Dispatch_(const AllocBufferNode* op) final;                       // 
NOLINT(*)
+  void Dispatch_(const AttrStmtNode* op) final;                          // 
NOLINT(*)
+  void Dispatch_(const ForNode* op) final;                               // 
NOLINT(*)
+  void Dispatch_(const BufferStoreNode* op) final;                       // 
NOLINT(*)=
+  void Dispatch_(const EvaluateNode* op) final;                          // 
NOLINT(*)
   std::string PrintIndices(const ffi::Array<PrimExpr>& indices);         // 
NOLINT(*)
   void Dispatch_(const TensorLoadNode* op, std::ostream& os) final;      // 
NOLINT(*)
   void Dispatch_(const CallNode* op, std::ostream& os) final;            // 
NOLINT(*)
@@ -71,8 +70,8 @@ class CodeGenTrainium final : public CodeGenC {
   void Dispatch_(const prim::CastNode* op, std::ostream& os) final;      // 
NOLINT(*)
   void Dispatch_(const prim::FloorDivNode* op, std::ostream& os) final;  // 
NOLINT(*)
   void Dispatch_(const prim::FloorModNode* op, std::ostream& os) final;  // 
NOLINT(*)
-  void VisitStmt_(const DeclBufferNode* op) final;                       // 
NOLINT(*)
-  void VisitStmt_(const IfThenElseNode* op) final;                       // 
NOLINT(*)
+  void Dispatch_(const DeclBufferNode* op) final;                        // 
NOLINT(*)
+  void Dispatch_(const IfThenElseNode* op) final;                        // 
NOLINT(*)
   void Dispatch_(const prim::AndNode* op, std::ostream& os) final;       // 
NOLINT(*)
   void Dispatch_(const prim::OrNode* op, std::ostream& os) final;        // 
NOLINT(*)
 
diff --git a/src/backend/trn/transform/lower_trainium_layout.cc 
b/src/backend/trn/transform/lower_trainium_layout.cc
index 30ed8a5bf4..6d44daf95f 100644
--- a/src/backend/trn/transform/lower_trainium_layout.cc
+++ b/src/backend/trn/transform/lower_trainium_layout.cc
@@ -37,7 +37,7 @@
 #include <utility>
 #include <vector>
 
-#include "../../../tirx/ir_mutator_with_analyzer.h"
+#include "../../../tirx/ir/ir_mutator_with_analyzer.h"
 
 namespace tvm {
 namespace tirx {
diff --git a/src/backend/vulkan/codegen/codegen_spirv.cc 
b/src/backend/vulkan/codegen/codegen_spirv.cc
index 45bfe016b2..62892c96f3 100644
--- a/src/backend/vulkan/codegen/codegen_spirv.cc
+++ b/src/backend/vulkan/codegen/codegen_spirv.cc
@@ -137,7 +137,7 @@ runtime::SPIRVShader CodeGenSPIRV::BuildFunction(const 
PrimFunc& f, const std::s
       }
     }
   }
-  this->VisitStmt(f->body);
+  this->Dispatch(f->body);
   builder_->SetLocalSize(func_ptr, workgroup_size_);
   builder_->MakeInst(spv::OpReturn);
   builder_->MakeInst(spv::OpFunctionEnd);
@@ -684,7 +684,7 @@ spirv::Value CodeGenSPIRV::Dispatch_(const 
prim::ShuffleNode* op) {
   return element;
 }
 
-void CodeGenSPIRV::VisitStmt_(const BufferStoreNode* op) {
+void CodeGenSPIRV::Dispatch_(const BufferStoreNode* op) {
   TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "SPIR-V codegen expects flat 
memory buffers";
   Var buffer_var = op->buffer.var();
   PrimExpr prim_index = op->indices[0];
@@ -733,7 +733,7 @@ void CodeGenSPIRV::VisitStmt_(const BufferStoreNode* op) {
   }
 }
 
-void CodeGenSPIRV::VisitStmt_(const ForNode* op) {
+void CodeGenSPIRV::Dispatch_(const ForNode* op) {
   analyzer_->Bind(op->loop_var, Range::FromMinExtent(op->min, op->extent));
   spirv::Value init_value = MakeValue(op->min);
   PrimExpr end = is_zero(op->min) ? op->extent : analyzer_->Simplify(op->min + 
op->extent);
@@ -775,7 +775,7 @@ void CodeGenSPIRV::VisitStmt_(const ForNode* op) {
   // loop body
   builder_->StartLabel(body_label);
   var_map_[op->loop_var.get()] = spirv::Value(loop_var);
-  this->VisitStmt(op->body);
+  this->Dispatch(op->body);
   builder_->MakeInst(spv::OpBranch, continue_label);
 
   // loop continue
@@ -788,7 +788,7 @@ void CodeGenSPIRV::VisitStmt_(const ForNode* op) {
   builder_->StartLabel(merge_label);
 }
 
-void CodeGenSPIRV::VisitStmt_(const WhileNode* op) {
+void CodeGenSPIRV::Dispatch_(const WhileNode* op) {
   spirv::Label head_label = builder_->NewLabel();
   spirv::Label condition_label = builder_->NewLabel();
   spirv::Label body_label = builder_->NewLabel();
@@ -812,7 +812,7 @@ void CodeGenSPIRV::VisitStmt_(const WhileNode* op) {
 
   // loop body
   builder_->StartLabel(body_label);
-  this->VisitStmt(op->body);
+  this->Dispatch(op->body);
   builder_->MakeInst(spv::OpBranch, continue_label);
 
   // loop continue
@@ -823,7 +823,7 @@ void CodeGenSPIRV::VisitStmt_(const WhileNode* op) {
   builder_->StartLabel(merge_label);
 }
 
-void CodeGenSPIRV::VisitStmt_(const IfThenElseNode* op) {
+void CodeGenSPIRV::Dispatch_(const IfThenElseNode* op) {
   spirv::Value cond = MakeValue(op->condition);
   spirv::Label then_label = builder_->NewLabel();
   spirv::Label merge_label = builder_->NewLabel();
@@ -833,11 +833,11 @@ void CodeGenSPIRV::VisitStmt_(const IfThenElseNode* op) {
     builder_->MakeInst(spv::OpBranchConditional, cond, then_label, else_label);
     // then block
     builder_->StartLabel(then_label);
-    this->VisitStmt(op->then_case);
+    this->Dispatch(op->then_case);
     builder_->MakeInst(spv::OpBranch, merge_label);
     // else block
     builder_->StartLabel(else_label);
-    this->VisitStmt(op->else_case.value());
+    this->Dispatch(op->else_case.value());
     builder_->MakeInst(spv::OpBranch, merge_label);
   } else {
     builder_->MakeInst(spv::OpSelectionMerge, merge_label, 
spv::SelectionControlMaskNone);
@@ -845,14 +845,14 @@ void CodeGenSPIRV::VisitStmt_(const IfThenElseNode* op) {
                        weight_likely_branch_, 1);
     // then block
     builder_->StartLabel(then_label);
-    this->VisitStmt(op->then_case);
+    this->Dispatch(op->then_case);
     builder_->MakeInst(spv::OpBranch, merge_label);
   }
   // start merge label;
   builder_->StartLabel(merge_label);
 }
 
-void CodeGenSPIRV::VisitStmt_(const AllocBufferNode* op) {
+void CodeGenSPIRV::Dispatch_(const AllocBufferNode* op) {
   TVM_FFI_ICHECK(!op->buffer->dtype.IsVoid());
   const IntImmNode* dim_imm = op->buffer->shape[0].as<IntImmNode>();
   TVM_FFI_ICHECK(dim_imm) << "Can only handle constant size stack allocation 
in GPU";
@@ -915,7 +915,7 @@ void CodeGenSPIRV::VisitStmt_(const AllocBufferNode* op) {
   }
 }
 
-void CodeGenSPIRV::VisitStmt_(const DeclBufferNode* op) {
+void CodeGenSPIRV::Dispatch_(const DeclBufferNode* op) {
   const VarNode* buffer_var = op->buffer.get();
   TVM_FFI_ICHECK(!var_map_.count(buffer_var))
       << "Buffer variable " << op->buffer.name() << " is already defined";
@@ -947,7 +947,7 @@ void CodeGenSPIRV::VisitStmt_(const DeclBufferNode* op) {
   storage_info_[buffer_var] = std::move(info);
 }
 
-void CodeGenSPIRV::VisitStmt_(const AttrStmtNode* op) {
+void CodeGenSPIRV::Dispatch_(const AttrStmtNode* op) {
   if (op->attr_key == tirx::attr::thread_extent) {
     auto iv_opt = op->node.as<IterVar>();
     TVM_FFI_ICHECK(iv_opt);
@@ -964,14 +964,14 @@ void CodeGenSPIRV::VisitStmt_(const AttrStmtNode* op) {
     const prim::StringImmNode* shape_str = op->value.as<prim::StringImmNode>();
     fragment_info_[buffer] = {shape_str->value};
   }
-  this->VisitStmt(op->body);
+  this->Dispatch(op->body);
 }
 
-void CodeGenSPIRV::VisitStmt_(const AssertStmtNode* op) {
+void CodeGenSPIRV::Dispatch_(const AssertStmtNode* op) {
   // AssertStmt is a leaf — no body to visit.
 }
 
-void CodeGenSPIRV::VisitStmt_(const BindNode* op) {
+void CodeGenSPIRV::Dispatch_(const BindNode* op) {
   TVM_FFI_ICHECK(!var_map_.count(op->var.get()));
   if (auto prim_type = op->var->ty.as<PrimType>()) {
     TVM_FFI_ICHECK(!prim_type.value().IsVoid());
@@ -984,13 +984,13 @@ void CodeGenSPIRV::VisitStmt_(const BindNode* op) {
   }
 }
 
-void CodeGenSPIRV::VisitStmt_(const SeqStmtNode* op) {
+void CodeGenSPIRV::Dispatch_(const SeqStmtNode* op) {
   for (Stmt stmt : op->seq) {
-    this->VisitStmt(stmt);
+    this->Dispatch(stmt);
   }
 }
 
-void CodeGenSPIRV::VisitStmt_(const EvaluateNode* op) { MakeValue(op->value); }
+void CodeGenSPIRV::Dispatch_(const EvaluateNode* op) { MakeValue(op->value); }
 
 spirv::SType CodeGenSPIRV::GetFragmentSType(const VarNode* buffer, const 
PrimType& dtype) {
   TVM_FFI_ICHECK(fragment_info_.count(buffer));
diff --git a/src/backend/vulkan/codegen/codegen_spirv.h 
b/src/backend/vulkan/codegen/codegen_spirv.h
index b88e5f828f..e5ee1321b2 100644
--- a/src/backend/vulkan/codegen/codegen_spirv.h
+++ b/src/backend/vulkan/codegen/codegen_spirv.h
@@ -53,6 +53,9 @@ using namespace tirx;
 class CodeGenSPIRV : public tirx::ExprFunctor<spirv::Value(const Expr&)>,
                      public StmtFunctor<void(const Stmt&)> {
  public:
+  using tirx::ExprFunctor<spirv::Value(const Expr&)>::Dispatch;
+  using StmtFunctor::Dispatch;
+
   /*!
    * \brief Initialize the codegen based on a specific target.
    *
@@ -105,17 +108,17 @@ class CodeGenSPIRV : public 
tirx::ExprFunctor<spirv::Value(const Expr&)>,
   spirv::Value Dispatch_(const TensorLoadNode* op) override;
   spirv::Value Dispatch_(const prim::ShuffleNode* op) override;
   // stmt
-  void VisitStmt_(const BufferStoreNode* op) override;
-  void VisitStmt_(const ForNode* op) override;
-  void VisitStmt_(const WhileNode* op) override;
-  void VisitStmt_(const IfThenElseNode* op) override;
-  void VisitStmt_(const DeclBufferNode* op) override;
-  void VisitStmt_(const AllocBufferNode* op) override;
-  void VisitStmt_(const AttrStmtNode* op) override;
-  void VisitStmt_(const AssertStmtNode* op) override;
-  void VisitStmt_(const BindNode* op) override;
-  void VisitStmt_(const SeqStmtNode* op) override;
-  void VisitStmt_(const EvaluateNode* op) override;
+  void Dispatch_(const BufferStoreNode* op) override;
+  void Dispatch_(const ForNode* op) override;
+  void Dispatch_(const WhileNode* op) override;
+  void Dispatch_(const IfThenElseNode* op) override;
+  void Dispatch_(const DeclBufferNode* op) override;
+  void Dispatch_(const AllocBufferNode* op) override;
+  void Dispatch_(const AttrStmtNode* op) override;
+  void Dispatch_(const AssertStmtNode* op) override;
+  void Dispatch_(const BindNode* op) override;
+  void Dispatch_(const SeqStmtNode* op) override;
+  void Dispatch_(const EvaluateNode* op) override;
 
  protected:
   /*! \brief Storage information for a buffer */
diff --git a/src/backend/webgpu/codegen/codegen_webgpu.cc 
b/src/backend/webgpu/codegen/codegen_webgpu.cc
index 9d345c27fd..1a4a12267e 100644
--- a/src/backend/webgpu/codegen/codegen_webgpu.cc
+++ b/src/backend/webgpu/codegen/codegen_webgpu.cc
@@ -645,7 +645,7 @@ void CodeGenWebGPU::Dispatch_(const TensorLoadNode* op, 
std::ostream& os) {  //
   }
 }
 
-void CodeGenWebGPU::VisitStmt_(const BindNode* op) {
+void CodeGenWebGPU::Dispatch_(const BindNode* op) {
   // use ssa form.
   if (print_ssa_form_) {
     std::string value = PrintExpr(op->value);
@@ -660,7 +660,7 @@ void CodeGenWebGPU::VisitStmt_(const BindNode* op) {
   }
 }
 
-void CodeGenWebGPU::VisitStmt_(const BufferStoreNode* op) {
+void CodeGenWebGPU::Dispatch_(const BufferStoreNode* op) {
   TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "Store to non-flat memory not 
supported.";
 
   PrimType value_ty = op->value.ty();
@@ -720,7 +720,7 @@ void CodeGenWebGPU::VisitStmt_(const BufferStoreNode* op) {
   }
 }
 
-void CodeGenWebGPU::VisitStmt_(const AllocBufferNode* op) {
+void CodeGenWebGPU::Dispatch_(const AllocBufferNode* op) {
   TVM_FFI_ICHECK(op->buffer.defined());
   std::string vid = AllocVarID(op->buffer.get());
   size_t constant_size = 1;
@@ -782,7 +782,7 @@ void CodeGenWebGPU::VisitStmt_(const AllocBufferNode* op) {
   }
 }
 
-void CodeGenWebGPU::VisitStmt_(const ForNode* op) {
+void CodeGenWebGPU::Dispatch_(const ForNode* op) {
   std::string begin_str = PrintExpr(op->min);
   PrimExpr end = is_zero(op->min) ? op->extent : 
arith::Analyzer()->Simplify(op->min + op->extent);
   std::string end_str = PrintExpr(end);
@@ -805,11 +805,11 @@ void CodeGenWebGPU::VisitStmt_(const ForNode* op) {
   stream << "}\n";
 }
 
-void CodeGenWebGPU::VisitStmt_(const AssertStmtNode* op) {
+void CodeGenWebGPU::Dispatch_(const AssertStmtNode* op) {
   // skip assert — AssertStmt is a leaf, nothing to emit.
 }
 
-void CodeGenWebGPU::VisitStmt_(const WhileNode* op) {
+void CodeGenWebGPU::Dispatch_(const WhileNode* op) {
   PrintIndent();
   stream << "while (true) {\n";
   int while_scope = BeginScope();
@@ -822,12 +822,12 @@ void CodeGenWebGPU::VisitStmt_(const WhileNode* op) {
   stream << "}\n";
 }
 
-void CodeGenWebGPU::VisitStmt_(const BreakNode* op) {
+void CodeGenWebGPU::Dispatch_(const BreakNode* op) {
   PrintIndent();
   stream << "break;\n";
 }
 
-void CodeGenWebGPU::VisitStmt_(const ContinueNode* op) {
+void CodeGenWebGPU::Dispatch_(const ContinueNode* op) {
   PrintIndent();
   stream << "continue;\n";
 }
diff --git a/src/backend/webgpu/codegen/codegen_webgpu.h 
b/src/backend/webgpu/codegen/codegen_webgpu.h
index 9f906b98c4..e7e4bc1d06 100644
--- a/src/backend/webgpu/codegen/codegen_webgpu.h
+++ b/src/backend/webgpu/codegen/codegen_webgpu.h
@@ -76,14 +76,14 @@ class CodeGenWebGPU final : public CodeGenC {
   void Dispatch_(const IntImmNode* op, std::ostream& os) final;           // 
NOLINT(*)
 
   // stmt printing
-  void VisitStmt_(const BindNode* op) final;
-  void VisitStmt_(const BufferStoreNode* op) final;
-  void VisitStmt_(const ForNode* op) final;
-  void VisitStmt_(const AllocBufferNode* op) final;
-  void VisitStmt_(const AssertStmtNode* op) final;
-  void VisitStmt_(const WhileNode* op) final;
-  void VisitStmt_(const BreakNode* op) final;
-  void VisitStmt_(const ContinueNode* op) final;
+  void Dispatch_(const BindNode* op) final;
+  void Dispatch_(const BufferStoreNode* op) final;
+  void Dispatch_(const ForNode* op) final;
+  void Dispatch_(const AllocBufferNode* op) final;
+  void Dispatch_(const AssertStmtNode* op) final;
+  void Dispatch_(const WhileNode* op) final;
+  void Dispatch_(const BreakNode* op) final;
+  void Dispatch_(const ContinueNode* op) final;
 
  private:
   /*!
diff --git a/src/relax/transform/split_call_tir_by_pattern.cc 
b/src/relax/transform/split_call_tir_by_pattern.cc
index 5f24da7cec..c2c621fb03 100644
--- a/src/relax/transform/split_call_tir_by_pattern.cc
+++ b/src/relax/transform/split_call_tir_by_pattern.cc
@@ -49,12 +49,12 @@ using namespace tvm::prim;
 using relax::FCodegen;
 using relax::MatchResult;
 using relax::TIRPattern;
-using s_tir::ExprComparator;
 using s_tir::TensorizeComparator;
 
 /*! \brief helper to match a for stmt to a pattern*/
 class ForMatcher : public TensorizeComparator {
  public:
+  using TensorizeComparator::Dispatch;
   using SymbolMap = std::unordered_map<Var, PrimExpr>;
   explicit ForMatcher(const tirx::PrimFunc& pattern, const ffi::Array<Var>& 
pattern_vars)
       : TensorizeComparator(IRModule({{GlobalVar(""), pattern}}), false), 
pattern_(pattern) {
@@ -67,7 +67,7 @@ class ForMatcher : public TensorizeComparator {
   bool Match(const For& top) {
     const ForNode* pattern_top = 
pattern_->body.as<SBlockRealizeNode>()->block->body.as<ForNode>();
     TVM_FFI_ICHECK(pattern_top) << "Invalid pattern function";
-    if (!VisitStmt(top, ffi::GetRef<Stmt>(pattern_top))) {
+    if (!Dispatch(top, ffi::GetRef<Stmt>(pattern_top))) {
       return false;
     }
     // Get evaluated symbols, buffers from the pattern.
@@ -85,7 +85,7 @@ class ForMatcher : public TensorizeComparator {
   std::vector<BufferVar> evaluated_buffers;
 
  private:
-  using ExprComparator::Dispatch_;
+  using TensorizeComparator::Dispatch_;
 
   ffi::Optional<PrimExpr> QueryEvaluatedSymbols(const Var& var) {
     for (const SymbolMap& symbol_map : evaluated_symbols) {
@@ -249,7 +249,7 @@ class ForMatcher : public TensorizeComparator {
     return true;
   }
 
-  bool VisitStmt_(const tirx::ForNode* op, const Stmt& other) final {
+  bool Dispatch_(const tirx::ForNode* op, const Stmt& other) final {
     const auto* rhs = other.as<ForNode>();
     loop_stack_lhs_.push_back(ffi::GetRef<For>(op));
     loop_stack_rhs_.push_back(ffi::GetRef<For>(rhs));
@@ -269,10 +269,10 @@ class ForMatcher : public TensorizeComparator {
     if (!op->annotations.empty() || !rhs->annotations.empty()) return false;
     // Match the extents of loops
     if (!Dispatch(op->extent, rhs->extent)) return false;
-    return VisitStmt(op->body, rhs->body);
+    return Dispatch(op->body, rhs->body);
   }
 
-  bool VisitStmt_(const tirx::SBlockNode* op, const Stmt& other) final {
+  bool Dispatch_(const tirx::SBlockNode* op, const Stmt& other) final {
     const auto* rhs = other.as<SBlockNode>();
     // Check block equality.
     // All iter vars and buffer regions including the order should match.
@@ -296,12 +296,12 @@ class ForMatcher : public TensorizeComparator {
     if (op->init.has_value() && !rhs->init.has_value()) return false;
     if (!op->init.has_value() && rhs->init.has_value()) return false;
     if (op->init.has_value() && rhs->init.has_value()) {
-      if (!VisitStmt(op->init.value(), rhs->init.value())) return false;
+      if (!Dispatch(op->init.value(), rhs->init.value())) return false;
     }
-    return VisitStmt(op->body, rhs->body);
+    return Dispatch(op->body, rhs->body);
   }
 
-  bool VisitStmt_(const SBlockRealizeNode* op, const Stmt& other) final {
+  bool Dispatch_(const SBlockRealizeNode* op, const Stmt& other) final {
     const auto* rhs = other.as<SBlockRealizeNode>();
     // Only allow trivial bindings
     for (size_t i = 0; i < op->iter_values.size(); ++i) {
@@ -312,10 +312,10 @@ class ForMatcher : public TensorizeComparator {
     }
     // Disallow predicates now
     if (!is_one(op->predicate) || !is_one(rhs->predicate)) return false;
-    return VisitStmt(op->block, rhs->block);
+    return Dispatch(op->block, rhs->block);
   }
 
-  bool VisitStmt_(const BufferStoreNode* op, const Stmt& other) {
+  bool Dispatch_(const BufferStoreNode* op, const Stmt& other) {
     const auto* rhs = other.as<BufferStoreNode>();
     return CompareBufferAccess(op, rhs) && Dispatch(op->value, rhs->value);
   }
@@ -357,7 +357,9 @@ class ForMatcher : public TensorizeComparator {
   template <typename T>
   bool CompareBufferAccess(const T* lhs, const T* rhs) {
     if (!CompareBuffer(lhs->buffer, rhs->buffer)) return false;
-    return CompareArray(lhs->indices, rhs->indices, &ForMatcher::Dispatch);
+    return CompareArray(
+        lhs->indices, rhs->indices,
+        static_cast<bool (ForMatcher::*)(const Expr&, const 
PrimExpr&)>(&ForMatcher::Dispatch));
   }
 
   bool CompareBufferAccess(const TensorLoadNode* lhs, const TensorLoadNode* 
rhs) {
@@ -366,7 +368,9 @@ class ForMatcher : public TensorizeComparator {
                        rhs->source.as_or_throw<BufferVar>())) {
       return false;
     }
-    return CompareArray(lhs->indices, rhs->indices, &ForMatcher::Dispatch);
+    return CompareArray(
+        lhs->indices, rhs->indices,
+        static_cast<bool (ForMatcher::*)(const Expr&, const 
PrimExpr&)>(&ForMatcher::Dispatch));
   }
 
   template <typename T, typename Self, typename F>
diff --git a/src/s_tir/analysis/domain_touched.cc 
b/src/s_tir/analysis/domain_touched.cc
index 11c45d6b28..2e67be88ea 100644
--- a/src/s_tir/analysis/domain_touched.cc
+++ b/src/s_tir/analysis/domain_touched.cc
@@ -34,7 +34,7 @@
 #include <unordered_map>
 #include <unordered_set>
 
-#include "../../tirx/ir_visitor_with_analyzer.h"
+#include "../../tirx/ir/ir_visitor_with_analyzer.h"
 
 namespace tvm {
 namespace s_tir {
diff --git a/src/s_tir/analysis/estimate_flops.cc 
b/src/s_tir/analysis/estimate_flops.cc
index 9e410064fa..0da2814819 100644
--- a/src/s_tir/analysis/estimate_flops.cc
+++ b/src/s_tir/analysis/estimate_flops.cc
@@ -90,7 +90,7 @@ class FlopEstimator : private tirx::ExprFunctor<TResult(const 
Expr& n)>,
 
  public:
   using tirx::ExprFunctor<TResult(const Expr&)>::Dispatch;
-  TResult VisitStmt(const Stmt& stmt) override { return 
StmtFunctor::VisitStmt(stmt); }
+  TResult Dispatch(const Stmt& stmt) override { return 
StmtFunctor::Dispatch(stmt); }
 
 #define TVM_TIR_ESTIMATE_FLOP_VISIT_BINARY(Node)       \
   TResult Dispatch_(const Node* op) final {            \
@@ -138,49 +138,49 @@ class FlopEstimator : private 
tirx::ExprFunctor<TResult(const Expr& n)>,
   }
 
   TResult Dispatch_(const TensorLoadNode* op) override { return TResult(); }
-  TResult VisitStmt_(const AttrStmtNode* op) override {
-    TResult result = VisitStmt(op->body);
+  TResult Dispatch_(const AttrStmtNode* op) override {
+    TResult result = Dispatch(op->body);
     result += Dispatch(op->value);
     return result;
   }
-  TResult VisitStmt_(const BufferStoreNode* store) override { return 
Dispatch(store->value); }
-  TResult VisitStmt_(const SBlockRealizeNode* block) override {
-    return VisitStmt(block->block->body);
+  TResult Dispatch_(const BufferStoreNode* store) override { return 
Dispatch(store->value); }
+  TResult Dispatch_(const SBlockRealizeNode* block) override {
+    return Dispatch(block->block->body);
   }
-  TResult VisitStmt_(const SBlockNode* block) override {
+  TResult Dispatch_(const SBlockNode* block) override {
     TResult result;
     if (block->init.has_value()) {
-      result += VisitStmt(block->init.value());
+      result += Dispatch(block->init.value());
     }
-    result += VisitStmt(block->body);
+    result += Dispatch(block->body);
     return result;
   }
-  TResult VisitStmt_(const ForNode* loop) override {
+  TResult Dispatch_(const ForNode* loop) override {
     ana->Bind(loop->loop_var, Range::FromMinExtent(loop->min, loop->extent));
     const auto int_imm = GetLoopExtent(loop, ana);
-    TResult result = VisitStmt(loop->body);
+    TResult result = Dispatch(loop->body);
     result *= int_imm;
     return result;
   }
 
-  TResult VisitStmt_(const IfThenElseNode* branch) override {
+  TResult Dispatch_(const IfThenElseNode* branch) override {
     TResult cond = Dispatch(branch->condition);
     if (branch->else_case) {
-      cond += 
VisitStmt(branch->then_case).MaxWith(VisitStmt(branch->else_case.value()));
+      cond += 
Dispatch(branch->then_case).MaxWith(Dispatch(branch->else_case.value()));
     } else {
-      cond += VisitStmt(branch->then_case);
+      cond += Dispatch(branch->then_case);
     }
     return cond;
   }
 
-  TResult VisitStmt_(const WhileNode* op) override {
+  TResult Dispatch_(const WhileNode* op) override {
     // TODO(jikechao): Improve while loop FLOP estimation with loop bound 
analysis
     TResult result = Dispatch(op->condition);
-    result += VisitStmt(op->body);
+    result += Dispatch(op->body);
     return result;
   }
 
-  TResult VisitStmt_(const BindNode* let) override {
+  TResult Dispatch_(const BindNode* let) override {
     if (auto value = let->value.as<PrimExpr>()) return Dispatch(value.value());
     return TResult();
   }
@@ -191,7 +191,7 @@ class FlopEstimator : private 
tirx::ExprFunctor<TResult(const Expr& n)>,
     return cond;
   }
 
-  TResult VisitStmt_(const AssertStmtNode* op) override {
+  TResult Dispatch_(const AssertStmtNode* op) override {
     TResult result = Dispatch(op->condition);
     return result;
   }
@@ -201,14 +201,14 @@ class FlopEstimator : private 
tirx::ExprFunctor<TResult(const Expr& n)>,
   TResult Dispatch_(const FloatImmNode* op) override { return TResult(); }
   TResult Dispatch_(const prim::StringImmNode* op) override { return 
TResult(); }
   TResult Dispatch_(const prim::CastNode* op) override { return 
Dispatch(op->value); }
-  TResult VisitStmt_(const AllocBufferNode* op) override { return TResult(); }
-  TResult VisitStmt_(const DeclBufferNode* op) override { return TResult(); }
-  TResult VisitStmt_(const EvaluateNode* op) override { return TResult(); }
+  TResult Dispatch_(const AllocBufferNode* op) override { return TResult(); }
+  TResult Dispatch_(const DeclBufferNode* op) override { return TResult(); }
+  TResult Dispatch_(const EvaluateNode* op) override { return TResult(); }
 
-  TResult VisitStmt_(const SeqStmtNode* seq) override {
+  TResult Dispatch_(const SeqStmtNode* seq) override {
     TResult result;
     for (const Stmt& stmt : seq->seq) {
-      result += VisitStmt(stmt);
+      result += Dispatch(stmt);
     }
     return result;
   }
@@ -232,7 +232,7 @@ double PostprocessResults(const TResult& result) {
 
 double EstimateTIRFlops(const Stmt& stmt) {
   FlopEstimator counter;
-  return PostprocessResults(counter.VisitStmt(stmt));
+  return PostprocessResults(counter.Dispatch(stmt));
 }
 
 double EstimateTIRFlops(const IRModule& mod) {
@@ -243,7 +243,7 @@ double EstimateTIRFlops(const IRModule& mod) {
     if (auto cached = f->attrs.GetAttr<int64_t>("estimated_flops")) {
       cached_result += cached.value();
     } else {
-      result += counter.VisitStmt(f->body);  //
+      result += counter.Dispatch(f->body);  //
     }
   });
   return PostprocessResults(result) + cached_result;
diff --git a/src/s_tir/analysis/identify_memcpy.cc 
b/src/s_tir/analysis/identify_memcpy.cc
index f965745fa6..62be9ec6c4 100644
--- a/src/s_tir/analysis/identify_memcpy.cc
+++ b/src/s_tir/analysis/identify_memcpy.cc
@@ -38,7 +38,7 @@
 #include <string>
 #include <variant>
 
-#include "../../tirx/ir_visitor_with_analyzer.h"
+#include "../../tirx/ir/ir_visitor_with_analyzer.h"
 
 namespace tvm {
 namespace s_tir {
diff --git a/src/s_tir/analysis/is_pure_function.cc 
b/src/s_tir/analysis/is_pure_function.cc
index d0c4785cc4..3c7acb10e6 100644
--- a/src/s_tir/analysis/is_pure_function.cc
+++ b/src/s_tir/analysis/is_pure_function.cc
@@ -45,13 +45,13 @@ class PurityChecker : TIRVisitorWithPath {
  private:
   explicit PurityChecker(bool assert_on_error) : 
assert_on_error_(assert_on_error) {}
 
-  void VisitStmt_(const AllocBufferNode* op, ffi::reflection::AccessPath path) 
override {
+  void Dispatch_(const AllocBufferNode* op, ffi::reflection::AccessPath path) 
override {
     internal_allocations_.insert(op->buffer.var());
-    TIRVisitorWithPath::VisitStmt_(op, path);
+    TIRVisitorWithPath::Dispatch_(op, path);
   }
 
-  void VisitStmt_(const BufferStoreNode* op, ffi::reflection::AccessPath path) 
override {
-    TIRVisitorWithPath::VisitStmt_(op, path);
+  void Dispatch_(const BufferStoreNode* op, ffi::reflection::AccessPath path) 
override {
+    TIRVisitorWithPath::Dispatch_(op, path);
 
     if (!internal_allocations_.count(op->buffer.var())) {
       is_pure_ = false;
diff --git a/src/s_tir/analysis/oob_checker.cc 
b/src/s_tir/analysis/oob_checker.cc
index bb40c9951e..55bf73e344 100644
--- a/src/s_tir/analysis/oob_checker.cc
+++ b/src/s_tir/analysis/oob_checker.cc
@@ -24,7 +24,7 @@
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/tirx/transform.h>
 
-#include "../../tirx/ir_visitor_with_analyzer.h"
+#include "../../tirx/ir/ir_visitor_with_analyzer.h"
 #include "../schedule/error.h"
 
 namespace tvm {
diff --git a/src/s_tir/backend/adreno/inject_texture_alloc.cc 
b/src/s_tir/backend/adreno/inject_texture_alloc.cc
index 13ee5520fc..0602e47a77 100644
--- a/src/s_tir/backend/adreno/inject_texture_alloc.cc
+++ b/src/s_tir/backend/adreno/inject_texture_alloc.cc
@@ -27,7 +27,7 @@
 #include <tvm/tirx/stmt_functor.h>
 
 #include "../../../backend/opencl/runtime/texture.h"
-#include "../../../tirx/ir_mutator_with_analyzer.h"
+#include "../../../tirx/ir/ir_mutator_with_analyzer.h"
 #include "../../../tirx/transform/ir_utils.h"
 
 namespace tvm {
diff --git a/src/s_tir/backend/adreno/texture_flatten.cc 
b/src/s_tir/backend/adreno/texture_flatten.cc
index 3e76a21fe3..616c307a47 100644
--- a/src/s_tir/backend/adreno/texture_flatten.cc
+++ b/src/s_tir/backend/adreno/texture_flatten.cc
@@ -35,7 +35,7 @@
 
 #include "../../../backend/opencl/runtime/texture.h"
 #include "../../../runtime/thread_storage_scope.h"
-#include "../../../tirx/ir_visitor_with_analyzer.h"
+#include "../../../tirx/ir/ir_visitor_with_analyzer.h"
 
 namespace tvm {
 namespace s_tir {
diff --git a/src/s_tir/schedule/analysis/analysis.cc 
b/src/s_tir/schedule/analysis/analysis.cc
index 6d695274b5..acc17917e0 100644
--- a/src/s_tir/schedule/analysis/analysis.cc
+++ b/src/s_tir/schedule/analysis/analysis.cc
@@ -2159,7 +2159,7 @@ bool CheckAutoTensorizeApplicable(const ScheduleState& 
state, const tirx::StmtSR
   arith::Analyzer analyzer;
   auto desc_info = ExtractTensorIntrinDescInfo(analyzer.get(), desc_func);
 
-  return extractor->VisitStmt(block->block, desc_info.desc_block->block);
+  return extractor->Dispatch(block->block, desc_info.desc_block->block);
 }
 
 bool CheckAutoTensorizeApplicable(const s_tir::Schedule& sch, const 
s_tir::SBlockRV& block_rv,
diff --git a/src/s_tir/schedule/ir_comparator.cc 
b/src/s_tir/schedule/ir_comparator.cc
index 81da621e37..1b8b1851cf 100644
--- a/src/s_tir/schedule/ir_comparator.cc
+++ b/src/s_tir/schedule/ir_comparator.cc
@@ -85,9 +85,9 @@ class TensorIntrinMismatchError : public 
ScheduleErrorContextObj {
 };
 
 /* Override the dispatcher to make sure RHS is always valid */
-bool TensorizeComparator::VisitStmt(const Stmt& n, const Stmt& other) {
+bool TensorizeComparator::Dispatch(const Stmt& n, const Stmt& other) {
   bool equal = n.same_as(other) ||
-               ((n->type_index() == other->type_index()) && 
StmtComparator::VisitStmt(n, other));
+               ((n->type_index() == other->type_index()) && 
StmtComparator::Dispatch(n, other));
   if (!equal && assert_mode_ && (n->IsInstance<ForNode>() || 
n->IsInstance<SBlockNode>())) {
     throw MakeScheduleError<TensorIntrinMismatchError>(lhs_mod_, n, other,
                                                        
std::move(error_messages_));
@@ -171,7 +171,7 @@ bool TensorizeComparator::Dispatch_(const CallNode* op, 
const PrimExpr& other) {
   return true;
 }
 
-bool TensorizeComparator::VisitStmt_(const ForNode* op, const Stmt& other) {
+bool TensorizeComparator::Dispatch_(const ForNode* op, const Stmt& other) {
   const auto* rhs = other.as<ForNode>();
   if (!DefEqual(op->loop_var, rhs->loop_var)) {
     if (assert_mode_) {
@@ -230,23 +230,27 @@ bool TensorizeComparator::VisitStmt_(const ForNode* op, 
const Stmt& other) {
     }
     return false;
   }
-  return VisitStmt(op->body, rhs->body);
+  return Dispatch(op->body, rhs->body);
 }
 
-bool TensorizeComparator::VisitStmt_(const SeqStmtNode* op, const Stmt& other) 
{
+bool TensorizeComparator::Dispatch_(const SeqStmtNode* op, const Stmt& other) {
   const auto* rhs = other.as<SeqStmtNode>();
-  return CompareArray(op->seq, rhs->seq, &TensorizeComparator::VisitStmt);
+  return CompareArray(op->seq, rhs->seq,
+                      static_cast<bool (TensorizeComparator::*)(const Stmt&, 
const Stmt&)>(
+                          &TensorizeComparator::Dispatch));
 }
 
-bool TensorizeComparator::VisitStmt_(const BufferStoreNode* op, const Stmt& 
other) {
+bool TensorizeComparator::Dispatch_(const BufferStoreNode* op, const Stmt& 
other) {
   const auto* rhs = other.as<BufferStoreNode>();
   return CompareBufferAccess(op, rhs) && Dispatch(op->value, rhs->value);
 }
 
-bool TensorizeComparator::VisitStmt_(const SBlockRealizeNode* op, const Stmt& 
other) {
+bool TensorizeComparator::Dispatch_(const SBlockRealizeNode* op, const Stmt& 
other) {
   const auto* rhs = other.as<SBlockRealizeNode>();
   if (!is_scope_block) {
-    if (!CompareArray(op->iter_values, rhs->iter_values, 
&TensorizeComparator::Dispatch)) {
+    if (!CompareArray(op->iter_values, rhs->iter_values,
+                      static_cast<bool (TensorizeComparator::*)(const Expr&, 
const PrimExpr&)>(
+                          &TensorizeComparator::Dispatch))) {
       if (assert_mode_) {
         std::ostringstream os;
         os << "BlockRealizeNode iter_values do not match: op->iter_values=" << 
op->iter_values
@@ -256,10 +260,10 @@ bool TensorizeComparator::VisitStmt_(const 
SBlockRealizeNode* op, const Stmt& ot
       return false;
     }
   }
-  return Dispatch(op->predicate, rhs->predicate) && VisitStmt(op->block, 
rhs->block);
+  return Dispatch(op->predicate, rhs->predicate) && Dispatch(op->block, 
rhs->block);
 }
 
-bool TensorizeComparator::VisitStmt_(const SBlockNode* op, const Stmt& other) {
+bool TensorizeComparator::Dispatch_(const SBlockNode* op, const Stmt& other) {
   const auto* rhs = other.as<SBlockNode>();
   for (const IterVar& iter : op->iter_vars) {
     lhs_analyzer_->Bind(iter->var, iter->dom);
@@ -306,7 +310,7 @@ bool TensorizeComparator::VisitStmt_(const SBlockNode* op, 
const Stmt& other) {
     return false;
   }
   is_scope_block = false;
-  return VisitStmt(op->body, rhs->body);
+  return Dispatch(op->body, rhs->body);
 }
 
 // Exprs
@@ -709,11 +713,11 @@ bool AutoTensorizeComparator::DispatchDefault_(const 
ffi::Object* op, const Prim
   return false;
 }
 
-bool AutoTensorizeComparator::VisitStmtDefault_(const ffi::Object* op, const 
Stmt& other) {
+bool AutoTensorizeComparator::DispatchDefault_(const ffi::Object* op, const 
Stmt& other) {
   return false;
 }
 
-bool AutoTensorizeComparator::VisitStmt_(const SBlockNode* op, const Stmt& 
other) {
+bool AutoTensorizeComparator::Dispatch_(const SBlockNode* op, const Stmt& 
other) {
   const auto* rhs = other.as<SBlockNode>();
   // Check block equality.
   // All iter vars and buffer regions including the order should match.
@@ -753,7 +757,7 @@ bool AutoTensorizeComparator::VisitStmt_(const SBlockNode* 
op, const Stmt& other
     }
   }
   is_scope_block = false;
-  return VisitStmt(op->body, rhs->body);
+  return Dispatch(op->body, rhs->body);
 }
 
 bool AutoTensorizeComparator::CompareBuffer(const BufferVar& lhs, const 
BufferVar& rhs) {
@@ -783,7 +787,7 @@ bool AutoTensorizeComparator::CompareBuffer(const 
BufferVar& lhs, const BufferVa
   return equal;
 }
 
-bool AutoTensorizeComparator::VisitStmt_(const BufferStoreNode* op, const 
Stmt& other) {
+bool AutoTensorizeComparator::Dispatch_(const BufferStoreNode* op, const Stmt& 
other) {
   const auto* rhs = other.as<BufferStoreNode>();
   return CompareBufferAccess(op, rhs) && Dispatch(op->value, rhs->value);
 }
diff --git a/src/s_tir/schedule/ir_comparator.h 
b/src/s_tir/schedule/ir_comparator.h
index 28c8332341..75303df5b8 100644
--- a/src/s_tir/schedule/ir_comparator.h
+++ b/src/s_tir/schedule/ir_comparator.h
@@ -47,14 +47,14 @@ class TensorizeComparator : public ExprComparator, public 
StmtComparator {
       : lhs_mod_(std::move(lhs_mod)), assert_mode_(assert_mode) {}
 
   bool Dispatch(const Expr& n, const PrimExpr& other) override;
-  bool VisitStmt(const Stmt& n, const Stmt& other) override;
+  bool Dispatch(const Stmt& n, const Stmt& other) override;
 
   bool Dispatch_(const CallNode* op, const PrimExpr& other) override;
-  bool VisitStmt_(const ForNode* op, const Stmt& other) override;
-  bool VisitStmt_(const SeqStmtNode* op, const Stmt& other) override;
-  bool VisitStmt_(const BufferStoreNode* op, const Stmt& other) override;
-  bool VisitStmt_(const SBlockRealizeNode* op, const Stmt& other) override;
-  bool VisitStmt_(const SBlockNode* op, const Stmt& other) override;
+  bool Dispatch_(const ForNode* op, const Stmt& other) override;
+  bool Dispatch_(const SeqStmtNode* op, const Stmt& other) override;
+  bool Dispatch_(const BufferStoreNode* op, const Stmt& other) override;
+  bool Dispatch_(const SBlockRealizeNode* op, const Stmt& other) override;
+  bool Dispatch_(const SBlockNode* op, const Stmt& other) override;
 
   bool Dispatch_(const AddNode* op, const PrimExpr& other) override;
   bool Dispatch_(const SubNode* op, const PrimExpr& other) override;
@@ -139,10 +139,10 @@ class AutoTensorizeComparator : public 
TensorizeComparator {
 
  private:
   bool DispatchDefault_(const ffi::Object* op, const PrimExpr& other) override;
-  bool VisitStmtDefault_(const ffi::Object* op, const Stmt& other) override;
+  bool DispatchDefault_(const ffi::Object* op, const Stmt& other) override;
 
-  bool VisitStmt_(const SBlockNode* op, const Stmt& other) override;
-  bool VisitStmt_(const BufferStoreNode* op, const Stmt& other) override;
+  bool Dispatch_(const SBlockNode* op, const Stmt& other) override;
+  bool Dispatch_(const BufferStoreNode* op, const Stmt& other) override;
 
   bool Dispatch_(const TensorLoadNode* op, const PrimExpr& other) override;
 
diff --git a/src/s_tir/schedule/primitive/blockize_tensorize.cc 
b/src/s_tir/schedule/primitive/blockize_tensorize.cc
index 830909374d..572f8c4804 100644
--- a/src/s_tir/schedule/primitive/blockize_tensorize.cc
+++ b/src/s_tir/schedule/primitive/blockize_tensorize.cc
@@ -835,7 +835,7 @@ void Tensorize(ScheduleState self, const StmtSRef& sref, 
const TensorIntrin& int
                     ->Rewrite(intrin_impl);
   // Step 2: Structural pattern matching
   TensorizeComparator comparator(self->mod, /*assert_mode=*/true);
-  comparator.VisitStmt(block_realize, intrin_desc->body);
+  comparator.Dispatch(block_realize, intrin_desc->body);
   // Step 3: Prepare necessary mapping
   // 1) BufferVar mapping from intrin impl buffers to intrin desc buffers.
   // 2) BufferVar mapping from intrin impl buffers to buffers in the current 
AST.
diff --git a/src/s_tir/schedule/primitive/layout_transformation.cc 
b/src/s_tir/schedule/primitive/layout_transformation.cc
index 72475f2465..207b226ef8 100644
--- a/src/s_tir/schedule/primitive/layout_transformation.cc
+++ b/src/s_tir/schedule/primitive/layout_transformation.cc
@@ -27,7 +27,7 @@
 #include <optional>
 #include <variant>
 
-#include "../../../tirx/ir_mutator_with_analyzer.h"
+#include "../../../tirx/ir/ir_mutator_with_analyzer.h"
 #include "../utils.h"
 
 namespace tvm {
diff --git a/src/s_tir/schedule/transform.h b/src/s_tir/schedule/transform.h
index 72c76daaa9..8e0cd2098a 100644
--- a/src/s_tir/schedule/transform.h
+++ b/src/s_tir/schedule/transform.h
@@ -28,7 +28,7 @@
 #include <utility>
 
 #include "../../tirx/ir/functor_common.h"
-#include "../../tirx/ir_mutator_with_analyzer.h"
+#include "../../tirx/ir/ir_mutator_with_analyzer.h"
 
 namespace tvm {
 namespace s_tir {
diff --git a/src/s_tir/transform/hoist_expression.cc 
b/src/s_tir/transform/hoist_expression.cc
index 305bda9be8..9af41d25c5 100644
--- a/src/s_tir/transform/hoist_expression.cc
+++ b/src/s_tir/transform/hoist_expression.cc
@@ -37,7 +37,7 @@
 
 #include "../../arith/interval_set.h"
 #include "../../runtime/thread_storage_scope.h"
-#include "../../tirx/ir_mutator_with_analyzer.h"
+#include "../../tirx/ir/ir_mutator_with_analyzer.h"
 #include "../../tirx/transform/ir_utils.h"
 
 namespace tvm {
diff --git a/src/s_tir/transform/inject_permuted_layout.cc 
b/src/s_tir/transform/inject_permuted_layout.cc
index 55e71985f8..11bcdbcebe 100644
--- a/src/s_tir/transform/inject_permuted_layout.cc
+++ b/src/s_tir/transform/inject_permuted_layout.cc
@@ -31,7 +31,7 @@
 
 #include "../../runtime/thread_storage_scope.h"
 #include "../../support/utils.h"
-#include "../../tirx/ir_mutator_with_analyzer.h"
+#include "../../tirx/ir/ir_mutator_with_analyzer.h"
 #include "../../tirx/transform/ir_utils.h"
 
 namespace tvm {
diff --git a/src/s_tir/transform/inject_virtual_thread.cc 
b/src/s_tir/transform/inject_virtual_thread.cc
index 6397723504..5c4dd96d02 100644
--- a/src/s_tir/transform/inject_virtual_thread.cc
+++ b/src/s_tir/transform/inject_virtual_thread.cc
@@ -33,7 +33,7 @@
 
 #include <unordered_set>
 
-#include "../../tirx/ir_mutator_with_analyzer.h"
+#include "../../tirx/ir/ir_mutator_with_analyzer.h"
 #include "../../tirx/transform/ir_utils.h"
 
 namespace tvm {
diff --git a/src/s_tir/transform/lower_async_dma.cc 
b/src/s_tir/transform/lower_async_dma.cc
index 12fdd9e13a..2e176067bc 100644
--- a/src/s_tir/transform/lower_async_dma.cc
+++ b/src/s_tir/transform/lower_async_dma.cc
@@ -36,7 +36,7 @@
 #include <optional>
 #include <set>
 
-#include "../../tirx/ir_mutator_with_analyzer.h"
+#include "../../tirx/ir/ir_mutator_with_analyzer.h"
 #include "../../tirx/transform/ir_utils.h"
 
 namespace tvm {
diff --git a/src/s_tir/transform/renormalize_split_pattern.cc 
b/src/s_tir/transform/renormalize_split_pattern.cc
index a81cc0c2d8..eadb6e4540 100644
--- a/src/s_tir/transform/renormalize_split_pattern.cc
+++ b/src/s_tir/transform/renormalize_split_pattern.cc
@@ -30,7 +30,7 @@
 #include <tvm/tirx/stmt_functor.h>
 
 #include "../../arith/pattern_match.h"
-#include "../../tirx/ir_mutator_with_analyzer.h"
+#include "../../tirx/ir/ir_mutator_with_analyzer.h"
 
 namespace tvm {
 namespace s_tir {
diff --git a/src/s_tir/transform/using_assume_to_reduce_branches.cc 
b/src/s_tir/transform/using_assume_to_reduce_branches.cc
index 4592203dd1..8d8e9e5d54 100644
--- a/src/s_tir/transform/using_assume_to_reduce_branches.cc
+++ b/src/s_tir/transform/using_assume_to_reduce_branches.cc
@@ -49,7 +49,7 @@
 #include <unordered_map>
 
 #include "../../arith/constraint_extract.h"
-#include "../../tirx/ir_mutator_with_analyzer.h"
+#include "../../tirx/ir/ir_mutator_with_analyzer.h"
 #include "tvm/ir/expr.h"
 namespace tvm {
 namespace s_tir {
diff --git a/src/target/llvm/codegen_aarch64.cc 
b/src/target/llvm/codegen_aarch64.cc
index 1ac7450ea2..6523b6be91 100644
--- a/src/target/llvm/codegen_aarch64.cc
+++ b/src/target/llvm/codegen_aarch64.cc
@@ -40,7 +40,7 @@ class CodeGenAArch64 final : public CodeGenCPU {
   CodeGenAArch64() = default;
   virtual ~CodeGenAArch64() = default;
 
-  void VisitStmt_(const AttrStmtNode* op);
+  void Dispatch_(const AttrStmtNode* op);
   void AddFunction(const GlobalVar& gvar, const PrimFunc& f);
   void SetTargetAttributes(llvm::Function* func);
 
@@ -84,16 +84,16 @@ void CodeGenAArch64::SetTargetAttributes(llvm::Function* 
func) {
  * \brief Visit and handle AArch64 specific pragmas. To be AArch64 specific,
  * the expectation is that they are prepended with "pragma_aarch64".
  */
-void CodeGenAArch64::VisitStmt_(const AttrStmtNode* op) {
+void CodeGenAArch64::Dispatch_(const AttrStmtNode* op) {
   std::string attr_key = op->attr_key;
 
   if (!tirx::attr::IsPragmaKey(attr_key)) {
-    CodeGenCPU::VisitStmt_(op);
+    CodeGenCPU::Dispatch_(op);
     return;
   }
   bool is_aarch64_specific_pragma = attr_key.substr(7, 7) == "aarch64";
   if (!is_aarch64_specific_pragma) {
-    CodeGenCPU::VisitStmt_(op);
+    CodeGenCPU::Dispatch_(op);
     return;
   }
 
@@ -117,7 +117,7 @@ void CodeGenAArch64::VisitStmt_(const AttrStmtNode* op) {
   } else {
     LOG(WARNING) << "Unknown pragma " << op->attr_key;
   }
-  this->VisitStmt(op->body);
+  this->Dispatch(op->body);
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/target/llvm/codegen_cpu.cc b/src/target/llvm/codegen_cpu.cc
index 840fdf12e3..ed3a4c9c29 100644
--- a/src/target/llvm/codegen_cpu.cc
+++ b/src/target/llvm/codegen_cpu.cc
@@ -604,7 +604,7 @@ void CodeGenCPU::CreateComputeScope(const AttrStmtNode* op) 
{
       CreateDebugFunction(MakeStringRef(value->value), debug_param_types, 
PrimType::Int(32));
   auto* compute_entry = llvm::BasicBlock::Create(*ctx, "entry", function_);
   builder_->SetInsertPoint(compute_entry);
-  this->VisitStmt(op->body);
+  this->Dispatch(op->body);
   builder_->CreateRet(ConstInt32(0));
   builder_->SetInsertPoint(compute_call_end);
 
@@ -690,7 +690,7 @@ void CodeGenCPU::CreateParallelLaunch(const Stmt& body, int 
num_task, std::strin
   std::swap(parallel_env_, par_env);
   std::swap(analyzer_, new_analyzer);
   std::swap(var_map_, new_vmap);
-  this->VisitStmt(body);
+  this->Dispatch(body);
   builder_->CreateRet(ConstInt32(0));
   // swap the var map back, now we are back on track.
   std::swap(var_map_, new_vmap);
@@ -1113,7 +1113,7 @@ llvm::Value* CodeGenCPU::CreateIntrinsic(const CallNode* 
op) {
   }
 }
 
-void CodeGenCPU::VisitStmt_(const AssertStmtNode* op) {
+void CodeGenCPU::Dispatch_(const AssertStmtNode* op) {
   EmitDebugLocation(op);
   llvm::Value* cond = MakeValue(op->condition);
   llvm::LLVMContext* ctx = llvm_target_->GetContext();
@@ -1146,10 +1146,10 @@ void CodeGenCPU::VisitStmt_(const AssertStmtNode* op) {
   builder_->CreateRet(ConstInt32(-1));
   // otherwise set it to be new end.
   builder_->SetInsertPoint(end_block);
-  CodeGenLLVM::VisitStmt_(op);
+  CodeGenLLVM::Dispatch_(op);
 }
 
-void CodeGenCPU::VisitStmt_(const AttrStmtNode* op) {
+void CodeGenCPU::Dispatch_(const AttrStmtNode* op) {
   EmitDebugLocation(op);
   if (op->attr_key == tirx::attr::compute_scope) {
     this->CreateComputeScope(op);
@@ -1158,7 +1158,7 @@ void CodeGenCPU::VisitStmt_(const AttrStmtNode* op) {
       TVM_FFI_ICHECK(parallel_env_.penv != nullptr)
           << "Pragma parallel_stride_pattern only valid in parallel launch";
       parallel_env_.stride_pattern = true;
-      this->VisitStmt(op->body);
+      this->Dispatch(op->body);
     } else if (op->attr_key == "pragma_parallel_launch_point") {
       CreateParallelLaunch(op->body, 0, "pragma_parallel");
     } else if (op->attr_key == "pragma_parallel_barrier_when_finish") {
@@ -1167,7 +1167,7 @@ void CodeGenCPU::VisitStmt_(const AttrStmtNode* op) {
       TVM_FFI_ICHECK(!parallel_env_.in_parallel_loop)
           << "Cannot not place within parallel loop as the workload may 
differ, "
           << " place it between parallel and parallel_launch_point";
-      this->VisitStmt(op->body);
+      this->Dispatch(op->body);
       auto bar_callee =
           llvm::FunctionCallee(ftype_tvm_parallel_barrier_, 
RuntimeTVMParallelBarrier());
       builder_->CreateCall(bar_callee, {MakeValue(parallel_env_.task_id), 
parallel_env_.penv});
@@ -1175,20 +1175,20 @@ void CodeGenCPU::VisitStmt_(const AttrStmtNode* op) {
       const prim::StringImmNode* value = op->value.as<prim::StringImmNode>();
       TVM_FFI_ICHECK(value != nullptr);
       this->HandleImport(value->value);
-      this->VisitStmt(op->body);
+      this->Dispatch(op->body);
     } else {
       LOG(WARNING) << "Unknown pragma " << op->attr_key;
-      this->VisitStmt(op->body);
+      this->Dispatch(op->body);
     }
   } else {
-    CodeGenLLVM::VisitStmt_(op);
+    CodeGenLLVM::Dispatch_(op);
   }
 }
 
-void CodeGenCPU::VisitStmt_(const ForNode* op) {
+void CodeGenCPU::Dispatch_(const ForNode* op) {
   EmitDebugLocation(op);
   if (op->kind == ForKind::kSerial || op->kind == ForKind::kUnrolled) {
-    CodeGenLLVM::VisitStmt_(op);
+    CodeGenLLVM::Dispatch_(op);
   } else if (op->kind == ForKind::kParallel) {
     TVM_FFI_ICHECK(is_zero(op->min))
         << "Parallel launch require canonical loop with zero start index";
diff --git a/src/target/llvm/codegen_cpu.h b/src/target/llvm/codegen_cpu.h
index 88799b7779..4d253ba38b 100644
--- a/src/target/llvm/codegen_cpu.h
+++ b/src/target/llvm/codegen_cpu.h
@@ -61,6 +61,7 @@ class LLVMTarget;
 // CPU host code generation
 class CodeGenCPU : public CodeGenLLVM {
  public:
+  using CodeGenLLVM::Dispatch_;
   CodeGenCPU();
   virtual ~CodeGenCPU();
 
@@ -70,9 +71,9 @@ class CodeGenCPU : public CodeGenLLVM {
   void AddFunction(const GlobalVar& gvar, const PrimFunc& f) override;
   void AddMainFunction(const std::string& entry_func_name) override;
   std::unique_ptr<llvm::Module> Finish() override;
-  void VisitStmt_(const AssertStmtNode* op) override;
-  void VisitStmt_(const AttrStmtNode* op) override;
-  void VisitStmt_(const ForNode* op) override;
+  void Dispatch_(const AssertStmtNode* op) override;
+  void Dispatch_(const AttrStmtNode* op) override;
+  void Dispatch_(const ForNode* op) override;
   llvm::Value* CreateIntrinsic(const CallNode* op) override;
   llvm::Value* CreateCallExtern(Type ret_type, ffi::String global_symbol,
                                 const ffi::Array<Expr>& args, bool 
skip_first_arg) override;
diff --git a/src/target/llvm/codegen_llvm.cc b/src/target/llvm/codegen_llvm.cc
index 92892ec7bf..2301ff43e9 100644
--- a/src/target/llvm/codegen_llvm.cc
+++ b/src/target/llvm/codegen_llvm.cc
@@ -340,7 +340,7 @@ void CodeGenLLVM::AddFunctionInternal(const GlobalVar& 
gvar, const PrimFunc& f)
   llvm::LLVMContext* ctx = llvm_target_->GetContext();
   llvm::BasicBlock* entry = llvm::BasicBlock::Create(*ctx, "entry", function_);
   builder_->SetInsertPoint(entry);
-  this->VisitStmt(f->body);
+  this->Dispatch(f->body);
 
   // Add alignment attribute if needed.
   for (size_t i = 0; i < f->params.size(); ++i) {
@@ -901,7 +901,7 @@ void CodeGenLLVM::CreateSerialFor(llvm::Value* begin, 
llvm::Value* end, llvm::Va
   EmitDebugLocation(body->span);
 
   PushLoopFrame(for_next, for_end);
-  this->VisitStmt(body);
+  this->Dispatch(body);
   PopLoopFrame();
   var_map_.erase(loop_var.get());
 
@@ -2071,7 +2071,7 @@ llvm::Value* CodeGenLLVM::Dispatch_(const 
prim::BroadcastNode* op) {
   return builder_->CreateShuffleVector(value, undef, mask);
 }
 
-void CodeGenLLVM::VisitStmt_(const BufferStoreNode* op) {
+void CodeGenLLVM::Dispatch_(const BufferStoreNode* op) {
   EmitDebugLocation(op);
   PrimType value_dtype = PrimType(op->value.ty()->dtype);
   Var buffer_var = op->buffer.var();
@@ -2111,7 +2111,7 @@ void CodeGenLLVM::VisitStmt_(const BufferStoreNode* op) {
   BufferAccessHelper(op->buffer, op->indices, std::nullopt, value_dtype, 
make_store);
 }
 
-void CodeGenLLVM::VisitStmt_(const ForNode* op) {
+void CodeGenLLVM::Dispatch_(const ForNode* op) {
   EmitDebugLocation(op);
   analyzer_->Bind(op->loop_var, Range::FromMinExtent(op->min, op->extent));
   if (op->kind == ForKind::kUnrolled) {
@@ -2127,7 +2127,7 @@ void CodeGenLLVM::VisitStmt_(const ForNode* op) {
   CreateSerialFor(begin_value, end_value, MakeValue(step), op->loop_var, 
op->body);
 }
 
-void CodeGenLLVM::VisitStmt_(const WhileNode* op) {
+void CodeGenLLVM::Dispatch_(const WhileNode* op) {
   EmitDebugLocation(op);
   llvm::LLVMContext* ctx = llvm_target_->GetContext();
   auto* while_cond = llvm::BasicBlock::Create(*ctx, "while_cond", function_);
@@ -2138,13 +2138,13 @@ void CodeGenLLVM::VisitStmt_(const WhileNode* op) {
   builder_->CreateCondBr(MakeValue(op->condition), while_body, while_merge);
   builder_->SetInsertPoint(while_body);
   PushLoopFrame(while_cond, while_merge);
-  this->VisitStmt(op->body);
+  this->Dispatch(op->body);
   PopLoopFrame();
   builder_->CreateBr(while_cond);
   builder_->SetInsertPoint(while_merge);
 }
 
-void CodeGenLLVM::VisitStmt_(const ReturnNode* op) {
+void CodeGenLLVM::Dispatch_(const ReturnNode* op) {
   EmitDebugLocation(op);
   auto const* val = op->value.as<IntImmNode>();
   TVM_FFI_ICHECK(val) << "Return should be transformed to return zero "
@@ -2159,7 +2159,7 @@ void CodeGenLLVM::VisitStmt_(const ReturnNode* op) {
   builder_->SetInsertPoint(ret_dummy);
 }
 
-void CodeGenLLVM::VisitStmt_(const IfThenElseNode* op) {
+void CodeGenLLVM::Dispatch_(const IfThenElseNode* op) {
   EmitDebugLocation(op);
   llvm::Value* cond = MakeValue(op->condition);
   llvm::LLVMContext* ctx = llvm_target_->GetContext();
@@ -2169,21 +2169,21 @@ void CodeGenLLVM::VisitStmt_(const IfThenElseNode* op) {
     auto* else_block = llvm::BasicBlock::Create(*ctx, "if_else", function_);
     builder_->CreateCondBr(cond, then_block, else_block);
     builder_->SetInsertPoint(then_block);
-    this->VisitStmt(op->then_case);
+    this->Dispatch(op->then_case);
     builder_->CreateBr(end_block);
     builder_->SetInsertPoint(else_block);
-    this->VisitStmt(op->else_case.value());
+    this->Dispatch(op->else_case.value());
     builder_->CreateBr(end_block);
   } else {
     builder_->CreateCondBr(cond, then_block, end_block, 
md_very_likely_branch_);
     builder_->SetInsertPoint(then_block);
-    this->VisitStmt(op->then_case);
+    this->Dispatch(op->then_case);
     builder_->CreateBr(end_block);
   }
   builder_->SetInsertPoint(end_block);
 }
 
-void CodeGenLLVM::VisitStmt_(const AllocBufferNode* op) {
+void CodeGenLLVM::Dispatch_(const AllocBufferNode* op) {
   EmitDebugLocation(op);
   TVM_FFI_ICHECK_EQ(op->buffer->shape.size(), 1)
       << "LLVM codegen only supports flat 1-d buffer allocation, but 
allocation of "
@@ -2230,7 +2230,7 @@ void CodeGenLLVM::VisitStmt_(const AllocBufferNode* op) {
   }
 }
 
-void CodeGenLLVM::VisitStmt_(const AttrStmtNode* op) {
+void CodeGenLLVM::Dispatch_(const AttrStmtNode* op) {
   EmitDebugLocation(op);
   if (op->attr_key == tirx::attr::thread_extent) {
     IterVar iv = op->node.as_or_throw<IterVar>();
@@ -2249,16 +2249,16 @@ void CodeGenLLVM::VisitStmt_(const AttrStmtNode* op) {
                                           alloc_storage_info_[v].alignment);
     }
   }
-  this->VisitStmt(op->body);
+  this->Dispatch(op->body);
 }
 
-void CodeGenLLVM::VisitStmt_(const AssertStmtNode* op) {
+void CodeGenLLVM::Dispatch_(const AssertStmtNode* op) {
   EmitDebugLocation(op);
   // AssertStmt is a leaf — no body to visit.
   // Constraint scoping is handled by ScopeStack in analysis passes.
 }
 
-void CodeGenLLVM::VisitStmt_(const BindNode* op) {
+void CodeGenLLVM::Dispatch_(const BindNode* op) {
   EmitDebugLocation(op);
   const VarNode* v = op->var.get();
   TVM_FFI_ICHECK(!var_map_.count(v));
@@ -2297,14 +2297,14 @@ void CodeGenLLVM::VisitStmt_(const BindNode* op) {
   AddDebugInformation(value, op->var);
 }
 
-void CodeGenLLVM::VisitStmt_(const SeqStmtNode* op) {
+void CodeGenLLVM::Dispatch_(const SeqStmtNode* op) {
   EmitDebugLocation(op);
   for (Stmt stmt : op->seq) {
-    this->VisitStmt(stmt);
+    this->Dispatch(stmt);
   }
 }
 
-void CodeGenLLVM::VisitStmt_(const DeclBufferNode* op) {
+void CodeGenLLVM::Dispatch_(const DeclBufferNode* op) {
   EmitDebugLocation(op);
   const VarNode* buffer = op->buffer.get();
   TVM_FFI_ICHECK(!var_map_.count(buffer));
@@ -2338,7 +2338,7 @@ void CodeGenLLVM::VisitStmt_(const DeclBufferNode* op) {
   }
 }
 
-void CodeGenLLVM::VisitStmt_(const EvaluateNode* op) {
+void CodeGenLLVM::Dispatch_(const EvaluateNode* op) {
   EmitDebugLocation(op);
   MakeValue(op->value);
 }
diff --git a/src/target/llvm/codegen_llvm.h b/src/target/llvm/codegen_llvm.h
index 6839811799..c3e4989a5a 100644
--- a/src/target/llvm/codegen_llvm.h
+++ b/src/target/llvm/codegen_llvm.h
@@ -94,6 +94,9 @@ using namespace tirx;
 class CodeGenLLVM : public tirx::ExprFunctor<llvm::Value*(const Expr&)>,
                     public StmtFunctor<void(const Stmt&)> {
  public:
+  using tirx::ExprFunctor<llvm::Value*(const Expr&)>::Dispatch;
+  using StmtFunctor::Dispatch;
+
   CodeGenLLVM();           // Do not make it default here.
   virtual ~CodeGenLLVM();  // Do not make it default here.
 
@@ -227,18 +230,18 @@ class CodeGenLLVM : public 
tirx::ExprFunctor<llvm::Value*(const Expr&)>,
   llvm::Value* Dispatch_(const prim::ShuffleNode* op) override;
   llvm::Value* Dispatch_(const prim::BroadcastNode* op) override;
   // stmt
-  void VisitStmt_(const BufferStoreNode* op) override;
-  void VisitStmt_(const ForNode* op) override;
-  void VisitStmt_(const WhileNode* op) override;
-  void VisitStmt_(const ReturnNode* op) override;
-  void VisitStmt_(const IfThenElseNode* op) override;
-  void VisitStmt_(const AllocBufferNode* op) override;
-  void VisitStmt_(const AttrStmtNode* op) override;
-  void VisitStmt_(const AssertStmtNode* op) override;
-  void VisitStmt_(const BindNode* op) override;
-  void VisitStmt_(const SeqStmtNode* op) override;
-  void VisitStmt_(const EvaluateNode* op) override;
-  void VisitStmt_(const DeclBufferNode* op) override;
+  void Dispatch_(const BufferStoreNode* op) override;
+  void Dispatch_(const ForNode* op) override;
+  void Dispatch_(const WhileNode* op) override;
+  void Dispatch_(const ReturnNode* op) override;
+  void Dispatch_(const IfThenElseNode* op) override;
+  void Dispatch_(const AllocBufferNode* op) override;
+  void Dispatch_(const AttrStmtNode* op) override;
+  void Dispatch_(const AssertStmtNode* op) override;
+  void Dispatch_(const BindNode* op) override;
+  void Dispatch_(const SeqStmtNode* op) override;
+  void Dispatch_(const EvaluateNode* op) override;
+  void Dispatch_(const DeclBufferNode* op) override;
 
   // Get constant string
   llvm::Constant* GetConstString(const std::string& str);
diff --git a/src/target/source/codegen_c.cc b/src/target/source/codegen_c.cc
index 4cca7b6255..8ac80b4f74 100644
--- a/src/target/source/codegen_c.cc
+++ b/src/target/source/codegen_c.cc
@@ -926,7 +926,7 @@ void CodeGenC::PrintVecBinaryOp(const std::string& op, 
const PrimType& t, PrimEx
   }
 }
 
-void CodeGenC::VisitStmt_(const DeclBufferNode* op) {
+void CodeGenC::Dispatch_(const DeclBufferNode* op) {
   const VarNode* source = op->data.as<VarNode>();
   if (const auto* call = op->data.as<CallNode>();
       call && call->op.same_as(tirx::builtin::buffer_data()) && 
call->args.size() == 1) {
@@ -1039,7 +1039,7 @@ void CodeGenC::Dispatch_(const TensorLoadNode* op, 
std::ostream& os) {  // NOLIN
   }
 }
 
-void CodeGenC::VisitStmt_(const BufferStoreNode* op) {
+void CodeGenC::Dispatch_(const BufferStoreNode* op) {
   TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "Store to non-flat memory not 
supported.";
 
   PrimType value_ty = op->value.ty();
@@ -1228,7 +1228,7 @@ void CodeGenC::Dispatch_(const prim::SelectNode* op, 
std::ostream& os) {  // NOL
   os << ")";
 }
 
-void CodeGenC::VisitStmt_(const BindNode* op) {
+void CodeGenC::Dispatch_(const BindNode* op) {
   RegisterHandleTypeFromPointer(op->var, &op->value);
   std::string value = PrintExpr(op->value);
   if (print_ssa_form_) {
@@ -1249,7 +1249,7 @@ void CodeGenC::VisitStmt_(const BindNode* op) {
   }
 }
 
-void CodeGenC::VisitStmt_(const AllocBufferNode* op) {
+void CodeGenC::Dispatch_(const AllocBufferNode* op) {
   TVM_FFI_ICHECK(op->buffer.defined());
   std::string vid = AllocVarID(op->buffer.get(), op->buffer.name() + "_ptr");
 
@@ -1276,7 +1276,7 @@ void CodeGenC::VisitStmt_(const AllocBufferNode* op) {
   }
 }
 
-void CodeGenC::VisitStmt_(const AttrStmtNode* op) {
+void CodeGenC::Dispatch_(const AttrStmtNode* op) {
   if (op->attr_key == tirx::attr::thread_extent) {
     IterVar iv = op->node.as_or_throw<IterVar>();
     if (iv->thread_tag.length() != 0) {
@@ -1340,7 +1340,7 @@ void CodeGenC::PrintEscapedCString(const std::string& 
str, std::ostream& os) {
   os << "\"";
 }
 
-void CodeGenC::VisitStmt_(const AssertStmtNode* op) {
+void CodeGenC::Dispatch_(const AssertStmtNode* op) {
   std::string cond = PrintExpr(op->condition);
   PrintIndent();
   int num_parts = static_cast<int>(op->message_parts.size());
@@ -1368,7 +1368,7 @@ void CodeGenC::VisitStmt_(const AssertStmtNode* op) {
   }
 }
 
-void CodeGenC::VisitStmt_(const ForNode* op) {
+void CodeGenC::Dispatch_(const ForNode* op) {
   std::string begin_str = PrintExpr(op->min);
   PrimExpr end = is_zero(op->min) ? op->extent : 
arith::Analyzer()->Simplify(op->min + op->extent);
   std::string end_str = PrintExpr(end);
@@ -1391,7 +1391,7 @@ void CodeGenC::VisitStmt_(const ForNode* op) {
   stream << "}\n";
 }
 
-void CodeGenC::VisitStmt_(const WhileNode* op) {
+void CodeGenC::Dispatch_(const WhileNode* op) {
   PrintIndent();
   stream << "#pragma unroll 1\n";
   PrintIndent();
@@ -1406,24 +1406,24 @@ void CodeGenC::VisitStmt_(const WhileNode* op) {
   stream << "}\n";
 }
 
-void CodeGenC::VisitStmt_(const ReturnNode* op) {
+void CodeGenC::Dispatch_(const ReturnNode* op) {
   PrintIndent();
   stream << "return ";
   PrintExpr(op->value, stream);
   stream << ";\n";
 }
 
-void CodeGenC::VisitStmt_(const BreakNode* op) {
+void CodeGenC::Dispatch_(const BreakNode* op) {
   PrintIndent();
   stream << "break;\n";
 }
 
-void CodeGenC::VisitStmt_(const ContinueNode* op) {
+void CodeGenC::Dispatch_(const ContinueNode* op) {
   PrintIndent();
   stream << "continue;\n";
 }
 
-void CodeGenC::VisitStmt_(const IfThenElseNode* op) {
+void CodeGenC::Dispatch_(const IfThenElseNode* op) {
   std::string cond = PrintExpr(op->condition);
   PrintIndent();
   if (cond[0] == '(' && cond[cond.length() - 1] == ')') {
@@ -1446,13 +1446,13 @@ void CodeGenC::VisitStmt_(const IfThenElseNode* op) {
   stream << "}\n";
 }
 
-void CodeGenC::VisitStmt_(const SeqStmtNode* op) {
+void CodeGenC::Dispatch_(const SeqStmtNode* op) {
   for (Stmt stmt : op->seq) {
     PrintStmt(stmt);
   }
 }
 
-void CodeGenC::VisitStmt_(const EvaluateNode* op) {
+void CodeGenC::Dispatch_(const EvaluateNode* op) {
   if (auto value = op->value.as<PrimExpr>(); value && 
is_const_int(value.value())) return;
   const CallNode* call = op->value.as<CallNode>();
   if (call) {
diff --git a/src/target/source/codegen_c.h b/src/target/source/codegen_c.h
index b5ac52aa67..b2e0a99b56 100644
--- a/src/target/source/codegen_c.h
+++ b/src/target/source/codegen_c.h
@@ -61,6 +61,9 @@ class CodeGenC : public tirx::ExprFunctor<void(const Expr&, 
std::ostream&)>,
                  public StmtFunctor<void(const Stmt&)>,
                  public CodeGenSourceBase {
  public:
+  using tirx::ExprFunctor<void(const Expr&, std::ostream&)>::Dispatch;
+  using StmtFunctor::Dispatch;
+
   /*!
    * \brief Initialize the code generator.
    * \param output_ssa Whether output SSA.
@@ -102,7 +105,7 @@ class CodeGenC : public tirx::ExprFunctor<void(const Expr&, 
std::ostream&)>,
    * \brief Print the Stmt n to CodeGenC->stream
    * \param n The statement to be printed.
    */
-  void PrintStmt(const Stmt& n) { VisitStmt(n); }
+  void PrintStmt(const Stmt& n) { Dispatch(n); }
   /*!
    * \brief Print the expression n(or its ssa id if in ssa mode) into os
    * \param n The expression to be printed.
@@ -194,20 +197,20 @@ class CodeGenC : public tirx::ExprFunctor<void(const 
Expr&, std::ostream&)>,
   void Dispatch_(const FloatImmNode* op, std::ostream& os) override;         
// NOLINT(*)
   void Dispatch_(const prim::StringImmNode* op, std::ostream& os) override;  
// NOLINT(*)
   // statment
-  void VisitStmt_(const BindNode* op) override;
-  void VisitStmt_(const BufferStoreNode* op) override;
-  void VisitStmt_(const ForNode* op) override;
-  void VisitStmt_(const WhileNode* op) override;
-  void VisitStmt_(const ReturnNode* op) override;
-  void VisitStmt_(const BreakNode* op) override;
-  void VisitStmt_(const ContinueNode* op) override;
-  void VisitStmt_(const IfThenElseNode* op) override;
-  void VisitStmt_(const AllocBufferNode* op) override;
-  void VisitStmt_(const AttrStmtNode* op) override;
-  void VisitStmt_(const AssertStmtNode* op) override;
-  void VisitStmt_(const EvaluateNode* op) override;
-  void VisitStmt_(const SeqStmtNode* op) override;
-  void VisitStmt_(const DeclBufferNode* op) override;
+  void Dispatch_(const BindNode* op) override;
+  void Dispatch_(const BufferStoreNode* op) override;
+  void Dispatch_(const ForNode* op) override;
+  void Dispatch_(const WhileNode* op) override;
+  void Dispatch_(const ReturnNode* op) override;
+  void Dispatch_(const BreakNode* op) override;
+  void Dispatch_(const ContinueNode* op) override;
+  void Dispatch_(const IfThenElseNode* op) override;
+  void Dispatch_(const AllocBufferNode* op) override;
+  void Dispatch_(const AttrStmtNode* op) override;
+  void Dispatch_(const AssertStmtNode* op) override;
+  void Dispatch_(const EvaluateNode* op) override;
+  void Dispatch_(const SeqStmtNode* op) override;
+  void Dispatch_(const DeclBufferNode* op) override;
 
   /*!
    * \brief Print expr representing the thread tag
diff --git a/src/target/source/codegen_c_host.cc 
b/src/target/source/codegen_c_host.cc
index 599f45d890..8e6b4d1fe5 100644
--- a/src/target/source/codegen_c_host.cc
+++ b/src/target/source/codegen_c_host.cc
@@ -320,7 +320,7 @@ void CodeGenCHost::Dispatch_(const CallNode* op, 
std::ostream& os) {  // NOLINT(
   }
 }
 
-void CodeGenCHost::VisitStmt_(const AssertStmtNode* op) {  // NOLINT(*)
+void CodeGenCHost::Dispatch_(const AssertStmtNode* op) {  // NOLINT(*)
   if (emit_asserts_) {
     std::string cond = PrintExpr(op->condition);
     PrintIndent();
diff --git a/src/target/source/codegen_c_host.h 
b/src/target/source/codegen_c_host.h
index 1176a86827..34ffa3c809 100644
--- a/src/target/source/codegen_c_host.h
+++ b/src/target/source/codegen_c_host.h
@@ -69,7 +69,7 @@ class CodeGenCHost : public CodeGenC {
   void Dispatch_(const prim::MinNode* op, std::ostream& os) final;  // 
NOLINT(*)
   void Dispatch_(const prim::MaxNode* op, std::ostream& os) final;  // 
NOLINT(*)
 
-  void VisitStmt_(const AssertStmtNode* op) final;  // NOLINT(*)
+  void Dispatch_(const AssertStmtNode* op) final;  // NOLINT(*)
 
   void GenerateForwardFunctionDeclarations(ffi::String global_symbol,
                                            const ffi::Array<Type>& arg_types,
diff --git a/src/tirx/analysis/verify_tirx_well_formed.cc 
b/src/tirx/analysis/verify_tirx_well_formed.cc
index 889aba164f..1fab6449cd 100644
--- a/src/tirx/analysis/verify_tirx_well_formed.cc
+++ b/src/tirx/analysis/verify_tirx_well_formed.cc
@@ -51,16 +51,15 @@ class ExecScopeVerifier : public 
Verifier<ExecScopeVerifier> {
  private:
   using Verifier::Visit;
 
-  void VisitStmt_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override {
+  void Dispatch_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override {
     Verify(false) << "TIRxError: SBlock is not allowed in tirx=True mode at " 
<< path;
   }
 
-  void VisitStmt_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override {
+  void Dispatch_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override {
     Verify(false) << "TIRxError: SBlockRealize is not allowed in tirx=True 
mode at " << path;
   }
 
-  void VisitStmt_(const tirx::TilePrimitiveCallNode* op,
-                  ffi::reflection::AccessPath path) override {
+  void Dispatch_(const tirx::TilePrimitiveCallNode* op, 
ffi::reflection::AccessPath path) override {
     static const auto& category_map = 
Op::GetAttrMap<tirx::TIRxOpCategory>("TIRxOpCategory");
     Verify(category_map.get(op->op, ffi::String("")) == "tile_primitive")
         << "TIRxError: TilePrimitiveCall at " << path << " has non-tile op " 
<< op->op;
@@ -74,12 +73,12 @@ class ScopeIdVerifier : public Verifier<ScopeIdVerifier> {
  private:
   using Verifier::Visit;
 
-  void VisitStmt_(const AttrStmtNode* op, ffi::reflection::AccessPath path) 
override {
+  void Dispatch_(const AttrStmtNode* op, ffi::reflection::AccessPath path) 
override {
     if (op->attr_key == tvm::tirx::attr::kDeviceEntry) {
       // Device-region marker: defs gathered from the body are verified when
       // the AttrStmt exits, with launch-param sanity enforced as ``is_root``.
       size_t baseline = scope_id_def_.size();
-      Verifier::VisitStmt_(op, path);
+      Verifier::Dispatch_(op, path);
       size_t total = scope_id_def_.size();
       if (total > baseline) {
         RunScopeIdVerify(path, baseline, /*is_root=*/true);
@@ -89,7 +88,7 @@ class ScopeIdVerifier : public Verifier<ScopeIdVerifier> {
       }
       return;
     }
-    Verifier::VisitStmt_(op, path);
+    Verifier::Dispatch_(op, path);
   }
 
   void RunScopeIdVerify(ffi::reflection::AccessPath path, size_t baseline, 
bool is_root) {
@@ -118,9 +117,9 @@ class ScopeIdVerifier : public Verifier<ScopeIdVerifier> {
     }
   }
 
-  void VisitStmt_(const ScopeIdDefStmtNode* op, ffi::reflection::AccessPath 
path) override {
+  void Dispatch_(const ScopeIdDefStmtNode* op, ffi::reflection::AccessPath 
path) override {
     scope_id_def_.push_back(op->def);
-    Verifier::VisitStmt_(op, path);
+    Verifier::Dispatch_(op, path);
   }
 
   Array<ScopeIdDef> scope_id_def_;
@@ -134,11 +133,11 @@ class LayoutVerifier : public Verifier<LayoutVerifier> {
  private:
   using Verifier::Visit;
 
-  void VisitStmt_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override {
+  void Dispatch_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override {
     Verify(false) << "TIRxError: SBlock is not allowed in tirx=True mode at " 
<< path;
   }
 
-  void VisitStmt_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override {
+  void Dispatch_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override {
     Verify(false) << "TIRxError: SBlockRealize is not allowed in tirx=True 
mode at " << path;
   }
 };
@@ -150,11 +149,11 @@ class AsyncStructsVerifier : public 
Verifier<AsyncStructsVerifier> {
  private:
   using Verifier::Visit;
 
-  void VisitStmt_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override {
+  void Dispatch_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override {
     Verify(false) << "TIRxError: SBlock is not allowed in tirx=True mode at " 
<< path;
   }
 
-  void VisitStmt_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override {
+  void Dispatch_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override {
     Verify(false) << "TIRxError: SBlockRealize is not allowed in tirx=True 
mode at " << path;
   }
 };
@@ -166,11 +165,11 @@ class DeviceFuncVerifier : public 
Verifier<DeviceFuncVerifier> {
  private:
   using Verifier::Visit;
 
-  void VisitStmt_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override {
+  void Dispatch_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override {
     Verify(false) << "TIRxError: SBlock is not allowed in tirx=True mode at " 
<< path;
   }
 
-  void VisitStmt_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override {
+  void Dispatch_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override {
     Verify(false) << "TIRxError: SBlockRealize is not allowed in tirx=True 
mode at " << path;
   }
 };
diff --git a/src/tirx/ir_mutator_with_analyzer.cc 
b/src/tirx/ir/ir_mutator_with_analyzer.cc
similarity index 99%
rename from src/tirx/ir_mutator_with_analyzer.cc
rename to src/tirx/ir/ir_mutator_with_analyzer.cc
index 279ab2c29c..e26fdf5282 100644
--- a/src/tirx/ir_mutator_with_analyzer.cc
+++ b/src/tirx/ir/ir_mutator_with_analyzer.cc
@@ -18,7 +18,7 @@
  */
 
 /*!
- * \file tirx/ir_mutator_with_analyzer.cc
+ * \file tirx/ir/ir_mutator_with_analyzer.cc
  */
 #include "ir_mutator_with_analyzer.h"
 
@@ -30,7 +30,7 @@
 #include <tvm/tirx/builtin.h>
 #include <tvm/tirx/op.h>
 
-#include "../arith/constraint_helpers.h"
+#include "../../arith/constraint_helpers.h"
 
 namespace tvm {
 namespace tirx {
diff --git a/src/tirx/ir_mutator_with_analyzer.h 
b/src/tirx/ir/ir_mutator_with_analyzer.h
similarity index 95%
rename from src/tirx/ir_mutator_with_analyzer.h
rename to src/tirx/ir/ir_mutator_with_analyzer.h
index 5463bdb6ec..20a6dd2587 100644
--- a/src/tirx/ir_mutator_with_analyzer.h
+++ b/src/tirx/ir/ir_mutator_with_analyzer.h
@@ -18,11 +18,11 @@
  */
 
 /*!
- * \file tirx/ir_mutator_with_analyzer.h
+ * \file tirx/ir/ir_mutator_with_analyzer.h
  * \brief IR mutator base-class with an analyzer context.
  */
-#ifndef TVM_TIRX_IR_MUTATOR_WITH_ANALYZER_H_
-#define TVM_TIRX_IR_MUTATOR_WITH_ANALYZER_H_
+#ifndef TVM_TIRX_IR_IR_MUTATOR_WITH_ANALYZER_H_
+#define TVM_TIRX_IR_IR_MUTATOR_WITH_ANALYZER_H_
 
 #include <tvm/arith/analyzer.h>
 #include <tvm/ffi/cast.h>
@@ -45,7 +45,7 @@ namespace tirx {
  * It will populates scope-related info such as bounds of loop-variables and 
constraints
  * for the analyzer, so that the child class can do accurate context-dependent 
analysis.
  *
- * \sa src/tirx/ir_mutator_with_analyzer.cc
+ * \sa src/tirx/ir/ir_mutator_with_analyzer.cc
  */
 class IRMutatorWithAnalyzer : public StmtExprMutator {
  public:
@@ -123,4 +123,4 @@ class IRMutatorWithAnalyzer : public StmtExprMutator {
 };
 }  // namespace tirx
 }  // namespace tvm
-#endif  // TVM_TIRX_IR_MUTATOR_WITH_ANALYZER_H_
+#endif  // TVM_TIRX_IR_IR_MUTATOR_WITH_ANALYZER_H_
diff --git a/src/tirx/ir_visitor_with_analyzer.cc 
b/src/tirx/ir/ir_visitor_with_analyzer.cc
similarity index 99%
rename from src/tirx/ir_visitor_with_analyzer.cc
rename to src/tirx/ir/ir_visitor_with_analyzer.cc
index 639768f6c0..9bdc895722 100644
--- a/src/tirx/ir_visitor_with_analyzer.cc
+++ b/src/tirx/ir/ir_visitor_with_analyzer.cc
@@ -18,7 +18,7 @@
  */
 
 /*!
- * \file tirx/ir_visitor_with_analyzer.cc
+ * \file tirx/ir/ir_visitor_with_analyzer.cc
  */
 #include "ir_visitor_with_analyzer.h"
 
diff --git a/src/tirx/ir_visitor_with_analyzer.h 
b/src/tirx/ir/ir_visitor_with_analyzer.h
similarity index 92%
rename from src/tirx/ir_visitor_with_analyzer.h
rename to src/tirx/ir/ir_visitor_with_analyzer.h
index d1e36b86cf..33a895acf6 100644
--- a/src/tirx/ir_visitor_with_analyzer.h
+++ b/src/tirx/ir/ir_visitor_with_analyzer.h
@@ -18,12 +18,12 @@
  */
 
 /*!
- * \file tirx/ir_visitor_with_analyzer.h
+ * \file tirx/ir/ir_visitor_with_analyzer.h
  * \brief IR visitor class with an analyzer context.
  */
 
-#ifndef TVM_TIRX_IR_VISITOR_WITH_ANALYZER_H_
-#define TVM_TIRX_IR_VISITOR_WITH_ANALYZER_H_
+#ifndef TVM_TIRX_IR_IR_VISITOR_WITH_ANALYZER_H_
+#define TVM_TIRX_IR_IR_VISITOR_WITH_ANALYZER_H_
 
 #include <tvm/arith/analyzer.h>
 #include <tvm/ir/prim/expr.h>
@@ -70,4 +70,4 @@ class IRVisitorWithAnalyzer : public StmtExprVisitor {
 
 }  // namespace tirx
 }  // namespace tvm
-#endif  // TVM_TIRX_IR_VISITOR_WITH_ANALYZER_H_
+#endif  // TVM_TIRX_IR_IR_VISITOR_WITH_ANALYZER_H_
diff --git a/src/tirx/ir/tir_visitor_with_path.cc 
b/src/tirx/ir/tir_visitor_with_path.cc
index 53fe95f12c..0f0f684a50 100644
--- a/src/tirx/ir/tir_visitor_with_path.cc
+++ b/src/tirx/ir/tir_visitor_with_path.cc
@@ -179,14 +179,14 @@ void TIRVisitorWithPath::Visit(const Range& range, 
AccessPath path) {
   Visit(range->extent, path->Attr("extent"));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const BindNode* op, AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const BindNode* op, AccessPath path) {
   Visit(op->value, path->Attr("value"));
   // Push the Bind's var definition into the current scope.
   // The def lives until the enclosing scope (body-carrying stmt) exits.
   bind_scope_.Current().push_back(WithDef(op->var, path->Attr("var")));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const AttrStmtNode* op, AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const AttrStmtNode* op, AccessPath path) {
   Visit(op->value, path->Attr("value"));
 
   std::vector<std::variant<DefContext<IterVar>, DefContext<Var>, 
DefContext<BufferVar>>> context;
@@ -207,68 +207,68 @@ void TIRVisitorWithPath::VisitStmt_(const AttrStmtNode* 
op, AccessPath path) {
   }
 }
 
-void TIRVisitorWithPath::VisitStmt_(const ForNode* op, AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const ForNode* op, AccessPath path) {
   Visit(op->min, path->Attr("min"));
   Visit(op->extent, path->Attr("extent"));
   auto context = WithDef(op->loop_var, path->Attr("loop_var"));
   bind_scope_.WithNewScope([&]() { Visit(op->body, path->Attr("body")); });
 }
 
-void TIRVisitorWithPath::VisitStmt_(const WhileNode* op, AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const WhileNode* op, AccessPath path) {
   Visit(op->condition, path->Attr("condition"));
   bind_scope_.WithNewScope([&]() { Visit(op->body, path->Attr("body")); });
 }
 
-void TIRVisitorWithPath::VisitStmt_(const ReturnNode* op, AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const ReturnNode* op, AccessPath path) {
   Visit(op->value, path->Attr("value"));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const BreakNode* op, AccessPath path) {}
+void TIRVisitorWithPath::Dispatch_(const BreakNode* op, AccessPath path) {}
 
-void TIRVisitorWithPath::VisitStmt_(const ContinueNode* op, AccessPath path) {}
+void TIRVisitorWithPath::Dispatch_(const ContinueNode* op, AccessPath path) {}
 
-void TIRVisitorWithPath::VisitStmt_(const AllocBufferNode* op, AccessPath 
path) {
+void TIRVisitorWithPath::Dispatch_(const AllocBufferNode* op, AccessPath path) 
{
   // Push definitions into the current scope so they are visible to subsequent 
siblings.
   auto buf_path = path->Attr("buffer");
   bind_scope_.Current().push_back(WithDef(op->buffer, buf_path));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const DeclBufferNode* op, AccessPath path) 
{
+void TIRVisitorWithPath::Dispatch_(const DeclBufferNode* op, AccessPath path) {
   Visit(op->data, path->Attr("data"));
   // Push buffer definition into the current scope so it is visible to 
subsequent siblings.
   bind_scope_.Current().push_back(WithDef(op->buffer, path->Attr("buffer")));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const BufferStoreNode* op, AccessPath 
path) {
+void TIRVisitorWithPath::Dispatch_(const BufferStoreNode* op, AccessPath path) 
{
   Visit(op->value, path->Attr("value"));
   VisitBufferUse(op->buffer, path->Attr("buffer"));
   Visit(op->indices, path->Attr("indices"));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const IfThenElseNode* op, AccessPath path) 
{
+void TIRVisitorWithPath::Dispatch_(const IfThenElseNode* op, AccessPath path) {
   Visit(op->condition, path->Attr("condition"));
   bind_scope_.WithNewScope([&]() { Visit(op->then_case, 
path->Attr("then_case")); });
   bind_scope_.WithNewScope([&]() { Visit(op->else_case, 
path->Attr("else_case")); });
 }
 
-void TIRVisitorWithPath::VisitStmt_(const AssertStmtNode* op, AccessPath path) 
{
+void TIRVisitorWithPath::Dispatch_(const AssertStmtNode* op, AccessPath path) {
   Visit(op->condition, path->Attr("condition"));
   Visit(op->error_kind, path->Attr("error_kind"));
   Visit(op->message_parts, path->Attr("message_parts"));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const SeqStmtNode* op, AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const SeqStmtNode* op, AccessPath path) {
   auto seq_path = path->Attr("seq");
   for (size_t i = 0; i < op->seq.size(); i++) {
     Visit(op->seq[i], seq_path->ArrayItem(i));
   }
 }
 
-void TIRVisitorWithPath::VisitStmt_(const EvaluateNode* op, AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const EvaluateNode* op, AccessPath path) {
   Visit(op->value, path->Attr("value"));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const SBlockNode* op, AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const SBlockNode* op, AccessPath path) {
   std::vector<std::variant<DefContext<Var>, DefContext<IterVar>, 
DefContext<BufferVar>>> context;
 
   {
@@ -313,13 +313,13 @@ void TIRVisitorWithPath::VisitStmt_(const SBlockNode* op, 
AccessPath path) {
   while (context.size()) context.pop_back();
 }
 
-void TIRVisitorWithPath::VisitStmt_(const SBlockRealizeNode* op, AccessPath 
path) {
+void TIRVisitorWithPath::Dispatch_(const SBlockRealizeNode* op, AccessPath 
path) {
   Visit(op->iter_values, path->Attr("iter_values"));
   Visit(op->predicate, path->Attr("predicate"));
   Visit(op->block, path->Attr("block"));
 }
 
-void TIRVisitorWithPath::VisitStmt_(const tirx::TilePrimitiveCallNode* op, 
AccessPath path) {
+void TIRVisitorWithPath::Dispatch_(const tirx::TilePrimitiveCallNode* op, 
AccessPath path) {
   for (size_t i = 0; i < op->args.size(); i++) {
     if (op->args[i] == nullptr) {
       continue;
@@ -336,7 +336,7 @@ void TIRVisitorWithPath::VisitStmt_(const 
tirx::TilePrimitiveCallNode* op, Acces
   }
 }
 
-void TIRVisitorWithPath::VisitStmt_(const ScopeIdDefStmtNode* op, AccessPath 
path) {
+void TIRVisitorWithPath::Dispatch_(const ScopeIdDefStmtNode* op, AccessPath 
path) {
   // Flat stmt -- no body. Visit extents and preferred_extents (if present),
   // then push the bound Var(s) into the current scope so subsequent siblings
   // see them as defined.
diff --git a/src/tirx/ir/tir_visitor_with_path.h 
b/src/tirx/ir/tir_visitor_with_path.h
index ef54a7c35e..f4547becd6 100644
--- a/src/tirx/ir/tir_visitor_with_path.h
+++ b/src/tirx/ir/tir_visitor_with_path.h
@@ -75,9 +75,9 @@ class TIRVisitorWithPath : protected ExprFunctor<void(const 
Expr&, ffi::reflecti
       TVM_FFI_THROW(TypeError) << "Unsupported non-primitive TIR expression " 
<< obj.GetTypeKey();
     }
   }
-  // Delegate to ExprFunctor::VisitStmt for Stmt, and any subclasses
+  // Delegate to StmtFunctor::Dispatch for Stmt, and any subclasses
   virtual inline void Visit(const Stmt& obj, ffi::reflection::AccessPath path) 
{
-    VisitStmt(obj, path);
+    Dispatch(obj, path);
   }
 
   // Visit a buffer at a use site (BufferLoad, BufferStore, reads/writes).
@@ -132,25 +132,25 @@ class TIRVisitorWithPath : protected 
ExprFunctor<void(const Expr&, ffi::reflecti
     }
   }
 
-  using StmtFunctor::VisitStmt;
-  void VisitStmt_(const BindNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const AttrStmtNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const IfThenElseNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const ForNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const WhileNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const ReturnNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const BreakNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const ContinueNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const AllocBufferNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const DeclBufferNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const BufferStoreNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const AssertStmtNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const SeqStmtNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const EvaluateNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override;
-  void VisitStmt_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override;
-  void VisitStmt_(const tirx::TilePrimitiveCallNode* op, 
ffi::reflection::AccessPath path) override;
-  void VisitStmt_(const ScopeIdDefStmtNode* op, ffi::reflection::AccessPath 
path) override;
+  using StmtFunctor::Dispatch;
+  void Dispatch_(const BindNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const AttrStmtNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const IfThenElseNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const ForNode* op, ffi::reflection::AccessPath path) override;
+  void Dispatch_(const WhileNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const ReturnNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const BreakNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const ContinueNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const AllocBufferNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const DeclBufferNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const BufferStoreNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const AssertStmtNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const SeqStmtNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const EvaluateNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const SBlockNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const SBlockRealizeNode* op, ffi::reflection::AccessPath 
path) override;
+  void Dispatch_(const tirx::TilePrimitiveCallNode* op, 
ffi::reflection::AccessPath path) override;
+  void Dispatch_(const ScopeIdDefStmtNode* op, ffi::reflection::AccessPath 
path) override;
 
   using ExprFunctor::Dispatch;
   void Dispatch_(const VarNode* op, ffi::reflection::AccessPath path) override;
diff --git a/src/tirx/transform/flatten_buffer.cc 
b/src/tirx/transform/flatten_buffer.cc
index 7baad9cc3a..0bf7e3cb43 100644
--- a/src/tirx/transform/flatten_buffer.cc
+++ b/src/tirx/transform/flatten_buffer.cc
@@ -32,7 +32,7 @@
 
 #include <unordered_set>
 
-#include "../ir_mutator_with_analyzer.h"
+#include "../ir/ir_mutator_with_analyzer.h"
 #include "ir_utils.h"
 
 namespace tvm {
diff --git a/src/tirx/transform/lower_intrin.cc 
b/src/tirx/transform/lower_intrin.cc
index 8baf89355e..53af3e51c2 100644
--- a/src/tirx/transform/lower_intrin.cc
+++ b/src/tirx/transform/lower_intrin.cc
@@ -38,7 +38,7 @@
 #include <unordered_set>
 
 #include "../../arith/pattern_match.h"
-#include "../ir_mutator_with_analyzer.h"
+#include "../ir/ir_mutator_with_analyzer.h"
 
 namespace tvm {
 namespace tirx {
diff --git a/src/tirx/transform/lower_tirx_cleanup.cc 
b/src/tirx/transform/lower_tirx_cleanup.cc
index 929a9e079e..9a05731c93 100644
--- a/src/tirx/transform/lower_tirx_cleanup.cc
+++ b/src/tirx/transform/lower_tirx_cleanup.cc
@@ -38,7 +38,7 @@
 #include <utility>
 #include <vector>
 
-#include "../ir_mutator_with_analyzer.h"
+#include "../ir/ir_mutator_with_analyzer.h"
 #include "ir_utils.h"
 
 namespace tvm {
diff --git a/src/tirx/transform/remove_no_op.cc 
b/src/tirx/transform/remove_no_op.cc
index 0a58927e1b..a688acd074 100644
--- a/src/tirx/transform/remove_no_op.cc
+++ b/src/tirx/transform/remove_no_op.cc
@@ -37,7 +37,7 @@
 
 #include "../../arith/const_fold.h"
 #include "../analysis/var_use_def_analysis.h"
-#include "../ir_mutator_with_analyzer.h"
+#include "../ir/ir_mutator_with_analyzer.h"
 #include "ir_utils.h"
 
 namespace tvm {
diff --git a/src/tirx/transform/stmt_simplify.cc 
b/src/tirx/transform/stmt_simplify.cc
index 3d9dea2b68..f6904e9b56 100644
--- a/src/tirx/transform/stmt_simplify.cc
+++ b/src/tirx/transform/stmt_simplify.cc
@@ -36,7 +36,7 @@
 #include <tvm/tirx/op.h>
 #include <tvm/tirx/transform.h>
 
-#include "../ir_mutator_with_analyzer.h"
+#include "../ir/ir_mutator_with_analyzer.h"
 
 namespace tvm {
 namespace arith {
diff --git a/src/tirx/transform/tile_primitive_dispatch.cc 
b/src/tirx/transform/tile_primitive_dispatch.cc
index c0535bd17c..73634b18fc 100644
--- a/src/tirx/transform/tile_primitive_dispatch.cc
+++ b/src/tirx/transform/tile_primitive_dispatch.cc
@@ -246,7 +246,7 @@ class NoOpCallVerifier : public Verifier<NoOpCallVerifier> {
  private:
   using Verifier::Visit;
 
-  void VisitStmt_(const tirx::TilePrimitiveCallNode* obj, 
ffi::reflection::AccessPath path) final {
+  void Dispatch_(const tirx::TilePrimitiveCallNode* obj, 
ffi::reflection::AccessPath path) final {
     Verify(false) << "TIRxError: TilePrimitiveCall at " << path
                   << " is not allowed in TIRx before lowering";
   }
@@ -322,7 +322,7 @@ class TilePrimitiveDispatcher : public StmtExprMutator {
 
     launch_params_.clear();
     // Pre-dispatch: only populate ``launch_params_`` + synthesize
-    // ``warp_id_in_cta``. The dispatch impls (run via ``VisitStmt`` below)
+    // ``warp_id_in_cta``. The dispatch impls (run via ``Dispatch`` below)
     // read ``launch_params_`` through ``sctx``, so this much must happen
     // first. The per-def Bind resolution is deferred to AFTER dispatch so
     // it can pick up any ``ScopeIdDef`` declared inside dispatched impls.
diff --git a/tests/cpp/ir_functor_test.cc b/tests/cpp/ir_functor_test.cc
index abbdf56035..5456119c8a 100644
--- a/tests/cpp/ir_functor_test.cc
+++ b/tests/cpp/ir_functor_test.cc
@@ -181,6 +181,8 @@ TEST(IRF, ExprVisit) {
   class MyVisitor : public tirx::ExprFunctor<void(const Expr&)>,
                     public tirx::StmtFunctor<void(const Stmt&)> {
    public:
+    using tirx::ExprFunctor<void(const Expr&)>::Dispatch;
+    using tirx::StmtFunctor<void(const Stmt&)>::Dispatch;
     int count = 0;
     // implementation
     void Dispatch_(const VarNode* op) final { ++count; }
@@ -189,10 +191,10 @@ TEST(IRF, ExprVisit) {
       Dispatch(op->a);
       Dispatch(op->b);
     }
-    void VisitStmt_(const EvaluateNode* op) final { Dispatch(op->value); }
+    void Dispatch_(const EvaluateNode* op) final { Dispatch(op->value); }
   };
   MyVisitor v;
-  v.VisitStmt(Evaluate(z));
+  v.Dispatch(Evaluate(z));
   TVM_FFI_ICHECK_EQ(v.count, 1);
 }
 
diff --git a/tests/cpp/stmt_functor_test.cc b/tests/cpp/stmt_functor_test.cc
new file mode 100644
index 0000000000..d799819003
--- /dev/null
+++ b/tests/cpp/stmt_functor_test.cc
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+#include <gtest/gtest.h>
+#include <tvm/tirx/stmt_functor.h>
+
+#include <memory>
+
+namespace tvm {
+namespace tirx {
+namespace {
+
+class ExtensionStmtNode : public StmtNode {
+ public:
+  static constexpr uint32_t _type_child_slots = 1;
+  TVM_FFI_DECLARE_OBJECT_INFO("test.ExtensionStmt", ExtensionStmtNode, 
StmtNode);
+};
+
+class ChildExtensionStmtNode : public ExtensionStmtNode {
+ public:
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.ChildExtensionStmt", 
ChildExtensionStmtNode,
+                                    ExtensionStmtNode);
+};
+
+using ForwardFunctor = StmtFunctor<int(const Stmt&, std::unique_ptr<int>, 
int&)>;
+
+class ExtendedFunctor : public ForwardFunctor {
+ public:
+  using ForwardFunctor::Dispatch_;
+  ExtendedFunctor() : ForwardFunctor(Table()) {}
+
+  int Dispatch_(const EvaluateNode*, std::unique_ptr<int> value, int& count) 
override {
+    ++count;
+    return *value;
+  }
+  virtual int Dispatch_(const ExtensionStmtNode*, std::unique_ptr<int> value, 
int& count) {
+    ++count;
+    return *value + 10;
+  }
+  int DispatchDefault_(const ffi::Object*, std::unique_ptr<int> value, int& 
count) override {
+    ++count;
+    return -*value;
+  }
+
+ private:
+  static const VTable* Table() {
+    static const VTable table = [] {
+      VTable table;
+      ForwardFunctor::InitVTable(&table);
+      SetDispatch<ExtendedFunctor, ExtensionStmtNode>(&table);
+      table.Finalize();
+      return table;
+    }();
+    return &table;
+  }
+};
+
+TEST(StmtFunctor, ExtendedTableForwardingAndAncestorDispatch) {
+  ExtendedFunctor functor;
+  int calls = 0;
+  auto value = std::make_unique<int>(3);
+  EXPECT_EQ(functor(Evaluate(IntImm::Int32(1)), std::move(value), calls), 3);
+  EXPECT_EQ(value, nullptr);
+  EXPECT_EQ(calls, 1);
+
+  Stmt extension(ffi::make_object<ExtensionStmtNode>());
+  EXPECT_EQ(functor.Dispatch(extension, std::make_unique<int>(4), calls), 14);
+  Stmt child(ffi::make_object<ChildExtensionStmtNode>());
+  EXPECT_EQ(functor(child, std::make_unique<int>(5), calls), 15);
+  EXPECT_EQ(calls, 3);
+
+  // An inherited default hook receives the same move-only and reference 
arguments.
+  EXPECT_EQ(functor(Break(Span()), std::make_unique<int>(6), calls), -6);
+  EXPECT_EQ(calls, 4);
+  // Extending one finalized table does not alter the base signature's default 
table.
+  ForwardFunctor base;
+  EXPECT_THROW(base(extension, std::make_unique<int>(1), calls), ffi::Error);
+  EXPECT_EQ(calls, 4);
+}
+
+TEST(StmtFunctor, EntryPointLifetimeAndNoRecursion) {
+  class Functor : public StmtFunctor<int(const Stmt&)> {
+   public:
+    explicit Functor(bool* destroyed) : destroyed_(destroyed) {}
+    ~Functor() override { *destroyed_ = true; }
+    int Dispatch(const Stmt& node) override { return 10 + 
StmtFunctor::Dispatch(node); }
+    int Dispatch_(const SeqStmtNode*) override { return 2; }
+
+   private:
+    bool* destroyed_;
+  };
+  bool destroyed = false;
+  {
+    std::unique_ptr<StmtFunctor<int(const Stmt&)>> functor = 
std::make_unique<Functor>(&destroyed);
+    // Evaluate would throw if the dispatcher recursed into the sequence.
+    EXPECT_EQ((*functor)(SeqStmt({Evaluate(IntImm::Int32(1)), 
Evaluate(IntImm::Int32(2))})), 12);
+    EXPECT_THROW((*functor)(Evaluate(IntImm::Int32(1))), ffi::Error);
+    EXPECT_THROW((*functor)(Stmt(nullptr)), ffi::Error);
+  }
+  EXPECT_TRUE(destroyed);
+}
+
+TEST(StmtFunctor, ReferenceResult) {
+  class Functor : public StmtFunctor<int&(const Stmt&, int&)> {
+   public:
+    int& Dispatch_(const EvaluateNode*, int& value) override { return value; }
+  };
+  Functor functor;
+  int value = 3;
+  functor(Evaluate(IntImm::Int32(1)), value) = 7;
+  EXPECT_EQ(value, 7);
+}
+
+}  // namespace
+}  // namespace tirx
+}  // namespace tvm

Reply via email to