This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch refactor-s0
in repository https://gitbox.apache.org/repos/asf/tvm.git

commit 2a461fa42fa3d97066bd115b4b004f2bf2e1e903
Author: tqchen <[email protected]>
AuthorDate: Tue Aug 13 08:35:18 2024 -0400

    [FFI] Add libbacktrace support
    
    Co-authored-by: Junru Shao <[email protected]>
---
 ffi/CMakeLists.txt                           | 102 ++++++++-----
 ffi/cmake/Utils/AddLibbacktrace.cmake        |  52 +++++++
 ffi/cmake/Utils/Library.cmake                |  28 ++++
 ffi/include/tvm/ffi/{c_ffi_abi.h => c_api.h} |  39 ++---
 ffi/include/tvm/ffi/c_ffi_api.h              |  60 --------
 ffi/include/tvm/ffi/error.h                  | 162 ++++++++++++++++++--
 ffi/include/tvm/ffi/internal_utils.h         |  15 +-
 ffi/include/tvm/ffi/object.h                 | 138 ++++++++++--------
 ffi/scripts/run_tests.sh                     |   4 +-
 ffi/src/ffi/object.cc                        | 211 +++++++++++++++++++++++++++
 ffi/src/ffi/registry.cc                      |   0
 ffi/src/ffi/traceback.cc                     | 176 ++++++++++++++++++++++
 ffi/src/ffi/traceback.h                      | 127 ++++++++++++++++
 ffi/tests/example/CMakeLists.txt             |   4 +
 ffi/tests/example/test_c_ffi_abi.cc          |   2 +-
 ffi/tests/example/test_error.cc              |  19 ++-
 16 files changed, 945 insertions(+), 194 deletions(-)

diff --git a/ffi/CMakeLists.txt b/ffi/CMakeLists.txt
index 532922bf14..7c830f8a0f 100644
--- a/ffi/CMakeLists.txt
+++ b/ffi/CMakeLists.txt
@@ -4,53 +4,34 @@ project(
   tvm_ffi
   VERSION 1.0
   DESCRIPTION "TVM's FFI system"
-  LANGUAGES CXX
+  LANGUAGES CXX C
 )
 
