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);

Reply via email to