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

C Symbols

Kernel libraries, compiler codegen

Export __tvm_ffi_<name> in a shared library; load via tvm_ffi.load_module()

Global Functions

Application-level APIs, cross-language callbacks

Register by string name; retrieve from any language

Classes

Structured data with fields and methods

Define a C++ Object subclass; use from Python as a dataclass

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

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 key is a module kind string, or the special value _lib for the host dynamic library itself. For entries other than _lib, val contains the serialized bytes of the custom sub-module.

  • Both str and bytes values 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:

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_FINAL declares the type with a unique string key (my_ffi_extension.IntPair), the class name, and parent class.

  • static constexpr bool _type_mutable = true allows field modification from Python. Omit this (or set to false) 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