-option(TVM_FFI_ALLOW_DYN_TYPE "Support for objects with non-static type 
indices. When turned on, targets linked against `tvm_ffi` will allow objects 
that comes with non-pre-defined type indices, so that the object hierarchy 
could expand without limitation. This will require the downstream targets to 
link against target `tvm_ffi_registry` to be effective." OFF)
 option(TVM_FFI_BUILD_TESTS "Adding test targets." OFF)
 
+########## NOTE: all options below are related to dynamic registry #####
+option(TVM_FFI_BUILD_REGISTRY
+  "Support for objects with non-static type indices. When turned on, \
+  targets linked against `tvm_ffi` will allow objects that comes with 
non-pre-defined type indices, \
+  as well as getting full stacktrace during debugging. \
+  so that the object hierarchy could expand without limitation. \
+  This will require the downstream targets to link against target 
`tvm_ffi_registry` to be effective."
+  OFF
+)
+option(TVM_FFI_USE_LIBBRACKTRACE "Enable libbacktrace" ON)
+option(TVM_FFI_BACKTRACE_ON_SEGFAULT "Set signal handler to print traceback on 
segfault" ON)
+option(TVM_FFI_ALLOW_DYN_TYPE "Wehthert to allow dynamic features" ON)
+
 include(cmake/Utils/CxxWarning.cmake)
 include(cmake/Utils/Sanitizer.cmake)
+include(cmake/Utils/Library.cmake)
+include(cmake/Utils/AddLibbacktrace.cmake)
 
 ########## Target: `dlpack_header` ##########
 
 add_library(dlpack_header INTERFACE)
 target_include_directories(dlpack_header INTERFACE 
"${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/dlpack/include")
 
-########## Target: `tvm_ffi_registry_{objs|static|shared}` ##########
-
-add_library(
-  tvm_ffi_registry_objs
-  EXCLUDE_FROM_ALL
-  OBJECT "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/registry.cc"
-)
-set_target_properties(
-  tvm_ffi_registry_objs PROPERTIES
-  POSITION_INDEPENDENT_CODE ON
-  CXX_STANDARD 17
-  CXX_EXTENSIONS OFF
-  CXX_VISIBILITY_PRESET hidden
-  VISIBILITY_INLINES_HIDDEN ON
-)
-add_cxx_warning(tvm_ffi_registry_objs)
-target_link_libraries(tvm_ffi_registry_objs PRIVATE dlpack_header)
-target_include_directories(tvm_ffi_registry_objs PRIVATE 
"${CMAKE_CURRENT_SOURCE_DIR}/include")
-target_compile_definitions(tvm_ffi_registry_objs PRIVATE 
TVM_FFI_ALLOW_DYN_TYPE=1)
-if (MSVC)
-  target_compile_definitions(tvm_ffi_registry_objs PRIVATE TVM_FFI_EXPORTS)
-endif()
-
-add_library(tvm_ffi_registry_static EXCLUDE_FROM_ALL STATIC 
$<TARGET_OBJECTS:tvm_ffi_registry_objs>)
-add_library(tvm_ffi_registry_shared EXCLUDE_FROM_ALL SHARED 
$<TARGET_OBJECTS:tvm_ffi_registry_objs>)
-set_target_properties(tvm_ffi_registry_shared tvm_ffi_registry_static 
tvm_ffi_registry_objs
-    PROPERTIES
-    MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
-    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
-    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
-    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
-)
-
 ########## Target: `tvm_ffi` ##########
 
 add_library(tvm_ffi INTERFACE)
@@ -58,14 +39,61 @@ target_link_libraries(tvm_ffi INTERFACE dlpack_header)
 target_compile_features(tvm_ffi INTERFACE cxx_std_17)
 target_include_directories(tvm_ffi INTERFACE 
"${CMAKE_CURRENT_SOURCE_DIR}/include")
 
-if (TVM_FFI_ALLOW_DYN_TYPE)
+if (TVM_FFI_USE_LIBBRACKTRACE)
   message(STATUS "Setting C++ macro TVM_FFI_ALLOW_DYN_TYPE - 1")
   target_compile_definitions(tvm_ffi INTERFACE TVM_FFI_ALLOW_DYN_TYPE=1)
 else()
-  message(STATUS "Setting C++ macro TVM_FFI_ALLOW_DYN_TYPE - 0")
+  message(STATUS "Setting C++ macro TVM_FFI_ALLOW_DYN_TYPES - 0")
   target_compile_definitions(tvm_ffi INTERFACE TVM_FFI_ALLOW_DYN_TYPE=0)
 endif()
 
+########## Target: `tvm_ffi_registry` ##########
+
+if (TVM_FFI_BUILD_REGISTRY)
+  add_library(tvm_ffi_registry_objs OBJECT
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/traceback.cc"
+    "${CMAKE_CURRENT_SOURCE_DIR}/src/ffi/object.cc"
+  )
+  set_target_properties(
+    tvm_ffi_registry_objs PROPERTIES
+    POSITION_INDEPENDENT_CODE ON
+    CXX_STANDARD 17
+    CXX_EXTENSIONS OFF
+    CXX_STANDARD_REQUIRED ON
+    CXX_VISIBILITY_PRESET hidden
+    VISIBILITY_INLINES_HIDDEN ON
+    PREFIX "lib"
+  )
+  add_cxx_warning(tvm_ffi_registry_objs)
+  target_link_libraries(tvm_ffi_registry_objs PRIVATE dlpack_header)
+  target_include_directories(tvm_ffi_registry_objs PRIVATE 
"${CMAKE_CURRENT_SOURCE_DIR}/include")
+
+  if (TVM_FFI_USE_LIBBRACKTRACE)
+    message(STATUS "Setting C++ macro TVM_FFI_USE_LIBBRACKTRACE - 1")
+    target_compile_definitions(tvm_ffi_registry_objs PRIVATE 
TVM_FFI_USE_LIBBRACKTRACE=1)
+  else()
+    message(STATUS "Setting C++ macro TVM_FFI_USE_LIBBRACKTRACE - 0")
+    target_compile_definitions(tvm_ffi_registry_objs PRIVATE 
TVM_FFI_USE_LIBBRACKTRACE=0)
+  endif()
+
+  if (TVM_FFI_BACKTRACE_ON_SEGFAULT)
+    message(STATUS "Setting C++ macro TVM_FFI_BACKTRACE_ON_SEGFAULT - 1")
+    target_compile_definitions(tvm_ffi_registry_objs PRIVATE 
TVM_FFI_BACKTRACE_ON_SEGFAULT=1)
+  else()
+    message(STATUS "Setting C++ macro TVM_FFI_BACKTRACE_ON_SEGFAULT - 0")
+    target_compile_definitions(tvm_ffi_registry_objs PRIVATE 
TVM_FFI_BACKTRACE_ON_SEGFAULT=0)
+  endif()
+
+  add_target_from_obj(tvm_ffi_registry tvm_ffi_registry_objs)
+  if (TARGET libbacktrace)
+    target_link_libraries(tvm_ffi_registry_objs PRIVATE libbacktrace)
+    target_link_libraries(tvm_ffi_registry_shared PRIVATE libbacktrace)
+    target_link_libraries(tvm_ffi_registry_static PRIVATE libbacktrace)
+  endif ()
+  install(TARGETS tvm_ffi_registry_static DESTINATION "lib/")
+  install(TARGETS tvm_ffi_registry_shared DESTINATION "lib/")
+endif (TVM_FFI_BUILD_REGISTRY)
+
 ########## Adding tests ##########
 
 if (${PROJECT_NAME} STREQUAL ${CMAKE_PROJECT_NAME})
diff --git a/ffi/cmake/Utils/AddLibbacktrace.cmake 
b/ffi/cmake/Utils/AddLibbacktrace.cmake
new file mode 100644
index 0000000000..a837ca7b3f
--- /dev/null
+++ b/ffi/cmake/Utils/AddLibbacktrace.cmake
@@ -0,0 +1,52 @@
+include(ExternalProject)
+
+function(_libbacktrace_compile)
+  set(_libbacktrace_source 
${CMAKE_CURRENT_LIST_DIR}/../../../3rdparty/libbacktrace)
+  set(_libbacktrace_prefix ${CMAKE_CURRENT_BINARY_DIR}/libbacktrace)
+  if(CMAKE_SYSTEM_NAME MATCHES "Darwin" AND (CMAKE_C_COMPILER MATCHES 
"^/Library" OR CMAKE_C_COMPILER MATCHES "^/Applications"))
+    set(_cmake_c_compiler "/usr/bin/cc")
+  else()
+    set(_cmake_c_compiler "${CMAKE_C_COMPILER}")
+  endif()
+
+  message(STATUS CMAKC_C_COMPILER="${CMAKE_C_COMPILER}")
+
+  file(MAKE_DIRECTORY ${_libbacktrace_prefix}/include)
+  file(MAKE_DIRECTORY ${_libbacktrace_prefix}/lib)
+  ExternalProject_Add(project_libbacktrace
+    PREFIX libbacktrace
+    SOURCE_DIR ${_libbacktrace_source}
+    BINARY_DIR ${_libbacktrace_prefix}
+    CONFIGURE_COMMAND
+      ${_libbacktrace_source}/configure
+      "--prefix=${_libbacktrace_prefix}"
+      "--with-pic"
+      "CC=${_cmake_c_compiler}"
+      "CPP=${_cmake_c_compiler} -E"
+      "CFLAGS=${CMAKE_C_FLAGS}"
+      "LDFLAGS=${CMAKE_EXE_LINKER_FLAGS}"
+      "NM=${CMAKE_NM}"
+      "STRIP=${CMAKE_STRIP}"
+      "--host=${MACHINE_NAME}"
+    BUILD_COMMAND make -j
+    BUILD_BYPRODUCTS ${_libbacktrace_prefix}/lib/libbacktrace.a 
${_libbacktrace_prefix}/include/backtrace.h
+    INSTALL_DIR ${_libbacktrace_prefix}
+    INSTALL_COMMAND make install
+    LOG_CONFIGURE ON
+    LOG_INSTALL ON
+    LOG_BUILD ON
+    LOG_OUTPUT_ON_FAILURE ON
+  )
+  ExternalProject_Add_Step(project_libbacktrace checkout DEPENDERS configure 
DEPENDEES download)
+  set_target_properties(project_libbacktrace PROPERTIES EXCLUDE_FROM_ALL TRUE)
+  add_library(libbacktrace STATIC IMPORTED)
+  add_dependencies(libbacktrace project_libbacktrace)
+  set_target_properties(libbacktrace PROPERTIES
+    IMPORTED_LOCATION ${_libbacktrace_prefix}/lib/libbacktrace.a
+    INTERFACE_INCLUDE_DIRECTORIES ${_libbacktrace_prefix}/include
+  )
+endfunction()
+
+if(NOT MSVC)
+  _libbacktrace_compile()
+endif()
diff --git a/ffi/cmake/Utils/Library.cmake b/ffi/cmake/Utils/Library.cmake
new file mode 100644
index 0000000000..fe8c48a52c
--- /dev/null
+++ b/ffi/cmake/Utils/Library.cmake
@@ -0,0 +1,28 @@
+function(add_target_from_obj target_name obj_target_name)
+  add_library(${target_name}_static STATIC 
$<TARGET_OBJECTS:${obj_target_name}>)
+  set_target_properties(
+    ${target_name}_static PROPERTIES
+    OUTPUT_NAME "${target_name}_static"
+    PREFIX "lib"
+    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+    )
+  add_library(${target_name}_shared SHARED 
$<TARGET_OBJECTS:${obj_target_name}>)
+  set_target_properties(
+    ${target_name}_shared PROPERTIES
+    OUTPUT_NAME "${target_name}"
+    PREFIX "lib"
+    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+  )
+  add_custom_target(${target_name})
+  add_dependencies(${target_name} ${target_name}_static ${target_name}_shared)
+  if (MSVC)
+    target_compile_definitions(${obj_target_name} PRIVATE TVM_FFI_EXPORTS)
+    set_target_properties(
+      ${obj_target_name} ${target_name}_shared ${target_name}_static
+      PROPERTIES
+      MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
+    )
+  endif()
+endfunction()
diff --git a/ffi/include/tvm/ffi/c_ffi_abi.h b/ffi/include/tvm/ffi/c_api.h
similarity index 85%
rename from ffi/include/tvm/ffi/c_ffi_abi.h
rename to ffi/include/tvm/ffi/c_api.h
index 34d25bf17b..2616ae1648 100644
--- a/ffi/include/tvm/ffi/c_ffi_abi.h
+++ b/ffi/include/tvm/ffi/c_api.h
@@ -18,28 +18,40 @@
  */
 
 /*
- * \file tvm/ffi/c_ffi_abi.h
- * \brief This file defines the ABI convention of the FFI convention
- *
- * \note This file only include data structures that can be used in
- *  a header only way. The APIs are defined in c_ffi_api.h
- *  and requires linking to tvm_ffi library.
+ * \file tvm/ffi/c_api.h
+ * \brief This file defines the C convention of the FFI convention
  *
  *  Only use the APIs when TVM_FFI_ALLOW_DYN_TYPE is set to true
  */
-#ifndef TVM_FFI_C_FFI_ABI_H_
-#define TVM_FFI_C_FFI_ABI_H_
+#ifndef TVM_FFI_C_API_H_
+#define TVM_FFI_C_API_H_
 
 #include <dlpack/dlpack.h>
 #include <stdint.h>
 
 /*!
  * \brief Macro defines whether we enable dynamic runtime features.
+ * \note Turning this one would mean that we need to link 
tvm_ffi_registry_shared
  */
 #ifndef TVM_FFI_ALLOW_DYN_TYPE
 #define TVM_FFI_ALLOW_DYN_TYPE 1
 #endif
 
+#if !defined(TVM_FFI_DLL) && defined(__EMSCRIPTEN__)
+#include <emscripten/emscripten.h>
+#define TVM_FFI_DLL EMSCRIPTEN_KEEPALIVE
+#endif
+#if !defined(TVM_FFI_DLL) && defined(_MSC_VER)
+#ifdef TVM_FFI_EXPORTS
+#define TVM_FFI_DLL __declspec(dllexport)
+#else
+#define TVM_FFI_DLL __declspec(dllimport)
+#endif
+#endif
+#ifndef TVM_FFI_DLL
+#define TVM_FFI_DLL __attribute__((visibility("default")))
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -130,16 +142,7 @@ typedef struct {
   const char* bytes;
 } TVMFFIByteArray;
 
-/*! \brief The error type. */
-typedef struct {
-  /*! \brief header */
-  TVMFFIObject header_;
-
-
-} TVMFFIError;
-
-
 #ifdef __cplusplus
 }  // TVM_FFI_EXTERN_C
 #endif
-#endif  // TVM_FFI_C_FFI_ABI_H_
+#endif  // TVM_FFI_C_API_H_
diff --git a/ffi/include/tvm/ffi/c_ffi_api.h b/ffi/include/tvm/ffi/c_ffi_api.h
deleted file mode 100644
index dda54f0032..0000000000
--- a/ffi/include/tvm/ffi/c_ffi_api.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/*
- * \file tvm/ffi/c_ffi_abi.h
- * \brief This file defines the ABI convention of the FFI convention
- *
- * Including global calling conventions
- */
-#ifndef TVM_FFI_C_FFI_ABI_H_
-#define TVM_FFI_C_FFI_ABI_H_
-
-#include <tvm/ffi/c_ffi_abi.h>
-
-#if !defined(TVM_FFI_DLL) && defined(__EMSCRIPTEN__)
-#include <emscripten/emscripten.h>
-#define TVM_FFI_API EMSCRIPTEN_KEEPALIVE
-#endif
-#if !defined(TVM_FFI_DLL) && defined(_MSC_VER)
-#ifdef TVM_FFI_EXPORTS
-#define TVM_FFI_DLL __declspec(dllexport)
-#else
-#define TVM_FFI_DLL __declspec(dllimport)
-#endif
-#endif
-#ifndef TVM_FFI_DLL
-#define TVM_FFI_DLL __attribute__((visibility("default")))
-#endif
-
-#ifdef __cplusplus
-static_assert(
-  TVM_FFI_ALLOW_DYN_TYPE,
-  "Only include c_ffi_abi when TVM_FFI_ALLOW_DYN_TYPE is set to true"
-);
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#ifdef __cplusplus
-}  // TVM_FFI_EXTERN_C
-#endif
-#endif  // TVM_FFI_C_FFI_ABI_H_
diff --git a/ffi/include/tvm/ffi/error.h b/ffi/include/tvm/ffi/error.h
index 7b40f00bf9..250206ac8b 100644
--- a/ffi/include/tvm/ffi/error.h
+++ b/ffi/include/tvm/ffi/error.h
@@ -24,11 +24,30 @@
 #ifndef TVM_FFI_ERROR_H_
 #define TVM_FFI_ERROR_H_
 
