Export Functions and Classes#
TVM-FFI provides three mechanisms to make functions and classes available across C, C++, and Python. Each targets a different use case:
Mechanism |
When to use |
How it works |
|---|---|---|
Kernel libraries, compiler codegen |
Export |
|
Application-level APIs, cross-language callbacks |
Register by string name; retrieve from any language |
|
Structured data with fields and methods |
Define a C++ |
Include the umbrella header to access all C++ APIs used in this guide:
#include <tvm/ffi/tvm_ffi.h>
Metadata (type signatures, field names, docstrings) is captured automatically and can be turned into Python type hints via stub generation.
See also
All C++ examples in this guide are under examples/python_packaging/; ANSI C examples are under examples/stable_c_abi/.
Function and Module: Calling convention, module system, and global registry concepts.
Stable C ABI: Low-level C ABI walkthrough.
Python Packaging: Packaging extensions as Python wheels.
Stub Generation: Generating Python type stubs from C++ metadata.
C Symbols#
C symbols are the most direct export mechanism. A function is compiled into a
shared library with a __tvm_ffi_<name> symbol, then loaded dynamically at
runtime. This is the recommended approach for kernel libraries and compiler
codegen because it keeps the language boundary thin.
Tip
For exporting and calling C symbols from pure ANSI C code, see Stable C ABI.
Export and Look up in C++#
Export. Use TVM_FFI_DLL_EXPORT_TYPED_FUNC to export a C++ function
as a C symbol that follows the
TVM-FFI calling convention:
static int AddTwo(int x) { return x + 2; }
TVM_FFI_DLL_EXPORT_TYPED_FUNC(add_two, AddTwo)
This creates a symbol __tvm_ffi_add_two in the shared library. The macro
handles argument unmarshalling and error propagation automatically.
Look up. Use tvm::ffi::Module::LoadFromFile() to load a shared
library and retrieve functions by name:
namespace ffi = tvm::ffi;
ffi::Module mod = ffi::Module::LoadFromFile("path/to/library.so");
ffi::Function func = mod->GetFunction("add_two").value();
int result = func(40).cast<int>(); // -> 42
Load from Python#
Use tvm_ffi.load_module() to load the shared library and call its
functions by name:
import tvm_ffi
mod = tvm_ffi.load_module("path/to/library.so")
result = mod.add_two(40) # -> 42
See also
For DSO loading at Python package level, see Load the Library in the Python packaging guide.
Embedded Binary Data#
Shared libraries can embed a binary symbol __tvm_ffi__library_bin alongside
function symbols to support composite modules — modules that import custom
sub-modules (e.g., a PTX module loaded via cuModuleLoad). The binary layout
is:
<nbytes: u64> <import_tree> <key0: str> [val0: bytes] <key1: str> [val1: bytes] ...
nbytes: total byte count following this header.import_tree: a CSR sparse array (<indptr: vec<u64>> <child_indices: vec<u64>>) encoding the parent-child relationships among module nodes.Each
keyis a module kind string, or the special value_libfor the host dynamic library itself. For entries other than_lib,valcontains the serialized bytes of the custom sub-module.Both
strandbytesvalues are length-prefixed:<size: u64> <content>.
When tvm_ffi.load_module() opens a library containing this symbol, it
deserializes each sub-module by calling ffi.Module.load_from_bytes.<kind>,
reconstructs the import tree, and returns the composed module. The custom module
class must be available at load time — either by importing its runtime library
beforehand or by embedding the class definition in the generated library.
See Custom Modules in Function and Module
for details on custom module subclassing.
Global Functions#
Global functions are registered by string name in a shared registry, making them accessible from any language without loading a specific module. This is useful for application-level APIs and cross-language callbacks.
Register and Retrieve in C++#
Register. Use tvm::ffi::reflection::GlobalDef inside a
TVM_FFI_STATIC_INIT_BLOCK to register a function during library
initialization:
static int AddOne(int x) { return x + 1; }
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef() //
.def("my_ffi_extension.add_one", AddOne);
}
The function becomes available by its string name
(my_ffi_extension.add_one) from any language once the library is loaded.
Retrieve. Use tvm::ffi::Function::GetGlobal() (returns
Optional) or tvm::ffi::Function::GetGlobalRequired() (throws if
missing):
namespace ffi = tvm::ffi;
// Optional retrieval
ffi::Optional<ffi::Function> maybe_func =
ffi::Function::GetGlobal("my_ffi_extension.add_one");
if (maybe_func.has_value()) {
int result = maybe_func.value()(3).cast<int>();
}
// Required retrieval (throws if not found)
ffi::Function func =
ffi::Function::GetGlobalRequired("my_ffi_extension.add_one");
int result = func(3).cast<int>(); // -> 4
Register and Retrieve in Python#
Register. Use the tvm_ffi.register_global_func() decorator:
import tvm_ffi
@tvm_ffi.register_global_func("my_ffi_extension.add_one")
def add_one(x: int) -> int:
return x + 1
Retrieve. Use tvm_ffi.get_global_func() to look up a function by
name:
import tvm_ffi
add_one = tvm_ffi.get_global_func("my_ffi_extension.add_one")
result = add_one(3) # -> 4
See also
For packaged extensions, stub generation produces
type-annotated bindings so that users can call global functions directly
(e.g. my_ffi_extension.add_one(3)) with full IDE support.
Note
Global functions can also be retrieved via TVMFFIFunctionGetGlobal()
in C.
Classes#
Any class derived from tvm::ffi::Object can be registered, exported,
and instantiated from Python. The reflection helper
tvm::ffi::reflection::ObjectDef makes it easy to expose:
Fields: immutable via
ObjectDef::def_ro, mutable viaObjectDef::def_rwMethods: instance via
ObjectDef::def, static viaObjectDef::def_staticConstructors via
tvm::ffi::reflection::init
Register in C++#
Define a class that inherits from tvm::ffi::Object, then register
it with tvm::ffi::reflection::ObjectDef inside a
TVM_FFI_STATIC_INIT_BLOCK:
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; }
static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL(
/*type_key=*/"my_ffi_extension.IntPair",
/*class=*/IntPairObj,
/*parent_class=*/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, "IntPairObj::Sum() method");
}
Key elements:
TVM_FFI_DECLARE_OBJECT_INFO_FINALdeclares the type with a unique string key (my_ffi_extension.IntPair), the class name, and parent class.static constexpr bool _type_mutable = trueallows field modification from Python. Omit this (or set tofalse) for immutable objects.
Use in Python#
After importing the extension, the class is available with property access and method calls:
import my_ffi_extension
pair = my_ffi_extension.IntPair(1, 2)
print(pair.a) # -> 1
print(pair.b) # -> 2
print(pair.sum()) # -> 3
See also
For packaged extensions, stub generation produces type-annotated Python classes with full IDE support.
Further Reading#
Stable C ABI: End-to-end C ABI walkthrough with callee and caller examples
Function and Module: Calling convention, module system, and global registry concepts
Object and Class: Object system, type hierarchy, and reference counting
Python Packaging: Packaging extensions as Python wheels with stub generation
C++ Tooling: Build toolchain, CMake integration, and library distribution
C++ Guide: Full C++ API guide covering Any, Function, containers, and more