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 e8028e8 doc: Object and Class (#392)
e8028e8 is described below
commit e8028e826b78ed4c2848e8f64194dff4ba271bf8
Author: Junru Shao <[email protected]>
AuthorDate: Fri Jan 9 05:58:49 2026 -0800
doc: Object and Class (#392)
---
docs/concepts/any.rst | 1 +
docs/concepts/object_and_class.rst | 478 +++++++++++++++++++++++++++++++++++++
docs/concepts/tensor.rst | 4 +-
docs/index.rst | 1 +
4 files changed, 482 insertions(+), 2 deletions(-)
diff --git a/docs/concepts/any.rst b/docs/concepts/any.rst
index b6e38f1..a32744f 100644
--- a/docs/concepts/any.rst
+++ b/docs/concepts/any.rst
@@ -431,6 +431,7 @@ inline using **small string optimization**, avoiding heap
allocation entirely:
Further Reading
---------------
+- **Object system**: :doc:`object_and_class` covers how TVM-FFI objects work,
including reference counting and type checking
- **C examples**: :doc:`../get_started/stable_c_abi` demonstrates working with
:cpp:class:`TVMFFIAny` directly in C
- **Tensor conversions**: :doc:`tensor` covers how tensors flow through
:cpp:class:`~tvm::ffi::Any` and :cpp:class:`~tvm::ffi::AnyView`
- **Function calling**: :doc:`../guides/cpp_lang_guide` explains how functions
use :cpp:class:`~tvm::ffi::Any` for arguments and returns
diff --git a/docs/concepts/object_and_class.rst
b/docs/concepts/object_and_class.rst
new file mode 100644
index 0000000..6c25c79
--- /dev/null
+++ b/docs/concepts/object_and_class.rst
@@ -0,0 +1,478 @@
+.. 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.
+
+Object and Class
+================
+
+TVM-FFI provides a unified object system that enables cross-language
interoperability
+between C++, Python, and Rust. The object system is built around
:cpp:class:`tvm::ffi::Object`
+and :cpp:class:`tvm::ffi::ObjectRef`, which together form the foundation for:
+
+- **Type-safe runtime type identification** without relying on C++ RTTI
+- **Intrusive reference counting** for smart memory management
+- **Reflection-based class exposure** across programming languages
+- **Serialization and deserialization** via reflection metadata
+
+This tutorial covers everything you need to know about defining, using, and
extending
+TVM-FFI objects across languages.
+
+
+Glossary
+--------
+
+:cpp:class:`tvm::ffi::Object`
+ A heap-allocated, reference-counted container.
+ All TVM-FFI objects inherit from this base class and share a common 24-byte
header
+ that stores reference counts, type index, and a deleter callback.
+
+:cpp:class:`tvm::ffi::ObjectRef`
+ An intrusive pointer that manages an :cpp:class:`~tvm::ffi::Object`'s
lifetime through reference counting.
+ Its subclasses provide type-safe access to specific object types. In its
low-level implementation, it is
+ equivalent to a normal C++ pointer to a heap-allocated
:cpp:class:`~tvm::ffi::Object`.
+
+Type index and type key
+ Type index is an integer that uniquely identifies each object type.
+ Built-in types have statically assigned indices defined in
:cpp:enum:`TVMFFITypeIndex`,
+ while user-defined types receive indices at startup when first accessed.
+ Type key is a unique string identifier (e.g., ``"my_ext.MyClass"``) that
names an object type.
+ It is used for registration, serialization, and cross-language mapping.
+
+Common Usage
+------------
+
+Define a Class in C++
+~~~~~~~~~~~~~~~~~~~~~
+
+To define a custom object class in normal C++, inherit it from
:cpp:class:`tvm::ffi::Object` or its subclasses,
+and then add one of the following macros that declares its metadata:
+
+ :c:macro:`TVM_FFI_DECLARE_OBJECT_INFO(TypeKey, TypeName, ParentType)
<TVM_FFI_DECLARE_OBJECT_INFO>`
+ Declare an object type that can be subclassed. Type index is assigned
dynamically.
+
+ :c:macro:`TVM_FFI_DECLARE_OBJECT_INFO_FINAL(TypeKey, TypeName, ParentType)
<TVM_FFI_DECLARE_OBJECT_INFO_FINAL>`
+ Declare a final object type (no subclasses). Enables faster type checking.
+
+**Example**. The code below shows a minimal example of defining a TVM-FFI
object class.
+It declares a class ``MyObjectObj`` that inherits from
:cpp:class:`~tvm::ffi::Object`.
+
+.. code-block:: cpp
+
+ #include <tvm/ffi/tvm_ffi.h>
+
+ namespace ffi = tvm::ffi;
+
+ class MyObjectObj : public ffi::Object {
+ public:
+ // Normal C++ code: Declare fields, methods, constructor, destructor, etc.
+ int64_t value;
+ ffi::String name;
+
+ MyObjectObj(int64_t value, ffi::String name) : value(value),
name(std::move(name)) {}
+
+ int64_t GetValue() const { return value; }
+
+ void AddToValue(int64_t other) { value += other; }
+
+ // Declare object type info
+ TVM_FFI_DECLARE_OBJECT_INFO(
+ /*type_key=*/"my_ext.MyObject",
+ /*type_name=*/MyObjectObj,
+ /*parent_type=*/ffi::Object);
+ };
+
+**Managed reference**. Optionally, a managed reference class can be defined by
inheriting
+from :cpp:class:`~tvm::ffi::ObjectRef` and using one of the following macros
to define the methods.
+Define its constructor by wrapping the :cpp:func:`tvm::ffi::make_object`
function.
+
+ :c:macro:`TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TypeName, ParentType,
ObjectName) <TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE>`
+ Define a nullable reference class.
+
+ :c:macro:`TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TypeName, ParentType,
ObjectName) <TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE>`
+ Define a non-nullable reference class.
+
+For example, a non-nullable reference class ``MyObject`` can be defined as
follows:
+
+.. code-block:: cpp
+
+ class MyObject : public ffi::ObjectRef {
+ public:
+ MyObject(int64_t value, ffi::String name)
+ : ObjectRef(ffi::make_object<MyObjectObj>(value, std::move(name))) {}
+ TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(
+ /*type_name=*/MyObject,
+ /*parent_type=*/ffi::ObjectRef,
+ /*object_name=*/MyObjectObj);
+ };
+
+ // Create a managed object
+ MyObject obj = MyObject(42, "hello");
+
+ // Access fields via operator->
+ std::cout << obj->value << std::endl; // -> 42
+
+
+Expose a Class in Python
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Reflection**. The object's metadata is used for reflection. Use
:cpp:class:`tvm::ffi::reflection::ObjectDef`
+to register an object's constructor, fields, and methods.
+
+.. code-block:: cpp
+
+ TVM_FFI_STATIC_INIT_BLOCK() {
+ namespace refl = tvm::ffi::reflection;
+ refl::ObjectDef<MyObjectObj>()
+ // Register constructor with signature
+ .def(refl::init<int64_t, ffi::String>())
+ // Register read-write fields
+ .def_rw("value", &MyObjectObj::value, "The integer value")
+ .def_rw("name", &MyObjectObj::name, "The name string")
+ // Register methods
+ .def("get_value", &MyObjectObj::GetValue, "Returns the value");
+ }
+
+**Python binding**. After registration, the object is automatically available
in Python. Use
+:py:func:`tvm_ffi.register_object` to bind a Python class to a registered C++
type:
+
+.. code-block:: python
+
+ import tvm_ffi
+ from typing import TYPE_CHECKING
+
+ @tvm_ffi.register_object("my_ext.MyObject")
+ class MyObject(tvm_ffi.Object):
+ # tvm-ffi-stubgen(begin): object/my_ext.MyObject
+ value: int
+ name: str
+
+ if TYPE_CHECKING:
+ def __init__(self, value: int, name: str) -> None: ...
+ def get_value(self) -> int: ...
+ # tvm-ffi-stubgen(end)
+
+ # Create and use objects
+ obj = MyObject(42, "hello")
+ print(obj.value) # -> 42
+ print(obj.get_value()) # -> 42
+ obj.value = 100 # Mutable field access
+
+The decorator looks up the type key ``"my_ext.MyObject"`` in the C++ type
registry and binds the Python class to it.
+Fields and methods registered via
:cpp:class:`~tvm::ffi::reflection::ObjectDef` are automatically available on
the Python class.
+
+The tool ``tvm-ffi-stubgen`` automatically generates the Python type stubs
(the code between the markers)
+from reflection metadata. See :ref:`Stub Generation Tool <sec-stubgen>` for
details.
+
+
+.. _type-checking-and-casting:
+
+Type Checking and Casting
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Type checking**. Use :cpp:func:`Object::IsInstance\<T\>()
<tvm::ffi::Object::IsInstance>`
+for runtime type checking:
+
+.. code-block:: cpp
+
+ bool CheckType(const ffi::ObjectRef& obj) {
+ if (obj->IsInstance<MyObjectObj>()) {
+ // obj is a MyObjectObj or subclass
+ return true;
+ }
+ return false;
+ }
+
+**Type casting**. Use :cpp:func:`ObjectRef::as\<T\>()
<tvm::ffi::ObjectRef::as>` for safe downcasting:
+
+.. code-block:: cpp
+
+ ffi::ObjectRef obj = ...;
+
+ // as<ObjectType>() returns a pointer (nullptr if type doesn't match)
+ if (const MyObjectObj* ptr = obj.as<MyObjectObj>()) {
+ std::cout << ptr->value << std::endl;
+ }
+
+ // as<ObjectRefType>() returns std::optional
+ if (auto opt = obj.as<MyObject>()) {
+ std::cout << opt->get()->value << std::endl;
+ }
+
+**Type info**. Type index is available via :cpp:func:`ObjectRef::type_index()
<tvm::ffi::ObjectRef::type_index>`
+and type key is available via :cpp:func:`ObjectRef::GetTypeKey()
<tvm::ffi::ObjectRef::GetTypeKey>`. These methods
+can be used to safely identify object types without relying on C++ RTTI.
+
+.. note::
+ C++ RTTI (e.g. ``typeid``, ``dynamic_cast``) is strictly not useful in
TVM-FFI-based approaches.
+
+Miscellaneous APIs
+~~~~~~~~~~~~~~~~~~
+
+**C++ Serialization**. Use :cpp:func:`tvm::ffi::ToJSONGraph` to serialize an
object to a JSON value,
+and :cpp:func:`tvm::ffi::FromJSONGraph` to deserialize a JSON value to an
object.
+
+.. code-block:: cpp
+
+ #include <tvm/ffi/extra/serialization.h>
+
+ // Serialize to JSON
+ ffi::Any obj = ...;
+ ffi::json::Value json = ffi::ToJSONGraph(obj);
+
+ // Deserialize from JSON
+ ffi::Any restored = ffi::FromJSONGraph(json);
+
+**Python Serialization**. Pickle is overloaded in Python to support TVM-FFI
object serialization.
+Or explicitly use the :py:func:`tvm_ffi.serialization.to_json_graph_str`
+and :py:func:`tvm_ffi.serialization.from_json_graph_str` to serialize and
deserialize an object to a JSON string.
+
+.. code-block:: python
+
+ import pickle
+
+ obj = MyObject(42, "test")
+ data = pickle.dumps(obj)
+ restored = pickle.loads(data)
+
+**Convert between raw and managed references**. Use
:cpp:func:`tvm::ffi::GetRef` to convert a raw object pointer to a managed
reference,
+and :cpp:func:`tvm::ffi::ObjectRef::get` to convert a managed reference to a
raw object pointer.
+
+ABI and Layout
+--------------
+
+Stable C Layout
+~~~~~~~~~~~~~~~
+
+All subclasses of :cpp:class:`tvm::ffi::Object` share a common 24-byte header
(:cpp:class:`TVMFFIObject`):
+
+.. code-block:: cpp
+
+ typedef struct {
+ uint64_t combined_ref_count; // Bytes 0-7: strong + weak ref counts
+ int32_t type_index; // Bytes 8-11: runtime type identifier
+ uint32_t __padding; // Bytes 12-15: alignment padding
+ void (*deleter)(void*, int); // Bytes 16-23: destructor callback
+ } TVMFFIObject;
+
+
+It is designed with the following components:
+
+- Reference counting and deleter callback, which are used to manage the
lifetime of the object;
+- Type index, which is used to interact with type registration system for type
checking and casting.
+
+:cpp:class:`tvm::ffi::ObjectRef` and :cpp:class:`tvm::ffi::ObjectPtr` are
smart pointers whose
+layout is equivalent to:
+
+.. code-block:: cpp
+
+ struct { void* data; };
+
+
+Reference Counting
+~~~~~~~~~~~~~~~~~~
+
+**Deleter action**. When an object is managed by
:cpp:class:`~tvm::ffi::ObjectRef`, the ``deleter`` callback is invoked:
+
+- When strong reference count reaches zero: the object's destructor is called.
+- When weak reference count reaches zero: the memory is freed.
+
+The flags in :cpp:enum:`TVMFFIObjectDeleterFlagBitMask` indicate which action
to perform.
+
+**Intrusive reference counting**. The reference count is stored directly in
the object header, not in a separate control block.
+This design reduces memory overhead and improves cache locality. Specifically,
the :cpp:member:`TVMFFIObject::combined_ref_count`
+field stores a 64-bit integer that packs both strong and weak reference counts:
+
+.. code-block:: cpp
+
+ // Strong ref count: lower 32 bits
+ uint32_t strong_ref_count = combined_ref_count & 0xFFFFFFFF;
+ // Weak ref count: upper 32 bits
+ uint32_t weak_ref_count = (combined_ref_count >> 32) & 0xFFFFFFFF;
+
+C APIs are provided to manipulate the reference count of an object:
+
+- :cpp:func:`TVMFFIObjectIncRef` to increase the strong reference count;
+- :cpp:func:`TVMFFIObjectDecRef` to decrease the strong reference count.
+
+
+Conversion between :cpp:class:`~tvm::ffi::Any`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+At the stable C ABI boundary, TVM-FFI passes values using :cpp:class:`Any
<tvm::ffi::Any>` (owning)
+or :cpp:class:`AnyView <tvm::ffi::AnyView>` (non-owning). Object handles are
stored in the
+:cpp:member:`TVMFFIAny::v_obj` field with a type index >=
``kTVMFFIStaticObjectBegin``.
+
+**Any/AnyView to Object**. Extract an object handle from
:cpp:class:`TVMFFIAny`:
+
+.. code-block:: cpp
+
+ // Converts Any/AnyView to Object handle (non-owning)
+ int AnyToObjectPtr(const TVMFFIAny* value, TVMFFIObject** out) {
+ if (value->type_index >= kTVMFFIStaticObjectBegin) {
+ *out = (TVMFFIObject*)(value->v_obj);
+ return SUCCESS;
+ }
+ return FAILURE; // Not an object type
+ }
+
+**Object to AnyView**. Store an object handle into non-owning
:cpp:class:`AnyView <tvm::ffi::AnyView>`:
+
+.. code-block:: cpp
+
+ // Converts Object handle to AnyView (non-owning)
+ void ObjectToAnyView(TVMFFIObject* obj, int32_t type_index, TVMFFIAny* out)
{
+ out->type_index = type_index;
+ out->zero_padding = 0;
+ out->v_obj = obj;
+ }
+
+**Object to Any**. Store an object handle into owning :cpp:class:`Any
<tvm::ffi::Any>`.
+The function increments the reference count to take shared ownership.
+
+.. code-block:: cpp
+
+ // Converts Object handle to Any (owning, increments refcount)
+ void ObjectToAny(TVMFFIObject* obj, int32_t type_index, TVMFFIAny* out) {
+ ObjectToAnyView(obj, type_index, out);
+ TVMFFIObjectIncRef(obj); // Take ownership
+ }
+
+Later, release ownership by calling :cpp:func:`TVMFFIObjectDecRef` on
:cpp:member:`TVMFFIAny::v_obj`.
+
+Object Type Registry
+--------------------
+
+TVM-FFI maintains a global type registry that keeps track of all registered
object types,
+their inheritance relationships, and their reflection metadata.
+
+Inheritance and Type Casting
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. warning::
+ Only single inheritance is supported in TVM-FFI Object system.
+
+TVM-FFI implements its own runtime type system that enables type-safe
operations
+without relying on C++ RTTI. Every object carries a runtime type index in its
header.
+
+**Example**. Code below shows a minimal example of defining a base class and a
derived class.
+
+.. code-block:: cpp
+
+ class MyBaseObj : public ffi::Object {
+ public:
+ TVM_FFI_DECLARE_OBJECT_INFO("my_ext.MyBase", MyBaseObj, ffi::Object);
+ };
+
+ class MyDerivedObj : public MyBaseObj {
+ public:
+ // Final class: no subclasses allowed
+ TVM_FFI_DECLARE_OBJECT_INFO_FINAL("my_ext.MyDerived", MyDerivedObj,
MyBaseObj);
+ };
+
+
+Registration happens automatically on first access. The
:c:macro:`TVM_FFI_DECLARE_OBJECT_INFO`
+and :c:macro:`TVM_FFI_DECLARE_OBJECT_INFO_FINAL` macros use
:cpp:func:`TVMFFITypeGetOrAllocIndex`
+internally to allocate a type index.
+
+See :ref:`type-checking-and-casting` for how to use the type system.
+
+Reflect Fields and Methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The reflection system enables cross-language exposure of C++ classes, their
fields,
+and methods. Use :cpp:class:`ObjectDef\<T\> <tvm::ffi::reflection::ObjectDef>`
+to register reflection metadata for object type ``T``:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Method
+ - Description
+ * - ``.def(init<Args...>())``
+ - Register a constructor with the given argument types
+ * - ``.def_ro("name", &T::field)``
+ - Register a read-only field
+ * - ``.def_rw("name", &T::field)``
+ - Register a read-write field
+ * - ``.def("name", &T::method)``
+ - Register a member method
+ * - ``.def_static("name", &func)``
+ - Register a static method
+
+**Example**. Code below shows a minimal example of registering a class with
reflection metadata.
+
+.. code-block:: cpp
+
+ class IntPairObj : public ffi::Object {
+ public:
+ int64_t a;
+ int64_t b;
+
+ IntPairObj(int64_t a, int64_t b) : a(a), b(b) {}
+
+ int64_t Sum() const { return a + b; }
+
+ TVM_FFI_DECLARE_OBJECT_INFO_FINAL("my_ext.IntPair", IntPairObj,
ffi::Object);
+ };
+
+ TVM_FFI_STATIC_INIT_BLOCK() {
+ namespace refl = tvm::ffi::reflection;
+ refl::ObjectDef<IntPairObj>()
+ .def(refl::init<int64_t, int64_t>())
+ .def_rw("a", &IntPairObj::a, "the first field")
+ .def_rw("b", &IntPairObj::b, "the second field")
+ .def("sum", &IntPairObj::Sum, "compute a + b");
+ }
+
+
+**Metadata and Documentation**. Add documentation strings and custom metadata
to fields and methods:
+
+.. code-block:: cpp
+
+ // The following example uses MyObjectObj defined earlier to show
+ // how to add documentation and metadata.
+ refl::ObjectDef<MyObjectObj>()
+ .def_rw("value", &MyObjectObj::value,
+ "The numeric value", // docstring
+ refl::DefaultValue(0), // default value
+ refl::Metadata{{"min", 0}, {"max", 100}}) // custom metadata
+ .def("add_to_value", &MyObjectObj::AddToValue,
+ "Add a value to the object's value field");
+
+
+Python Interoperability
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Cross-language lifetime**. Each Python :py:class:`tvm_ffi.Object` instance
holds a C handle
+(``void*``) that references the underlying C++ object. The Python wrapper
increments the
+reference count when constructed and decrements when garbage collected.
+
+.. code-block:: python
+
+ obj = MyObject(42, "test") # C++ object created, C++ refcount = 1
+ obj2 = obj # Python alias created, C++ refcount unchanged
+ del obj # Python alias removed, C++ refcount unchanged
+ del obj2 # Last Python reference gone, C++ refcount ->
0, object destroyed
+
+
+Further Reading
+---------------
+
+- :doc:`any`: How objects are stored in :cpp:class:`~tvm::ffi::Any` containers
+- :doc:`tensor`: Tensor objects and DLPack interoperability
+- :doc:`../packaging/python_packaging`: Packaging C++ objects for Python
+- :doc:`abi_overview`: Low-level ABI details for the object system
diff --git a/docs/concepts/tensor.rst b/docs/concepts/tensor.rst
index e85175d..fcdd1c4 100644
--- a/docs/concepts/tensor.rst
+++ b/docs/concepts/tensor.rst
@@ -143,7 +143,7 @@ C++ Allocation
TVM-FFI is not a kernel library and is not linked to any specific device
memory allocator or runtime.
However, it provides standardized allocation entry points for kernel library
developers by interfacing
-with the surrounding framework's allocator—for example, using PyTorch's
allocator when running inside
+with the surrounding framework's allocator - for example, using PyTorch's
allocator when running inside
a PyTorch environment.
**Env Allocator.** Use :cpp:func:`Tensor::FromEnvAlloc()
<tvm::ffi::Tensor::FromEnvAlloc>` along with C API
@@ -484,7 +484,7 @@ Further Reading
---------------
- :cpp:class:`TensorObj <tvm::ffi::TensorObj>` and :cpp:class:`Tensor
<tvm::ffi::Tensor>` are part of the standard TVM-FFI object system.
- See :ref:`Object System <object-storage-format>` for details on how TVM-FFI
objects work.
+ See :doc:`object_and_class` for a comprehensive guide, or :ref:`Object
Storage Format <object-storage-format>` for low-level layout details.
- :cpp:class:`AnyView <tvm::ffi::AnyView>` and :cpp:class:`Any
<tvm::ffi::Any>` are part of the stable C ABI.
Tutorial :doc:`Stable C ABI<../get_started/stable_c_abi>` explains the ABI
design at a high level,
and :doc:`ABI Overview <abi_overview>` shares details on the design.
diff --git a/docs/index.rst b/docs/index.rst
index dc92c98..dcef01f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -60,6 +60,7 @@ Table of Contents
concepts/abi_overview.md
concepts/any.rst
+ concepts/object_and_class.rst
concepts/tensor.rst
.. toctree::