-#include <tvm/ffi/object.h>
+#include <tvm/ffi/c_api.h>
+#include <tvm/ffi/internal_utils.h>
 #include <tvm/ffi/memory.h>
+#include <tvm/ffi/object.h>
 
-#include <string>
+#include <iostream>
+#include <memory>
 #include <sstream>
+#include <string>
+
+/*!
+ * \brief Macro defines whether we enable libbacktrace
+ */
+#ifndef TVM_FFI_USE_LIBBACKTRACE
+#define TVM_FFI_USE_LIBBACKTRACE 1
+#endif
+
+/*!
+ * \brief Macro defines whether to install signal handler
+ *   and print backtrace during segfault
+ */
+#ifndef TVM_FFI_BACKTRACE_ON_SEGFAULT
+#define TVM_FFI_BACKTRACE_ON_SEGFAULT 1
+#endif
 
 namespace tvm {
 namespace ffi {
@@ -83,41 +102,152 @@ namespace details {
 
 class ErrorBuilder {
  public:
-  explicit ErrorBuilder(const char* kind, const char* filename, const char* 
func, int32_t lineno)
-      : kind_(kind) {
-    std::ostringstream backtrace;
-    // python style backtrace
-    backtrace << "  " << filename << ", line " << lineno << ", in " << func << 
'\n';
-    backtrace_ = backtrace.str();
-  }
+  explicit ErrorBuilder(std::string kind, std::string traceback, bool 
log_before_throw)
+      : kind_(kind), traceback_(traceback), 
log_before_throw_(log_before_throw) {}
 
 // MSVC disable warning in error builder as it is exepected
 #ifdef _MSC_VER
 #pragma disagnostic push
 #pragma warning(disable : 4722)
 #endif
+  // avoid inline to reduce binary size, error throw path do not need to be 
fast
   [[noreturn]] ~ErrorBuilder() noexcept(false) {
-    throw ::tvm::ffi::Error(std::move(kind_), message_.str(), 
std::move(backtrace_));
+    ::tvm::ffi::Error error(std::move(kind_), stream_.str(), 
std::move(traceback_));
+    if (log_before_throw_) {
+      std::cerr << error.what();
+    }
+    throw error;
   }
 #ifdef _MSC_VER
 #pragma disagnostic pop
 #endif
 
-  std::ostringstream &Get() { return message_; }
+  std::ostringstream& stream() { return stream_; }
 
-protected:
+ protected:
   std::string kind_;
-  std::ostringstream message_;
-  std::string backtrace_;
+  std::ostringstream stream_;
+  std::string traceback_;
+  bool log_before_throw_;
 };
+
+// Code section that depends on dynamic components that requires linking
+#if TVM_FFI_ALLOW_DYN_TYPE
+/*!
+ * \brief Get stack traceback in a string.
+ * \param filaname The current file name.
+ * \param func The current function
+ * \param lineno The current line number
+ * \return The traceback string
+ *
+ * \note filename func and lino are only used as a backup info, most cases 
they are not needed.
+ *  The return value is set to const char* to be more compatible across dll 
boundaries.
+ */
+TVM_FFI_DLL const char* Traceback(const char* filename, const char* func, int 
lineno);
+// define traceback here as call into traceback function
+#define TVM_FFI_TRACEBACK_HERE ::tvm::ffi::details::Traceback(__FILE__, 
TVM_FFI_FUNC_SIG, __LINE__)
+
+#else
+// simple traceback when allow dyn type is set to false
+inline std::string SimpleTraceback(const char* filename, const char* func, int 
lineno) {
+  std::ostringstream traceback;
+  // python style backtrace
+  traceback << "  " << filename << ", line " << lineno << ", in " << func << 
'\n';
+  return traceback.str();
+}
+#define TVM_FFI_TRACEBACK_HERE \
+  ::tvm::ffi::details::SimpleTraceback(__FILE__, TVM_FFI_FUNC_SIG, __LINE__)
+#endif  // TVM_FFI_ALLOW_DYN_TYPE
 }  // namespace details
 
 /*!
- * \brief Helper macro to throw an error with backtrace and message
+ * \brief Helper macro to throw an error with traceback and message
+ *
+ * \code
+ *
+ *   void ThrowError() {
+ *     TVM_FFI_THROW(RuntimeError) << "error message";
+ *   }
+ *
+ * \endcode
  */
 #define TVM_FFI_THROW(ErrorKind) \
-  ::tvm::ffi::details::ErrorBuilder(#ErrorKind, __FILE__, TVM_FFI_FUNC_SIG, 
__LINE__).Get()
+  ::tvm::ffi::details::ErrorBuilder(#ErrorKind, TVM_FFI_TRACEBACK_HERE, 
false).stream()
+
+/*!
+ * \brief Explicitly log error in stderr and then throw the error.
+ *
+ * \note This is only necessary on startup functions where we know error
+ *  cannot be caught, and it is better to have a clear log message.
+ *  In most cases, we should use use TVM_FFI_THROW.
+ */
+#define TVM_FFI_LOG_AND_THROW(ErrorKind) \
+  ::tvm::ffi::details::ErrorBuilder(#ErrorKind, TVM_FFI_TRACEBACK_HERE, 
true).stream()
+
+// Glog style checks with TVM_FFI prefix
+// NOTE: we explicitly avoid glog style generic macros (LOG/CHECK) in tvm ffi
+// to avoid potential conflict of downstream users who might have their own 
GLOG style macros
+namespace details {
+
+template <typename X, typename Y>
+TVM_FFI_INLINE std::unique_ptr<std::string> LogCheckFormat(const X& x, const 
Y& y) {
+  std::ostringstream os;
+  os << " (" << x << " vs. " << y << ") ";  // CHECK_XX(x, y) requires x and y 
can be serialized to
+                                            // string. Use CHECK(x OP y) 
otherwise.
+  return std::make_unique<std::string>(os.str());
+}
+
+#define TVM_FFI_CHECK_FUNC(name, op)                                           
        \
+  template <typename X, typename Y>                                            
        \
+  TVM_FFI_INLINE std::unique_ptr<std::string> LogCheck##name(const X& x, const 
Y& y) { \
+    if (x op y) return nullptr;                                                
        \
+    return LogCheckFormat(x, y);                                               
        \
+  }                                                                            
        \
+  TVM_FFI_INLINE std::unique_ptr<std::string> LogCheck##name(int x, int y) {   
        \
+    return LogCheck##name<int, int>(x, y);                                     
        \
+  }
+
+// Inline _Pragma in macros does not work reliably on old version of MSVC and
+// GCC. We wrap all comparisons in a function so that we can use #pragma to
+// silence bad comparison warnings.
+#if defined(__GNUC__) || defined(__clang__)  // GCC and Clang
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#elif defined(_MSC_VER)  // MSVC
+#pragma warning(push)
+#pragma warning(disable : 4389)  // '==' : signed/unsigned mismatch
+#endif
+
+TVM_FFI_CHECK_FUNC(_LT, <)
+TVM_FFI_CHECK_FUNC(_GT, >)
+TVM_FFI_CHECK_FUNC(_LE, <=)
+TVM_FFI_CHECK_FUNC(_GE, >=)
+TVM_FFI_CHECK_FUNC(_EQ, ==)
+TVM_FFI_CHECK_FUNC(_NE, !=)
+
+#if defined(__GNUC__) || defined(__clang__)  // GCC and Clang
+#pragma GCC diagnostic pop
+#elif defined(_MSC_VER)  // MSVC
+#pragma warning(pop)
+#endif
+}  // namespace details
 
