Exception Handling#
TVM-FFI gracefully handles exceptions across language boundaries without requiring manual error code management. This document covers throwing, catching, and propagating exceptions in TVM-FFI functions.
Important
Stack traces from all languages are properly preserved and concatenated in the TVM-FFI Stable C ABI.
Cross-language exceptions are first-class citizens in TVM-FFI. TVM-FFI provides a stable C ABI for propagating exceptions across language boundaries, and wraps this ABI to provide language-native exception handling without exposing the underlying C machinery.
Error in C. tvm::ffi::Error is a TVM-FFI object with a TVMFFIErrorCell payload containing:
kind: Error type name (e.g.,"ValueError","RuntimeError")message: Human-readable error messagebacktrace: Stack trace from the point of error
Propagating Errors in C. For call chains, simply propagate return codes—TLS carries the error details:
int my_function(...) {
int rc = some_ffi_call(...);
if (rc != 0) return rc; // Propagate error
// Continue on success
return 0;
}
The following sections describe how to throw and catch exceptions across ABI and language boundaries.
Throwing Exceptions#
Python#
Raise native Exception instances or derived classes.
TVM-FFI catches these at the ABI boundary and converts them to tvm::ffi::Error objects.
When C++ code calls into Python and a Python exception occurs, it propagates back to C++ as a
tvm::ffi::Error, which C++ code can handle appropriately.
def my_function(x: int) -> int:
if x < 0:
raise ValueError(f"x must be non-negative, got {x}")
return x + 1
TVM-FFI automatically maps common Python exception types to their corresponding error kinds:
RuntimeError, ValueError, TypeError, AttributeError, KeyError,
IndexError, AssertionError, and MemoryError. Custom exception types can be
registered using tvm_ffi.register_error().
C++#
Use tvm::ffi::Error or the TVM_FFI_THROW macro:
#include <tvm/ffi/error.h>
void ThrowError(int x) {
if (x < 0) {
TVM_FFI_THROW(ValueError) << "x must be non-negative, got " << x;
}
}
The TVM_FFI_THROW macro captures the current file name, line number, stack trace,
and error message, then constructs a tvm::ffi::Error object. At the ABI boundary,
this error is stored in TLS and the function returns -1 per the TVMFFISafeCallType
calling convention (see Calling Convention).
Additional check macros are available for common validation patterns:
TVM_FFI_CHECK(condition, ErrorKind) << "message"; // Custom error kind
TVM_FFI_ICHECK(condition) << "message"; // InternalError
TVM_FFI_ICHECK_EQ(x, y) << "message"; // Check equality
TVM_FFI_ICHECK_LT(x, y) << "message"; // Check less than
// Also: TVM_FFI_ICHECK_GT, TVM_FFI_ICHECK_LE, TVM_FFI_ICHECK_GE, TVM_FFI_ICHECK_NE
Hint
A detailed implementation of such graceful handling behavior can be found
in TVM_FFI_SAFE_CALL_BEGIN / TVM_FFI_SAFE_CALL_END macros.
ANSI C#
For LLVM code generation and other C-based environments, use TVMFFIErrorSetRaisedFromCStr()
to set the TLS error and return -1:
int Error_RaiseException(void* handle, const TVMFFIAny* args, int32_t num_args, TVMFFIAny* result) {
TVMFFIErrorSetRaisedFromCStr("ValueError", "Expected at least 2 arguments");
return -1;
}
For constructing error messages from multiple parts (useful in code generators),
use TVMFFIErrorSetRaisedFromCStrParts():
const char* parts[] = {"Expected ", "2", " arguments, got ", "1"};
TVMFFIErrorSetRaisedFromCStrParts("ValueError", parts, 4);
return -1;
Catching Exceptions in C#
In C++, Python, and many other languages, TVM-FFI exceptions are first-class citizens, meaning they can be caught and handled like native exceptions:
try {
// Calls a TVM-FFI function that throws an exception
} catch (const tvm::ffi::Error& e) {
// Handle the exception
std::cout << e.kind() << ": " << e.message() << "\n" << e.backtrace() << "\n";
}
Important
This section covers the pure C low-level details of exception handling. For C++ and other languages, the example above is sufficient.
Checking Return Codes#
When a TVM-FFI function returns a non-zero code, an error occurred.
The error object is stored in thread-local storage (TLS) and can be retrieved
with TVMFFIErrorMoveFromRaised():
void Error_HandleReturnCode(int rc) {
TVMFFIObject* err = NULL;
if (rc == -1) {
// Move the raised error from TLS (clears TLS slot)
TVMFFIErrorMoveFromRaised((void**)(&err)); // now `err` owns the error object
if (err != NULL) {
PrintError(err); // print the error
// IMPORTANT: Release the error object, or gets memory leaks
TVMFFIObjectDecRef(err);
}
}
}
Important
The caller must release the error object via TVMFFIObjectDecRef() to avoid memory leaks.
Accessing Error Details#
The error payload is a TVMFFIErrorCell structure containing the error kind, message,
and backtrace. Access it by skipping the TVMFFIObject header:
void PrintError(TVMFFIObject* err) {
TVMFFIErrorCell* cell = (TVMFFIErrorCell*)((char*)err + sizeof(TVMFFIObject));
fprintf(stderr, "%.*s: %.*s\n", //
(int)cell->kind.size, cell->kind.data, // e.g. "ValueError"
(int)cell->message.size, cell->message.data); // e.g. "Expected at least 2 arguments"
if (cell->backtrace.size) {
fprintf(stderr, "Backtrace:\n%.*s\n", (int)cell->backtrace.size, cell->backtrace.data);
}
}
Return Code Reference#
Error code 0: Success
Error code -1: Error occurred, retrieve via
TVMFFIErrorMoveFromRaised()
Further Reading#
Function and Module: Functions and modules that use this exception handling mechanism
Object and Class: The object system that backs
ErrorAny and AnyView: How errors are stored and transported in
AnycontainersABI Overview: Low-level C ABI details for exceptions
tvm/ffi/error.h: C++ error handling API
tvm/ffi/c_api.h: C ABI error functions