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