+#define TVM_FFI_ICHECK_BINARY_OP(name, op, x, y)                        \
+  if (auto __tvm__log__err = ::tvm::ffi::details::LogCheck##name(x, y)) \
+  TVM_FFI_THROW(InternalError) << "Check failed: " << #x " " #op " " #y << 
*__tvm__log__err << ": "
+
+#define TVM_FFI_ICHECK(x) \
+  if (!(x)) TVM_FFI_THROW(InternalError) << "Check failed: (" #x << ") is 
false: "
+
+#define TVM_FFI_ICHECK_LT(x, y) TVM_FFI_ICHECK_BINARY_OP(_LT, <, x, y)
+#define TVM_FFI_ICHECK_GT(x, y) TVM_FFI_ICHECK_BINARY_OP(_GT, >, x, y)
+#define TVM_FFI_ICHECK_LE(x, y) TVM_FFI_ICHECK_BINARY_OP(_LE, <=, x, y)
+#define TVM_FFI_ICHECK_GE(x, y) TVM_FFI_ICHECK_BINARY_OP(_GE, >=, x, y)
+#define TVM_FFI_ICHECK_EQ(x, y) TVM_FFI_ICHECK_BINARY_OP(_EQ, ==, x, y)
+#define TVM_FFI_ICHECK_NE(x, y) TVM_FFI_ICHECK_BINARY_OP(_NE, !=, x, y)
+#define TVM_FFI_ICHECK_NOTNULL(x)                                              
   \
+  ((x) == nullptr ? TVM_FFI_THROW(InternalError) << "Check not null: " #x << ' 
', \
+   (x)            : (x))  // NOLINT(*)
 }  // namespace ffi
 }  // namespace tvm
 #endif  // TVM_FFI_ERROR_H_
diff --git a/ffi/include/tvm/ffi/internal_utils.h 
b/ffi/include/tvm/ffi/internal_utils.h
index 5eca030028..900b7fd2e2 100644
--- a/ffi/include/tvm/ffi/internal_utils.h
+++ b/ffi/include/tvm/ffi/internal_utils.h
@@ -18,12 +18,12 @@
  */
 /*!
  * \file tvm/ffi/internal_utils.h
- * \brief Utility functions and macros for internal use, not meant for
+ * \brief Utility functions and macros for internal use
  */
 #ifndef TVM_FFI_INTERNAL_UTILS_H_
 #define TVM_FFI_INTERNAL_UTILS_H_
 
-#include <tvm/ffi/c_ffi_abi.h>
+#include <tvm/ffi/c_api.h>
 
 #include <cstddef>
 
@@ -33,6 +33,17 @@
 #define TVM_FFI_INLINE inline __attribute__((always_inline))
 #endif
 
+/*!
+ * \brief Macro helper to force a function not to be inlined.
+ * It is only used in places that we know not inlining is good,
+ * e.g. some logging functions.
+ */
+#if defined(_MSC_VER)
+#define TVM_FFI_NO_INLINE __declspec(noinline)
+#else
+#define TVM_FFI_NO_INLINE __attribute__((noinline))
+#endif
+
 #if defined(_MSC_VER)
 #define TVM_FFI_UNREACHABLE() __assume(false)
 #else
