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 f392ee50d7 [REFACTOR] Use local verifier scopes and exception visit 
context (#20684)
f392ee50d7 is described below

commit f392ee50d7adf8ab82e98cea9ff029f6e758ff6b
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 12 19:08:33 2026 -0400

    [REFACTOR] Use local verifier scopes and exception visit context (#20684)
    
    Migrate TIRx and S-TIR well-formedness, purity analysis, and remaining
    tile-op checks to ordinary visitors. Keep definition scopes and earlier
    definition locations within well-formedness checking, and resolve
    current diagnostic paths from exception visit context at verifier
    entrypoints.
    
    Preserve each analysis's traversal and binding policy while removing the
    private path visitor implementations.
---
 src/s_tir/analysis/is_pure_function.cc   | 159 ++++++-
 src/s_tir/analysis/verify_well_formed.cc |  85 +++-
 src/s_tir/ir/tir_visitor_with_path.cc    |  84 ----
 src/s_tir/ir/tir_visitor_with_path.h     |  54 ---
 src/tirx/analysis/verify_well_formed.cc  |   5 +-
 src/tirx/analysis/verify_well_formed.h   | 711 +++++++++++++++++++++----------
 src/tirx/ir/tir_visitor_with_path.cc     | 355 ---------------
 src/tirx/ir/tir_visitor_with_path.h      | 403 ------------------
 src/tirx/tile/lower.cc                   | 114 ++++-
 9 files changed, 792 insertions(+), 1178 deletions(-)

diff --git a/src/s_tir/analysis/is_pure_function.cc 
b/src/s_tir/analysis/is_pure_function.cc
index f5d6f7d842..bea01754b8 100644
--- a/src/s_tir/analysis/is_pure_function.cc
+++ b/src/s_tir/analysis/is_pure_function.cc
@@ -31,41 +31,59 @@
 #include <tvm/tirx/analysis.h>
 #include <tvm/tirx/op/memory.h>
 
-#include "../ir/tir_visitor_with_path.h"
-
 namespace tvm {
 namespace s_tir {
 using namespace tvm::tirx;
 
 namespace {
-class PurityChecker : TIRVisitorWithPath {
+class PurityChecker : public StmtExprVisitor {
+  friend class tvm::ObjectVisitor;
+  using Result = ffi::Optional<VisitInterrupt>;
+
  public:
   static bool Check(const Function& func, bool assert_on_error) {
-    PurityChecker visitor(assert_on_error);
-    visitor(func);
-    return visitor.is_pure_;
+    auto visitor = ffi::make_object<PurityChecker>(assert_on_error);
+    visitor->Visit(func);
+    return visitor->is_pure_;
   }
 
+  explicit PurityChecker(bool assert_on_error)
+      : StmtExprVisitor(GlobalVTable()), assert_on_error_(assert_on_error) {}
+
  private:
-  explicit PurityChecker(bool assert_on_error) : 
assert_on_error_(assert_on_error) {}
+  using StmtExprVisitor::Visit_;
+  Result Visit(ffi::AnyView value) override {
+    bool is_root = !entered_;
+    entered_ = true;
+    if (auto function = value.as<Function>(); function && !is_root) {
+      TVM_FFI_VISIT_THROW(TypeError, function.value())
+          << "Unsupported non-primitive TIR expression " << 
function.value()->GetTypeKey();
+    }
+    if (auto stmt = value.as<Stmt>(); stmt && 
!GlobalVTable()->CanDispatch(value)) {
+      TVM_FFI_VISIT_THROW(InternalError, stmt.value())
+          << "Do not have a default for " << stmt.value()->GetTypeKey();
+    }
+    if (auto expr = value.as<Expr>(); expr && 
!GlobalVTable()->CanDispatch(value)) {
+      if (value.as<OpaqueExprNode>() || value.as<VarNode>()) return 
std::nullopt;
+      TVM_FFI_VISIT_THROW(TypeError, expr.value())
+          << "Unsupported non-primitive TIR expression " << 
expr.value()->GetTypeKey();
+    }
+    return StmtExprVisitor::Visit(value);
+  }
 
-  void Dispatch_(const BindNode* op, ffi::reflection::AccessPath path) final {
+  Result Visit_(const BindNode* op) final {
     if (const auto* call = op->value.as<CallNode>();
         call && call->op.same_as(tirx::alloc_tensor_op())) {
-      return DispatchAllocTensor(op, call, path);
+      internal_allocations_.insert(op->var);
+      allocation_calls_.insert(call);
     }
-    return TIRVisitorWithPath::Dispatch_(op, path);
+    this->Visit(op->value);
+    return this->Visit(op->var->ty);
   }
 
-  void DispatchAllocTensor(const BindNode* op, const CallNode* call,
-                           ffi::reflection::AccessPath path) {
-    internal_allocations_.insert(op->var);
-    allocation_calls_.insert(call);
-    TIRVisitorWithPath::Dispatch_(op, path);
-  }
-
-  void Dispatch_(const TensorStoreNode* op, ffi::reflection::AccessPath path) 
override {
-    TIRVisitorWithPath::Dispatch_(op, path);
+  Result Visit_(const TensorStoreNode* op) override {
+    this->Visit(op->value);
+    this->Visit(op->indices);
 
     if (!internal_allocations_.count(op->dest.as_or_throw<TensorVar>().var())) 
{
       is_pure_ = false;
@@ -76,11 +94,13 @@ class PurityChecker : TIRVisitorWithPath {
             << op->indices << " of value " << op->value;
       }
     }
+    return std::nullopt;
   }
 
-  void Dispatch_(const CallNode* call, ffi::reflection::AccessPath path) 
override {
-    TIRVisitorWithPath::Dispatch_(call, path);
-    if (allocation_calls_.count(call)) return;
+  Result Visit_(const CallNode* call) override {
+    if (call->op.as<GlobalVar>() || call->op.as<OpaqueExprNode>()) 
this->Visit(call->op);
+    this->Visit(call->args);
+    if (allocation_calls_.count(call)) return std::nullopt;
 
     static auto op_call_effect = 
Op::GetAttrMap<TCallEffectKind>(tvm::op_attr::kCallEffectKind);
     CallEffectKind effect = [&]() {
@@ -100,8 +120,103 @@ class PurityChecker : TIRVisitorWithPath {
             << ", which has side effect " << effect;
       }
     }
+    return std::nullopt;
+  }
+
+  // Match the analysis' traversal policy without introducing binder state.
+  Result Visit_(const FunctionNode* op) {
+    for (const auto& param : op->params) {
+      if (!param->ty.as<TensorTypeNode>()) this->Visit(param->ty);
+    }
+    for (const auto& param : op->params) {
+      if (param->ty.as<TensorTypeNode>()) this->Visit(param->ty);
+    }
+    return this->Visit(op->body);
+  }
+
+  Result Visit_(const LambdaExprNode* op) {
+    this->Visit(op->ty);
+    for (const auto& var : op->vars) this->Visit(var->ty);
+    return this->Visit(op->body);
+  }
+
+  Result Visit_(const RegionStmtNode* op) override {
+    this->Visit(op->args);
+    this->Visit(op->attrs);
+    for (const auto& param : op->body_params) this->Visit(param->ty);
+    this->Visit(op->body);
+    for (const auto& var : op->result_vars) this->Visit(var->ty);
+    return std::nullopt;
+  }
+
+  Result Visit_(const ForNode* op) override {
+    this->Visit(op->min);
+    this->Visit(op->extent);
+    this->Visit(op->loop_var->ty);
+    return this->Visit(op->body);
+  }
+
+  Result Visit_(const prim::LetNode* op) override {
+    this->Visit(op->value);
+    this->Visit(op->var->ty);
+    return this->Visit(op->body);
+  }
+
+  Result Visit_(const TensorLoadNode* op) override { return 
this->Visit(op->indices); }
+
+  Result Visit_(const TensorRegionNode* op) override {
+    if (!op->source.as<TensorVar>()) this->Visit(op->source);
+    return this->Visit(op->region);
+  }
+
+  Result Visit_(const GenericConstNode* op) override { return std::nullopt; }
+
+  Result Visit_(const AssertStmtNode* op) override {
+    this->Visit(op->condition);
+    this->Visit(op->error_kind);
+    return this->Visit(op->message_parts);
+  }
+
+  Result Visit_(const prim::RampNode* op) override {
+    this->Visit(op->base);
+    this->Visit(op->stride);
+    return this->Visit(op->lanes);
+  }
+
+  Result Visit_(const prim::BroadcastNode* op) override {
+    this->Visit(op->value);
+    return this->Visit(op->lanes);
+  }
+
+  Result Visit_(const SBlockNode* op) override {
+    for (const auto& axis : op->iter_vars) {
+      this->Visit(axis->dom);
+      this->Visit(axis->var.as_or_throw<Var>()->ty);
+    }
+    for (const auto& tensor : op->alloc_tensors) this->Visit(tensor.var()->ty);
+    for (const auto& match : op->match_tensors) {
+      this->Visit(match->source);
+      this->Visit(match->tensor.var()->ty);
+    }
+    this->Visit(op->reads);
+    this->Visit(op->writes);
+    this->Visit(op->init);
+    return this->Visit(op->body);
+  }
+
+  static const VTable* GlobalVTable() {
+    static const VTable table = [] {
+      VTable table;
+      StmtExprVisitor::InitVTable(&table);
+      SetDispatch<PurityChecker, FunctionNode>(&table);
+      SetDispatch<PurityChecker, LambdaExprNode>(&table);
+      table.Finalize();
+      return table;
+    }();
+    return &table;
   }
 
+  bool entered_{false};
   bool assert_on_error_{false};
   bool is_pure_{true};
   std::unordered_set<Var> internal_allocations_;
diff --git a/src/s_tir/analysis/verify_well_formed.cc 
b/src/s_tir/analysis/verify_well_formed.cc
index d37e0c8134..c1f705fbb9 100644
--- a/src/s_tir/analysis/verify_well_formed.cc
+++ b/src/s_tir/analysis/verify_well_formed.cc
@@ -23,8 +23,6 @@
 #include <tvm/s_tir/analysis.h>
 #include <tvm/s_tir/stmt_functor.h>
 
-#include "../ir/tir_visitor_with_path.h"
-
 namespace tvm {
 namespace s_tir {
 using tirx::Function;
@@ -136,42 +134,87 @@ class BlockVarAccessVerifier : public StmtExprVisitor {
   bool has_error_{false};
 };
 
-/*! \brief Validate the scheduling annotation before thread-binding lowering. 
*/
-class ThreadBindingVerifier : public tirx::Verifier<ThreadBindingVerifier, 
TIRVisitorWithPath> {
-  using Verifier = tirx::Verifier<ThreadBindingVerifier, TIRVisitorWithPath>;
+// S-TIR definitions and scopes are part of this verifier, not its visitor 
base.
+class WellFormednessVerifier : public 
tirx::WellFormednessVerifier<StmtExprVisitor> {
+  using Parent = tirx::WellFormednessVerifier<StmtExprVisitor>;
 
  public:
-  using Verifier::Verifier;
-  using Verifier::Verify;
+  WellFormednessVerifier(Check check, bool assert_mode)
+      : Parent(GlobalVTable(), check, assert_mode) {}
+
+ protected:
+  using Parent::Visit_;
+  Result Visit_(const ForNode* op) override {
+    if (check_ == Check::kThreadBinding) {
+      if (auto tag = op->annotations.Get(attr::kThreadBinding)) {
+        Verify(op->kind == ForKind::kParallel)
+            << "ValueError: thread_binding requires a parallel loop at 
<current location>";
+        Verify(tag->as<ffi::String>().has_value())
+            << "TypeError: thread_binding annotation must be a string at 
<current location>";
+        Verify(op->HasTrivialStep())
+            << "ValueError: Thread binding loops require a unit step at 
<current location>";
+      }
+    }
+    return Parent::Visit_(op);
+  }
+
+  Result Visit_(const SBlockNode* op) override {
+    return WithScope([&]() -> Result {
+      for (size_t i = 0; i < op->iter_vars.size(); ++i) {
+        const auto& axis = op->iter_vars[i];
+        this->Visit(axis->dom);
+        Define(axis->var.as_or_throw<Var>(),
+               
AccessPath::Root()->Attr("iter_vars")->ArrayItem(i)->Attr("var"));
+      }
+      for (size_t i = 0; i < op->alloc_tensors.size(); ++i) {
+        Define(op->alloc_tensors[i].var(), 
AccessPath::Root()->Attr("alloc_tensors")->ArrayItem(i));
+      }
+      for (size_t i = 0; i < op->match_tensors.size(); ++i) {
+        const auto& match = op->match_tensors[i];
+        this->Visit(match->source);
+        auto site = 
AccessPath::Root()->Attr("match_tensors")->ArrayItem(i)->Attr("tensor");
+        MatchTensorDefinitions(match->tensor, site->Attr("ty"));
+        Define(match->tensor.var(), site);
+      }
+      this->Visit(op->reads);
+      this->Visit(op->writes);
+      WithScope([&]() { return this->Visit(op->init); });
+      return WithScope([&]() { return this->Visit(op->body); });
+    });
+  }
 
  private:
-  void Dispatch_(const ForNode* op, AccessPath path) override {
-    if (auto tag = op->annotations.Get(attr::kThreadBinding)) {
-      Verify(op->kind == ForKind::kParallel)
-          << "ValueError: thread_binding requires a parallel loop at " << path;
-      Verify(tag->as<ffi::String>().has_value())
-          << "TypeError: thread_binding annotation must be a string at " << 
path;
-      Verify(op->HasTrivialStep())
-          << "ValueError: Thread binding loops require a unit step at " << 
path;
-    }
-    Verifier::Dispatch_(op, path);
+  static const VTable* GlobalVTable() {
+    static const VTable table = [] {
+      VTable table;
+      Parent::InitVTable(&table);
+      table.Finalize();
+      return table;
+    }();
+    return &table;
   }
 };
 
+bool VerifyThreadBindings(const ffi::ObjectRef& node, bool assert_mode) {
+  return 
ffi::make_object<WellFormednessVerifier>(WellFormednessVerifier::Check::kThreadBinding,
+                                                  assert_mode)
+      ->Run(node);
+}
+
 bool VerifyWellFormed(const tirx::Function& func, bool assert_mode) {
-  return ThreadBindingVerifier::Verify(func, assert_mode) &&
+  return VerifyThreadBindings(func, assert_mode) &&
          BlockVarAccessVerifier::Verify(func, assert_mode) &&
-         tirx::VerifyWellFormedCommon<TIRVisitorWithPath>(func, assert_mode);
+         tirx::VerifyWellFormedCommon<WellFormednessVerifier>(func, 
assert_mode);
 }
 
 bool VerifyWellFormed(const IRModule& mod, bool assert_mode) {
-  if (!ThreadBindingVerifier::Verify(mod, assert_mode)) return false;
+  if (!VerifyThreadBindings(mod, assert_mode)) return false;
   for (const auto& [gvar, base_func] : mod->functions) {
     if (auto func = base_func.as<tirx::Function>()) {
       if (!BlockVarAccessVerifier::Verify(func.value(), assert_mode)) return 
false;
     }
   }
-  return tirx::VerifyWellFormedCommon<TIRVisitorWithPath>(mod, assert_mode);
+  return tirx::VerifyWellFormedCommon<WellFormednessVerifier>(mod, 
assert_mode);
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/s_tir/ir/tir_visitor_with_path.cc 
b/src/s_tir/ir/tir_visitor_with_path.cc
deleted file mode 100644
index 89005fd097..0000000000
--- a/src/s_tir/ir/tir_visitor_with_path.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#include "tir_visitor_with_path.h"
-
-#include <tvm/s_tir/stmt.h>
-
-namespace tvm {
-namespace s_tir {
-using namespace tirx;
-using AccessPath = ffi::reflection::AccessPath;
-
-void TIRVisitorWithPath::Dispatch_(const SBlockNode* op, AccessPath path) {
-  std::vector<DefContext<Var>> context;
-
-  {
-    auto iter_path = path->Attr("iter_vars");
-    for (size_t i = 0; i < op->iter_vars.size(); i++) {
-      const IterVar& axis = op->iter_vars[i];
-      auto axis_path = iter_path->ArrayItem(i);
-      if (axis->dom.has_value()) Visit(axis->dom.value(), 
axis_path->Attr("dom"));
-      context.push_back(WithDef(axis->var.as_or_throw<Var>(), 
axis_path->Attr("var")));
-    }
-  }
-
-  // Define alloc_tensors before visiting reads/writes, since reads/writes
-  // may reference tensors from alloc_tensors (e.g. after transform_layout).
-  {
-    auto alloc_path = path->Attr("alloc_tensors");
-    for (size_t i = 0; i < op->alloc_tensors.size(); i++) {
-      auto tensor_path = alloc_path->ArrayItem(i);
-      auto tensor = op->alloc_tensors[i];
-      context.push_back(WithDef(tensor.var(), tensor_path));
-    }
-  }
-
-  {
-    auto match_path = path->Attr("match_tensors");
-    for (size_t i = 0; i < op->match_tensors.size(); i++) {
-      Visit(op->match_tensors[i]->source, 
match_path->ArrayItem(i)->Attr("source"));
-      auto tensor = op->match_tensors[i]->tensor;
-      auto tensor_path = match_path->ArrayItem(i)->Attr("tensor");
-
-      for (auto& def : WithMatchTensorDefs(tensor, tensor_path->Attr("ty"))) {
-        context.push_back(std::move(def));
-      }
-      context.push_back(WithDef(tensor.var(), tensor_path));
-    }
-  }
-
-  // Regions may use allocation and match-tensor definitions in this block.
-  Visit(op->reads, path->Attr("reads"));
-  Visit(op->writes, path->Attr("writes"));
-
-  bind_scope_.WithNewScope([&]() { Visit(op->init, path->Attr("init")); });
-  bind_scope_.WithNewScope([&]() { Visit(op->body, path->Attr("body")); });
-
-  while (context.size()) context.pop_back();
-}
-
-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"));
-}
-
-}  // namespace s_tir
-}  // namespace tvm
diff --git a/src/s_tir/ir/tir_visitor_with_path.h 
b/src/s_tir/ir/tir_visitor_with_path.h
deleted file mode 100644
index 6556d2eec7..0000000000
--- a/src/s_tir/ir/tir_visitor_with_path.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#ifndef TVM_S_TIR_IR_TIR_VISITOR_WITH_PATH_H_
-#define TVM_S_TIR_IR_TIR_VISITOR_WITH_PATH_H_
-#include <tvm/s_tir/stmt.h>
-
-#include "../../tirx/ir/tir_visitor_with_path.h"
-namespace tvm {
-namespace s_tir {
-class TIRVisitorWithPath : public tirx::TIRVisitorWithPath {
- public:
-  using Parent = tirx::TIRVisitorWithPath;
-  TVM_DEFINE_OBJECT_FUNCTOR_DEFAULT_CONSTRUCTOR(TIRVisitorWithPath, Parent)
- protected:
-  using AccessPath = ffi::reflection::AccessPath;
-  using Parent::Dispatch_;
-  virtual void Dispatch_(const SBlockNode* op, AccessPath path);
-  virtual void Dispatch_(const SBlockRealizeNode* op, AccessPath path);
-  static void InitVTable(VTable* vtable) {
-    Parent::InitVTable(vtable);
-    // StmtVisitor is a protected base of the path visitor, so create the
-    // downcast thunks here instead of in StmtVisitor::SetDispatch.
-    vtable->SetDispatch<SBlockNode>(
-        [](const ffi::ObjectRef& node, StmtVisitor* self, AccessPath path) {
-          static_cast<TIRVisitorWithPath*>(self)->Dispatch_(
-              static_cast<const SBlockNode*>(node.get()), path);
-        });
-    vtable->SetDispatch<SBlockRealizeNode>(
-        [](const ffi::ObjectRef& node, StmtVisitor* self, AccessPath path) {
-          static_cast<TIRVisitorWithPath*>(self)->Dispatch_(
-              static_cast<const SBlockRealizeNode*>(node.get()), path);
-        });
-  }
-};
-}  // namespace s_tir
-}  // namespace tvm
-#endif  // TVM_S_TIR_IR_TIR_VISITOR_WITH_PATH_H_
diff --git a/src/tirx/analysis/verify_well_formed.cc 
b/src/tirx/analysis/verify_well_formed.cc
index 2ce62ba761..ef3d193325 100644
--- a/src/tirx/analysis/verify_well_formed.cc
+++ b/src/tirx/analysis/verify_well_formed.cc
@@ -35,18 +35,17 @@
 #include <tuple>
 #include <variant>
 
-#include "../ir/tir_visitor_with_path.h"
 #include "tvm/ir/module.h"
 
 namespace tvm {
 namespace tirx {
 
 bool VerifyWellFormed(const Function& func, bool assert_mode) {
-  return VerifyWellFormedCommon<TIRVisitorWithPath>(func, assert_mode);
+  return VerifyWellFormedCommon<WellFormednessVerifier<StmtExprVisitor>>(func, 
assert_mode);
 }
 
 bool VerifyWellFormed(const IRModule& mod, bool assert_mode) {
-  return VerifyWellFormedCommon<TIRVisitorWithPath>(mod, assert_mode);
+  return VerifyWellFormedCommon<WellFormednessVerifier<StmtExprVisitor>>(mod, 
assert_mode);
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/tirx/analysis/verify_well_formed.h 
b/src/tirx/analysis/verify_well_formed.h
index aaae0bdabd..763d74c545 100644
--- a/src/tirx/analysis/verify_well_formed.h
+++ b/src/tirx/analysis/verify_well_formed.h
@@ -20,299 +20,556 @@
 #ifndef TVM_TIRX_ANALYSIS_VERIFY_WELL_FORMED_H_
 #define TVM_TIRX_ANALYSIS_VERIFY_WELL_FORMED_H_
 
-#include <tvm/ir/prim/op.h>
+#include <tvm/ffi/extra/structural_visit.h>
+#include <tvm/ffi/extra/visit_error_context.h>
+#include <tvm/ir/function.h>
+#include <tvm/ir/module.h>
+#include <tvm/ir/scope_stack.h>
+#include <tvm/runtime/logging.h>
 #include <tvm/tirx/analysis.h>
-#include <tvm/tirx/op/region.h>
-#include <tvm/tirx/op_attr_types.h>
-#include <tvm/tirx/tile/exec_scope.h>
+#include <tvm/tirx/stmt_functor.h>
 
+#include <algorithm>
+#include <optional>
+#include <sstream>
+#include <unordered_map>
 #include <utility>
+#include <vector>
 
-#include "../ir/tir_visitor_with_path.h"
 namespace tvm {
 namespace tirx {
-using AccessPath = ffi::reflection::AccessPath;
 
-template <typename PathVisitor>
-class UndefinedVarVerifier : public 
Verifier<UndefinedVarVerifier<PathVisitor>, PathVisitor> {
-  using Verifier = tirx::Verifier<UndefinedVarVerifier<PathVisitor>, 
PathVisitor>;
+// This state and traversal belong to well-formedness checking. In particular,
+// ordinary visitors do not impose a definition/use or lexical-scope policy.
+template <typename Visitor>
+class WellFormednessVerifier : public Visitor {
+  friend class tvm::ObjectVisitor;
 
  public:
-  // Until templated-this arrives in C++23, the CRTP can't inject a
-  // constructor into the child class.  Therefore, must explicitly add
-  // the constructor.
-  using Verifier::Verifier;
-  using Verifier::Verify;
-
- private:
-  using Verifier::Visit;
-  void EnterDef(const Var& var, AccessPath path) override {
-    {
-      auto it = currently_defined_.find(var);
-      auto verify = Verify(it == currently_defined_.end());
-      verify << "ValueError: "
-             << "TIR is ill-formed, "
-             << "due to multiple nested definitions of variable " << var->name 
<< ".";
-      if (it != currently_defined_.end()) {
-        verify << " It was first defined at " << it->second << ", and was 
re-defined at " << path;
+  enum class Check { kVariables, kTensors, kTensorLoadType, kLoopControl, 
kThreadBinding };
+  using AccessPath = ffi::reflection::AccessPath;
+  using Result = ffi::Optional<VisitInterrupt>;
+
+  WellFormednessVerifier(Check check, bool assert_mode)
+      : WellFormednessVerifier(GlobalVTable(), check, assert_mode) {}
+
+  bool Run(const ffi::ObjectRef& root) {
+    root_ = root;
+    try {
+      this->Visit(root);
+    } catch (const ffi::Error& error) {
+      // Resolve at the verifier boundary for both direct and pass-driven 
calls.
+      // Once reported, consume the visit context to avoid a second enrichment.
+      auto context = ffi::VisitErrorContext::TryGetFromError(error);
+      if (!context) throw;
+      auto normalized = ffi::make_object<ffi::VisitErrorContextObj>();
+      normalized->prev_error_context = context.value()->prev_error_context;
+      for (const auto& object : context.value()->reverse_visit_pattern) {
+        if (!IsContainer(object)) 
normalized->reverse_visit_pattern.push_back(object);
       }
+      auto location = ffi::VisitErrorContext(std::move(normalized));
+      auto paths = ffi::VisitErrorContext::FindAccessPaths(root, location);
+      if (paths.empty()) throw;
+      std::ostringstream path;
+      path << paths[0];
+      std::string message = error.message();
+      const std::string marker = "<current location>";
+      size_t pos = 0;
+      bool replaced = false;
+      while ((pos = message.find(marker, pos)) != std::string::npos) {
+        message.replace(pos, marker.size(), path.str());
+        pos += path.str().size();
+        replaced = true;
+      }
+      if (!replaced) message += " at " + path.str();
+      throw ffi::Error(error.kind(), message, error.backtrace(), 
error.cause_chain(),
+                       location->prev_error_context.has_value()
+                           ? 
std::optional<ffi::ObjectRef>(location->prev_error_context.value())
+                           : std::nullopt);
     }
+    return !has_error_;
+  }
 
-    {
-      auto it = previously_defined_.find(var);
-      auto verify = Verify(it == previously_defined_.end());
-      verify << "ValueError: "
-             << "TIR is ill-formed, "
-             << "due to multiple definitions of variable " << var->name << ".";
-      if (it != previously_defined_.end()) {
-        verify << " It was first defined at " << it->second << ", and was 
later re-defined at "
-               << path;
+ protected:
+  using VTable = typename Visitor::VTable;
+  using Visitor::Visit_;
+  explicit WellFormednessVerifier(const VTable* table, Check check, bool 
assert_mode)
+      : Visitor(table), check_(check), table_(table), 
assert_mode_(assert_mode) {}
+
+  static void InitVTable(VTable* table) {
+    Visitor::InitVTable(table);
+    Visitor::template SetDispatch<WellFormednessVerifier, IRModuleNode>(table);
+    Visitor::template SetDispatch<WellFormednessVerifier, FunctionNode>(table);
+    Visitor::template SetDispatch<WellFormednessVerifier, 
LambdaExprNode>(table);
+  }
+
+  Result Visit(ffi::AnyView value) override {
+    auto object = value.as<ffi::ObjectRef>();
+    if (!object) return Visitor::Visit(value);
+    visit_stack_.push_back(object.value());
+    struct Pop {
+      std::vector<ffi::ObjectRef>* stack;
+      ~Pop() { stack->pop_back(); }
+    } pop{&visit_stack_};
+    // Function entrypoints and module members are roots of verification;
+    // a Function embedded as an expression remains unsupported.
+    if (value.as<Function>() && visit_stack_.size() != 1 &&
+        !(visit_stack_.size() == 2 && root_.as<IRModule>())) {
+      TVM_FFI_VISIT_THROW(TypeError, object.value())
+          << "Unsupported non-primitive TIR expression " << 
object.value()->GetTypeKey();
+    }
+    if (value.as<Stmt>() && !table_->CanDispatch(value)) {
+      TVM_FFI_VISIT_BEGIN();
+      Verify(false) << "TIR verifier does not support statement " << 
object.value()->GetTypeKey()
+                    << " at <current location>";
+      return std::nullopt;
+      TVM_FFI_VISIT_END(object.value());
+    }
+    if (value.as<Expr>() && !table_->CanDispatch(value)) {
+      if (value.as<OpaqueExprNode>()) return std::nullopt;
+      if (auto var = value.as<VarNode>()) {
+        TVM_FFI_VISIT_BEGIN();
+        return this->Visit_(var);
+        TVM_FFI_VISIT_END(object.value());
       }
+      TVM_FFI_VISIT_THROW(TypeError, object.value())
+          << "Unsupported non-primitive TIR expression " << 
object.value()->GetTypeKey();
     }
-
-    currently_defined_.insert({var, path});
+    return Visitor::Visit(value);
   }
 
-  void ExitDef(const Var& var, AccessPath path) override {
-    auto active_def = currently_defined_.find(var);
+  class VerifyStream {
+   public:
+    explicit VerifyStream(bool fail) {
+      if (fail) message_.emplace();
+    }
+    VerifyStream(const VerifyStream&) = delete;
+    VerifyStream(VerifyStream&& other) { std::swap(message_, other.message_); }
+    template <typename T>
+    VerifyStream& operator<<(T&& value) {
+      if (message_) *message_ << std::forward<T>(value);
+      return *this;
+    }
+    ~VerifyStream() noexcept(false) {
+      if (message_) LOG(FATAL) << message_->str();
+    }
+
+   private:
+    std::optional<std::ostringstream> message_;
+  };
 
-    if (active_def != currently_defined_.end()) 
currently_defined_.erase(active_def);
-    previously_defined_.insert({var, path});
+  VerifyStream Verify(bool condition) {
+    has_error_ |= !condition;
+    return VerifyStream(!condition && assert_mode_);
   }
 
-  void Dispatch_(const VarNode* op, AccessPath path) override {
-    auto var = ffi::GetRef<Var>(op);
+  // Only earlier definitions need a saved occurrence. Keep their enclosing
+  // objects and the defining field, rather than searching for a Var that may
+  // also occur at arbitrarily many use sites.
+  struct DefinitionSite {
+    ffi::VisitErrorContext context;
+    AccessPath field;
+  };
+
+  // FindAccessPaths descends through containers without matching their 
identity.
+  // Native visiting can record them; keep only matchable object breadcrumbs.
+  static bool IsContainer(const ffi::ObjectRef& object) {
+    switch (object->type_index()) {
+      case ffi::TypeIndex::kTVMFFIArray:
+      case ffi::TypeIndex::kTVMFFIList:
+      case ffi::TypeIndex::kTVMFFIMap:
+      case ffi::TypeIndex::kTVMFFIDict:
+        return true;
+      default:
+        return false;
+    }
+  }
 
-    auto active_def = currently_defined_.find(var);
-    auto verify = Verify(active_def != currently_defined_.end());
-    verify << "ValueError: "
-           << "Invalid use of undefined variable " << var->name << " at " << 
path << ".";
-
-    // Check if there was a previous definition, and append the
-    // location to the error message if there was.  This is to aid in
-    // debugging, by distinguishing between a variable that is
-    // currently out-of-scope, and a variable that never had a
-    // definition in the first place.
-    if (auto prev_def = previously_defined_.find(var); prev_def != 
previously_defined_.end()) {
-      verify << ".  While this variable was previously defined at " << 
prev_def->second
-             << ", this definition is no longer in-scope.";
+  DefinitionSite DefinitionAt(AccessPath field) const {
+    auto context = ffi::make_object<ffi::VisitErrorContextObj>();
+    for (auto it = visit_stack_.rbegin(); it != visit_stack_.rend(); ++it) {
+      if (!IsContainer(*it)) context->reverse_visit_pattern.push_back(*it);
     }
+    return {ffi::VisitErrorContext(std::move(context)), std::move(field)};
   }
 
-  // Variables that are defined in the currently-visited scope.
-  std::unordered_map<Var, AccessPath> currently_defined_;
+  AccessPath DefinitionPath(const DefinitionSite& site) const {
+    auto paths = ffi::VisitErrorContext::FindAccessPaths(root_, site.context);
+    auto path = paths.empty() ? AccessPath::Root() : paths[0];
+    for (const auto& step : site.field->ToSteps()) path = path->Extend(step);
+    return path;
+  }
 
-  // Variables that were previously defined, and are now out of scope.
-  std::unordered_map<Var, AccessPath> previously_defined_;
-};
+  template <typename F>
+  Result WithScope(F&& body) {
+    return scopes_.WithNewScope([&]() -> Result {
+      Result result = body();
+      // Definition exit never diagnoses an error. On an exception the entire
+      // verifier is discarded, so only normal completion needs history 
updates.
+      for (auto it = scopes_.Current().rbegin(); it != 
scopes_.Current().rend(); ++it) {
+        current_.erase(it->first);
+        previous_.insert(*it);
+      }
+      return result;
+    });
+  }
 
-/*! \brief Verify that tensors with a declaration are not used outside their 
declared scope.
- *
- * When a tensor is declared via one of the following sites:
- *   - TensorType-annotated Function parameters
- *   - DeclTensor statement
- *   - Dialect-specific definitions exposed by PathVisitor
- *
- * it must not appear in a TensorLoad, TensorStore, or TensorRegion outside 
that declaration's
- * scope.
- *
- * All tensors that appear in TensorLoad or TensorStore must have a prior 
declaration.
- */
-template <typename PathVisitor>
-class UndefinedTensorVerifier : public 
Verifier<UndefinedTensorVerifier<PathVisitor>, PathVisitor> {
-  using Verifier = tirx::Verifier<UndefinedTensorVerifier<PathVisitor>, 
PathVisitor>;
+  void Define(const Var& var, AccessPath field, bool visit_type = true) {
+    if (visit_type) this->Visit(var->ty);
+    auto site = DefinitionAt(std::move(field));
+    if (check_ == Check::kVariables) {
+      {
+        auto it = current_.find(var);
+        auto verify = Verify(it == current_.end());
+        verify << "ValueError: TIR is ill-formed, due to multiple nested 
definitions of variable "
+               << var->name << ".";
+        if (it != current_.end()) {
+          verify << " It was first defined at " << DefinitionPath(it->second)
+                 << ", and was re-defined at <current location>";
+        }
+      }
+      {
+        auto it = previous_.find(var);
+        auto verify = Verify(it == previous_.end());
+        verify << "ValueError: TIR is ill-formed, due to multiple definitions 
of variable "
+               << var->name << ".";
+        if (it != previous_.end()) {
+          verify << " It was first defined at " << DefinitionPath(it->second)
+                 << ", and was later re-defined at <current location>";
+        }
+      }
+    }
+    current_.insert({var, site});
+    scopes_.Current().emplace_back(var, std::move(site));
+  }
 
- public:
-  using Verifier::Verifier;
-  using Verifier::Verify;
+  void MatchTensorDefinitions(const TensorVar& tensor, AccessPath field) {
+    auto define_if_needed = [&](const Var& var, AccessPath site) {
+      if (!current_.count(var)) Define(var, std::move(site));
+    };
+    for (size_t i = 0; i < tensor->shape.size(); ++i) {
+      auto site = field->Attr("shape")->ArrayItem(i);
+      auto walk = [&](const Var& var) -> ffi::Expected<ffi::WalkResult> {
+        define_if_needed(var, site);
+        return ffi::WalkResult::Advance();
+      };
+      ffi::StructuralWalk<ffi::WalkOrder::kPostOrder>(tensor->shape[i], walk);
+    }
+    for (size_t i = 0; i < tensor->strides.size(); ++i) {
+      if (auto var = tensor->strides[i].template as<Var>()) {
+        define_if_needed(var.value(), field->Attr("strides")->ArrayItem(i));
+      }
+    }
+    if (auto var = tensor->elem_offset.template as<Var>()) {
+      define_if_needed(var.value(), field->Attr("elem_offset"));
+    }
+  }
 
- private:
-  using Verifier::Visit;
+  Result Visit_(const IRModuleNode* op) {
+    std::vector<GlobalVar> globals;
+    for (const auto& [global, function] : op->functions) 
globals.push_back(global);
+    std::sort(globals.begin(), globals.end(), [&](const GlobalVar& a, const 
GlobalVar& b) {
+      bool a_public =
+          op->functions[a]->template 
GetAttr<ffi::String>(tvm::attr::kGlobalSymbol).has_value();
+      bool b_public =
+          op->functions[b]->template 
GetAttr<ffi::String>(tvm::attr::kGlobalSymbol).has_value();
+      return a_public != b_public ? a_public > b_public : a->name_hint < 
b->name_hint;
+    });
+    for (const auto& global : globals) {
+      if (auto function = op->functions[global].template as<Function>())
+        this->Visit(function.value());
+    }
+    return std::nullopt;
+  }
 
-  void Visit(const Function& function, AccessPath path) override {
-    Verifier::Visit(function, path);
-    // Clear per-function state (tensors should not cross function boundaries).
-    currently_defined_.clear();
-    previously_defined_.clear();
+  Result Visit_(const FunctionNode* op) {
+    int enclosing_depth = loop_depth_;
+    loop_depth_ = 0;
+    WithScope([&]() -> Result {
+      auto params = AccessPath::Root()->Attr("params");
+      for (size_t i = 0; i < op->params.size(); ++i) {
+        if (!op->params[i]->ty.template as<TensorTypeNode>())
+          Define(op->params[i], params->ArrayItem(i));
+      }
+      for (size_t i = 0; i < op->params.size(); ++i) {
+        if (auto tensor = op->params[i].template as<TensorVar>()) {
+          MatchTensorDefinitions(tensor.value(), 
params->ArrayItem(i)->Attr("ty"));
+        }
+      }
+      for (size_t i = 0; i < op->params.size(); ++i) {
+        if (op->params[i].template as<TensorVar>()) Define(op->params[i], 
params->ArrayItem(i));
+      }
+      return WithScope([&]() { return this->Visit(op->body); });
+    });
+    loop_depth_ = enclosing_depth;
+    if (check_ == Check::kTensors) {
+      current_.clear();
+      previous_.clear();
+    }
+    return std::nullopt;
   }
 
-  void EnterDef(const Var& var, AccessPath path) override {
-    if (auto tensor = var.as<TensorVar>()) {
-      currently_defined_.insert({tensor.value(), path});
+  Result Visit_(const VarNode* op) override {
+    auto var = ffi::GetRef<Var>(op);
+    if (check_ == Check::kVariables) {
+      auto verify = Verify(current_.count(var));
+      verify << "ValueError: Invalid use of undefined variable " << var->name
+             << " at <current location>.";
+      if (auto it = previous_.find(var); it != previous_.end()) {
+        verify << ".  While this variable was previously defined at " << 
DefinitionPath(it->second)
+               << ", this definition is no longer in-scope.";
+      }
+    }
+    return std::nullopt;
+  }
+
+  void TensorUse(const TensorVar& tensor) {
+    if (check_ != Check::kTensors) return;
+    if (current_.count(tensor.var())) return;
+    auto it = previous_.find(tensor.var());
+    auto verify = Verify(false);
+    verify << "TIR is ill-formed: tensor " << tensor.name() << " is used at 
<current location>";
+    if (it != previous_.end()) {
+      verify << " but its declaration is no longer in-scope. It was declared 
at "
+             << DefinitionPath(it->second) << ".";
+    } else {
+      verify << " without a prior DeclTensor or other declaration.";
     }
   }
 
-  void ExitDef(const Var& var, AccessPath path) override {
-    if (!var->ty.as<TensorTypeNode>()) return;
-    auto tensor = var.as_or_throw<TensorVar>();
-    auto active_def = currently_defined_.find(tensor);
-    if (active_def != currently_defined_.end()) {
-      currently_defined_.erase(active_def);
+  Result Visit_(const BindNode* op) override {
+    this->Visit(op->value);
+    Define(op->var, AccessPath::Root()->Attr("var"));
+    return std::nullopt;
+  }
+
+  Result Visit_(const RegionStmtNode* op) override {
+    this->Visit(op->args);
+    this->Visit(op->attrs);
+    for (const auto& param : op->body_params) this->Visit(param->ty);
+    WithScope([&]() -> Result {
+      for (size_t i = 0; i < op->body_params.size(); ++i) {
+        Define(op->body_params[i], 
AccessPath::Root()->Attr("body_params")->ArrayItem(i), false);
+      }
+      return this->Visit(op->body);
+    });
+    for (size_t i = 0; i < op->result_vars.size(); ++i) {
+      Define(op->result_vars[i], 
AccessPath::Root()->Attr("result_vars")->ArrayItem(i));
     }
-    previously_defined_.insert({tensor, path});
-  }
-
-  void VisitTensorUse(const TensorVar& tensor, AccessPath path) override {
-    bool is_declared = currently_defined_.count(tensor);
-    bool was_declared = previously_defined_.count(tensor);
-
-    if (was_declared && !is_declared) {
-      // TensorVar was previously declared but is now out of scope — always an 
error.
-      auto prev_def = previously_defined_.find(tensor);
-      Verify(false) << "TIR is ill-formed: tensor " << tensor.name() << " is 
used at " << path
-                    << " but its declaration is no longer in-scope. "
-                    << "It was declared at " << prev_def->second << ".";
-    } else if (!is_declared && !was_declared) {
-      // TensorVar was never declared — error.
-      Verify(false) << "TIR is ill-formed: tensor " << tensor.name() << " is 
used at " << path
-                    << " without a prior DeclTensor or other declaration.";
+    return std::nullopt;
+  }
+
+  Result Visit_(const ForNode* op) override {
+    this->Visit(op->min);
+    this->Visit(op->extent);
+    if (check_ == Check::kLoopControl) {
+      this->Visit(op->step);
+      ++loop_depth_;
+      this->Visit(op->body);
+      --loop_depth_;
+      return std::nullopt;
     }
-    // TensorVar fields are visited at definition site (EnterDef), not here.
-    Verifier::VisitTensorUse(tensor, path);
+    return WithScope([&]() -> Result {
+      Define(op->loop_var, AccessPath::Root()->Attr("loop_var"));
+      return WithScope([&]() { return this->Visit(op->body); });
+    });
   }
 
-  // Tensors defined in the currently-visited scope.
-  std::unordered_map<TensorVar, AccessPath, ffi::ObjectPtrHash, 
ffi::ObjectPtrEqual>
-      currently_defined_;
-  // Tensors that were previously defined and are now out of scope.
-  std::unordered_map<TensorVar, AccessPath, ffi::ObjectPtrHash, 
ffi::ObjectPtrEqual>
-      previously_defined_;
-};
+  Result Visit_(const WhileNode* op) override {
+    this->Visit(op->condition);
+    ++loop_depth_;
+    WithScope([&]() { return this->Visit(op->body); });
+    --loop_depth_;
+    return std::nullopt;
+  }
 
-/*! \brief Verify the asserted type of each tirx tensor load. */
-template <typename PathVisitor>
-class TensorLoadTypeVerifier : public 
Verifier<TensorLoadTypeVerifier<PathVisitor>, PathVisitor> {
-  using Verifier = tirx::Verifier<TensorLoadTypeVerifier<PathVisitor>, 
PathVisitor>;
+  Result Visit_(const IfNode* op) override {
+    this->Visit(op->condition);
+    WithScope([&]() { return this->Visit(op->then_case); });
+    return WithScope([&]() { return this->Visit(op->else_case); });
+  }
 
- public:
-  using Verifier::Verifier;
-  using Verifier::Verify;
+  Result Visit_(const prim::LetNode* op) override {
+    this->Visit(op->value);
+    return WithScope([&]() -> Result {
+      Define(op->var, AccessPath::Root()->Attr("var"));
+      return this->Visit(op->body);
+    });
+  }
 
- private:
-  using Verifier::Visit;
-  void Dispatch_(const TensorLoadNode* op, AccessPath path) override {
-    auto tensor = op->source.as<TensorVar>();
-    auto valid_source = Verify(tensor.has_value());
-    valid_source << "TypeError: TIR TensorLoad source at " << 
path->Attr("source")
-                 << " must be a TensorVar.";
-    if (!tensor.has_value()) {
-      Visit(op->indices, path->Attr("indices"));
-      return;
-    }
+  Result Visit_(const LambdaExprNode* op) {
+    this->Visit(op->ty);
+    return WithScope([&]() -> Result {
+      for (size_t i = 0; i < op->vars.size(); ++i) {
+        Define(op->vars[i], AccessPath::Root()->Attr("vars")->ArrayItem(i));
+      }
+      return this->Visit(op->body);
+    });
+  }
 
-    bool valid_indices = tensor.value()->shape.size() == op->indices.size();
-    auto valid_rank = Verify(valid_indices);
-    valid_rank << "ValueError: TIR TensorLoad at " << path << " indexes "
-               << tensor.value()->shape.size() << "-dimensional tensor " << 
tensor.value().name()
-               << " with " << op->indices.size() << " indices.";
-    if (!valid_indices) {
-      Visit(op->indices, path->Attr("indices"));
-      return;
-    }
+  Result Visit_(const CallNode* op) override {
+    if (op->op.template as<GlobalVar>() || op->op.template 
as<OpaqueExprNode>())
+      this->Visit(op->op);
+    return this->Visit(op->args);
+  }
 
-    for (size_t i = 0; i + 1 < op->indices.size(); ++i) {
-      bool is_scalar = op->indices[i].ty().IsScalar();
-      auto valid_index = Verify(is_scalar);
-      valid_index << "TypeError: TIR TensorLoad index " << i << " at "
-                  << path->Attr("indices")->ArrayItem(i)
-                  << " must be scalar because only the final index may be 
vector-valued.";
-      valid_indices = valid_indices && is_scalar;
-    }
-    if (!valid_indices) {
-      Visit(op->indices, path->Attr("indices"));
-      return;
-    }
+  Result Visit_(const TensorStoreNode* op) override {
+    this->Visit(op->value);
+    TensorUse(op->dest.template as_or_throw<TensorVar>());
+    return this->Visit(op->indices);
+  }
 
-    ffi::Optional<PrimType> index_ty = op->indices.empty()
-                                           ? 
ffi::Optional<PrimType>(tensor.value()->dtype)
-                                           : 
op->indices.back().ty().as<PrimType>();
-    AccessPath final_index_path = op->indices.empty()
-                                      ? path->Attr("indices")
-                                      : 
path->Attr("indices")->ArrayItem(op->indices.size() - 1);
-    auto valid_index_type = Verify(index_ty.has_value());
-    valid_index_type << "TypeError: TIR TensorLoad final index at " << 
final_index_path
-                     << " must have a primitive type.";
-    if (!index_ty.has_value()) {
-      Visit(op->indices, path->Attr("indices"));
-      return;
+  Result Visit_(const TensorRegionNode* op) override {
+    if (auto tensor = op->source.template as<TensorVar>()) {
+      TensorUse(tensor.value());
+    } else {
+      this->Visit(op->source);
     }
+    return this->Visit(op->region);
+  }
+
+  Result Visit_(const GenericConstNode* op) override { return std::nullopt; }
+
+  Result Visit_(const AssertStmtNode* op) override {
+    this->Visit(op->condition);
+    this->Visit(op->error_kind);
+    return this->Visit(op->message_parts);
+  }
+
+  Result Visit_(const prim::RampNode* op) override {
+    this->Visit(op->base);
+    this->Visit(op->stride);
+    return this->Visit(op->lanes);
+  }
+
+  Result Visit_(const prim::BroadcastNode* op) override {
+    this->Visit(op->value);
+    return this->Visit(op->lanes);
+  }
 
-    bool scalable_compatible = op->indices.empty() || 
!(tensor.value()->dtype.IsScalableVector() &&
-                                                        
index_ty.value().IsScalableVector());
-    auto valid_scalability = Verify(scalable_compatible);
-    valid_scalability << "TypeError: TIR TensorLoad at " << path
-                      << " cannot combine a scalable tensor dtype with a 
scalable index.";
-    if (!scalable_compatible) {
-      Visit(op->indices, path->Attr("indices"));
-      return;
+  Result Visit_(const BreakNode* op) override {
+    if (check_ == Check::kLoopControl) {
+      Verify(loop_depth_ > 0) << "ValueError: break at <current location> 
requires an enclosing "
+                                 "loop in the same function.";
     }
+    return std::nullopt;
+  }
 
-    TensorLoad expected = MakeTensorLoad(tensor.value(), op->indices, op->loc);
-    ffi::Optional<PrimType> asserted_ty = op->ty.as<PrimType>();
-    ffi::Optional<PrimType> expected_ty = expected->ty.as<PrimType>();
-    auto valid_type = Verify(asserted_ty.has_value() && 
expected_ty.has_value() &&
-                             asserted_ty.value() == expected_ty.value());
-    valid_type << "TypeError: TIR TensorLoad at " << path << " asserts result 
type " << op->ty
-               << ", but its source and indices imply " << expected->ty << ".";
-    PathVisitor::Dispatch_(op, path);
+  Result Visit_(const ContinueNode* op) override {
+    if (check_ == Check::kLoopControl) {
+      Verify(loop_depth_ > 0) << "ValueError: continue at <current location> 
requires an enclosing "
+                                 "loop in the same function.";
+    }
+    return std::nullopt;
   }
-};
 
-/*! \brief Verify that loop control belongs to a loop body in the same 
function. */
-template <typename PathVisitor>
-class LoopControlVerifier : public Verifier<LoopControlVerifier<PathVisitor>, 
PathVisitor> {
-  using Verifier = tirx::Verifier<LoopControlVerifier<PathVisitor>, 
PathVisitor>;
+  Result Visit_(const TensorLoadNode* op) override;
 
- public:
-  using Verifier::Verifier;
-  using Verifier::Verify;
+  Check check_;
 
  private:
-  using Verifier::Visit;
+  static const VTable* GlobalVTable() {
+    static const VTable table = [] {
+      VTable table;
+      InitVTable(&table);
+      table.Finalize();
+      return table;
+    }();
+    return &table;
+  }
 
-  void Visit(const Function& function, AccessPath path) override {
-    int enclosing_depth = loop_depth_;
-    loop_depth_ = 0;
-    Verifier::Visit(function, path);
-    loop_depth_ = enclosing_depth;
+  const VTable* table_;
+  bool assert_mode_;
+  bool has_error_{false};
+  int loop_depth_{0};
+  ffi::ObjectRef root_;
+  std::vector<ffi::ObjectRef> visit_stack_;
+  ScopeStack<std::vector<std::pair<Var, DefinitionSite>>> scopes_;
+  std::unordered_map<Var, DefinitionSite> current_;
+  std::unordered_map<Var, DefinitionSite> previous_;
+};
+
+template <typename Visitor>
+auto WellFormednessVerifier<Visitor>::Visit_(const TensorLoadNode* op) -> 
Result {
+  if (check_ != Check::kTensorLoadType) {
+    TensorUse(op->source.template as_or_throw<TensorVar>());
+    return this->Visit(op->indices);
+  }
+  auto tensor = op->source.as<TensorVar>();
+  auto valid_source = Verify(tensor.has_value());
+  valid_source << "TypeError: TIR TensorLoad source at " << "<current 
location>.source"
+               << " must be a TensorVar.";
+  if (!tensor.has_value()) {
+    this->Visit(op->indices);
+    return std::nullopt;
   }
 
-  void Dispatch_(const ForNode* op, AccessPath path) override {
-    Visit(op->min, path->Attr("min"));
-    Visit(op->extent, path->Attr("extent"));
-    Visit(op->step, path->Attr("step"));
-    ++loop_depth_;
-    Visit(op->body, path->Attr("body"));
-    --loop_depth_;
+  bool valid_indices = tensor.value()->shape.size() == op->indices.size();
+  auto valid_rank = Verify(valid_indices);
+  valid_rank << "ValueError: TIR TensorLoad at " << "<current location>" << " 
indexes "
+             << tensor.value()->shape.size() << "-dimensional tensor " << 
tensor.value().name()
+             << " with " << op->indices.size() << " indices.";
+  if (!valid_indices) {
+    this->Visit(op->indices);
+    return std::nullopt;
   }
 
-  void Dispatch_(const WhileNode* op, AccessPath path) override {
-    Visit(op->condition, path->Attr("condition"));
-    ++loop_depth_;
-    Visit(op->body, path->Attr("body"));
-    --loop_depth_;
+  for (size_t i = 0; i + 1 < op->indices.size(); ++i) {
+    bool is_scalar = op->indices[i].ty().IsScalar();
+    auto valid_index = Verify(is_scalar);
+    valid_index << "TypeError: TIR TensorLoad index " << i << " at "
+                << "<current location>.indices[" << i << "]"
+                << " must be scalar because only the final index may be 
vector-valued.";
+    valid_indices = valid_indices && is_scalar;
+  }
+  if (!valid_indices) {
+    this->Visit(op->indices);
+    return std::nullopt;
   }
 
-  void Dispatch_(const BreakNode* op, AccessPath path) override {
-    Verify(loop_depth_ > 0) << "ValueError: break at " << path
-                            << " requires an enclosing loop in the same 
function.";
+  ffi::Optional<PrimType> index_ty = op->indices.empty()
+                                         ? 
ffi::Optional<PrimType>(tensor.value()->dtype)
+                                         : 
op->indices.back().ty().as<PrimType>();
+  auto valid_index_type = Verify(index_ty.has_value());
+  valid_index_type << "TypeError: TIR TensorLoad final index at <current 
location>.indices";
+  if (!op->indices.empty()) valid_index_type << "[" << op->indices.size() - 1 
<< "]";
+  valid_index_type << " must have a primitive type.";
+  if (!index_ty.has_value()) {
+    this->Visit(op->indices);
+    return std::nullopt;
   }
 
-  void Dispatch_(const ContinueNode* op, AccessPath path) override {
-    Verify(loop_depth_ > 0) << "ValueError: continue at " << path
-                            << " requires an enclosing loop in the same 
function.";
+  bool scalable_compatible = op->indices.empty() || 
!(tensor.value()->dtype.IsScalableVector() &&
+                                                      
index_ty.value().IsScalableVector());
+  auto valid_scalability = Verify(scalable_compatible);
+  valid_scalability << "TypeError: TIR TensorLoad at " << "<current location>"
+                    << " cannot combine a scalable tensor dtype with a 
scalable index.";
+  if (!scalable_compatible) {
+    this->Visit(op->indices);
+    return std::nullopt;
   }
 
-  int loop_depth_{0};
-};
+  TensorLoad expected = MakeTensorLoad(tensor.value(), op->indices, op->loc);
+  ffi::Optional<PrimType> asserted_ty = op->ty.as<PrimType>();
+  ffi::Optional<PrimType> expected_ty = expected->ty.as<PrimType>();
+  auto valid_type = Verify(asserted_ty.has_value() && expected_ty.has_value() 
&&
+                           asserted_ty.value() == expected_ty.value());
+  valid_type << "TypeError: TIR TensorLoad at " << "<current location>" << " 
asserts result type "
+             << op->ty << ", but its source and indices imply " << 
expected->ty << ".";
+  TensorUse(tensor.value());
+  return this->Visit(op->indices);
+}
 
-template <typename PathVisitor, typename NodeRef>
+template <typename Checker, typename NodeRef>
 bool VerifyWellFormedCommon(const NodeRef& node, bool assert_mode) {
-  return UndefinedVarVerifier<PathVisitor>::Verify(node, assert_mode) &&
-         UndefinedTensorVerifier<PathVisitor>::Verify(node, assert_mode) &&
-         TensorLoadTypeVerifier<PathVisitor>::Verify(node, assert_mode) &&
-         LoopControlVerifier<PathVisitor>::Verify(node, assert_mode);
+  using Check = typename Checker::Check;
+  for (Check check :
+       {Check::kVariables, Check::kTensors, Check::kTensorLoadType, 
Check::kLoopControl}) {
+    if (!ffi::make_object<Checker>(check, assert_mode)->Run(node)) return 
false;
+  }
+  return true;
 }
+
 }  // namespace tirx
 }  // namespace tvm
 #endif  // TVM_TIRX_ANALYSIS_VERIFY_WELL_FORMED_H_
diff --git a/src/tirx/ir/tir_visitor_with_path.cc 
b/src/tirx/ir/tir_visitor_with_path.cc
deleted file mode 100644
index 3bf6f490c6..0000000000
--- a/src/tirx/ir/tir_visitor_with_path.cc
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/*!
- * \file tirx/ir/tir_visitor_with_path.cc
- * \brief Provide a TIR visitor that tracks the current location
- */
-#include "tir_visitor_with_path.h"
-
-#include <tvm/ffi/reflection/access_path.h>
-#include <tvm/ffi/reflection/accessor.h>
-#include <tvm/ir/function.h>
-#include <tvm/tirx/stmt.h>
-
-#include <algorithm>
-#include <optional>
-#include <unordered_set>
-#include <variant>
-#include <vector>
-
-namespace tvm {
-namespace tirx {
-using AccessPath = ffi::reflection::AccessPath;
-
-void TIRVisitorWithPath::Visit(const IRModule& mod, AccessPath path) {
-  // To ensure deterministic order of visits, sort the GlobalVar first
-  // by visibility (public then private), then alphabetically by name.
-  std::vector<GlobalVar> gvars;
-  std::unordered_set<GlobalVar> externally_exposed;
-  for (const auto& [gvar, func] : mod->functions) {
-    gvars.push_back(gvar);
-    if (func->GetAttr<ffi::String>(tvm::attr::kGlobalSymbol).has_value()) {
-      externally_exposed.insert(gvar);
-    }
-  }
-
-  std::sort(gvars.begin(), gvars.end(),
-            [&externally_exposed](const GlobalVar& a, const GlobalVar& b) {
-              bool a_exposed = externally_exposed.count(a);
-              bool b_exposed = externally_exposed.count(b);
-              if (a_exposed != b_exposed) {
-                return a_exposed > b_exposed;
-              } else {
-                return a->name_hint < b->name_hint;
-              }
-            });
-
-  std::vector<DefContext<GlobalVar>> context;
-
-  for (const auto& gvar : gvars) {
-    context.push_back(WithDef(gvar, 
path->Attr("global_var_map_")->MapItem(gvar->name_hint)));
-  }
-
-  for (const auto& gvar : gvars) {
-    auto base_func = mod->functions[gvar];
-    if (auto function = base_func.as<Function>()) {
-      Visit(function.value(), path->Attr("functions")->MapItem(gvar));
-    }
-  }
-
-  while (context.size()) context.pop_back();
-}
-
-void TIRVisitorWithPath::Visit(const Function& func, AccessPath path) {
-  // TensorType metadata may introduce symbolic dimensions.  Define those
-  // symbols before entering the tensor parameter itself.
-  std::vector<DefContext<Var>> context;
-
-  auto ppath = path->Attr("params");
-  for (size_t i = 0; i < func->params.size(); i++) {
-    const Var& param = func->params[i];
-    if (!param->ty.as<TensorTypeNode>()) {
-      context.push_back(WithDef(param, ppath->ArrayItem(i)));
-    }
-  }
-
-  for (size_t i = 0; i < func->params.size(); i++) {
-    if (auto opt = func->params[i].as<TensorVar>()) {
-      auto buf = opt.value();
-      auto buf_path = ppath->ArrayItem(i)->Attr("ty");
-
-      for (auto& def : WithMatchTensorDefs(buf, buf_path)) {
-        context.push_back(std::move(def));
-      }
-    }
-  }
-
-  // Only after all the implicit definitions have been visited can we
-  // visit the tensor definition itself.
-  for (size_t i = 0; i < func->params.size(); i++) {
-    if (auto opt = func->params[i].as<TensorVar>()) {
-      context.push_back(WithDef(opt.value().var(), ppath->ArrayItem(i)));
-    }
-  }
-
-  bind_scope_.WithNewScope([&]() { Visit(func->body, path->Attr("body")); });
-
-  while (context.size()) context.pop_back();
-}
-
-void TIRVisitorWithPath::Visit(const Type& type, AccessPath path) {
-  Visit(ffi::AnyView(type), path);
-}
-
-void TIRVisitorWithPath::Visit(ffi::AnyView obj, AccessPath path) {
-  if (obj == nullptr) return;
-  if (auto expr = obj.as<Expr>()) {
-    Visit(expr.value(), path);
-  } else if (auto array = obj.as<ffi::Array<ffi::Any>>()) {
-    for (size_t i = 0; i < array.value().size(); ++i) {
-      Visit(ffi::AnyView(array.value()[i]), path->ArrayItem(i));
-    }
-  } else if (auto map = obj.as<ffi::Map<ffi::Any, ffi::Any>>()) {
-    for (const auto& [key, value] : map.value()) {
-      Visit(ffi::AnyView(value), path->MapItem(key));
-    }
-  } else if (auto object = obj.as<ffi::ObjectRef>()) {
-    ffi::reflection::ForEachFieldInfo(
-        TVMFFIGetTypeInfo(object.value()->type_index()), [&](const 
TVMFFIFieldInfo* field) {
-          
Visit(ffi::AnyView(ffi::reflection::FieldGetter(field)(object.value())),
-                path->Attr(ffi::String(field->name)));
-        });
-  }
-}
-
-// Uses retain their definition's identity without revisiting its type 
metadata.
-void TIRVisitorWithPath::VisitTensorUse(const TensorVar& tensor, AccessPath 
path) {}
-
-void TIRVisitorWithPath::Visit(const TensorRegion& region, AccessPath path) {
-  if (auto tensor = region->source.as<TensorVar>()) {
-    VisitTensorUse(tensor.value(), path->Attr("source"));
-  } else {
-    Visit(region->source, path->Attr("source"));
-  }
-  Visit(region->region, path->Attr("region"));
-}
-
-void TIRVisitorWithPath::Visit(const Range& range, AccessPath path) {
-  Visit(range->min, path->Attr("min"));
-  Visit(range->extent, path->Attr("extent"));
-}
-
-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::Dispatch_(const RegionStmtNode* op, AccessPath path) {
-  Visit(op->args, path->Attr("args"));
-  Visit(ffi::AnyView(op->attrs), path->Attr("attrs"));
-  // All parameter types belong to the enclosing scope, including types with
-  // symbolic dimensions. No body parameter is visible to another parameter's 
type.
-  auto params_path = path->Attr("body_params");
-  for (size_t i = 0; i < op->body_params.size(); ++i) {
-    Visit(op->body_params[i]->ty, params_path->ArrayItem(i)->Attr("ty"));
-  }
-  bind_scope_.WithNewScope([&]() {
-    for (size_t i = 0; i < op->body_params.size(); ++i) {
-      bind_scope_.Current().push_back(
-          WithDef(op->body_params[i], params_path->ArrayItem(i), 
/*visit_type=*/false));
-    }
-    Visit(op->body, path->Attr("body"));
-  });
-  auto results_path = path->Attr("result_vars");
-  for (size_t i = 0; i < op->result_vars.size(); ++i) {
-    bind_scope_.Current().push_back(WithDef(op->result_vars[i], 
results_path->ArrayItem(i)));
-  }
-}
-
-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::Dispatch_(const WhileNode* op, AccessPath path) {
-  Visit(op->condition, path->Attr("condition"));
-  bind_scope_.WithNewScope([&]() { Visit(op->body, path->Attr("body")); });
-}
-
-void TIRVisitorWithPath::Dispatch_(const ReturnNode* op, AccessPath path) {
-  Visit(op->value, path->Attr("value"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const BreakNode* op, AccessPath path) {}
-
-void TIRVisitorWithPath::Dispatch_(const ContinueNode* op, AccessPath path) {}
-
-void TIRVisitorWithPath::Dispatch_(const TensorStoreNode* op, AccessPath path) 
{
-  Visit(op->value, path->Attr("value"));
-  VisitTensorUse(op->dest.as_or_throw<TensorVar>(), path->Attr("dest"));
-  Visit(op->indices, path->Attr("indices"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const IfNode* 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::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::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::Dispatch_(const EvaluateNode* op, AccessPath path) {
-  Visit(op->value, path->Attr("value"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const VarNode* op, AccessPath path) {}
-
-void TIRVisitorWithPath::Dispatch_(const TensorLoadNode* op, AccessPath path) {
-  VisitTensorUse(op->source.as_or_throw<tvm::tirx::TensorVar>(), 
path->Attr("source"));
-  Visit(op->indices, path->Attr("indices"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const TensorRegionNode* op, AccessPath 
path) {
-  Visit(ffi::GetRef<TensorRegion>(op), path);
-}
-
-void TIRVisitorWithPath::Dispatch_(const OpaqueExprNode* op, AccessPath path) 
{}
-
-void TIRVisitorWithPath::VisitLambda(const LambdaExprNode* op, AccessPath 
path) {
-  Visit(op->ty, path->Attr("ty"));
-  std::vector<DefContext<Var>> context;
-  for (size_t i = 0; i < op->vars.size(); ++i) {
-    context.push_back(WithDef(op->vars[i], path->Attr("vars")->ArrayItem(i)));
-  }
-  Visit(op->body, path->Attr("body"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const TupleNode* op, AccessPath path) {
-  Visit(op->fields, path->Attr("fields"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const TupleGetItemNode* op, AccessPath 
path) {
-  Visit(op->tuple, path->Attr("tuple"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const prim::LetNode* op, AccessPath path) {
-  Visit(op->value, path->Attr("value"));
-  auto context = WithDef(op->var, path->Attr("var"));
-  Visit(op->body, path->Attr("body"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const CallNode* op, AccessPath path) {
-  if (auto gvar = op->op.as<GlobalVar>()) {
-    Visit(gvar.value(), path->Attr("op"));
-  } else if (op->op.as<OpaqueExprNode>()) {
-    Visit(op->op, path->Attr("op"));
-  }
-  Visit(op->args, path->Attr("args"));
-}
-
-#define DEFINE_BINOP_VISIT_(OP)                                       \
-  void TIRVisitorWithPath::Dispatch_(const OP* op, AccessPath path) { \
-    Visit(op->a, path->Attr("a"));                                    \
-    Visit(op->b, path->Attr("b"));                                    \
-  }
-
-DEFINE_BINOP_VISIT_(prim::AddNode);
-DEFINE_BINOP_VISIT_(prim::SubNode);
-DEFINE_BINOP_VISIT_(prim::MulNode);
-DEFINE_BINOP_VISIT_(prim::DivNode);
-DEFINE_BINOP_VISIT_(prim::ModNode);
-DEFINE_BINOP_VISIT_(prim::FloorDivNode);
-DEFINE_BINOP_VISIT_(prim::FloorModNode);
-DEFINE_BINOP_VISIT_(prim::MinNode);
-DEFINE_BINOP_VISIT_(prim::MaxNode);
-DEFINE_BINOP_VISIT_(prim::EQNode);
-DEFINE_BINOP_VISIT_(prim::NENode);
-DEFINE_BINOP_VISIT_(prim::LTNode);
-DEFINE_BINOP_VISIT_(prim::LENode);
-DEFINE_BINOP_VISIT_(prim::GTNode);
-DEFINE_BINOP_VISIT_(prim::GENode);
-DEFINE_BINOP_VISIT_(prim::AndNode);
-DEFINE_BINOP_VISIT_(prim::OrNode);
-
-DEFINE_BINOP_VISIT_(prim::LShiftNode);
-DEFINE_BINOP_VISIT_(prim::RShiftNode);
-DEFINE_BINOP_VISIT_(prim::BitwiseAndNode);
-DEFINE_BINOP_VISIT_(prim::BitwiseOrNode);
-DEFINE_BINOP_VISIT_(prim::BitwiseXorNode);
-
-#undef DEFINE_BINOP_VISIT_
-
-void TIRVisitorWithPath::Dispatch_(const IntImmNode* op, AccessPath path) {}
-void TIRVisitorWithPath::Dispatch_(const FloatImmNode* op, AccessPath path) {}
-void TIRVisitorWithPath::Dispatch_(const StringImmNode* op, AccessPath path) {}
-void TIRVisitorWithPath::Dispatch_(const DataTypeImmNode* op, AccessPath path) 
{}
-void TIRVisitorWithPath::Dispatch_(const GenericConstNode* op, AccessPath 
path) {}
-
-void TIRVisitorWithPath::Dispatch_(const prim::CastNode* op, AccessPath path) {
-  Visit(op->value, path->Attr("value"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const prim::BitwiseNotNode* op, AccessPath 
path) {
-  Visit(op->a, path->Attr("a"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const prim::NotNode* op, AccessPath path) {
-  Visit(op->a, path->Attr("a"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const prim::SelectNode* op, AccessPath 
path) {
-  Visit(op->condition, path->Attr("condition"));
-  Visit(op->true_value, path->Attr("true_value"));
-  Visit(op->false_value, path->Attr("false_value"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const prim::RampNode* op, AccessPath path) {
-  Visit(op->base, path->Attr("base"));
-  Visit(op->stride, path->Attr("stride"));
-  Visit(op->lanes, path->Attr("lanes"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const prim::ShuffleNode* op, AccessPath 
path) {
-  Visit(op->indices, path->Attr("indices"));
-  Visit(op->vectors, path->Attr("vectors"));
-}
-
-void TIRVisitorWithPath::Dispatch_(const prim::BroadcastNode* op, AccessPath 
path) {
-  Visit(op->value, path->Attr("value"));
-  Visit(op->lanes, path->Attr("lanes"));
-}
-
-}  // namespace tirx
-}  // namespace tvm
diff --git a/src/tirx/ir/tir_visitor_with_path.h 
b/src/tirx/ir/tir_visitor_with_path.h
deleted file mode 100644
index c86ae7fa72..0000000000
--- a/src/tirx/ir/tir_visitor_with_path.h
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/*!
- * \file tirx/ir/tir_visitor_with_path.h
- * \brief Provide a TIR visitor that tracks the current location
- */
-#ifndef TVM_TIRX_IR_TIR_VISITOR_WITH_PATH_H_
-#define TVM_TIRX_IR_TIR_VISITOR_WITH_PATH_H_
-
-#include <tvm/ffi/extra/structural_visit.h>
-#include <tvm/ir/expr_functor.h>
-#include <tvm/ir/module.h>
-#include <tvm/ir/scope_stack.h>
-#include <tvm/runtime/logging.h>
-#include <tvm/tirx/stmt.h>
-#include <tvm/tirx/stmt_functor.h>
-
-#include <exception>
-#include <optional>
-#include <unordered_set>
-#include <utility>
-#include <variant>
-#include <vector>
-
-namespace tvm {
-namespace tirx {
-
-/*! \brief Visit TIR while tracking the ffi::reflection::AccessPath */
-class TIRVisitorWithPath : protected ExprFunctor<void(const Expr&, 
ffi::reflection::AccessPath)>,
-                           protected StmtFunctor<void(const Stmt&, 
ffi::reflection::AccessPath)> {
- public:
-  TIRVisitorWithPath() = default;
-  template <typename TObjectRef>
-  void operator()(TObjectRef&& obj) {
-    Visit(std::forward<TObjectRef>(obj), ffi::reflection::AccessPath::Root());
-  }
-
- protected:
-  using StmtVisitor = StmtFunctor<void(const Stmt&, 
ffi::reflection::AccessPath)>;
-  using VTable = StmtVisitor::VTable;
-  explicit TIRVisitorWithPath(const VTable* vtable) : StmtVisitor(vtable) {}
-  static void InitVTable(VTable* vtable) { StmtVisitor::InitVTable(vtable); }
-  // Delegate to ExprFunctor::Dispatch for PrimExpr, and any subclasses
-  virtual inline void Visit(const PrimExpr& obj, ffi::reflection::AccessPath 
path) {
-    Dispatch(obj, path);
-  }
-  // Core Call stores arguments as Expr, including pointer-typed Vars.
-  virtual inline void Visit(const Expr& obj, ffi::reflection::AccessPath path) 
{
-    if (auto prim = obj.as<PrimExpr>()) {
-      Visit(prim.value(), path);
-    } else if (auto* constant = obj.as<GenericConstNode>()) {
-      Dispatch_(constant, path);
-    } else if (auto* str = obj.as<StringImmNode>()) {
-      Dispatch_(str, path);
-    } else if (auto* str = obj.as<DataTypeImmNode>()) {
-      Dispatch_(str, path);
-    } else if (obj.as<OpNode>()) {
-      // Registered operations are atomic expression inputs.
-    } else if (auto global_var = obj.as<GlobalVar>()) {
-      Visit(global_var.value(), path);
-    } else if (auto* var = obj.as<VarNode>()) {
-      Dispatch_(var, path);
-    } else if (auto* call = obj.as<CallNode>()) {
-      Dispatch_(call, path);
-    } else if (auto* tuple = obj.as<TupleNode>()) {
-      Dispatch_(tuple, path);
-    } else if (auto* tuple_get_item = obj.as<TupleGetItemNode>()) {
-      Dispatch_(tuple_get_item, path);
-    } else if (auto* tensor_region = obj.as<TensorRegionNode>()) {
-      Dispatch_(tensor_region, path);
-    } else if (auto* lambda = obj.as<LambdaExprNode>()) {
-      VisitLambda(lambda, path);
-    } else if (obj.as<OpaqueExprNode>()) {
-      Dispatch(obj, path);
-    } else {
-      TVM_FFI_THROW(TypeError) << "Unsupported non-primitive TIR expression " 
<< obj.GetTypeKey();
-    }
-  }
-  // Delegate to StmtFunctor::Dispatch for Stmt, and any subclasses
-  virtual inline void Visit(const Stmt& obj, ffi::reflection::AccessPath path) 
{
-    Dispatch(obj, path);
-  }
-
-  // Visit a tensor at a use site (TensorLoad, TensorStore, reads/writes).
-  // By default, does not re-visit tensor fields (shape, strides, elem_offset),
-  // as those are visited at the definition site via EnterDef.
-  virtual void VisitTensorUse(const TensorVar& obj, 
ffi::reflection::AccessPath path);
-
-  // Visit type metadata through its reflected fields, preserving source 
access paths.
-  virtual void Visit(const Type& obj, ffi::reflection::AccessPath path);
-  virtual void Visit(ffi::AnyView obj, ffi::reflection::AccessPath path);
-  void VisitLambda(const LambdaExprNode* op, ffi::reflection::AccessPath path);
-
-  // Visitors for TIR constructs that are neither PrimExpr nor Stmt
-  virtual void Visit(const IRModule& obj, ffi::reflection::AccessPath path);
-  virtual void Visit(const Function& obj, ffi::reflection::AccessPath path);
-  virtual void Visit(const GlobalVar& obj, ffi::reflection::AccessPath path) {}
-  virtual void Visit(const Range& obj, ffi::reflection::AccessPath path);
-  virtual void Visit(const TensorRegion& obj, ffi::reflection::AccessPath 
path);
-
-  // Called when entering/exiting the scope of a GlobalVar definition.
-  virtual void EnterDef(const GlobalVar& var, ffi::reflection::AccessPath 
path) {}
-  virtual void ExitDef(const GlobalVar& var, ffi::reflection::AccessPath path) 
{}
-
-  // Called when entering/exiting the scope of a tvm::Var definition.
-  virtual void EnterDef(const Var& var, ffi::reflection::AccessPath path) {}
-  virtual void ExitDef(const Var& var, ffi::reflection::AccessPath path) {}
-
-  // Utility to visit an array of nodes
-  template <typename T>
-  inline void Visit(const ffi::Array<T>& arr, ffi::reflection::AccessPath 
path) {
-    for (size_t i = 0; i < arr.size(); i++) {
-      Visit(arr[i], path->ArrayItem(i));
-    }
-  }
-
-  // Utility to visit an optional node nodes
-  template <typename T>
-  inline void Visit(const ffi::Optional<T>& opt, ffi::reflection::AccessPath 
path) {
-    if (opt) {
-      Visit(opt.value(), path);
-    }
-  }
-
-  using StmtFunctor::Dispatch;
-  void Dispatch_(const BindNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const RegionStmtNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const IfNode* 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 TensorStoreNode* 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;
-
-  using ExprFunctor::Dispatch;
-  void Dispatch_(const VarNode* op, ffi::reflection::AccessPath path) override;
-  void Dispatch_(const TensorLoadNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const TensorRegionNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const OpaqueExprNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const TupleNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const TupleGetItemNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const CallNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const IntImmNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const FloatImmNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const StringImmNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const DataTypeImmNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const GenericConstNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::LetNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::AddNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::SubNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::MulNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::DivNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::ModNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::FloorDivNode* op, ffi::reflection::AccessPath 
path) override;
-  void Dispatch_(const prim::FloorModNode* op, ffi::reflection::AccessPath 
path) override;
-  void Dispatch_(const prim::MinNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::MaxNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::EQNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::NENode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::LTNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::LENode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::GTNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::GENode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::AndNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::OrNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::CastNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::LShiftNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::RShiftNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::BitwiseAndNode* op, ffi::reflection::AccessPath 
path) override;
-  void Dispatch_(const prim::BitwiseOrNode* op, ffi::reflection::AccessPath 
path) override;
-  void Dispatch_(const prim::BitwiseXorNode* op, ffi::reflection::AccessPath 
path) override;
-  void Dispatch_(const prim::BitwiseNotNode* op, ffi::reflection::AccessPath 
path) override;
-  void Dispatch_(const prim::NotNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::SelectNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::RampNode* op, ffi::reflection::AccessPath path) 
override;
-  void Dispatch_(const prim::BroadcastNode* op, ffi::reflection::AccessPath 
path) override;
-  void Dispatch_(const prim::ShuffleNode* op, ffi::reflection::AccessPath 
path) override;
-
-  // Utility to call EnterDef/ExitDef.  Used in the implementation of
-  // WithDef.
-  template <typename T>
-  class DefContext {
-   public:
-    DefContext(DefContext&& other) : obj_(other.obj_) { 
swap(std::move(other)); }
-    DefContext& operator=(DefContext&& other) {
-      swap(std::move(other));
-      return *this;
-    }
-
-    DefContext(const DefContext&) = delete;
-    DefContext& operator=(const DefContext&) = delete;
-    ~DefContext() noexcept(false) {
-      // Checks performed when a definition goes out of scope may
-      // raise an exception.  If the stack is already being unwound
-      // due to another exception being thrown, this would cause a
-      // segfault and terminate the program.  By checking that no
-      // additional exceptions have been thrown between the
-      // construction of the DefContext and the destruction, we avoid
-      // this case and allow the first error to propagate upward.
-      if (self_ && std::uncaught_exceptions() == uncaught_exceptions_) {
-        self_->in_scope_definitions_.erase(obj_);
-        self_->ExitDef(obj_, path_);
-      }
-    }
-
-   private:
-    friend class TIRVisitorWithPath;
-
-    DefContext(TIRVisitorWithPath* self, T obj, ffi::reflection::AccessPath 
path, bool visit_type)
-        : self_(self), obj_(obj), path_(path), 
uncaught_exceptions_(std::uncaught_exceptions()) {
-      if (auto var = obj_.template as<Var>(); var && visit_type) {
-        self_->Visit(var.value()->ty, path_->Attr("ty"));
-      }
-      self_->in_scope_definitions_.insert(obj_);
-      self_->EnterDef(obj_, path_);
-    }
-
-    void swap(DefContext&& other) {
-      std::swap(this->self_, other.self_);
-      std::swap(this->obj_, other.obj_);
-      std::swap(this->path_, other.path_);
-      std::swap(this->uncaught_exceptions_, other.uncaught_exceptions_);
-    }
-
-    TIRVisitorWithPath* self_{nullptr};
-    T obj_;
-    ffi::reflection::AccessPath path_{ffi::reflection::AccessPath::Root()};
-    int uncaught_exceptions_{-1};
-  };
-
-  // Utility to track the scope of a node's definition.
-  template <typename T>
-  DefContext<T> WithDef(T obj, ffi::reflection::AccessPath path, bool 
visit_type = true) {
-    return DefContext(this, obj, path, visit_type);
-  }
-
-  /* \brief Utility to track the scope of a node's definition. */
-  template <typename T>
-  std::optional<DefContext<T>> WithDefIfUndefined(T obj, 
ffi::reflection::AccessPath path) {
-    if (in_scope_definitions_.count(obj)) {
-      return std::nullopt;
-    } else {
-      return WithDef(obj, path);
-    }
-  }
-
-  std::vector<DefContext<Var>> WithMatchTensorDefs(TensorVar buf,
-                                                   ffi::reflection::AccessPath 
path) {
-    std::vector<DefContext<Var>> context;
-
-    auto try_visit_implicit_var_def = [this, &context](const Expr& expr,
-                                                       
ffi::reflection::AccessPath path) {
-      if (auto opt = expr.as<Var>()) {
-        auto var = opt.value();
-        if (auto var_def = WithDefIfUndefined(var, path)) {
-          context.push_back(std::move(var_def).value());
-        }
-      }
-    };
-
-    // A Tensor shape is a match scope.  The first shape expression that
-    // contains an undefined Var defines it, even when the expression is
-    // compound (for example, `n + 1`).  Later expressions then see the same
-    // Var in `in_scope_definitions_` and reuse it.
-    auto shape_path = path->Attr("shape");
-    for (size_t i = 0; i < buf->shape.size(); i++) {
-      auto dim_path = shape_path->ArrayItem(i);
-      auto walk_fn = [this, &context, &dim_path](const Var& var) -> 
ffi::Expected<ffi::WalkResult> {
-        if (auto var_def = WithDefIfUndefined(var, dim_path)) {
-          context.push_back(std::move(var_def).value());
-        }
-        return ffi::WalkResult::Advance();
-      };
-      ffi::StructuralWalk<ffi::WalkOrder::kPostOrder>(buf->shape[i], walk_fn);
-    }
-
-    auto strides_path = path->Attr("strides");
-    for (size_t i = 0; i < buf->strides.size(); i++) {
-      try_visit_implicit_var_def(buf->strides[i], strides_path->ArrayItem(i));
-    }
-    try_visit_implicit_var_def(buf->elem_offset, path->Attr("elem_offset"));
-
-    return context;
-  }
-
-  std::unordered_set<ffi::ObjectRef, ffi::ObjectPtrHash, ffi::ObjectPtrEqual> 
in_scope_definitions_;
-
-  /*! \brief Scope stack for Bind variable definitions.
-   *
-   * Body-carrying statements (For, If, etc.) push a new scope.
-   * BindNode pushes its WithDef into the current scope.  When the
-   * scope exits, all Bind defs are cleaned up automatically.
-   */
-  ScopeStack<std::vector<DefContext<Var>>> bind_scope_;
-};
-
-namespace {
-
-template <typename DerivedVerifier, typename PathVisitor = TIRVisitorWithPath>
-class Verifier : protected PathVisitor {
- public:
-  template <typename TirNodeRef>
-  static bool Verify(const TirNodeRef& node, bool assert_on_error) {
-    DerivedVerifier verifier(assert_on_error);
-    verifier(node);
-    return !verifier.has_error_;
-  }
-
- protected:
-  explicit Verifier(bool assert_on_error) : assert_on_error_(assert_on_error) 
{}
-  void DispatchDefault_(const ffi::Object* op, ffi::reflection::AccessPath 
path) override {
-    Verify(false) << "TIR verifier does not support statement " << 
op->GetTypeKey() << " at "
-                  << path;
-  }
-
-  /* \brief Helper class to handle the bool-or-assert handles
-   *
-   * Each verifier can either return a boolean, or assert on failure.
-   * To avoid needing to duplicate this logic at every step, the
-   * Verify() method can be used.  Similar to `LOG(FATAL)` or
-   * `LOG(DEBUG)`, it returns an object that can accept streamed
-   * context information.
-   *
-   * If the error should be raised, then the context is collected
-   * identically to `LOG(FATAL)`.  If a boolean is returned, or if the
-   * condition passes, then the streamed context is discarded.
-   *
-   * Usage:
-   *
-   *     Verify(value == expected_value)
-   *            << "ValueError: " << value
-   *            << " was not the expected value of " << expected_value;
-   */
-  class VerifyStream {
-   public:
-    explicit VerifyStream(bool log_fatal) {
-      if (log_fatal) {
-        log_.emplace();
-      }
-    }
-
-    VerifyStream(const VerifyStream&) = delete;
-    VerifyStream& operator=(const VerifyStream&) = delete;
-    VerifyStream(VerifyStream&& other) { std::swap(log_, other.log_); }
-    VerifyStream& operator=(VerifyStream&& other) {
-      std::swap(log_, other.log_);
-      return *this;
-    }
-
-    template <typename T>
-    VerifyStream& operator<<(T&& t) {
-      if (log_.has_value()) {
-        log_.value() << std::forward<T>(t);
-      }
-      return *this;
-    }
-
-    ~VerifyStream() noexcept(false) {
-      if (log_.has_value()) {
-        LOG(FATAL) << log_->str();
-      }
-    }
-
-    std::optional<std::ostringstream> log_{std::nullopt};
-  };
-
-  // TODO(Lunderberg): Add the filename/linenum with
-  // std::source_location when C++20 is available.
-  VerifyStream Verify(bool condition) {
-    has_error_ = has_error_ || !condition;
-    return VerifyStream(!condition && assert_on_error_);
-  }
-
-  bool assert_on_error_;
-  bool has_error_{false};
-};
-
-}  // namespace
-
-}  // namespace tirx
-}  // namespace tvm
-#endif  // TVM_TIR_IR_TIR_VISITOR_WITH_PATH_H_
diff --git a/src/tirx/tile/lower.cc b/src/tirx/tile/lower.cc
index aaabf822d5..7a3b436851 100644
--- a/src/tirx/tile/lower.cc
+++ b/src/tirx/tile/lower.cc
@@ -49,29 +49,125 @@
 #include <vector>
 
 #include "../../backend/cuda/tile/exec_analysis.h"
-#include "../ir/tir_visitor_with_path.h"
 
 namespace tvm {
 namespace tirx {
 
 namespace {
 
-class NoOpCallVerifier : public Verifier<NoOpCallVerifier> {
+class NoOpCallVerifier : public StmtExprVisitor {
+  friend class tvm::ObjectVisitor;
+  using Result = ffi::Optional<VisitInterrupt>;
+
  public:
-  using Verifier::Verifier;
+  NoOpCallVerifier() : StmtExprVisitor(GlobalVTable()) {}
+  static bool Verify(const Stmt& stmt) {
+    auto verifier = ffi::make_object<NoOpCallVerifier>();
+    verifier->Visit(stmt);
+    return verifier->valid_;
+  }
 
  private:
-  using Verifier::Visit;
+  using StmtExprVisitor::Visit_;
+  Result Visit(ffi::AnyView value) override {
+    if (value.as<Stmt>() && !GlobalVTable()->CanDispatch(value)) {
+      valid_ = false;
+      return std::nullopt;
+    }
+    if (auto expr = value.as<Expr>(); expr && 
!GlobalVTable()->CanDispatch(value)) {
+      if (value.as<OpaqueExprNode>() || value.as<VarNode>()) return 
std::nullopt;
+      TVM_FFI_VISIT_THROW(TypeError, expr.value())
+          << "Unsupported non-primitive TIR expression " << 
expr.value()->GetTypeKey();
+    }
+    return StmtExprVisitor::Visit(value);
+  }
 
-  void Dispatch_(const CallNode* call, ffi::reflection::AccessPath path) final 
{
+  Result Visit_(const CallNode* call) final {
     if (auto op = call->op.as<Op>(); op && 
Op::HasAttrMap(tile::op_attr::kTileOpDispatch)) {
       static const auto& dispatchers =
           
Op::GetAttrMap<tile::FTileOpDispatch>(tile::op_attr::kTileOpDispatch);
-      Verify(!dispatchers.count(op.value()))
-          << "Unlowered tensor instruction " << op.value()->name << " at " << 
path;
+      valid_ &= !dispatchers.count(op.value());
     }
-    Verifier::Dispatch_(call, path);
+    if (call->op.as<GlobalVar>() || call->op.as<OpaqueExprNode>()) 
this->Visit(call->op);
+    return this->Visit(call->args);
+  }
+
+  Result Visit_(const BindNode* op) override {
+    this->Visit(op->value);
+    return this->Visit(op->var->ty);
+  }
+
+  Result Visit_(const RegionStmtNode* op) override {
+    this->Visit(op->args);
+    this->Visit(op->attrs);
+    for (const auto& param : op->body_params) this->Visit(param->ty);
+    this->Visit(op->body);
+    for (const auto& var : op->result_vars) this->Visit(var->ty);
+    return std::nullopt;
+  }
+
+  Result Visit_(const ForNode* op) override {
+    this->Visit(op->min);
+    this->Visit(op->extent);
+    this->Visit(op->loop_var->ty);
+    return this->Visit(op->body);
+  }
+
+  Result Visit_(const prim::LetNode* op) override {
+    this->Visit(op->value);
+    this->Visit(op->var->ty);
+    return this->Visit(op->body);
+  }
+
+  Result Visit_(const LambdaExprNode* op) {
+    this->Visit(op->ty);
+    for (const auto& var : op->vars) this->Visit(var->ty);
+    return this->Visit(op->body);
+  }
+
+  Result Visit_(const TensorLoadNode* op) override { return 
this->Visit(op->indices); }
+
+  Result Visit_(const TensorStoreNode* op) override {
+    this->Visit(op->value);
+    return this->Visit(op->indices);
+  }
+
+  Result Visit_(const TensorRegionNode* op) override {
+    if (!op->source.as<TensorVar>()) this->Visit(op->source);
+    return this->Visit(op->region);
+  }
+
+  Result Visit_(const prim::RampNode* op) override {
+    this->Visit(op->base);
+    this->Visit(op->stride);
+    return this->Visit(op->lanes);
+  }
+
+  Result Visit_(const prim::BroadcastNode* op) override {
+    this->Visit(op->value);
+    return this->Visit(op->lanes);
+  }
+
+  Result Visit_(const GenericConstNode* op) override { return std::nullopt; }
+
+  Result Visit_(const AssertStmtNode* op) override {
+    this->Visit(op->condition);
+    this->Visit(op->error_kind);
+    return this->Visit(op->message_parts);
   }
+
+  static const VTable* GlobalVTable() {
+    static const VTable table = [] {
+      VTable table;
+      StmtExprVisitor::InitVTable(&table);
+      SetDispatch<NoOpCallVerifier, LambdaExprNode>(&table);
+      table.Finalize();
+      return table;
+    }();
+    return &table;
+  }
+
+  bool valid_{true};
 };
 
 class TileDispatcher : public StmtExprMutator {
@@ -543,7 +639,7 @@ Pass TileDispatch() {
     Target target = ResolveTarget(f);
     auto* n = f.CopyOnWrite();
     n->body = TileDispatcher::LowerOpCalls(n->body.value(), target);
-    if (!NoOpCallVerifier::Verify(n->body.value(), false)) {
+    if (!NoOpCallVerifier::Verify(n->body.value())) {
       LOG(FATAL) << "Failed to lower the TIRx program: " << f;
     }
     return f;

Reply via email to