You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tvm.apache.org by tq...@apache.org on 2025/03/10 22:55:57 UTC
(tvm) 05/37: [FFI] Add libbacktrace support
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 <ti...@gmail.com>
AuthorDate: Tue Aug 13 08:35:18 2024 -0400
[FFI] Add libbacktrace support
Co-authored-by: Junru Shao <ju...@apache.org>
---
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