diff --git a/ffi/include/tvm/ffi/object.h b/ffi/include/tvm/ffi/object.h
index 037b08bbca..03ba7d7dbf 100644
--- a/ffi/include/tvm/ffi/object.h
+++ b/ffi/include/tvm/ffi/object.h
@@ -23,9 +23,12 @@
 #ifndef TVM_FFI_OBJECT_H_
 #define TVM_FFI_OBJECT_H_
 
-#include <tvm/ffi/c_ffi_abi.h>
+#include <tvm/ffi/c_api.h>
 #include <tvm/ffi/internal_utils.h>
 
+#include <type_traits>
+#include <utility>
+
 namespace tvm {
 namespace ffi {
 
@@ -65,8 +68,8 @@ struct ObjectInternal;
  *
  * - _type_child_slots_can_overflow:
  *       Whether we can add additional child classes even if the number of 
child classes
- *       exceeds the _type_child_slots. A fallback mechanism to check global 
type table will be
- * used. Recommendation: set to false for optimal runtime speed if we know 
exact number of children.
+ *       exceeds the _type_child_slots. A fallback mechanism to check type 
table will be used.
+ *       Recommendation: set to false for optimal runtime speed if we know 
exact number of children.
  *
  * Two macros are used to declare helper functions in the object:
  * - Use TVM_FFI_DECLARE_BASE_OBJECT_INFO for object classes that can be 
sub-classed.
@@ -96,7 +99,7 @@ class Object {
   // NOTE: the following field is not type index of Object
   // but was intended to be used by sub-classes as default value.
   // The type index of Object is TypeIndex::kRoot
-  static constexpr int32_t _type_index = TypeIndex::kTVMFFIDynObject;
+  static constexpr int32_t _type_index = TypeIndex::kTVMFFIObject;
 
   // The following functions are provided by macro
   // TVM_FFI_DECLARE_BASE_OBJECT_INFO and TVM_DECLARE_FINAL_OBJECT_INFO
@@ -264,17 +267,6 @@ class ObjectPtr {
       data_->IncRef();
     }
   }
-  /*!
-   * \brief Move an ObjectPtr from an RValueRef argument.
-   * \param ref The rvalue reference.
-   * \return the moved result.
-   */
-  static ObjectPtr<T> MoveFromRValueRefArg(Object** ref) {
-    ObjectPtr<T> ptr;
-    ptr.data_ = *ref;
-    *ref = nullptr;
-    return ptr;
-  }
   // friend classes
   friend class Object;
   friend class ObjectRef;
@@ -380,56 +372,45 @@ class ObjectRef {
 template <typename BaseType, typename ObjectType>
 inline ObjectPtr<BaseType> GetObjectPtr(ObjectType* ptr);
 
-/*! \brief ObjectRef hash functor */
-struct ObjectPtrHash {
-  size_t operator()(const ObjectRef& a) const { return operator()(a.data_); }
-
-  template <typename T>
-  size_t operator()(const ObjectPtr<T>& a) const {
-    return std::hash<Object*>()(a.get());
-  }
-};
-
-/*! \brief ObjectRef equal functor */
-struct ObjectPtrEqual {
-  bool operator()(const ObjectRef& a, const ObjectRef& b) const { return 
a.same_as(b); }
-
-  template <typename T>
-  size_t operator()(const ObjectPtr<T>& a, const ObjectPtr<T>& b) const {
-    return a == b;
-  }
-};
-
 /*!
- * \brief helper macro to declare a base object type that can be inherited.
+ * \brief Helper macro to declare list of static checks about object meta-data.
  * \param TypeName The name of the current type.
  * \param ParentType The name of the ParentType
  */
-#define TVM_FFI_DECLARE_STATIC_OBJECT_INFO(TypeName, ParentType)               
             \
-  static_assert(!ParentType::_type_final, "ParentObj marked as final");        
             \
-  static int32_t RuntimeTypeIndex() {                                          
             \
-    static_assert(TypeName::_type_child_slots == 0 || 
ParentType::_type_child_slots == 0 || \
-                      TypeName::_type_child_slots < 
ParentType::_type_child_slots,          \
-                  "Need to set _type_child_slots when parent specifies it.");  
             \
-    static_assert(TypeName::_type_child_slots == 0 || 
ParentType::_type_child_slots == 0 || \
-                      TypeName::_type_child_slots < 
ParentType::_type_child_slots,          \
-                  "Need to set _type_child_slots when parent specifies it.");  
             \
-    static_assert(TypeName::_type_index != TypeIndex::kTVMFFIDynObject,        
             \
-                  "Static object cannot have dynamic type index.");            
             \
-    return TypeName::_type_index;                                              
             \
-  }
+#define TVM_FFI_OBJECT_STATIC_CHECKS(TypeName, ParentType)                     
           \
+  static_assert(!ParentType::_type_final, "ParentType marked as final");       
           \
+  static_assert(TypeName::_type_child_slots == 0 || 
ParentType::_type_child_slots == 0 || \
+                    TypeName::_type_child_slots < 
ParentType::_type_child_slots,          \
+                "Need to set _type_child_slots when parent specifies it.");    
           \
+  static_assert(TypeName::_type_child_slots == 0 || 
ParentType::_type_child_slots == 0 || \
+                    TypeName::_type_child_slots < 
ParentType::_type_child_slots,          \
+                "Need to set _type_child_slots when parent specifies it.");
 
-#define TVM_FFI_OBJECT_REG_VAR_DEF static TVM_ATTRIBUTE_UNUSED uint32_t 
__make_Object_tid
+/*!
+ * \brief Helper macro to declare a object that comes with static type index.
+ * \param TypeName The name of the current type.
+ * \param ParentType The name of the ParentType
+ */
+#define TVM_FFI_DECLARE_STATIC_OBJECT_INFO(TypeName, ParentType) \
+  TVM_FFI_OBJECT_STATIC_CHECKS(TypeName, ParentType)             \
+  static int32_t RuntimeTypeIndex() { return TypeName::_type_index; }
 
 /*!
- * \brief Helper macro to register the object type to runtime.
- *  Makes sure that the runtime type table is correctly populated.
- *
- *  Use this macro in the cc file for each terminal class.
+ * \brief helper macro to declare a base object type that can be inherited.
+ * \param TypeName The name of the current type.
+ * \param ParentType The name of the ParentType
  */
-#define TVM_FFI_REGISTER_OBJECT_TYPE(TypeName)                  \
-  TVM_FFI_STR_CONCAT(TVM_FFI_OBJECT_REG_VAR_DEF, __COUNTER__) = \
-      TypeName::_GetOrAllocRuntimeTypeIndex()
+#define TVM_DECLARE_BASE_OBJECT_INFO(TypeName, ParentType)                     
 \
+  static_assert(TVM_FFI_ALLOW_DYN_TYPE,                                        
 \
+                "Dynamic object depend on TVM_FFI_ALLOW_DYN_TYPE cd set to 
1"); \
+  TVM_FFI_OBJECT_STATIC_CHECKS(TypaName, ParentType)                           
 \
+  static inline int32_t _type_index = _GetOrAllocRuntimeTypeIndex();           
 \
+  static int32_t RuntimeTypeIndex() { return TypeName::_type_index; }          
 \
+  static int32_t _GetOrAllocRuntimeTypeIndex() {                               
 \
+    return ::tvm::ffi::details::ObjectGetOrAllocTypeIndex(                     
 \
+        TypeName::_type_key, -1, ParentType::_GetOrAllocRuntimeTypeIndex(),    
 \
+        TypeName::_type_child_slots, 
TypeName::_type_child_slots_can_overflow); \
+  }
 
 /*
  * \brief Define object reference methods.
@@ -472,12 +453,53 @@ struct ObjectInternal {
     return &(src->header_);
   }
 
-  // create ObjectPtr from unknowned ptr
+  // Create ObjectPtr from unknowned ptr
   template <typename T>
   static TVM_FFI_INLINE ObjectPtr<T> ObjectPtrFromUnowned(Object* raw_ptr) {
     return tvm::ffi::ObjectPtr<T>(raw_ptr);
   }
+  // Create objectptr by moving from an existing address of object and setting 
its
+  // address to nullptr
+  template <typename T>
+  static TVM_FFI_INLINE ObjectPtr<T> MoveObjectPtrFromRValueRef(Object** ref) {
+    ObjectPtr<T> ptr;
+    ptr.data_ = *ref;
+    *ref = nullptr;
+    return ptr;
+  }
 };
+
+// Code section that depends on dynamic components
+#if TVM_FFI_ALLOW_DYN_TYPE
+/*!
+ * \brief Get the type index using type key.
+ *
+ *  When the function is first time called for a type,
+ *  it will register the type to the type table in the runtime.
+ *  If the static_tindex is TypeIndex::kDynamic, the function will
+ *  allocate a runtime type index.
+ *  Otherwise, we will populate the type table and return the static index.
+ *
+ * \param type_key the type key.
+ * \param static_tindex Static type index if any, can be -1, which means this 
is a dynamic index
+ * \param parent_tindex The index of the parent.
+ * \param type_child_slots Number of slots reserved for its children.
+ * \param type_child_slots_can_overflow Whether to allow child to overflow the 
slots.
+ *
+ * \return The allocated type index
+ */
+TVM_FFI_DLL int ObjectGetOrAllocTypeIndex(const char* type_key, int32_t 
static_tindex,
+                                          int32_t parent_tindex, int32_t 
type_child_slots,
+                                          bool type_child_slots_can_overflow);
+
+/*!
+ * \brief Check whether child type is derived from parent type.
+ * \param child_type_index The candidate child type index.
+ * \param parent_type_index The candidate parent type index.
+ * \return the Check result.
+ */
+TVM_FFI_DLL bool ObjectDerivedFrom(int32_t child_type_index, int32_t 
parent_type_index);
+#endif  // TVM_FFI_ALLOW_DYN_TYPE
 }  // namespace details
 }  // namespace ffi
 }  // namespace tvm
diff --git a/ffi/scripts/run_tests.sh b/ffi/scripts/run_tests.sh
index 704abaeab9..0d4efe6bf2 100755
--- a/ffi/scripts/run_tests.sh
+++ b/ffi/scripts/run_tests.sh
@@ -5,6 +5,8 @@ HEADER_ONLY=OFF
 BUILD_TYPE=RelWithDebInfo
 
 rm -rf build/CMakeFiles build/CMakeCache.txt
-cmake -G Ninja -S . -B build -DTVM_FFI_ALLOW_DYN_TYPE=${HEADER_ONLY} 
-DTVM_FFI_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=${BUILD_TYPE} 
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
+cmake -G Ninja -S . -B build -DTVM_FFI_ALLOW_DYN_TYPE=${HEADER_ONLY} 
-DTVM_FFI_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
+  -DTVM_FFI_BUILD_REGISTRY=ON \
+  -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
 cmake --build build --parallel 16 --clean-first --config ${BUILD_TYPE} 
--target tvm_ffi_tests
 GTEST_COLOR=1 ctest -V -C ${BUILD_TYPE} --test-dir build --output-on-failure
diff --git a/ffi/src/ffi/object.cc b/ffi/src/ffi/object.cc
new file mode 100644
index 0000000000..ac8e04ebb9
--- /dev/null
+++ b/ffi/src/ffi/object.cc
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * \file src/ffi/object.cc
+ * \brief Registry to record dynamic types
+ */
+#include <tvm/ffi/c_api.h>
+#include <tvm/ffi/error.h>
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace tvm {
+namespace ffi {
+
+/*! \brief Type information */
+struct TypeInfo {
+  /*! \brief The current index. */
+  int32_t index{0};
+  /*! \brief Index of the parent in the type hierarchy */
+  int32_t parent_index{0};
+  // NOTE: the indices in [index, index + num_reserved_slots) are
+  // reserved for the child-class of this type.
+  /*! \brief Total number of slots reserved for the type and its children. */
+  int32_t num_slots{0};
+  /*! \brief number of allocated child slots. */
+  int32_t allocated_slots{0};
+  /*! \brief Whether child can overflow. */
+  bool child_slots_can_overflow{true};
+  /*! \brief name of the type. */
+  std::string name;
+  /*! \brief hash of the name */
+  size_t name_hash{0};
+};
+
+/*!
+ * \brief Type context that manages the type hierarchy information.
+ *
+ * \note We do not use mutex to guard updating of TypeContext
+ *
+ * The assumption is that updating of TypeContext will be done
+ * in the main thread during initialization or loading.
+ *
+ * Then the followup code will leverage the information
+ */
+class TypeContext {
+ public:
+  // NOTE: this is a relatively slow path for child checking
+  // Most types are already checked by the fast-path via reserved slot 
checking.
+  bool DerivedFrom(int32_t child_tindex, int32_t parent_tindex) {
+    // invariance: child's type index is always bigger than its parent.
+    if (child_tindex < parent_tindex) return false;
+    if (child_tindex == parent_tindex) return true;
+    TVM_FFI_ICHECK_LT(child_tindex, type_table_.size());
+    while (child_tindex > parent_tindex) {
+      child_tindex = type_table_[child_tindex].parent_index;
+    }
+    return child_tindex == parent_tindex;
+  }
+
+  int32_t GetOrAllocRuntimeTypeIndex(const std::string& skey, int32_t 
static_tindex,
+                                     int32_t parent_tindex, int32_t 
num_child_slots,
+                                     bool child_slots_can_overflow) {
+    auto it = type_key2index_.find(skey);
+    if (it != type_key2index_.end()) {
+      return it->second;
+    }
+    // try to allocate from parent's type table.
+    TVM_FFI_ICHECK_LT(parent_tindex, type_table_.size())
+        << " skey=" << skey << ", static_index=" << static_tindex;
+
+    TypeInfo& pinfo = type_table_[parent_tindex];
+    TVM_FFI_ICHECK_EQ(pinfo.index, parent_tindex);
+
+    // if parent cannot overflow, then this class cannot.
+    if (!pinfo.child_slots_can_overflow) {
+      child_slots_can_overflow = false;
+    }
+
+    // total number of slots include the type itself.
+    int32_t num_slots = num_child_slots + 1;
+    int32_t allocated_tindex;
+
+    if (static_tindex > 0) {
+      // statically assigned type
+      allocated_tindex = static_tindex;
+      TVM_FFI_ICHECK_LT(static_tindex, type_table_.size());
+      TVM_FFI_ICHECK_EQ(type_table_[allocated_tindex].allocated_slots, 0U)
+          << "Conflicting static index " << static_tindex << " between "
+          << type_table_[allocated_tindex].name << " and " << skey;
+    } else if (pinfo.allocated_slots + num_slots <= pinfo.num_slots) {
+      // allocate the slot from parent's reserved pool
+      allocated_tindex = parent_tindex + pinfo.allocated_slots;
+      // update parent's state
+      pinfo.allocated_slots += num_slots;
+    } else {
+      TVM_FFI_ICHECK(pinfo.child_slots_can_overflow)
+          << "Reach maximum number of sub-classes for " << pinfo.name;
+      // allocate new entries.
+      allocated_tindex = type_counter_;
+      type_counter_ += num_slots;
+      TVM_FFI_ICHECK_LE(type_table_.size(), type_counter_);
+      type_table_.resize(type_counter_, TypeInfo());
+    }
+    TVM_FFI_ICHECK_GT(allocated_tindex, parent_tindex);
+    // initialize the slot.
+    type_table_[allocated_tindex].index = allocated_tindex;
+    type_table_[allocated_tindex].parent_index = parent_tindex;
+    type_table_[allocated_tindex].num_slots = num_slots;
+    type_table_[allocated_tindex].allocated_slots = 1;
+    type_table_[allocated_tindex].child_slots_can_overflow = 
child_slots_can_overflow;
+    type_table_[allocated_tindex].name = skey;
+    type_table_[allocated_tindex].name_hash = std::hash<std::string>()(skey);
+    // update the key2index mapping.
+    type_key2index_[skey] = allocated_tindex;
+    return allocated_tindex;
+  }
+
+  const std::string& TypeIndex2Key(int32_t tindex) {
+    if (tindex != 0) {
+      // always return the right type key for root
+      // for non-root type nodes, allocated slots should not equal 0
+      TVM_FFI_ICHECK(tindex < static_cast<int32_t>(type_table_.size()) &&
+                     type_table_[tindex].allocated_slots != 0)
+          << "Unknown type index " << tindex;
+    }
+    return type_table_[tindex].name;
+  }
+
+  size_t TypeIndex2KeyHash(int32_t tindex) {
+    TVM_FFI_ICHECK(tindex < static_cast<int32_t>(type_table_.size()) &&
+                   type_table_[tindex].allocated_slots != 0)
+        << "Unknown type index " << tindex;
+    return type_table_[tindex].name_hash;
+  }
+
+  int32_t TypeKey2Index(const std::string& skey) {
+    auto it = type_key2index_.find(skey);
+    TVM_FFI_ICHECK(it != type_key2index_.end()) << "Cannot find type " << skey;
+    return it->second;
+  }
+
+  void Dump(int min_children_count) {
+    std::vector<int> num_children(type_table_.size(), 0);
+    // reverse accumulation so we can get total counts in a bottom-up manner.
+    for (auto it = type_table_.rbegin(); it != type_table_.rend(); ++it) {
+      if (it->index != 0) {
+        num_children[it->parent_index] += num_children[it->index] + 1;
+      }
+    }
+
+    for (const auto& info : type_table_) {
+      if (info.index != 0 && num_children[info.index] >= min_children_count) {
+        std::cerr << '[' << info.index << "] " << info.name
+                  << "\tparent=" << type_table_[info.parent_index].name
+                  << "\tnum_child_slots=" << info.num_slots - 1
+                  << "\tnum_children=" << num_children[info.index] << 
std::endl;
+      }
+    }
+  }
+
+  static TypeContext* Global() {
+    static TypeContext inst;
+    return &inst;
+  }
+
+ private:
+  TypeContext() {
+    type_table_.resize(TypeIndex::kTVMFFIDynObjectBegin, TypeInfo());
+    type_table_[0].name = "runtime.Object";
+  }
+
+  int32_t type_counter_{TypeIndex::kTVMFFIDynObjectBegin};
+  std::vector<TypeInfo> type_table_;
+  std::unordered_map<std::string, int32_t> type_key2index_;
+};
+
+namespace details {
+
+int32_t ObjectGetOrAllocTypeIndex(const char* type_key, int32_t static_tindex,
+                                  int32_t parent_tindex, int32_t 
type_child_slots,
+                                  bool type_child_slots_can_overflow) {
+  return tvm::ffi::TypeContext::Global()->GetOrAllocRuntimeTypeIndex(
+      type_key, static_tindex, parent_tindex, type_child_slots, 
type_child_slots_can_overflow != 0);
+}
+
+bool ObjectDerivedFrom(int32_t child_type_index, int32_t parent_type_index) {
+  return static_cast<int>(
+      tvm::ffi::TypeContext::Global()->DerivedFrom(child_type_index, 
parent_type_index));
+}
+}  // namespace details
+}  // namespace ffi
+}  // namespace tvm
diff --git a/ffi/src/ffi/registry.cc b/ffi/src/ffi/registry.cc
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/ffi/src/ffi/traceback.cc b/ffi/src/ffi/traceback.cc
new file mode 100644
index 0000000000..f2826957a2
--- /dev/null
+++ b/ffi/src/ffi/traceback.cc
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * 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.
+ */
+#ifndef _MSC_VER
+
+#include "./traceback.h"
+
+#include <tvm/ffi/c_api.h>
+#include <tvm/ffi/error.h>
+
+#if TVM_FFI_USE_LIBBACKTRACE
+
+#include <backtrace.h>
+#include <cxxabi.h>
+
+#include <cstring>
+#include <iomanip>
+#include <iostream>
+#include <mutex>
+
+#if TVM_FFI_BACKTRACE_ON_SEGFAULT
+#include <csignal>
+#endif
+
+namespace tvm {
+namespace ffi {
+namespace {
+
+void BacktraceCreateErrorCallback(void*, const char* msg, int) {
+  std::cerr << "Could not initialize backtrace state: " << msg << std::endl;
+}
+
+backtrace_state* BacktraceCreate() {
+  return backtrace_create_state(nullptr, 1, BacktraceCreateErrorCallback, 
nullptr);
+}
+
+static backtrace_state* _bt_state = BacktraceCreate();
+
+std::string DemangleName(std::string name) {
+  int status = 0;
+  size_t length = name.size();
+  std::unique_ptr<char, void (*)(void* __ptr)> demangled_name = {
+      abi::__cxa_demangle(name.c_str(), nullptr, &length, &status), 
&std::free};
+  if (demangled_name && status == 0 && length > 0) {
+    return demangled_name.get();
+  } else {
+    return name;
+  }
+}
+
+void BacktraceErrorCallback(void*, const char*, int) {
+  // do nothing
+}
+
+void BacktraceSyminfoCallback(void* data, uintptr_t pc, const char* symname, 
uintptr_t,
+                              uintptr_t symsize) {
+  auto str = reinterpret_cast<std::string*>(data);
+
+  if (symname != nullptr) {
+    std::string tmp(symname, symsize);
+    *str = DemangleName(tmp.c_str());
+  } else {
+    std::ostringstream s;
+    s << "0x" << std::setfill('0') << std::setw(sizeof(uintptr_t) * 2) << 
std::hex << pc;
+    *str = s.str();
+  }
+}
+
+int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int 
lineno,
+                          const char* symbol) {
+  auto stack_trace = reinterpret_cast<TracebackStorage*>(data);
+  std::string symbol_str = "<unknown>";
+  if (symbol) {
+    symbol_str = DemangleName(symbol);
+  } else {
+    // see if syminfo gives anything
+    backtrace_syminfo(_bt_state, pc, BacktraceSyminfoCallback, 
BacktraceErrorCallback, &symbol_str);
+  }
+  symbol = symbol_str.data();
+
+  if (stack_trace->ExceedTracebackLimit()) {
+    return 1;
+  }
+  if (ShouldExcludeFrame(filename, symbol)) {
+    return 0;
+  }
+
+  stack_trace->Append(filename, symbol, lineno);
+  return 0;
+}
+
+std::string Traceback() {
+  TracebackStorage traceback;
+
+  if (_bt_state == nullptr) {
+    return "";
+  }
+  // libbacktrace eats memory if run on multiple threads at the same time, so 
we guard against it
+  {
+    static std::mutex m;
+    std::lock_guard<std::mutex> lock(m);
+    backtrace_full(_bt_state, 0, BacktraceFullCallback, 
BacktraceErrorCallback, &traceback);
+  }
+  return traceback.GetTraceback();
+}
+
+#if TVM_FFI_BACKTRACE_ON_SEGFAULT
+void backtrace_handler(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.
+  std::cerr << "!!!!!!! TVM FFI encountered a Segfault !!!!!!!\n" << 
Traceback() << std::endl;
+
+  // Re-raise signal with default handler
+  struct sigaction act;
+  std::memset(&act, 0, sizeof(struct sigaction));
+  act.sa_flags = SA_RESETHAND;
+  act.sa_handler = SIG_DFL;
+  sigaction(sig, &act, nullptr);
+  raise(sig);
+}
+
+__attribute__((constructor)) void install_signal_handler(void) {
+  // this may override already installed signal handlers
+  std::signal(SIGSEGV, backtrace_handler);
+}
+#endif  // TVM_FFI_BACKTRACE_ON_SEGFAULT
+}  // namespace
+
+namespace details {
+const char* Traceback(const char*, const char*, int) {
+  static thread_local std::string traceback_str;
+  traceback_str = ::tvm::ffi::Traceback();
+  return traceback_str.c_str();
+}
+}  // namespace details
+}  // namespace ffi
+}  // namespace tvm
+#else
+namespace tvm {
+namespace ffi {
+namespace details {
+// fallback implementation simply print out the last trace
+const char* Traceback(const char* filename, const char* func, int lineno) {
+  static thread_local std::string traceback_str;
+  std::ostringstream traceback_stream;
+  // python style backtrace
+  traceback_stream << "  " << filename << ", line " << lineno << ", in " << 
func << '\n';
+  traceback_str = traceback_stream.str();
+  return traceback_str.c_str();
+}
+}  // namespace details
+}  // namespace ffi
+}  // namespace tvm
+#endif  // TVM_FFI_USE_LIBBACKTRACE
+#endif  // _MSC_VER
diff --git a/ffi/src/ffi/traceback.h b/ffi/src/ffi/traceback.h
new file mode 100644
index 0000000000..2bc9e523a7
--- /dev/null
+++ b/ffi/src/ffi/traceback.h
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*!
+ * \file traceback.h
+ * \brief Common headers for traceback.
+ * \note We use the term "traceback" to be consistent with python naming 
convention.
+ */
+#ifndef TVM_FFI_TRACEBACK_H_
+#define TVM_FFI_TRACEBACK_H_
+
+#include <cstring>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace tvm {
+namespace ffi {
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4996)  // std::getenv is unsafe
+#endif
+
+inline int32_t GetTracebackLimit() {
+  if (const char* env = std::getenv("TVM_TRACEBACK_LIMIT")) {
+    return std::stoi(env);
+  }
+  return 512;
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+/*!
+ * \brief List frame patterns that should be excluded as they contain less 
information
+ */
+inline bool ShouldExcludeFrame(const char* filename, const char* symbol) {
+  if (filename) {
+    // Stack frames for TVM FFI
+    if (strstr(filename, "include/tvm/ffi/error.h")) {
+      return true;
+    }
+    if (strstr(filename, "src/ffi/traceback.cc")) {
+      return true;
+    }
+    // Python interpreter stack frames
+    if (strstr(filename, "/python-") || strstr(filename, "/Python/ceval.c") ||
+        strstr(filename, "/Modules/_ctypes")) {
+      return true;
+    }
+    // C++ stdlib frames
+    if (strstr(filename, "include/c++/")) {
+      return true;
+    }
+  }
+
+  if (symbol) {
+    // C++ stdlib frames
+    if (strstr(symbol, "__libc_")) {
+      return true;
+    }
+    // Python interpreter stack frames
+    if (strstr(symbol, "_Py") == symbol || strstr(symbol, "PyObject")) {
+      return true;
+    }
+  }
+  // libffi.so stack frames.  These may also show up as numeric
+  // addresses with no symbol name.  This could be improved in the
+  // future by using dladdr() to check whether an address is contained
+  // in libffi.so
+  if (filename == nullptr && strstr(symbol, "ffi_call_")) {
+    return true;
+  }
+  return false;
+}
+
+/*!
+ * \brief storage to store traceback
+ */
+struct TracebackStorage {
+  std::vector<std::string> lines;
+  /*! \brief Maximum size of the traceback. */
+  size_t max_frame_size = GetTracebackLimit();
+
+  void Append(const char* filename, const char* func, int lineno) {
+    std::ostringstream trackeback_stream;
+    trackeback_stream << "  " << filename;
+    if (lineno != 0) {
+      trackeback_stream << ", line " << lineno;
+    }
+    trackeback_stream << ", in " << func << '\n';
+    lines.push_back(trackeback_stream.str());
+  }
+
+  bool ExceedTracebackLimit() const { return lines.size() >= 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;
+  }
+};
+
+}  // namespace ffi
+}  // namespace tvm
+
+#endif
diff --git a/ffi/tests/example/CMakeLists.txt b/ffi/tests/example/CMakeLists.txt
index a78e3ee01a..62543756b9 100644
--- a/ffi/tests/example/CMakeLists.txt
+++ b/ffi/tests/example/CMakeLists.txt
@@ -18,4 +18,8 @@ add_cxx_warning(tvm_ffi_tests)
 add_sanitizer_address(tvm_ffi_tests)
 target_link_libraries(tvm_ffi_tests PRIVATE tvm_ffi)
 
