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 70e5af34 [PERF] Forward single StructuralMutate callbacks directly
(#774)
70e5af34 is described below
commit 70e5af34b162096340695c583b11c197f3a89595
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Sep 10 17:37:36 2026 -0400
[PERF] Forward single StructuralMutate callbacks directly (#774)
Forward single StructuralMutate callback results directly and use
bool-match/result-output dispatch for multiple callbacks. Preserve
owning conversions, typed fallback, first-match behavior, and cold
error-context attachment.
---
include/tvm/ffi/extra/structural_mutate.h | 138 ++++++++++++++++++++++--------
tests/cpp/extra/test_structural_mutate.cc | 14 +++
2 files changed, 115 insertions(+), 37 deletions(-)
diff --git a/include/tvm/ffi/extra/structural_mutate.h
b/include/tvm/ffi/extra/structural_mutate.h
index cf980088..a50ea694 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -579,11 +579,18 @@ class StructuralMutatorObj : public Object {
result = DefaultMutateRawTail(value, attr);
}
if (TVM_FFI_PREDICT_FALSE(result.type_index == TypeIndex::kTVMFFIError)) {
- details::UpdateVisitErrorContext(result, value);
+ return AttachVisitErrorContextRaw(result, value);
}
return result;
}
+ /*! \brief Keep address-taking for error decoration off the successful
raw-result path. */
+ TVM_FFI_COLD_CODE static TVMFFIAny AttachVisitErrorContextRaw(TVMFFIAny
result,
+ AnyView value)
noexcept {
+ details::UpdateVisitErrorContext(result, value);
+ return result;
+ }
+
/*! \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) {
@@ -654,7 +661,7 @@ class StructuralMutatorObj : public Object {
// 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 AttachVisitErrorContextRaw(result, value);
}
return result;
}
@@ -670,7 +677,7 @@ class StructuralMutatorObj : public Object {
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 AttachVisitErrorContextRaw(result, value);
}
return result;
}
@@ -1580,19 +1587,29 @@ class StructuralMutateEngine : public Parent {
/*! \brief Dispatch ordinary mutation from the erased mutator pointer. */
static TVMFFIAny DispatchMutate(StructuralMutatorObj* mutator, AnyView
value) noexcept {
- return static_cast<StructuralMutateEngine*>(mutator)->MutateImplRaw(value);
+ auto* self = static_cast<StructuralMutateEngine*>(mutator);
+ if constexpr (sizeof...(Callbacks) == 1) {
+ return self->template MutateSingleCallbackRaw<false>(value);
+ } else {
+ return self->MutateImplRaw(value);
+ }
}
/*! \brief Dispatch maybe-in-place mutation from the erased mutator pointer.
*/
static TVMFFIAny DispatchMaybeInplaceMutate(StructuralMutatorObj* mutator,
AnyView value) noexcept {
- return
static_cast<StructuralMutateEngine*>(mutator)->MaybeInplaceMutateImplRaw(value);
+ auto* self = static_cast<StructuralMutateEngine*>(mutator);
+ if constexpr (sizeof...(Callbacks) == 1) {
+ return self->template MutateSingleCallbackRaw<true>(value);
+ } else {
+ return self->MaybeInplaceMutateImplRaw(value);
+ }
}
/*! \brief Mutate one value, handing a matched callback ownership of
descent. */
TVMFFIAny MutateImplRaw(AnyView value) noexcept {
- if (std::optional<Expected<Any>> matched = DispatchCallbacks(value,
false)) {
- Expected<Any> result = *std::move(matched);
+ Expected<Any> result{Any()};
+ if (DispatchCallbacks(value, false, &result)) {
if (TVM_FFI_PREDICT_FALSE(result.is_err())) {
// Keep callback-boundary context in addition to the default-descent
// context: a callback may return a rebuilt value, so the two nodes
can differ.
@@ -1605,8 +1622,8 @@ class StructuralMutateEngine : public Parent {
/*! \brief Maybe mutate one value in place, with callback-owned descent. */
TVMFFIAny MaybeInplaceMutateImplRaw(AnyView value) noexcept {
- if (std::optional<Expected<Any>> matched = DispatchCallbacks(value, true))
{
- Expected<Any> result = *std::move(matched);
+ Expected<Any> result{Any()};
+ if (DispatchCallbacks(value, true, &result)) {
if (TVM_FFI_PREDICT_FALSE(result.is_err())) {
// Keep callback-boundary context in addition to the default-descent
// context: a callback may return a rebuilt value, so the two nodes
can differ.
@@ -1618,16 +1635,14 @@ class StructuralMutateEngine : public Parent {
Parent::DefaultMaybeInplaceMutateExpected(value));
}
- /*! \brief Try one typed callback and preserve Error as an expected result.
*/
- template <typename Callback>
- TVM_FFI_INLINE std::optional<Expected<Any>> TryLink(Callback& callback,
AnyView value,
- bool allow_inplace)
noexcept {
+ /*! \brief Invoke a matched callback with the Parent view and preserve
returned/thrown Error. */
+ template <typename Callback, typename Matched>
+ TVM_FFI_INLINE Expected<Any> InvokeCallback(Callback& callback, Matched&&
matched,
+ bool allow_inplace) noexcept {
using FuncInfo = details::FunctionInfo<std::decay_t<Callback>>;
static_assert(FuncInfo::num_args == 2 || FuncInfo::num_args == 3,
"StructuralMutate callback must take (value, mutator) or "
"(value, mutator, allow_inplace)");
- using FirstArg = std::tuple_element_t<0, typename FuncInfo::ArgType>;
- using TSub = std::remove_cv_t<std::remove_reference_t<FirstArg>>;
using SecondArg = std::decay_t<std::tuple_element_t<1, typename
FuncInfo::ArgType>>;
using Second = std::remove_pointer_t<SecondArg>;
static_assert(std::is_same_v<Second, typename Parent::MutatorObjType>,
@@ -1639,39 +1654,88 @@ class StructuralMutateEngine : public Parent {
"third StructuralMutate callback argument must be bool");
}
auto* mutator = static_cast<typename Parent::MutatorObjType*>(this);
- auto invoke = [&](auto&& matched) -> Expected<Any> {
- try {
- if constexpr (FuncInfo::num_args == 3) {
- return callback(std::forward<decltype(matched)>(matched), mutator,
allow_inplace);
- } else {
- return callback(std::forward<decltype(matched)>(matched), mutator);
- }
- } catch (Error& err) {
- return Unexpected(std::move(err));
+ try {
+ if constexpr (FuncInfo::num_args == 3) {
+ return callback(std::forward<Matched>(matched), mutator,
allow_inplace);
+ } else {
+ return callback(std::forward<Matched>(matched), mutator);
}
- };
+ } catch (Error& err) {
+ return Unexpected(std::move(err));
+ }
+ }
+
+ /*! \brief Materialize the callback result only on the cold Parent
error-decoration path. */
+ TVM_FFI_COLD_CODE TVMFFIAny AnnotateCallbackErrorRaw(TVMFFIAny raw, AnyView
value) noexcept {
+ Expected<Any> result =
details::ExpectedUnsafe::MoveFromTVMFFIAny<Any>(raw);
+ Parent::UpdateVisitErrorContext(result, value);
+ return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+ }
+
+ // One callback forwards its result directly, without a callback-chain
envelope.
+ // AnyView/Any always match; only a typed miss needs the Parent's default
descent.
+ template <bool kMaybeInplace>
+ TVM_FFI_INLINE TVMFFIAny MutateSingleCallbackRaw(AnyView value) noexcept {
+ auto& callback = std::get<0>(callbacks_);
+ using FuncInfo = details::FunctionInfo<std::decay_t<decltype(callback)>>;
+ using FirstArg = std::tuple_element_t<0, typename FuncInfo::ArgType>;
+ using TSub = std::remove_cv_t<std::remove_reference_t<FirstArg>>;
+ TVMFFIAny result;
if constexpr (std::is_same_v<TSub, AnyView>) {
- return invoke(value);
+ result =
+ details::ExpectedUnsafe::MoveToTVMFFIAny(InvokeCallback(callback,
value, kMaybeInplace));
} else if constexpr (std::is_same_v<TSub, Any>) {
- return invoke(Any(value));
+ result = details::ExpectedUnsafe::MoveToTVMFFIAny(
+ InvokeCallback(callback, Any(value), kMaybeInplace));
} else if (auto matched = value.template as<TSub>()) {
- return invoke(*std::move(matched));
+ result = details::ExpectedUnsafe::MoveToTVMFFIAny(
+ InvokeCallback(callback, *std::move(matched), kMaybeInplace));
+ } else {
+ if constexpr (kMaybeInplace) {
+ return details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Parent::DefaultMaybeInplaceMutateExpected(value));
+ } else {
+ return
details::ExpectedUnsafe::MoveToTVMFFIAny(Parent::DefaultMutateExpected(value));
+ }
}
- return std::nullopt;
+ // Release any owning match before naming the callback boundary, as in the
general path.
+ if (TVM_FFI_PREDICT_FALSE(result.type_index == TypeIndex::kTVMFFIError)) {
+ return AnnotateCallbackErrorRaw(result, value);
+ }
+ return result;
+ }
+
+ /*! \brief Write a matched callback result, leaving out untouched on a type
miss. */
+ template <typename Callback>
+ TVM_FFI_INLINE bool TryLink(Callback& callback, AnyView value, bool
allow_inplace,
+ Expected<Any>* out) noexcept {
+ using FuncInfo = details::FunctionInfo<std::decay_t<Callback>>;
+ 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>) {
+ *out = InvokeCallback(callback, value, allow_inplace);
+ return true;
+ } else if constexpr (std::is_same_v<TSub, Any>) {
+ *out = InvokeCallback(callback, Any(value), allow_inplace);
+ return true;
+ } else if (auto matched = value.template as<TSub>()) {
+ *out = InvokeCallback(callback, *std::move(matched), allow_inplace);
+ return true;
+ }
+ return false;
}
/*! \brief Fold callbacks in declaration order, stopping at the first match.
*/
template <size_t... Is>
- TVM_FFI_INLINE std::optional<Expected<Any>> TryLinks(AnyView value, bool
allow_inplace,
-
std::index_sequence<Is...>) noexcept {
- std::optional<Expected<Any>> result;
- (... || (result = TryLink(std::get<Is>(callbacks_), value,
allow_inplace)).has_value());
- return result;
+ TVM_FFI_INLINE bool TryLinks(AnyView value, bool allow_inplace,
Expected<Any>* out,
+ std::index_sequence<Is...>) noexcept {
+ return (TryLink(std::get<Is>(callbacks_), value, allow_inplace, out) ||
...);
}
- /*! \brief Run the callback chain, or return empty when no callback matched.
*/
- std::optional<Expected<Any>> DispatchCallbacks(AnyView value, bool
allow_inplace) noexcept {
- return TryLinks(value, allow_inplace,
std::index_sequence_for<Callbacks...>{});
+ /*! \brief Run the callback chain, returning whether a callback matched. */
+ TVM_FFI_INLINE bool DispatchCallbacks(AnyView value, bool allow_inplace,
+ Expected<Any>* out) noexcept {
+ return TryLinks(value, allow_inplace, out,
std::index_sequence_for<Callbacks...>{});
}
/*! \brief Typed callbacks tested in declaration order, first match wins. */
diff --git a/tests/cpp/extra/test_structural_mutate.cc
b/tests/cpp/extra/test_structural_mutate.cc
index f4c80a72..dd254c80 100644
--- a/tests/cpp/extra/test_structural_mutate.cc
+++ b/tests/cpp/extra/test_structural_mutate.cc
@@ -432,6 +432,20 @@ TEST(StructuralMutate, CallbackControlsRecursion) {
EXPECT_TRUE(mapped->rhs.same_as(original_rhs));
}
+TEST(StructuralMutate, SingleCallbackCanDelegateToDefault) {
+ auto mutate = [](AnyView value, StructuralMutatorObj* mutator) ->
Expected<UnchangedOr<Any>> {
+ if (auto integer = value.as<int64_t>()) {
+ return Any(*integer + 1);
+ }
+ return mutator->DefaultMutateExpected(value);
+ };
+ AnyArray root{int64_t{1}, AnyArray{int64_t{2}}};
+ AnyArray result = StructuralMutate(root, mutate).cast<AnyArray>();
+ EXPECT_EQ(result[0].cast<int64_t>(), 2);
+ EXPECT_EQ(result[1].cast<AnyArray>()[0].cast<int64_t>(), 3);
+ EXPECT_EQ(root[0].cast<int64_t>(), 1);
+}
+
TEST(StructuralMutate, PreservesUniqueContainerIdentity) {
AnyArray inner{int64_t{1}};
const Object* inner_address = inner.get();