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 0af62893 [FEAT] Add typed native reflection hooks with ABI dispatch
(#821)
0af62893 is described below
commit 0af62893f8033b65c3f424a0dbfd3138b5cbe5d4
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Sep 27 21:14:34 2026 -0400
[FEAT] Add typed native reflection hooks with ABI dispatch (#821)
Add `NativeFunctionView<Expected<R>(Args...)>` for reflection hooks with
a context-free native ABI adapter and a borrowed packed-function
fallback. Add an `Any`-backed owning `NativeFunction` with TypeTraits
support.
Use typed native hooks for structural mutation and visitation while
retaining the raw `TVMFFIAny` vtable ABI. Carry `Expected` results
through the sequence, map, and registered hook bodies.
Native targets bind at compile time with `FromNative<&Fn>()`; packed
hooks remain available for frontend registration.
---
include/tvm/ffi/any.h | 3 +-
include/tvm/ffi/expected.h | 55 -----
include/tvm/ffi/extra/structural_mutate.h | 61 +++---
include/tvm/ffi/extra/structural_visit.h | 36 ++--
include/tvm/ffi/reflection/native_function.h | 310 +++++++++++++++++++++++++++
include/tvm/ffi/reflection/registry.h | 2 +-
src/ffi/extra/structural_mutate.cc | 119 +++++-----
src/ffi/extra/structural_visit.cc | 46 ++--
tests/cpp/extra/test_structural_mutate.cc | 17 +-
tests/cpp/test_expected_checks.cc | 17 +-
tests/cpp/test_native_function_view.cc | 100 +++++++++
tests/cpp/testing_object.h | 18 +-
12 files changed, 561 insertions(+), 223 deletions(-)
diff --git a/include/tvm/ffi/any.h b/include/tvm/ffi/any.h
index bd077b24..6226c749 100644
--- a/include/tvm/ffi/any.h
+++ b/include/tvm/ffi/any.h
@@ -337,7 +337,7 @@ class Any {
* \tparam T The value type of the other
*/
template <typename T, typename =
std::enable_if_t<TypeTraits<T>::convert_enabled>>
- Any(T other) { // NOLINT(*)
+ TVM_FFI_INLINE Any(T other) { // NOLINT(*)
TypeTraits<T>::MoveToAny(std::move(other), &data_);
}
/*!
@@ -562,6 +562,7 @@ static_assert(sizeof(Any) == sizeof(TVMFFIAny));
// or non-trivial destructor on AnyView would silently regress this
// calling convention — catch it here at compile time.
static_assert(std::is_trivially_copyable_v<AnyView>, "AnyView must be
trivially copyable.");
+static_assert(std::is_standard_layout_v<AnyView>, "AnyView must have standard
layout.");
namespace details {
diff --git a/include/tvm/ffi/expected.h b/include/tvm/ffi/expected.h
index 420e0d29..f96de25b 100644
--- a/include/tvm/ffi/expected.h
+++ b/include/tvm/ffi/expected.h
@@ -513,61 +513,6 @@ struct ExpectedUnsafe {
}
};
-/*!
- * \brief Error-only return proxy used by mutation early-return macros.
- *
- * An error has the same shape in every mutation return type, so this proxy
- * converts to the raw ``TVMFFIAny`` used by hooks or to any ``Expected<T>``.
- */
-class UnexpectedReturnHelper {
- public:
- TVM_FFI_INLINE explicit UnexpectedReturnHelper(Unexpected<Error>&& value)
noexcept
- : value_(std::move(value)) {}
-
- // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
- TVM_FFI_INLINE operator TVMFFIAny() && noexcept {
- return ExpectedUnsafe::MoveToTVMFFIAny(Expected<Any>(std::move(value_)));
- }
-
- template <typename T>
- // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
- TVM_FFI_INLINE operator Expected<T>() && noexcept {
- return std::move(value_);
- }
-
- private:
- Unexpected<Error> value_;
-};
-
-/*!
- * \brief Expected-value return proxy used by visit early-return macros.
- * \tparam T The success type, fixed when the proxy stores its ``Expected<T>``.
- *
- * A return statement selects the raw ``TVMFFIAny`` conversion used by hooks or
- * the same ``Expected<T>`` type used by typed helpers. The payload-bearing
- * conversions are rvalue-qualified because handing it off is a single move; an
- * lvalue helper cannot accidentally transfer it twice. As with other
moved-from
- * values, deliberately converting ``std::move(helper)`` twice remains caller
- * error.
- */
-template <typename T>
-class ExpectedReturnHelper {
- public:
- TVM_FFI_INLINE explicit ExpectedReturnHelper(Expected<T>&& value) noexcept
- : value_(std::move(value)) {}
-
- // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
- TVM_FFI_INLINE operator TVMFFIAny() && noexcept {
- return ExpectedUnsafe::MoveToTVMFFIAny(std::move(value_));
- }
-
- // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
- TVM_FFI_INLINE operator Expected<T>() && noexcept { return
std::move(value_); }
-
- private:
- Expected<T> value_;
-};
-
} // namespace details
// TypeTraits specialization for Expected<T>
diff --git a/include/tvm/ffi/extra/structural_mutate.h
b/include/tvm/ffi/extra/structural_mutate.h
index e0ae09a1..7bd0ef9b 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -91,7 +91,7 @@ class StructuralMutateEngine;
* already established the type; allocation failure and violated
container invariants
* remain fatal.
*/
-using FStructuralMutate = TVMFFIAny (*)(StructuralMutatorObj* mutator, AnyView
value) noexcept;
+using FStructuralMutateABI = TVMFFIAny (*)(StructuralMutatorObj* mutator,
AnyView value) noexcept;
/*!
* \brief ABI callback type for looking up an identity substitution.
@@ -133,7 +133,7 @@ struct StructuralMutatorVTable {
* \param value The borrowed value to mutate.
* \return Raw ``TVMFFIAny`` carrying a replacement, the unchanged marker,
or Error.
*/
- FStructuralMutate mutate = nullptr;
+ FStructuralMutateABI mutate = nullptr;
/*!
* \brief Mutate a value, permitting an in-place implementation when it is
safe.
*
@@ -144,7 +144,7 @@ struct StructuralMutatorVTable {
* The returned value may refer to the same object as \p value when the
implementation mutates
* that object in place.
*/
- FStructuralMutate maybe_inplace_mutate = nullptr;
+ FStructuralMutateABI maybe_inplace_mutate = nullptr;
/*!
* \brief Look up the replacement for a variable identity.
*
@@ -164,6 +164,10 @@ struct StructuralMutatorVTable {
FStructuralVarRemapSet var_remap_set = nullptr;
};
+/*! \brief Borrowed structural mutation hook at attribute lookup and call. */
+using FStructuralMutate =
+
reflection::NativeFunctionView<Expected<UnchangedOr<Any>>(StructuralMutatorObj*,
AnyView)>;
+
namespace details {
template <typename Parent>
class StructuralMutateDynEngine;
@@ -423,9 +427,9 @@ namespace details {
// Takes nothing on purpose. Naming the offending type in the message would
keep the result live
// across the predicted-not-taken guard in the hot path. The declared type is
already present in
// the source line to which the diagnostic points.
-TVM_FFI_COLD_CODE inline UnexpectedReturnHelper SMutateDeclaredTypeError()
noexcept {
- return UnexpectedReturnHelper(Unexpected(
- Error("TypeError", "structural mutate result does not match the declared
type", "")));
+TVM_FFI_COLD_CODE inline Unexpected<Error> SMutateDeclaredTypeError() noexcept
{
+ return Unexpected(
+ Error("TypeError", "structural mutate result does not match the declared
type", ""));
}
} // namespace details
@@ -448,7 +452,7 @@ class StructuralMutatorObj : public Object {
* only when inplace_mode is InplaceMode::kAllow and the current value
is uniquely owned.
* See \ref MutateExpected for the full permission and error semantics.
*
- * Use \ref TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN in Expected-returning helpers
or raw hooks to
+ * Use \ref TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN in Expected-returning hooks
and helpers to
* preserve checked typed mutation results and the fixed mismatch
diagnostic. At a throwing
* boundary, an explicit cast may instead report its own TypeError:
* \code{.cpp}
@@ -599,7 +603,7 @@ class StructuralMutatorObj : public Object {
*/
TVM_FFI_COLD_CODE static Expected<Any> BadStructuralMutateHookError()
noexcept {
return Unexpected(
- Error("TypeError", "__s_mutate__ must be an opaque function pointer or
ffi.Function", ""));
+ Error("TypeError", "__s_mutate__ must be a direct hook pointer or
ffi.Function", ""));
}
// Convention: the ABI boundary is a raw TVMFFIAny; mutation results inside
a callback or hook
@@ -622,7 +626,7 @@ class StructuralMutatorObj : public Object {
// 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);
+ result =
(*reinterpret_cast<FStructuralMutateABI>(attr.cast<void*>()))(this, value);
} else {
result = DefaultMutateRawTail(value, attr);
}
@@ -642,7 +646,7 @@ class StructuralMutatorObj : public Object {
/*! \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.
+ // Registered as a typed function pointer or ffi.Function.
if (attr.type_index() == TypeIndex::kTVMFFIFunction) {
return details::ExpectedUnsafe::MoveToTVMFFIAny(
attr.cast<Function>().CallExpected<Any>(this, value));
@@ -707,7 +711,7 @@ class StructuralMutatorObj : public Object {
// 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);
+ TVMFFIAny result =
(*reinterpret_cast<FStructuralMutateABI>(attr.cast<void*>()))(this, value);
if (TVM_FFI_PREDICT_FALSE(result.type_index == TypeIndex::kTVMFFIError))
{
return AttachVisitErrorContextRaw(result, value);
}
@@ -896,15 +900,13 @@ TVM_FFI_INLINE static Expected<Any>
MutateReflectedFieldsExpected(StructuralMuta
namespace details {
/// \cond Doxygen_Suppress
-// Return an error from the current raw or Expected mutation function.
-// The rvalue-only helper lets the enclosing return type select the
representation.
-#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::UnexpectedReturnHelper( \
- ::tvm::ffi::Unexpected(::std::move(tvm_ffi_res_).error())); \
- } \
+// Return an error from the current Expected-returning mutation function.
+#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::Unexpected(::std::move(tvm_ffi_res_).error()); \
+ } \
} while (0)
/// \endcond
@@ -928,16 +930,16 @@ namespace details {
* \brief Unwrap a successful mutation result into a newly declared value or
return its error.
*
* ``Type`` must be concrete; use a type alias when it contains a top-level
comma. A type mismatch
- * returns ``TypeError`` through the surrounding raw or ``Expected`` function
without throwing,
+ * returns ``TypeError`` through the surrounding ``Expected`` function without
throwing,
* reported with a fixed string. The check is omitted when the declared type
subsumes the result's
- * success type. Its early returns work from either a raw ``TVMFFIAny`` hook
or an
- * ``Expected<T>`` helper, including one with a different success type. This
macro declares
+ * success type. Its early returns work from an ``Expected<T>`` hook or
helper, including one
+ * with a different success type. This macro declares
* ``Name`` into the enclosing scope and must be used in a braced block, never
as an unbraced
* control-flow body.
*
* Example:
* \code{.cpp}
- * TVMFFIAny FooMutate(StructuralMutatorObj* mutator, AnyView value) noexcept {
+ * Expected<UnchangedOr<Any>> FooMutate(StructuralMutatorObj* mutator, AnyView
value) noexcept {
* const FooNode* self =
* details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
FooNode>(value);
* constexpr InplaceMode inplace_mode = InplaceMode::kDisallow;
@@ -946,12 +948,12 @@ namespace details {
* TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Expr>, b,
* mutator->MutateExpected(self->b,
inplace_mode));
* if (a.UnchangedOrSameAs(self->a) && b.UnchangedOrSameAs(self->b)) {
- * return Unchanged().CopyToTVMFFIAny();
+ * return Unchanged();
* }
* ObjectPtr<FooNode> copy = make_object<FooNode>(*self);
* copy->a = std::move(a).ValueOrUnchanged(std::move(copy->a));
* copy->b = std::move(b).ValueOrUnchanged(std::move(copy->b));
- * return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(copy)));
+ * return copy;
* }
* \endcode
*
@@ -961,7 +963,7 @@ namespace details {
* \param Type The concrete successful value type.
* \param Name The name of the value declared in the enclosing scope.
* \param ResultExpr An expression producing the ``Expected`` value to unwrap.
- * \sa Unchanged::CopyToTVMFFIAny
+ * \sa Unchanged
*/
#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__), \
@@ -1027,9 +1029,8 @@ class StructuralMapEngineBase : public
StructuralMutatorObj {
return Unexpected(Error("TypeError", "structural mutate: descent changed
the node type", ""));
}
- TVM_FFI_COLD_CODE static details::UnexpectedReturnHelper
VarRemapKeyTypeError() noexcept {
- return details::UnexpectedReturnHelper(
- Unexpected(Error("TypeError", "Variable-remap key must be an
object-backed value", "")));
+ TVM_FFI_COLD_CODE static Unexpected<Error> VarRemapKeyTypeError() noexcept {
+ return Unexpected(Error("TypeError", "Variable-remap key must be an
object-backed value", ""));
}
/// \endcond
diff --git a/include/tvm/ffi/extra/structural_visit.h
b/include/tvm/ffi/extra/structural_visit.h
index d6d2ced7..ac638f8a 100644
--- a/include/tvm/ffi/extra/structural_visit.h
+++ b/include/tvm/ffi/extra/structural_visit.h
@@ -35,6 +35,7 @@
#include <tvm/ffi/function_details.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/accessor.h>
+#include <tvm/ffi/reflection/native_function.h>
#include <cstddef>
#include <exception>
@@ -99,7 +100,7 @@ class StructuralVisitorObj;
* ``AnyView``. It returns a raw ``TVMFFIAny`` storing
* ``Expected<Optional<VisitInterrupt>>``.
*/
-using FStructuralVisit = TVMFFIAny (*)(StructuralVisitorObj* visitor, AnyView
value) noexcept;
+using FStructuralVisitABI = TVMFFIAny (*)(StructuralVisitorObj* visitor,
AnyView value) noexcept;
namespace details {
@@ -124,9 +125,13 @@ struct StructuralVisitorVTable {
* failure, the returned ``TVMFFIAny`` stores ``Error``; on success, it
stores
* either None or ``VisitInterrupt``.
*/
- FStructuralVisit visit = nullptr;
+ FStructuralVisitABI visit = nullptr;
};
+/*! \brief Borrowed typed structural visit hook at attribute lookup and call.
*/
+using FStructuralVisit =
reflection::NativeFunctionView<Expected<Optional<VisitInterrupt>>(
+ StructuralVisitorObj*, AnyView)>;
+
/*!
* \brief Object node of a structural visitor.
*
@@ -236,7 +241,7 @@ class StructuralVisitorObj : public Object {
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
(*reinterpret_cast<FStructuralVisitABI>(attr.cast<void*>()))(this, value);
}
return DefaultVisitRawTail(value, attr);
}
@@ -306,19 +311,6 @@ class StructuralVisitor : public ObjectRef {
namespace details {
-/*!
- * \brief Return an optional visit result directly or wrap an Expected for
return conversion.
- */
-template <typename T>
-TVM_FFI_INLINE auto VisitReturnHelper(T&& result) {
- if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
- Optional<VisitInterrupt>>) {
- return std::forward<T>(result);
- } else {
- return ExpectedReturnHelper(std::forward<T>(result));
- }
-}
-
/*!
* \brief Return true when \p result carries a traversal-stopping interrupt.
*/
@@ -522,21 +514,21 @@ namespace details {
* \brief Return from a visit hook if \p Result stops traversal.
*
* Propagates an ``Error`` or a ``VisitInterrupt`` out of the enclosing
function
- * and otherwise falls through. An ``Optional<VisitInterrupt>`` result is
returned
- * directly. An ``Expected`` result uses an rvalue-only proxy to select the
return
- * representation for raw ``TVMFFIAny`` hooks or typed ``Expected`` helpers.
+ * and otherwise falls through. The stopping result is moved directly into the
+ * enclosing Optional or Expected return type.
*
* A registered ``__s_visit__`` hook is one line per traversed field followed
by
* the terminal return. A field skipped on purpose is guarded by a condition
and
* carries a ``// skips:`` note saying why.
*
* \code{.cpp}
- * TVMFFIAny FooVisit(StructuralVisitorObj* visitor, AnyView value) noexcept {
+ * Expected<Optional<VisitInterrupt>> FooVisit(StructuralVisitorObj* visitor,
+ * AnyView value) noexcept {
* const FooNode* self =
* details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
FooNode>(value);
* TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->a));
* TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->b));
- * return AnyView(nullptr).CopyToTVMFFIAny();
+ * return Optional<VisitInterrupt>(std::nullopt);
* }
* \endcode
*
@@ -547,7 +539,7 @@ namespace details {
auto&& tvm_ffi_res_ = (Result);
\
if (TVM_FFI_PREDICT_FALSE(
\
::tvm::ffi::details::StructuralVisitNeedEarlyReturn(tvm_ffi_res_))) { \
- return
::tvm::ffi::details::VisitReturnHelper(::std::move(tvm_ffi_res_)); \
+ return ::std::move(tvm_ffi_res_);
\
}
\
} while (0)
diff --git a/include/tvm/ffi/reflection/native_function.h
b/include/tvm/ffi/reflection/native_function.h
new file mode 100644
index 00000000..d42562f0
--- /dev/null
+++ b/include/tvm/ffi/reflection/native_function.h
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*!
+ * \file tvm/ffi/reflection/native_function.h
+ * \brief Borrowed and owning typed hooks with native ABI dispatch and packed
fallback.
+ */
+#ifndef TVM_FFI_REFLECTION_NATIVE_FUNCTION_H_
+#define TVM_FFI_REFLECTION_NATIVE_FUNCTION_H_
+
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/expected.h>
+#include <tvm/ffi/function.h>
+
+#include <type_traits>
+#include <utility>
+
+namespace tvm {
+namespace ffi {
+namespace reflection {
+
+template <typename Signature>
+class NativeFunctionView;
+
+/*!
+ * \brief Borrowed typed function for reflection attributes with native ABI
dispatch.
+ *
+ * Bind a named `Expected<R>(Args...) noexcept` native target with
`FromNative<&Fn>()`. The view
+ * stores its C ABI-compatible `TVMFFIAny(Args...) noexcept` adapter pointer.
+ * A borrowed `ffi::Function` supports frontend registration and packed
fallback. This approach
+ * provides efficient native calls for most natively registered hooks while
preserving flexibility
+ * in how functions are registered.
+ *
+ * \code{.cpp}
+ * TVM_FFI_INLINE tvm::ffi::Expected<int> Increment(int value) noexcept {
return value + 1; }
+ * using FIncrement =
tvm::ffi::reflection::NativeFunctionView<tvm::ffi::Expected<int>(int)>;
+ * FIncrement native = FIncrement::FromNative<&Increment>();
+ * tvm::ffi::Expected<int> result = native(3);
+ * \endcode
+ *
+ * \note The original function must remain alive while a borrowed view is used.
+ */
+template <typename R, typename... Args>
+class NativeFunctionView<Expected<R>(Args...)> {
+ public:
+ static_assert(((std::is_lvalue_reference_v<Args> ||
+ (std::is_standard_layout_v<Args> &&
std::is_trivially_copyable_v<Args>)) &&
+ ...),
+ "NativeFunctionView parameters require lvalue references or
standard-layout, "
+ "trivially-copyable values");
+ static_assert((ffi::details::ArgSupported<Args> && ...) &&
ffi::details::RetSupported<R>,
+ "NativeFunctionView signature must be supported by
TypedFunction");
+ /*! \brief Raw C ABI-compatible function pointer stored for native hook
dispatch. */
+ using ABIType = TVMFFIAny (*)(Args...) noexcept;
+
+ /*! \brief A hook view cannot be empty. */
+ NativeFunctionView() = delete;
+ /*! \brief A null pointer is not a callable hook. */
+ NativeFunctionView(std::nullptr_t) = delete;
+ /*!
+ * \brief Borrow a non-null typed packed function without retaining it.
+ *
+ * \param packed The function that must outlive this view.
+ */
+ NativeFunctionView(const TypedFunction<Expected<R>(Args...)>& packed) //
NOLINT(*)
+ : data_(AnyView(packed.packed()).CopyToTVMFFIAny()) {
+ TVM_FFI_CHECK(packed != nullptr, ValueError)
+ << "NativeFunctionView requires a non-null typed function";
+ }
+ /*! \brief Reject a temporary packed function, which would leave a dangling
view. */
+ NativeFunctionView(const TypedFunction<Expected<R>(Args...)>&&) = delete;
+
+ /*!
+ * \brief Bind a named noexcept native function through a context-free ABI
adapter.
+ *
+ * \tparam Fn The native hook function.
+ * \return A borrowed view of its static adapter.
+ * \note Declare the native target `Fn` with `TVM_FFI_INLINE` when its
definition is visible
+ * to the adapter. This gives the compiler an opportunity to inline it into
the adapter;
+ * calls and result handling inside `Fn` may still remain.
+ */
+ template <Expected<R> (*Fn)(Args...) noexcept>
+ static NativeFunctionView FromNative() {
+ TVMFFIAny data;
+ data.type_index = TypeIndex::kTVMFFIOpaquePtr;
+ data.zero_padding = 0;
+ data.v_ptr = reinterpret_cast<void*>(&NativeABIFuncPtr<Fn>);
+ return NativeFunctionView(UnsafeInit{}, data);
+ }
+
+ /*!
+ * \brief Invoke the hook and return its declared Expected result.
+ *
+ * \param args The typed hook arguments.
+ * \return A value or an error.
+ */
+ TVM_FFI_INLINE Expected<R> operator()(Args... args) const noexcept {
+ return
ffi::details::ExpectedUnsafe::MoveFromTVMFFIAny<R>(CallABI(std::forward<Args>(args)...));
+ }
+
+ private:
+ explicit NativeFunctionView(UnsafeInit, TVMFFIAny data) : data_(data) {}
+
+ template <Expected<R> (*Fn)(Args...) noexcept>
+ static TVMFFIAny NativeABIFuncPtr(Args... args) noexcept {
+ return
ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Fn(std::forward<Args>(args)...));
+ }
+
+ TVM_FFI_INLINE TVMFFIAny CallABI(Args... args) const noexcept {
+ if (TVM_FFI_PREDICT_TRUE(data_.type_index == TypeIndex::kTVMFFIOpaquePtr))
{
+ return
reinterpret_cast<ABIType>(data_.v_obj)(std::forward<Args>(args)...);
+ }
+ return CallABITail(std::forward<Args>(args)...);
+ }
+
+ TVM_FFI_COLD_CODE TVMFFIAny CallABITail(Args... args) const noexcept {
+ auto* cell = TVMFFIFunctionGetCellPtr(data_.v_obj);
+ AnyView packed_args[sizeof...(Args) > 0 ? sizeof...(Args) : 1];
+ PackedArgs::Fill(packed_args, std::forward<Args>(args)...);
+ Any result;
+ // The function cell follows the object header in the C ABI allocation.
+ // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)
+ int code = cell->safe_call(data_.v_obj, reinterpret_cast<const
TVMFFIAny*>(packed_args),
+ sizeof...(Args),
reinterpret_cast<TVMFFIAny*>(&result));
+ if (TVM_FFI_PREDICT_FALSE(code != 0)) {
+ return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Expected<R>(Unexpected(ffi::details::MoveFromSafeCallRaised())));
+ }
+ if constexpr (std::is_same_v<R, Any>) {
+ return
ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Expected<R>(std::move(result)));
+ } else if constexpr (std::is_void_v<R>) {
+ if (result.type_index() == TypeIndex::kTVMFFINone) {
+ return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Expected<void>());
+ }
+ } else if (auto value = result.template try_cast<R>()) {
+ return
ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Expected<R>(*std::move(value)));
+ }
+ return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(
+ Expected<R>(Unexpected(Error("TypeError", "Packed hook result type
mismatch", ""))));
+ }
+
+ template <typename, typename>
+ friend struct ::tvm::ffi::TypeTraits;
+ template <typename>
+ friend class NativeFunction;
+
+ TVMFFIAny data_;
+};
+
+template <typename Signature>
+class NativeFunction;
+
+/*!
+ * \brief Owning counterpart of NativeFunctionView for stored hook values.
+ *
+ * Construct a borrowed view, then copy it into this owner. A packed function
remains valid after
+ * the original TypedFunction is destroyed; native adapters retain their
static function address.
+ * The owner stores one `ffi::Any`, which manages the lifetime of packed
function objects.
+ *
+ * \code{.cpp}
+ * tvm::ffi::Expected<int> Increment(int value) noexcept { return value + 1; }
+ * using FIncrement =
tvm::ffi::reflection::NativeFunction<tvm::ffi::Expected<int>(int)>;
+ * FIncrement owned = FIncrement::FromNative<&Increment>();
+ * tvm::ffi::Expected<int> result = owned(3);
+ * \endcode
+ */
+template <typename R, typename... Args>
+class NativeFunction<Expected<R>(Args...)> {
+ public:
+ /*! \brief The borrowed view with the same callable signature. */
+ using View = NativeFunctionView<Expected<R>(Args...)>;
+
+ /*!
+ * \brief Retain a copy of a borrowed native or packed hook.
+ *
+ * \param view The borrowed view to copy.
+ */
+ explicit NativeFunction(View view) : data_(AnyView(view)) {}
+
+ /*!
+ * \brief Retain a non-null typed packed function.
+ *
+ * \param packed The typed function to copy.
+ */
+ NativeFunction(const TypedFunction<Expected<R>(Args...)>& packed) //
NOLINT(*)
+ : NativeFunction(View(packed)) {}
+
+ /*!
+ * \brief Copy a borrowed hook into an owning hook.
+ *
+ * \param view The borrowed view to retain.
+ * \return An independent owner of the hook.
+ */
+ static NativeFunction From(View view) { return NativeFunction(view); }
+
+ /*!
+ * \brief Own a named native hook bound through the same ABI adapter as View.
+ *
+ * \tparam Fn The noexcept native hook function.
+ * \return An owner of the generated static adapter.
+ * \note As with `View::FromNative`, declaring `Fn` with `TVM_FFI_INLINE`
helps the compiler
+ * inline a visible target into the adapter, without guaranteeing complete
call elimination.
+ */
+ template <Expected<R> (*Fn)(Args...) noexcept>
+ static NativeFunction FromNative() {
+ return From(View::template FromNative<Fn>());
+ }
+
+ /*!
+ * \brief Invoke the hook and return its declared Expected result.
+ *
+ * \param args The typed hook arguments.
+ * \return A value or an error.
+ */
+ TVM_FFI_INLINE Expected<R> operator()(Args... args) const noexcept {
+ return View(UnsafeInit{},
AnyView(data_).CopyToTVMFFIAny())(std::forward<Args>(args)...);
+ }
+
+ private:
+ explicit NativeFunction(UnsafeInit, Any data) : data_(std::move(data)) {}
+
+ template <typename, typename>
+ friend struct ::tvm::ffi::TypeTraits;
+
+ Any data_;
+};
+
+} // namespace reflection
+
+template <typename Signature>
+inline constexpr bool
use_default_type_traits_v<reflection::NativeFunctionView<Signature>> = false;
+
+template <typename Signature>
+inline constexpr bool
use_default_type_traits_v<reflection::NativeFunction<Signature>> = false;
+
+template <typename Signature>
+struct TypeTraits<reflection::NativeFunctionView<Signature>> : public
TypeTraitsBase {
+ using View = reflection::NativeFunctionView<Signature>;
+ static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIAny;
+
+ static void CopyToAnyView(const View& src, TVMFFIAny* result) { *result =
src.data_; }
+ static void MoveToAny(View src, TVMFFIAny* result) {
+ *result =
details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(AnyView::CopyFromTVMFFIAny(src.data_)));
+ }
+ static bool CheckAnyStrict(const TVMFFIAny* src) {
+ return (src->type_index == TypeIndex::kTVMFFIOpaquePtr ||
+ src->type_index == TypeIndex::kTVMFFIFunction);
+ }
+ static View CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { return
View(UnsafeInit{}, *src); }
+ static View MoveFromAnyAfterCheck(TVMFFIAny* src) { return
CopyFromAnyViewAfterCheck(src); }
+ static std::optional<View> TryCastFromAnyView(const TVMFFIAny* src) {
+ if (CheckAnyStrict(src)) return CopyFromAnyViewAfterCheck(src);
+ return std::nullopt;
+ }
+ static std::string TypeStr() {
+ return "Variant<" + TypeTraits<void*>::TypeStr() + ", " +
+ TypeTraits<TypedFunction<Signature>>::TypeStr() + ">";
+ }
+ static std::string TypeSchema() {
+ return R"({"type":"Variant","args":[)" + TypeTraits<void*>::TypeSchema() +
"," +
+ TypeTraits<TypedFunction<Signature>>::TypeSchema() + "]}";
+ }
+};
+
+template <typename Signature>
+struct TypeTraits<reflection::NativeFunction<Signature>> : public
TypeTraitsBase {
+ using Owner = reflection::NativeFunction<Signature>;
+ using View = typename Owner::View;
+ static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIAny;
+
+ static void CopyToAnyView(const Owner& src, TVMFFIAny* result) {
+ *result = AnyView(src.data_).CopyToTVMFFIAny();
+ }
+ static void MoveToAny(Owner src, TVMFFIAny* result) {
+ *result = details::AnyUnsafe::MoveAnyToTVMFFIAny(std::move(src.data_));
+ }
+ static bool CheckAnyStrict(const TVMFFIAny* src) { return
TypeTraits<View>::CheckAnyStrict(src); }
+ static Owner CopyFromAnyViewAfterCheck(const TVMFFIAny* src) {
+ return Owner(TypeTraits<View>::CopyFromAnyViewAfterCheck(src));
+ }
+ static Owner MoveFromAnyAfterCheck(TVMFFIAny* src) {
+ return Owner(UnsafeInit{}, details::AnyUnsafe::MoveTVMFFIAnyToAny(src));
+ }
+ static std::optional<Owner> TryCastFromAnyView(const TVMFFIAny* src) {
+ if (CheckAnyStrict(src)) return CopyFromAnyViewAfterCheck(src);
+ return std::nullopt;
+ }
+ static std::string TypeStr() { return TypeTraits<View>::TypeStr(); }
+ static std::string TypeSchema() { return TypeTraits<View>::TypeSchema(); }
+};
+
+} // namespace ffi
+} // namespace tvm
+
+#endif // TVM_FFI_REFLECTION_NATIVE_FUNCTION_H_
diff --git a/include/tvm/ffi/reflection/registry.h
b/include/tvm/ffi/reflection/registry.h
index 612e5a38..6b9f8be4 100644
--- a/include/tvm/ffi/reflection/registry.h
+++ b/include/tvm/ffi/reflection/registry.h
@@ -1145,7 +1145,7 @@ class TypeAttrDef : public ReflectionDefBase {
* \return The TypeAttrDef object.
*/
template <typename T>
- TypeAttrDef& attr(const char* name, T value) {
+ TypeAttrDef& attr(const char* name, T value) { //
NOLINT(performance-unnecessary-value-param)
RegisterTypeAttrValue(type_index_, name, std::move(value));
return *this;
}
diff --git a/src/ffi/extra/structural_mutate.cc
b/src/ffi/extra/structural_mutate.cc
index 3764322e..f3361bc4 100644
--- a/src/ffi/extra/structural_mutate.cc
+++ b/src/ffi/extra/structural_mutate.cc
@@ -171,9 +171,10 @@ Expected<Any> StructuralMutateExpected(
* \return The mutated sequence, or an Error.
*/
template <typename SeqObj>
-TVM_FFI_INLINE TVMFFIAny MutateSeqContainerChanged(StructuralMutatorObj*
mutator,
- const SeqObj* self, int64_t
index,
- Any first) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MutateSeqContainerChanged(StructuralMutatorObj* mutator,
+ const
SeqObj* self,
+ int64_t
index,
+ Any first)
noexcept {
int64_t size = static_cast<int64_t>(self->size());
const Any* items = self->begin();
ObjectPtr<SeqObj> output = SeqObj::CreateRepeated(size, Any());
@@ -186,7 +187,7 @@ TVM_FFI_INLINE TVMFFIAny
MutateSeqContainerChanged(StructuralMutatorObj* mutator
mutator->MutateExpected(item,
InplaceMode::kDisallow));
output->SetItemAfterCheck(i,
std::move(mapped_value).ValueOrUnchanged(AnyView(item)));
}
- return AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(output)));
+ return output;
}
/*!
@@ -198,7 +199,8 @@ TVM_FFI_INLINE TVMFFIAny
MutateSeqContainerChanged(StructuralMutatorObj* mutator
* \return The mutated sequence, or an Error.
*/
template <typename SeqObj>
-TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj* mutator, const SeqObj*
self) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MutateSeqContainerRaw(StructuralMutatorObj* mutator,
+ const SeqObj*
self) noexcept {
int64_t size = static_cast<int64_t>(self->size());
const Any* items = self->begin();
@@ -210,7 +212,7 @@ TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj*
mutator, const SeqObj* sel
return MutateSeqContainerChanged(mutator, self, i,
std::move(mapped_value).ValueUnchecked());
}
}
- return Unchanged().CopyToTVMFFIAny();
+ return Unchanged();
}
/*!
@@ -222,7 +224,8 @@ TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj*
mutator, const SeqObj* sel
* \return The mutated sequence, or an Error.
*/
template <typename SeqObj>
-TVMFFIAny MaybeInplaceMutateSeqContainerRaw(StructuralMutatorObj* mutator,
SeqObj* self) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MaybeInplaceMutateSeqContainerRaw(
+ StructuralMutatorObj* mutator, 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(UnchangedOr<Any>, mapped_value,
@@ -231,7 +234,7 @@ TVMFFIAny
MaybeInplaceMutateSeqContainerRaw(StructuralMutatorObj* mutator, SeqOb
self->SetItemAfterCheck(i, std::move(mapped_value).ValueUnchecked());
}
}
- return Unchanged().CopyToTVMFFIAny();
+ return Unchanged();
}
/*!
@@ -246,10 +249,10 @@ TVMFFIAny
MaybeInplaceMutateSeqContainerRaw(StructuralMutatorObj* mutator, SeqOb
* \return The mutated map, or an Error.
*/
template <typename MapObjType>
-TVM_FFI_INLINE TVMFFIAny MutateMapValuesChanged(StructuralMutatorObj* mutator,
- const MapObjType* self,
- MapBaseObj::iterator
source_it, size_t index,
- Any first) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MutateMapValuesChanged(StructuralMutatorObj* mutator,
+ const
MapObjType* self,
+
MapBaseObj::iterator source_it,
+ size_t index,
Any first) noexcept {
ObjectPtr<Object> output = MapObjType::ShallowCopy(self);
auto output_it = static_cast<MapBaseObj*>(output.get())->begin();
for (size_t i = 0; i < index; ++i) {
@@ -267,7 +270,7 @@ TVM_FFI_INLINE TVMFFIAny
MutateMapValuesChanged(StructuralMutatorObj* mutator,
output_it->second = std::move(new_value).ValueUnchecked();
}
}
- return AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(output)));
+ return output;
}
/*!
@@ -279,7 +282,8 @@ TVM_FFI_INLINE TVMFFIAny
MutateMapValuesChanged(StructuralMutatorObj* mutator,
* \return The mutated map, or an Error.
*/
template <typename MapObjType>
-TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* mutator, const MapObjType*
self) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MutateMapValuesRaw(StructuralMutatorObj* mutator,
+ const MapObjType*
self) noexcept {
size_t index = 0;
for (auto source_it = self->begin(); source_it != self->end(); ++source_it,
++index) {
const Any& old_value = source_it->second;
@@ -290,7 +294,7 @@ TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* mutator,
const MapObjType* se
std::move(new_value).ValueUnchecked());
}
}
- return Unchanged().CopyToTVMFFIAny();
+ return Unchanged();
}
/*!
@@ -302,7 +306,8 @@ TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* mutator,
const MapObjType* se
* \return The mutated map, or an Error.
*/
template <typename MapObjType>
-TVMFFIAny MaybeInplaceMutateMapValuesRaw(StructuralMutatorObj* mutator,
MapObjType* self) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MaybeInplaceMutateMapValuesRaw(
+ StructuralMutatorObj* mutator, 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(UnchangedOr<Any>, new_value,
@@ -311,61 +316,71 @@ TVMFFIAny
MaybeInplaceMutateMapValuesRaw(StructuralMutatorObj* mutator, MapObjTy
it->second = std::move(new_value).ValueUnchecked();
}
}
- return Unchanged().CopyToTVMFFIAny();
+ return Unchanged();
}
/*! \brief Identity structural mutation hook for immutable String and Bytes
leaves. */
-TVMFFIAny MutateImmutableLeaf(StructuralMutatorObj*, AnyView) noexcept {
- return Unchanged().CopyToTVMFFIAny();
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MutateImmutableLeaf(StructuralMutatorObj*,
+ AnyView)
noexcept {
+ return Unchanged();
}
-/*! \brief Structural mutation hook for ArrayObj. */
-TVMFFIAny MutateArray(StructuralMutatorObj* mutator, AnyView value) noexcept {
+/*! \brief Typed structural mutation hook for ArrayObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MutateArray(StructuralMutatorObj*
mutator,
+ AnyView value) noexcept {
return MutateSeqContainerRaw(
mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
ArrayObj>(value));
}
-/*! \brief Maybe-in-place structural mutation hook for ArrayObj. */
-TVMFFIAny MaybeInplaceMutateArray(StructuralMutatorObj* mutator, AnyView
value) noexcept {
+/*! \brief Typed structural mutation hook for ArrayObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MaybeInplaceMutateArray(StructuralMutatorObj* mutator,
+ AnyView
value) noexcept {
return MaybeInplaceMutateSeqContainerRaw(
mutator,
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ArrayObj>(value));
}
-/*! \brief Structural mutation hook for ListObj. */
-TVMFFIAny MutateList(StructuralMutatorObj* mutator, AnyView value) noexcept {
+/*! \brief Typed structural mutation hook for ListObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MutateList(StructuralMutatorObj*
mutator,
+ AnyView value) noexcept {
return MutateSeqContainerRaw(
mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
ListObj>(value));
}
-/*! \brief Maybe-in-place structural mutation hook for ListObj. */
-TVMFFIAny MaybeInplaceMutateList(StructuralMutatorObj* mutator, AnyView value)
noexcept {
+/*! \brief Typed structural mutation hook for ListObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MaybeInplaceMutateList(StructuralMutatorObj* mutator,
+ AnyView
value) noexcept {
return MaybeInplaceMutateSeqContainerRaw(
mutator,
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ListObj>(value));
}
-/*! \brief Structural mutation hook for MapObj. */
-TVMFFIAny MutateMap(StructuralMutatorObj* mutator, AnyView value) noexcept {
+/*! \brief Typed structural mutation hook for MapObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MutateMap(StructuralMutatorObj*
mutator,
+ AnyView value) noexcept {
return MutateMapValuesRaw(
mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
MapObj>(value));
}
-/*! \brief Maybe-in-place structural mutation hook for MapObj. */
-TVMFFIAny MaybeInplaceMutateMap(StructuralMutatorObj* mutator, AnyView value)
noexcept {
+/*! \brief Typed structural mutation hook for MapObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MaybeInplaceMutateMap(StructuralMutatorObj* mutator,
+ AnyView value)
noexcept {
return MaybeInplaceMutateMapValuesRaw(
mutator,
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<MapObj>(value));
}
-/*! \brief Structural mutation hook for DictObj. */
-TVMFFIAny MutateDict(StructuralMutatorObj* mutator, AnyView value) noexcept {
+/*! \brief Typed structural mutation hook for DictObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MutateDict(StructuralMutatorObj*
mutator,
+ AnyView value) noexcept {
return MutateMapValuesRaw(
mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
DictObj>(value));
}
-/*! \brief Maybe-in-place structural mutation hook for DictObj. */
-TVMFFIAny MaybeInplaceMutateDict(StructuralMutatorObj* mutator, AnyView value)
noexcept {
+/*! \brief Typed structural mutation hook for DictObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>>
MaybeInplaceMutateDict(StructuralMutatorObj* mutator,
+ AnyView
value) noexcept {
return MaybeInplaceMutateMapValuesRaw(
mutator,
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<DictObj>(value));
}
+
} // namespace details
// ---------------------------------------------------------------------------
@@ -422,43 +437,39 @@ TVM_FFI_STATIC_INIT_BLOCK() {
refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMaybeInplaceMutate);
refl::TypeAttrDef<details::BigIntObj>()
.attr(refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)))
+ FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
.attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)));
+ FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
refl::TypeAttrDef<details::StringObj>()
.attr(refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)))
+ FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
.attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)));
+ FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
refl::TypeAttrDef<details::BytesObj>()
.attr(refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)))
+ FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
.attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)));
+ FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
refl::TypeAttrDef<ArrayObj>()
.attr(refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateArray)))
+ FStructuralMutate::FromNative<&details::MutateArray>())
.attr(refl::type_attr::kStructuralMaybeInplaceMutate,
- reinterpret_cast<void*>(
-
static_cast<FStructuralMutate>(&details::MaybeInplaceMutateArray)));
+
FStructuralMutate::FromNative<&details::MaybeInplaceMutateArray>());
refl::TypeAttrDef<ListObj>()
.attr(refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateList)))
+ FStructuralMutate::FromNative<&details::MutateList>())
.attr(refl::type_attr::kStructuralMaybeInplaceMutate,
- reinterpret_cast<void*>(
-
static_cast<FStructuralMutate>(&details::MaybeInplaceMutateList)));
+ FStructuralMutate::FromNative<&details::MaybeInplaceMutateList>());
refl::TypeAttrDef<MapObj>()
.attr(refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateMap)))
- .attr(
- refl::type_attr::kStructuralMaybeInplaceMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MaybeInplaceMutateMap)));
+ FStructuralMutate::FromNative<&details::MutateMap>())
+ .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
+ FStructuralMutate::FromNative<&details::MaybeInplaceMutateMap>());
refl::TypeAttrDef<DictObj>()
.attr(refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateDict)))
+ FStructuralMutate::FromNative<&details::MutateDict>())
.attr(refl::type_attr::kStructuralMaybeInplaceMutate,
- reinterpret_cast<void*>(
-
static_cast<FStructuralMutate>(&details::MaybeInplaceMutateDict)));
+ FStructuralMutate::FromNative<&details::MaybeInplaceMutateDict>());
}
} // namespace ffi
diff --git a/src/ffi/extra/structural_visit.cc
b/src/ffi/extra/structural_visit.cc
index aaa0f804..729a4a7c 100644
--- a/src/ffi/extra/structural_visit.cc
+++ b/src/ffi/extra/structural_visit.cc
@@ -111,43 +111,25 @@ Expected<Optional<VisitInterrupt>>
StructuralVisitExpected(
}
/*! \brief Visit entries in a sequence container. */
-TVMFFIAny VisitSeqContainer(StructuralVisitorObj* visitor, const SeqBaseObj*
self) noexcept {
+template <typename SeqObj>
+TVM_FFI_INLINE Expected<Optional<VisitInterrupt>>
VisitSeqContainer(StructuralVisitorObj* visitor,
+ AnyView
value) noexcept {
+ const auto* self = value.cast<const SeqObj*>();
for (const Any& item : *self) {
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(item));
}
- return AnyView(nullptr).CopyToTVMFFIAny();
+ return Optional<VisitInterrupt>(std::nullopt);
}
/*! \brief Visit values in a map container while treating keys as structural
anchors. */
-TVMFFIAny VisitMapContainer(StructuralVisitorObj* visitor, const MapBaseObj*
self) noexcept {
+template <typename MapObjType>
+TVM_FFI_INLINE Expected<Optional<VisitInterrupt>>
VisitMapContainer(StructuralVisitorObj* visitor,
+ AnyView
value) noexcept {
+ const auto* self = value.cast<const MapObjType*>();
for (const auto& kv : *self) {
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(kv.second));
}
- return AnyView(nullptr).CopyToTVMFFIAny();
-}
-
-/*! \brief Structural visit hook for ArrayObj. */
-TVMFFIAny VisitArray(StructuralVisitorObj* visitor, AnyView value) noexcept {
- const auto* array = value.cast<const ArrayObj*>();
- return VisitSeqContainer(visitor, array);
-}
-
-/*! \brief Structural visit hook for ListObj. */
-TVMFFIAny VisitList(StructuralVisitorObj* visitor, AnyView value) noexcept {
- const auto* list = value.cast<const ListObj*>();
- return VisitSeqContainer(visitor, list);
-}
-
-/*! \brief Structural visit hook for MapObj. */
-TVMFFIAny VisitMap(StructuralVisitorObj* visitor, AnyView value) noexcept {
- const auto* map = value.cast<const MapObj*>();
- return VisitMapContainer(visitor, map);
-}
-
-/*! \brief Structural visit hook for DictObj. */
-TVMFFIAny VisitDict(StructuralVisitorObj* visitor, AnyView value) noexcept {
- const auto* dict = value.cast<const DictObj*>();
- return VisitMapContainer(visitor, dict);
+ return Optional<VisitInterrupt>(std::nullopt);
}
} // namespace details
@@ -190,16 +172,16 @@ TVM_FFI_STATIC_INIT_BLOCK() {
refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);
refl::TypeAttrDef<ArrayObj>().attr(
refl::type_attr::kStructuralVisit,
-
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&details::VisitArray)));
+ FStructuralVisit::FromNative<&details::VisitSeqContainer<ArrayObj>>());
refl::TypeAttrDef<ListObj>().attr(
refl::type_attr::kStructuralVisit,
-
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&details::VisitList)));
+ FStructuralVisit::FromNative<&details::VisitSeqContainer<ListObj>>());
refl::TypeAttrDef<MapObj>().attr(
refl::type_attr::kStructuralVisit,
-
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&details::VisitMap)));
+ FStructuralVisit::FromNative<&details::VisitMapContainer<MapObj>>());
refl::TypeAttrDef<DictObj>().attr(
refl::type_attr::kStructuralVisit,
-
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&details::VisitDict)));
+ FStructuralVisit::FromNative<&details::VisitMapContainer<DictObj>>());
}
} // namespace ffi
diff --git a/tests/cpp/extra/test_structural_mutate.cc
b/tests/cpp/extra/test_structural_mutate.cc
index 1debe194..1cc31e75 100644
--- a/tests/cpp/extra/test_structural_mutate.cc
+++ b/tests/cpp/extra/test_structural_mutate.cc
@@ -270,20 +270,21 @@ class TNestedMapHookObj : public Object {
explicit TNestedMapHookObj(AnyArray field) : field(std::move(field)) {}
- static TVMFFIAny StructuralMutate(StructuralMutatorObj* mutator, AnyView
value) noexcept {
+ TVM_FFI_INLINE static Expected<UnchangedOr<Any>>
StructuralMutate(StructuralMutatorObj* mutator,
+ AnyView
value) noexcept {
const auto* self = value.cast<const TNestedMapHookObj*>();
TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, mapped,
mutator->MutateExpected(self->field,
InplaceMode::kDisallow));
if (mapped.UnchangedOrSameAs(Any(self->field))) {
- return Unchanged().CopyToTVMFFIAny();
+ return Unchanged();
}
Any mapped_value = std::move(mapped).ValueOrUnchanged(Any(self->field));
AnyArray mapped_field = mapped_value.cast<AnyArray>();
- return details::AnyUnsafe::MoveAnyToTVMFFIAny(
- Any(make_object<TNestedMapHookObj>(std::move(mapped_field))));
+ return make_object<TNestedMapHookObj>(std::move(mapped_field));
}
- static TVMFFIAny MaybeInplaceMutate(StructuralMutatorObj*, AnyView value)
noexcept {
+ TVM_FFI_INLINE static Expected<UnchangedOr<Any>>
MaybeInplaceMutate(StructuralMutatorObj*,
+ AnyView
value) noexcept {
auto* self = value.cast<TNestedMapHookObj*>();
TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(
Any, mapped,
@@ -291,7 +292,7 @@ class TNestedMapHookObj : public Object {
Any(std::move(self->field)),
[](int64_t item) -> Expected<Any> { return Any(item + 1); }));
self->field = mapped.cast<AnyArray>();
- return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
+ return Any(value);
}
static void RegisterReflection() {
@@ -301,9 +302,9 @@ class TNestedMapHookObj : public Object {
refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMaybeInplaceMutate);
refl::TypeAttrDef<TNestedMapHookObj>()
.attr(refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&StructuralMutate)))
+
FStructuralMutate::FromNative<&TNestedMapHookObj::StructuralMutate>())
.attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&MaybeInplaceMutate)));
+
FStructuralMutate::FromNative<&TNestedMapHookObj::MaybeInplaceMutate>());
}
static constexpr bool _type_mutable = true;
diff --git a/tests/cpp/test_expected_checks.cc
b/tests/cpp/test_expected_checks.cc
index 6797652e..b6f408ac 100644
--- a/tests/cpp/test_expected_checks.cc
+++ b/tests/cpp/test_expected_checks.cc
@@ -98,28 +98,23 @@ TEST(ExpectedChecks, ReturnChecks) {
// NOLINTEND(google-readability-braces-around-statements)
}
-template <typename Return>
-Return UnsafeMutateAssign(Expected<Any> result, int* evaluations) noexcept {
+Expected<int> UnsafeMutateAssign(Expected<Any> result, int* evaluations)
noexcept {
TVM_FFI_UNSAFE_S_MUTATE_ASSIGN_OR_RETURN(int, value, (++*evaluations,
std::move(result)));
- if constexpr (std::is_same_v<Return, TVMFFIAny>) {
- return AnyView(value + 1).CopyToTVMFFIAny();
- } else {
- return value + 1;
- }
+ return value + 1;
}
TEST(ExpectedChecks, UnsafeAssignment) {
int evaluations = 0;
- EXPECT_EQ(UnsafeMutateAssign<Expected<int>>(Any(41), &evaluations).value(),
42);
+ EXPECT_EQ(UnsafeMutateAssign(Any(41), &evaluations).value(), 42);
auto raw_ok = details::ExpectedUnsafe::MoveFromTVMFFIAny<int>(
- UnsafeMutateAssign<TVMFFIAny>(Any(41), &evaluations));
+ details::ExpectedUnsafe::MoveToTVMFFIAny(UnsafeMutateAssign(Any(41),
&evaluations)));
EXPECT_EQ(raw_ok.value(), 42);
Error error("ValueError", "unsafe assign", "");
- auto typed_error = UnsafeMutateAssign<Expected<int>>(error, &evaluations);
+ auto typed_error = UnsafeMutateAssign(error, &evaluations);
ASSERT_TRUE(typed_error.is_err());
EXPECT_EQ(typed_error.error().message(), "unsafe assign");
auto raw_error = details::ExpectedUnsafe::MoveFromTVMFFIAny<int>(
- UnsafeMutateAssign<TVMFFIAny>(error, &evaluations));
+ details::ExpectedUnsafe::MoveToTVMFFIAny(UnsafeMutateAssign(error,
&evaluations)));
ASSERT_TRUE(raw_error.is_err());
EXPECT_EQ(raw_error.error().message(), "unsafe assign");
EXPECT_EQ(evaluations, 4);
diff --git a/tests/cpp/test_native_function_view.cc
b/tests/cpp/test_native_function_view.cc
new file mode 100644
index 00000000..09eb195c
--- /dev/null
+++ b/tests/cpp/test_native_function_view.cc
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#include <gtest/gtest.h>
+#include <tvm/ffi/reflection/native_function.h>
+
+#include <optional>
+#include <string>
+#include <type_traits>
+
+namespace {
+
+using namespace tvm::ffi;
+using tvm::ffi::reflection::NativeFunction;
+using tvm::ffi::reflection::NativeFunctionView;
+
+static_assert(sizeof(NativeFunctionView<Expected<int>(int)>) ==
sizeof(TVMFFIAny));
+static_assert(sizeof(NativeFunction<Expected<int>(int)>) == sizeof(Any));
+static_assert(!std::is_default_constructible_v<NativeFunctionView<Expected<int>(int)>>);
+static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>,
std::nullptr_t>);
+static_assert(std::is_convertible_v<TypedFunction<Expected<int>(int)>&,
+ NativeFunctionView<Expected<int>(int)>>);
+static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>,
+ TypedFunction<Expected<int>(int)>&&>);
+static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>,
AnyView>);
+static_assert(std::is_same_v<decltype(std::declval<NativeFunctionView<Expected<int>(int)>>()(0)),
+ Expected<int>>);
+
+Expected<int> IncrementHook(int value) noexcept { return value + 1; }
+Expected<int> FailHook(int) noexcept { return Unexpected(Error("ValueError",
"hook failed", "")); }
+
+TEST(NativeFunctionView, NativeAndBorrowedPacked) {
+ auto native =
NativeFunctionView<Expected<int>(int)>::FromNative<&IncrementHook>();
+ EXPECT_EQ(native(3).value(), 4);
+ EXPECT_EQ(AnyView(native).type_index(), TypeIndex::kTVMFFIOpaquePtr);
+
EXPECT_EQ(Any(native).cast<NativeFunctionView<Expected<int>(int)>>()(3).value(),
4);
+
EXPECT_EQ(TypeTraits<NativeFunctionView<Expected<int>(int)>>::TypeStr().find("Variant<"),
0);
+ EXPECT_NE(
+
TypeTraits<NativeFunctionView<Expected<int>(int)>>::TypeSchema().find("\"type\":\"Variant\""),
+ std::string::npos);
+
+ TypedFunction<Expected<int>(int)> packed(
+ Function::FromTyped([](int x) -> Expected<int> { return x + 2; }));
+ auto* obj = packed.packed().get();
+ auto before = obj->use_count();
+ NativeFunctionView<Expected<int>(int)> borrowed = packed;
+ EXPECT_EQ(obj->use_count(), before);
+ EXPECT_EQ(borrowed(3).value(), 5);
+}
+
+TEST(NativeFunctionView, ErrorAndNullRejection) {
+ auto native =
NativeFunctionView<Expected<int>(int)>::FromNative<&FailHook>();
+ auto failed = native(0);
+ ASSERT_TRUE(failed.is_err());
+ EXPECT_EQ(failed.error().kind(), "ValueError");
+
+ TypedFunction<Expected<int>(int)> empty(nullptr);
+ EXPECT_ANY_THROW(NativeFunctionView<Expected<int>(int)> invalid(empty));
+
EXPECT_FALSE(Any(nullptr).try_cast<NativeFunctionView<Expected<int>(int)>>().has_value());
+
EXPECT_FALSE(Any(nullptr).try_cast<NativeFunction<Expected<int>(int)>>().has_value());
+}
+
+TEST(NativeFunctionView, OwningLifetimeAndAnyRoundtrip) {
+ std::optional<NativeFunction<Expected<int>(int)>> owned;
+ {
+ TypedFunction<Expected<int>(int)> packed(
+ Function::FromTyped([](int value) -> Expected<int> { return value + 2;
}));
+ auto* obj = packed.packed().get();
+ auto before = obj->use_count();
+ owned.emplace(NativeFunctionView<Expected<int>(int)>(packed));
+ EXPECT_EQ(obj->use_count(), before + 1);
+ NativeFunction<Expected<int>(int)> copy(*owned);
+ EXPECT_EQ(obj->use_count(), before + 2);
+ NativeFunction<Expected<int>(int)> moved(std::move(copy));
+ EXPECT_EQ(moved(3).value(), 5);
+ }
+ EXPECT_EQ((*owned)(3).value(), 5);
+ Any attr = *owned;
+ EXPECT_EQ(attr.cast<NativeFunction<Expected<int>(int)>>()(3).value(), 5);
+ Any moved_attr = std::move(*owned);
+ EXPECT_EQ(moved_attr.cast<NativeFunction<Expected<int>(int)>>()(3).value(),
5);
+
EXPECT_EQ(NativeFunction<Expected<int>(int)>::FromNative<&IncrementHook>()(3).value(),
4);
+}
+
+} // namespace
diff --git a/tests/cpp/testing_object.h b/tests/cpp/testing_object.h
index 366bb96a..0b8881c3 100644
--- a/tests/cpp/testing_object.h
+++ b/tests/cpp/testing_object.h
@@ -249,7 +249,8 @@ class TMutatePairObj : public Object {
return count;
}
- static TVMFFIAny StructuralMutate(StructuralMutatorObj* mutator, AnyView
value) noexcept {
+ TVM_FFI_INLINE static Expected<UnchangedOr<Any>> StructuralMutateHook(
+ StructuralMutatorObj* mutator, AnyView value) noexcept {
++StructuralMutateCallCount();
const TMutatePairObj* self =
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
TMutatePairObj>(value);
@@ -258,12 +259,12 @@ class TMutatePairObj : public Object {
TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<ObjectRef>, rhs,
mutator->MutateExpected(self->rhs,
InplaceMode::kDisallow));
if (lhs.UnchangedOrSameAs(self->lhs) && rhs.UnchangedOrSameAs(self->rhs)) {
- return Unchanged().CopyToTVMFFIAny();
+ return Unchanged();
}
ObjectPtr<TMutatePairObj> copy = make_object<TMutatePairObj>(*self);
copy->lhs = std::move(lhs).ValueOrUnchanged(std::move(copy->lhs));
copy->rhs = std::move(rhs).ValueOrUnchanged(std::move(copy->rhs));
- return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(copy)));
+ return copy;
}
static void RegisterReflection() {
@@ -274,7 +275,7 @@ class TMutatePairObj : public Object {
refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMutate);
refl::TypeAttrDef<TMutatePairObj>().attr(
refl::type_attr::kStructuralMutate,
-
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&TMutatePairObj::StructuralMutate)));
+
FStructuralMutate::FromNative<&TMutatePairObj::StructuralMutateHook>());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind =
kTVMFFISEqHashKindTreeNode;
@@ -411,15 +412,14 @@ class TFuncObj : public Object {
TFuncObj(Array<TVar> params, Array<ObjectRef> body, Optional<String> comment)
: params(params), body(body), comment(comment) {}
- static TVMFFIAny StructuralVisit(StructuralVisitorObj* visitor, AnyView
value) noexcept {
+ TVM_FFI_INLINE static Expected<Optional<VisitInterrupt>> StructuralVisitHook(
+ StructuralVisitorObj* visitor, AnyView value) noexcept {
const auto* self = value.cast<const TFuncObj*>();
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->WithDefRegionKind(
kTVMFFIDefRegionKindPattern, [&]() { return
visitor->VisitExpected(self->params); }));
- auto body_result = visitor->VisitExpected(self->body);
- TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(body_result);
- return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(body_result));
+ return visitor->VisitExpected(self->body);
}
static void RegisterReflection() {
@@ -431,7 +431,7 @@ class TFuncObj : public Object {
refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);
refl::TypeAttrDef<TFuncObj>().attr(
refl::type_attr::kStructuralVisit,
-
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&TFuncObj::StructuralVisit)));
+ FStructuralVisit::FromNative<&TFuncObj::StructuralVisitHook>());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind =
kTVMFFISEqHashKindTreeNode;