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 tvm::ffi::Object
and 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 defining, using, and extending TVM-FFI objects across languages.
Glossary#
tvm::ffi::ObjectA 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.
tvm::ffi::ObjectRefAn intrusive pointer that manages an
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-allocatedObject.- 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
TVMFFITypeIndex(see Atomic Types and Heap-Allocated Objects for the complete list), 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 tvm::ffi::Object or its subclasses,
and then add one of the following macros that declares its metadata:
TVM_FFI_DECLARE_OBJECT_INFO(TypeKey, TypeName, ParentType)Declare an object type that can be subclassed. Type index is assigned dynamically.
TVM_FFI_DECLARE_OBJECT_INFO_FINAL(TypeKey, TypeName, ParentType)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 Object.
#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);
};
Named managed reference. ObjectRef is the base class for named,
managed references to TVM-FFI objects. Each reference holds an ObjectPtr<Object>, which
manages the underlying object’s lifetime through reference counting. Use operator-> to
access data members and member functions of the underlying object.
A reference class can define its own constructors, member functions, and operators, and
inherit from other reference classes to share and specialize its interface. It can also
impose additional constraints on the underlying object. For example, Array<T> uses
ArrayObj as its underlying object type while constraining the array’s element type to T.
Optionally, define a named managed reference class by inheriting from
ObjectRef and using one of the following macros. A constructor that
creates the underlying object can wrap tvm::ffi::make_object().
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TypeName, ParentType, ObjectName)Define a nullable reference class.
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TypeName, ParentType, ObjectName)Define a non-nullable reference class.
For example, a non-nullable reference class MyObject can be defined as follows:
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 tvm::ffi::reflection::ObjectDef
to register an object’s constructor, fields, and methods.
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
tvm_ffi.register_object() to bind a Python class to a registered C++ type:
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 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 Stub Generation Tool for details.
Type Checking and Casting#
Type checking. Use Object::IsInstance<T>()
for runtime type checking:
bool CheckType(const ffi::ObjectRef& obj) {
if (obj->IsInstance<MyObjectObj>()) {
// obj is a MyObjectObj or subclass
return true;
}
return false;
}
Type casting. Use ObjectRef::as<T>() for safe downcasting.
For strict conversion that throws on mismatch, use cast()
(see Exception Handling for error handling details):
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 ObjectRef::type_index()
and type key is available via 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 tvm::ffi::ToJSONGraph() to serialize an object to a JSON value,
and tvm::ffi::FromJSONGraph() to deserialize a JSON value to an object.
#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 tvm_ffi.serialization.to_json_graph_str()
and tvm_ffi.serialization.from_json_graph_str() to serialize and deserialize an object to a JSON string.
import pickle
obj = MyObject(42, "test")
data = pickle.dumps(obj)
restored = pickle.loads(data)
Convert between raw and managed references. Use tvm::ffi::GetRef() to convert a raw object pointer to a managed reference,
and tvm::ffi::ObjectRef::get() to convert a managed reference to a raw object pointer.
ABI and Layout#
Stable C Layout. All subclasses of tvm::ffi::Object share a common 24-byte header (TVMFFIObject)
containing reference counts, type index, and a deleter callback.
See Object for the C struct definition. tvm::ffi::ObjectRef and tvm::ffi::ObjectPtr are smart pointers
equivalent to a single void* pointer.
Reference Counting#
See also
Ownership Management for C code examples.
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. The TVMFFIObject::combined_ref_count
field packs both strong (lower 32 bits) and weak (upper 32 bits) reference counts in a single 64-bit integer.
C APIs are provided to manipulate the reference count:
TVMFFIObjectIncRef()to increase the strong reference countTVMFFIObjectDecRef()to decrease the strong reference count
Deleter. When an object is managed by 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 TVMFFIObjectDeleterFlagBitMask indicate which action to perform.
Conversion with Any. At the stable C ABI boundary, TVM-FFI passes values using Any
(owning) or AnyView (non-owning). Object handles are stored in the
TVMFFIAny::v_obj field with a type index >= kTVMFFIStaticObjectBegin.
See Ownership Management for C code examples demonstrating:
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.
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 TVM_FFI_DECLARE_OBJECT_INFO
and TVM_FFI_DECLARE_OBJECT_INFO_FINAL macros use TVMFFITypeGetOrAllocIndex()
internally to allocate a type index.
See 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 ObjectDef<T>
to register reflection metadata for object type T:
Method |
Description |
|---|---|
|
Register a constructor with the given argument types |
|
Register a read-only field |
|
Register a read-write field |
|
Register a member method |
|
Register a static method |
Example. Code below shows a minimal example of registering a class with reflection metadata.
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:
// 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::default_(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 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.
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#
Any and AnyView: How objects are stored in
AnycontainersFunction and Module: Function objects and the global registry
Tensor and DLPack: Tensor objects and DLPack interoperability
Exception Handling: Error objects and cross-language exception propagation
ABI Overview: Low-level C ABI details for the object system
Python Packaging: Packaging C++ objects for Python wheels