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 550e92f [C-API] TVMFFIErrorSetRaisedFromCStrParts (#107)
550e92f is described below
commit 550e92fc16d6525bba126527d54db5c970a74b04
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 13 14:14:05 2025 -0400
[C-API] TVMFFIErrorSetRaisedFromCStrParts (#107)
This PR introduces a new C API TVMFFIErrorSetRaisedFromCStrParts.
Background: when DSL compilers report errors, sometimes there are common
parts that appears multiple times, such as function signature.
For example, the following are possible error messages from a DSL.
- Argument 1 mismatch in `matmul(x: Tensor, y: Tensor)`, dtype mismatch
- Argument 2 mismatch in `matmul(x: Tensor, y: Tensor)`, shape[0]
mismatch
While we can store each message as const string and pass them to
`TVMFFIErrorSetRaisedFromCStr`. There are quite a bit of duplication
here.
This API allows us to store the error messages in parts, so parts like
"mismatch in `matmul(x: Tensor, y: Tensor)`," get reused across multiple
error messages. Because DSLs usually have minimal runtime, having a
minimal helper C API would simplify the possible overhead of compiler
construction side.
---
docs/concepts/abi_overview.md | 4 +++-
docs/guides/compiler_integration.md | 5 +++--
examples/quick_start/src/add_one_c.c | 4 ++--
include/tvm/ffi/c_api.h | 29 ++++++++++++++++++++++++++++-
pyproject.toml | 2 +-
python/tvm_ffi/__init__.py | 2 +-
src/ffi/backtrace_utils.h | 3 +++
src/ffi/error.cc | 28 ++++++++++++++++++++++++++++
tests/cpp/test_function.cc | 15 +++++++++++++++
9 files changed, 84 insertions(+), 8 deletions(-)
diff --git a/docs/concepts/abi_overview.md b/docs/concepts/abi_overview.md
index 47639b5..c8e0cd5 100644
--- a/docs/concepts/abi_overview.md
+++ b/docs/concepts/abi_overview.md
@@ -397,7 +397,9 @@ File "src/extension.cc", line 45, in void
my_ffi_extension::RaiseError(tvm::ffi:
We provide C++ object `ffi::Error` that can be throwed as exception in c++
environment. When we encounter
the C ABI boundary, we will catch the error and call `TVMFFIErrorSetRaised` to
propagate the error
to the caller safely.
-`TVMFFIErrorSetRaisedFromCStr` is a convenient method to set error directly
from C string and can be useful in compiler backend construction to implement
features such as assert.
+`TVMFFIErrorSetRaisedFromCStr` is a convenient method to set error directly
from C string and can be useful in compiler
+backend construction to implement features such as assert.
+We also provide `TVMFFIErrorSetRaisedFromCStrParts` to concat reusable parts
in the error message.
**Rationales:** The error object contains minimal but sufficient information
to reconstruct structured
error in python side. We opt-for thread-local error state as it simplifies
overall support.
diff --git a/docs/guides/compiler_integration.md
b/docs/guides/compiler_integration.md
index 1c8fa58..aff6f90 100644
--- a/docs/guides/compiler_integration.md
+++ b/docs/guides/compiler_integration.md
@@ -50,7 +50,7 @@ int ReadDLTensorPtr(const TVMFFIAny *value, DLTensor** out) {
return 0;
}
if (value->type_index != kTVMFFITensor) {
- // Use TVMFFIErrorSetRaisedFromCStr to set an error which will
+ // Use TVMFFIErrorSetRaisedFromCStr / TVMFFIErrorSetRaisedFromCStrParts to
set an error which will
// be propagated to the caller
TVMFFIErrorSetRaisedFromCStr("ValueError", "Expects a Tensor input");
return -1;
@@ -86,7 +86,8 @@ Some of the key takeaways include:
- Prefix the symbol with `__tvm_ffi_`
- Call {cpp:func}`TVMFFIEnvGetStream` to get the current environment stream
-- Use return value for error handling, set error via
{cpp:func}`TVMFFIErrorSetRaisedFromCStr`.
+- Use return value for error handling, set error via
{cpp:func}`TVMFFIErrorSetRaisedFromCStr`
+ or {cpp:func}`TVMFFIErrorSetRaisedFromCStrParts`.
You can also check out the [ABI overview](../concepts/abi_overview.md) for a
more complete guide.
diff --git a/examples/quick_start/src/add_one_c.c
b/examples/quick_start/src/add_one_c.c
index a12987e..9997027 100644
--- a/examples/quick_start/src/add_one_c.c
+++ b/examples/quick_start/src/add_one_c.c
@@ -40,8 +40,8 @@ int ReadDLTensorPtr(const TVMFFIAny* value, DLTensor** out) {
return 0;
}
if (value->type_index != kTVMFFITensor) {
- // Use TVMFFIErrorSetRaisedFromCStr to set an error which will
- // be propagated to the caller
+ // Use TVMFFIErrorSetRaisedFromCStr or TVMFFIErrorSetRaisedFromCStrParts
to set an
+ // error which will be propagated to the caller
TVMFFIErrorSetRaisedFromCStr("ValueError", "Expects a Tensor input");
return -1;
}
diff --git a/include/tvm/ffi/c_api.h b/include/tvm/ffi/c_api.h
index e14c7e4..88d91f8 100644
--- a/include/tvm/ffi/c_api.h
+++ b/include/tvm/ffi/c_api.h
@@ -424,6 +424,7 @@ typedef struct {
* \sa TVMFFIErrorMoveFromRaised
* \sa TVMFFIErrorSetRaised
* \sa TVMFFIErrorSetRaisedFromCStr
+ * \sa TVMFFIErrorSetRaisedFromCStrParts
*/
typedef int (*TVMFFISafeCallType)(void* handle, const TVMFFIAny* args, int32_t
num_args,
TVMFFIAny* result);
@@ -566,13 +567,39 @@ TVM_FFI_DLL void
TVMFFIErrorMoveFromRaised(TVMFFIObjectHandle* result);
TVM_FFI_DLL void TVMFFIErrorSetRaised(TVMFFIObjectHandle error);
/*!
- * \brief Set a raised error in TLS, which can be fetched by
TVMFFIMoveFromRaised.
+ * \brief Set a raised error in TLS, which can be fetched by
TVMFFIErrorMoveFromRaised.
* \param kind The kind of the error.
* \param message The error message.
* \note This is a convenient method for the C API side to set an error
directly from a string.
*/
TVM_FFI_DLL void TVMFFIErrorSetRaisedFromCStr(const char* kind, const char*
message);
+/*!
+ * \brief Set a raised error in TLS, which can be fetched by
TVMFFIErrorMoveFromRaised.
+ *
+ * Rationale: This function can be used by compilers to create error messages
by
+ * concatenating multiple parts of the error message, which can reduce the
+ * storage size for common parts such as function signatures.
+ *
+ * For example, the following are possible error messages from a kernel DSL
+ *
+ * - Argument 1 mismatch in `matmul(x: Tensor, y: Tensor, z: Tensor)`, dtype
mismatch
+ * - Argument 2 mismatch in `matmul(x: Tensor, y: Tensor, z: Tensor)`,
shape[0] mismatch
+ * - Argument 2 mismatch in `matmul(x: Tensor, y: Tensor, z: Tensor)`,
shape[1] mismatch
+ *
+ * Storing each part of the error message as a separate global string can
cause quite
+ * a bit of duplication, especially considering the kinds of error reports we
may have.
+ * Instead, compilers can store error messages in parts, where items like
+ * `matmul(x: Tensor, y: Tensor, z: Tensor)` can be reused across multiple
error messages.
+ * This API simplifies error reporting for such cases.
+ *
+ * \param kind The kind of the error.
+ * \param message_parts The error message parts, each part can be NULL and
will be skipped.
+ * \param num_parts The number of error message parts.
+ */
+TVM_FFI_DLL void TVMFFIErrorSetRaisedFromCStrParts(const char* kind, const
char** message_parts,
+ int32_t num_parts);
+
/*!
* \brief Create an initial error object.
* \param kind The kind of the error.
diff --git a/pyproject.toml b/pyproject.toml
index a76862d..def1d80 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,7 +17,7 @@
[project]
name = "apache-tvm-ffi"
-version = "0.1.0b16"
+version = "0.1.0b17"
description = "tvm ffi"
authors = [{ name = "TVM FFI team" }]
diff --git a/python/tvm_ffi/__init__.py b/python/tvm_ffi/__init__.py
index 6ddf32f..5d32c92 100644
--- a/python/tvm_ffi/__init__.py
+++ b/python/tvm_ffi/__init__.py
@@ -17,7 +17,7 @@
"""TVM FFI Python package."""
# version
-__version__ = "0.1.0b16"
+__version__ = "0.1.0b17"
# order matters here so we need to skip isort here
# isort: skip_file
diff --git a/src/ffi/backtrace_utils.h b/src/ffi/backtrace_utils.h
index 8dd9223..346d51c 100644
--- a/src/ffi/backtrace_utils.h
+++ b/src/ffi/backtrace_utils.h
@@ -67,6 +67,9 @@ inline bool ShouldExcludeFrame(const char* filename, const
char* symbol) {
if (strncmp(symbol, "TVMFFIErrorSetRaisedFromCStr", 28) == 0) {
return true;
}
+ if (strncmp(symbol, "TVMFFIErrorSetRaisedFromCStrParts", 33) == 0) {
+ return true;
+ }
// C++ stdlib frames
if (strncmp(symbol, "__libc_", 7) == 0) {
return true;
diff --git a/src/ffi/error.cc b/src/ffi/error.cc
index c65fd2b..6358c75 100644
--- a/src/ffi/error.cc
+++ b/src/ffi/error.cc
@@ -23,6 +23,8 @@
#include <tvm/ffi/c_api.h>
#include <tvm/ffi/error.h>
+#include <cstring>
+
namespace tvm {
namespace ffi {
@@ -38,6 +40,25 @@ class SafeCallContext {
last_error_ =
details::ObjectUnsafe::ObjectPtrFromObjectRef<ErrorObj>(std::move(error));
}
+ void SetRaisedByCstrParts(const char* kind, const char** message_parts,
int32_t num_parts,
+ const TVMFFIByteArray* backtrace) {
+ std::string message;
+ size_t total_len = 0;
+ for (int i = 0; i < num_parts; ++i) {
+ if (message_parts[i] != nullptr) {
+ total_len += std::strlen(message_parts[i]);
+ }
+ }
+ message.reserve(total_len);
+ for (int i = 0; i < num_parts; ++i) {
+ if (message_parts[i] != nullptr) {
+ message.append(message_parts[i]);
+ }
+ }
+ Error error(kind, message, backtrace);
+ last_error_ =
details::ObjectUnsafe::ObjectPtrFromObjectRef<ErrorObj>(std::move(error));
+ }
+
void MoveFromRaised(TVMFFIObjectHandle* result) {
result[0] =
details::ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(last_error_));
}
@@ -60,6 +81,13 @@ void TVMFFIErrorSetRaisedFromCStr(const char* kind, const
char* message) {
kind, message, TVMFFIBacktrace(nullptr, 0, nullptr, 0));
}
+void TVMFFIErrorSetRaisedFromCStrParts(const char* kind, const char**
message_parts,
+ int32_t num_parts) {
+ // NOTE: run backtrace here to simplify the depth of tracekback
+ tvm::ffi::SafeCallContext::ThreadLocal()->SetRaisedByCstrParts(
+ kind, message_parts, num_parts, TVMFFIBacktrace(nullptr, 0, nullptr, 0));
+}
+
void TVMFFIErrorSetRaised(TVMFFIObjectHandle error) {
tvm::ffi::SafeCallContext::ThreadLocal()->SetRaised(error);
}
diff --git a/tests/cpp/test_function.cc b/tests/cpp/test_function.cc
index 00f5098..f075f7f 100644
--- a/tests/cpp/test_function.cc
+++ b/tests/cpp/test_function.cc
@@ -238,6 +238,21 @@ TEST(Func, ObjectRefWithFallbackTraits) {
::tvm::ffi::Error);
}
+TEST(SetRaisedFromCStr, ValueError) {
+ TVMFFIErrorSetRaisedFromCStr("ValueError", "Value must be non-negative, got
-5");
+ Error error = tvm::ffi::details::MoveFromSafeCallRaised();
+ EXPECT_EQ(error.kind(), "ValueError");
+ EXPECT_EQ(error.message(), "Value must be non-negative, got -5");
+}
+
+TEST(SetRaisedFromCStrParts, TypeError) {
+ const char* message_parts[] = {"Mismatched", nullptr, " got Tensor"};
+ TVMFFIErrorSetRaisedFromCStrParts("TypeError", message_parts, 3);
+ Error error = tvm::ffi::details::MoveFromSafeCallRaised();
+ EXPECT_EQ(error.kind(), "TypeError");
+ EXPECT_EQ(error.message(), "Mismatched got Tensor");
+}
+
int testing_add1(int x) { return x + 1; }
TVM_FFI_DLL_EXPORT_TYPED_FUNC(testing_add1, testing_add1);