This is an automated email from the ASF dual-hosted git repository.
junrushao 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 503b098f feat(abi): introduce Arc for required object values (#676)
503b098f is described below
commit 503b098fcc66632600112b7f3d36abac4a48f5aa
Author: Junru Shao <[email protected]>
AuthorDate: Tue Jul 21 10:48:32 2026 +0800
feat(abi): introduce Arc for required object values (#676)
Add `Arc<T>` and `make_arc<T>` as one-pointer, reference-counted
carriers whose safe API does not admit null. Give Arc bare object
schemas while representing nullable `ObjectPtr<T>` as `Optional[T]`, and
reject empty or moved-from Arc values at FFI boundaries.
Teach reflected fields, typed containers, and C++ ABI generation to
preserve object nullability. Required objects and object-only unions use
Arc, nullable pointer fields use ObjectPtr, and 16-byte optionals use
`Optional<Arc<T>>`.
Document the ownership and unsafe-initialization rules, preserve
container exception safety when conversion fails, and extend C++ and
Python coverage for casts, reflection, structural operations,
serialization, and generated layouts.
---
docs/guides/cpp_lang_guide.md | 49 ++++++----
include/tvm/ffi/container/array.h | 6 +-
include/tvm/ffi/container/list.h | 6 +-
include/tvm/ffi/memory.h | 11 +++
include/tvm/ffi/object.h | 141 +++++++++++++++++++++++++++++
python/tvm_ffi/dataclasses/gen_abi_cpp.py | 79 ++++++++++------
python/tvm_ffi/testing/testing.py | 9 +-
src/ffi/testing/testing.cc | 9 +-
tests/cpp/extra/test_serialization.cc | 19 ++--
tests/cpp/extra/test_structural_visit.cc | 4 +-
tests/cpp/test_abi_object.cc | 16 +++-
tests/cpp/test_object.cc | 46 ++++++++++
tests/cpp/test_object_ptr.cc | 128 ++++++++++++++++++++++++++
tests/cpp/test_reflection.cc | 82 +++++++++++------
tests/cpp/testing_object.h | 23 +++--
tests/python/test_dataclass_gen_abi_cpp.py | 138 +++++++++++++++++++++++-----
tests/python/test_dataclass_py_class.py | 25 ++++-
tests/python/test_metadata.py | 7 +-
18 files changed, 660 insertions(+), 138 deletions(-)
diff --git a/docs/guides/cpp_lang_guide.md b/docs/guides/cpp_lang_guide.md
index 0b5733a8..d964421c 100644
--- a/docs/guides/cpp_lang_guide.md
+++ b/docs/guides/cpp_lang_guide.md
@@ -88,7 +88,7 @@ counting correctly when the stored value is an on-heap object.
The tvm-ffi object system provides the foundation for all managed,
reference-counted objects
in the system. It enables type safety, cross-language compatibility, and
efficient memory management.
-The object system is built around three key classes: Object, ObjectPtr, and
ObjectRef.
+The object system is built around four key classes: `Object`, `ObjectPtr`,
`Arc`, and `ObjectRef`.
The `Object` class is the base class of all heap-allocated objects. It
contains a common header
that includes the `type_index`, reference counter and deleter for the object.
Users do not need to explicitly manage these fields as part of the C++ API.
Instead,
@@ -115,24 +115,40 @@ class MyIntPairObj : public tvm::ffi::Object {
void ExampleObjectPtr() {
namespace ffi = tvm::ffi;
- // make_object automatically sets up the deleter correctly
- // This function creates a new ObjectPtr with proper memory management
- // It handles allocation, initialization, and sets up the reference counting
system
- ffi::ObjectPtr<MyIntPairObj> obj = ffi::make_object<MyIntPairObj>(100, 200);
+ // make_arc allocates, initializes, and sets up reference counting and the
deleter.
+ ffi::Arc<MyIntPairObj> required = ffi::make_arc<MyIntPairObj>(100, 200);
+ // ObjectPtr is the nullable one-pointer carrier.
+ ffi::ObjectPtr<MyIntPairObj> optional = required;
// EXPECT_EQ is used here for demonstration purposes (testing framework)
- EXPECT_EQ(obj->a, 100);
- EXPECT_EQ(obj->b, 200);
+ EXPECT_EQ(required->a, 100);
+ EXPECT_EQ(required->b, 200);
+ optional = nullptr;
}
```
-For an unqualified `Object` subclass `T`, `ObjectPtr<T>` can also cross the
FFI boundary
-directly. It can be stored in `Any` and typed containers such as `Array`,
`List`, `Map`,
-`Dict`, `Tuple`, `Optional`, `Variant`, and `Expected`, and can be used as a
reflected field or
-function parameter. Copying through `Any` retains the object; moving transfers
its reference. A
-null pointer is represented by the FFI `None` value. Qualified pointee types
such as
-`ObjectPtr<const T>`, `ObjectPtr<volatile T>`, and reference pointee types are
not supported.
-Because the ABI has one `None` representation, `Optional<ObjectPtr<T>>` cannot
preserve the
-difference between an absent optional and a present null pointer across an FFI
round trip.
+For an unqualified `Object` subclass `T`, use `Arc<T>` when the object is
required and
+`ObjectPtr<T>` when it may be absent. Both are owning pointers with the same
one-pointer layout and
+reference-counting behavior. Copying through `Any` retains the object, while
moving transfers its
+reference. They can be used in typed containers such as `Array`, `List`,
`Map`, `Dict`, `Tuple`,
+`Optional`, `Variant`, and `Expected`, as well as reflected fields and
function signatures.
+
+`Arc<T>` has no default or `nullptr` constructor, and `make_arc<T>(args...)`
is its normal creation
+API. Its type schema is the bare object schema `{"type":"T"}`, so ABI `None`
is rejected. By
+contrast, `ObjectPtr<T>` remains mechanically nullable, maps a null pointer to
ABI `None`, and has
+schema `Optional[T]`. `Optional<Arc<T>>` is also supported and produces
exactly `Optional[T]`; use
+it when the native field needs the 16-byte `Optional` carrier rather than the
8-byte
+`ObjectPtr<T>` carrier.
+
+Normal C++ move semantics leave a moved-from `Arc` empty. `Arc(UnsafeInit{})`
also creates an empty
+value for reflection's controlled construct-then-populate path. These states
are exceptions to the
+safe API's non-null guarantee and must be populated before use; Arc operations
rely on the invariant
+without runtime validation. A reflected class with an `Arc` field should
initialize that field
+explicitly in its unsafe constructor, for example
+`HolderObj(UnsafeInit) : required(UnsafeInit{}) {}`. Because `Arc` publicly
inherits `ObjectPtr`,
+deliberately mutating it through the base class can likewise bypass the
guarantee and is unsafe.
+
+Qualified pointee types such as `Arc<const T>`, `ObjectPtr<const T>`, volatile
pointees, and
+reference pointee types are not supported.
We typically provide a reference class that wraps the ObjectPtr.
The `ObjectRef` base class provides the interface and reference counting
@@ -169,7 +185,8 @@ The ObjectRef acts as a smart pointer wrapper that
automatically manages the Obj
The overall implementation pattern is as follows:
- **Object Class**: Inherits from `ffi::Object`, stores data and implements
the core functionality.
-- **ObjectPtr**: Smart pointer that manages the Object lifecycle and reference
counting.
+- **Arc**: Non-null owning pointer used for required object values.
+- **ObjectPtr**: Nullable owning pointer used when the object may be absent.
- **Ref Class**: Inherits from `ffi::ObjectRef`, provides a user-friendly
interface and automatic memory management.
This design ensures efficient memory management while providing a clean API
for users. Once we define an ObjectRef class,
diff --git a/include/tvm/ffi/container/array.h
b/include/tvm/ffi/container/array.h
index fd2c8233..c2da576c 100644
--- a/include/tvm/ffi/container/array.h
+++ b/include/tvm/ffi/container/array.h
@@ -425,7 +425,8 @@ class Array : public ObjectRef {
*/
void push_back(const T& item) {
ArrayObj* p = CopyOnWrite(1);
- p->EmplaceInit(p->TVMFFISeqCell::size++, item);
+ p->EmplaceInit(p->TVMFFISeqCell::size, item);
+ ++p->TVMFFISeqCell::size;
}
/*!
@@ -435,7 +436,8 @@ class Array : public ObjectRef {
template <typename... Args>
void emplace_back(Args&&... args) {
ArrayObj* p = CopyOnWrite(1);
- p->EmplaceInit(p->TVMFFISeqCell::size++, std::forward<Args>(args)...);
+ p->EmplaceInit(p->TVMFFISeqCell::size, std::forward<Args>(args)...);
+ ++p->TVMFFISeqCell::size;
}
/*!
diff --git a/include/tvm/ffi/container/list.h b/include/tvm/ffi/container/list.h
index be2bda81..bf865664 100644
--- a/include/tvm/ffi/container/list.h
+++ b/include/tvm/ffi/container/list.h
@@ -323,7 +323,8 @@ class List : public ObjectRef {
*/
void push_back(const T& item) {
ListObj* p = EnsureCapacity(1);
- p->EmplaceInit(p->TVMFFISeqCell::size++, item);
+ p->EmplaceInit(p->TVMFFISeqCell::size, item);
+ ++p->TVMFFISeqCell::size;
}
/*!
@@ -333,7 +334,8 @@ class List : public ObjectRef {
template <typename... Args>
void emplace_back(Args&&... args) {
ListObj* p = EnsureCapacity(1);
- p->EmplaceInit(p->TVMFFISeqCell::size++, std::forward<Args>(args)...);
+ p->EmplaceInit(p->TVMFFISeqCell::size, std::forward<Args>(args)...);
+ ++p->TVMFFISeqCell::size;
}
/*!
diff --git a/include/tvm/ffi/memory.h b/include/tvm/ffi/memory.h
index 3b02c5b7..c4e07841 100644
--- a/include/tvm/ffi/memory.h
+++ b/include/tvm/ffi/memory.h
@@ -296,6 +296,17 @@ inline ObjectPtr<T> make_object(Args&&... args) {
return
details::SimpleObjAllocator().make_object<T>(std::forward<Args>(args)...);
}
+/*!
+ * \brief Allocate an object and return a non-null owning pointer.
+ * \param args Arguments to the constructor.
+ * \tparam T The node type.
+ * \return An Arc owning the allocated object.
+ */
+template <typename T, typename... Args>
+inline Arc<T> make_arc(Args&&... args) {
+ return
details::ObjectUnsafe::ArcFromObjectPtr(make_object<T>(std::forward<Args>(args)...));
+}
+
/*!
* \brief Allocate an Object with additional ElemType[num_elems] that are
stored right after.
* \param num_elems The number of elements in the array.
diff --git a/include/tvm/ffi/object.h b/include/tvm/ffi/object.h
index c945b5b1..25ee7699 100644
--- a/include/tvm/ffi/object.h
+++ b/include/tvm/ffi/object.h
@@ -529,6 +529,70 @@ class ObjectPtr {
friend struct tvm::ffi::details::ObjectUnsafe;
};
+/*!
+ * \brief A non-null owning pointer for Object.
+ * \tparam T The Object subclass being owned.
+ * \sa make_arc
+ *
+ * Arc has the same layout as ObjectPtr and supports the same copy, move, and
+ * derived-to-base conversions. Unlike ObjectPtr, its safe public API cannot
+ * create a null value. A moved-from Arc and an Arc explicitly constructed with
+ * UnsafeInit may be null; such values must be populated before use. Operations
+ * on Arc rely on the non-null invariant and do not validate it at runtime.
+ */
+template <typename T>
+class Arc : public ObjectPtr<T> {
+ public:
+ Arc() = delete;
+ Arc(std::nullptr_t) = delete;
+
+ /*! \brief Copy constructor. */
+ Arc(const Arc&) = default;
+
+ /*! \brief Move constructor. */
+ Arc(Arc&&) = default;
+
+ /*! \brief Copy assignment operator. */
+ Arc& operator=(const Arc&) = default;
+
+ /*! \brief Move assignment operator. */
+ Arc& operator=(Arc&&) = default;
+
+ /*!
+ * \brief Copy-upcast an Arc of a derived Object type.
+ * \param other The Arc to copy.
+ */
+ template <typename U, std::enable_if_t<std::is_base_of_v<T, U>, int> = 0>
+ Arc(const Arc<U>& other) // NOLINT(*)
+ : ObjectPtr<T>(static_cast<const ObjectPtr<U>&>(other)) {}
+
+ /*!
+ * \brief Move-upcast an Arc of a derived Object type.
+ * \param other The Arc to move.
+ */
+ template <typename U, std::enable_if_t<std::is_base_of_v<T, U>, int> = 0>
+ Arc(Arc<U>&& other) // NOLINT(*)
+ : ObjectPtr<T>(std::move(static_cast<ObjectPtr<U>&>(other))) {}
+
+ /*!
+ * \brief Construct an empty Arc for controlled construct-then-populate
flows.
+ * \param tag The unsafe initialization tag.
+ */
+ explicit Arc(UnsafeInit tag) : ObjectPtr<T>(nullptr) {
static_cast<void>(tag); }
+
+ private:
+ explicit Arc(ObjectPtr<T> ptr) : ObjectPtr<T>(std::move(ptr)) {}
+
+ using ObjectPtr<T>::reset;
+ using ObjectPtr<T>::swap;
+
+ template <typename U, typename... Args>
+ friend Arc<U> make_arc(Args&&... args);
+ template <typename>
+ friend class Arc;
+ friend struct tvm::ffi::details::ObjectUnsafe;
+};
+
/*!
* \brief Whether T is Object or a subclass of Object.
* \tparam T The type to inspect.
@@ -555,6 +619,24 @@ inline constexpr bool is_qualified_object_v =
template <typename BaseObject, typename DerivedObject>
inline constexpr bool type_subsumes_v<ObjectPtr<BaseObject>,
ObjectPtr<DerivedObject>> =
std::is_base_of_v<BaseObject, DerivedObject>;
+
+/*!
+ * \brief Whether target Arc storage subsumes source Arc storage.
+ * \tparam BaseObject The target Arc pointee type.
+ * \tparam DerivedObject The source Arc pointee type.
+ */
+template <typename BaseObject, typename DerivedObject>
+inline constexpr bool type_subsumes_v<Arc<BaseObject>, Arc<DerivedObject>> =
+ std::is_base_of_v<BaseObject, DerivedObject>;
+
+/*!
+ * \brief Whether nullable ObjectPtr storage subsumes non-null Arc storage.
+ * \tparam BaseObject The target ObjectPtr pointee type.
+ * \tparam DerivedObject The source Arc pointee type.
+ */
+template <typename BaseObject, typename DerivedObject>
+inline constexpr bool type_subsumes_v<ObjectPtr<BaseObject>,
Arc<DerivedObject>> =
+ std::is_base_of_v<BaseObject, DerivedObject>;
/// \endcond
/*!
@@ -1256,6 +1338,11 @@ struct ObjectUnsafe {
return ObjectPtrFromOwned<T>(reinterpret_cast<Object*>(obj_ptr));
}
+ template <typename T>
+ TVM_FFI_INLINE static Arc<T> ArcFromObjectPtr(ObjectPtr<T>&& ptr) {
+ return Arc<T>(std::move(ptr));
+ }
+
template <typename T>
TVM_FFI_INLINE static T* RawObjectPtrFromUnowned(TVMFFIObject* obj_ptr) {
// NOTE: this is important to first cast to Object*
@@ -1324,6 +1411,13 @@ struct TypeToRuntimeTypeIndex<
static int32_t v() { return TObject::RuntimeTypeIndex(); }
};
+template <typename TObject>
+struct TypeToRuntimeTypeIndex<
+ Arc<TObject>, std::enable_if_t<is_object_subclass_v<TObject> &&
+ std::is_same_v<TObject,
std::remove_cv_t<TObject>>>> {
+ static int32_t v() { return TObject::RuntimeTypeIndex(); }
+};
+
/*!
* \brief Type traits for an owning pointer to an unqualified Object subclass.
* \tparam TObject The unqualified Object subclass.
@@ -1388,6 +1482,53 @@ struct TypeTraits<ObjectPtr<TObject>,
}
};
+/*!
+ * \brief Type traits for a non-null owning pointer to an unqualified Object
subclass.
+ * \tparam TObject The unqualified Object subclass.
+ */
+template <typename TObject>
+struct TypeTraits<Arc<TObject>,
+ std::enable_if_t<is_object_subclass_v<TObject> &&
+ std::is_same_v<TObject,
std::remove_cv_t<TObject>>>>
+ : public TypeTraitsBase {
+ static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIObject;
+
+ TVM_FFI_INLINE static void CopyToAnyView(const Arc<TObject>& src, TVMFFIAny*
result) {
+ TypeTraits<ObjectPtr<TObject>>::CopyToAnyView(src, result);
+ }
+
+ TVM_FFI_INLINE static void MoveToAny(Arc<TObject> src, TVMFFIAny* result) {
+ TypeTraits<ObjectPtr<TObject>>::MoveToAny(std::move(src), result);
+ }
+
+ TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
+ return src->type_index >= TypeIndex::kTVMFFIStaticObjectBegin &&
+ details::IsObjectInstance<TObject>(src->type_index);
+ }
+
+ TVM_FFI_INLINE static Arc<TObject> CopyFromAnyViewAfterCheck(const
TVMFFIAny* src) {
+ return details::ObjectUnsafe::ArcFromObjectPtr(
+ details::ObjectUnsafe::ObjectPtrFromUnowned<TObject>(src->v_obj));
+ }
+
+ TVM_FFI_INLINE static Arc<TObject> MoveFromAnyAfterCheck(TVMFFIAny* src) {
+ Arc<TObject> result = details::ObjectUnsafe::ArcFromObjectPtr(
+ details::ObjectUnsafe::ObjectPtrFromOwned<TObject>(src->v_obj));
+ TypeTraits<std::nullptr_t>::MoveToAny(nullptr, src);
+ return result;
+ }
+
+ TVM_FFI_INLINE static std::optional<Arc<TObject>> TryCastFromAnyView(const
TVMFFIAny* src) {
+ if (CheckAnyStrict(src)) return CopyFromAnyViewAfterCheck(src);
+ return std::nullopt;
+ }
+
+ TVM_FFI_INLINE static std::string TypeStr() { return TObject::_type_key; }
+ TVM_FFI_INLINE static std::string TypeSchema() {
+ return R"({"type":")" + std::string(TObject::_type_key) + R"("})";
+ }
+};
+
template <typename TObjRef>
struct ObjectRefTypeTraitsBase : public TypeTraitsBase {
static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIObject;
diff --git a/python/tvm_ffi/dataclasses/gen_abi_cpp.py
b/python/tvm_ffi/dataclasses/gen_abi_cpp.py
index b7db91b2..6ec58cff 100644
--- a/python/tvm_ffi/dataclasses/gen_abi_cpp.py
+++ b/python/tvm_ffi/dataclasses/gen_abi_cpp.py
@@ -316,22 +316,37 @@ class _Generator:
self.builtins = _builtin_table()
self.dependencies: dict[int, TypeInfo] = {info.type_index: info for
info in emitted_infos}
+ def _get_dynamic_object_cpp_type(self, schema: TypeSchema) -> str:
+ if schema.origin_type_index < _DYNAMIC_OBJECT_TYPE_INDEX_BEGIN:
+ raise ValueError(f"Schema {schema!r} is not a registered object
type")
+ info = _lookup_or_register_type_info_from_type_key(schema.origin)
+ self.dependencies[info.type_index] = info
+ return _cpp_name(info.type_key).qualified
+
def _lower_object(self, schema: TypeSchema) -> _Carrier:
type_index = schema.origin_type_index
if type_index == _OBJECT_TYPE_INDEX or schema.origin == "Object":
- return _Carrier("::tvm::ffi::ObjectPtr<::tvm::ffi::Object>", 8, 8)
+ return _Carrier("::tvm::ffi::Arc<::tvm::ffi::Object>", 8, 8)
builtin = self.builtins.get(schema.origin_type_index)
if builtin is not None:
if builtin.value_type is None:
raise ValueError(
f"Static TVM-FFI type {schema.origin!r} has no supported
C++ value wrapper"
)
+ if builtin.value_type.startswith("::tvm::ffi::ObjectPtr<"):
+ return _Carrier(f"::tvm::ffi::Arc<{builtin.object_type}>", 8,
8)
return _Carrier(builtin.value_type, builtin.size,
builtin.alignment)
- if schema.origin_type_index < _DYNAMIC_OBJECT_TYPE_INDEX_BEGIN:
- raise ValueError(f"Schema {schema!r} is not a registered object
type")
- info = _lookup_or_register_type_info_from_type_key(schema.origin)
- self.dependencies[info.type_index] = info
- object_type = _cpp_name(info.type_key).qualified
+ object_type = self._get_dynamic_object_cpp_type(schema)
+ return _Carrier(f"::tvm::ffi::Arc<{object_type}>", 8, 8)
+
+ def _lower_nullable_object(self, schema: TypeSchema) -> _Carrier:
+ type_index = schema.origin_type_index
+ if type_index == _OBJECT_TYPE_INDEX or schema.origin == "Object":
+ object_type = "::tvm::ffi::Object"
+ elif (builtin := self.builtins.get(type_index)) is not None:
+ object_type = builtin.object_type
+ else:
+ object_type = self._get_dynamic_object_cpp_type(schema)
return _Carrier(f"::tvm::ffi::ObjectPtr<{object_type}>", 8, 8)
def _lower_value(
@@ -359,18 +374,22 @@ class _Generator:
if origin in scalar:
return scalar[origin]
args = schema.args
- if (
- origin == "Optional"
- and is_native_field
- and container_argument
- and args[0].origin_type_index >= _OBJECT_TYPE_INDEX
- ):
- return self._lower_object(args[0])
if origin in ("Optional", "Union"):
- # Container elements are already stored as Any cells. Keeping the
- # view erased avoids depending on the unstable native wrapper
- # representation while preserving the outer container layout.
- return _Carrier("::tvm::ffi::Any", 16, 8)
+ carrier = _Carrier("::tvm::ffi::Any", 16, 8)
+ if (
+ origin == "Optional"
+ and container_argument
+ and args[0].origin_type_index >= _OBJECT_TYPE_INDEX
+ ):
+ carrier = self._lower_nullable_object(args[0])
+ elif (
+ origin == "Union"
+ and container_argument
+ and all(arg.origin_type_index >= _OBJECT_TYPE_INDEX for arg in
args)
+ ):
+ carrier = _Carrier("::tvm::ffi::Arc<::tvm::ffi::Object>", 8, 8)
+ # Other structural elements keep their discriminated Any storage.
+ return carrier
container_origins = {
"Array": "::tvm::ffi::Array",
@@ -441,18 +460,24 @@ class _Generator:
elif value_schema.origin_type_index >= _OBJECT_TYPE_INDEX and (
field.size,
field.alignment,
+ ) == (
+ 16,
+ 8,
+ ):
+ value_carrier = self._lower_object(value_schema)
+ carrier = _Carrier(
+ f"::tvm::ffi::Optional<{value_carrier.cpp_type}>",
+ 16,
+ 8,
+ )
+ elif value_schema.origin_type_index >= _OBJECT_TYPE_INDEX and (
+ field.size,
+ field.alignment,
) == (
8,
8,
):
- carrier = self._lower_object(value_schema)
- if carrier.size != 8:
- builtin =
self.builtins.get(value_schema.origin_type_index)
- if builtin is None:
- raise ValueError(
- f"Cannot prove pointer carrier for
{owner.type_key}.{field.name}"
- )
- carrier =
_Carrier(f"::tvm::ffi::ObjectPtr<{builtin.object_type}>", 8, 8)
+ carrier = self._lower_nullable_object(value_schema)
else:
raise ValueError(
f"Ambiguous native Optional carrier for
{owner.type_key}.{field.name} "
@@ -463,7 +488,7 @@ class _Generator:
and (field.size, field.alignment) == (8, 8)
and all(arg.origin_type_index >= _OBJECT_TYPE_INDEX for arg in
schema.args)
):
- carrier =
_Carrier("::tvm::ffi::ObjectPtr<::tvm::ffi::Object>", 8, 8)
+ carrier = _Carrier("::tvm::ffi::Arc<::tvm::ffi::Object>", 8, 8)
else:
raise ValueError(
f"Ambiguous native {schema.origin} carrier for
{owner.type_key}.{field.name} "
@@ -493,7 +518,7 @@ class _Generator:
# derives from std::exception) contain more than one pointer.
# A reflected one-pointer field uses the canonical object class
# instead of pretending that the larger wrapper is
layout-compatible.
- carrier =
_Carrier(f"::tvm::ffi::ObjectPtr<{builtin.object_type}>", 8, 8)
+ carrier = _Carrier(f"::tvm::ffi::Arc<{builtin.object_type}>", 8, 8)
expected = (8, 8)
if actual != expected:
raise ValueError(
diff --git a/python/tvm_ffi/testing/testing.py
b/python/tvm_ffi/testing/testing.py
index e28af64c..b4797d67 100644
--- a/python/tvm_ffi/testing/testing.py
+++ b/python/tvm_ffi/testing/testing.py
@@ -128,16 +128,17 @@ class TestObjectDerived(TestObjectBase):
@c_class("testing.TestObjectPtrHolder")
class TestObjectPtrHolder(Object):
- """Test object with an ``ObjectPtr<TestObjectBase>`` field."""
+ """Test object with ``Arc`` and nullable ``ObjectPtr`` fields."""
__test__ = False
# tvm-ffi-stubgen(begin): object/testing.TestObjectPtrHolder
# fmt: off
- value: TestObjectBase | None
+ value: TestObjectBase
+ optional_value: TestObjectBase | None
if TYPE_CHECKING:
- def __init__(self, value: TestObjectBase | None) -> None: ...
- def __ffi_init__(self, value: TestObjectBase | None) -> None: ... #
ty: ignore[invalid-method-override]
+ def __init__(self, value: TestObjectBase, optional_value:
TestObjectBase | None = ...) -> None: ...
+ def __ffi_init__(self, value: TestObjectBase, optional_value:
TestObjectBase | None = ...) -> None: ... # ty: ignore[invalid-method-override]
# fmt: on
# tvm-ffi-stubgen(end)
diff --git a/src/ffi/testing/testing.cc b/src/ffi/testing/testing.cc
index 7c9e47a4..21b87b46 100644
--- a/src/ffi/testing/testing.cc
+++ b/src/ffi/testing/testing.cc
@@ -171,9 +171,10 @@ class TestObjectDerived : public TestObjectBase {
class TestObjectPtrHolder : public Object {
public:
- ObjectPtr<TestObjectBase> value;
+ Arc<TestObjectBase> value;
+ ObjectPtr<TestObjectBase> optional_value;
- explicit TestObjectPtrHolder(UnsafeInit) {}
+ explicit TestObjectPtrHolder(UnsafeInit) : value(UnsafeInit{}) {}
static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("testing.TestObjectPtrHolder",
TestObjectPtrHolder, Object);
@@ -481,7 +482,9 @@ TVM_FFI_STATIC_INIT_BLOCK() {
.def_rw("v_map", &TestObjectDerived::v_map)
.def_rw("v_array", &TestObjectDerived::v_array);
- refl::ObjectDef<TestObjectPtrHolder>().def_rw("value",
&TestObjectPtrHolder::value);
+ refl::ObjectDef<TestObjectPtrHolder>()
+ .def_rw("value", &TestObjectPtrHolder::value)
+ .def_rw("optional_value", &TestObjectPtrHolder::optional_value,
refl::default_value(nullptr));
refl::ObjectDef<TestCxxClassBase>()
.def_rw("v_i64", &TestCxxClassBase::v_i64, refl::repr(false))
diff --git a/tests/cpp/extra/test_serialization.cc
b/tests/cpp/extra/test_serialization.cc
index 65f7d414..7c30c7f3 100644
--- a/tests/cpp/extra/test_serialization.cc
+++ b/tests/cpp/extra/test_serialization.cc
@@ -757,9 +757,9 @@ TEST(Serialization, SharedObjectReferences) {
EXPECT_EQ(result->body[0].get(), result->body[1].get());
}
-TEST(Serialization, ObjectPtrFields) {
- ObjectPtr<TIntObj> shared = make_object<TIntObj>(42);
- ObjectPtr<TIntObj> value = shared;
+TEST(Serialization, ArcAndObjectPtrFields) {
+ tvm::ffi::Arc<TIntObj> shared = make_arc<TIntObj>(42);
+ tvm::ffi::Arc<TIntObj> value = shared;
ObjectPtr<TNumberObj> alias = shared;
TObjectPtrHolder holder(std::move(value), std::move(alias));
@@ -767,20 +767,21 @@ TEST(Serialization, ObjectPtrFields) {
TObjectPtrHolder result = deserialized.cast<TObjectPtrHolder>();
ASSERT_NE(result->value, nullptr);
- ASSERT_NE(result->alias, nullptr);
- EXPECT_EQ(result->value.get(), result->alias.get());
+ ASSERT_NE(result->optional_alias, nullptr);
+ EXPECT_EQ(result->value.get(), result->optional_alias.get());
ASSERT_TRUE(result->value->IsInstance<TIntObj>());
EXPECT_EQ(static_cast<TIntObj*>(result->value.get())->value, 42);
}
-TEST(Serialization, NullObjectPtrFields) {
- TObjectPtrHolder holder(nullptr, nullptr);
+TEST(Serialization, NullableObjectPtrFieldNone) {
+ TObjectPtrHolder holder(make_arc<TIntObj>(42), nullptr);
Any deserialized = FromJSONGraph(ToJSONGraph(holder));
TObjectPtrHolder result = deserialized.cast<TObjectPtrHolder>();
- EXPECT_EQ(result->value, nullptr);
- EXPECT_EQ(result->alias, nullptr);
+ ASSERT_NE(result->value, nullptr);
+ EXPECT_EQ(result->value->value, 42);
+ EXPECT_EQ(result->optional_alias, nullptr);
}
// ---------------------------------------------------------------------------
diff --git a/tests/cpp/extra/test_structural_visit.cc
b/tests/cpp/extra/test_structural_visit.cc
index 1cc0a9df..0c65a298 100644
--- a/tests/cpp/extra/test_structural_visit.cc
+++ b/tests/cpp/extra/test_structural_visit.cc
@@ -345,8 +345,8 @@ TEST(StructuralVisitor, WalkVisitsObjectPtr) {
ExpectTrace(visited, {"x"});
}
-TEST(StructuralVisitor, WalkTraversesObjectPtrFields) {
- ObjectPtr<TIntObj> value = make_object<TIntObj>(42);
+TEST(StructuralVisitor, WalkTraversesArcAndObjectPtrFields) {
+ tvm::ffi::Arc<TIntObj> value = make_arc<TIntObj>(42);
ObjectPtr<TNumberObj> alias = value;
TObjectPtrHolder root(value, alias);
size_t num_int_fields = 0;
diff --git a/tests/cpp/test_abi_object.cc b/tests/cpp/test_abi_object.cc
index 78badca8..23eef686 100644
--- a/tests/cpp/test_abi_object.cc
+++ b/tests/cpp/test_abi_object.cc
@@ -31,14 +31,14 @@ struct UnrelatedObj;
} // namespace abi_object_test
-// RecursiveObj is incomplete here. Specializing the trait enables its
ObjectPtr storage traits
-// before List<ObjectPtr<RecursiveObj>> is instantiated inside the class
definition below.
+// RecursiveObj is incomplete here. Specializing the trait enables its Arc
storage traits before
+// List<Arc<RecursiveObj>> is instantiated inside the class definition below.
template <>
inline constexpr bool
tvm::ffi::is_object_subclass_v<::abi_object_test::RecursiveObj> = true;
static_assert(::tvm::ffi::is_object_subclass_v<::abi_object_test::RecursiveObj>);
static_assert(
-
::tvm::ffi::details::storage_enabled_v<::tvm::ffi::ObjectPtr<::abi_object_test::RecursiveObj>>);
+
::tvm::ffi::details::storage_enabled_v<::tvm::ffi::Arc<::abi_object_test::RecursiveObj>>);
namespace abi_object_test {
@@ -55,7 +55,7 @@ struct GrandchildObj : public DerivedObj {
};
struct RecursiveObj : public ::tvm::ffi::Object {
- ::tvm::ffi::List<::tvm::ffi::ObjectPtr<RecursiveObj>> children;
+ ::tvm::ffi::List<::tvm::ffi::Arc<RecursiveObj>> children;
TVM_FFI_DECLARE_OBJECT_INFO_LOOKUP("testing.abi_object.Recursive", 1);
};
@@ -79,8 +79,16 @@ static_assert(
std::is_constructible_v<::tvm::ffi::ObjectPtr<BaseObj>,
::tvm::ffi::ObjectPtr<GrandchildObj>>);
static_assert(
std::is_assignable_v<::tvm::ffi::ObjectPtr<BaseObj>&,
::tvm::ffi::ObjectPtr<DerivedObj>>);
+static_assert(std::is_constructible_v<::tvm::ffi::Arc<BaseObj>,
::tvm::ffi::Arc<GrandchildObj>>);
+static_assert(std::is_assignable_v<::tvm::ffi::Arc<BaseObj>&,
::tvm::ffi::Arc<DerivedObj>>);
static_assert(::tvm::ffi::type_subsumes_v<::tvm::ffi::ObjectPtr<BaseObj>,
::tvm::ffi::ObjectPtr<GrandchildObj>>);
+static_assert(
+ ::tvm::ffi::type_subsumes_v<::tvm::ffi::Arc<BaseObj>,
::tvm::ffi::Arc<GrandchildObj>>);
+static_assert(
+ ::tvm::ffi::type_subsumes_v<::tvm::ffi::ObjectPtr<BaseObj>,
::tvm::ffi::Arc<GrandchildObj>>);
+static_assert(
+ !::tvm::ffi::type_subsumes_v<::tvm::ffi::Arc<BaseObj>,
::tvm::ffi::ObjectPtr<GrandchildObj>>);
static_assert(!::tvm::ffi::type_subsumes_v<::tvm::ffi::ObjectPtr<UnrelatedObj>,
::tvm::ffi::ObjectPtr<DerivedObj>>);
diff --git a/tests/cpp/test_object.cc b/tests/cpp/test_object.cc
index 70e453a9..b4e70359 100644
--- a/tests/cpp/test_object.cc
+++ b/tests/cpp/test_object.cc
@@ -680,4 +680,50 @@ TEST(ObjectPtrStorage, Expected) {
EXPECT_EQ(failure_roundtrip.error().kind(), "ValueError");
}
+using NumberArc = tvm::ffi::Arc<TNumberObj>;
+
+NumberArc MakeArcInt(int64_t value) { return make_arc<TIntObj>(value); }
+
+NumberArc MakeArcFloat(double value) { return make_arc<TFloatObj>(value); }
+
+TEST(ArcStorage, Containers) {
+ NumberArc first = MakeArcInt(1);
+ NumberArc second = MakeArcFloat(2.0);
+
+ Array<NumberArc> array{first};
+ array.push_back(second);
+ EXPECT_EQ(array[1], second);
+
+ List<NumberArc> list{first};
+ list.push_back(second);
+ EXPECT_EQ(list[1], second);
+
+ Map<String, NumberArc> map{{"value", first}};
+ map.Set("value", second);
+ EXPECT_EQ(map["value"], second);
+
+ Dict<String, NumberArc> dict{{"value", first}};
+ dict.Set("value", second);
+ EXPECT_EQ(dict["value"], second);
+
+ Tuple<NumberArc, NumberArc> tuple(first, first);
+ tuple.Set<1>(second);
+ EXPECT_EQ(tuple.get<1>(), second);
+
+ Variant<NumberArc, int64_t> variant = int64_t{3};
+ variant = first;
+ EXPECT_EQ(variant.get<NumberArc>(), first);
+
+ Expected<NumberArc> expected{first};
+ ASSERT_TRUE(expected.is_ok());
+ EXPECT_EQ(expected.value(), first);
+
+ EXPECT_EQ(TypeTraits<List<NumberArc>>::TypeSchema(),
+ R"({"type":"ffi.List","args":[{"type":"test.Number"}]})");
+ EXPECT_EQ((TypeTraits<Map<String, NumberArc>>::TypeSchema()),
+
R"({"type":"ffi.Map","args":[{"type":"ffi.String"},{"type":"test.Number"}]})");
+ EXPECT_EQ((TypeTraits<Tuple<NumberArc, int64_t>>::TypeSchema()),
+
R"({"type":"Tuple","args":[{"type":"test.Number"},{"type":"int"}]})");
+}
+
} // namespace
diff --git a/tests/cpp/test_object_ptr.cc b/tests/cpp/test_object_ptr.cc
index c8811bd1..6e87ccc0 100644
--- a/tests/cpp/test_object_ptr.cc
+++ b/tests/cpp/test_object_ptr.cc
@@ -114,14 +114,18 @@ class PointerAdjustedObj : public PointerAdjustmentPad,
public CxxBaseObj {
namespace {
using tvm::ffi::Any;
+using tvm::ffi::AnyView;
+using tvm::ffi::Arc;
using tvm::ffi::Array;
using tvm::ffi::Dict;
using tvm::ffi::List;
+using tvm::ffi::make_arc;
using tvm::ffi::make_object;
using tvm::ffi::Map;
using tvm::ffi::Object;
using tvm::ffi::ObjectPtr;
using tvm::ffi::ObjectRef;
+using tvm::ffi::Optional;
using tvm::ffi::String;
using tvm::ffi::TypeTraits;
using tvm::ffi::UnsafeInit;
@@ -134,6 +138,19 @@ using tvm::ffi::testing::MutualLeftObj;
using tvm::ffi::testing::MutualRightObj;
using tvm::ffi::testing::PointerAdjustedObj;
+template <typename T, typename = void>
+struct HasPublicReset : std::false_type {};
+
+template <typename T>
+struct HasPublicReset<T, std::void_t<decltype(std::declval<T&>().reset())>> :
std::true_type {};
+
+template <typename T, typename = void>
+struct HasPublicSwap : std::false_type {};
+
+template <typename T>
+struct HasPublicSwap<T,
std::void_t<decltype(std::declval<T&>().swap(std::declval<T&>()))>>
+ : std::true_type {};
+
static_assert(tvm::ffi::is_object_subclass_v<GeneratedBaseObj>);
static_assert(tvm::ffi::is_object_subclass_v<GeneratedDerivedObj>);
static_assert(std::is_convertible_v<GeneratedDerivedObj*, GeneratedBaseObj*>);
@@ -150,6 +167,25 @@ static_assert(
tvm::ffi::type_subsumes_v<ObjectPtr<GeneratedBaseObj>,
ObjectPtr<GeneratedDerivedObj>>);
static_assert(
!tvm::ffi::type_subsumes_v<ObjectPtr<GeneratedDerivedObj>,
ObjectPtr<GeneratedBaseObj>>);
+static_assert(sizeof(Arc<GeneratedDerivedObj>) ==
sizeof(ObjectPtr<GeneratedDerivedObj>));
+static_assert(alignof(Arc<GeneratedDerivedObj>) ==
alignof(ObjectPtr<GeneratedDerivedObj>));
+static_assert(std::is_standard_layout_v<Arc<GeneratedDerivedObj>>);
+static_assert(std::is_base_of_v<ObjectPtr<GeneratedDerivedObj>,
Arc<GeneratedDerivedObj>>);
+static_assert(!std::is_default_constructible_v<Arc<GeneratedDerivedObj>>);
+static_assert(!std::is_constructible_v<Arc<GeneratedDerivedObj>,
std::nullptr_t>);
+static_assert(std::is_constructible_v<Arc<GeneratedDerivedObj>, UnsafeInit>);
+static_assert(!std::is_constructible_v<Arc<GeneratedDerivedObj>,
ObjectPtr<GeneratedDerivedObj>>);
+static_assert(!HasPublicReset<Arc<GeneratedDerivedObj>>::value);
+static_assert(!HasPublicSwap<Arc<GeneratedDerivedObj>>::value);
+static_assert(std::is_constructible_v<Arc<GeneratedBaseObj>,
Arc<GeneratedDerivedObj>>);
+static_assert(std::is_assignable_v<Arc<GeneratedBaseObj>&,
Arc<GeneratedDerivedObj>>);
+static_assert(std::is_constructible_v<ObjectPtr<GeneratedBaseObj>,
Arc<GeneratedDerivedObj>>);
+static_assert(!TypeTraits<Arc<int>>::storage_enabled);
+static_assert(tvm::ffi::type_subsumes_v<Arc<GeneratedBaseObj>,
Arc<GeneratedDerivedObj>>);
+static_assert(tvm::ffi::type_subsumes_v<ObjectPtr<GeneratedBaseObj>,
Arc<GeneratedDerivedObj>>);
+static_assert(!tvm::ffi::type_subsumes_v<Arc<GeneratedBaseObj>,
ObjectPtr<GeneratedDerivedObj>>);
+static_assert(
+ !std::is_constructible_v<Array<Arc<GeneratedBaseObj>>,
Array<ObjectPtr<GeneratedDerivedObj>>>);
static_assert(std::is_same_v<Array<ObjectPtr<GeneratedDerivedObj>>::value_type,
ObjectPtr<GeneratedDerivedObj>>);
@@ -247,6 +283,98 @@ TEST(ObjectPtr, AnyRoundTripUsesRuntimeAncestry) {
EXPECT_EQ(null_value.cast<ObjectPtr<GeneratedBaseObj>>(), nullptr);
}
+TEST(Arc, ConstructionOwnershipAndUpcast) {
+ Arc<GeneratedDerivedObj> derived = make_arc<GeneratedDerivedObj>(42);
+ EXPECT_EQ(derived->value, 42);
+ EXPECT_EQ(derived.use_count(), 1);
+
+ Arc<GeneratedDerivedObj> copied = derived;
+ EXPECT_EQ(derived.use_count(), 2);
+ EXPECT_EQ(copied.get(), derived.get());
+
+ Arc<GeneratedBaseObj> upcast = derived;
+ EXPECT_EQ(derived.use_count(), 3);
+ EXPECT_EQ(upcast.get(), static_cast<GeneratedBaseObj*>(derived.get()));
+
+ copied = make_arc<GeneratedDerivedObj>(45);
+ EXPECT_EQ(derived.use_count(), 2);
+ EXPECT_EQ(copied->value, 45);
+
+ Arc<GeneratedDerivedObj> move_source = make_arc<GeneratedDerivedObj>(43);
+ const void* move_source_address = move_source.get();
+ Arc<GeneratedBaseObj> moved = std::move(move_source);
+ EXPECT_EQ(move_source, //
NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
+ nullptr);
+ EXPECT_EQ(moved.use_count(), 1);
+ EXPECT_EQ(static_cast<const void*>(moved.get()), move_source_address);
+
+ Arc<GeneratedBaseObj> assigned = make_arc<GeneratedDerivedObj>(44);
+ assigned = derived;
+ EXPECT_EQ(assigned.get(), static_cast<GeneratedBaseObj*>(derived.get()));
+ EXPECT_EQ(derived.use_count(), 3);
+}
+
+TEST(Arc, AnyRoundTripAndSchemas) {
+ Arc<GeneratedDerivedObj> derived = make_arc<GeneratedDerivedObj>(7);
+ Any value = derived;
+ EXPECT_EQ(derived.use_count(), 2);
+
+ Arc<GeneratedBaseObj> base = value.cast<Arc<GeneratedBaseObj>>();
+ EXPECT_EQ(base.get(), static_cast<GeneratedBaseObj*>(derived.get()));
+ EXPECT_EQ(derived.use_count(), 3);
+ EXPECT_FALSE(value.try_cast<Arc<GeneratedUnrelatedObj>>().has_value());
+
+ Any none;
+ EXPECT_FALSE(none.as<Arc<GeneratedBaseObj>>().has_value());
+ EXPECT_FALSE(none.try_cast<Arc<GeneratedBaseObj>>().has_value());
+ EXPECT_THROW(none.cast<Arc<GeneratedBaseObj>>(), tvm::ffi::Error);
+
+ EXPECT_EQ(tvm::ffi::TypeToRuntimeTypeIndex<Arc<GeneratedDerivedObj>>::v(),
+ GeneratedDerivedObj::RuntimeTypeIndex());
+ EXPECT_EQ(TypeTraits<Arc<GeneratedBaseObj>>::TypeSchema(),
R"({"type":"testing.GeneratedBase"})");
+ EXPECT_EQ(TypeTraits<ObjectPtr<GeneratedBaseObj>>::TypeSchema(),
+
R"({"type":"Optional","args":[{"type":"testing.GeneratedBase"}]})");
+ EXPECT_EQ(TypeTraits<Optional<Arc<GeneratedBaseObj>>>::TypeSchema(),
+
R"({"type":"Optional","args":[{"type":"testing.GeneratedBase"}]})");
+
+ Optional<Arc<GeneratedBaseObj>> present = Arc<GeneratedBaseObj>(derived);
+ ASSERT_TRUE(present.has_value());
+ EXPECT_EQ(present.value().get(),
static_cast<GeneratedBaseObj*>(derived.get()));
+ Optional<Arc<GeneratedBaseObj>> absent = std::nullopt;
+
EXPECT_FALSE(Any(absent).cast<Optional<Arc<GeneratedBaseObj>>>().has_value());
+}
+
+TEST(Arc, ContainerStorageAndValidation) {
+ Arc<GeneratedDerivedObj> first = make_arc<GeneratedDerivedObj>(1);
+ Arc<GeneratedDerivedObj> second = make_arc<GeneratedDerivedObj>(2);
+
+ Array<Arc<GeneratedDerivedObj>> derived_array{first};
+ Array<Arc<GeneratedBaseObj>> base_array = derived_array;
+ EXPECT_TRUE(base_array.same_as(derived_array));
+ Array<ObjectPtr<GeneratedBaseObj>> nullable_base_array = derived_array;
+ EXPECT_TRUE(nullable_base_array.same_as(derived_array));
+
+ derived_array.push_back(second);
+ EXPECT_EQ(derived_array.size(), 2U);
+
+ Array<ObjectPtr<GeneratedDerivedObj>> checked_source{first};
+ Array<Arc<GeneratedBaseObj>> checked =
Any(checked_source).cast<Array<Arc<GeneratedBaseObj>>>();
+ // Runtime casting validates every nullable source element before reusing
the storage.
+ EXPECT_TRUE(checked.same_as(checked_source));
+ EXPECT_EQ(checked[0].get(), static_cast<GeneratedBaseObj*>(first.get()));
+
+ Array<ObjectPtr<GeneratedDerivedObj>> nullable_source{nullptr, first};
+ Any nullable_value = nullable_source;
+
EXPECT_FALSE(nullable_value.try_cast<Array<Arc<GeneratedBaseObj>>>().has_value());
+ EXPECT_THROW(nullable_value.cast<Array<Arc<GeneratedBaseObj>>>(),
tvm::ffi::Error);
+
+ EXPECT_EQ(TypeTraits<Array<Arc<GeneratedDerivedObj>>>::TypeSchema(),
+
R"({"type":"ffi.Array","args":[{"type":"testing.GeneratedDerived"}]})");
+ EXPECT_EQ(
+ TypeTraits<Array<ObjectPtr<GeneratedDerivedObj>>>::TypeSchema(),
+
R"({"type":"ffi.Array","args":[{"type":"Optional","args":[{"type":"testing.GeneratedDerived"}]}]})");
+}
+
TEST(ObjectPtr, ContainerCovariance) {
ObjectPtr<GeneratedDerivedObj> first = make_object<GeneratedDerivedObj>(1);
ObjectPtr<GeneratedDerivedObj> second = make_object<GeneratedDerivedObj>(2);
diff --git a/tests/cpp/test_reflection.cc b/tests/cpp/test_reflection.cc
index 89746fc6..361ef9d5 100644
--- a/tests/cpp/test_reflection.cc
+++ b/tests/cpp/test_reflection.cc
@@ -138,79 +138,103 @@ TEST(Reflection, FieldSetter) {
}
TEST(Reflection, ObjectPtrField) {
- ObjectPtr<TIntObj> initial = make_object<TIntObj>(10);
+ tvm::ffi::Arc<TIntObj> initial = make_arc<TIntObj>(10);
TIntObj* initial_raw = initial.get();
TObjectPtrHolder holder(initial);
EXPECT_EQ(initial.use_count(), 2);
reflection::FieldGetter getter("test.ObjectPtrHolder", "value");
- Any value = getter(holder);
- EXPECT_EQ(initial.use_count(), 3);
- ObjectPtr<TIntObj> reflected = value.cast<ObjectPtr<TIntObj>>();
- EXPECT_EQ(initial.use_count(), 4);
- EXPECT_EQ(reflected.get(), initial_raw);
- reflected.reset();
- value.reset();
+ {
+ Any value = getter(holder);
+ EXPECT_EQ(initial.use_count(), 3);
+ tvm::ffi::Arc<TIntObj> reflected = value.cast<tvm::ffi::Arc<TIntObj>>();
+ EXPECT_EQ(initial.use_count(), 4);
+ EXPECT_EQ(reflected.get(), initial_raw);
+ }
EXPECT_EQ(initial.use_count(), 2);
reflection::FieldSetter setter("test.ObjectPtrHolder", "value");
- ObjectPtr<TIntObj> replacement = make_object<TIntObj>(20);
+ tvm::ffi::Arc<TIntObj> replacement = make_arc<TIntObj>(20);
setter(holder, replacement);
EXPECT_EQ(replacement.use_count(), 2);
EXPECT_EQ(holder->value.get(), replacement.get());
EXPECT_EQ(initial.use_count(), 1);
- ObjectPtr<TFloatObj> incompatible = make_object<TFloatObj>(2.5);
+ tvm::ffi::Arc<TFloatObj> incompatible = make_arc<TFloatObj>(2.5);
EXPECT_THROW(setter(holder, incompatible), Error);
EXPECT_EQ(holder->value.get(), replacement.get());
EXPECT_EQ(incompatible.use_count(), 1);
+ EXPECT_THROW(setter(holder, nullptr), Error);
+ EXPECT_EQ(holder->value.get(), replacement.get());
+
ObjectPtr<TIntObj> null_value;
- setter(holder, null_value);
- EXPECT_EQ(holder->value, nullptr);
- EXPECT_EQ(replacement.use_count(), 1);
+ EXPECT_THROW(setter(holder, null_value), Error);
+ EXPECT_EQ(holder->value.get(), replacement.get());
EXPECT_THROW(setter(holder, String("not a number")), Error);
- EXPECT_EQ(holder->value, nullptr);
+ EXPECT_EQ(holder->value.get(), replacement.get());
+
+ reflection::FieldGetter optional_getter("test.ObjectPtrHolder",
"optional_alias");
+ reflection::FieldSetter optional_setter("test.ObjectPtrHolder",
"optional_alias");
+ EXPECT_EQ(optional_getter(holder), nullptr);
+ optional_setter(holder, initial);
+ EXPECT_EQ(optional_getter(holder).cast<ObjectPtr<TNumberObj>>().get(),
initial.get());
+ optional_setter(holder, nullptr);
+ EXPECT_EQ(optional_getter(holder), nullptr);
}
TEST(Reflection, ObjectPtrFieldInfo) {
const TVMFFIFieldInfo* info =
reflection::GetFieldInfo("test.ObjectPtrHolder", "value");
EXPECT_EQ(info->field_static_type_index, TypeIndex::kTVMFFIObject);
- EXPECT_EQ(info->size, sizeof(ObjectPtr<TIntObj>));
- EXPECT_EQ(info->alignment, alignof(ObjectPtr<TIntObj>));
+ EXPECT_EQ(info->size, sizeof(tvm::ffi::Arc<TIntObj>));
+ EXPECT_EQ(info->alignment, alignof(tvm::ffi::Arc<TIntObj>));
Map<String, Any> metadata =
json::Parse(String(info->metadata)).cast<Map<String, Any>>();
- EXPECT_EQ(metadata["type_schema"].cast<String>(),
- R"({"type":"Optional","args":[{"type":"test.Int"}]})");
-
- const TVMFFIFieldInfo* alias_info =
reflection::GetFieldInfo("test.ObjectPtrHolder", "alias");
- Map<String, Any> alias_metadata =
- json::Parse(String(alias_info->metadata)).cast<Map<String, Any>>();
- EXPECT_EQ(alias_metadata["type_schema"].cast<String>(),
+ EXPECT_EQ(metadata["type_schema"].cast<String>(), R"({"type":"test.Int"})");
+
+ const TVMFFIFieldInfo* optional_info =
+ reflection::GetFieldInfo("test.ObjectPtrHolder", "optional_alias");
+ EXPECT_EQ(optional_info->size, sizeof(ObjectPtr<TNumberObj>));
+ EXPECT_EQ(optional_info->alignment, alignof(ObjectPtr<TNumberObj>));
+ Map<String, Any> optional_metadata =
+ json::Parse(String(optional_info->metadata)).cast<Map<String, Any>>();
+ EXPECT_EQ(optional_metadata["type_schema"].cast<String>(),
R"({"type":"Optional","args":[{"type":"test.Number"}]})");
}
TEST(Reflection, ObjectPtrMethod) {
Function identity = reflection::GetMethod("test.ObjectPtrHolder",
"identity");
- ObjectPtr<TIntObj> input = make_object<TIntObj>(21);
+ tvm::ffi::Arc<TIntObj> input = make_arc<TIntObj>(21);
TIntObj* raw_input = input.get();
Any result = identity(input);
EXPECT_EQ(input.use_count(), 2);
- ObjectPtr<TIntObj> output = std::move(result).cast<ObjectPtr<TIntObj>>();
+ tvm::ffi::Arc<TIntObj> output =
std::move(result).cast<tvm::ffi::Arc<TIntObj>>();
EXPECT_EQ(result, nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(output.get(), raw_input);
EXPECT_EQ(input.use_count(), 2);
- Any null_result = identity(ObjectPtr<TIntObj>());
- EXPECT_EQ(null_result, nullptr);
- EXPECT_THROW(identity(make_object<TFloatObj>(2.5)), Error);
+ EXPECT_THROW(identity(nullptr), Error);
+ EXPECT_THROW(identity(ObjectPtr<TIntObj>()), Error);
+ EXPECT_THROW(identity(make_arc<TFloatObj>(2.5)), Error);
EXPECT_THROW(identity(String("not a number")), Error);
+ Function optional_identity = reflection::GetMethod("test.ObjectPtrHolder",
"optional_identity");
+ EXPECT_EQ(optional_identity(nullptr), nullptr);
+ Any optional_result = optional_identity(input);
+ EXPECT_EQ(optional_result.cast<ObjectPtr<TIntObj>>().get(), raw_input);
+
const TVMFFIMethodInfo* info =
reflection::GetMethodInfo("test.ObjectPtrHolder", "identity");
Map<String, Any> metadata =
json::Parse(String(info->metadata)).cast<Map<String, Any>>();
+ EXPECT_EQ(metadata["type_schema"].cast<String>(),
+
R"({"type":"ffi.Function","args":[{"type":"test.Int"},{"type":"test.Int"}]})");
+
+ const TVMFFIMethodInfo* optional_info =
+ reflection::GetMethodInfo("test.ObjectPtrHolder", "optional_identity");
+ Map<String, Any> optional_metadata =
+ json::Parse(String(optional_info->metadata)).cast<Map<String, Any>>();
EXPECT_EQ(
- metadata["type_schema"].cast<String>(),
+ optional_metadata["type_schema"].cast<String>(),
R"({"type":"ffi.Function","args":[{"type":"Optional","args":[{"type":"test.Int"}]},{"type":"Optional","args":[{"type":"test.Int"}]}]})");
}
diff --git a/tests/cpp/testing_object.h b/tests/cpp/testing_object.h
index c2bc8e0a..b5a6a394 100644
--- a/tests/cpp/testing_object.h
+++ b/tests/cpp/testing_object.h
@@ -236,21 +236,24 @@ class TPair : public ObjectRef {
class TObjectPtrHolderObj : public Object {
public:
- ObjectPtr<TIntObj> value;
- ObjectPtr<TNumberObj> alias;
+ Arc<TIntObj> value;
+ ObjectPtr<TNumberObj> optional_alias;
- TObjectPtrHolderObj(ObjectPtr<TIntObj> value, ObjectPtr<TNumberObj> alias)
- : value(std::move(value)), alias(std::move(alias)) {}
- explicit TObjectPtrHolderObj(UnsafeInit) {}
+ TObjectPtrHolderObj(Arc<TIntObj> value, ObjectPtr<TNumberObj> optional_alias)
+ : value(std::move(value)), optional_alias(std::move(optional_alias)) {}
+ explicit TObjectPtrHolderObj(UnsafeInit) : value(UnsafeInit{}) {}
- static ObjectPtr<TIntObj> Identity(ObjectPtr<TIntObj> value) { return value;
}
+ static Arc<TIntObj> Identity(Arc<TIntObj> value) { return value; }
+ static ObjectPtr<TIntObj> OptionalIdentity(ObjectPtr<TIntObj> value) {
return value; }
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TObjectPtrHolderObj>()
.def_rw("value", &TObjectPtrHolderObj::value)
- .def_rw("alias", &TObjectPtrHolderObj::alias)
- .def_static("identity", &TObjectPtrHolderObj::Identity);
+ .def_rw("optional_alias", &TObjectPtrHolderObj::optional_alias,
+ refl::default_value(nullptr))
+ .def_static("identity", &TObjectPtrHolderObj::Identity)
+ .def_static("optional_identity",
&TObjectPtrHolderObj::OptionalIdentity);
}
static constexpr bool _type_mutable = true;
@@ -259,8 +262,8 @@ class TObjectPtrHolderObj : public Object {
class TObjectPtrHolder : public ObjectRef {
public:
- TObjectPtrHolder(ObjectPtr<TIntObj> value, ObjectPtr<TNumberObj> alias =
nullptr) {
- data_ = make_object<TObjectPtrHolderObj>(std::move(value),
std::move(alias));
+ TObjectPtrHolder(Arc<TIntObj> value, ObjectPtr<TNumberObj> optional_alias =
nullptr) {
+ data_ = make_object<TObjectPtrHolderObj>(std::move(value),
std::move(optional_alias));
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TObjectPtrHolder, ObjectRef,
TObjectPtrHolderObj);
diff --git a/tests/python/test_dataclass_gen_abi_cpp.py
b/tests/python/test_dataclass_gen_abi_cpp.py
index edf8c70a..98345c51 100644
--- a/tests/python/test_dataclass_gen_abi_cpp.py
+++ b/tests/python/test_dataclass_gen_abi_cpp.py
@@ -27,7 +27,13 @@ from typing import Any, Callable, Optional, Union, cast
import pytest
import tvm_ffi
from tvm_ffi import Array, Device, Map, Object
-from tvm_ffi.core import DataType, TypeField, TypeInfo,
_lookup_or_register_type_info_from_type_key
+from tvm_ffi.core import (
+ DataType,
+ TypeField,
+ TypeInfo,
+ TypeSchema,
+ _lookup_or_register_type_info_from_type_key,
+)
from tvm_ffi.dataclasses import gen_abi_cpp, py_class
from tvm_ffi.dataclasses.gen_abi_cpp import _Generator
@@ -157,6 +163,7 @@ def native_layout_probes(tmp_path_factory:
pytest.TempPathFactory) -> tvm_ffi.Mo
source = r"""
#include <tvm/ffi/container/dict.h>
#include <tvm/ffi/container/list.h>
+ #include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/registry.h>
namespace tvm {
@@ -175,6 +182,12 @@ def native_layout_probes(tmp_path_factory:
pytest.TempPathFactory) -> tvm_ffi.Mo
List<ABIRawTensorObj*> list_items;
Map<String, ABIRawTensorObj*> mapping;
Dict<String, ABIRawTensorObj*> dictionary;
+ Arc<ABIRawTensorObj> item;
+ ObjectPtr<ABIRawTensorObj> nullable_item;
+ Optional<Arc<ABIRawTensorObj>> optional_item;
+
+ explicit ABIObjectContainersObj(UnsafeInit) : item(UnsafeInit{}) {}
+
TVM_FFI_DECLARE_OBJECT_INFO_FINAL(
"testing.gen_abi_cpp.native.ObjectContainers",
ABIObjectContainersObj,
@@ -194,7 +207,10 @@ def native_layout_probes(tmp_path_factory:
pytest.TempPathFactory) -> tvm_ffi.Mo
.def_ro("array_items", &ABIObjectContainersObj::array_items)
.def_ro("list_items", &ABIObjectContainersObj::list_items)
.def_ro("mapping", &ABIObjectContainersObj::mapping)
- .def_ro("dictionary", &ABIObjectContainersObj::dictionary);
+ .def_ro("dictionary", &ABIObjectContainersObj::dictionary)
+ .def_ro("item", &ABIObjectContainersObj::item)
+ .def_ro("nullable_item", &ABIObjectContainersObj::nullable_item)
+ .def_ro("optional_item", &ABIObjectContainersObj::optional_item);
}
} // namespace testing
@@ -370,11 +386,11 @@ struct alignas(8) MixedObj : public ::tvm::ffi::Object {
::tvm::ffi::String title; // offset=88, size=16, align=8
::tvm::ffi::Bytes payload; // offset=104, size=16, align=8
::tvm::ffi::Function callback; // offset=120, size=8, align=8
- ::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::DependencyObj> dependency; //
offset=128, size=8, align=8
-
::tvm::ffi::Array<::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::DependencyObj>>
array_items; // offset=136, size=8, align=8
-
::tvm::ffi::List<::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::DependencyObj>>
list_items; // offset=144, size=8, align=8
- ::tvm::ffi::Map<::tvm::ffi::String,
::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::DependencyObj>> mapping; //
offset=152, size=8, align=8
- ::tvm::ffi::Dict<::tvm::ffi::String,
::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::DependencyObj>> dictionary; //
offset=160, size=8, align=8
+ ::tvm::ffi::Arc<::testing::gen_abi_cpp::DependencyObj> dependency; //
offset=128, size=8, align=8
+ ::tvm::ffi::Array<::tvm::ffi::Arc<::testing::gen_abi_cpp::DependencyObj>>
array_items; // offset=136, size=8, align=8
+ ::tvm::ffi::List<::tvm::ffi::Arc<::testing::gen_abi_cpp::DependencyObj>>
list_items; // offset=144, size=8, align=8
+ ::tvm::ffi::Map<::tvm::ffi::String,
::tvm::ffi::Arc<::testing::gen_abi_cpp::DependencyObj>> mapping; //
offset=152, size=8, align=8
+ ::tvm::ffi::Dict<::tvm::ffi::String,
::tvm::ffi::Arc<::testing::gen_abi_cpp::DependencyObj>> dictionary; //
offset=160, size=8, align=8
::tvm::ffi::Any optional; // offset=168, size=16, align=8
::tvm::ffi::Any choice; // offset=184, size=16, align=8
};
@@ -436,7 +452,7 @@ static_assert(offsetof(MixedObj, choice) == 184);
struct alignas(8) MutualLeftObj : public ::tvm::ffi::Object {
TVM_FFI_DECLARE_OBJECT_INFO_LOOKUP("testing.gen_abi_cpp.MutualLeft", 1);
-
::tvm::ffi::List<::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::MutualRightObj>>
rights; // offset=24, size=8, align=8
+ ::tvm::ffi::List<::tvm::ffi::Arc<::testing::gen_abi_cpp::MutualRightObj>>
rights; // offset=24, size=8, align=8
};
static_assert(sizeof(MutualLeftObj) == 32);
@@ -448,7 +464,7 @@ static_assert(offsetof(MutualLeftObj, rights) == 24);
struct alignas(8) MutualRightObj : public ::tvm::ffi::Object {
TVM_FFI_DECLARE_OBJECT_INFO_LOOKUP("testing.gen_abi_cpp.MutualRight", 1);
-
::tvm::ffi::List<::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::MutualLeftObj>>
lefts; // offset=24, size=8, align=8
+ ::tvm::ffi::List<::tvm::ffi::Arc<::testing::gen_abi_cpp::MutualLeftObj>>
lefts; // offset=24, size=8, align=8
};
static_assert(sizeof(MutualRightObj) == 32);
@@ -462,8 +478,8 @@ struct alignas(8) NestedStructuralObj : public
::tvm::ffi::Object {
::tvm::ffi::List<::tvm::ffi::Any> optionals; // offset=24, size=8, align=8
::tvm::ffi::Dict<::tvm::ffi::String, ::tvm::ffi::Any> unions; // offset=32,
size=8, align=8
- ::tvm::ffi::List<::tvm::ffi::Any> optional_objects; // offset=40, size=8,
align=8
- ::tvm::ffi::Dict<::tvm::ffi::String, ::tvm::ffi::Any> union_objects; //
offset=48, size=8, align=8
+
::tvm::ffi::List<::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::DependencyObj>>
optional_objects; // offset=40, size=8, align=8
+ ::tvm::ffi::Dict<::tvm::ffi::String, ::tvm::ffi::Arc<::tvm::ffi::Object>>
union_objects; // offset=48, size=8, align=8
};
static_assert(sizeof(NestedStructuralObj) == 56);
@@ -484,7 +500,7 @@ static_assert(offsetof(NestedStructuralObj, union_objects)
== 48);
struct alignas(8) RecursiveObj : public ::tvm::ffi::Object {
TVM_FFI_DECLARE_OBJECT_INFO_LOOKUP("testing.gen_abi_cpp.Recursive", 1);
-
::tvm::ffi::List<::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::RecursiveObj>>
children; // offset=24, size=8, align=8
+ ::tvm::ffi::List<::tvm::ffi::Arc<::testing::gen_abi_cpp::RecursiveObj>>
children; // offset=24, size=8, align=8
};
static_assert(sizeof(RecursiveObj) == 32);
@@ -572,7 +588,7 @@ struct alignas(8) ChildObj : public
::testing::gen_abi_cpp::empty::EmptyObj {
TVM_FFI_DECLARE_OBJECT_INFO_LOOKUP("testing.gen_abi_cpp.other.Child", 3);
bool child_flag; // offset=40, size=1, align=1
- ::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::DependencyObj> dependency; //
offset=48, size=8, align=8
+ ::tvm::ffi::Arc<::testing::gen_abi_cpp::DependencyObj> dependency; //
offset=48, size=8, align=8
};
static_assert(sizeof(ChildObj) == 56);
@@ -706,13 +722,16 @@ namespace native {
struct alignas(8) ObjectContainersObj : public ::tvm::ffi::Object {
TVM_FFI_DECLARE_OBJECT_INFO_LOOKUP("testing.gen_abi_cpp.native.ObjectContainers",
1);
-
::tvm::ffi::Array<::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::native::RawTensorObj>>
array_items; // offset=24, size=8, align=8
-
::tvm::ffi::List<::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::native::RawTensorObj>>
list_items; // offset=32, size=8, align=8
- ::tvm::ffi::Map<::tvm::ffi::String,
::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::native::RawTensorObj>> mapping;
// offset=40, size=8, align=8
- ::tvm::ffi::Dict<::tvm::ffi::String,
::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::native::RawTensorObj>>
dictionary; // offset=48, size=8, align=8
+
::tvm::ffi::Array<::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj>>
array_items; // offset=24, size=8, align=8
+
::tvm::ffi::List<::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj>>
list_items; // offset=32, size=8, align=8
+ ::tvm::ffi::Map<::tvm::ffi::String,
::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj>> mapping; //
offset=40, size=8, align=8
+ ::tvm::ffi::Dict<::tvm::ffi::String,
::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj>> dictionary; //
offset=48, size=8, align=8
+ ::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj> item; //
offset=56, size=8, align=8
+ ::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::native::RawTensorObj>
nullable_item; // offset=64, size=8, align=8
+
::tvm::ffi::Optional<::tvm::ffi::Arc<::testing::gen_abi_cpp::native::RawTensorObj>>
optional_item; // offset=72, size=16, align=8
};
-static_assert(sizeof(ObjectContainersObj) == 56);
+static_assert(sizeof(ObjectContainersObj) == 88);
static_assert(alignof(ObjectContainersObj) == 8);
static_assert(sizeof(decltype(ObjectContainersObj::array_items)) == 8);
static_assert(alignof(decltype(ObjectContainersObj::array_items)) == 8);
@@ -726,6 +745,15 @@ static_assert(offsetof(ObjectContainersObj, mapping) ==
40);
static_assert(sizeof(decltype(ObjectContainersObj::dictionary)) == 8);
static_assert(alignof(decltype(ObjectContainersObj::dictionary)) == 8);
static_assert(offsetof(ObjectContainersObj, dictionary) == 48);
+static_assert(sizeof(decltype(ObjectContainersObj::item)) == 8);
+static_assert(alignof(decltype(ObjectContainersObj::item)) == 8);
+static_assert(offsetof(ObjectContainersObj, item) == 56);
+static_assert(sizeof(decltype(ObjectContainersObj::nullable_item)) == 8);
+static_assert(alignof(decltype(ObjectContainersObj::nullable_item)) == 8);
+static_assert(offsetof(ObjectContainersObj, nullable_item) == 64);
+static_assert(sizeof(decltype(ObjectContainersObj::optional_item)) == 16);
+static_assert(alignof(decltype(ObjectContainersObj::optional_item)) == 8);
+static_assert(offsetof(ObjectContainersObj, optional_item) == 72);
} // namespace native
} // namespace gen_abi_cpp
@@ -770,8 +798,8 @@ namespace gen_abi_cpp {
struct alignas(8) ExtraObjectsObj : public ::tvm::ffi::Object {
TVM_FFI_DECLARE_OBJECT_INFO_LOOKUP("testing.gen_abi_cpp.ExtraObjects", 1);
- ::tvm::ffi::ObjectPtr<::tvm::ffi::ModuleObj> module; // offset=24, size=8,
align=8
- ::tvm::ffi::ObjectPtr<::tvm::ffi::VisitInterruptObj> interrupt; //
offset=32, size=8, align=8
+ ::tvm::ffi::Arc<::tvm::ffi::ModuleObj> module; // offset=24, size=8, align=8
+ ::tvm::ffi::Arc<::tvm::ffi::VisitInterruptObj> interrupt; // offset=32,
size=8, align=8
};
static_assert(sizeof(ExtraObjectsObj) == 40);
@@ -871,6 +899,68 @@ def
test_schema_less_custom_object_uses_object_ptr_carrier() -> None:
assert (carrier.size, carrier.alignment) == (8, 8)
+def test_native_object_carriers_preserve_schema_nullability() -> None:
+ object_schema = {"type": "testing.gen_abi_cpp.Dependency"}
+ optional_schema = {"type": "Optional", "args": [object_schema]}
+ owner = cast(
+ TypeInfo,
+ SimpleNamespace(type_key="testing.gen_abi_cpp.NativeObjectCarriers"),
+ )
+
+ def lower(name: str, raw_schema: dict[str, object], size: int) -> str:
+ field = cast(
+ TypeField,
+ SimpleNamespace(
+ name=name,
+ size=size,
+ alignment=8,
+ offset=24,
+ field_static_type_index=128,
+ ty=TypeSchema.from_json_obj(raw_schema),
+ metadata={"type_schema": raw_schema},
+ ),
+ )
+ return _Generator([])._lower_field(field, owner).cpp_type
+
+ object_type = "::testing::gen_abi_cpp::DependencyObj"
+ assert lower("required", object_schema, 8) ==
f"::tvm::ffi::Arc<{object_type}>"
+ assert lower("nullable", optional_schema, 8) ==
f"::tvm::ffi::ObjectPtr<{object_type}>"
+ assert (
+ lower("optional", optional_schema, 16)
+ == f"::tvm::ffi::Optional<::tvm::ffi::Arc<{object_type}>>"
+ )
+
+
+def test_non_null_object_union_uses_arc_carrier() -> None:
+ raw_schema = {
+ "type": "Variant",
+ "args": [
+ {"type": "testing.gen_abi_cpp.Dependency"},
+ {"type": "testing.gen_abi_cpp.other.Sibling"},
+ ],
+ }
+ field = cast(
+ TypeField,
+ SimpleNamespace(
+ name="choice",
+ size=8,
+ alignment=8,
+ offset=24,
+ field_static_type_index=64,
+ ty=TypeSchema.from_json_obj(raw_schema),
+ metadata={"type_schema": raw_schema},
+ ),
+ )
+ owner = cast(
+ TypeInfo,
+ SimpleNamespace(type_key="testing.gen_abi_cpp.NativeObjectUnion"),
+ )
+
+ carrier = _Generator([])._lower_field(field, owner)
+
+ assert carrier.cpp_type == "::tvm::ffi::Arc<::tvm::ffi::Object>"
+
+
def test_extra_object_carriers_remain_typed(tmp_path: Path) -> None:
header = gen_abi_cpp("testing.gen_abi_cpp.ExtraObjects")
assert header == _EXPECTED_EXTRA_PROGRAM
@@ -953,8 +1043,8 @@ def
test_generated_header_compiles_and_reads_live_objects(tmp_path: Path) -> Non
::testing::gen_abi_cpp::empty::EmptyObj,
::testing::gen_abi_cpp::other::ChildObj>);
static_assert(std::is_constructible_v<
- ::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::BaseObj>,
- ::tvm::ffi::ObjectPtr<
+ ::tvm::ffi::Arc<::testing::gen_abi_cpp::BaseObj>,
+ ::tvm::ffi::Arc<
::testing::gen_abi_cpp::other::ChildObj>>);
int64_t gen_abi_cpp_read_sequence(
@@ -969,9 +1059,9 @@ def
test_generated_header_compiles_and_reads_live_objects(tmp_path: Path) -> Non
}
bool gen_abi_cpp_upcast_identity(
- ::tvm::ffi::ObjectPtr<
+ ::tvm::ffi::Arc<
::testing::gen_abi_cpp::other::ChildObj> child) {
- ::tvm::ffi::ObjectPtr<::testing::gen_abi_cpp::BaseObj> base = child;
+ ::tvm::ffi::Arc<::testing::gen_abi_cpp::BaseObj> base = child;
return reinterpret_cast<const void*>(child.get()) ==
reinterpret_cast<const void*>(base.get());
}
diff --git a/tests/python/test_dataclass_py_class.py
b/tests/python/test_dataclass_py_class.py
index 3af110ca..c001bbcb 100644
--- a/tests/python/test_dataclass_py_class.py
+++ b/tests/python/test_dataclass_py_class.py
@@ -3698,9 +3698,21 @@ class TestNativeParentInheritance:
gc.collect()
assert use_count(target) == 2
- holder.value = None
- assert holder.value is None
- assert use_count(target) == 1
+ with pytest.raises(TypeError, match=r"testing\.TestObjectBase"):
+ holder.value = None # ty: ignore[invalid-assignment]
+ assert holder.value is target
+ assert use_count(target) == 2
+
+ assert holder.optional_value is None
+ holder.optional_value = target
+ assert holder.optional_value is target
+ assert use_count(target) == 3
+ holder.optional_value = None
+ assert holder.optional_value is None
+ assert use_count(target) == 2
+
+ with pytest.raises(TypeError, match=r"testing\.TestObjectBase"):
+ _TestObjectPtrHolder(None) # ty: ignore[invalid-argument-type]
holder.value = replacement
assert use_count(replacement) == 2
@@ -3708,11 +3720,14 @@ class TestNativeParentInheritance:
assert use_count(target) == 2
assert use_count(replacement) == 1
- unrelated = _TestObjectPtrHolder(None)
+ unrelated = _TestObjectPtrHolder(replacement)
with pytest.raises(TypeError):
holder.value = unrelated # ty: ignore[invalid-assignment]
assert holder.value is not None
assert holder.value.same_as(target)
+ del unrelated
+ gc.collect()
+ assert use_count(replacement) == 1
del holder
gc.collect()
@@ -3731,7 +3746,7 @@ class TestNativeParentInheritance:
v_f64=2.0,
v_str="target",
)
- child = Child(value=target, extra=3)
+ child = Child(value=target, optional_value=None, extra=3)
assert use_count(target) == 2
child_copy = copy.copy(child)
diff --git a/tests/python/test_metadata.py b/tests/python/test_metadata.py
index 5fe09d16..ad113ea4 100644
--- a/tests/python/test_metadata.py
+++ b/tests/python/test_metadata.py
@@ -130,10 +130,15 @@ def test_schema_field(field_name: str, expected: str) ->
None:
def test_schema_object_ptr_field() -> None:
type_info: TypeInfo = getattr(TestObjectPtrHolder, "__tvm_ffi_type_info__")
- assert len(type_info.fields) == 1
+ assert len(type_info.fields) == 2
assert type_info.fields[0].name == "value"
assert (
str(TypeSchema.from_json_str(type_info.fields[0].metadata["type_schema"]))
+ == "testing.TestObjectBase"
+ )
+ assert type_info.fields[1].name == "optional_value"
+ assert (
+
str(TypeSchema.from_json_str(type_info.fields[1].metadata["type_schema"]))
== "testing.TestObjectBase | None"
)