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 be35ec12 [PERF] Reduce structural traversal result overhead (#780)
be35ec12 is described below
commit be35ec1297d6ddf388b7feb4f2e89d2cf030dda8
Author: Tianqi Chen <[email protected]>
AuthorDate: Sat Sep 12 11:00:51 2026 -0400
[PERF] Reduce structural traversal result overhead (#780)
Keep compiled structural visit hooks on the raw TVMFFIAny path through
default descent and callback selection to avoid repeated result
materialization. Keep error-context decoration off the success path,
inline WalkResult lifecycle operations, and retain typed Parent
customization while preserving callback order, descent control, errors
and interrupt ownership.
---
include/tvm/ffi/extra/structural_visit.h | 187 ++++++++++++++-----------------
tests/cpp/extra/test_structural_visit.cc | 4 -
2 files changed, 83 insertions(+), 108 deletions(-)
diff --git a/include/tvm/ffi/extra/structural_visit.h
b/include/tvm/ffi/extra/structural_visit.h
index 1aa95bb7..a1f1d55a 100644
--- a/include/tvm/ffi/extra/structural_visit.h
+++ b/include/tvm/ffi/extra/structural_visit.h
@@ -221,57 +221,46 @@ class StructuralVisitorObj : public Object {
* \return Expected interrupt state. An error means traversal failed.
*/
TVM_FFI_INLINE Expected<Optional<VisitInterrupt>>
DefaultVisitExpected(AnyView value) noexcept {
- int32_t type_index = value.type_index();
- static reflection::TypeAttrColumn
column(reflection::type_attr::kStructuralVisit);
- AnyView attr = column[type_index];
+ return
details::ExpectedUnsafe::MoveFromTVMFFIAny<Optional<VisitInterrupt>>(
+ DefaultVisitRaw(value));
+ }
- // 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*>());
- TVMFFIAny raw = (*visit_fn)(this, value);
- return
details::ExpectedUnsafe::MoveFromTVMFFIAny<Optional<VisitInterrupt>>(raw);
+ /// \cond Doxygen_Suppress
+ static constexpr const bool _type_mutable = true;
+ TVM_FFI_DECLARE_OBJECT_INFO("ffi.StructuralVisitor", StructuralVisitorObj,
Object);
+ /// \endcond
+
+ private:
+ /*! \brief Internal raw ABI descent, avoiding a typed result around compiled
hooks. */
+ TVM_FFI_INLINE TVMFFIAny DefaultVisitRaw(AnyView value) noexcept {
+ static reflection::TypeAttrColumn
column(reflection::type_attr::kStructuralVisit);
+ AnyView attr = column[value.type_index()];
+ if (TVM_FFI_PREDICT_TRUE(attr.type_index() ==
TypeIndex::kTVMFFIOpaquePtr)) {
+ return (*reinterpret_cast<FStructuralVisit>(attr.cast<void*>()))(this,
value);
}
+ return DefaultVisitRawTail(value, attr);
+ }
- // case 2: Type-specific override registered as an ffi::Function.
+ /*! \brief Handle Function hooks and reflected descent outside the
compiled-hook path. */
+ TVMFFIAny DefaultVisitRawTail(AnyView value, AnyView attr) noexcept {
if (attr.type_index() == TypeIndex::kTVMFFIFunction) {
- return
attr.cast<Function>().CallExpected<Optional<VisitInterrupt>>(this, value);
+ return details::ExpectedUnsafe::MoveToTVMFFIAny(
+ attr.cast<Function>().CallExpected<Optional<VisitInterrupt>>(this,
value));
}
-
if (TVM_FFI_PREDICT_FALSE(attr.type_index() != TypeIndex::kTVMFFINone)) {
- return Unexpected(
- Error("TypeError", "__s_visit__ must be an opaque function pointer
or ffi.Function", ""));
+ return details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Expected<Optional<VisitInterrupt>>(Unexpected(Error(
+ "TypeError", "__s_visit__ must be an opaque function pointer or
ffi.Function", ""))));
}
-
- if (type_index < TypeIndex::kTVMFFIStaticObjectBegin) {
- return Optional<VisitInterrupt>(std::nullopt);
+ if (value.type_index() < TypeIndex::kTVMFFIStaticObjectBegin) {
+ return details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Expected<Optional<VisitInterrupt>>(std::nullopt));
}
-
- return details::VisitReflectedFieldsExpected(this, value.cast<const
Object*>());
+ return details::ExpectedUnsafe::MoveToTVMFFIAny(
+ details::VisitReflectedFieldsExpected(this, value.cast<const
Object*>()));
}
- /// \cond Doxygen_Suppress
- static constexpr const bool _type_mutable = true;
- TVM_FFI_DECLARE_OBJECT_INFO("ffi.StructuralVisitor", StructuralVisitorObj,
Object);
- /// \endcond
-
protected:
- /*!
- * \brief Redirect raw ABI descent to \ref DefaultVisitExpected.
- *
- * A visitor layer that overrides either descent form must redeclare both in
- * the same layer so dependent member lookup reaches the paired override. The
- * raw form is deliberately retained for ABI traversal and permits a layer to
- * forward raw storage without rematerializing a typed ``Expected``.
- *
- * \param value The value to descend into.
- * \return Raw ``Expected<Optional<VisitInterrupt>>`` storage produced by
- * ``details::ExpectedUnsafe::MoveToTVMFFIAny``. The caller
- * reinterprets this storage without a runtime type check.
- */
- TVM_FFI_INLINE TVMFFIAny DefaultVisitRaw(AnyView value) noexcept {
- return
details::ExpectedUnsafe::MoveToTVMFFIAny(DefaultVisitExpected(value));
- }
-
/*! \brief Return the state references maintained by this visitor layer. */
TVM_FFI_INLINE StateTupleType StateTuple() const noexcept { return {}; }
@@ -340,6 +329,13 @@ TVM_FFI_INLINE bool
StructuralVisitRawNeedEarlyReturn(const TVMFFIAny& result) n
return result.type_index != TypeIndex::kTVMFFINone;
}
+// Keep the raw result in registers on success; only error decoration takes
its address.
+TVM_FFI_COLD_CODE inline TVMFFIAny
AttachStructuralVisitErrorContextRaw(TVMFFIAny result,
+
AnyView value) noexcept {
+ UpdateVisitErrorContext(result, value);
+ return result;
+}
+
/*!
* \brief Walk reflected structural fields of object-backed \p obj.
*
@@ -418,25 +414,33 @@ class WalkResult : public Variant<VisitInterrupt,
int32_t> {
/*! \brief The underlying ``Variant`` used as storage. */
using Storage = Variant<VisitInterrupt, int32_t>;
+ /// \cond Doxygen_Suppress
+ TVM_FFI_INLINE ~WalkResult() = default;
+ TVM_FFI_INLINE WalkResult(const WalkResult&) = default;
+ TVM_FFI_INLINE WalkResult(WalkResult&&) noexcept = default;
+ TVM_FFI_INLINE WalkResult& operator=(const WalkResult&) = default;
+ TVM_FFI_INLINE WalkResult& operator=(WalkResult&&) noexcept = default;
+ /// \endcond
+
/*! \brief Continue traversal and visit this node's children. */
- static WalkResult Advance() { return WalkResult(kAdvanceTag); }
+ TVM_FFI_INLINE static WalkResult Advance() { return WalkResult(kAdvanceTag);
}
/*! \brief Continue traversal but skip this node's children. */
- static WalkResult Skip() { return WalkResult(kSkipTag); }
+ TVM_FFI_INLINE static WalkResult Skip() { return WalkResult(kSkipTag); }
/*!
* \brief Halt the walk and propagate an interrupt.
* \param signal The interrupt to propagate. Defaults to an interrupt with
* FFI None payload.
*/
- static WalkResult Interrupt(VisitInterrupt signal = VisitInterrupt()) {
+ TVM_FFI_INLINE static WalkResult Interrupt(VisitInterrupt signal =
VisitInterrupt()) {
return WalkResult(Storage(std::move(signal)));
}
private:
// Keep raw storage construction behind the named factories.
- explicit WalkResult(int32_t tag) : Storage(tag) {}
- explicit WalkResult(Storage storage) : Storage(std::move(storage)) {}
+ TVM_FFI_INLINE explicit WalkResult(int32_t tag) : Storage(tag) {}
+ TVM_FFI_INLINE explicit WalkResult(Storage storage) :
Storage(std::move(storage)) {}
friend struct TypeTraits<WalkResult>;
};
@@ -539,21 +543,12 @@ namespace details {
* ``StateTupleType``, accepts and forwards ``const StructuralVisitorVTable*``
* in its constructor, and provides an at-least-protected
* ``StateTuple() const noexcept`` that returns ``StateTupleType`` by value.
The
- * state references in that tuple must outlive the traversal. A layer
overriding
- * either ``DefaultVisitExpected`` or ``DefaultVisitRaw`` declares both forms,
- * at least protected, in that same class. The raw form must return raw
- * ``Expected<Optional<VisitInterrupt>>`` storage produced by
- * ``details::ExpectedUnsafe::MoveToTVMFFIAny``; the engine propagates it
without
- * a runtime type check. This deliberate pair keeps the raw ABI path available
- * without rematerializing a typed ``Expected``. Engine calls use ``Parent::``
- * qualification; this is static layer dispatch, not virtual dispatch. A layer
- * must still define its own raw boilerplate because boilerplate inherited from
- * a base resolves its unqualified typed call in that base's scope.
+ * state references in that tuple must outlive the traversal. A layer
customizes
+ * default descent by overriding ``DefaultVisitExpected``, at least protected.
+ * Engine calls use ``Parent::`` qualification; this is static layer dispatch,
+ * not virtual dispatch.
*
- * \tparam Parent Traversal layer extended by the engine. Each layer that
- * customizes typed descent must define its own ``Default*Raw``
- * boilerplate; inherited boilerplate resolves its unqualified
- * typed call in the base layer's scope. Engine protocol and
+ * \tparam Parent Traversal layer extended by the engine. Engine protocol and
* descent calls are ``Parent::``-qualified.
* \tparam order Callback placement relative to child traversal.
* \tparam Callbacks Callback links, tested in declaration order.
@@ -682,10 +677,11 @@ class StructuralWalkEngine : public Parent {
}
{
- TVMFFIAny result = Parent::DefaultVisitRaw(value);
+ TVMFFIAny result =
+
details::ExpectedUnsafe::MoveToTVMFFIAny(Parent::DefaultVisitExpected(value));
if
(TVM_FFI_PREDICT_FALSE(details::StructuralVisitRawNeedEarlyReturn(result))) {
if (TVM_FFI_PREDICT_FALSE(result.type_index ==
TypeIndex::kTVMFFIError)) {
- details::UpdateVisitErrorContext(result, value);
+ return details::AttachStructuralVisitErrorContextRaw(result, value);
}
return result;
}
@@ -772,11 +768,9 @@ Optional<VisitInterrupt> StructuralWalk(AnyView root,
Callbacks&&... callbacks)
* typed callback fold preserves declaration-order first match and converts an
* ``Error`` thrown by a matched callback into the visit result.
*
- * \tparam Parent Traversal layer extended by the engine. Each layer that
- * customizes typed descent must define its own ``Default*Raw``
- * boilerplate; inherited boilerplate resolves its unqualified
- * typed call in the base layer's scope. Engine protocol and
- * descent calls are ``Parent::``-qualified.
+ * \tparam Parent Traversal layer extended by the engine. Default descent uses
+ * ``Parent::DefaultVisitExpected``; a layer can override this
+ * method to customize descent.
* \tparam Callbacks Callable types whose first parameter selects the
dispatched value type.
*/
template <typename Parent, typename... Callbacks>
@@ -823,28 +817,21 @@ class StructuralVisitEngine : public Parent {
return details::ExpectedUnsafe::MoveToTVMFFIAny(
Expected<Optional<VisitInterrupt>>(std::nullopt));
}
- if (std::optional<Expected<Optional<VisitInterrupt>>> matched =
DispatchCallbacks(value)) {
- // The matched callback already traversed as much of `value` as it
wanted, so its
- // result is final and the engine does not descend on its own.
- Expected<Optional<VisitInterrupt>> result = *std::move(matched);
- if (TVM_FFI_PREDICT_FALSE(result.is_err())) {
- Error err = result.error();
- details::UpdateVisitErrorContext(err, value);
- }
- return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+ TVMFFIAny result;
+ if (!TryLinks(value, &result, std::index_sequence_for<Callbacks...>{})) {
+ // Only an unmatched value uses the Parent layer's default descent. A
matched
+ // callback already traversed as much of the value as it wanted.
+ result =
details::ExpectedUnsafe::MoveToTVMFFIAny(Parent::DefaultVisitExpected(value));
}
- // No callback claimed `value`. The Parent layer owns default descent.
- TVMFFIAny result = Parent::DefaultVisitRaw(value);
if (TVM_FFI_PREDICT_FALSE(result.type_index == TypeIndex::kTVMFFIError)) {
- details::UpdateVisitErrorContext(result, value);
+ return details::AttachStructuralVisitErrorContextRaw(result, value);
}
return result;
}
- /*! \brief Try one typed callback and preserve Error as an expected result.
*/
+ /*! \brief Try one typed callback, storing its raw result only when it
matches. */
template <typename Callback>
- TVM_FFI_INLINE std::optional<Expected<Optional<VisitInterrupt>>>
TryLink(Callback& callback,
-
AnyView value) noexcept {
+ inline bool TryLink(Callback& callback, AnyView value, TVMFFIAny* out)
noexcept {
using FuncInfo = details::FunctionInfo<std::decay_t<Callback>>;
static_assert(FuncInfo::num_args == 2, "StructuralVisit callback takes
(value, visitor)");
using FirstArg = std::tuple_element_t<0, typename FuncInfo::ArgType>;
@@ -857,38 +844,30 @@ class StructuralVisitEngine : public Parent {
auto* visitor = static_cast<typename Parent::VisitorObjType*>(this);
try {
if constexpr (std::is_same_v<TSub, AnyView>) {
- return callback(value, visitor);
+ *out = details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Expected<Optional<VisitInterrupt>>(callback(value, visitor)));
+ return true;
} else if constexpr (std::is_same_v<TSub, Any>) {
- return callback(Any(value), visitor);
+ *out = details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Expected<Optional<VisitInterrupt>>(callback(Any(value), visitor)));
+ return true;
} else if (auto matched = value.template as<TSub>()) {
- return callback(*std::move(matched), visitor);
+ *out = details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Expected<Optional<VisitInterrupt>>(callback(*std::move(matched),
visitor)));
+ return true;
}
} catch (const Error& err) {
- return Unexpected(err);
+ *out = details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Expected<Optional<VisitInterrupt>>(Unexpected(err)));
+ return true;
}
- return std::nullopt;
+ return false;
}
/*! \brief Fold this engine's callback tuple in declaration order. */
template <size_t... Is>
- TVM_FFI_INLINE std::optional<Expected<Optional<VisitInterrupt>>> TryLinks(
- AnyView value, std::index_sequence<Is...>) noexcept {
- std::optional<Expected<Optional<VisitInterrupt>>> result;
- (... || (result = TryLink(std::get<Is>(callbacks_), value)).has_value());
- return result;
- }
-
- /*!
- * \brief Run the callback chain on \p value.
- * \param value The value to dispatch on.
- * \return The matched callback's result, or an empty optional when none
matched.
- *
- * \note An unmatched value is reported as such rather than folded into a
"continue"
- * result: the engine has to tell "the callback chose to stop here" apart
from "no
- * callback claimed this value".
- */
- std::optional<Expected<Optional<VisitInterrupt>>> DispatchCallbacks(AnyView
value) noexcept {
- return TryLinks(value, std::index_sequence_for<Callbacks...>{});
+ TVM_FFI_INLINE bool TryLinks(AnyView value, TVMFFIAny* out,
std::index_sequence<Is...>) noexcept {
+ return (TryLink(std::get<Is>(callbacks_), value, out) || ...);
}
/*! \brief Typed callbacks tested in declaration order, first match wins. */
diff --git a/tests/cpp/extra/test_structural_visit.cc
b/tests/cpp/extra/test_structural_visit.cc
index 3319cbfd..7c015053 100644
--- a/tests/cpp/extra/test_structural_visit.cc
+++ b/tests/cpp/extra/test_structural_visit.cc
@@ -98,10 +98,6 @@ class StructuralWalkWithVisitCount : public Parent {
}
protected:
- TVM_FFI_INLINE TVMFFIAny DefaultVisitRaw(AnyView value) noexcept {
- return
details::ExpectedUnsafe::MoveToTVMFFIAny(DefaultVisitExpected(value));
- }
-
TVM_FFI_INLINE StateTupleType StateTuple() const noexcept {
return std::tie(visit_count_, visit_tag_);
}