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


The following commit(s) were added to refs/heads/main by this push:
     new 42d4be73 [REFACTOR][EXTRA] Optimize StructuralMap engine (#741)
42d4be73 is described below

commit 42d4be739fc452651b808f3cc6f2c87c17847b27
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Sep 4 20:48:27 2026 -0400

    [REFACTOR][EXTRA] Optimize StructuralMap engine (#741)
    
    This PR improves the `StructuralMap` engine by bringing lambda callback
    style to direct calling style. We also internally used the raw
    `TVMFFIAny` ABI path so engine overhead is minimized in the default
    path.
    
    Main changes:
    
    - **Direct calling style.** The callback chain was continuation-passing,
    a lambda nested per link. The mutator now owns its callbacks and tests
    them with a `(... || ...)` fold; selection, identity remap, descent, and
    invocation are one straight-line function.
    - **Raw `TVMFFIAny` on the ABI boundary.** Hooks are C-ABI function
    pointers returning `TVMFFIAny`, a 16-byte POD that passes in registers,
    while `Expected<Any>` is classified MEMORY and forces the result to the
    stack. Descent through an unmatched node is that boundary, so it now
    stays raw end to end. Hook bodies are unchanged:
    `TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN` still takes a typed `Expected<Any>`
    and moves out only on return.
    - **Split static and dynamic selection.** `StructuralMapMutatorObj`
    (header) and `StructuralMapDynMutatorObj` (`.cc`) now share only the
    identity remap. This also removes a hazard: the dynamic form kept its
    selected `Function` in mutable state, where a post-order walk could
    descend into a matching child and overwrite the parent's selection.
    - **Error context is attached by the engine, not by hooks.** The engine
    names a node where it dispatches into that node, giving one frame per
    node instead of two on some paths. Both `MAYBE_EARLY_RETURN` macros lose
    their node argument, and the visit engine no longer routes its own walk
    through the hook-facing macro.
    - **API.** Removes `TVM_FFI_S_MUTATE_ASSIGN_FROM`,
    `SMutateResultUnchanged`, `AssignOrReturnHelper`, and
    `MutateWithIdentityRemapExpected`. Adds
    `TVM_FFI_UNSAFE_S_MUTATE_ASSIGN_OR_RETURN_SKIP_CHECK` for hooks that
    have measured the type check to matter; it is UB on a wrong-typed result
    and has no call sites here. Also fixes `AnyUnsafe::CheckAnyStrict`,
    which was missing the `Any` special case both of its siblings had.
    - **Behavior change.** When descent hands back a node whose type no
    longer matches the link that selected it, the engine reports a
    `TypeError` instead of passing the node through. Default mutation must
    preserve a node's type, so this only fires when a hook has broken that
    invariant. Match-before-descent ordering, identity-remap entry and exit,
    and the pre-order in-place rules are unchanged.
---
 include/tvm/ffi/any.h                       |   7 +-
 include/tvm/ffi/expected.h                  |  23 -
 include/tvm/ffi/extra/structural_mutate.h   | 782 ++++++++++++++++------------
 include/tvm/ffi/extra/structural_visit.h    |  66 ++-
 include/tvm/ffi/extra/visit_error_context.h |  38 ++
 src/ffi/extra/structural_mutate.cc          | 297 ++++++++---
 src/ffi/extra/structural_visit.cc           |   4 +-
 tests/cpp/extra/test_structural_mutate.cc   |  25 +
 tests/cpp/extra/test_structural_visit.cc    |  25 +
 tests/cpp/testing_object.h                  |  24 +-
 10 files changed, 838 insertions(+), 453 deletions(-)

diff --git a/include/tvm/ffi/any.h b/include/tvm/ffi/any.h
index 3d203df3..219ab47a 100644
--- a/include/tvm/ffi/any.h
+++ b/include/tvm/ffi/any.h
@@ -609,7 +609,12 @@ struct AnyUnsafe : public ObjectUnsafe {
 
   template <typename T>
   TVM_FFI_INLINE static bool CheckAnyStrict(const Any& ref) {
-    return TypeTraits<T>::CheckAnyStrict(&(ref.data_));
+    if constexpr (!std::is_same_v<T, Any>) {
+      return TypeTraits<T>::CheckAnyStrict(&(ref.data_));
+    } else {
+      // Any holds any value, so there is nothing to check against.
+      return true;
+    }
   }
 
   template <typename T>
diff --git a/include/tvm/ffi/expected.h b/include/tvm/ffi/expected.h
index bd7417fa..6ebd9ab5 100644
--- a/include/tvm/ffi/expected.h
+++ b/include/tvm/ffi/expected.h
@@ -348,29 +348,6 @@ class Expected<void> {
 
 namespace details {
 
-// Helper for TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN.
-class AssignOrReturnHelper {
- public:
-  TVM_FFI_INLINE explicit AssignOrReturnHelper(Any&& data) : 
data_(std::move(data)) {}
-
-  template <typename T>
-  TVM_FFI_INLINE Expected<T> TryMove() && {
-    if constexpr (!std::is_same_v<T, Any>) {
-      const TVMFFIAny* data = AnyUnsafe::TVMFFIAnyPtrFromAny(data_);
-      if (TVM_FFI_PREDICT_FALSE(!TypeTraits<T>::CheckAnyStrict(data))) {
-        return Unexpected(Error("TypeError",
-                                "Cannot treat type `" + 
TypeTraits<T>::GetMismatchTypeInfo(data) +
-                                    "` as type `" + TypeTraits<T>::TypeStr() + 
"`",
-                                ""));
-      }
-    }
-    return AnyUnsafe::MoveFromAnyAfterCheck<T>(std::move(data_));
-  }
-
- private:
-  Any data_;
-};
-
 /*!
  * \brief Unsafe raw-storage helpers for Expected.
  *
diff --git a/include/tvm/ffi/extra/structural_mutate.h 
b/include/tvm/ffi/extra/structural_mutate.h
index d2d04aa1..f2164c09 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -212,27 +212,9 @@ class StructuralMutatorObj : public Object {
    *       responsible for variable-remap lookup and insertion when it 
represents a FreeVar or DAG
    *       identity. Automatic remapping applies only to the reflected 
fallback.
    */
+
   TVM_FFI_INLINE Expected<Any> DefaultMutateExpected(AnyView value) noexcept {
-    int32_t type_index = value.type_index();
-    static reflection::TypeAttrColumn 
column(reflection::type_attr::kStructuralMutate);
-    AnyView attr = column[type_index];
-    if (attr.type_index() != TypeIndex::kTVMFFINone) {
-      if (attr.type_index() == TypeIndex::kTVMFFIOpaquePtr) {
-        auto* hook = reinterpret_cast<FStructuralMutate>(attr.cast<void*>());
-        return details::ExpectedUnsafe::MoveFromTVMFFIAny<Any>((*hook)(this, 
value));
-      }
-      if (attr.type_index() == TypeIndex::kTVMFFIFunction) {
-        return attr.cast<Function>().CallExpected<Any>(this, value);
-      }
-      return Unexpected(Error(
-          "TypeError", "__s_mutate__ must be an opaque function pointer or 
ffi.Function", ""));
-    }
-    if (type_index < TypeIndex::kTVMFFIStaticObjectBegin) {
-      return Any(value);
-    }
-    return MutateWithIdentityRemapExpected(value, [&]() -> Expected<Any> {
-      return details::MutateReflectedFieldsExpected(this, value);
-    });
+    return 
details::ExpectedUnsafe::MoveFromTVMFFIAny<Any>(DefaultMutateRaw(value));
   }
 
   /*!
@@ -248,18 +230,7 @@ class StructuralMutatorObj : public Object {
    *       \ref DefaultMutateExpected.
    */
   TVM_FFI_INLINE Expected<Any> DefaultMaybeInplaceMutateExpected(AnyView 
value) noexcept {
-    int32_t type_index = value.type_index();
-    static reflection::TypeAttrColumn maybe_inplace_mutate_column(
-        reflection::type_attr::kStructuralMaybeInplaceMutate);
-    AnyView maybe_inplace_mutate_attr = 
maybe_inplace_mutate_column[type_index];
-    if (maybe_inplace_mutate_attr.type_index() == TypeIndex::kTVMFFIOpaquePtr) 
{
-      auto* hook = 
reinterpret_cast<FStructuralMutate>(maybe_inplace_mutate_attr.cast<void*>());
-      return details::ExpectedUnsafe::MoveFromTVMFFIAny<Any>((*hook)(this, 
value));
-    }
-    if (maybe_inplace_mutate_attr.type_index() == TypeIndex::kTVMFFIFunction) {
-      return 
maybe_inplace_mutate_attr.cast<Function>().CallExpected<Any>(this, value);
-    }
-    return DefaultMutateExpected(value);
+    return 
details::ExpectedUnsafe::MoveFromTVMFFIAny<Any>(DefaultMaybeInplaceMutateRaw(value));
   }
 
   /*!
@@ -332,48 +303,139 @@ class StructuralMutatorObj : public Object {
 
  protected:
   /*!
-   * \brief Mutate a FreeVar or DAG identity once and reuse its final result.
+   * \brief Diagnostic for a malformed ``__s_mutate__`` registration.
    *
-   * \tparam Mutation Nullary callable returning ``Expected<Any>``.
-   * \param value The borrowed value to mutate.
-   * \param mutation The complete mutation to apply on a cache miss.
-   * \return The cached or newly computed owning result, or an Error.
+   * Kept out of line and cold: it can only fire for a type whose registered 
attribute is
+   * neither an opaque function pointer nor an ffi.Function, so it is 
unreachable for any
+   * correctly registered type. Inlined, its three string literals and Error 
construction
+   * land in the traversal's hot path for no reason.
    */
-  template <typename Mutation>
-  TVM_FFI_INLINE Expected<Any> MutateWithIdentityRemapExpected(AnyView value,
-                                                               Mutation&& 
mutation) noexcept {
-    int32_t type_index = value.type_index();
-    if (type_index < TypeIndex::kTVMFFIStaticObjectBegin) {
-      return mutation();
+  TVM_FFI_COLD_CODE static Expected<Any> BadStructuralMutateHookError() 
noexcept {
+    return Unexpected(
+        Error("TypeError", "__s_mutate__ must be an opaque function pointer or 
ffi.Function", ""));
+  }
+
+  // Convention: the ABI boundary is a raw TVMFFIAny; everything inside a 
callback or hook body
+  // works in Expected<Any> and moves out to TVMFFIAny at that boundary.
+  //
+  // The Raw forms below exist because that boundary is also the default path. 
A hook is a C-ABI
+  // function pointer returning TVMFFIAny, a 16-byte POD that stays in 
registers; wrapping the
+  // result in Expected<Any> would force it to memory, since Expected<Any> is 
not trivially
+  // destructible and is therefore classified MEMORY. Descent through an 
unmatched node calls a
+  // hook and returns its result unchanged, so keeping that path raw removes 
the round trip
+  // entirely. Only a matched callback pays for an Expected<Any>, and it 
materializes an Any
+  // anyway to record its remap entry.
+  //
+  // Engine-internal: subclasses call the Expected forms above.
+  /*! \brief Raw default mutation: attr lookup then hook, favouring the fn-ptr 
case. */
+  TVM_FFI_INLINE TVMFFIAny DefaultMutateRaw(AnyView value) noexcept {
+    static reflection::TypeAttrColumn 
column(reflection::type_attr::kStructuralMutate);
+    AnyView attr = column[value.type_index()];
+    // Exactly one frame per node: hooks propagate errors untouched, and this 
is the engine
+    // dispatching into `value`, so both exits below name it here and nowhere 
else.
+    TVMFFIAny result;
+    if (TVM_FFI_PREDICT_TRUE(attr.type_index() == 
TypeIndex::kTVMFFIOpaquePtr)) {
+      result = 
(*reinterpret_cast<FStructuralMutate>(attr.cast<void*>()))(this, value);
+    } else {
+      result = DefaultMutateRawTail(value, attr);
     }
+    if (TVM_FFI_PREDICT_FALSE(result.type_index == TypeIndex::kTVMFFIError)) {
+      details::UpdateVisitErrorContext(result, value);
+    }
+    return result;
+  }
 
+  // The cold remainder of DefaultMutateRaw, out of line so that 
always-inlined caller stays
+  // small at every inlining site. `attr` is the attribute the caller already 
read, so the
+  // column is never looked up twice.
+  /*!
+   * \brief Whether a node's identity is remappable, so it maps once and 
reuses that result.
+   * \param type_index The node's runtime type index.
+   * \return True for a FreeVar or DAG node.
+   */
+  TVM_FFI_INLINE static bool IsRemappableIdentity(int32_t type_index) noexcept 
{
+    if (type_index < TypeIndex::kTVMFFIStaticObjectBegin) return false;
     const TVMFFITypeInfo* type_info = TVMFFIGetTypeInfo(type_index);
-    bool is_remappable_identity =
-        type_info->metadata != nullptr &&
-        (type_info->metadata->structural_eq_hash_kind == 
kTVMFFISEqHashKindFreeVar ||
-         type_info->metadata->structural_eq_hash_kind == 
kTVMFFISEqHashKindDAGNode);
-    if (!is_remappable_identity) {
-      return mutation();
-    }
+    return type_info->metadata != nullptr &&
+           (type_info->metadata->structural_eq_hash_kind == 
kTVMFFISEqHashKindFreeVar ||
+            type_info->metadata->structural_eq_hash_kind == 
kTVMFFISEqHashKindDAGNode);
+  }
 
-    Expected<Any> mapped_value = VarRemapGetExpected(value);
-    if (TVM_FFI_PREDICT_FALSE(mapped_value.is_err())) {
-      return Unexpected(std::move(mapped_value).error());
+  /*! \brief The cold remainder of DefaultMutateRaw: an ffi.Function hook, or 
no hook at all. */
+  TVMFFIAny DefaultMutateRawTail(AnyView value, AnyView attr) noexcept {
+    if (attr.type_index() != TypeIndex::kTVMFFINone) {
+      // Registered, but as an ffi.Function rather than an opaque pointer.
+      if (attr.type_index() == TypeIndex::kTVMFFIFunction) {
+        return details::ExpectedUnsafe::MoveToTVMFFIAny(
+            attr.cast<Function>().CallExpected<Any>(this, value));
+      }
+      // Registered as neither: a malformed hook.
+      return 
details::ExpectedUnsafe::MoveToTVMFFIAny(BadStructuralMutateHookError());
     }
-    if (details::ExpectedUnsafe::GetData(mapped_value).type_index() != 
TypeIndex::kTVMFFINone) {
-      return mapped_value;
+    // No hook at all. A POD carries through unchanged; an object walks its 
reflected fields.
+    if (value.type_index() < TypeIndex::kTVMFFIStaticObjectBegin) {
+      return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
     }
-
-    Expected<Any> result = mutation();
+    // A FreeVar or DAG node maps once and every later occurrence reuses that 
result, so the
+    // reflected walk runs under an identity remap.
+    const bool remappable = IsRemappableIdentity(value.type_index());
+    if (remappable) {
+      Expected<Any> mapped = VarRemapGetExpected(value);
+      if (TVM_FFI_PREDICT_FALSE(mapped.is_err()) ||
+          details::ExpectedUnsafe::GetData(mapped).type_index() != 
TypeIndex::kTVMFFINone) {
+        return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(mapped));
+      }
+    }
+    Expected<Any> result = details::MutateReflectedFieldsExpected(this, value);
     if (TVM_FFI_PREDICT_FALSE(result.is_err())) {
+      return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+    }
+    if (remappable) {
+      Expected<void> set_result =
+          VarRemapSetExpected(value, details::ExpectedUnsafe::GetData(result));
+      if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
+        return details::ExpectedUnsafe::MoveToTVMFFIAny(
+            Expected<Any>(Unexpected(std::move(set_result).error())));
+      }
+    }
+    return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+  }
+  /*!
+   * \brief Raw default maybe-in-place mutation.
+   *
+   * \note A registered opaque hook is the expected case here too, so the 
attribute is read once
+   *       and every other shape is handed to the out-of-line remainder.
+   */
+  TVM_FFI_INLINE TVMFFIAny DefaultMaybeInplaceMutateRaw(AnyView value) 
noexcept {
+    static reflection::TypeAttrColumn 
column(reflection::type_attr::kStructuralMaybeInplaceMutate);
+    AnyView attr = column[value.type_index()];
+    if (TVM_FFI_PREDICT_TRUE(attr.type_index() == 
TypeIndex::kTVMFFIOpaquePtr)) {
+      // This is the engine dispatching into `value`; hooks propagate errors 
untouched, so the
+      // node is named here. The fall-through re-dispatches the same node 
through
+      // DefaultMutateRaw, which names it there instead -- exactly one frame 
either way.
+      TVMFFIAny result = 
(*reinterpret_cast<FStructuralMutate>(attr.cast<void*>()))(this, value);
+      if (TVM_FFI_PREDICT_FALSE(result.type_index == TypeIndex::kTVMFFIError)) 
{
+        details::UpdateVisitErrorContext(result, value);
+      }
       return result;
     }
-    Expected<void> set_result =
-        VarRemapSetExpected(value, details::ExpectedUnsafe::GetData(result));
-    if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
-      return Unexpected(std::move(set_result).error());
+    return DefaultMaybeInplaceMutateRawTail(value, attr);
+  }
+
+  /*!
+   * \brief The cold remainder of DefaultMaybeInplaceMutateRaw: an 
ffi.Function in-place hook, or
+   *        no in-place hook at all, in which case the ordinary mutate path 
runs.
+   */
+  TVMFFIAny DefaultMaybeInplaceMutateRawTail(AnyView value, AnyView attr) 
noexcept {
+    if (attr.type_index() == TypeIndex::kTVMFFIFunction) {
+      TVMFFIAny result = details::ExpectedUnsafe::MoveToTVMFFIAny(
+          attr.cast<Function>().CallExpected<Any>(this, value));
+      if (TVM_FFI_PREDICT_FALSE(result.type_index == TypeIndex::kTVMFFIError)) 
{
+        details::UpdateVisitErrorContext(result, value);
+      }
+      return result;
     }
-    return result;
+    return DefaultMutateRaw(value);
   }
 
   /*!
@@ -540,31 +602,28 @@ namespace details {
 // Append Node to the mutate error context before returning. Node is required: 
dropping it
 // silently degrades every error message produced below this frame.
 // A raw pointer Node must be non-null; pass nullable nodes as ObjectRef or 
Any so None is skipped.
-#define TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result, Node)                      
                    \
-  do {                                                                         
                    \
-    auto&& tvm_ffi_res_ = (Result);                                            
                    \
-    if (TVM_FFI_PREDICT_FALSE(tvm_ffi_res_.type_index() == 
::tvm::ffi::TypeIndex::kTVMFFIError)) { \
-      auto&& tvm_ffi_mutate_node_owner_ = (Node);                              
                    \
-      ::tvm::ffi::AnyView tvm_ffi_mutate_node_ = tvm_ffi_mutate_node_owner_;   
                    \
-      if (tvm_ffi_mutate_node_.type_index() >= 
::tvm::ffi::TypeIndex::kTVMFFIStaticObjectBegin) {  \
-        ::tvm::ffi::Error tvm_ffi_mutate_err_ = tvm_ffi_res_.error();          
                    \
-        ::tvm::ffi::details::UpdateVisitErrorContext(                          
                    \
-            tvm_ffi_mutate_err_, 
tvm_ffi_mutate_node_.cast<::tvm::ffi::ObjectRef>());              \
-      }                                                                        
                    \
-      return ::std::move(tvm_ffi_res_);                                        
                    \
-    }                                                                          
                    \
+#define TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result)                            
               \
+  do {                                                                         
               \
+    auto&& tvm_ffi_res_ = (Result);                                            
               \
+    if (TVM_FFI_PREDICT_FALSE(tvm_ffi_res_.is_err())) {                        
               \
+      return 
::tvm::ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(::std::move(tvm_ffi_res_));
 \
+    }                                                                          
               \
   } while (0)
 
-#define TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN_IMPL_(Result, Converted, Type, Name, 
ResultExpr, Node) \
+/// \endcond
+
+/// \cond Doxygen_Suppress
+#define TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN_IMPL_(Result, Type, Name, 
ResultExpr)                  \
   auto Result = (ResultExpr); /* NOLINT(bugprone-macro-parentheses) */         
                  \
-  TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result, Node);                           
                  \
-  auto Converted = /* NOLINT(bugprone-macro-parentheses) */                    
                  \
-      ::tvm::ffi::details::AssignOrReturnHelper(                               
                  \
-          ::std::move(::tvm::ffi::details::ExpectedUnsafe::GetData(Result)))   
                  \
-          .template TryMove<Type>();                                           
                  \
-  TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Converted, Node);                        
                  \
-  Type Name = ::tvm::ffi::details::AnyUnsafe::MoveFromAnyAfterCheck<Type>(     
                  \
-      ::std::move(::tvm::ffi::details::ExpectedUnsafe::GetData(Converted)))
+  TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result);                                 
                  \
+  if 
(TVM_FFI_PREDICT_FALSE(!::tvm::ffi::details::AnyUnsafe::CheckAnyStrict<Type>(   
            \
+          ::tvm::ffi::details::ExpectedUnsafe::GetData(Result)))) {            
                  \
+    return 
::tvm::ffi::details::AnyUnsafe::MoveAnyToTVMFFIAny(::tvm::ffi::Any(::tvm::ffi::Error(
 \
+        "TypeError", "structural mutate result does not match the declared 
type", "")));         \
+  }                                                                            
                  \
+  Type Name = /* NOLINT(bugprone-macro-parentheses) */                         
                  \
+      ::tvm::ffi::details::AnyUnsafe::MoveFromAnyAfterCheck<Type>(             
                  \
+          ::std::move(::tvm::ffi::details::ExpectedUnsafe::GetData(Result)))
 /// \endcond
 
 /*!
@@ -572,25 +631,56 @@ namespace details {
  *
  * ``Type`` must be concrete; use a type alias when it contains a top-level 
comma. A type mismatch
  * returns ``Unexpected(TypeError)`` through the surrounding ``Expected`` 
function without
- * throwing. This macro declares ``Name`` into the enclosing scope and must be 
used in a braced
- * block, never as an unbraced control-flow body. A raw pointer node must be 
non-null; pass nullable
- * nodes as ``ObjectRef`` or ``Any`` so ``None`` is skipped when constructing 
error context.
+ * throwing, reported with a fixed string so a correct hook pays only one 
predicted-not-taken
+ * branch per field. This macro declares ``Name`` into the enclosing scope and 
must be used in a
+ * braced block, never as an unbraced control-flow body. A raw pointer node 
must be non-null; pass
+ * nullable nodes as ``ObjectRef`` or ``Any`` so ``None`` is skipped when 
constructing error
+ * context.
  *
  * Example:
  * \code{.cpp}
- * TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(
- *     ObjectRef, child, mutator->MutateExpected(self->child), self);
+ * TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ObjectRef, child, 
mutator->MutateExpected(self->child), self);
  * \endcode
  *
  * \param Type The concrete type of the successful value.
  * \param Name The name of the value declared in the enclosing scope.
  * \param ResultExpr An expression producing the ``Expected`` value to unwrap.
- * \param Node The current mutation node appended to the error context on 
failure.
  */
-#define TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Type, Name, ResultExpr, Node) \
-  TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN_IMPL_(                              \
-      TVM_FFI_STR_CONCAT(tvm_ffi_mutate_result_, __COUNTER__),          \
-      TVM_FFI_STR_CONCAT(tvm_ffi_mutate_converted_, __COUNTER__), Type, Name, 
ResultExpr, Node)
+#define TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Type, Name, ResultExpr)              
                    \
+  
TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN_IMPL_(TVM_FFI_STR_CONCAT(tvm_ffi_mutate_result_,
 __COUNTER__), \
+                                          Type, Name, ResultExpr)
+
+/// \cond Doxygen_Suppress
+#define TVM_FFI_UNSAFE_S_MUTATE_ASSIGN_OR_RETURN_SKIP_CHECK_IMPL_(Result, 
Type, Name, ResultExpr) \
+  auto Result = (ResultExpr); /* NOLINT(bugprone-macro-parentheses) */         
                   \
+  TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result);                                 
                   \
+  TVM_FFI_DCHECK(::tvm::ffi::details::AnyUnsafe::CheckAnyStrict<Type>(         
                   \
+      ::tvm::ffi::details::ExpectedUnsafe::GetData(Result)))                   
                   \
+      << "unchecked structural-mutate assign: result is not of the declared 
type";                \
+  Type Name = /* NOLINT(bugprone-macro-parentheses) */                         
                   \
+      ::tvm::ffi::details::AnyUnsafe::MoveFromAnyAfterCheck<Type>(             
                   \
+          ::std::move(::tvm::ffi::details::ExpectedUnsafe::GetData(Result)))
+/// \endcond
+
+/*!
+ * \brief \ref TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN without the type check.
+ *
+ * Same signature and same error propagation; the difference is only what 
happens to a
+ * successful result that is not of type \p Type.
+ *
+ * The caller must guarantee the result has the declared type; a mismatch is 
undefined behavior
+ * in a release build, and debug builds catch it with ``TVM_FFI_DCHECK``.
+ *
+ * \param Type The concrete type of the successful value.
+ * \param Name The name of the value declared in the enclosing scope.
+ * \param ResultExpr An expression producing the ``Expected`` value to unwrap.
+ */
+#define TVM_FFI_UNSAFE_S_MUTATE_ASSIGN_OR_RETURN_SKIP_CHECK(Type, Name, 
ResultExpr) \
+  TVM_FFI_UNSAFE_S_MUTATE_ASSIGN_OR_RETURN_SKIP_CHECK_IMPL_(                   
     \
+      TVM_FFI_STR_CONCAT(tvm_ffi_mutate_result_, __COUNTER__), Type, Name, 
ResultExpr)
+
+/// \cond Doxygen_Suppress
+/// \endcond
 
 /*!
  * \brief Structural mutator that invokes typed callbacks during recursive 
mapping.
@@ -599,45 +689,40 @@ namespace details {
  * \tparam Dispatch Callback dispatcher.
  *                  \sa StructuralMapCallbackChain
  */
-template <WalkOrder order, typename Dispatch>
-class StructuralMapMutatorObj : public StructuralMutatorObj {
- public:
-  /*!
-   * \brief Construct a callback-aware mutator.
-   * \param dispatch The composed callback dispatcher.
-   */
-  explicit StructuralMapMutatorObj(Dispatch dispatch)
-      : StructuralMutatorObj(VTable()), dispatch_(std::move(dispatch)) {}
+/*!
+ * \brief A runtime table of Function callbacks, usable as a single link.
+ *
+ * The typed links are matched at compile time from their argument type. The 
Python-driven API
+ * instead carries a runtime list keyed by type index, so it appears to the 
mutator as one link
+ * that performs its own lookup. That keeps a single traversal for both 
dispatch strategies.
+ */
 
- private:
-  /*!
-   * \brief Return the shared callback-aware mutator vtable.
-   * \return Pointer to the immutable mutator vtable for this specialization.
-   */
-  static const StructuralMutatorVTable* VTable() {
-    static const StructuralMutatorVTable vtable{
-        &StructuralMapMutatorObj::DispatchMutate,
-        &StructuralMapMutatorObj::DispatchMaybeInplaceMutate,
-        &StructuralMapMutatorObj::DispatchVarRemapGet,
-        &StructuralMapMutatorObj::DispatchVarRemapSet,
-    };
-    return &vtable;
-  }
+/*!
+ * \brief Base of both the static and the dynamic StructuralMapMutator.
+ *
+ * Carries the identity-substitution environment they share. The dispatch 
thunks downcast only
+ * as far as this class, so both reuse them regardless of how they store their 
callbacks.
+ *
+ */
+class StructuralMapMutatorBaseObj : public StructuralMutatorObj {
+ public:
+  explicit StructuralMapMutatorBaseObj(const StructuralMutatorVTable* vtable)
+      : StructuralMutatorObj(vtable) {}
 
+ protected:
   /*!
-   * \brief Dispatch variable-remap lookup through this mutator's vtable.
+   * \brief Dispatch variable-remap lookup through the mutator vtable.
    * \param mutator The erased callback-aware mutator.
    * \param var The borrowed variable identity to look up.
    * \return Raw ``TVMFFIAny`` containing the owning replacement, FFI None, or 
Error.
    */
   static TVMFFIAny DispatchVarRemapGet(StructuralMutatorObj* mutator, AnyView 
var) noexcept {
-    auto* self = static_cast<StructuralMapMutatorObj*>(mutator);
-    Expected<Any> result = self->VarRemapGetImpl(var);
-    return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+    auto* self = static_cast<StructuralMapMutatorBaseObj*>(mutator);
+    return ExpectedUnsafe::MoveToTVMFFIAny(self->VarRemapGetImpl(var));
   }
 
   /*!
-   * \brief Dispatch variable-remap insertion through this mutator's vtable.
+   * \brief Dispatch variable-remap insertion through the mutator vtable.
    * \param mutator The erased callback-aware mutator.
    * \param var The borrowed variable identity to bind.
    * \param mapped_value The borrowed replacement value.
@@ -645,13 +730,57 @@ class StructuralMapMutatorObj : public 
StructuralMutatorObj {
    */
   static TVMFFIAny DispatchVarRemapSet(StructuralMutatorObj* mutator, AnyView 
var,
                                        AnyView mapped_value) noexcept {
-    auto* self = static_cast<StructuralMapMutatorObj*>(mutator);
-    Expected<void> result = self->VarRemapSetImpl(var, mapped_value);
-    return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+    auto* self = static_cast<StructuralMapMutatorBaseObj*>(mutator);
+    return ExpectedUnsafe::MoveToTVMFFIAny(self->VarRemapSetImpl(var, 
mapped_value));
   }
 
   /*!
-   * \brief Look up a replacement in this mutator's identity-substitution 
environment.
+   * \brief Invoke a matched callback with optional def-region context.
+   * \tparam Callback Callable returning a value implicitly convertible to 
``Expected<Any>``.
+   * \tparam Value Type of the converted value passed to the callback.
+   * \param callback The matched callback.
+   * \param value The converted value passed to the callback.
+   * \param kind The active def-region kind.
+   * \return The callback result normalized to ``Expected<Any>``.
+   *
+   * The callback may return a different type than the one that selected the 
link; only the
+   * caller holding a field's static type can check that.
+   */
+  template <typename Callback, typename Value>
+  TVM_FFI_INLINE static Expected<Any> InvokeCallbackLink(Callback& callback, 
Value&& value,
+                                                         TVMFFIDefRegionKind 
kind) {
+    using FuncInfo = FunctionInfo<std::decay_t<Callback>>;
+    static_assert(std::is_convertible_v<typename FuncInfo::RetType, 
Expected<Any>>,
+                  "StructuralMap callbacks must return a replacement value, 
Error, Unexpected, "
+                  "or Expected<U> implicitly convertible to Expected<Any>");
+    try {
+      if constexpr (FuncInfo::num_args == 1) {
+        return callback(std::forward<Value>(value));
+      } else {
+        return callback(std::forward<Value>(value), kind);
+      }
+    } catch (const Error& err) {
+      return Unexpected(err);
+    }
+  }
+
+  /*!
+   * \brief Append \p node to a failed result's mutate error context.
+   * \param result The failed result whose Error is annotated.
+   * \param node The borrowed node to name in the context.
+   */
+  TVM_FFI_COLD_CODE static void UpdateVisitErrorContext(const Expected<Any>& 
result,
+                                                        AnyView node) noexcept 
{
+    // The Error is refcounted, so annotating the local handle annotates the 
object the result
+    // holds. A non-object node has no context to add.
+    if (node.type_index() >= TypeIndex::kTVMFFIStaticObjectBegin) {
+      Error err = result.error();
+      ::tvm::ffi::details::UpdateVisitErrorContext(err, 
node.cast<ObjectRef>());
+    }
+  }
+
+  /*!
+   * \brief Look up a replacement in the identity-substitution environment.
    * \param var The borrowed variable identity to look up.
    * \return The owning replacement, FFI None on a miss, or an Error.
    */
@@ -673,7 +802,7 @@ class StructuralMapMutatorObj : public StructuralMutatorObj 
{
   }
 
   /*!
-   * \brief Record a replacement in this mutator's identity-substitution 
environment.
+   * \brief Record a replacement in the identity-substitution environment.
    * \param var The borrowed variable identity to bind.
    * \param mapped_value The borrowed replacement value.
    * \return Successful completion, or an Error if the binding cannot be 
stored.
@@ -693,6 +822,45 @@ class StructuralMapMutatorObj : public 
StructuralMutatorObj {
     }
   }
 
+ private:
+  /*! \brief Identity-substitution environment, shared across the whole 
traversal. */
+  Map<ObjectRef, Any> var_remap_;
+};
+
+/*!
+ * \brief Structural mutator that invokes statically typed callbacks during 
recursive mapping.
+ *
+ * Each callback is an ordinary callable and the engine selects it on its 
first argument's
+ * type, so selection is a compile-time-known ``as<TSub>()`` on the input node.
+ *
+ * \tparam order Callback placement relative to child mapping.
+ * \tparam Callbacks The callbacks, tested in declaration order.
+ */
+template <WalkOrder order, typename... Callbacks>
+class StructuralMapMutatorObj : public StructuralMapMutatorBaseObj {
+ public:
+  /*!
+   * \brief Construct a callback-aware mutator that owns its callbacks.
+   * \param callbacks The typed callback links, tested in declaration order.
+   */
+  explicit StructuralMapMutatorObj(Callbacks... callbacks)
+      : StructuralMapMutatorBaseObj(VTable()), 
callbacks_(std::move(callbacks)...) {}
+
+ private:
+  /*!
+   * \brief Return the shared callback-aware mutator vtable.
+   * \return Pointer to the immutable mutator vtable for this specialization.
+   */
+  static const StructuralMutatorVTable* VTable() {
+    static const StructuralMutatorVTable vtable{
+        &StructuralMapMutatorObj::DispatchMutate,
+        &StructuralMapMutatorObj::DispatchMaybeInplaceMutate,
+        &StructuralMapMutatorObj::DispatchVarRemapGet,
+        &StructuralMapMutatorObj::DispatchVarRemapSet,
+    };
+    return &vtable;
+  }
+
   /*!
    * \brief Dispatch callback-aware optional in-place mutation through the ABI 
vtable.
    * \param mutator The erased callback-aware mutator.
@@ -702,7 +870,7 @@ class StructuralMapMutatorObj : public StructuralMutatorObj 
{
   static TVMFFIAny DispatchMaybeInplaceMutate(StructuralMutatorObj* mutator,
                                               AnyView value) noexcept {
     auto* self = static_cast<StructuralMapMutatorObj*>(mutator);
-    return 
ExpectedUnsafe::MoveToTVMFFIAny(self->MaybeInplaceMutateImpl(value));
+    return self->MaybeInplaceMutateImplRaw(value);
   }
 
   /*!
@@ -713,227 +881,185 @@ class StructuralMapMutatorObj : public 
StructuralMutatorObj {
    */
   static TVMFFIAny DispatchMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
     auto* self = static_cast<StructuralMapMutatorObj*>(mutator);
-    return ExpectedUnsafe::MoveToTVMFFIAny(self->MutateImpl(value));
+    return self->MutateImplRaw(value);
   }
 
   /*!
-   * \brief Invoke the callback and select mutation according to its result 
and walk order.
+   * \brief Test one link against \p value and, if it matches, mutate the node 
through it.
    *
-   * \param value The borrowed value to mutate.
-   * \return The mutated value or an Error.
+   * \tparam kMaybeInplace Whether the caller may mutate a uniquely owned node 
in place.
+   * \tparam Callback The link's callback type.
+   * \param callback The link's callback.
+   * \param value The borrowed value to test and mutate.
+   * \param out Receives the mutated value or Error when the link matched.
+   * \return Whether the link matched, in which case \p out was written.
    */
-  Expected<Any> MaybeInplaceMutateImpl(AnyView value) noexcept {
-    // The link match is tested before any descent, and the identity remap 
lives
-    // inside the match continuation, so the var-type detection only runs when 
a
-    // callback signature actually matches. Unmatched nodes skip both it and 
the
-    // callback chain.
+  template <bool kMaybeInplace, typename Callback>
+  TVM_FFI_INLINE bool TryLink(Callback& callback, AnyView value, 
Expected<Any>* out) noexcept {
+    using FuncInfo = FunctionInfo<std::decay_t<Callback>>;
+    static_assert(FuncInfo::num_args == 1 || FuncInfo::num_args == 2,
+                  "StructuralMap callbacks must take one argument (value) or 
two arguments "
+                  "(value, def-region kind)");
+    using FirstArg = std::tuple_element_t<0, typename FuncInfo::ArgType>;
+    using TSub = std::remove_cv_t<std::remove_reference_t<FirstArg>>;
+
+    // Deliberately duplicated by StructuralMapDynMutatorObj::TryLink in 
structural_mutate.cc,
+    // which differs only in how a link is found and called; keep the two in 
step.
     //
-    // The ordering is load-bearing: DefaultMutateExpected's reflected fallback
-    // writes its own remap entry for this node, so testing the match after 
descent
-    // would let the remap read that entry, treat the node as already handled, 
and
-    // never run the callback.
-    if constexpr (order == WalkOrder::kPreOrder) {
-      return dispatch_(
-          value, def_region_kind(),
-          [&](auto&& invoke_callback) -> Expected<Any> {
-            return MutateWithIdentityRemapExpected(value, [&]() -> 
Expected<Any> {
-              Expected<Any> callback_result = invoke_callback(value);
-              TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(callback_result, value);
-              // A pre-order result can be mutated in place if unchanged or 
uniquely owned.
-              const Any& mapped_value = 
ExpectedUnsafe::GetData(callback_result);
-              const TVMFFIAny* mapped_data = 
AnyUnsafe::TVMFFIAnyPtrFromAny(mapped_value);
-              const TVMFFIAny input_data = value.CopyToTVMFFIAny();
-              if (mapped_data->type_index != input_data.type_index ||
-                  mapped_data->zero_padding != input_data.zero_padding ||
-                  mapped_data->v_int64 != input_data.v_int64) {
-                const Object* mapped_obj = mapped_value.as<Object>();
-                bool can_mutate_mapped_value_inplace =
-                    mapped_obj != nullptr && mapped_obj->unique();
-                Expected<Any> result = can_mutate_mapped_value_inplace
-                                           ? 
DefaultMaybeInplaceMutateExpected(mapped_value)
-                                           : 
DefaultMutateExpected(mapped_value);
-                TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(result, mapped_value);
-                return result;
-              }
-              Expected<Any> result = DefaultMaybeInplaceMutateExpected(value);
-              TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(result, value);
-              return result;
-            });
-          },
-          [&]() -> Expected<Any> { return 
DefaultMaybeInplaceMutateExpected(value); });
-    } else {
-      return dispatch_(
-          value, def_region_kind(),
-          [&](auto&& invoke_callback) -> Expected<Any> {
-            return MutateWithIdentityRemapExpected(value, [&]() -> 
Expected<Any> {
-              Expected<Any> result = DefaultMaybeInplaceMutateExpected(value);
-              TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(result, value);
-
-              const Any& mapped_value = ExpectedUnsafe::GetData(result);
-              Expected<Any> callback_result = invoke_callback(mapped_value);
-              TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(callback_result, 
mapped_value);
-              return callback_result;
-            });
-          },
-          [&]() -> Expected<Any> { return 
DefaultMaybeInplaceMutateExpected(value); });
+    // The match test and the matched-node path live together rather than in 
separate functions:
+    // selecting the link already computes value.as<TSub>(), and a pre-order 
walk invokes on that
+    // very node, so the converted value is reused instead of converted twice.
+    //
+    // Selection is on the input node, before any descent. A post-order walk 
must not select on
+    // the descended node: the reflected fallback writes its own remap entry, 
so testing after
+    // descent lets that entry swallow the callback.
+    std::optional<TSub> matched;
+    if constexpr (!std::is_same_v<TSub, AnyView> && !std::is_same_v<TSub, 
Any>) {
+      matched = value.template as<TSub>();
+      if (!matched.has_value()) return false;
     }
-  }
 
-  /*!
-   * \brief Mutate one value and invoke its matching callback in the 
configured order.
-   * \param value The borrowed input value.
-   * \return The mutated value or an Error.
-   */
-  Expected<Any> MutateImpl(AnyView value) noexcept {
-    // The link match is tested before any descent, and the identity remap 
lives
-    // inside the match continuation, so the var-type detection only runs when 
a
-    // callback signature actually matches. Unmatched nodes skip both it and 
the
-    // callback chain.
-    //
-    // The ordering is load-bearing: DefaultMutateExpected's reflected fallback
-    // writes its own remap entry for this node, so testing the match after 
descent
-    // would let the remap read that entry, treat the node as already handled, 
and
-    // never run the callback.
+    // A FreeVar or DAG node maps once and every later occurrence reuses that 
result, so if this
+    // node already has a cached remap entry, return it instead of mutating it 
again.
+    const bool remappable = IsRemappableIdentity(value.type_index());
+    if (remappable) {
+      Expected<Any> mapped = VarRemapGetExpected(value);
+      if (mapped.is_err()) {
+        *out = std::move(mapped);
+        return true;
+      }
+      if (ExpectedUnsafe::GetData(mapped).type_index() != 
TypeIndex::kTVMFFINone) {
+        *out = std::move(mapped);
+        return true;
+      }
+    }
+
+    const TVMFFIDefRegionKind kind = def_region_kind();
     if constexpr (order == WalkOrder::kPreOrder) {
-      return dispatch_(
-          value, def_region_kind(),
-          [&](auto&& invoke_callback) -> Expected<Any> {
-            return MutateWithIdentityRemapExpected(value, [&]() -> 
Expected<Any> {
-              Expected<Any> callback_result = invoke_callback(value);
-              TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(callback_result, value);
-
-              const Any& mapped_value = 
ExpectedUnsafe::GetData(callback_result);
-              Expected<Any> result = DefaultMutateExpected(mapped_value);
-              TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(result, mapped_value);
-              return result;
-            });
-          },
-          [&]() -> Expected<Any> { return DefaultMutateExpected(value); });
+      // Pre-order: the callback rewrites this node first, then descent runs 
over whatever it
+      // produced, so a replacement subtree is itself mapped.
+      Expected<Any> callback_result = [&]() -> Expected<Any> {
+        if constexpr (std::is_same_v<TSub, AnyView>) {
+          return InvokeCallbackLink(callback, value, kind);
+        } else if constexpr (std::is_same_v<TSub, Any>) {
+          return InvokeCallbackLink(callback, Any(value), kind);
+        } else {
+          // Reuses the conversion the match already performed.
+          return InvokeCallbackLink(callback, *std::move(matched), kind);
+        }
+      }();
+      if (TVM_FFI_PREDICT_FALSE(callback_result.is_err())) {
+        UpdateVisitErrorContext(callback_result, value);
+        *out = std::move(callback_result);
+        return true;
+      }
+      // Own the callback's result: it is the only reference from here on, and 
moving it out
+      // beats holding a reference into an Expected that stays alive across 
the descent below.
+      Any mapped_value = ExpectedUnsafe::GetData(callback_result);
+      // Each descent names the node it actually ran on in the error context.
+      *out = [&]() -> Expected<Any> {
+        if constexpr (kMaybeInplace) {
+          // A pre-order result can be mutated in place if unchanged or 
uniquely owned.
+          const TVMFFIAny* mapped_data = 
AnyUnsafe::TVMFFIAnyPtrFromAny(mapped_value);
+          const TVMFFIAny input_data = value.CopyToTVMFFIAny();
+          if (mapped_data->type_index == input_data.type_index &&
+              mapped_data->zero_padding == input_data.zero_padding &&
+              mapped_data->v_int64 == input_data.v_int64) {
+            return DefaultMaybeInplaceMutateExpected(value);
+          }
+          const Object* mapped_obj = mapped_value.as<Object>();
+          bool can_inplace = mapped_obj != nullptr && mapped_obj->unique();
+          return can_inplace ? DefaultMaybeInplaceMutateExpected(mapped_value)
+                             : DefaultMutateExpected(mapped_value);
+        } else {
+          return DefaultMutateExpected(mapped_value);
+        }
+      }();
+      if (TVM_FFI_PREDICT_FALSE(out->is_err())) return true;
     } else {
-      return dispatch_(
-          value, def_region_kind(),
-          [&](auto&& invoke_callback) -> Expected<Any> {
-            return MutateWithIdentityRemapExpected(value, [&]() -> 
Expected<Any> {
-              Expected<Any> result = DefaultMutateExpected(value);
-              TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(result, value);
-
-              const Any& mapped_value = ExpectedUnsafe::GetData(result);
-              Expected<Any> callback_result = invoke_callback(mapped_value);
-              TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(callback_result, 
mapped_value);
-              return callback_result;
-            });
-          },
-          [&]() -> Expected<Any> { return DefaultMutateExpected(value); });
+      // Post-order: children are mapped first and the callback sees the 
rebuilt node, so it
+      // observes its operands already substituted.
+      Expected<Any> descended =
+          kMaybeInplace ? DefaultMaybeInplaceMutateExpected(value) : 
DefaultMutateExpected(value);
+      if (TVM_FFI_PREDICT_FALSE(descended.is_err())) {
+        *out = std::move(descended);
+        return true;
+      }
+      // Held by reference, not moved out: the error path below names this 
node, so it has to
+      // survive the callback. Only a storage-enabled TSub could gain from a 
move, and that is
+      // exactly the case whose move would empty it.
+      const Any& mapped_value = ExpectedUnsafe::GetData(descended);
+      *out = [&]() -> Expected<Any> {
+        if constexpr (std::is_same_v<TSub, AnyView>) {
+          return InvokeCallbackLink(callback, AnyView(mapped_value), kind);
+        } else if constexpr (std::is_same_v<TSub, Any>) {
+          return InvokeCallbackLink(callback, Any(mapped_value), kind);
+        } else {
+          // Re-converted rather than reusing the match: the callback is 
invoked on the node
+          // descent handed back, and must only see the type it asked for. 
Default mutation is
+          // required to preserve the type, so failing here means some hook 
broke that.
+          std::optional<TSub> descended_sub = mapped_value.template as<TSub>();
+          if (TVM_FFI_PREDICT_FALSE(!descended_sub.has_value())) {
+            return Unexpected(
+                Error("TypeError", "structural mutate: descent changed the 
node type", ""));
+          }
+          return InvokeCallbackLink(callback, *std::move(descended_sub), kind);
+        }
+      }();
+      if (TVM_FFI_PREDICT_FALSE(out->is_err())) {
+        UpdateVisitErrorContext(*out, mapped_value);
+        return true;
+      }
     }
-  }
-
-  /*! \brief Composed callback dispatcher owned by this mutator. */
-  Dispatch dispatch_;
 
-  /*! \brief Identity-substitution table. */
-  Map<ObjectRef, Any> var_remap_;
-};
+    // Bind this node's identity to its final result, so every later 
occurrence reuses it.
+    if (remappable) {
+      Expected<void> set_result = VarRemapSetExpected(value, 
ExpectedUnsafe::GetData(*out));
+      if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
+        *out = Unexpected(std::move(set_result).error());
+      }
+    }
+    return true;
+  }
 
-// Build a callback dispatcher from a typed callback chain. The dispatcher 
answers "which link
-// matches this node" as control flow rather than as a value. It takes the 
node, the active
-// def-region kind, an on-match continuation, and an on-no-match continuation, 
and returns whichever
-// one applies. The on-match continuation receives a callable 
`invoke_callback(AnyView target)` that
-// converts target to the matched link's argument type and invokes it, so the 
caller decides what
-// wraps the invocation. A node that matches no link therefore pays one type 
test per link and
-// nothing else.
-struct StructuralMapCallbackChain {
- public:
   /*!
-   * \brief Construct a dispatcher owning \p callbacks.
-   * \tparam Callbacks Callback types.
-   * \param callbacks Callbacks tested in declaration order.
-   * \return A callback-aware structural-map dispatcher.
+   * \brief Test every link in declaration order, stopping at the first that 
matches.
+   * \return Whether some link matched, in which case \p out was written.
    */
-  template <typename... Callbacks>
-  static auto FromChain(Callbacks... callbacks) {
-    auto callback_tuple = std::make_tuple(std::move(callbacks)...);
-    return [callbacks = std::move(callback_tuple)](AnyView value, 
TVMFFIDefRegionKind kind,
-                                                   auto&& on_match,
-                                                   auto&& on_no_match) mutable 
-> Expected<Any> {
-      std::optional<Expected<Any>> result;
-      auto run_match = [&](auto&& invoke_callback) {
-        
result.emplace(on_match(std::forward<decltype(invoke_callback)>(invoke_callback)));
-      };
-      // Selection is control flow: the first matching link hands its 
invocation thunk to
-      // run_match, while a node matching no link goes directly to on_no_match.
-      bool matched = std::apply(
-          [&](auto&... callback) { return (... || TryCallLink(callback, value, 
kind, run_match)); },
-          callbacks);
-      if (matched) {
-        return *std::move(result);
-      }
-      return on_no_match();
-    };
+  template <bool kMaybeInplace, size_t... Is>
+  TVM_FFI_INLINE bool TryLinks(AnyView value, Expected<Any>* out,
+                               std::index_sequence<Is...>) noexcept {
+    return (TryLink<kMaybeInplace>(std::get<Is>(callbacks_), value, out) || 
...);
   }
 
- private:
-  // Hand the matched link's invocation thunk to on_match, or return false on 
no match. The
-  // target conversion stays in the thunk so post-order can select on the 
input and invoke on the
-  // node whose children have already been mapped.
-  template <typename Callback, typename OnMatch>
-  TVM_FFI_INLINE static bool TryCallLink(Callback& callback, AnyView value,
-                                         TVMFFIDefRegionKind kind, OnMatch&& 
on_match) {
-    using FuncInfo = FunctionInfo<std::decay_t<Callback>>;
-    static_assert(FuncInfo::num_args == 1 || FuncInfo::num_args == 2,
-                  "StructuralMap callbacks must take one argument (value) or 
two arguments "
-                  "(value, def-region kind)");
-    using FirstArg = std::tuple_element_t<0, typename FuncInfo::ArgType>;
-    using TSub = std::remove_cv_t<std::remove_reference_t<FirstArg>>;
-    if constexpr (std::is_same_v<TSub, AnyView>) {
-      on_match([&](AnyView target) -> Expected<Any> {
-        return InvokeCallbackLink(callback, target, kind);
-      });
-      return true;
-    } else if constexpr (std::is_same_v<TSub, Any>) {
-      on_match([&](AnyView target) -> Expected<Any> {
-        return InvokeCallbackLink(callback, Any(target), kind);
-      });
-      return true;
-    } else if (auto opt = value.template as<TSub>()) {
-      on_match([&](AnyView target) -> Expected<Any> {
-        std::optional<TSub> converted = target.template as<TSub>();
-        if (!converted.has_value()) {
-          // A custom post-order hook may change the node type after the input 
selected this link.
-          return Any(target);
-        }
-        return InvokeCallbackLink(callback, *std::move(converted), kind);
-      });
-      return true;
+  /*!
+   * \brief Mutate a value, invoking the first matching callback link.
+   * \param value The borrowed value to mutate.
+   * \return Raw ``TVMFFIAny`` containing the mutated value or Error.
+   */
+  TVM_FFI_INLINE TVMFFIAny MutateImplRaw(AnyView value) noexcept {
+    Expected<Any> out{Any()};
+    if (TryLinks<false>(value, &out, std::index_sequence_for<Callbacks...>{})) 
{
+      return ExpectedUnsafe::MoveToTVMFFIAny(std::move(out));
     }
-    return false;
+    return DefaultMutateRaw(value);
   }
 
   /*!
-   * \brief Invoke a matched callback with optional def-region context.
-   * \tparam Callback Callable returning a value implicitly convertible to 
``Expected<Any>``.
-   * \tparam Value Type of the converted value passed to the callback.
-   * \param callback The matched callback.
-   * \param value The converted value passed to the callback.
-   * \param kind The active def-region kind.
-   * \return The callback result normalized to ``Expected<Any>``.
+   * \brief Mutate a value in place when safe, invoking the first matching 
callback link.
+   * \param value The borrowed value to mutate.
+   * \return Raw ``TVMFFIAny`` containing the mutated value or Error.
    */
-  template <typename Callback, typename Value>
-  TVM_FFI_INLINE static Expected<Any> InvokeCallbackLink(Callback& callback, 
Value&& value,
-                                                         TVMFFIDefRegionKind 
kind) {
-    using FuncInfo = FunctionInfo<std::decay_t<Callback>>;
-    static_assert(std::is_convertible_v<typename FuncInfo::RetType, 
Expected<Any>>,
-                  "StructuralMap callbacks must return a replacement value, 
Error, Unexpected, "
-                  "or Expected<U> implicitly convertible to Expected<Any>");
-    try {
-      if constexpr (FuncInfo::num_args == 1) {
-        return callback(std::forward<Value>(value));
-      } else {
-        return callback(std::forward<Value>(value), kind);
-      }
-    } catch (const Error& err) {
-      return Unexpected(err);
+  TVM_FFI_INLINE TVMFFIAny MaybeInplaceMutateImplRaw(AnyView value) noexcept {
+    Expected<Any> out{Any()};
+    if (TryLinks<true>(value, &out, std::index_sequence_for<Callbacks...>{})) {
+      return ExpectedUnsafe::MoveToTVMFFIAny(std::move(out));
     }
+    return DefaultMaybeInplaceMutateRaw(value);
   }
+
+  /*! \brief The callback links, tested in declaration order. */
+  std::tuple<Callbacks...> callbacks_;
 };
 
 }  // namespace details
@@ -993,10 +1119,8 @@ struct StructuralMapCallbackChain {
 template <WalkOrder order, typename... Callbacks>
 Expected<Any> StructuralMapExpected(AnyView root, Callbacks&&... callbacks) 
noexcept {
   static_assert(sizeof...(Callbacks) != 0, "StructuralMap requires at least 
one callback");
-  auto dispatch =
-      
details::StructuralMapCallbackChain::FromChain(std::forward<Callbacks>(callbacks)...);
-  using Mutator = details::StructuralMapMutatorObj<order, decltype(dispatch)>;
-  StructuralMutator mutator(make_object<Mutator>(std::move(dispatch)));
+  using Mutator = details::StructuralMapMutatorObj<order, 
std::decay_t<Callbacks>...>;
+  StructuralMutator 
mutator(make_object<Mutator>(std::forward<Callbacks>(callbacks)...));
   return mutator->MaybeInplaceMutateIfUniqueExpected(root);
 }
 
diff --git a/include/tvm/ffi/extra/structural_visit.h 
b/include/tvm/ffi/extra/structural_visit.h
index 89f53fac..468bfd4f 100644
--- a/include/tvm/ffi/extra/structural_visit.h
+++ b/include/tvm/ffi/extra/structural_visit.h
@@ -205,16 +205,27 @@ class StructuralVisitorObj : public Object {
     static reflection::TypeAttrColumn 
column(reflection::type_attr::kStructuralVisit);
     AnyView attr = column[type_index];
 
+    // Hooks propagate errors untouched; this is the engine dispatching into 
`value`, so the
+    // node is named here and nowhere else -- exactly one frame per node.
     // case 1: Type-specific override registered as an opaque ABI visit 
function pointer.
     if (attr.type_index() == TypeIndex::kTVMFFIOpaquePtr) {
       auto* visit_fn = reinterpret_cast<FStructuralVisit>(attr.cast<void*>());
-      return 
details::ExpectedUnsafe::MoveFromTVMFFIAny<Optional<VisitInterrupt>>(
-          (*visit_fn)(this, value));
+      TVMFFIAny raw = (*visit_fn)(this, value);
+      if (TVM_FFI_PREDICT_FALSE(raw.type_index == TypeIndex::kTVMFFIError)) {
+        details::UpdateVisitErrorContext(raw, value);
+      }
+      return 
details::ExpectedUnsafe::MoveFromTVMFFIAny<Optional<VisitInterrupt>>(raw);
     }
 
     // case 2: Type-specific override registered as an ffi::Function.
     if (attr.type_index() == TypeIndex::kTVMFFIFunction) {
-      return 
attr.cast<Function>().CallExpected<Optional<VisitInterrupt>>(this, value);
+      Expected<Optional<VisitInterrupt>> result =
+          attr.cast<Function>().CallExpected<Optional<VisitInterrupt>>(this, 
value);
+      if (TVM_FFI_PREDICT_FALSE(result.is_err())) {
+        Error err = result.error();
+        details::UpdateVisitErrorContext(err, value);
+      }
+      return result;
     }
 
     if (TVM_FFI_PREDICT_FALSE(attr.type_index() != TypeIndex::kTVMFFINone)) {
@@ -451,23 +462,13 @@ namespace details {
 // If Result is an Error, append Node to the visit error context before 
returning. Node is
 // required: dropping it silently degrades every error message produced below 
this frame.
 // A raw pointer Node must be non-null; pass nullable nodes as ObjectRef or 
Any so None is skipped.
-#define TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Result, Node)                       
                    \
-  do {                                                                         
                    \
-    auto&& tvm_ffi_res_ = (Result);                                            
                    \
-    if (TVM_FFI_PREDICT_FALSE(                                                 
                    \
-            
::tvm::ffi::details::StructuralVisitNeedEarlyReturn(tvm_ffi_res_))) {           
       \
-      if (TVM_FFI_PREDICT_FALSE(tvm_ffi_res_.type_index() ==                   
                    \
-                                ::tvm::ffi::TypeIndex::kTVMFFIError)) {        
                    \
-        auto&& tvm_ffi_visit_node_owner_ = (Node);                             
                    \
-        ::tvm::ffi::AnyView tvm_ffi_visit_node_ = tvm_ffi_visit_node_owner_;   
                    \
-        if (tvm_ffi_visit_node_.type_index() >= 
::tvm::ffi::TypeIndex::kTVMFFIStaticObjectBegin) { \
-          ::tvm::ffi::Error tvm_ffi_visit_err_ = tvm_ffi_res_.error();         
                    \
-          ::tvm::ffi::details::UpdateVisitErrorContext(                        
                    \
-              tvm_ffi_visit_err_, 
tvm_ffi_visit_node_.cast<::tvm::ffi::ObjectRef>());              \
-        }                                                                      
                    \
-      }                                                                        
                    \
-      return 
::tvm::ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(tvm_ffi_res_));  
      \
-    }                                                                          
                    \
+#define TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Result)                             
             \
+  do {                                                                         
             \
+    auto&& tvm_ffi_res_ = (Result);                                            
             \
+    if (TVM_FFI_PREDICT_FALSE(                                                 
             \
+            
::tvm::ffi::details::StructuralVisitNeedEarlyReturn(tvm_ffi_res_))) {           
\
+      return 
::tvm::ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(tvm_ffi_res_)); \
+    }                                                                          
             \
   } while (0)
 /// \endcond
 
@@ -523,7 +524,13 @@ class StructuralWalkVisitorObj : public 
StructuralVisitorObj {
     }
     if constexpr (order == WalkOrder::kPreOrder) {
       auto result = dispatch_(value, this->def_region_kind());
-      TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(result, value);
+      if 
(TVM_FFI_PREDICT_FALSE(details::StructuralVisitNeedEarlyReturn(result))) {
+        if (TVM_FFI_PREDICT_FALSE(result.is_err())) {
+          Error err = result.error();
+          details::UpdateVisitErrorContext(err, value);
+        }
+        return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+      }
       // Hoist the call out of TVM_FFI_UNSAFE_ASSUME: clang's -Wassume rejects
       // arguments that contain a call expression (its potential side effects
       // would be discarded), while [[maybe_unused]] keeps -Wunused-variable
@@ -537,10 +544,23 @@ class StructuralWalkVisitorObj : public 
StructuralVisitorObj {
       }
     }
 
-    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(DefaultVisitExpected(value), value);
+    {
+      // DefaultVisitExpected already named `value` if a hook it dispatched 
failed.
+      auto result = DefaultVisitExpected(value);
+      if 
(TVM_FFI_PREDICT_FALSE(details::StructuralVisitNeedEarlyReturn(result))) {
+        return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+      }
+    }
 
     if constexpr (order == WalkOrder::kPostOrder) {
-      TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(dispatch_(value, 
this->def_region_kind()), value);
+      auto result = dispatch_(value, this->def_region_kind());
+      if 
(TVM_FFI_PREDICT_FALSE(details::StructuralVisitNeedEarlyReturn(result))) {
+        if (TVM_FFI_PREDICT_FALSE(result.is_err())) {
+          Error err = result.error();
+          details::UpdateVisitErrorContext(err, value);
+        }
+        return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+      }
     }
 
     return details::ExpectedUnsafe::MoveToTVMFFIAny(
diff --git a/include/tvm/ffi/extra/visit_error_context.h 
b/include/tvm/ffi/extra/visit_error_context.h
index e8b5c482..76c49375 100644
--- a/include/tvm/ffi/extra/visit_error_context.h
+++ b/include/tvm/ffi/extra/visit_error_context.h
@@ -264,6 +264,44 @@ inline void UpdateVisitErrorContext(Error& err, const 
ObjectRef& node) {  // NOL
   error_obj->extra_context =
       
details::ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(new_context));
 }
+
+/*!
+ * \brief Name \p node in a raw failed result's visit error context.
+ *
+ * The overload the visit and mutate engines use at a hook boundary: hooks 
propagate errors
+ * untouched, and the engine, which knows the node it dispatched on, adds that 
frame. The Error
+ * is refcounted, so annotating this handle annotates the object \p result 
carries.
+ *
+ * \param result A raw result already known to hold an Error.
+ * \param node The node to name; a non-object node has no frame to add.
+ */
+TVM_FFI_COLD_CODE inline void UpdateVisitErrorContext(const TVMFFIAny& result,
+                                                      AnyView node) noexcept {
+  if (node.type_index() >= TypeIndex::kTVMFFIStaticObjectBegin) {
+    Error err = AnyView::CopyFromTVMFFIAny(result).cast<Error>();
+    UpdateVisitErrorContext(err, node.cast<ObjectRef>());
+  }
+}
+
+/*!
+ * \brief Name \p node in a failed result's visit error context.
+ *
+ * The guarded counterpart to the ObjectRef overload, for engine call sites 
that hold a borrowed
+ * node of unknown kind. A visited node may be a primitive -- a container 
element, a reflected
+ * field -- and `AnyView::cast<ObjectRef>()` throws on one. The engines that 
call this are
+ * `noexcept`, so an unguarded cast would terminate rather than propagate; 
skipping the frame is
+ * correct because a non-object node has no identity to name in the context.
+ *
+ * \param err The Error to annotate. Refcounted, so annotating this handle 
annotates the shared
+ *            object the caller's result carries.
+ * \param node The node to name; skipped when it is not object-backed.
+ */
+TVM_FFI_COLD_CODE inline void UpdateVisitErrorContext(Error& err,
+                                                      AnyView node) noexcept { 
 // NOLINT(*)
+  if (node.type_index() >= TypeIndex::kTVMFFIStaticObjectBegin) {
+    UpdateVisitErrorContext(err, node.cast<ObjectRef>());
+  }
+}
 }  // namespace details
 
 }  // namespace ffi
diff --git a/src/ffi/extra/structural_mutate.cc 
b/src/ffi/extra/structural_mutate.cc
index b8e5a072..1705046c 100644
--- a/src/ffi/extra/structural_mutate.cc
+++ b/src/ffi/extra/structural_mutate.cc
@@ -47,40 +47,225 @@ namespace details {
  * \param order Integer value of \ref WalkOrder.
  * \return The mapped owning value, or an Error.
  */
-Expected<Any> StructuralMapExpected(
-    AnyView root, const Array<Tuple<int32_t, Function>>& callbacks,
-    const Array<Tuple<int32_t, Function>>& callbacks_with_def_region_kind, int 
order) noexcept {
-  auto dispatch = [callbacks, callbacks_with_def_region_kind](AnyView x, 
TVMFFIDefRegionKind kind,
-                                                              auto&& on_match,
-                                                              auto&& 
on_no_match) -> Expected<Any> {
-    for (const auto& entry : callbacks) {
-      int32_t type_index = entry.template get<0>();
-      if (!RuntimeTypeIndexMatch(x.type_index(), type_index)) {
-        continue;
+/*!
+ * \brief Structural mutator whose links are runtime ffi.Functions keyed by 
type index.
+ *
+ * The dynamic counterpart of \ref StructuralMapMutatorObj. It selects a link 
by scanning
+ * registered type indices where the static version does a compile-time 
``as<TSub>()``. The two
+ * are kept apart, rather than sharing one template with a mode flag, so each 
version reads
+ * straight through; only the identity remap they share lives in the common 
base. Keeping this
+ * one in the .cc also keeps the link table out of the public header.
+ *
+ * \tparam order Callback placement relative to child mapping.
+ */
+template <WalkOrder order>
+class StructuralMapDynMutatorObj : public StructuralMapMutatorBaseObj {
+ public:
+  StructuralMapDynMutatorObj(Array<Tuple<int32_t, Function>> callbacks,
+                             Array<Tuple<int32_t, Function>> 
callbacks_with_def_region_kind)
+      : StructuralMapMutatorBaseObj(VTable()),
+        callbacks_(std::move(callbacks)),
+        
callbacks_with_def_region_kind_(std::move(callbacks_with_def_region_kind)) {}
+
+ private:
+  static const StructuralMutatorVTable* VTable() {
+    static const StructuralMutatorVTable vtable{
+        &StructuralMapDynMutatorObj::DispatchMutate,
+        &StructuralMapDynMutatorObj::DispatchMaybeInplaceMutate,
+        &StructuralMapDynMutatorObj::DispatchVarRemapGet,
+        &StructuralMapDynMutatorObj::DispatchVarRemapSet,
+    };
+    return &vtable;
+  }
+
+  static TVMFFIAny DispatchMaybeInplaceMutate(StructuralMutatorObj* mutator,
+                                              AnyView value) noexcept {
+    return 
static_cast<StructuralMapDynMutatorObj*>(mutator)->MaybeInplaceMutateImplRaw(value);
+  }
+
+  static TVMFFIAny DispatchMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
+    return 
static_cast<StructuralMapDynMutatorObj*>(mutator)->MutateImplRaw(value);
+  }
+
+  /*!
+   * \brief Find the first link registered for \p type_index.
+   *
+   * \param type_index The input node's runtime type index.
+   * \param with_kind Set when the matched link also takes a def-region kind.
+   * \param link_type_index Set to the registered type index the link matched 
on, so a post-order
+   *        walk can recheck the descended node against the same target.
+   * \return The matched Function, or nullopt when no link applies.
+   */
+  Optional<Function> FindLink(int32_t type_index, bool* with_kind,
+                              int32_t* link_type_index) const noexcept {
+    for (const Tuple<int32_t, Function>& entry : callbacks_) {
+      if (RuntimeTypeIndexMatch(type_index, entry.get<0>())) {
+        *with_kind = false;
+        *link_type_index = entry.get<0>();
+        return entry.get<1>();
       }
-      Function fn = entry.template get<1>();
-      return on_match(
-          [&](AnyView target) -> Expected<Any> { return 
fn.CallExpected<Any>(target); });
     }
-    for (const auto& entry : callbacks_with_def_region_kind) {
-      int32_t type_index = entry.template get<0>();
-      if (!RuntimeTypeIndexMatch(x.type_index(), type_index)) {
-        continue;
+    for (const Tuple<int32_t, Function>& entry : 
callbacks_with_def_region_kind_) {
+      if (RuntimeTypeIndexMatch(type_index, entry.get<0>())) {
+        *with_kind = true;
+        *link_type_index = entry.get<0>();
+        return entry.get<1>();
       }
-      Function fn = entry.template get<1>();
-      return on_match(
-          [&](AnyView target) -> Expected<Any> { return 
fn.CallExpected<Any>(target, kind); });
     }
-    return on_no_match();
-  };
+    return std::nullopt;
+  }
+
+  /*!
+   * \brief Invoke a matched link, threading the live def-region kind through.
+   *
+   * \p kind is passed rather than stashed at selection time: the engine only 
knows the true
+   * def-region kind of the node handed back after descent (post-order) or of 
the matched node
+   * itself (pre-order).
+   */
+  TVM_FFI_INLINE static Expected<Any> InvokeLink(const Function& fn, bool 
with_kind, AnyView target,
+                                                 TVMFFIDefRegionKind kind) 
noexcept {
+    // CallExpected is exception-free: it goes through the safe-call path and 
returns any raised
+    // error as Unexpected, unlike a directly invoked C++ callback.
+    return with_kind ? fn.CallExpected<Any>(target, kind) : 
fn.CallExpected<Any>(target);
+  }
+
+  /*!
+   * \brief Test the link table against \p value and mutate through the first 
match.
+   *
+   * \tparam kMaybeInplace Whether a uniquely owned node may be mutated in 
place.
+   * \param value The borrowed value to test and mutate.
+   * \param out Receives the mutated value or Error when a link matched.
+   * \return Whether a link matched, in which case \p out was written.
+   */
+  template <bool kMaybeInplace>
+  TVM_FFI_INLINE bool TryLink(AnyView value, Expected<Any>* out) noexcept {
+    // Step for step the same walk as StructuralMapMutatorObj::TryLink, and 
deliberately so:
+    // only link detection and invocation differ between the two, and keeping 
them as separate
+    // straight-line copies lets each specialize on its own selection strategy 
and keeps both
+    // readable. Everything below except finding and calling the link is 
shared semantics, so a
+    // change to either copy belongs in both.
+
+    bool with_kind = false;
+    int32_t link_type_index = TypeIndex::kTVMFFINone;
+    // A local, so descending into a matching child cannot change what this 
node invokes.
+    Optional<Function> matched = FindLink(value.type_index(), &with_kind, 
&link_type_index);
+    if (!matched.has_value()) return false;
 
+    // --- identity remap, entry half -----------------------------------------
+    // A FreeVar or DAG node maps once and every later occurrence reuses that 
result, so if this
+    // node already has a cached remap entry, return it instead of mutating it 
again.
+    const bool remappable = IsRemappableIdentity(value.type_index());
+    if (remappable) {
+      Expected<Any> mapped = VarRemapGetExpected(value);
+      if (mapped.is_err()) {
+        *out = std::move(mapped);
+        return true;
+      }
+      if (ExpectedUnsafe::GetData(mapped).type_index() != 
TypeIndex::kTVMFFINone) {
+        *out = std::move(mapped);
+        return true;
+      }
+    }
+
+    // --- callback and descent, in walk order --------------------------------
+    const TVMFFIDefRegionKind kind = def_region_kind();
+    if constexpr (order == WalkOrder::kPreOrder) {
+      // Pre-order: the callback rewrites this node first, then descent runs 
over what it made.
+      Expected<Any> callback_result = InvokeLink(*matched, with_kind, value, 
kind);
+      if (TVM_FFI_PREDICT_FALSE(callback_result.is_err())) {
+        UpdateVisitErrorContext(callback_result, value);
+        *out = std::move(callback_result);
+        return true;
+      }
+      // Own the callback's result: it is the only reference from here on.
+      Any mapped_value = ExpectedUnsafe::GetData(callback_result);
+      // Each descent names the node it actually ran on in the error context.
+      *out = [&]() -> Expected<Any> {
+        if constexpr (kMaybeInplace) {
+          const TVMFFIAny* mapped_data = 
AnyUnsafe::TVMFFIAnyPtrFromAny(mapped_value);
+          const TVMFFIAny input_data = value.CopyToTVMFFIAny();
+          if (mapped_data->type_index == input_data.type_index &&
+              mapped_data->zero_padding == input_data.zero_padding &&
+              mapped_data->v_int64 == input_data.v_int64) {
+            return DefaultMaybeInplaceMutateExpected(value);
+          }
+          const Object* mapped_obj = mapped_value.as<Object>();
+          bool can_inplace = mapped_obj != nullptr && mapped_obj->unique();
+          return can_inplace ? DefaultMaybeInplaceMutateExpected(mapped_value)
+                             : DefaultMutateExpected(mapped_value);
+        } else {
+          return DefaultMutateExpected(mapped_value);
+        }
+      }();
+      if (TVM_FFI_PREDICT_FALSE(out->is_err())) return true;
+    } else {
+      // Post-order: children are mapped first, so the callback sees the 
rebuilt node.
+      Expected<Any> descended =
+          kMaybeInplace ? DefaultMaybeInplaceMutateExpected(value) : 
DefaultMutateExpected(value);
+      if (TVM_FFI_PREDICT_FALSE(descended.is_err())) {
+        *out = std::move(descended);
+        return true;
+      }
+      // Held by reference, not moved out: the error path below names this 
node, so it has to
+      // survive the callback.
+      const Any& mapped_value = ExpectedUnsafe::GetData(descended);
+      // The link was selected on the input node, and the callback must only 
see the type it
+      // registered for. The typed mutator gets this from its 
`mapped_value.as<TSub>()`; here the
+      // registered type index is the same target, so recheck against it.
+      if (TVM_FFI_PREDICT_FALSE(
+              !RuntimeTypeIndexMatch(mapped_value.type_index(), 
link_type_index))) {
+        *out =
+            Unexpected(Error("TypeError", "structural mutate: descent changed 
the node type", ""));
+        UpdateVisitErrorContext(*out, mapped_value);
+        return true;
+      }
+      *out = InvokeLink(*matched, with_kind, mapped_value, kind);
+      if (TVM_FFI_PREDICT_FALSE(out->is_err())) {
+        UpdateVisitErrorContext(*out, mapped_value);
+        return true;
+      }
+    }
+
+    // Bind this node's identity to its final result, so every later 
occurrence reuses it.
+    if (remappable) {
+      Expected<void> set_result = VarRemapSetExpected(value, 
ExpectedUnsafe::GetData(*out));
+      if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
+        *out = Unexpected(std::move(set_result).error());
+      }
+    }
+    return true;
+  }
+
+  TVM_FFI_INLINE TVMFFIAny MutateImplRaw(AnyView value) noexcept {
+    Expected<Any> out{Any()};
+    if (TryLink<false>(value, &out)) {
+      return ExpectedUnsafe::MoveToTVMFFIAny(std::move(out));
+    }
+    return DefaultMutateRaw(value);
+  }
+
+  TVM_FFI_INLINE TVMFFIAny MaybeInplaceMutateImplRaw(AnyView value) noexcept {
+    Expected<Any> out{Any()};
+    if (TryLink<true>(value, &out)) {
+      return ExpectedUnsafe::MoveToTVMFFIAny(std::move(out));
+    }
+    return DefaultMaybeInplaceMutateRaw(value);
+  }
+
+  Array<Tuple<int32_t, Function>> callbacks_;
+  Array<Tuple<int32_t, Function>> callbacks_with_def_region_kind_;
+};
+
+Expected<Any> StructuralMapExpected(
+    AnyView root, const Array<Tuple<int32_t, Function>>& callbacks,
+    const Array<Tuple<int32_t, Function>>& callbacks_with_def_region_kind, int 
order) noexcept {
   if (order == static_cast<int>(WalkOrder::kPreOrder)) {
-    using Mutator = StructuralMapMutatorObj<WalkOrder::kPreOrder, 
decltype(dispatch)>;
-    StructuralMutator mutator(make_object<Mutator>(std::move(dispatch)));
+    using Mutator = StructuralMapDynMutatorObj<WalkOrder::kPreOrder>;
+    StructuralMutator mutator(make_object<Mutator>(callbacks, 
callbacks_with_def_region_kind));
     return mutator->MaybeInplaceMutateIfUniqueExpected(root);
   } else {
-    using Mutator = StructuralMapMutatorObj<WalkOrder::kPostOrder, 
decltype(dispatch)>;
-    StructuralMutator mutator(make_object<Mutator>(std::move(dispatch)));
+    using Mutator = StructuralMapDynMutatorObj<WalkOrder::kPostOrder>;
+    StructuralMutator mutator(make_object<Mutator>(callbacks, 
callbacks_with_def_region_kind));
     return mutator->MaybeInplaceMutateIfUniqueExpected(root);
   }
 }
@@ -99,15 +284,15 @@ Expected<Any> StructuralMapExpected(
  * \return The mutated sequence, or an Error.
  */
 template <typename SeqObj>
-Expected<Any> MutateSeqContainerExpected(StructuralMutatorObj* mutator, 
AnyView value,
-                                         const SeqObj* self) noexcept {
+TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj* mutator, AnyView value,
+                                const SeqObj* self) noexcept {
   int64_t size = static_cast<int64_t>(self->size());
   const Any* items = self->begin();
   ObjectPtr<SeqObj> output = nullptr;
 
   for (int64_t i = 0; i < size; ++i) {
     const Any& item = items[i];
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, mapped_value, 
mutator->MutateExpected(item), self);
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, mapped_value, 
mutator->MutateExpected(item));
 
     if (output == nullptr) {
       if (item.same_as(mapped_value)) {
@@ -120,9 +305,9 @@ Expected<Any> 
MutateSeqContainerExpected(StructuralMutatorObj* mutator, AnyView
   }
 
   if (output == nullptr) {
-    return Any(value);
+    return AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
   }
-  return Any(ObjectRef(std::move(output)));
+  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(output)));
 }
 
 /*!
@@ -135,18 +320,18 @@ Expected<Any> 
MutateSeqContainerExpected(StructuralMutatorObj* mutator, AnyView
  * \return The mutated sequence, or an Error.
  */
 template <typename SeqObj>
-Expected<Any> MaybeInplaceMutateSeqContainerExpected(StructuralMutatorObj* 
mutator, AnyView value,
-                                                     SeqObj* self) noexcept {
+TVMFFIAny MaybeInplaceMutateSeqContainerRaw(StructuralMutatorObj* mutator, 
AnyView value,
+                                            SeqObj* self) noexcept {
   for (int64_t i = 0; i < static_cast<int64_t>(self->size()); ++i) {
     const Any& item = self->begin()[i];
     TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, mapped_value,
-                                      
mutator->MaybeInplaceMutateIfUniqueExpected(item), self);
+                                      
mutator->MaybeInplaceMutateIfUniqueExpected(item));
 
     if (!item.same_as(mapped_value)) {
       self->SetItemAfterCheck(i, std::move(mapped_value));
     }
   }
-  return Any(value);
+  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
 }
 
 /*!
@@ -159,15 +344,15 @@ Expected<Any> 
MaybeInplaceMutateSeqContainerExpected(StructuralMutatorObj* mutat
  * \return The mutated map, or an Error.
  */
 template <typename MapObjType>
-Expected<Any> MutateMapValuesExpected(StructuralMutatorObj* mutator, AnyView 
value,
-                                      const MapObjType* self) noexcept {
+TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* mutator, AnyView value,
+                             const MapObjType* self) noexcept {
   ObjectPtr<Object> output = nullptr;
   MapBaseObj::iterator output_it;
   size_t index = 0;
 
   for (auto source_it = self->begin(); source_it != self->end(); ++source_it, 
++index) {
     const Any& old_value = source_it->second;
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, new_value, 
mutator->MutateExpected(old_value), self);
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, new_value, 
mutator->MutateExpected(old_value));
     bool changed = !old_value.same_as(new_value);
     if (output == nullptr) {
       if (!changed) {
@@ -186,9 +371,9 @@ Expected<Any> MutateMapValuesExpected(StructuralMutatorObj* 
mutator, AnyView val
   }
 
   if (output == nullptr) {
-    return Any(value);
+    return AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
   }
-  return Any(ObjectRef(std::move(output)));
+  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(output)));
 }
 
 /*!
@@ -201,18 +386,18 @@ Expected<Any> 
MutateMapValuesExpected(StructuralMutatorObj* mutator, AnyView val
  * \return The mutated map, or an Error.
  */
 template <typename MapObjType>
-Expected<Any> MaybeInplaceMutateMapValuesExpected(StructuralMutatorObj* 
mutator, AnyView value,
-                                                  MapObjType* self) noexcept {
+TVMFFIAny MaybeInplaceMutateMapValuesRaw(StructuralMutatorObj* mutator, 
AnyView value,
+                                         MapObjType* self) noexcept {
   for (auto it = self->begin(); it != self->end(); ++it) {
     const Any& old_value = it->second;
     TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, new_value,
-                                      
mutator->MaybeInplaceMutateIfUniqueExpected(old_value), self);
+                                      
mutator->MaybeInplaceMutateIfUniqueExpected(old_value));
 
     if (!old_value.same_as(new_value)) {
       it->second = std::move(new_value);
     }
   }
-  return Any(value);
+  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
 }
 
 /*! \brief Identity structural mutation hook for immutable String and Bytes 
leaves. */
@@ -223,58 +408,50 @@ TVMFFIAny MutateImmutableLeaf(StructuralMutatorObj*, 
AnyView value) noexcept {
 
 /*! \brief Structural mutation hook for ArrayObj. */
 TVMFFIAny MutateArray(StructuralMutatorObj* mutator, AnyView value) noexcept {
-  Expected<Any> result = MutateSeqContainerExpected(
+  return MutateSeqContainerRaw(
       mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const ArrayObj>(value));
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
 }
 
 /*! \brief Maybe-in-place structural mutation hook for ArrayObj. */
 TVMFFIAny MaybeInplaceMutateArray(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
-  Expected<Any> result = MaybeInplaceMutateSeqContainerExpected(
+  return MaybeInplaceMutateSeqContainerRaw(
       mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ArrayObj>(value));
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
 }
 
 /*! \brief Structural mutation hook for ListObj. */
 TVMFFIAny MutateList(StructuralMutatorObj* mutator, AnyView value) noexcept {
-  Expected<Any> result = MutateSeqContainerExpected(
+  return MutateSeqContainerRaw(
       mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const ListObj>(value));
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
 }
 
 /*! \brief Maybe-in-place structural mutation hook for ListObj. */
 TVMFFIAny MaybeInplaceMutateList(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
-  Expected<Any> result = MaybeInplaceMutateSeqContainerExpected(
+  return MaybeInplaceMutateSeqContainerRaw(
       mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ListObj>(value));
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
 }
 
 /*! \brief Structural mutation hook for MapObj. */
 TVMFFIAny MutateMap(StructuralMutatorObj* mutator, AnyView value) noexcept {
-  Expected<Any> result = MutateMapValuesExpected(
+  return MutateMapValuesRaw(
       mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const MapObj>(value));
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
 }
 
 /*! \brief Maybe-in-place structural mutation hook for MapObj. */
 TVMFFIAny MaybeInplaceMutateMap(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
-  Expected<Any> result = MaybeInplaceMutateMapValuesExpected(
+  return MaybeInplaceMutateMapValuesRaw(
       mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<MapObj>(value));
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
 }
 
 /*! \brief Structural mutation hook for DictObj. */
 TVMFFIAny MutateDict(StructuralMutatorObj* mutator, AnyView value) noexcept {
-  Expected<Any> result = MutateMapValuesExpected(
+  return MutateMapValuesRaw(
       mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const DictObj>(value));
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
 }
 
 /*! \brief Maybe-in-place structural mutation hook for DictObj. */
 TVMFFIAny MaybeInplaceMutateDict(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
-  Expected<Any> result = MaybeInplaceMutateMapValuesExpected(
+  return MaybeInplaceMutateMapValuesRaw(
       mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<DictObj>(value));
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
 }
 }  // namespace details
 
diff --git a/src/ffi/extra/structural_visit.cc 
b/src/ffi/extra/structural_visit.cc
index 95bf80d8..a0ca4d9d 100644
--- a/src/ffi/extra/structural_visit.cc
+++ b/src/ffi/extra/structural_visit.cc
@@ -87,7 +87,7 @@ Expected<Optional<VisitInterrupt>> StructuralWalkExpected(
 /*! \brief Visit entries in a sequence container. */
 TVMFFIAny VisitSeqContainer(StructuralVisitorObj* visitor, const SeqBaseObj* 
self) noexcept {
   for (const Any& item : *self) {
-    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(item), self);
+    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(item));
   }
   return 
ExpectedUnsafe::MoveToTVMFFIAny(Expected<Optional<VisitInterrupt>>(std::nullopt));
 }
@@ -95,7 +95,7 @@ TVMFFIAny VisitSeqContainer(StructuralVisitorObj* visitor, 
const SeqBaseObj* sel
 /*! \brief Visit values in a map container while treating keys as structural 
anchors. */
 TVMFFIAny VisitMapContainer(StructuralVisitorObj* visitor, const MapBaseObj* 
self) noexcept {
   for (const auto& kv : *self) {
-    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(kv.second), 
self);
+    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(kv.second));
   }
   return 
ExpectedUnsafe::MoveToTVMFFIAny(Expected<Optional<VisitInterrupt>>(std::nullopt));
 }
diff --git a/tests/cpp/extra/test_structural_mutate.cc 
b/tests/cpp/extra/test_structural_mutate.cc
index c5ae8599..162cb38d 100644
--- a/tests/cpp/extra/test_structural_mutate.cc
+++ b/tests/cpp/extra/test_structural_mutate.cc
@@ -401,4 +401,29 @@ TEST(StructuralMap, 
HandlesInlineAndHeapStringAndBytesLeaves) {
   CheckStringAndBytesLeaves<WalkOrder::kPostOrder>();
 }
 
+// The dynamic mutator duplicates the static one's walk deliberately, so the 
semantics they
+// share need coverage on this copy too. Driven through the same entry point 
Python uses.
+Any CallDynStructuralMap(AnyView root, const Array<Tuple<int32_t, Function>>& 
callbacks,
+                         WalkOrder order) {
+  Function fn = Function::GetGlobalRequired("ffi.StructuralMap");
+  return fn(root, callbacks, Array<Tuple<int32_t, Function>>(), 
static_cast<int32_t>(order));
+}
+
+TEST(StructuralMapDyn, ReusesRemapResultForRepeatedVar) {
+  // A FreeVar maps once and every later occurrence reuses that result. Both 
mutators share this
+  // half of the walk, so it must hold identically here.
+  TVar var("x");
+  AnyArray root{Any(var), Any(var)};
+  int64_t calls = 0;
+  Function remap = Function::FromTyped([&](AnyView v) -> Any {
+    ++calls;
+    return Any(TVar(v.cast<TVar>()->name + "-mapped"));
+  });
+  Any mapped = CallDynStructuralMap(
+      root, {Tuple<int32_t, Function>(TVarObj::RuntimeTypeIndex(), remap)}, 
WalkOrder::kPostOrder);
+  auto arr = mapped.cast<AnyArray>();
+  EXPECT_EQ(calls, 1);
+  EXPECT_TRUE(arr[0].cast<TVar>().same_as(arr[1].cast<TVar>()));
+}
+
 }  // namespace
diff --git a/tests/cpp/extra/test_structural_visit.cc 
b/tests/cpp/extra/test_structural_visit.cc
index 6ac34a43..eeb71fc6 100644
--- a/tests/cpp/extra/test_structural_visit.cc
+++ b/tests/cpp/extra/test_structural_visit.cc
@@ -429,6 +429,31 @@ TEST(StructuralVisitor, WalkReturnsError) {
   EXPECT_EQ(result.error().message(), "walk callback failed");
 }
 
+// A callback that fails on a primitive node must surface the error, not abort.
+//
+// On failure the engine names the node it dispatched on in the visit error 
context. A visited
+// node is not always object-backed -- a container element or reflected field 
may be a primitive --
+// and `AnyView::cast<ObjectRef>()` throws on one. The walk is `noexcept`, so 
naming the node
+// without a type-index guard terminates the process instead of returning the 
error. Every other
+// error test above fails on an ObjectRef node, so only a primitive exercises 
that guard.
+TEST(StructuralVisitor, WalkReturnsErrorOnPrimitiveNode) {
+  Array<Any> root = {Any(static_cast<int64_t>(1))};
+  auto fail = [](int64_t) -> Expected<WalkResult> {
+    return Unexpected(Error("ValueError", "walk callback failed on primitive", 
""));
+  };
+
+  Expected<Optional<VisitInterrupt>> pre = 
StructuralWalkExpected<WalkOrder::kPreOrder>(root, fail);
+  ASSERT_TRUE(pre.is_err());
+  EXPECT_EQ(pre.error().kind(), "ValueError");
+  EXPECT_EQ(pre.error().message(), "walk callback failed on primitive");
+
+  Expected<Optional<VisitInterrupt>> post =
+      StructuralWalkExpected<WalkOrder::kPostOrder>(root, fail);
+  ASSERT_TRUE(post.is_err());
+  EXPECT_EQ(post.error().kind(), "ValueError");
+  EXPECT_EQ(post.error().message(), "walk callback failed on primitive");
+}
+
 TEST(StructuralVisitor, WalkCatchesError) {
   ObjectRef root = TVar("root");
 
diff --git a/tests/cpp/testing_object.h b/tests/cpp/testing_object.h
index 32e1e917..b5cf64e0 100644
--- a/tests/cpp/testing_object.h
+++ b/tests/cpp/testing_object.h
@@ -249,20 +249,16 @@ class TMutatePairObj : public Object {
     return count;
   }
 
-  static Expected<Any> StructuralMutateExpected(StructuralMutatorObj* mutator,
-                                                AnyView value) noexcept {
+  static TVMFFIAny StructuralMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
     ++StructuralMutateCallCount();
     const auto* self = value.cast<const TMutatePairObj*>();
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ObjectRef, lhs, 
mutator->MutateExpected(self->lhs), self);
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ObjectRef, rhs, 
mutator->MutateExpected(self->rhs), self);
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ObjectRef, lhs, 
mutator->MutateExpected(self->lhs));
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ObjectRef, rhs, 
mutator->MutateExpected(self->rhs));
     if (lhs.same_as(self->lhs) && rhs.same_as(self->rhs)) {
-      return Any(value);
+      return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
     }
-    return Any(ObjectRef(make_object<TMutatePairObj>(std::move(lhs), 
std::move(rhs))));
-  }
-
-  static TVMFFIAny StructuralMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
-    return 
details::ExpectedUnsafe::MoveToTVMFFIAny(StructuralMutateExpected(mutator, 
value));
+    return details::AnyUnsafe::MoveAnyToTVMFFIAny(
+        Any(make_object<TMutatePairObj>(std::move(lhs), std::move(rhs))));
   }
 
   static void RegisterReflection() {
@@ -413,13 +409,11 @@ class TFuncObj : public Object {
   static TVMFFIAny StructuralVisit(StructuralVisitorObj* visitor, AnyView 
value) noexcept {
     const auto* self = value.cast<const TFuncObj*>();
 
-    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(
-        visitor->WithDefRegionKind(kTVMFFIDefRegionKindRecursive,
-                                   [&]() { return 
visitor->VisitExpected(self->params); }),
-        self);
+    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->WithDefRegionKind(
+        kTVMFFIDefRegionKindRecursive, [&]() { return 
visitor->VisitExpected(self->params); }));
 
     auto body_result = visitor->VisitExpected(self->body);
-    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(body_result, self);
+    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(body_result);
     return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(body_result));
   }
 

Reply via email to