+if (TVM_FFI_BUILD_REGISTRY)
+  target_link_libraries(tvm_ffi_tests PRIVATE tvm_ffi_registry_shared)
+endif()
+
 add_googletest(tvm_ffi_tests)
diff --git a/ffi/tests/example/test_c_ffi_abi.cc 
b/ffi/tests/example/test_c_ffi_abi.cc
index 66f2dcf739..d936247653 100644
--- a/ffi/tests/example/test_c_ffi_abi.cc
+++ b/ffi/tests/example/test_c_ffi_abi.cc
@@ -1,5 +1,5 @@
 #include <gtest/gtest.h>
-#include <tvm/ffi/c_ffi_abi.h>
+#include <tvm/ffi/c_api.h>
 
 namespace {
 
diff --git a/ffi/tests/example/test_error.cc b/ffi/tests/example/test_error.cc
index a2c073aa3f..4f208b6999 100644
--- a/ffi/tests/example/test_error.cc
+++ b/ffi/tests/example/test_error.cc
@@ -19,10 +19,27 @@ TEST(Error, Traceback) {
       EXPECT_EQ(error->kind, "RuntimeError");
       std::string what = error.what();
       EXPECT_NE(what.find("line"), std::string::npos);
-      EXPECT_NE(what.find("ThrowRuntimeError()"), std::string::npos);
+      EXPECT_NE(what.find("ThrowRuntimeError"), std::string::npos);
       EXPECT_NE(what.find("RuntimeError: test0"), std::string::npos);
       throw;
     }
   }, ::tvm::ffi::Error);
 }
+
+TEST(CheckError, Traceback) {
+  EXPECT_THROW(
+      {
+        try {
+          TVM_FFI_ICHECK_GT(2, 3);
+        } catch (const Error& error) {
+          EXPECT_EQ(error->kind, "InternalError");
+          std::cout << error.what();
+          std::string what = error.what();
+          EXPECT_NE(what.find("line"), std::string::npos);
+          EXPECT_NE(what.find("2 > 3"), std::string::npos);
+          throw;
+        }
+      },
+      ::tvm::ffi::Error);
+}
 }  // namespace

Reply via email to