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 6f020c1  [REFACTOR][ABI] Update backtrace storage for append-friendly 
(#39)
6f020c1 is described below

commit 6f020c11c304ef11ac5d0dad904d41ebe42a3ffd
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Sep 22 10:54:28 2025 -0400

    [REFACTOR][ABI] Update backtrace storage for append-friendly (#39)
    
    This PR updates the default backtrace order convention to most recent
    call first. Such storage format makes it easy to append the backtrace
    and incrementally build up as we propagate the error up.
    
    Importantly, the previous rationale of using traceback(most recent call
    last) is to align with python style error handling. This is still
    remains an important design consideration. As a result, when we render
    the error or propagate the result, we will still call
    TracebackMostRecentCallLast, so such change is not visible to the user,
    but will enable more memory efficient incremental building of backtrace.
    
    This PR also updates the naming convention accordingly. We also updated
    TVMFFIErrorCreate to be more consistent with other parts.
    
    Also fixes minor nits in quick start to simplify dependency
    installation.
    
    This is an ABI change. Given we are still in pre-release beta, we bump
    beta version number.
---
 CMakeLists.txt                                 |  10 +--
 docs/concepts/abi_overview.md                  |  29 +++++--
 docs/guides/cpp_guide.md                       |  11 +--
 docs/guides/packaging.md                       |   2 +-
 docs/guides/python_guide.md                    |   2 +-
 examples/packaging/README.md                   |   2 +-
 examples/quick_start/README.md                 |  11 ++-
 examples/quick_start/requirements.txt          |   3 -
 examples/quick_start/run_example.sh            |   6 +-
 include/tvm/ffi/c_api.h                        |  67 +++++++++++----
 include/tvm/ffi/container/tensor.h             |   2 +-
 include/tvm/ffi/error.h                        | 111 +++++++++++++++++--------
 pyproject.toml                                 |   2 +-
 python/tvm_ffi/core.pyi                        |  10 +--
 python/tvm_ffi/cython/base.pxi                 |  16 ++--
 python/tvm_ffi/cython/error.pxi                |  60 ++++++++-----
 python/tvm_ffi/cython/function.pxi             |   2 +-
 python/tvm_ffi/cython/type_info.pxi            |   2 +-
 python/tvm_ffi/error.py                        |  26 +++---
 src/ffi/{traceback.cc => backtrace.cc}         |  68 +++++++--------
 src/ffi/{traceback.h => backtrace_utils.h}     |  52 ++++++------
 src/ffi/{traceback_win.cc => backtrace_win.cc} |  44 +++++-----
 src/ffi/error.cc                               |  28 ++++---
 src/ffi/extra/testing.cc                       |   6 +-
 tests/cpp/test_error.cc                        |   9 +-
 tests/cpp/test_example.cc                      |   2 +-
 tests/python/test_error.py                     |  36 ++++----
 tests/scripts/task_cpp_tests.sh                |   9 +-
 28 files changed, 372 insertions(+), 256 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1fcf4e8..d0a3c0b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,7 +23,7 @@ project(
 
 option(TVM_FFI_USE_LIBBACKTRACE "Enable libbacktrace" ON)
 option(TVM_FFI_USE_EXTRA_CXX_API "Enable extra CXX API in shared lib" ON)
-option(TVM_FFI_BACKTRACE_ON_SEGFAULT "Set signal handler to print traceback on 
segfault" ON)
+option(TVM_FFI_BACKTRACE_ON_SEGFAULT "Set signal handler to print backtrace on 
segfault" ON)
 
 if (TVM_FFI_USE_LIBBACKTRACE)
   include(${CMAKE_CURRENT_LIST_DIR}/cmake/Utils/AddLibbacktrace.cmake)
@@ -52,8 +52,8 @@ target_include_directories(
 ########## Target: `tvm_ffi_objs` ##########
 
 set(tvm_ffi_objs_sources
-  "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/traceback.cc"
-  "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/traceback_win.cc"
+  "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/backtrace.cc"
+  "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/backtrace_win.cc"
   "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/object.cc"
   "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/error.cc"
   "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/function.cc"
@@ -156,7 +156,7 @@ include(cmake/Utils/CxxWarning.cmake)
 include(cmake/Utils/Sanitizer.cmake)
 
 # remap the file name to the source directory so we can see the
-# exact file name in traceback relative to the project source root
+# exact file name in backtrace relative to the project source root
 tvm_ffi_add_prefix_map(tvm_ffi_objs ${CMAKE_SOURCE_DIR})
 
 ########## Adding cpp tests ##########
@@ -193,7 +193,7 @@ if (TVM_FFI_BUILD_PYTHON_MODULE)
     ${CMAKE_CURRENT_SOURCE_DIR}/python/tvm_ffi/cython/object.pxi
     ${CMAKE_CURRENT_SOURCE_DIR}/python/tvm_ffi/cython/string.pxi
   )
-  # set working directory to source so we can see the exact file name in 
traceback
+  # set working directory to source so we can see the exact file name in 
backtrace
   # relatived to the project source root
   add_custom_command(
       OUTPUT ${core_cpp}
diff --git a/docs/concepts/abi_overview.md b/docs/concepts/abi_overview.md
index ad671d3..0843efd 100644
--- a/docs/concepts/abi_overview.md
+++ b/docs/concepts/abi_overview.md
@@ -348,19 +348,34 @@ using `TVMFFIErrorMoveFromRaised` function.
 The ABI stores Error also as a specific Object,
 the overall error object is stored as follows
 ```c++
+/*!
+ * \brief Error cell used in error object following header.
+ */
 typedef struct {
   /*! \brief The kind of the error. */
   TVMFFIByteArray kind;
   /*! \brief The message of the error. */
   TVMFFIByteArray message;
-  /*! \brief The traceback of the error. */
-  TVMFFIByteArray traceback;
   /*!
-   * \brief Function handle to update the traceback of the error.
+   * \brief The backtrace of the error.
+   *
+   * The backtrace is in the order of recent call first from the top of the 
stack
+   * to the bottom of the stack. This order makes it helpful for appending
+   * the extra backtrace to the end as we go up when error is propagated.
+   *
+   * When printing out, we encourage reverse the order of lines to make it
+   * align with python style.
+   */
+  TVMFFIByteArray backtrace;
+  /*!
+   * \brief Function handle to update the backtrace of the error.
    * \param self The self object handle.
-   * \param traceback The traceback to update.
+   * \param backtrace The backtrace to update.
+   * \param update_mode The mode to update the backtrace,
+   *        can be either kTVMFFIBacktraceUpdateModeReplace, 
kTVMFFIBacktraceUpdateModeAppend.
    */
-  void (*update_traceback)(TVMFFIObjectHandle self, const TVMFFIByteArray* 
traceback);
+  void (*update_backtrace)(
+    TVMFFIObjectHandle self, const TVMFFIByteArray* backtrace, int32_t 
update_mode);
 } TVMFFIErrorCell;
 
 // error object
@@ -368,8 +383,8 @@ class ErrorObj : public ffi::Object, public TVMFFIErrorCell 
{
 };
 ```
 
-The error object stores kind, message and traceback as string. When possible,
-we store the traceback in the same format of python traceback (see an example 
as follows):
+The error object stores kind, message and backtrace as string. When possible,
+we store the backtrace in the same format of python-style (see an example as 
follows):
 ```
 File "src/extension.cc", line 45, in void 
my_ffi_extension::RaiseError(tvm::ffi::String)
 ```
diff --git a/docs/guides/cpp_guide.md b/docs/guides/cpp_guide.md
index a27fe2d..836cb84 100644
--- a/docs/guides/cpp_guide.md
+++ b/docs/guides/cpp_guide.md
@@ -321,14 +321,15 @@ void ExampleErrorHandling() {
   } catch (const ffi::Error& e) {
     EXPECT_EQ(e.kind(), "TypeError");
     EXPECT_EQ(e.message(), "test0");
-    std::cout << e.traceback() << std::endl;
+    std::cout << e.TracebackMostRecentCallLast() << std::endl;
   }
 }
 ```
-The structured error class records kind, message and traceback that can be 
mapped to
-Pythonic style error types and tracebacks. The traceback follows the Python 
style,
-tvm-ffi will try to preserve the traceback when possible. In the above example,
-you can see the traceback output as
+The structured error class records kind, message and backtrace that can be 
mapped to
+Pythonic style error types and traces. The `TracebackMostRecentCallLast()` 
call reverses
+the backtrace and print out follows the Python style,
+tvm-ffi will try to preserve the backtrace when possible. In the above example,
+you can see the output as
 ```
 ... more lines omitted
 File "cpp/test_example.cc", line 106, in ExampleErrorHandling
diff --git a/docs/guides/packaging.md b/docs/guides/packaging.md
index 197935e..3384c05 100644
--- a/docs/guides/packaging.md
+++ b/docs/guides/packaging.md
@@ -266,7 +266,7 @@ across language boundaries:
 python run_example.py raise_error
 ```
 
-When possible, tvm-ffi will try to preserve tracebacks across language 
boundaries. You will see tracebacks like:
+When possible, tvm-ffi will try to preserve backtraces across language 
boundaries. You will see outputs like:
 ```
 File "src/extension.cc", line 45, in void 
my_ffi_extension::RaiseError(tvm::ffi::String)
 ```
diff --git a/docs/guides/python_guide.md b/docs/guides/python_guide.md
index cd997af..529c030 100644
--- a/docs/guides/python_guide.md
+++ b/docs/guides/python_guide.md
@@ -190,7 +190,7 @@ translate the error to the corresponding error kind in 
Python
 import tvm_ffi
 
 # defined in C++
-# [](String kind, String msg) { throw Error(kind, msg, traceback); }
+# [](String kind, String msg) { throw Error(kind, msg, backtrace); }
 test_raise_error = tvm_ffi.get_global_func("testing.test_raise_error")
 
 test_raise_error("ValueError", "message")
diff --git a/examples/packaging/README.md b/examples/packaging/README.md
index 25bcc1c..ae17107 100644
--- a/examples/packaging/README.md
+++ b/examples/packaging/README.md
@@ -53,7 +53,7 @@ across the language boundaries.
 python run_example.py raise_error
 ```
 
-When possible, tvm_ffi will try to preserve traceback across language 
boundary. You will see traceback like
+When possible, tvm_ffi will try to preserve backtrace across language 
boundary. You will see output like
 ```
 File "src/extension.cc", line 45, in void 
my_ffi_extension::RaiseError(tvm::ffi::String)
 ```
diff --git a/examples/quick_start/README.md b/examples/quick_start/README.md
index 0e03133..db1a22d 100644
--- a/examples/quick_start/README.md
+++ b/examples/quick_start/README.md
@@ -32,7 +32,8 @@ Before running the quick start, ensure you have:
 
 ```bash
 # From the quick_start directory
-pip install -ve ../..
+# install and include test dependency(this will install torch and numpy)
+pip install -ve "../..[test]"
 ```
 
 ## Run the Quick Start
@@ -43,20 +44,18 @@ From `examples/quick_start` you can build and run 
everything with the helper scr
 ./run_example.sh
 ```
 
-The script picks an available CMake generator (preferring Ninja), configures a 
build in `build/`, compiles the C++ libraries and drivers, installs the Python 
dependencies from `requirements.txt`, and finally runs the Python and C++ 
demos. If the CUDA toolkit is detected it will also build and execute 
`run_example_cuda`.
+The script picks an available CMake generator (preferring Ninja), configures a 
build in `build/`, compiles the C++ libraries and examples,
+and finally runs the Python and C++ demos. If the CUDA toolkit is detected it 
will also build and execute `run_example_cuda`.
 
 If you prefer to drive the build manually, run the following instead:
 
 ```bash
 # configure (omit -G Ninja if Ninja is not installed)
-cmake --fresh -G Ninja -B build -S .
+cmake -G Ninja -B build -S .
 
 # compile the example targets
 cmake --build build --parallel
 
-# install python dependencies for the scripts
-python -m pip install -r requirements.txt
-
 # run the demos
 python run_example.py
 ./build/run_example
diff --git a/examples/quick_start/requirements.txt 
b/examples/quick_start/requirements.txt
deleted file mode 100644
index 597d120..0000000
--- a/examples/quick_start/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# Editable Git install with no remote (apache-tvm-ffi==0.1.0b3)
-numpy==2.2.6
-PyYAML==5.4.1
diff --git a/examples/quick_start/run_example.sh 
b/examples/quick_start/run_example.sh
index 80aa37e..e6ada48 100755
--- a/examples/quick_start/run_example.sh
+++ b/examples/quick_start/run_example.sh
@@ -24,12 +24,10 @@ else
        generator="Unix Makefiles"
 fi
 
-cmake --fresh -G "$generator" -B build -S .
+rm -rf build/CMakeCache.txt
+cmake -G "$generator" -B build -S .
 cmake --build build --parallel
 
-# install python dependencies
-python -m pip install -r requirements.txt
-
 # running python example
 python run_example.py
 
diff --git a/include/tvm/ffi/c_api.h b/include/tvm/ffi/c_api.h
index 0ab4d08..b0ce3c1 100644
--- a/include/tvm/ffi/c_api.h
+++ b/include/tvm/ffi/c_api.h
@@ -313,6 +313,22 @@ typedef struct {
   size_t size;
 } TVMFFIShapeCell;
 
+/*!
+ * \brief Mode to update the backtrace of the error.
+ */
+#ifdef __cplusplus
+enum TVMFFIBacktraceUpdateMode : int32_t {
+#else
+typedef enum {
+#endif
+  kTVMFFIBacktraceUpdateModeReplace = 0,
+  kTVMFFIBacktraceUpdateModeAppend = 1,
+#ifdef __cplusplus
+};
+#else
+} TVMFFIBacktraceUpdateMode;
+#endif
+
 /*!
  * \brief Error cell used in error object following header.
  */
@@ -322,15 +338,25 @@ typedef struct {
   /*! \brief The message of the error. */
   TVMFFIByteArray message;
   /*!
-   * \brief The traceback of the error.
+   * \brief The backtrace of the error.
+   *
+   * The backtrace is in the order of recent call first from the top of the 
stack
+   * to the bottom of the stack. This order makes it helpful for appending
+   * the extra backtrace to the end as we go up when error is propagated.
+   *
+   * When printing out, we encourage reverse the order of lines to make it
+   * align with python style.
    */
-  TVMFFIByteArray traceback;
+  TVMFFIByteArray backtrace;
   /*!
-   * \brief Function handle to update the traceback of the error.
+   * \brief Function handle to update the backtrace of the error.
    * \param self The self object handle.
-   * \param traceback The traceback to update.
+   * \param backtrace The backtrace to update.
+   * \param update_mode The mode to update the backtrace,
+   *        can be either kTVMFFIBacktraceUpdateModeReplace, 
kTVMFFIBacktraceUpdateModeAppend.
    */
-  void (*update_traceback)(TVMFFIObjectHandle self, const TVMFFIByteArray* 
traceback);
+  void (*update_backtrace)(TVMFFIObjectHandle self, const TVMFFIByteArray* 
backtrace,
+                           int32_t update_mode);
 } TVMFFIErrorCell;
 
 /*!
@@ -504,14 +530,17 @@ TVM_FFI_DLL void TVMFFIErrorSetRaisedFromCStr(const char* 
kind, const char* mess
  * \brief Create an initial error object.
  * \param kind The kind of the error.
  * \param message The error message.
- * \param traceback The traceback of the error.
- * \return The created error object handle.
+ * \param backtrace The backtrace of the error.
+ * \param out The output error object handle.
+ * \return 0 on success, nonzero on failure(likely MemoryError)
+ *
  * \note This function is different from other functions as it is used in the 
error handling loop.
- * So we do not follow normal error handling patterns via returning an error 
code.
+ *       So we do not follow normal error handling patterns. When error 
happens it will not set
+ *       the error in TLS (since TLS error setting also involves creating an 
Error object).
+ *       Instead, caller should simply report MemoryError to the logger.
  */
-TVM_FFI_DLL TVMFFIObjectHandle TVMFFIErrorCreate(const TVMFFIByteArray* kind,
-                                                 const TVMFFIByteArray* 
message,
-                                                 const TVMFFIByteArray* 
traceback);
+TVM_FFI_DLL int TVMFFIErrorCreate(const TVMFFIByteArray* kind, const 
TVMFFIByteArray* message,
+                                  const TVMFFIByteArray* backtrace, 
TVMFFIObjectHandle* out);
 
 //------------------------------------------------------------
 // Section: DLPack support APIs
@@ -954,18 +983,26 @@ TVM_FFI_DLL const TVMFFITypeAttrColumn* 
TVMFFIGetTypeAttrColumn(const TVMFFIByte
 // so exception handling do not apply
 //------------------------------------------------------------
 /*!
- * \brief Get stack traceback in a string.
+ * \brief Get stack backtrace in a string.
+ *
+ * The backtrace is in the order of recent call first from the top of the stack
+ * to the bottom of the stack. This order makes it helpful for appending
+ * the extra backtrace as we unwind the stack.
+ *
+ * When printing out, we encourage reverse the order of lines to make it
+ * align with python style.
+ *
  * \param filename The current file name.
  * \param lineno The current line number
  * \param func The current function
- * \param cross_ffi_boundary Whether the traceback is crossing the ffi boundary
+ * \param cross_ffi_boundary Whether the backtrace is crossing the ffi boundary
  *                           or we should stop at the ffi boundary when 
detected
- * \return The traceback string
+ * \return The backtrace string
  *
  * \note filename/func can be nullptr, then this info is skipped, they are 
useful
  * for cases when debug symbols are not available.
  */
-TVM_FFI_DLL const TVMFFIByteArray* TVMFFITraceback(const char* filename, int 
lineno,
+TVM_FFI_DLL const TVMFFIByteArray* TVMFFIBacktrace(const char* filename, int 
lineno,
                                                    const char* func, int 
cross_ffi_boundary);
 
 /*!
diff --git a/include/tvm/ffi/container/tensor.h 
b/include/tvm/ffi/container/tensor.h
index 59dc773..1e251b0 100644
--- a/include/tvm/ffi/container/tensor.h
+++ b/include/tvm/ffi/container/tensor.h
@@ -392,7 +392,7 @@ class Tensor : public ObjectRef {
     int ret = (*allocator)(&prototype, &tensor, &error_context, 
ErrorContext::SetError);
     if (ret != 0) {
       throw ffi::Error(error_context.kind, error_context.message,
-                       TVMFFITraceback(__FILE__, __LINE__, __func__, 0));
+                       TVMFFIBacktrace(__FILE__, __LINE__, __func__, 0));
     }
     return 
Tensor(make_object<details::TensorObjFromDLPack<DLManagedTensorVersioned>>(tensor));
   }
diff --git a/include/tvm/ffi/error.h b/include/tvm/ffi/error.h
index 261b69e..b4d9d3a 100644
--- a/include/tvm/ffi/error.h
+++ b/include/tvm/ffi/error.h
@@ -29,12 +29,14 @@
 #include <tvm/ffi/memory.h>
 #include <tvm/ffi/object.h>
 
+#include <cstring>
 #include <iostream>
 #include <memory>
 #include <sstream>
 #include <string>
 #include <string_view>
 #include <utility>
+#include <vector>
 
 /*!
  * \brief Macro defines whether we enable libbacktrace
@@ -94,28 +96,37 @@ class ErrorObj : public Object, public TVMFFIErrorCell {
 namespace details {
 class ErrorObjFromStd : public ErrorObj {
  public:
-  ErrorObjFromStd(std::string kind, std::string message, std::string traceback)
-      : kind_data_(kind), message_data_(message), traceback_data_(traceback) {
+  ErrorObjFromStd(std::string kind, std::string message, std::string backtrace)
+      : kind_data_(kind), message_data_(message), backtrace_data_(backtrace) {
     this->kind = TVMFFIByteArray{kind_data_.data(), kind_data_.length()};
     this->message = TVMFFIByteArray{message_data_.data(), 
message_data_.length()};
-    this->traceback = TVMFFIByteArray{traceback_data_.data(), 
traceback_data_.length()};
-    this->update_traceback = UpdateTraceback;
+    this->backtrace = TVMFFIByteArray{backtrace_data_.data(), 
backtrace_data_.length()};
+    this->update_backtrace = UpdateBacktrace;
   }
 
  private:
   /*!
-   * \brief Update the traceback of the error object.
-   * \param traceback The traceback to update.
+   * \brief Update the backtrace of the error object.
+   * \param backtrace The backtrace to update.
+   * \param update_mode The mode to update the backtrace,
+   *        can be either kTVMFFIBacktraceUpdateModeReplace, 
kTVMFFIBacktraceUpdateModeAppend.
    */
-  static void UpdateTraceback(TVMFFIObjectHandle self, const TVMFFIByteArray* 
traceback_str) {
+  static void UpdateBacktrace(TVMFFIObjectHandle self, const TVMFFIByteArray* 
backtrace_str,
+                              int32_t update_mode) {
     ErrorObjFromStd* obj = static_cast<ErrorObjFromStd*>(self);
-    obj->traceback_data_ = std::string(traceback_str->data, 
traceback_str->size);
-    obj->traceback = TVMFFIByteArray{obj->traceback_data_.data(), 
obj->traceback_data_.length()};
+    if (update_mode == kTVMFFIBacktraceUpdateModeReplace) {
+      obj->backtrace_data_.resize(backtrace_str->size);
+      std::memcpy(obj->backtrace_data_.data(), backtrace_str->data, 
backtrace_str->size);
+      obj->backtrace = TVMFFIByteArray{obj->backtrace_data_.data(), 
obj->backtrace_data_.length()};
+    } else {
+      obj->backtrace_data_.append(backtrace_str->data, backtrace_str->size);
+      obj->backtrace = TVMFFIByteArray{obj->backtrace_data_.data(), 
obj->backtrace_data_.length()};
+    }
   }
 
   std::string kind_data_;
   std::string message_data_;
-  std::string traceback_data_;
+  std::string backtrace_data_;
 };
 }  // namespace details
 
@@ -129,20 +140,20 @@ class Error : public ObjectRef, public std::exception {
    * \brief Constructor
    * \param kind The kind of the error.
    * \param message The message of the error.
-   * \param traceback The traceback of the error.
+   * \param backtrace The backtrace of the error.
    */
-  Error(std::string kind, std::string message, std::string traceback) {
-    data_ = make_object<details::ErrorObjFromStd>(kind, message, traceback);
+  Error(std::string kind, std::string message, std::string backtrace) {
+    data_ = make_object<details::ErrorObjFromStd>(kind, message, backtrace);
   }
 
   /*!
    * \brief Constructor
    * \param kind The kind of the error.
    * \param message The message of the error.
-   * \param traceback The traceback of the error.
+   * \param backtrace The backtrace of the error.
    */
-  Error(std::string kind, std::string message, const TVMFFIByteArray* 
traceback)
-      : Error(kind, message, std::string(traceback->data, traceback->size)) {}
+  Error(std::string kind, std::string message, const TVMFFIByteArray* 
backtrace)
+      : Error(kind, message, std::string(backtrace->data, backtrace->size)) {}
 
   /*!
    * \brief Get the kind of the error object.
@@ -163,21 +174,52 @@ class Error : public ObjectRef, public std::exception {
   }
 
   /*!
-   * \brief Get the traceback of the error object.
+   * \brief Get the backtrace of the error object.
+   * \return The backtrace of the error object.
+   * \note Consider use TracebackMostRecentCallLast for pythonic style 
traceback.
+   *
+   * \sa TracebackMostRecentCallLast
+   */
+  std::string backtrace() const {
+    ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
+    return std::string(obj->backtrace.data, obj->backtrace.size);
+  }
+
+  /*!
+   * \brief Get the traceback in the order of most recent call last.
+   *
    * \return The traceback of the error object.
    */
-  std::string traceback() const {
+  std::string TracebackMostRecentCallLast() const {
+    // add placeholder for the first line
+    std::vector<int64_t> line_breakers = {-1};
     ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
-    return std::string(obj->traceback.data, obj->traceback.size);
+    for (size_t i = 0; i < obj->backtrace.size; i++) {
+      if (obj->backtrace.data[i] == '\n') {
+        line_breakers.push_back(i);
+      }
+    }
+    std::string result;
+    result.reserve(obj->backtrace.size);
+    for (size_t i = line_breakers.size() - 1; i > 0; --i) {
+      int64_t line_start = line_breakers[i - 1] + 1;
+      int64_t line_end = line_breakers[i];
+      if (line_start == line_end) continue;
+      result.append(obj->backtrace.data + line_start, line_end - line_start);
+      result.append("\n");
+    }
+    return result;
   }
 
   /*!
-   * \brief Update the traceback of the error object.
-   * \param traceback_str The traceback to update.
+   * \brief Update the backtrace of the error object.
+   * \param backtrace_str The backtrace to update.
+   * \param update_mode The mode to update the backtrace,
+   *        can be either kTVMFFIBacktraceUpdateModeReplace, 
kTVMFFIBacktraceUpdateModeAppend.
    */
-  void UpdateTraceback(const TVMFFIByteArray* traceback_str) {
+  void UpdateBacktrace(const TVMFFIByteArray* backtrace_str, int32_t 
update_mode) {
     ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
-    obj->update_traceback(obj, traceback_str);
+    obj->update_backtrace(obj, backtrace_str, update_mode);
   }
 
   /*!
@@ -188,9 +230,8 @@ class Error : public ObjectRef, public std::exception {
     thread_local std::string what_data;
     ErrorObj* obj = static_cast<ErrorObj*>(data_.get());
     what_data = (std::string("Traceback (most recent call last):\n") +
-                 std::string(obj->traceback.data, obj->traceback.size) +
-                 std::string(obj->kind.data, obj->kind.size) + std::string(": 
") +
-                 std::string(obj->message.data, obj->message.size) + '\n');
+                 TracebackMostRecentCallLast() + std::string(obj->kind.data, 
obj->kind.size) +
+                 std::string(": ") + std::string(obj->message.data, 
obj->message.size) + '\n');
     return what_data.c_str();
   }
 
@@ -203,11 +244,11 @@ namespace details {
 
 class ErrorBuilder {
  public:
-  explicit ErrorBuilder(std::string kind, std::string traceback, bool 
log_before_throw)
-      : kind_(kind), traceback_(traceback), 
log_before_throw_(log_before_throw) {}
+  explicit ErrorBuilder(std::string kind, std::string backtrace, bool 
log_before_throw)
+      : kind_(kind), backtrace_(backtrace), 
log_before_throw_(log_before_throw) {}
 
-  explicit ErrorBuilder(std::string kind, const TVMFFIByteArray* traceback, 
bool log_before_throw)
-      : ErrorBuilder(kind, std::string(traceback->data, traceback->size), 
log_before_throw) {}
+  explicit ErrorBuilder(std::string kind, const TVMFFIByteArray* backtrace, 
bool log_before_throw)
+      : ErrorBuilder(kind, std::string(backtrace->data, backtrace->size), 
log_before_throw) {}
 
 // MSVC disable warning in error builder as it is exepected
 #ifdef _MSC_VER
@@ -216,7 +257,7 @@ class ErrorBuilder {
 #endif
   // avoid inline to reduce binary size, error throw path do not need to be 
fast
   [[noreturn]] ~ErrorBuilder() noexcept(false) {
-    ::tvm::ffi::Error error(std::move(kind_), stream_.str(), 
std::move(traceback_));
+    ::tvm::ffi::Error error(std::move(kind_), stream_.str(), 
std::move(backtrace_));
     if (log_before_throw_) {
       std::cerr << error.what();
     }
@@ -231,14 +272,14 @@ class ErrorBuilder {
  protected:
   std::string kind_;
   std::ostringstream stream_;
-  std::string traceback_;
+  std::string backtrace_;
   bool log_before_throw_;
 };
 
 }  // namespace details
 
 /*!
- * \brief Helper macro to throw an error with traceback and message
+ * \brief Helper macro to throw an error with backtrace and message
  *
  * \code
  *
@@ -250,7 +291,7 @@ class ErrorBuilder {
  */
 #define TVM_FFI_THROW(ErrorKind)                                               
               \
   ::tvm::ffi::details::ErrorBuilder(#ErrorKind,                                
               \
-                                    TVMFFITraceback(__FILE__, __LINE__, 
TVM_FFI_FUNC_SIG, 0), \
+                                    TVMFFIBacktrace(__FILE__, __LINE__, 
TVM_FFI_FUNC_SIG, 0), \
                                     TVM_FFI_ALWAYS_LOG_BEFORE_THROW)           
               \
       .stream()
 
@@ -263,7 +304,7 @@ class ErrorBuilder {
  */
 #define TVM_FFI_LOG_AND_THROW(ErrorKind)                                       
   \
   ::tvm::ffi::details::ErrorBuilder(                                           
   \
-      #ErrorKind, TVMFFITraceback(__FILE__, __LINE__, TVM_FFI_FUNC_SIG, 0), 
true) \
+      #ErrorKind, TVMFFIBacktrace(__FILE__, __LINE__, TVM_FFI_FUNC_SIG, 0), 
true) \
       .stream()
 
 // Glog style checks with TVM_FFI prefix
diff --git a/pyproject.toml b/pyproject.toml
index 4a6bd8e..1e97de7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,7 +17,7 @@
 
 [project]
 name = "apache-tvm-ffi"
-version = "0.1.0b4"
+version = "0.1.0b5"
 description = "tvm ffi"
 
 authors = [{ name = "TVM FFI team" }]
diff --git a/python/tvm_ffi/core.pyi b/python/tvm_ffi/core.pyi
index 9ca9a18..cfb3ea9 100644
--- a/python/tvm_ffi/core.pyi
+++ b/python/tvm_ffi/core.pyi
@@ -26,8 +26,8 @@ from typing import Any, Callable
 ERROR_NAME_TO_TYPE: dict[str, type]
 ERROR_TYPE_TO_NAME: dict[type, str]
 
-_WITH_APPEND_TRACEBACK: Callable[[BaseException, str], BaseException] | None
-_TRACEBACK_TO_STR: Callable[[types.TracebackType | None], str] | None
+_WITH_APPEND_BACKTRACE: Callable[[BaseException, str], BaseException] | None
+_TRACEBACK_TO_BACKTRACE_STR: Callable[[types.TracebackType | None], str] | None
 
 # DLPack protocol version (defined in tensor.pxi)
 __dlpack_version__: tuple[int, int]
@@ -90,15 +90,15 @@ def _lookup_type_info_from_type_key(type_key: str) -> 
TypeInfo: ...
 class Error(Object):
     """Base class for FFI errors."""
 
-    def __init__(self, kind: str, message: str, traceback: str) -> None: ...
-    def update_traceback(self, traceback: str) -> None: ...
+    def __init__(self, kind: str, message: str, backtrace: str) -> None: ...
+    def update_backtrace(self, backtrace: str) -> None: ...
     def py_error(self) -> BaseException: ...
     @property
     def kind(self) -> str: ...
     @property
     def message(self) -> str: ...
     @property
-    def traceback(self) -> str: ...
+    def backtrace(self) -> str: ...
 
 def _convert_to_ffi_error(error: BaseException) -> Error: ...
 def _env_set_current_stream(device_type: int, device_id: int, stream: int) -> 
int: ...
diff --git a/python/tvm_ffi/cython/base.pxi b/python/tvm_ffi/cython/base.pxi
index 6fe10fd..3ff3ecc 100644
--- a/python/tvm_ffi/cython/base.pxi
+++ b/python/tvm_ffi/cython/base.pxi
@@ -137,11 +137,17 @@ cdef extern from "tvm/ffi/c_api.h":
         const int64_t* data
         size_t size
 
+    ctypedef enum TVMFFIBacktraceUpdateMode:
+        kTVMFFIBacktraceUpdateModeReplace = 0
+        kTVMFFIBacktraceUpdateModeAppend = 1
+
     ctypedef struct TVMFFIErrorCell:
         TVMFFIByteArray kind
         TVMFFIByteArray message
-        TVMFFIByteArray traceback
-        void (*update_traceback)(TVMFFIObjectHandle self, const 
TVMFFIByteArray* traceback)
+        TVMFFIByteArray backtrace
+        void (*update_backtrace)(
+            TVMFFIObjectHandle self, const TVMFFIByteArray* backtrace, int32_t 
update_mode
+        )
 
     ctypedef int (*TVMFFISafeCallType)(
         void* handle, const TVMFFIAny* args, int32_t num_args,
@@ -207,15 +213,15 @@ cdef extern from "tvm/ffi/c_api.h":
     int TVMFFIFunctionGetGlobal(TVMFFIByteArray* name, TVMFFIObjectHandle* 
out) nogil
     void TVMFFIErrorMoveFromRaised(TVMFFIObjectHandle* result) nogil
     void TVMFFIErrorSetRaised(TVMFFIObjectHandle error) nogil
-    TVMFFIObjectHandle TVMFFIErrorCreate(TVMFFIByteArray* kind, 
TVMFFIByteArray* message,
-                                         TVMFFIByteArray* traceback) nogil
+    int TVMFFIErrorCreate(TVMFFIByteArray* kind, TVMFFIByteArray* message,
+                          TVMFFIByteArray* backtrace, TVMFFIObjectHandle* out) 
nogil
 
     int TVMFFITypeKeyToIndex(TVMFFIByteArray* type_key, int32_t* out_tindex) 
nogil
     int TVMFFIStringFromByteArray(TVMFFIByteArray* input_, TVMFFIAny* out) 
nogil
     int TVMFFIBytesFromByteArray(TVMFFIByteArray* input_, TVMFFIAny* out) nogil
     int TVMFFIDataTypeFromString(TVMFFIByteArray* str, DLDataType* out) nogil
     int TVMFFIDataTypeToString(const DLDataType* dtype, TVMFFIAny* out) nogil
-    const TVMFFIByteArray* TVMFFITraceback(const char* filename, int lineno,
+    const TVMFFIByteArray* TVMFFIBacktrace(const char* filename, int lineno,
                                            const char* func, int 
cross_ffi_boundary) nogil
     int TVMFFITensorFromDLPack(DLManagedTensor* src, int32_t require_alignment,
                                int32_t require_contiguous, TVMFFIObjectHandle* 
out) nogil
diff --git a/python/tvm_ffi/cython/error.pxi b/python/tvm_ffi/cython/error.pxi
index b777100..8186bff 100644
--- a/python/tvm_ffi/cython/error.pxi
+++ b/python/tvm_ffi/cython/error.pxi
@@ -22,8 +22,8 @@ import re
 ERROR_NAME_TO_TYPE = {}
 ERROR_TYPE_TO_NAME = {}
 
-_WITH_APPEND_TRACEBACK = None
-_TRACEBACK_TO_STR = None
+_WITH_APPEND_BACKTRACE = None
+_TRACEBACK_TO_BACKTRACE_STR = None
 
 
 cdef class Error(Object):
@@ -35,24 +35,37 @@ cdef class Error(Object):
     to convert it to a python error then raise it.
     """
 
-    def __init__(self, kind, message, traceback):
+    def __init__(self, kind, message, backtrace):
         cdef ByteArrayArg kind_arg = ByteArrayArg(c_str(kind))
         cdef ByteArrayArg message_arg = ByteArrayArg(c_str(message))
-        cdef ByteArrayArg traceback_arg = ByteArrayArg(c_str(traceback))
-        (<Object>self).chandle = TVMFFIErrorCreate(
-            kind_arg.cptr(), message_arg.cptr(), traceback_arg.cptr()
+        cdef ByteArrayArg backtrace_arg = ByteArrayArg(c_str(backtrace))
+        cdef TVMFFIObjectHandle out
+        cdef int ret = TVMFFIErrorCreate(
+            kind_arg.cptr(), message_arg.cptr(), backtrace_arg.cptr(), &out
         )
+        if ret != 0:
+            raise MemoryError("Failed to create error object")
+        (<Object>self).chandle = out
 
-    def update_traceback(self, traceback):
-        """Update the traceback of the error
+    def update_backtrace(self, backtrace):
+        """Update the backtrace of the error
 
         Parameters
         ----------
-        traceback : str
-            The traceback to update.
+        backtrace : str
+            The backtrace to update.
+
+        Note
+        ----
+        The backtrace is stored in the reverse order of python traceback.
+        Such storage pattern makes it easier to append the backtrace to
+        the end when error is propagated. When we translate the backtrace to 
python traceback,
+        we will reverse the order of the lines to make it align with python 
traceback.
         """
-        cdef ByteArrayArg traceback_arg = ByteArrayArg(c_str(traceback))
-        TVMFFIErrorGetCellPtr(self.chandle).update_traceback(self.chandle, 
traceback_arg.cptr())
+        cdef ByteArrayArg backtrace_arg = ByteArrayArg(c_str(backtrace))
+        TVMFFIErrorGetCellPtr(self.chandle).update_backtrace(
+            self.chandle, backtrace_arg.cptr(), 
kTVMFFIBacktraceUpdateModeReplace
+        )
 
     def py_error(self):
         """
@@ -60,7 +73,7 @@ cdef class Error(Object):
         """
         error_cls = ERROR_NAME_TO_TYPE.get(self.kind, RuntimeError)
         py_error = error_cls(self.message)
-        py_error = _WITH_APPEND_TRACEBACK(py_error, self.traceback)
+        py_error = _WITH_APPEND_BACKTRACE(py_error, self.backtrace)
         py_error.__tvm_ffi_error__ = self
         return py_error
 
@@ -73,8 +86,8 @@ cdef class Error(Object):
         return bytearray_to_str(&(TVMFFIErrorGetCellPtr(self.chandle).message))
 
     @property
-    def traceback(self):
-        return 
bytearray_to_str(&(TVMFFIErrorGetCellPtr(self.chandle).traceback))
+    def backtrace(self):
+        return 
bytearray_to_str(&(TVMFFIErrorGetCellPtr(self.chandle).backtrace))
 
 
 _register_object_by_index(kTVMFFIError, Error)
@@ -97,31 +110,32 @@ cdef inline int set_last_ffi_error(error) except -1:
 
     kind = ERROR_TYPE_TO_NAME.get(type(error), "RuntimeError")
     message = error.__str__()
-    py_traceback = _TRACEBACK_TO_STR(error.__traceback__)
-    c_traceback = bytearray_to_str(TVMFFITraceback(NULL, 0, NULL, 0))
+    # NOTE: backtrace storage convention is reverse of python traceback
+    py_backtrace = _TRACEBACK_TO_BACKTRACE_STR(error.__traceback__)
+    c_backtrace = bytearray_to_str(TVMFFIBacktrace(NULL, 0, NULL, 0))
 
     # error comes from an exception thrown from C++ side
     if hasattr(error, "__tvm_ffi_error__"):
         # already have stack trace
         ffi_error = error.__tvm_ffi_error__
-        # attach the python traceback together with the C++ traceback to get 
full trace
-        ffi_error.update_traceback(c_traceback + py_traceback)
+        # attach the python backtrace together with the C++ backtrace to get 
full trace
+        ffi_error.update_backtrace(py_backtrace + c_backtrace)
         TVMFFIErrorSetRaised(ffi_error.chandle)
     else:
-        ffi_error = Error(kind, message, c_traceback + py_traceback)
+        ffi_error = Error(kind, message, py_backtrace + c_backtrace)
         TVMFFIErrorSetRaised(ffi_error.chandle)
 
 
 def _convert_to_ffi_error(error):
     """Convert the python error to the FFI error"""
-    py_traceback = _TRACEBACK_TO_STR(error.__traceback__)
+    py_backtrace = _TRACEBACK_TO_BACKTRACE_STR(error.__traceback__)
     if hasattr(error, "__tvm_ffi_error__"):
-        error.__tvm_ffi_error__.update_traceback(py_traceback)
+        error.__tvm_ffi_error__.update_backtrace(py_backtrace)
         return error.__tvm_ffi_error__
     else:
         kind = ERROR_TYPE_TO_NAME.get(type(error), "RuntimeError")
         message = error.__str__()
-        return Error(kind, message, py_traceback)
+        return Error(kind, message, py_backtrace)
 
 
 cdef inline int CHECK_CALL(int ret) except -2:
diff --git a/python/tvm_ffi/cython/function.pxi 
b/python/tvm_ffi/cython/function.pxi
index 0df3c00..ff79cff 100644
--- a/python/tvm_ffi/cython/function.pxi
+++ b/python/tvm_ffi/cython/function.pxi
@@ -647,7 +647,7 @@ cdef class Function(Object):
             &c_dlpack_to_pyobject
         )
         # NOTE: logic is same as check_call
-        # directly inline here to simplify traceback
+        # directly inline here to simplify the resulting trace
         if c_api_ret_code == 0:
             return make_ret(result, c_dlpack_to_pyobject)
         elif c_api_ret_code == -2:
diff --git a/python/tvm_ffi/cython/type_info.pxi 
b/python/tvm_ffi/cython/type_info.pxi
index a50a95f..4ab9f15 100644
--- a/python/tvm_ffi/cython/type_info.pxi
+++ b/python/tvm_ffi/cython/type_info.pxi
@@ -49,7 +49,7 @@ cdef class FieldSetter:
             &c_api_ret_code
         )
         # NOTE: logic is same as check_call
-        # directly inline here to simplify traceback
+        # directly inline here to simplify backtrace
         if c_api_ret_code == 0:
             return
         elif c_api_ret_code == -2:
diff --git a/python/tvm_ffi/error.py b/python/tvm_ffi/error.py
index 66f068f..f29fc90 100644
--- a/python/tvm_ffi/error.py
+++ b/python/tvm_ffi/error.py
@@ -26,13 +26,13 @@ from typing import Any, Optional
 from . import core
 
 
-def _parse_traceback(traceback: str) -> list[tuple[str, int, str]]:
-    """Parse the traceback string into a list of (filename, lineno, func).
+def _parse_backtrace(backtrace: str) -> list[tuple[str, int, str]]:
+    """Parse the backtrace string into a list of (filename, lineno, func).
 
     Parameters
     ----------
-    traceback : str
-        The traceback string.
+    backtrace : str
+        The backtrace string.
 
     Returns
     -------
@@ -42,7 +42,7 @@ def _parse_traceback(traceback: str) -> list[tuple[str, int, 
str]]:
     """
     pattern = r'File "(.+?)", line (\d+), in (.+)'
     result = []
-    for line in traceback.split("\n"):
+    for line in backtrace.split("\n"):
         match = re.match(pattern, line.strip())
         if match:
             try:
@@ -126,15 +126,15 @@ class TracebackManager:
 _TRACEBACK_MANAGER = TracebackManager()
 
 
-def _with_append_traceback(py_error: BaseException, traceback: str) -> 
BaseException:
-    """Append the traceback to the py_error and return it."""
+def _with_append_backtrace(py_error: BaseException, backtrace: str) -> 
BaseException:
+    """Append the backtrace to the py_error and return it."""
     tb = py_error.__traceback__
-    for filename, lineno, func in reversed(_parse_traceback(traceback)):
+    for filename, lineno, func in _parse_backtrace(backtrace):
         tb = _TRACEBACK_MANAGER.append_traceback(tb, filename, lineno, func)
     return py_error.with_traceback(tb)
 
 
-def _traceback_to_str(tb: Optional[types.TracebackType]) -> str:
+def _traceback_to_backtrace_str(tb: Optional[types.TracebackType]) -> str:
     """Convert the traceback to a string."""
     lines = []
     while tb is not None:
@@ -144,11 +144,13 @@ def _traceback_to_str(tb: Optional[types.TracebackType]) 
-> str:
         funcname = frame.f_code.co_name
         lines.append(f'  File "{filename}", line {lineno}, in {funcname}\n')
         tb = tb.tb_next
-    return "".join(lines)
+    # needs to reverse the order of the lines so backtrace stores in
+    # the reverse order of python traceback
+    return "".join(reversed(lines))
 
 
-core._WITH_APPEND_TRACEBACK = _with_append_traceback
-core._TRACEBACK_TO_STR = _traceback_to_str
+core._WITH_APPEND_BACKTRACE = _with_append_backtrace
+core._TRACEBACK_TO_BACKTRACE_STR = _traceback_to_backtrace_str
 
 
 def register_error(
diff --git a/src/ffi/traceback.cc b/src/ffi/backtrace.cc
similarity index 73%
rename from src/ffi/traceback.cc
rename to src/ffi/backtrace.cc
index 57638d7..ce99b84 100644
--- a/src/ffi/traceback.cc
+++ b/src/ffi/backtrace.cc
@@ -17,17 +17,17 @@
  * under the License.
  */
 /*!
- * \file traceback.cc
- * \brief Traceback implementation on non-windows platforms
- * \note We use the term "traceback" to be consistent with python naming 
convention.
+ * \file backtrace.cc
+ * \brief Backtrace implementation on non-windows platforms
+ * \note We use the term "backtrace" to be consistent with python naming 
convention.
  */
 #ifndef _MSC_VER
 
-#include "./traceback.h"
-
 #include <tvm/ffi/c_api.h>
 #include <tvm/ffi/error.h>
 
+#include "./backtrace_utils.h"
+
 #if TVM_FFI_USE_LIBBACKTRACE
 
 #include <backtrace.h>
@@ -86,7 +86,7 @@ void BacktraceSyminfoCallback(void* data, uintptr_t pc, const 
char* symname, uin
 
 int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int 
lineno,
                           const char* symbol) {
-  auto stack_trace = reinterpret_cast<TracebackStorage*>(data);
+  auto stack_trace = reinterpret_cast<BacktraceStorage*>(data);
   std::string symbol_str = "<unknown>";
   if (symbol) {
     symbol_str = DemangleName(symbol);
@@ -95,7 +95,7 @@ int BacktraceFullCallback(void* data, uintptr_t pc, const 
char* filename, int li
     backtrace_syminfo(_bt_state, pc, BacktraceSyminfoCallback, 
BacktraceErrorCallback, &symbol_str);
   }
   symbol = symbol_str.data();
-  if (stack_trace->ExceedTracebackLimit()) {
+  if (stack_trace->ExceedBacktraceLimit()) {
     return 1;
   }
   if (stack_trace->stop_at_boundary && DetectFFIBoundary(filename, symbol)) {
@@ -116,21 +116,21 @@ int BacktraceFullCallback(void* data, uintptr_t pc, const 
char* filename, int li
 }  // namespace ffi
 }  // namespace tvm
 
-const TVMFFIByteArray* TVMFFITraceback(const char* filename, int lineno, const 
char* func,
+const TVMFFIByteArray* TVMFFIBacktrace(const char* filename, int lineno, const 
char* func,
                                        int cross_ffi_boundary) {
-  // We collapse the traceback into a single function
-  // to simplify the traceback detection handling (since we need to detect 
TVMFFITraceback)
-  static thread_local std::string traceback_str;
-  static thread_local TVMFFIByteArray traceback_array;
-  // pass in current line as here so last line of traceback is always accurate
-  tvm::ffi::TracebackStorage traceback;
-  traceback.stop_at_boundary = cross_ffi_boundary == 0;
+  // We collapse the backtrace into a single function
+  // to simplify the backtrace detection handling (since we need to detect 
TVMFFIBacktrace)
+  static thread_local std::string backtrace_str;
+  static thread_local TVMFFIByteArray backtrace_array;
+  // pass in current line as here so last line of backtrace is always accurate
+  tvm::ffi::BacktraceStorage backtrace;
+  backtrace.stop_at_boundary = cross_ffi_boundary == 0;
   if (filename != nullptr && func != nullptr) {
-    // need to skip TVMFFITraceback and the caller function
+    // need to skip TVMFFIBacktrace and the caller function
     // which is already included in filename and func
-    traceback.skip_frame_count = 2;
+    backtrace.skip_frame_count = 2;
     if (!tvm::ffi::ShouldExcludeFrame(filename, func)) {
-      traceback.Append(filename, func, lineno);
+      backtrace.Append(filename, func, lineno);
     }
   }
   // libbacktrace eats memory if run on multiple threads at the same time, so 
we guard against it
@@ -138,12 +138,12 @@ const TVMFFIByteArray* TVMFFITraceback(const char* 
filename, int lineno, const c
     static std::mutex m;
     std::lock_guard<std::mutex> lock(m);
     backtrace_full(tvm::ffi::_bt_state, 0, tvm::ffi::BacktraceFullCallback,
-                   tvm::ffi::BacktraceErrorCallback, &traceback);
+                   tvm::ffi::BacktraceErrorCallback, &backtrace);
   }
-  traceback_str = traceback.GetTraceback();
-  traceback_array.data = traceback_str.data();
-  traceback_array.size = traceback_str.size();
-  return &traceback_array;
+  backtrace_str = backtrace.GetBacktrace();
+  backtrace_array.data = backtrace_str.data();
+  backtrace_array.size = backtrace_str.size();
+  return &backtrace_array;
 }
 
 #if TVM_FFI_BACKTRACE_ON_SEGFAULT
@@ -151,9 +151,9 @@ void TVMFFISegFaultHandler(int sig) {
   // Technically we shouldn't do any allocation in a signal handler, but
   // Backtrace may allocate. What's the worst it could do? We're already
   // crashing.
-  const TVMFFIByteArray* traceback = TVMFFITraceback(nullptr, 0, nullptr, 1);
+  const TVMFFIByteArray* backtrace = TVMFFIBacktrace(nullptr, 0, nullptr, 1);
   std::cerr << "!!!!!!! Segfault encountered !!!!!!!\n"
-            << std::string(traceback->data, traceback->size) << std::endl;
+            << std::string(backtrace->data, backtrace->size) << std::endl;
   // Re-raise signal with default handler
   struct sigaction act;
   std::memset(&act, 0, sizeof(struct sigaction));
@@ -170,19 +170,19 @@ __attribute__((constructor)) void 
TVMFFIInstallSignalHandler(void) {
 #endif  // TVM_FFI_BACKTRACE_ON_SEGFAULT
 #else
 // fallback implementation simply print out the last trace
-const TVMFFIByteArray* TVMFFITraceback(const char* filename, int lineno, const 
char* func,
+const TVMFFIByteArray* TVMFFIBacktrace(const char* filename, int lineno, const 
char* func,
                                        int cross_ffi_boundary) {
-  static thread_local std::string traceback_str;
-  static thread_local TVMFFIByteArray traceback_array;
-  std::ostringstream traceback_stream;
+  static thread_local std::string backtrace_str;
+  static thread_local TVMFFIByteArray backtrace_array;
+  std::ostringstream backtrace_stream;
   if (filename != nullptr && func != nullptr) {
     // python style backtrace
-    traceback_stream << "  File \"" << filename << "\", line " << lineno << ", 
in " << func << '\n';
+    backtrace_stream << "  File \"" << filename << "\", line " << lineno << ", 
in " << func << '\n';
   }
-  traceback_str = traceback_stream.str();
-  traceback_array.data = traceback_str.data();
-  traceback_array.size = traceback_str.size();
-  return &traceback_array;
+  backtrace_str = backtrace_stream.str();
+  backtrace_array.data = backtrace_str.data();
+  backtrace_array.size = backtrace_str.size();
+  return &backtrace_array;
 }
 #endif  // TVM_FFI_USE_LIBBACKTRACE
 #endif  // _MSC_VER
diff --git a/src/ffi/traceback.h b/src/ffi/backtrace_utils.h
similarity index 76%
rename from src/ffi/traceback.h
rename to src/ffi/backtrace_utils.h
index 7104144..8dd9223 100644
--- a/src/ffi/traceback.h
+++ b/src/ffi/backtrace_utils.h
@@ -17,9 +17,9 @@
  * under the License.
  */
 /*!
- * \file traceback.h
- * \brief Common headers for traceback.
- * \note We use the term "traceback" to be consistent with python naming 
convention.
+ * \file backtrace.h
+ * \brief Common headers for backtrace.
+ * \note We use the term "backtrace" to be consistent with python naming 
convention.
  */
 #ifndef TVM_FFI_TRACEBACK_H_
 #define TVM_FFI_TRACEBACK_H_
@@ -39,7 +39,7 @@ namespace ffi {
 #pragma warning(disable : 4996)  // std::getenv is unsafe
 #endif
 
-inline int32_t GetTracebackLimit() {
+inline int32_t GetBacktraceLimit() {
   if (const char* env = std::getenv("TVM_TRACEBACK_LIMIT")) {
     return std::stoi(env);
   }
@@ -61,7 +61,7 @@ inline bool ShouldExcludeFrame(const char* filename, const 
char* symbol) {
     if (strncmp(symbol, "tvm::ffi::details::", 19) == 0) {
       return true;
     }
-    if (strncmp(symbol, "TVMFFITraceback", 15) == 0) {
+    if (strncmp(symbol, "TVMFFIBacktrace", 15) == 0) {
       return true;
     }
     if (strncmp(symbol, "TVMFFIErrorSetRaisedFromCStr", 28) == 0) {
@@ -102,11 +102,11 @@ inline bool ShouldExcludeFrame(const char* filename, 
const char* symbol) {
 }
 
 /**
- * \brief List frames that should stop the traceback.
+ * \brief List frames that should stop the backtrace.
  * \param filename The filename of the frame.
  * \param symbol The symbol name of the frame.
- * \return true if the frame should stop the traceback.
- * \note We stop traceback at the FFI boundary.
+ * \return true if the frame should stop the backtrace.
+ * \note We stop backtrace at the FFI boundary.
  */
 inline bool DetectFFIBoundary(const char* filename, const char* symbol) {
   if (symbol != nullptr) {
@@ -121,7 +121,7 @@ inline bool DetectFFIBoundary(const char* filename, const 
char* symbol) {
       return true;
     }
     // Python interpreter stack frames
-    // we stop traceback at the Python interpreter stack frames
+    // we stop backtrace at the Python interpreter stack frames
     // since these frame will be handled from by the python side.
     if (strncmp(symbol, "_Py", 3) == 0 || strncmp(symbol, "PyObject", 8) == 0) 
{
       return true;
@@ -131,12 +131,15 @@ inline bool DetectFFIBoundary(const char* filename, const 
char* symbol) {
 }
 
 /*!
- * \brief storage to store traceback
+ * \brief storage to store backtrace
  */
-struct TracebackStorage {
-  std::vector<std::string> lines;
-  /*! \brief Maximum size of the traceback. */
-  size_t max_frame_size = GetTracebackLimit();
+struct BacktraceStorage {
+  /*! \brief The stream to store the backtrace. */
+  std::ostringstream backtrace_stream_;
+  /*! \brief The number of lines in the backtrace. */
+  size_t line_count_ = 0;
+  /*! \brief Maximum size of the backtrace. */
+  size_t max_frame_size = GetBacktraceLimit();
   /*! \brief Number of frames to skip. */
   size_t skip_frame_count = 0;
   /*! \brief Whether to stop at the ffi boundary. */
@@ -157,23 +160,16 @@ struct TracebackStorage {
         return;
       }
     }
-    std::ostringstream trackeback_stream;
-    trackeback_stream << "  File \"" << filename << "\"";
-    trackeback_stream << ", line " << lineno;
-    trackeback_stream << ", in " << func << '\n';
-    lines.push_back(trackeback_stream.str());
+    backtrace_stream_ << "  File \"" << filename << "\"";
+    backtrace_stream_ << ", line " << lineno;
+    backtrace_stream_ << ", in " << func << '\n';
+    line_count_++;
   }
 
-  bool ExceedTracebackLimit() const { return lines.size() >= max_frame_size; }
+  bool ExceedBacktraceLimit() const { return line_count_ >= max_frame_size; }
 
-  // get traceback in the order of most recent call last
-  std::string GetTraceback() const {
-    std::string traceback;
-    for (auto it = lines.rbegin(); it != lines.rend(); ++it) {
-      traceback.insert(traceback.end(), it->begin(), it->end());
-    }
-    return traceback;
-  }
+  // get backtrace in the order of most recent call last
+  std::string GetBacktrace() const { return backtrace_stream_.str(); }
 };
 
 }  // namespace ffi
diff --git a/src/ffi/traceback_win.cc b/src/ffi/backtrace_win.cc
similarity index 78%
rename from src/ffi/traceback_win.cc
rename to src/ffi/backtrace_win.cc
index ae7d85d..c4491db 100644
--- a/src/ffi/traceback_win.cc
+++ b/src/ffi/backtrace_win.cc
@@ -17,9 +17,9 @@
  * under the License.
  */
 /*!
- * \file traceback_win.cc
- * \brief Traceback implementation on windows platform
- * \note We use the term "traceback" to be consistent with python naming 
convention.
+ * \file backtrace_win.cc
+ * \brief Backtrace implementation on windows platform
+ * \note We use the term "backtrace" to be consistent with python naming 
convention.
  */
 #ifdef _MSC_VER
 
@@ -34,21 +34,21 @@
 #include <iostream>
 #include <vector>
 
-#include "./traceback.h"
+#include "./backtrace_utils.h"
 
-const TVMFFIByteArray* TVMFFITraceback(const char* filename, int lineno, const 
char* func,
+const TVMFFIByteArray* TVMFFIBacktrace(const char* filename, int lineno, const 
char* func,
                                        int cross_ffi_boundary) {
-  static thread_local std::string traceback_str;
-  static thread_local TVMFFIByteArray traceback_array;
+  static thread_local std::string backtrace_str;
+  static thread_local TVMFFIByteArray backtrace_array;
 
-  // pass in current line as here so last line of traceback is always accurate
-  tvm::ffi::TracebackStorage traceback;
-  traceback.stop_at_boundary = cross_ffi_boundary == 0;
+  // pass in current line as here so last line of backtrace is always accurate
+  tvm::ffi::BacktraceStorage backtrace;
+  backtrace.stop_at_boundary = cross_ffi_boundary == 0;
   if (filename != nullptr && func != nullptr) {
-    // need to skip TVMFFITraceback and the caller function
+    // need to skip TVMFFIBacktrace and the caller function
     // which is already included in filename and func
-    traceback.skip_frame_count = 2;
-    traceback.Append(filename, func, lineno);
+    backtrace.skip_frame_count = 2;
+    backtrace.Append(filename, func, lineno);
   }
 
   HANDLE process = GetCurrentProcess();
@@ -80,7 +80,7 @@ const TVMFFIByteArray* TVMFFITraceback(const char* filename, 
int lineno, const c
   stack.AddrFrame.Mode = AddrModeFlat;
   stack.AddrStack.Mode = AddrModeFlat;
 
-  while (!traceback.ExceedTracebackLimit()) {
+  while (!backtrace.ExceedBacktraceLimit()) {
     if (!StackWalk64(machine_type, process, thread, &stack, &context, nullptr,
                      SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) {
       break;
@@ -120,23 +120,23 @@ const TVMFFIByteArray* TVMFFITraceback(const char* 
filename, int lineno, const c
         symbol = symbol_info->Name;
       }
     }
-    if (traceback.stop_at_boundary && tvm::ffi::DetectFFIBoundary(filename, 
symbol)) {
+    if (backtrace.stop_at_boundary && tvm::ffi::DetectFFIBoundary(filename, 
symbol)) {
       break;
     }
     // skip extra frames
-    if (traceback.skip_frame_count > 0) {
-      traceback.skip_frame_count--;
+    if (backtrace.skip_frame_count > 0) {
+      backtrace.skip_frame_count--;
       continue;
     }
     if (tvm::ffi::ShouldExcludeFrame(filename, symbol)) {
       continue;
     }
-    traceback.Append(filename, symbol, lineno);
+    backtrace.Append(filename, symbol, lineno);
   }
   SymCleanup(process);
-  traceback_str = traceback.GetTraceback();
-  traceback_array.data = traceback_str.data();
-  traceback_array.size = traceback_str.size();
-  return &traceback_array;
+  backtrace_str = backtrace.GetBacktrace();
+  backtrace_array.data = backtrace_str.data();
+  backtrace_array.size = backtrace_str.size();
+  return &backtrace_array;
 }
 #endif  // _MSC_VER
diff --git a/src/ffi/error.cc b/src/ffi/error.cc
index ba8dbbf..c65fd2b 100644
--- a/src/ffi/error.cc
+++ b/src/ffi/error.cc
@@ -33,8 +33,8 @@ class SafeCallContext {
         
details::ObjectUnsafe::ObjectPtrFromUnowned<ErrorObj>(static_cast<TVMFFIObject*>(error));
   }
 
-  void SetRaisedByCstr(const char* kind, const char* message, const 
TVMFFIByteArray* traceback) {
-    Error error(kind, message, traceback);
+  void SetRaisedByCstr(const char* kind, const char* message, const 
TVMFFIByteArray* backtrace) {
+    Error error(kind, message, backtrace);
     last_error_ = 
details::ObjectUnsafe::ObjectPtrFromObjectRef<ErrorObj>(std::move(error));
   }
 
@@ -55,9 +55,9 @@ class SafeCallContext {
 }  // namespace tvm
 
 void TVMFFIErrorSetRaisedFromCStr(const char* kind, const char* message) {
-  // NOTE: run traceback here to simplify the depth of tracekback
+  // NOTE: run backtrace here to simplify the depth of tracekback
   tvm::ffi::SafeCallContext::ThreadLocal()->SetRaisedByCstr(
-      kind, message, TVMFFITraceback(nullptr, 0, nullptr, 0));
+      kind, message, TVMFFIBacktrace(nullptr, 0, nullptr, 0));
 }
 
 void TVMFFIErrorSetRaised(TVMFFIObjectHandle error) {
@@ -68,14 +68,18 @@ void TVMFFIErrorMoveFromRaised(TVMFFIObjectHandle* result) {
   tvm::ffi::SafeCallContext::ThreadLocal()->MoveFromRaised(result);
 }
 
-TVMFFIObjectHandle TVMFFIErrorCreate(const TVMFFIByteArray* kind, const 
TVMFFIByteArray* message,
-                                     const TVMFFIByteArray* traceback) {
+int TVMFFIErrorCreate(const TVMFFIByteArray* kind, const TVMFFIByteArray* 
message,
+                      const TVMFFIByteArray* backtrace, TVMFFIObjectHandle* 
out) {
+  // log other errors to the logger
   TVM_FFI_LOG_EXCEPTION_CALL_BEGIN();
-  tvm::ffi::Error error(std::string(kind->data, kind->size),
-                        std::string(message->data, message->size),
-                        std::string(traceback->data, traceback->size));
-  TVMFFIObjectHandle out =
-      
tvm::ffi::details::ObjectUnsafe::MoveObjectRefToTVMFFIObjectPtr(std::move(error));
-  return out;
+  try {
+    tvm::ffi::Error error(std::string(kind->data, kind->size),
+                          std::string(message->data, message->size),
+                          std::string(backtrace->data, backtrace->size));
+    *out = 
tvm::ffi::details::ObjectUnsafe::MoveObjectRefToTVMFFIObjectPtr(std::move(error));
+    return 0;
+  } catch (const std::bad_alloc& e) {
+    return -1;
+  }
   TVM_FFI_LOG_EXCEPTION_CALL_END(TVMFFIErrorCreate);
 }
diff --git a/src/ffi/extra/testing.cc b/src/ffi/extra/testing.cc
index 370a1c6..79b6164 100644
--- a/src/ffi/extra/testing.cc
+++ b/src/ffi/extra/testing.cc
@@ -122,12 +122,12 @@ class TestCxxClassDerivedDerived : public 
TestCxxClassDerived {
 };
 
 TVM_FFI_NO_INLINE void TestRaiseError(String kind, String msg) {
-  // keep name and no liner for testing traceback
-  throw ffi::Error(kind, msg, TVMFFITraceback(__FILE__, __LINE__, 
TVM_FFI_FUNC_SIG, 0));
+  // keep name and no liner for testing backtrace
+  throw ffi::Error(kind, msg, TVMFFIBacktrace(__FILE__, __LINE__, 
TVM_FFI_FUNC_SIG, 0));
 }
 
 TVM_FFI_NO_INLINE void TestApply(PackedArgs args, Any* ret) {
-  // keep name and no liner for testing traceback
+  // keep name and no liner for testing backtrace
   auto f = args[0].cast<Function>();
   f.CallPacked(args.Slice(1), ret);
 }
diff --git a/tests/cpp/test_error.cc b/tests/cpp/test_error.cc
index 9938603..157673d 100644
--- a/tests/cpp/test_error.cc
+++ b/tests/cpp/test_error.cc
@@ -27,7 +27,7 @@ using namespace tvm::ffi;
 
 void ThrowRuntimeError() { TVM_FFI_THROW(RuntimeError) << "test0"; }
 
-TEST(Error, Traceback) {
+TEST(Error, Backtrace) {
   EXPECT_THROW(
       {
         try {
@@ -45,7 +45,7 @@ TEST(Error, Traceback) {
       ::tvm::ffi::Error);
 }
 
-TEST(CheckError, Traceback) {
+TEST(CheckError, Backtrace) {
   EXPECT_THROW(
       {
         try {
@@ -67,4 +67,9 @@ TEST(Error, AnyConvert) {
   EXPECT_EQ(opt_err.value().kind(), "TypeError");
   EXPECT_EQ(opt_err.value().message(), "here");
 }
+
+TEST(Error, TracebackMostRecentCallLast) {
+  Error error("TypeError", "here", "test0\ntest1\ntest2\n");
+  EXPECT_EQ(error.TracebackMostRecentCallLast(), "test2\ntest1\ntest0\n");
+}
 }  // namespace
diff --git a/tests/cpp/test_example.cc b/tests/cpp/test_example.cc
index ee450bc..e9760ef 100644
--- a/tests/cpp/test_example.cc
+++ b/tests/cpp/test_example.cc
@@ -110,7 +110,7 @@ void ExampleErrorHandling() {
   } catch (const ffi::Error& e) {
     EXPECT_EQ(e.kind(), "TypeError");
     EXPECT_EQ(e.message(), "test0");
-    std::cout << e.traceback() << std::endl;
+    std::cout << e.TracebackMostRecentCallLast() << std::endl;
   }
 }
 
diff --git a/tests/python/test_error.py b/tests/python/test_error.py
index 3b77ed7..dda436c 100644
--- a/tests/python/test_error.py
+++ b/tests/python/test_error.py
@@ -22,12 +22,12 @@ import pytest
 import tvm_ffi
 
 
-def test_parse_traceback() -> None:
-    traceback = """
+def test_parse_backtrace() -> None:
+    backtrace = """
     File "test.py", line 1, in <module>
     File "test.py", line 3, in run_test
     """
-    parsed = tvm_ffi.error._parse_traceback(traceback)
+    parsed = tvm_ffi.error._parse_backtrace(backtrace)
     assert len(parsed) == 2
     assert parsed[0] == ("test.py", 1, "<module>")
     assert parsed[1] == ("test.py", 3, "run_test")
@@ -41,7 +41,7 @@ def test_error_from_cxx() -> None:
     except ValueError as e:
         assert e.__tvm_ffi_error__.kind == "ValueError"
         assert e.__tvm_ffi_error__.message == "error XYZ"
-        assert e.__tvm_ffi_error__.traceback.find("TestRaiseError") != -1
+        assert e.__tvm_ffi_error__.backtrace.find("TestRaiseError") != -1
 
     fapply = tvm_ffi.convert(lambda f, *args: f(*args))
 
@@ -66,25 +66,25 @@ def test_error_from_nested_pyfunc() -> None:
         except ValueError as e:
             assert e.__tvm_ffi_error__.kind == "ValueError"
             assert e.__tvm_ffi_error__.message == "error XYZ"
-            assert e.__tvm_ffi_error__.traceback.find("TestRaiseError") != -1
+            assert e.__tvm_ffi_error__.backtrace.find("TestRaiseError") != -1
             record_object.append(e.__tvm_ffi_error__)
             raise e
 
     try:
         cxx_test_apply(raise_error)
     except ValueError as e:
-        traceback = e.__tvm_ffi_error__.traceback
+        backtrace = e.__tvm_ffi_error__.backtrace
         assert e.__tvm_ffi_error__.same_as(record_object[0])
-        assert traceback.count("TestRaiseError") == 1
+        assert backtrace.count("TestRaiseError") == 1
         # The following lines may fail if debug symbols are missing
         try:
-            assert traceback.count("TestApply") == 1
-            assert traceback.count("<lambda>") == 1
-            pos_cxx_raise = traceback.find("TestRaiseError")
-            pos_cxx_apply = traceback.find("TestApply")
-            pos_lambda = traceback.find("<lambda>")
-            assert pos_cxx_raise > pos_lambda
-            assert pos_lambda > pos_cxx_apply
+            assert backtrace.count("TestApply") == 1
+            assert backtrace.count("<lambda>") == 1
+            pos_cxx_raise = backtrace.find("TestRaiseError")
+            pos_cxx_apply = backtrace.find("TestApply")
+            pos_lambda = backtrace.find("<lambda>")
+            assert pos_cxx_raise < pos_lambda
+            assert pos_lambda < pos_cxx_apply
         except Exception as e:
             pytest.xfail("May fail if debug symbols are missing")
 
@@ -99,7 +99,7 @@ def test_error_traceback_update() -> None:
         raise_error()
     except ValueError as e:
         ffi_error = tvm_ffi.convert(e)
-        assert ffi_error.traceback.find("raise_error") != -1
+        assert ffi_error.backtrace.find("raise_error") != -1
 
     def raise_cxx_error() -> None:
         cxx_test_raise_error = 
tvm_ffi.get_global_func("testing.test_raise_error")
@@ -108,8 +108,8 @@ def test_error_traceback_update() -> None:
     try:
         raise_cxx_error()
     except ValueError as e:
-        assert e.__tvm_ffi_error__.traceback.find("raise_cxx_error") == -1
+        assert e.__tvm_ffi_error__.backtrace.find("raise_cxx_error") == -1
         ffi_error1 = tvm_ffi.convert(e)
         ffi_error2 = fecho(e)
-        assert ffi_error1.traceback.find("raise_cxx_error") != -1
-        assert ffi_error2.traceback.find("raise_cxx_error") != -1
+        assert ffi_error1.backtrace.find("raise_cxx_error") != -1
+        assert ffi_error2.backtrace.find("raise_cxx_error") != -1
diff --git a/tests/scripts/task_cpp_tests.sh b/tests/scripts/task_cpp_tests.sh
index d7e935d..57f93ce 100755
--- a/tests/scripts/task_cpp_tests.sh
+++ b/tests/scripts/task_cpp_tests.sh
@@ -19,9 +19,10 @@ set -euxo pipefail
 
 BUILD_TYPE=RelWithDebugInfo
 
-rm -rf build/CMakeCache.txt
+rm -rf build/cpp_tests/CMakeCache.txt
+mkdir -p build/cpp_tests
 
-cmake -G Ninja -S . -B build -DTVM_FFI_BUILD_TESTS=ON 
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
+cmake -G Ninja -S . -B build/cpp_tests -DTVM_FFI_BUILD_TESTS=ON 
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
        -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
-cmake --build build --clean-first --config ${BUILD_TYPE} --target tvm_ffi_tests
-GTEST_COLOR=1 ctest -V -C ${BUILD_TYPE} --test-dir build --output-on-failure
+cmake --build build/cpp_tests --clean-first --config ${BUILD_TYPE} --target 
tvm_ffi_tests
+GTEST_COLOR=1 ctest -V -C ${BUILD_TYPE} --test-dir build/cpp_tests 
--output-on-failure

Reply via email to