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/12 13:25:26 UTC
(tvm) 01/38: [FFI] Initial Code Commit
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 73ea198a2ae749560b9a887eb7e4e55b2457f57b
Author: Junru Shao <ju...@apache.org>
AuthorDate: Wed Apr 17 09:05:27 2024 +0800
[FFI] Initial Code Commit
---
.gitmodules | 3 +
ffi/3rdparty/dlpack | 1 +
ffi/CMakeLists.txt | 78 +++++
ffi/cmake/Utils/AddGoogleTest.cmake | 42 +++
ffi/cmake/Utils/CxxWarning.cmake | 13 +
ffi/cmake/Utils/Sanitizer.cmake | 18 +
ffi/include | 1 +
ffi/scripts/run_tests.sh | 10 +
ffi/src | 1 +
ffi/tests/cpp/CMakeLists.txt | 25 ++
ffi/tests/cpp/test_any.cc | 635 ++++++++++++++++++++++++++++++++++++
ffi/tests/cpp/test_any_view.cc | 630 +++++++++++++++++++++++++++++++++++
ffi/tests/cpp/test_dict.cc | 183 +++++++++++
ffi/tests/cpp/test_func.cc | 215 ++++++++++++
ffi/tests/cpp/test_list.cc | 461 ++++++++++++++++++++++++++
ffi/tests/cpp/test_ref.cc | 293 +++++++++++++++++
ffi/tests/cpp/test_str.cc | 47 +++
ffi/tests/cpp/test_type_dyn.cc | 71 ++++
ffi/tests/cpp/test_type_static.cc | 119 +++++++
include/tvm/ffi/core/c_ffi_abi.h | 166 ++++++++++
include/tvm/ffi/core/core.h | 421 ++++++++++++++++++++++++
include/tvm/ffi/core/traits.h | 613 ++++++++++++++++++++++++++++++++++
include/tvm/ffi/core/utils.h | 281 ++++++++++++++++
include/tvm/ffi/ext/dict.h | 515 +++++++++++++++++++++++++++++
include/tvm/ffi/ext/error.h | 89 +++++
include/tvm/ffi/ext/func.h | 390 ++++++++++++++++++++++
include/tvm/ffi/ext/list.h | 457 ++++++++++++++++++++++++++
include/tvm/ffi/ext/str.h | 245 ++++++++++++++
include/tvm/ffi/ffi.hpp | 9 +
src/ffi/registry.cc | 144 ++++++++
30 files changed, 6176 insertions(+)
diff --git a/.gitmodules b/.gitmodules
index a1187967f7..e8a48d99c2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -31,3 +31,6 @@
[submodule "3rdparty/zlib"]
path = 3rdparty/zlib
url = https://github.com/madler/zlib.git
+[submodule "ffi/3rdparty/dlpack"]
+ path = ffi/3rdparty/dlpack
+ url = https://github.com/dmlc/dlpack.git
diff --git a/ffi/3rdparty/dlpack b/ffi/3rdparty/dlpack
new file mode 160000
index 0000000000..bbd2f4d324
--- /dev/null
+++ b/ffi/3rdparty/dlpack
@@ -0,0 +1 @@
+Subproject commit bbd2f4d32427e548797929af08cfe2a9cbb3cf12
diff --git a/ffi/CMakeLists.txt b/ffi/CMakeLists.txt
new file mode 100644
index 0000000000..8710a0c377
--- /dev/null
+++ b/ffi/CMakeLists.txt
@@ -0,0 +1,78 @@
+cmake_minimum_required(VERSION 3.14)
+
+project(
+ tvm_ffi
+ VERSION 1.0
+ DESCRIPTION "TVM's FFI system"
+ LANGUAGES CXX
+)
+
+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)
+
+include(cmake/Utils/CxxWarning.cmake)
+include(cmake/Utils/Sanitizer.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)
+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)
+ 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")
+ target_compile_definitions(tvm_ffi INTERFACE TVM_FFI_ALLOW_DYN_TYPE=0)
+endif()
+
+########## Adding tests ##########
+
+if (${PROJECT_NAME} STREQUAL ${CMAKE_PROJECT_NAME})
+ if (TVM_FFI_BUILD_TESTS)
+ enable_testing()
+ message(STATUS "Enable Testing")
+ include(cmake/Utils/AddGoogleTest.cmake)
+ add_subdirectory(tests/cpp/)
+ endif()
+endif ()
diff --git a/ffi/cmake/Utils/AddGoogleTest.cmake b/ffi/cmake/Utils/AddGoogleTest.cmake
new file mode 100644
index 0000000000..aec5c82b03
--- /dev/null
+++ b/ffi/cmake/Utils/AddGoogleTest.cmake
@@ -0,0 +1,42 @@
+include(FetchContent)
+set(gtest_force_shared_crt ON CACHE BOOL "Always use msvcrt.dll" FORCE)
+set(BUILD_GMOCK ON CACHE BOOL "" FORCE)
+set(BUILD_GTEST ON CACHE BOOL "" FORCE)
+FetchContent_Declare(
+ googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG v1.14.0
+)
+FetchContent_GetProperties(googletest)
+if (NOT googletest_POPULATED)
+ FetchContent_Populate(googletest)
+ message(STATUS "Found googletest_SOURCE_DIR - ${googletest_SOURCE_DIR}")
+ message(STATUS "Found googletest_BINARY_DIR - ${googletest_BINARY_DIR}")
+ add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
+ include(GoogleTest)
+ set_target_properties(gtest PROPERTIES EXPORT_COMPILE_COMMANDS OFF EXCLUDE_FROM_ALL ON FOLDER 3rdparty)
+ set_target_properties(gtest_main PROPERTIES EXPORT_COMPILE_COMMANDS OFF EXCLUDE_FROM_ALL ON FOLDER 3rdparty)
+ set_target_properties(gmock PROPERTIES EXPORT_COMPILE_COMMANDS OFF EXCLUDE_FROM_ALL ON FOLDER 3rdparty)
+ set_target_properties(gmock_main PROPERTIES EXPORT_COMPILE_COMMANDS OFF EXCLUDE_FROM_ALL ON FOLDER 3rdparty)
+ mark_as_advanced(
+ BUILD_GMOCK BUILD_GTEST BUILD_SHARED_LIBS
+ gmock_build_tests gtest_build_samples gtest_build_tests
+ gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols
+ )
+endif()
+
+macro(add_googletest target_name)
+ add_test(
+ NAME ${target_name}
+ COMMAND ${target_name}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ target_link_libraries(${target_name} PRIVATE gtest_main)
+ gtest_discover_tests(${target_name}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ DISCOVERY_MODE PRE_TEST
+ PROPERTIES
+ VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
+ )
+ set_target_properties(${target_name} PROPERTIES FOLDER tests)
+endmacro()
diff --git a/ffi/cmake/Utils/CxxWarning.cmake b/ffi/cmake/Utils/CxxWarning.cmake
new file mode 100644
index 0000000000..50ee5b616d
--- /dev/null
+++ b/ffi/cmake/Utils/CxxWarning.cmake
@@ -0,0 +1,13 @@
+function(add_cxx_warning target_name)
+ # GNU, Clang, or AppleClang
+ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
+ target_compile_options(${target_name} PRIVATE "-Werror" "-Wall" "-Wextra" "-Wpedantic")
+ return()
+ endif()
+ # MSVC
+ if(MSVC)
+ target_compile_options(${target_name} PRIVATE "/W4" "/WX")
+ return()
+ endif()
+ message(FATAL_ERROR "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}")
+endfunction()
diff --git a/ffi/cmake/Utils/Sanitizer.cmake b/ffi/cmake/Utils/Sanitizer.cmake
new file mode 100644
index 0000000000..c1facc1999
--- /dev/null
+++ b/ffi/cmake/Utils/Sanitizer.cmake
@@ -0,0 +1,18 @@
+function(add_sanitizer_address target_name)
+ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
+ include(CheckCXXCompilerFlag)
+ set (_saved_CRF ${CMAKE_REQUIRED_FLAGS})
+ set(CMAKE_REQUIRED_FLAGS "-fsanitize=address")
+ check_cxx_source_compiles("int main() { return 0; }" COMPILER_SUPPORTS_ASAN)
+ set (CMAKE_REQUIRED_FLAGS ${_saved_CRF})
+ get_target_property(_saved_type ${target_name} TYPE)
+ if (${_saved_type} STREQUAL "INTERFACE_LIBRARY")
+ set(_saved_type INTERFACE)
+ else()
+ set(_saved_type PRIVATE)
+ endif()
+ target_link_options(${target_name} ${_saved_type} "-fsanitize=address")
+ target_compile_options(${target_name} ${_saved_type} "-fsanitize=address" "-fno-omit-frame-pointer" "-g")
+ return()
+ endif()
+endfunction()
diff --git a/ffi/include b/ffi/include
new file mode 120000
index 0000000000..f5030fe889
--- /dev/null
+++ b/ffi/include
@@ -0,0 +1 @@
+../include
\ No newline at end of file
diff --git a/ffi/scripts/run_tests.sh b/ffi/scripts/run_tests.sh
new file mode 100755
index 0000000000..704abaeab9
--- /dev/null
+++ b/ffi/scripts/run_tests.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -euxo pipefail
+
+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 --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 b/ffi/src
new file mode 120000
index 0000000000..5cd551cf26
--- /dev/null
+++ b/ffi/src
@@ -0,0 +1 @@
+../src
\ No newline at end of file
diff --git a/ffi/tests/cpp/CMakeLists.txt b/ffi/tests/cpp/CMakeLists.txt
new file mode 100644
index 0000000000..539115f24d
--- /dev/null
+++ b/ffi/tests/cpp/CMakeLists.txt
@@ -0,0 +1,25 @@
+file(GLOB _test_sources "${CMAKE_CURRENT_SOURCE_DIR}/test*.cc")
+add_executable(
+ tvm_ffi_tests
+ EXCLUDE_FROM_ALL
+ ${_test_sources}
+)
+set_target_properties(
+ tvm_ffi_tests PROPERTIES
+ CXX_STANDARD 17
+ CXX_STANDARD_REQUIRED ON
+ CXX_EXTENSIONS OFF
+ 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"
+)
+add_cxx_warning(tvm_ffi_tests)
+add_sanitizer_address(tvm_ffi_tests)
+target_link_libraries(tvm_ffi_tests PRIVATE tvm_ffi)
+if (TVM_FFI_ALLOW_DYN_TYPE)
+ add_sanitizer_address(tvm_ffi_registry_shared)
+ target_link_libraries(tvm_ffi_tests PRIVATE tvm_ffi_registry_shared)
+endif()
+
+add_googletest(tvm_ffi_tests)
diff --git a/ffi/tests/cpp/test_any.cc b/ffi/tests/cpp/test_any.cc
new file mode 100644
index 0000000000..a57829bd0a
--- /dev/null
+++ b/ffi/tests/cpp/test_any.cc
@@ -0,0 +1,635 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+
+namespace {
+using namespace tvm::ffi;
+
+template <typename SrcType, typename Checker>
+void TestAnyConstructor(Checker check, TVMFFITypeIndex expected_type_index, const SrcType &source) {
+ Any v(source);
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(expected_type_index));
+ EXPECT_EQ(v.ref_cnt, 0);
+ check(&v, source);
+};
+
+int64_t FuncCall(int64_t x) { return x + 1; }
+
+std::vector<Any> AnyArrayFactory() {
+ return std::vector<Any>{
+ Any(nullptr),
+ Any(1),
+ Any(2.5),
+ Any(reinterpret_cast<void *>(FuncCall)),
+ Any(DLDevice{kDLCPU, 0}),
+ Any(DLDataType{kDLInt, 32, 1}),
+ Any("Hello (raw str)"),
+ Any(Ref<Object>::New()),
+ Any(Ref<Func>::New(FuncCall)),
+ Any(std::string("World (std::string)")),
+ Any(Ref<Str>::New("Hello World (Ref<Str>)")),
+ };
+}
+
+template <typename SrcType>
+void TestAnyStringify(const SrcType &source, TVMFFITypeIndex expected_type_index,
+ const std::string &expected) {
+ Any v(source);
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(expected_type_index));
+ EXPECT_EQ(v.str()->c_str(), expected);
+}
+
+template <typename SrcType, typename Checker>
+void TestAnyStringifyChecker(const SrcType &source, TVMFFITypeIndex expected_type_index,
+ Checker check) {
+ Any v(source);
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(expected_type_index));
+ check(v);
+}
+
+void CheckAnyRefCnt(const TVMFFIAny *v) {
+ if (v->type_index >= static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStaticObjectBegin)) {
+ EXPECT_EQ(v->v_obj->ref_cnt, 1);
+ }
+}
+
+TEST(Any_Constructor_0_Default, Default) {
+ Any v;
+ EXPECT_EQ(v.type_index, 0);
+ EXPECT_EQ(v.ref_cnt, 0);
+ EXPECT_EQ(v.v_int64, 0);
+}
+
+TEST(Any_Constructor_1_Any, Copy) {
+ Any v1(1);
+ Any v2(v1);
+ EXPECT_EQ(v1.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v1.v_int64, 1);
+ EXPECT_EQ(v2.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v2.v_int64, 1);
+}
+
+TEST(Any_Constructor_1_Any, Move) {
+ Any v1(1);
+ Any v2(std::move(v1));
+ EXPECT_EQ(v1.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone));
+ EXPECT_EQ(v1.v_int64, 0);
+ EXPECT_EQ(v2.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v2.v_int64, 1);
+}
+
+TEST(Any_Constructor_2_AnyView, Copy) {
+ AnyView v1(1);
+ Any v2(v1);
+ EXPECT_EQ(v1.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v1.v_int64, 1);
+ EXPECT_EQ(v2.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v2.v_int64, 1);
+}
+
+TEST(Any_Constructor_2_AnyView, Move) {
+ AnyView v1(1);
+ Any v2(std::move(v1));
+ EXPECT_EQ(v1.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone));
+ EXPECT_EQ(v1.v_int64, 0);
+ EXPECT_EQ(v2.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v2.v_int64, 1);
+}
+
+TEST(Any_Constructor_3_Ref, Copy) {
+ Ref<Object> obj = Ref<Object>::New();
+ Any v(obj);
+ const TVMFFIAny *v_obj = v.v_obj;
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject));
+ EXPECT_EQ(v.ref_cnt, 0);
+ EXPECT_EQ(v_obj, static_cast<const void *>(obj.get()));
+ EXPECT_EQ(v_obj->ref_cnt, 2);
+}
+
+TEST(Any_Constructor_3_Ref, Move) {
+ Ref<Object> obj = Ref<Object>::New();
+ Any v(std::move(obj));
+ const TVMFFIAny *v_obj = v.v_obj;
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject));
+ EXPECT_EQ(v.ref_cnt, 0);
+ EXPECT_EQ(v_obj->ref_cnt, 1);
+ EXPECT_EQ(obj.get(), nullptr);
+}
+
+TEST(Any_Constructor_4_TypeTraits, Integer) {
+ auto check = [](TVMFFIAny *v, int64_t source) -> void { EXPECT_EQ(v->v_int64, source); };
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<int8_t>(1));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<int16_t>(2));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<int32_t>(3));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<int64_t>(4));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<uint8_t>(1));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<uint16_t>(2));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<uint32_t>(3));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<uint64_t>(4));
+}
+
+TEST(Any_Constructor_4_TypeTraits, Float) {
+ auto check = [](TVMFFIAny *v, double source) -> void { EXPECT_EQ(v->v_float64, source); };
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIFloat, static_cast<float>(3));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIFloat, static_cast<float>(4));
+}
+
+TEST(Any_Constructor_4_TypeTraits, Ptr) {
+ int p = 4;
+ auto check = [](TVMFFIAny *v, void *source) -> void { EXPECT_EQ(v->v_ptr, source); };
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFINone, static_cast<void *>(nullptr));
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIPtr, static_cast<void *>(&p));
+}
+
+TEST(Any_Constructor_4_TypeTraits, Device) {
+ auto check = [](TVMFFIAny *v, const DLDevice &source) -> void {
+ EXPECT_EQ(v->v_device.device_type, source.device_type);
+ EXPECT_EQ(v->v_device.device_id, source.device_id);
+ };
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIDevice, DLDevice{kDLCPU, 0});
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIDevice, DLDevice{kDLCUDA, 1});
+}
+
+TEST(Any_Constructor_4_TypeTraits, DataType) {
+ auto check = [](TVMFFIAny *v, const DLDataType &source) -> void {
+ EXPECT_EQ(v->v_dtype.code, source.code);
+ EXPECT_EQ(v->v_dtype.bits, source.bits);
+ EXPECT_EQ(v->v_dtype.lanes, source.lanes);
+ };
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIDataType, DLDataType{kDLInt, 32, 1});
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIDataType, DLDataType{kDLUInt, 0, 0});
+}
+
+TEST(Any_Constructor_4_TypeTraits, RawStr) {
+ auto check = [](TVMFFIAny *v, const char *source) -> void {
+ Str *str = static_cast<Str *>(v->v_ptr);
+ EXPECT_STREQ(str->c_str(), source);
+ };
+ const char *empty = "";
+ const char *hello = "hello";
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIStr, empty);
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIStr, hello);
+}
+
+TEST(Any_Constructor_4_TypeTraits, CharArray) {
+ auto check = [](TVMFFIAny *v, const char *source) -> void {
+ Str *str = static_cast<Str *>(v->v_ptr);
+ EXPECT_STREQ(str->c_str(), source);
+ };
+ const char empty[] = "";
+ const char hello[] = "hello";
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIStr, empty);
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIStr, hello);
+}
+
+TEST(Any_Constructor_4_TypeTraits, StdString) {
+ auto check = [](TVMFFIAny *v, const std::string &source) -> void {
+ Str *str = static_cast<Str *>(v->v_ptr);
+ EXPECT_EQ(str->c_str(), source);
+ };
+ std::string empty = "";
+ std::string hello = "hello";
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIStr, hello);
+ TestAnyConstructor(check, TVMFFITypeIndex::kTVMFFIStr, empty);
+}
+
+TEST(Any_Constructor_5_Object_Ptr, Object) {
+ Ref<Object> obj = Ref<Object>::New();
+ TVMFFIAny *ptr = reinterpret_cast<TVMFFIAny *>(obj.get());
+ {
+ Any v(obj.get());
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject));
+ EXPECT_EQ(v.v_obj->ref_cnt, 2);
+ EXPECT_EQ(v.v_obj, ptr);
+ }
+ EXPECT_EQ(ptr->ref_cnt, 1);
+}
+
+TEST(Any_Constructor_5_Object_Ptr, Func) {
+ Ref<Func> func = Ref<Func>::New(FuncCall);
+ TVMFFIAny *ptr = reinterpret_cast<TVMFFIAny *>(func.get());
+ {
+ Any v(func.get());
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFunc));
+ EXPECT_EQ(v.v_obj->ref_cnt, 2);
+ EXPECT_EQ(v.v_obj, ptr);
+ }
+ EXPECT_EQ(ptr->ref_cnt, 1);
+}
+
+TEST(Any_Constructor_5_Object_Ptr, Str) {
+ Ref<Str> str = Ref<Str>::New("hello");
+ TVMFFIAny *ptr = reinterpret_cast<TVMFFIAny *>(str.get());
+ {
+ Any v(str.get());
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_EQ(v.v_obj->ref_cnt, 2);
+ EXPECT_EQ(v.v_obj, ptr);
+ }
+ EXPECT_EQ(ptr->ref_cnt, 1);
+}
+
+TEST(Any_Converter_0_TypeTraits, Integer) {
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> int64_t { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt)) {
+ EXPECT_EQ(convert(), 1);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `int`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_0_TypeTraits, Float) {
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> double { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt)) {
+ EXPECT_EQ(convert(), 1.0);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFloat)) {
+ EXPECT_EQ(convert(), 2.5);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `float`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_0_TypeTraits, Ptr) {
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> void * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ EXPECT_EQ(convert(), nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIPtr)) {
+ EXPECT_EQ(convert(), reinterpret_cast<void *>(&FuncCall));
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr)) {
+ EXPECT_EQ(convert(), v.v_str);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `Ptr`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_0_TypeTraits, Device) {
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> DLDevice { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDevice)) {
+ EXPECT_EQ(convert().device_type, kDLCPU);
+ EXPECT_EQ(convert().device_id, 0);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `Device`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_0_TypeTraits, DataType) {
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> DLDataType { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDataType)) {
+ EXPECT_EQ(convert().code, kDLInt);
+ EXPECT_EQ(convert().bits, 32);
+ EXPECT_EQ(convert().lanes, 1);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `dtype`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_0_TypeTraits, RawStr) {
+ std::vector<Any> vs = AnyArrayFactory();
+ int counter = 0;
+ for (const Any &v : vs) {
+ auto convert = [&]() -> const char * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr)) {
+ ++counter;
+ EXPECT_LE(counter, 3);
+ if (counter == 1) {
+ EXPECT_STREQ(convert(), "Hello (raw str)");
+ } else if (counter == 2) {
+ EXPECT_STREQ(convert(), "World (std::string)");
+ } else if (counter == 3) {
+ EXPECT_STREQ(convert(), "Hello World (Ref<Str>)");
+ }
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `const char *`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_1_AnyView, Any) {
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> AnyView { return v; };
+ {
+ AnyView ret = convert();
+ EXPECT_EQ(ret.type_index, v.type_index);
+ EXPECT_EQ(ret.ref_cnt, 0);
+ EXPECT_EQ(v.ref_cnt, 0);
+ EXPECT_EQ(ret.v_obj, v.v_obj);
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_2_Ref, Object) {
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> Ref<Object> { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Ref<Object> ret = convert();
+ EXPECT_EQ(ret.get(), nullptr);
+ } else if (v.type_index >= static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStaticObjectBegin)) {
+ Ref<Object> ret = convert();
+ EXPECT_EQ(ret.get(), static_cast<void *>(v.v_obj));
+ EXPECT_EQ(v.v_obj->ref_cnt, 2);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Object`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_2_Ref, Func) {
+ std::vector<Any> views = AnyArrayFactory();
+ for (const Any &v : views) {
+ auto convert = [&]() -> Ref<Func> { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Ref<Func> ret = convert();
+ EXPECT_EQ(ret.get(), nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFunc)) {
+ Ref<Func> ret = convert();
+ EXPECT_EQ(ret.get(), static_cast<void *>(v.v_obj));
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Func`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_2_Ref, Str) {
+ int counter = 0;
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> Ref<Str> { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Ref<Str> ret = convert();
+ EXPECT_EQ(ret.get(), nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr)) {
+ Ref<Str> ret = convert();
+ ++counter;
+ EXPECT_LE(counter, 3);
+ if (counter == 1) {
+ EXPECT_STREQ(ret->c_str(), "Hello (raw str)");
+ } else if (counter == 2) {
+ EXPECT_STREQ(ret->c_str(), "World (std::string)");
+ } else {
+ EXPECT_STREQ(ret->c_str(), "Hello World (Ref<Str>)");
+ }
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Str`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_3_Object_Ptr, Object) {
+ std::vector<Any> vs = AnyArrayFactory();
+ for (const Any &v : vs) {
+ auto convert = [&]() -> Object * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Object *ret = convert();
+ EXPECT_EQ(ret, nullptr);
+ } else if (v.type_index >= static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStaticObjectBegin)) {
+ Object *ret = convert();
+ EXPECT_EQ(ret, static_cast<void *>(v.v_obj));
+ EXPECT_EQ(v.v_obj->ref_cnt, 1);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Object *`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_3_Object_Ptr, Func) {
+ std::vector<Any> views = AnyArrayFactory();
+ for (const Any &v : views) {
+ auto convert = [&]() -> Func * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Func *ret = convert();
+ EXPECT_EQ(ret, nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFunc)) {
+ Func *ret = convert();
+ EXPECT_EQ(ret, reinterpret_cast<Func *>(v.v_ptr));
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Func *`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Converter_3_Object_Ptr, Str) {
+ std::vector<Any> vs = AnyArrayFactory();
+ int counter = 0;
+ for (const Any &v : vs) {
+ auto convert = [&]() -> Str * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Str *ret = convert();
+ EXPECT_EQ(ret, nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr)) {
+ Str *ret = convert();
+ ++counter;
+ EXPECT_LE(counter, 3);
+ if (counter == 1) {
+ EXPECT_STREQ(ret->c_str(), "Hello (raw str)");
+ } else if (counter == 2) {
+ EXPECT_STREQ(ret->c_str(), "World (std::string)");
+ } else {
+ EXPECT_STREQ(ret->c_str(), "Hello World (Ref<Str>)");
+ }
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Str *`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyRefCnt(&v);
+ }
+}
+
+TEST(Any_Stringify, Integer) {
+ TestAnyStringify<int8_t>(-13, TVMFFITypeIndex::kTVMFFIInt, "-13");
+ TestAnyStringify<int16_t>(-5, TVMFFITypeIndex::kTVMFFIInt, "-5");
+ TestAnyStringify<int32_t>(0, TVMFFITypeIndex::kTVMFFIInt, "0");
+ TestAnyStringify<int64_t>(1, TVMFFITypeIndex::kTVMFFIInt, "1");
+}
+
+TEST(Any_Stringify, Float) {
+ auto check = [](const Any &v) -> void {
+ std::string str = v.str()->c_str();
+ double f_str = std::stod(str);
+ double f_src = v.v_float64;
+ EXPECT_NEAR(f_src, f_str, 1e-5);
+ };
+ TestAnyStringifyChecker<float>(float(-3.14), TVMFFITypeIndex::kTVMFFIFloat, check);
+ TestAnyStringifyChecker<double>(0.0, TVMFFITypeIndex::kTVMFFIFloat, check);
+}
+
+TEST(Any_Stringify, Ptr) {
+ auto check = [](const Any &v) -> void {
+ std::string str = v.str()->c_str();
+ EXPECT_GT(str.size(), 2);
+ };
+ TestAnyStringify<void *>(nullptr, TVMFFITypeIndex::kTVMFFINone, "None");
+ TestAnyStringifyChecker<void *>(reinterpret_cast<void *>(FuncCall), TVMFFITypeIndex::kTVMFFIPtr,
+ check);
+}
+
+TEST(Any_Stringify, Device) {
+ TestAnyStringify<DLDevice>(DLDevice{kDLCPU, 0}, TVMFFITypeIndex::kTVMFFIDevice, "cpu:0");
+ TestAnyStringify<DLDevice>(DLDevice{kDLCUDA, 1}, TVMFFITypeIndex::kTVMFFIDevice, "cuda:1");
+}
+
+TEST(Any_Stringify, DataType) {
+ TestAnyStringify<DLDataType>(DLDataType{kDLInt, 32, 1}, TVMFFITypeIndex::kTVMFFIDataType,
+ "int32");
+ TestAnyStringify<DLDataType>(DLDataType{kDLUInt, 1, 1}, TVMFFITypeIndex::kTVMFFIDataType, "bool");
+ TestAnyStringify<DLDataType>(DLDataType{kDLOpaqueHandle, 0, 0}, TVMFFITypeIndex::kTVMFFIDataType,
+ "void");
+ TestAnyStringify<DLDataType>(DLDataType{kDLFloat, 8, 4}, TVMFFITypeIndex::kTVMFFIDataType,
+ "float8x4");
+}
+
+TEST(Any_Stringify, RawStr) {
+ TestAnyStringify<const char *>("Hello", TVMFFITypeIndex::kTVMFFIStr, "\"Hello\"");
+ TestAnyStringify<char[6]>("Hello", TVMFFITypeIndex::kTVMFFIStr, "\"Hello\"");
+ TestAnyStringify<const char[6]>("Hello", TVMFFITypeIndex::kTVMFFIStr, "\"Hello\"");
+}
+
+TEST(Any_Stringify, Object) {
+ auto check = [](const Any &v) -> void {
+ std::string expected_prefix = "object.Object@0";
+ int n = static_cast<int>(expected_prefix.size());
+ std::string str = v.str()->c_str();
+ EXPECT_GT(str.size(), n);
+ EXPECT_EQ(str.substr(0, n), expected_prefix);
+ };
+ TestAnyStringifyChecker<Ref<Object>>(Ref<Object>::New(), TVMFFITypeIndex::kTVMFFIObject, check);
+}
+
+TEST(Any_Stringify, Func) {
+ auto check = [](const Any &v) -> void {
+ std::string expected_prefix = "object.Func@0";
+ int n = static_cast<int>(expected_prefix.size());
+ std::string str = v.str()->c_str();
+ EXPECT_GT(str.size(), n);
+ EXPECT_EQ(str.substr(0, n), expected_prefix);
+ };
+ TestAnyStringifyChecker<Ref<Func>>(Ref<Func>::New(FuncCall), TVMFFITypeIndex::kTVMFFIFunc, check);
+}
+
+TEST(Any_Stringify, Str) {
+ auto check = [](const Any &v) -> void {
+ std::string str = v.str()->c_str();
+ EXPECT_EQ(str, "\"Hello World\"");
+ };
+ TestAnyStringifyChecker<Ref<Str>>(Ref<Str>::New("Hello World"), TVMFFITypeIndex::kTVMFFIStr,
+ check);
+}
+
+} // namespace
diff --git a/ffi/tests/cpp/test_any_view.cc b/ffi/tests/cpp/test_any_view.cc
new file mode 100644
index 0000000000..67aaa9de3a
--- /dev/null
+++ b/ffi/tests/cpp/test_any_view.cc
@@ -0,0 +1,630 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+
+namespace {
+using namespace tvm::ffi;
+
+template <typename SrcType, typename Checker>
+void TestAnyViewConstructor(Checker check, TVMFFITypeIndex expected_type_index,
+ const SrcType &source) {
+ AnyView v(source);
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(expected_type_index));
+ EXPECT_EQ(v.ref_cnt, 0);
+ check(&v, source);
+};
+
+int64_t FuncCall(int64_t x) { return x + 1; }
+
+std::vector<AnyView> AnyViewArrayFactory() {
+ static const char *raw_str = "Hello (raw str)";
+ static std::string std_str = "World (std::string)";
+ static std::string ref_str = "Hello World (Ref<Str>)";
+ static Ref<Object> obj = Ref<Object>::New();
+ static Ref<Func> func = Ref<Func>::New(FuncCall);
+ static Ref<Str> str = Ref<Str>::New(ref_str);
+ return std::vector<AnyView>{
+ AnyView(nullptr),
+ AnyView(1),
+ AnyView(2.5),
+ AnyView(reinterpret_cast<void *>(FuncCall)),
+ AnyView(DLDevice{kDLCPU, 0}),
+ AnyView(DLDataType{kDLInt, 32, 1}),
+ AnyView(raw_str),
+ AnyView(obj),
+ AnyView(func),
+ AnyView(std_str), // TODO: disable AnyView(std::string&&)
+ AnyView(str),
+ };
+}
+
+template <typename SrcType>
+void TestAnyViewStringify(const SrcType &source, TVMFFITypeIndex expected_type_index,
+ const std::string &expected) {
+ AnyView v(source);
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(expected_type_index));
+ EXPECT_EQ(v.str()->c_str(), expected);
+}
+
+template <typename SrcType, typename Checker>
+void TestAnyViewStringifyChecker(const SrcType &source, TVMFFITypeIndex expected_type_index,
+ Checker check) {
+ AnyView v(source);
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(expected_type_index));
+ check(v);
+}
+
+void CheckAnyViewRefCnt(const TVMFFIAny *v) {
+ if (v->type_index >= static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStaticObjectBegin)) {
+ EXPECT_EQ(v->v_obj->ref_cnt, 1);
+ }
+}
+
+TEST(AnyView_Constructor_0_Default, Default) {
+ AnyView v;
+ EXPECT_EQ(v.type_index, 0);
+ EXPECT_EQ(v.ref_cnt, 0);
+ EXPECT_EQ(v.v_int64, 0);
+}
+
+TEST(AnyView_Constructor_1_AnyView, Copy) {
+ AnyView v1(1);
+ AnyView v2(v1);
+ EXPECT_EQ(v1.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v1.v_int64, 1);
+ EXPECT_EQ(v2.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v2.v_int64, 1);
+}
+
+TEST(AnyView_Constructor_1_AnyView, Move) {
+ AnyView v1(1);
+ AnyView v2(std::move(v1));
+ EXPECT_EQ(v1.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone));
+ EXPECT_EQ(v1.v_int64, 0);
+ EXPECT_EQ(v2.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v2.v_int64, 1);
+}
+
+TEST(AnyView_Constructor_2_Any, Copy) {
+ Any v1(1);
+ AnyView v2(v1);
+ EXPECT_EQ(v1.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v1.v_int64, 1);
+ EXPECT_EQ(v2.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt));
+ EXPECT_EQ(v2.v_int64, 1);
+}
+
+TEST(AnyView_Constructor_2_Any, Move) {
+ // ---- The following behavior is disallowed ----
+ // Any v1(1);
+ // AnyView v2(std::move(v1));
+}
+
+TEST(AnyView_Constructor_3_Ref, Copy) {
+ Ref<Object> obj = Ref<Object>::New();
+ AnyView v(obj);
+ const TVMFFIAny *v_obj = v.v_obj;
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject));
+ EXPECT_EQ(v.ref_cnt, 0);
+ EXPECT_EQ(v_obj, static_cast<const void *>(obj.get()));
+ EXPECT_EQ(v_obj->ref_cnt, 1);
+}
+
+TEST(AnyView_Constructor_3_Ref, Move) {
+ // The following behavior is disallowed
+ // Ref<Object> obj = Ref<Object>::New();
+ // AnyView v(std::move(obj));
+}
+
+TEST(AnyView_Constructor_4_TypeTraits, Integer) {
+ auto check = [](TVMFFIAny *v, int64_t source) -> void { EXPECT_EQ(v->v_int64, source); };
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<int8_t>(1));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<int16_t>(2));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<int32_t>(3));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<int64_t>(4));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<uint8_t>(1));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<uint16_t>(2));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<uint32_t>(3));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIInt, static_cast<uint64_t>(4));
+}
+
+TEST(AnyView_Constructor_4_TypeTraits, Float) {
+ auto check = [](TVMFFIAny *v, double source) -> void { EXPECT_EQ(v->v_float64, source); };
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIFloat, static_cast<float>(3));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIFloat, static_cast<float>(4));
+}
+
+TEST(AnyView_Constructor_4_TypeTraits, Ptr) {
+ int p = 4;
+ auto check = [](TVMFFIAny *v, void *source) -> void { EXPECT_EQ(v->v_ptr, source); };
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFINone, static_cast<void *>(nullptr));
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIPtr, static_cast<void *>(&p));
+}
+
+TEST(AnyView_Constructor_4_TypeTraits, Device) {
+ auto check = [](TVMFFIAny *v, const DLDevice &source) -> void {
+ EXPECT_EQ(v->v_device.device_type, source.device_type);
+ EXPECT_EQ(v->v_device.device_id, source.device_id);
+ };
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIDevice, DLDevice{kDLCPU, 0});
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIDevice, DLDevice{kDLCUDA, 1});
+}
+
+TEST(AnyView_Constructor_4_TypeTraits, DataType) {
+ auto check = [](TVMFFIAny *v, const DLDataType &source) -> void {
+ EXPECT_EQ(v->v_dtype.code, source.code);
+ EXPECT_EQ(v->v_dtype.bits, source.bits);
+ EXPECT_EQ(v->v_dtype.lanes, source.lanes);
+ };
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIDataType, DLDataType{kDLInt, 32, 1});
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIDataType, DLDataType{kDLUInt, 0, 0});
+}
+
+TEST(AnyView_Constructor_4_TypeTraits, RawStr) {
+ auto check = [](TVMFFIAny *v, const char *source) -> void { EXPECT_EQ(v->v_str, source); };
+ const char *empty = "";
+ const char *hello = "hello";
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIRawStr, empty);
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIRawStr, hello);
+}
+
+TEST(AnyView_Constructor_4_TypeTraits, CharArray) {
+ auto check = [](TVMFFIAny *v, const char *source) -> void { EXPECT_EQ(v->v_str, source); };
+ const char empty[] = "";
+ const char hello[] = "hello";
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIRawStr, empty);
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIRawStr, hello);
+}
+
+TEST(AnyView_Constructor_4_TypeTraits, StdString) {
+ auto check = [](TVMFFIAny *v, const std::string &source) -> void {
+ EXPECT_EQ(v->v_str, source.data());
+ };
+ std::string empty = "";
+ std::string hello = "hello";
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIRawStr, hello);
+ TestAnyViewConstructor(check, TVMFFITypeIndex::kTVMFFIRawStr, empty);
+}
+
+TEST(AnyView_Constructor_5_Object_Ptr, Object) {
+ Ref<Object> obj = Ref<Object>::New();
+ AnyView v(obj.get());
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject));
+ EXPECT_EQ(v.v_obj->ref_cnt, 1);
+ EXPECT_EQ(v.v_obj, static_cast<void *>(obj.get()));
+}
+
+TEST(AnyView_Constructor_5_Object_Ptr, Func) {
+ Ref<Func> func = Ref<Func>::New(FuncCall);
+ AnyView v(func.get());
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFunc));
+ EXPECT_EQ(v.v_obj->ref_cnt, 1);
+ EXPECT_EQ(v.v_ptr, static_cast<void *>(func.get()));
+}
+
+TEST(AnyView_Constructor_5_Object_Ptr, Str) {
+ Ref<Str> str = Ref<Str>::New("hello");
+ AnyView v(str.get());
+ EXPECT_EQ(v.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_EQ(v.v_obj->ref_cnt, 1);
+ EXPECT_EQ(v.v_obj, static_cast<void *>(str.get()));
+}
+
+TEST(AnyView_Converter_0_TypeTraits, Integer) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> int64_t { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt)) {
+ EXPECT_EQ(convert(), 1);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `int`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_0_TypeTraits, Float) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> double { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt)) {
+ EXPECT_EQ(convert(), 1.0);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFloat)) {
+ EXPECT_EQ(convert(), 2.5);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `float`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_0_TypeTraits, Ptr) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> void * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ EXPECT_EQ(convert(), nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIPtr)) {
+ EXPECT_EQ(convert(), reinterpret_cast<void *>(&FuncCall));
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr)) {
+ EXPECT_EQ(convert(), v.v_str);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `Ptr`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_0_TypeTraits, Device) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> DLDevice { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDevice)) {
+ EXPECT_EQ(convert().device_type, kDLCPU);
+ EXPECT_EQ(convert().device_id, 0);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `Device`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_0_TypeTraits, DataType) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> DLDataType { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDataType)) {
+ EXPECT_EQ(convert().code, kDLInt);
+ EXPECT_EQ(convert().bits, 32);
+ EXPECT_EQ(convert().lanes, 1);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index) << "` to `dtype`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_0_TypeTraits, RawStr) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ int counter = 0;
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> const char * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr)) {
+ counter += 1;
+ EXPECT_LT(counter, 3);
+ if (counter == 1) {
+ EXPECT_STREQ(convert(), "Hello (raw str)");
+ } else if (counter == 2) {
+ EXPECT_STREQ(convert(), "World (std::string)");
+ }
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr)) {
+ EXPECT_STREQ(convert(), "Hello World (Ref<Str>)");
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `const char *`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_0_TypeTraits, RawStrToStrStar_Fail) {
+ AnyView v = "Hello";
+ try {
+ Str *v_str = v;
+ (void)v_str;
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(), "Cannot convert from type `const char *` to `object.Str *`");
+ }
+}
+
+TEST(AnyView_Converter_0_TypeTraits, RawStrToStrStar_WrithStorage) {
+ Any storage;
+ AnyView v = "Hello";
+ Str *v_str = v.CastWithStorage<Str *>(&storage);
+ EXPECT_STREQ(v_str->c_str(), "Hello");
+}
+
+TEST(AnyView_Converter_1_Any, Any) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &view : views) {
+ auto convert = [&]() -> Any { return view; };
+ {
+ Any ret = convert();
+ EXPECT_EQ(view.ref_cnt, 0);
+ if (view.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr)) {
+ Str *str = ret;
+ EXPECT_EQ(ret.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_STREQ(str->c_str(), view.v_str);
+ EXPECT_EQ(ret.ref_cnt, 0);
+ } else {
+ EXPECT_EQ(ret.type_index, view.type_index);
+ EXPECT_EQ(ret.ref_cnt, 0);
+ EXPECT_EQ(ret.v_obj, view.v_obj);
+ }
+ }
+ CheckAnyViewRefCnt(&view);
+ }
+}
+
+TEST(AnyView_Converter_2_Ref, Object) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> Ref<Object> { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Ref<Object> ret = convert();
+ EXPECT_EQ(ret.get(), nullptr);
+ } else if (v.type_index >= static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStaticObjectBegin)) {
+ Ref<Object> ret = convert();
+ EXPECT_EQ(ret.get(), static_cast<void *>(v.v_obj));
+ EXPECT_EQ(v.v_obj->ref_cnt, 2);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Object`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_2_Ref, Func) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> Ref<Func> { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Ref<Func> ret = convert();
+ EXPECT_EQ(ret.get(), nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFunc)) {
+ Ref<Func> ret = convert();
+ EXPECT_EQ(ret.get(), static_cast<void *>(v.v_obj));
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Func`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_2_Ref, Str) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> Ref<Str> { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Ref<Str> ret = convert();
+ EXPECT_EQ(ret.get(), nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr)) {
+ Ref<Str> ret = convert();
+ EXPECT_STREQ(ret->c_str(), "Hello World (Ref<Str>)");
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr)) {
+ Ref<Str> ret = convert();
+ EXPECT_EQ(reinterpret_cast<TVMFFIStr *>(ret.get())->ref_cnt, 1);
+ EXPECT_STREQ(ret->c_str(), v.v_str);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Str`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_3_Object_Ptr, Object) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> Object * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Object *ret = convert();
+ EXPECT_EQ(ret, nullptr);
+ } else if (v.type_index >= static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStaticObjectBegin)) {
+ Object *ret = convert();
+ EXPECT_EQ(ret, static_cast<void *>(v.v_obj));
+ EXPECT_EQ(v.v_obj->ref_cnt, 1);
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Object *`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_3_Object_Ptr, Func) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> Func * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Func *ret = convert();
+ EXPECT_EQ(ret, nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFunc)) {
+ Func *ret = convert();
+ EXPECT_EQ(ret, reinterpret_cast<Func *>(v.v_ptr));
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Func *`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Converter_3_Object_Ptr, Str) {
+ std::vector<AnyView> views = AnyViewArrayFactory();
+ for (const AnyView &v : views) {
+ auto convert = [&]() -> Str * { return v; };
+ if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)) {
+ Str *ret = convert();
+ EXPECT_EQ(ret, nullptr);
+ } else if (v.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr)) {
+ Str *ret = convert();
+ EXPECT_STREQ(ret->c_str(), "Hello World (Ref<Str>)");
+ } else {
+ try {
+ convert();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ std::ostringstream os;
+ os << "Cannot convert from type `" << TypeIndex2TypeKey(v.type_index)
+ << "` to `object.Str *`";
+ EXPECT_EQ(ex.what(), os.str());
+ }
+ }
+ CheckAnyViewRefCnt(&v);
+ }
+}
+
+TEST(AnyView_Stringify, Integer) {
+ TestAnyViewStringify<int8_t>(-13, TVMFFITypeIndex::kTVMFFIInt, "-13");
+ TestAnyViewStringify<int16_t>(-5, TVMFFITypeIndex::kTVMFFIInt, "-5");
+ TestAnyViewStringify<int32_t>(0, TVMFFITypeIndex::kTVMFFIInt, "0");
+ TestAnyViewStringify<int64_t>(1, TVMFFITypeIndex::kTVMFFIInt, "1");
+}
+
+TEST(AnyView_Stringify, Float) {
+ auto check = [](const AnyView &v) -> void {
+ std::string str = v.str()->c_str();
+ double f_str = std::stod(str);
+ double f_src = v.v_float64;
+ EXPECT_NEAR(f_src, f_str, 1e-5);
+ };
+ TestAnyViewStringifyChecker<float>(float(-3.14), TVMFFITypeIndex::kTVMFFIFloat, check);
+ TestAnyViewStringifyChecker<double>(0.0, TVMFFITypeIndex::kTVMFFIFloat, check);
+}
+
+TEST(AnyView_Stringify, Ptr) {
+ auto check = [](const AnyView &v) -> void {
+ std::string str = v.str()->c_str();
+ EXPECT_GT(str.size(), 2);
+ };
+ TestAnyViewStringify<void *>(nullptr, TVMFFITypeIndex::kTVMFFINone, "None");
+ TestAnyViewStringifyChecker<void *>(reinterpret_cast<void *>(FuncCall),
+ TVMFFITypeIndex::kTVMFFIPtr, check);
+}
+
+TEST(AnyView_Stringify, Device) {
+ TestAnyViewStringify<DLDevice>(DLDevice{kDLCPU, 0}, TVMFFITypeIndex::kTVMFFIDevice, "cpu:0");
+ TestAnyViewStringify<DLDevice>(DLDevice{kDLCUDA, 1}, TVMFFITypeIndex::kTVMFFIDevice, "cuda:1");
+}
+
+TEST(AnyView_Stringify, DataType) {
+ TestAnyViewStringify<DLDataType>(DLDataType{kDLInt, 32, 1}, TVMFFITypeIndex::kTVMFFIDataType,
+ "int32");
+ TestAnyViewStringify<DLDataType>(DLDataType{kDLUInt, 1, 1}, TVMFFITypeIndex::kTVMFFIDataType,
+ "bool");
+ TestAnyViewStringify<DLDataType>(DLDataType{kDLOpaqueHandle, 0, 0},
+ TVMFFITypeIndex::kTVMFFIDataType, "void");
+ TestAnyViewStringify<DLDataType>(DLDataType{kDLFloat, 8, 4}, TVMFFITypeIndex::kTVMFFIDataType,
+ "float8x4");
+}
+
+TEST(AnyView_Stringify, RawStr) {
+ TestAnyViewStringify<const char *>("Hello", TVMFFITypeIndex::kTVMFFIRawStr, "\"Hello\"");
+ TestAnyViewStringify<char[6]>("Hello", TVMFFITypeIndex::kTVMFFIRawStr, "\"Hello\"");
+ TestAnyViewStringify<const char[6]>("Hello", TVMFFITypeIndex::kTVMFFIRawStr, "\"Hello\"");
+}
+
+TEST(AnyView_Stringify, Object) {
+ auto check = [](const AnyView &v) -> void {
+ std::string expected_prefix = "object.Object@0";
+ int n = static_cast<int>(expected_prefix.size());
+ std::string str = v.str()->c_str();
+ EXPECT_GT(str.size(), n);
+ EXPECT_EQ(str.substr(0, n), expected_prefix);
+ };
+ TestAnyViewStringifyChecker<Ref<Object>>(Ref<Object>::New(), TVMFFITypeIndex::kTVMFFIObject,
+ check);
+}
+
+TEST(AnyView_Stringify, Func) {
+ auto check = [](const AnyView &v) -> void {
+ std::string expected_prefix = "object.Func@0";
+ int n = static_cast<int>(expected_prefix.size());
+ std::string str = v.str()->c_str();
+ EXPECT_GT(str.size(), n);
+ EXPECT_EQ(str.substr(0, n), expected_prefix);
+ };
+ TestAnyViewStringifyChecker<Ref<Func>>(Ref<Func>::New(FuncCall), TVMFFITypeIndex::kTVMFFIFunc,
+ check);
+}
+
+TEST(AnyView_Stringify, Str) {
+ auto check = [](const AnyView &v) -> void {
+ std::string str = v.str()->c_str();
+ EXPECT_EQ(str, "\"Hello World\"");
+ };
+ TestAnyViewStringifyChecker<Ref<Str>>(Ref<Str>::New("Hello World"), TVMFFITypeIndex::kTVMFFIStr,
+ check);
+}
+
+} // namespace
diff --git a/ffi/tests/cpp/test_dict.cc b/ffi/tests/cpp/test_dict.cc
new file mode 100644
index 0000000000..04d5a2854f
--- /dev/null
+++ b/ffi/tests/cpp/test_dict.cc
@@ -0,0 +1,183 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+#include <unordered_set>
+
+namespace {
+
+using namespace tvm::ffi;
+
+bool DTypeEqual(DLDataType a, DLDataType b) {
+ return a.code == b.code && a.bits == b.bits && a.lanes == b.lanes;
+}
+// bool DeviceEqual(DLDevice a, DLDevice b) {
+// return a.device_type == b.device_type && a.device_id == b.device_id;
+// }
+
+TEST(Dict_Construtor, Default) {
+ Ref<Dict> dict;
+ ASSERT_EQ(dict.size(), 0);
+ TVMFFIDict *dict_ptr = reinterpret_cast<TVMFFIDict *>(dict.get());
+ EXPECT_EQ(dict_ptr->type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDict));
+ EXPECT_EQ(dict_ptr->ref_cnt, 1);
+ EXPECT_NE(dict_ptr->deleter, nullptr);
+ EXPECT_EQ(dict_ptr->size, 0);
+ EXPECT_EQ(dict_ptr->capacity, 0);
+}
+
+TEST(Dict_Construtor, InitializerList) {
+ Ref<Dict> dict{{"key1", 1}, {"key2", "value2"}, {3, 4}};
+ EXPECT_EQ(dict.size(), 3);
+ EXPECT_EQ(int(dict["key1"]), 1);
+ EXPECT_EQ(std::string(dict["key2"]), "value2");
+ EXPECT_EQ(int(dict[3]), 4);
+
+ bool found[3] = {false, false, false};
+ for (const auto &kv : dict) {
+ if (AnyEqual()(kv.first, Any("key1"))) {
+ found[0] = true;
+ EXPECT_EQ(int(kv.second), 1);
+ } else if (AnyEqual()(kv.first, Any("key2"))) {
+ found[1] = true;
+ EXPECT_EQ(std::string(kv.second), "value2");
+ } else if (AnyEqual()(kv.first, Any(3))) {
+ found[2] = true;
+ EXPECT_EQ(int(kv.second), 4);
+ } else {
+ FAIL() << "Unexpected key: " << kv.first;
+ }
+ }
+ EXPECT_TRUE(found[0]);
+ EXPECT_TRUE(found[1]);
+ EXPECT_TRUE(found[2]);
+}
+
+TEST(Dict_Insert, New) {
+ int64_t integer = 100;
+ double fp = 1.0;
+ std::string str = "Hi";
+ DLDataType dtype{kDLInt, 32, 1};
+ DLDevice device{kDLCPU, 0};
+ Ref<Object> obj = Ref<Object>::New();
+ Ref<Object> null_obj{nullptr};
+ Ref<Dict> dict{{integer, fp}, {str, dtype}, {null_obj, 0}};
+ dict[device] = null_obj;
+ EXPECT_EQ(dict.size(), 4);
+ EXPECT_DOUBLE_EQ(double(dict[integer]), fp);
+ EXPECT_PRED2(DTypeEqual, DLDataType(dict[str]), dtype);
+ EXPECT_EQ(int(dict[null_obj]), 0);
+ EXPECT_EQ((Object *)(dict[device]), nullptr);
+}
+
+TEST(Dict_Insert, Override) {
+ Ref<Dict> dict{{"key1", 1}, {"key2", "value2"}, {3, 4}};
+ EXPECT_EQ(dict.size(), 3);
+ dict["key1"] = 2;
+ dict["key2"] = "new_value";
+ dict[3] = 5;
+ EXPECT_EQ(dict.size(), 3);
+ EXPECT_EQ(int(dict["key1"]), 2);
+ EXPECT_EQ(std::string(dict["key2"]), "new_value");
+ EXPECT_EQ(int(dict[3]), 5);
+}
+
+TEST(Dict_At, Found) {
+ int64_t integer = 100;
+ double fp = 1.0;
+ std::string str = "Hi";
+ DLDataType dtype{kDLInt, 32, 1};
+ Ref<Object> obj = Ref<Object>::New();
+ Ref<Object> null_obj{nullptr};
+ Ref<Dict> dict{{integer, fp}, {str, dtype}, {null_obj, 0}};
+ EXPECT_DOUBLE_EQ(double(dict.at(integer)), fp);
+ EXPECT_PRED2(DTypeEqual, DLDataType(dict.at(str)), dtype);
+ EXPECT_EQ(int(dict.at(null_obj)), 0);
+}
+
+TEST(Dict_At, NotFound) {
+ Ref<Dict> dict{{"key1", 1}, {"key2", "value2"}, {3, 4}};
+ try {
+ dict.at("key3");
+ FAIL() << "Expected TVMError";
+ } catch (const TVMError &e) {
+ }
+}
+
+TEST(Dict_ReHash, POD) {
+ Ref<Dict> dict;
+ for (int j = 0; j < 1000; ++j) {
+ dict[j] = j;
+ }
+ EXPECT_EQ(dict.size(), 1000);
+ std::unordered_set<int64_t> keys;
+ for (auto &kv : dict) {
+ int64_t key = kv.first;
+ int64_t value = kv.second;
+ EXPECT_EQ(key, value);
+ EXPECT_FALSE(keys.count(key));
+ EXPECT_EQ(key, value);
+ EXPECT_TRUE(0 <= key && key < 1000);
+ }
+ EXPECT_EQ(dict.size(), 1000);
+}
+
+TEST(Dict_ReHash, Object) {
+ std::vector<Ref<Object>> objs;
+ std::unordered_map<Object *, int64_t> obj_map;
+ for (int j = 0; j < 1000; ++j) {
+ objs.push_back(Ref<Object>::New());
+ obj_map[objs[j].get()] = j;
+ }
+ Ref<Dict> dict;
+ for (int j = 0; j < 1000; ++j) {
+ dict[objs[j]] = j;
+ }
+ EXPECT_EQ(dict.size(), 1000);
+ std::unordered_set<Object *> keys;
+ for (auto &kv : dict) {
+ Ref<Object> key = kv.first;
+ int64_t value = kv.second;
+ keys.insert(key.get());
+ EXPECT_EQ(value, obj_map[key.get()]);
+ }
+ EXPECT_EQ(dict.size(), 1000);
+}
+
+TEST(Dict_Erase, POD) {
+ Ref<Dict> dict;
+ for (int j = 0; j < 1000; ++j) {
+ dict[j] = j;
+ }
+ EXPECT_EQ(dict.size(), 1000);
+ for (int j = 0; j < 1000; ++j) {
+ dict.erase(j);
+ EXPECT_EQ(dict.size(), 1000 - j - 1);
+ }
+ for (int j = 0; j < 1000; ++j) {
+ dict[j] = j;
+ EXPECT_EQ(dict.size(), j + 1);
+ }
+}
+
+TEST(Dict_Erase, Object) {
+ std::vector<Ref<Object>> objs;
+ std::unordered_map<Object *, int64_t> obj_map;
+ for (int j = 0; j < 1000; ++j) {
+ objs.push_back(Ref<Object>::New());
+ obj_map[objs[j].get()] = j;
+ }
+ Ref<Dict> dict;
+ for (int j = 0; j < 1000; ++j) {
+ dict[objs[j]] = j;
+ }
+ EXPECT_EQ(dict.size(), 1000);
+ for (int j = 0; j < 1000; ++j) {
+ dict.erase(objs[j]);
+ EXPECT_EQ(dict.size(), 1000 - j - 1);
+ }
+ for (int j = 0; j < 1000; ++j) {
+ dict[objs[j]] = j;
+ EXPECT_EQ(dict.size(), j + 1);
+ }
+}
+
+} // namespace
diff --git a/ffi/tests/cpp/test_func.cc b/ffi/tests/cpp/test_func.cc
new file mode 100644
index 0000000000..32512d3dcf
--- /dev/null
+++ b/ffi/tests/cpp/test_func.cc
@@ -0,0 +1,215 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+
+namespace {
+using namespace tvm::ffi;
+
+const char *c_str_raw = "Hello";
+
+double func_unpacked_0(int64_t a, double b, const char *c, const double &d) {
+ EXPECT_STREQ(c, c_str_raw);
+ return a + b + d;
+}
+
+void func_unpacked_1(DLDataType dtype, DLDevice device, std::string str) {
+ (void)dtype;
+ (void)device;
+ (void)str;
+}
+
+void func_packed_0(int num_args, const AnyView *, Any *ret) { *ret = num_args; }
+
+template <enum TVMFFITypeIndex type_index>
+void func_unpacked_anyview_arg(AnyView a) {
+ EXPECT_EQ(a.type_index, static_cast<int32_t>(type_index));
+}
+template <enum TVMFFITypeIndex type_index>
+void func_unpacked_any_arg(Any a) {
+ EXPECT_EQ(a.type_index, static_cast<int32_t>(type_index));
+}
+AnyView func_unpacked_anyview_ret() { return AnyView(1); }
+Any func_unpacked_any_ret() { return Any(1); }
+
+std::string func_unpacked_str_obj(Str *str, const char *str_2) {
+ EXPECT_EQ(reinterpret_cast<TVMFFIStr *>(str)->ref_cnt, 1);
+ EXPECT_STREQ(str->c_str(), str_2);
+ return str->c_str();
+}
+
+TEST(Func_Signature, 0) {
+ EXPECT_EQ(details::FuncFunctor<decltype(func_unpacked_0)>::Sig(),
+ "(0: int, 1: float, 2: const char *, 3: float) -> float");
+}
+
+TEST(Func_Signature, 1) {
+ EXPECT_EQ(details::FuncFunctor<decltype(func_unpacked_1)>::Sig(),
+ "(0: dtype, 1: Device, 2: str) -> void");
+}
+
+TEST(Func_Signature, AnyView_Arg) {
+ EXPECT_EQ(details::FuncFunctor<decltype(func_unpacked_anyview_arg<
+ TVMFFITypeIndex::kTVMFFIInt>)>::Sig(),
+ "(0: AnyView) -> void");
+}
+
+TEST(Func_Signature, AnyView_Ret) {
+ EXPECT_EQ(details::FuncFunctor<decltype(func_unpacked_anyview_ret)>::Sig(),
+ "() -> AnyView");
+}
+
+TEST(Func_Signature, Any_Arg) {
+ EXPECT_EQ(
+ details::FuncFunctor<
+ decltype(func_unpacked_any_arg<TVMFFITypeIndex::kTVMFFIInt>)>::Sig(),
+ "(0: Any) -> void");
+}
+
+TEST(Func_Signature, Any_Ret) {
+ EXPECT_EQ(details::FuncFunctor<decltype(func_unpacked_any_ret)>::Sig(),
+ "() -> Any");
+}
+
+TEST(Func_Unpacked_Invoke, Func0_RawStr) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_0);
+ double ret = func(1, 2, c_str_raw, 4);
+ EXPECT_DOUBLE_EQ(ret, 7);
+ double ret2 = func(1, 2, std::string("Hello"), 4);
+ EXPECT_DOUBLE_EQ(ret2, 7);
+}
+
+TEST(Func_Unpacked_Invoke, Func0_StdString_Move) {
+ std::string str = c_str_raw;
+ Ref<Func> func = Ref<Func>::New(func_unpacked_0);
+ double ret = func(1, 2, std::move(str), 4);
+ EXPECT_DOUBLE_EQ(ret, 7);
+}
+
+TEST(Func_Unpacked_Invoke, Func0_StdString_Copy) {
+ std::string str = c_str_raw;
+ Ref<Func> func = Ref<Func>::New(func_unpacked_0);
+ double ret = func(1, 2, str, 4);
+ EXPECT_DOUBLE_EQ(ret, 7);
+ EXPECT_EQ(str, c_str_raw);
+}
+
+TEST(Func_Unpacked_Invoke, Func1) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_1);
+ func(DLDataType{kDLInt, 32, 1}, DLDevice{kDLCPU, 0}, "Hello");
+}
+
+TEST(Func_Unpacked_Invoke, AnyView_Arg) {
+ Ref<Func> func =
+ Ref<Func>::New(func_unpacked_anyview_arg<TVMFFITypeIndex::kTVMFFIInt>);
+ func(1);
+}
+
+TEST(Func_Unpacked_Invoke, AnyView_Ret) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_anyview_ret);
+ int ret = func();
+ EXPECT_EQ(ret, 1);
+}
+
+TEST(Func_Unpacked_Invoke, Any_Arg) {
+ Ref<Func> func =
+ Ref<Func>::New(func_unpacked_any_arg<TVMFFITypeIndex::kTVMFFIInt>);
+ func(1);
+}
+
+TEST(Func_Unpacked_Invoke, Any_Ret) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_any_ret);
+ int ret = func();
+ EXPECT_EQ(ret, 1);
+}
+
+TEST(Func_Unpacked_Invoke, StrObj) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_str_obj);
+ std::string ret = func("Hello", "Hello");
+ EXPECT_EQ(ret, "Hello");
+}
+
+TEST(Func_Packed_Invoke, 0) {
+ Ref<Func> func = Ref<Func>::New(func_packed_0);
+ int ret = func();
+ EXPECT_EQ(ret, 0);
+}
+
+TEST(Func_Packed_Invoke, 1) {
+ Ref<Func> func = Ref<Func>::New(func_packed_0);
+ int ret = func(1.0);
+ EXPECT_EQ(ret, 1);
+}
+
+TEST(Func_Packed_Invoke, 2) {
+ Ref<Func> func = Ref<Func>::New(func_packed_0);
+ int ret = func(1.0, "test");
+ EXPECT_EQ(ret, 2);
+}
+
+TEST(Func_Packed_Invoke, 4) {
+ Ref<Func> func = Ref<Func>::New(func_packed_0);
+ int ret = func(1.0, "test", DLDataType{kDLInt, 32, 1}, DLDevice{kDLCPU, 0});
+ EXPECT_EQ(ret, 4);
+}
+
+TEST(Func_Unpacked_Invoke_TypeError, TypeMismatch_0) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_0);
+ try {
+ func(1.0, 2, c_str_raw, 4);
+ FAIL() << "No execption thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(),
+ "Mismatched type on argument #0 when calling: "
+ "`(0: int, 1: float, 2: const char *, 3: float) -> float`. "
+ "Expected `int` but got `float`");
+ }
+}
+
+TEST(Func_Unpacked_Invoke_TypeError, TypeMismatch_1) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_1);
+ try {
+ func(DLDataType{kDLInt, 32, 1}, DLDevice{kDLCPU, 0}, 1);
+ FAIL() << "No execption thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(), "Mismatched type on argument #2 when calling: "
+ "`(0: dtype, 1: Device, 2: str) -> void`. "
+ "Expected `str` but got `int`");
+ }
+}
+
+TEST(Func_Unpacked_Invoke_TypeError, ArgCountMismatch_0) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_0);
+ try {
+ func(1, 2, c_str_raw);
+ FAIL() << "No execption thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(),
+ "Mismatched number of arguments when calling: "
+ "`(0: int, 1: float, 2: const char *, 3: float) -> float`. "
+ "Expected 4 but got 3 arguments");
+ }
+}
+
+TEST(Func_Unpacked_Invoke_TypeError, ArgCountMismatch_1) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_1);
+ try {
+ func(DLDataType{kDLInt, 32, 1}, DLDevice{kDLCPU, 0});
+ FAIL() << "No execption thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(), "Mismatched number of arguments when calling: "
+ "`(0: dtype, 1: Device, 2: str) -> void`. "
+ "Expected 3 but got 2 arguments");
+ }
+}
+
+TEST(Func_Unpacked_Invoke_TypeError, ReturnTypeMismatch_0) {
+ Ref<Func> func = Ref<Func>::New(func_unpacked_1);
+ try {
+ int ret = func(DLDataType{kDLInt, 32, 1}, DLDevice{kDLCPU, 0}, "Hello");
+ (void)ret;
+ FAIL() << "No execption thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(), "Cannot convert from type `None` to `int`");
+ }
+}
+
+} // namespace
diff --git a/ffi/tests/cpp/test_list.cc b/ffi/tests/cpp/test_list.cc
new file mode 100644
index 0000000000..53fd727af6
--- /dev/null
+++ b/ffi/tests/cpp/test_list.cc
@@ -0,0 +1,461 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+
+namespace {
+
+using namespace tvm::ffi;
+
+bool DTypeEqual(DLDataType a, DLDataType b) {
+ return a.code == b.code && a.bits == b.bits && a.lanes == b.lanes;
+}
+bool DeviceEqual(DLDevice a, DLDevice b) {
+ return a.device_type == b.device_type && a.device_id == b.device_id;
+}
+
+void TestSizeCapacityClear(Ref<List> *list, int64_t size, int64_t capacity) {
+ EXPECT_EQ(list->size(), size);
+ EXPECT_EQ(list->capacity(), capacity);
+ EXPECT_EQ(list->empty(), size == 0);
+ list->clear();
+ EXPECT_EQ(list->size(), 0);
+ EXPECT_EQ(list->capacity(), capacity);
+ EXPECT_EQ(list->empty(), true);
+}
+
+TEST(List_Constructor, Default) {
+ Ref<List> list = Ref<List>::New();
+ TVMFFIList *list_ptr = reinterpret_cast<TVMFFIList *>(list.get());
+ ASSERT_NE(list_ptr, nullptr);
+ EXPECT_EQ(list_ptr->type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIList));
+ EXPECT_EQ(list_ptr->ref_cnt, 1);
+ EXPECT_NE(list_ptr->deleter, nullptr);
+ EXPECT_EQ(list_ptr->list_capacity, 0);
+ EXPECT_EQ(list_ptr->list_length, 0);
+ EXPECT_EQ(list_ptr->pool_capacity, 0);
+ EXPECT_EQ(list_ptr->pool_length, 0);
+ TestSizeCapacityClear(&list, 0, 0);
+}
+
+TEST(List_Constructor, InitializerList) {
+ Ref<List> list1{
+ 100, 1.0f, "Hi", DLDataType{kDLInt, 32, 1}, DLDevice{kDLCPU, 0}, Ref<Object>::New(),
+ Ref<Object>()};
+ Ref<List> list2 = {
+ 100, 1.0f, "Hi", DLDataType{kDLInt, 32, 1}, DLDevice{kDLCPU, 0}, Ref<Object>::New(),
+ Ref<Object>()};
+
+ auto test = [](Ref<List> *src) {
+ auto *list_ptr = reinterpret_cast<const TVMFFIList *>(src->get());
+ ASSERT_NE(list_ptr, nullptr);
+ EXPECT_EQ(list_ptr->type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIList));
+ EXPECT_EQ(list_ptr->ref_cnt, 1);
+ EXPECT_NE(list_ptr->deleter, nullptr);
+ EXPECT_EQ(list_ptr->list_capacity, 7);
+ EXPECT_EQ(list_ptr->list_length, 7);
+ EXPECT_EQ(list_ptr->pool_capacity, 7);
+ EXPECT_EQ(list_ptr->pool_length, 4); // string is not in the POD pool
+ EXPECT_EQ(src->size(), 7);
+ EXPECT_EQ(src->capacity(), 7);
+ EXPECT_EQ(src->empty(), false);
+ TestSizeCapacityClear(src, 7, 7);
+ };
+ test(&list1);
+ test(&list2);
+}
+
+TEST(List_PushBack, POD) {
+ Ref<List> list;
+ ASSERT_NE(list.get(), nullptr);
+ list.push_back(100);
+ list.push_back(1.0f);
+ TVMFFIList *list_ptr = reinterpret_cast<TVMFFIList *>(list.get());
+ ASSERT_NE(list_ptr, nullptr);
+ EXPECT_EQ(list_ptr->type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIList));
+ EXPECT_EQ(list_ptr->ref_cnt, 1);
+ EXPECT_NE(list_ptr->deleter, nullptr);
+ EXPECT_EQ(list_ptr->list_capacity, List::kMinCapacity);
+ EXPECT_EQ(list_ptr->list_length, 2);
+ EXPECT_EQ(list_ptr->pool_capacity, List::kMinCapacity);
+ EXPECT_EQ(list_ptr->pool_length, 2);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+ TestSizeCapacityClear(&list, 2, List::kMinCapacity);
+}
+
+TEST(List_PushBack, Obj) {
+ Ref<List> list;
+ Ref<Object> obj1 = Ref<Object>::New();
+ Ref<Object> obj2 = Ref<Object>::New();
+ list.push_back(obj1);
+ list.push_back(obj2);
+ TVMFFIList *list_ptr = reinterpret_cast<TVMFFIList *>(list.get());
+ ASSERT_NE(list_ptr, nullptr);
+ EXPECT_EQ(list_ptr->type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIList));
+ EXPECT_EQ(list_ptr->ref_cnt, 1);
+ EXPECT_NE(list_ptr->deleter, nullptr);
+ EXPECT_EQ(list_ptr->list_capacity, List::kMinCapacity);
+ EXPECT_EQ(list_ptr->list_length, 2);
+ EXPECT_EQ(list_ptr->pool_capacity, 0);
+ EXPECT_EQ(list_ptr->pool_length, 0);
+ EXPECT_EQ((Object *)(list[0]), obj1.get());
+ EXPECT_EQ((Object *)(list[1]), obj2.get());
+ TestSizeCapacityClear(&list, 2, List::kMinCapacity);
+}
+
+TEST(List_PushBack, Heterogeneous) {
+ constexpr int n = 128;
+ constexpr int k = 8;
+ constexpr int expected_size = n * k;
+ constexpr int expected_capacity = 1024;
+ constexpr int expected_pool_capacity = 1024;
+ constexpr int expected_pool_length = 512;
+ int64_t integer = 100;
+ double fp = 1.0;
+ std::string str = "Hi";
+ DLDataType dtype{kDLInt, 32, 1};
+ DLDevice device{kDLCPU, 0};
+ Ref<Object> obj = Ref<Object>::New();
+ Ref<Object> null_obj{nullptr};
+ std::string long_str(1024, 'a');
+
+ Ref<List> list = Ref<List>::New();
+ {
+ std::string long_str_copy(1024, 'a');
+ for (int i = 0; i < n; ++i) {
+ list.push_back(integer);
+ list.push_back(fp);
+ list.push_back(str);
+ list.push_back(dtype);
+ list.push_back(device);
+ list.push_back(obj);
+ list.push_back(null_obj);
+ list.push_back(long_str_copy);
+ }
+ }
+ for (int i = 0; i < n; ++i) {
+ int64_t i_0 = list[i * k];
+ double i_1 = list[i * k + 1];
+ std::string i_2 = list[i * k + 2];
+ DLDataType i_3 = list[i * k + 3];
+ DLDevice i_4 = list[i * k + 4];
+ Object *i_5 = list[i * k + 5];
+ Object *i_6 = list[i * k + 6];
+ const char *i_7 = list[i * k + 7];
+ EXPECT_EQ(i_0, integer);
+ EXPECT_DOUBLE_EQ(i_1, fp);
+ EXPECT_EQ(i_2, str);
+ EXPECT_PRED2(DTypeEqual, i_3, dtype);
+ EXPECT_PRED2(DeviceEqual, i_4, device);
+ EXPECT_EQ(i_5, obj.get());
+ EXPECT_EQ(i_6, nullptr);
+ EXPECT_STREQ(i_7, long_str.c_str());
+ }
+ auto *list_ptr = reinterpret_cast<const TVMFFIList *>(list.get());
+ EXPECT_EQ(list_ptr->list_capacity, expected_capacity);
+ EXPECT_EQ(list_ptr->list_length, expected_size);
+ EXPECT_EQ(list_ptr->pool_capacity, expected_pool_capacity);
+ EXPECT_EQ(list_ptr->pool_length, expected_pool_length);
+}
+
+TEST(List_Insert, Once) {
+ Ref<List> values = {100,
+ 1.0,
+ "Hi", //
+ DLDataType{kDLInt, 32, 1},
+ DLDevice{kDLCPU, 0},
+ Ref<Object>::New(),
+ Ref<Object>(),
+ std::string(1024, 'a')};
+ int n = values.size();
+ for (int pos = 0; pos <= n; ++pos) {
+ for (AnyView data : values) {
+ // Test: insert at `pos` with value `data`
+ Ref<List> list(values.begin(), values.end());
+ list.insert(pos, data);
+ auto test = [](AnyView expected, AnyView actual) {
+ EXPECT_EQ(expected.type_index, actual.type_index);
+ EXPECT_EQ(expected.v_int64, actual.v_int64);
+ };
+ for (int i = 0; i < pos; ++i) {
+ test(values[i], list[i]);
+ }
+ for (int i = pos; i < n; ++i) {
+ test(values[i], list[i + 1]);
+ }
+ test(data, list[pos]);
+ }
+ }
+}
+
+TEST(List_Insert, Error_0) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ try {
+ list.insert(-1, 1.0);
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(), "Indexing `-1` of a list of size 3");
+ }
+}
+
+TEST(List_Insert, Error_1) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ try {
+ list.insert(4, 1.0);
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(), "Indexing `4` of a list of size 3");
+ }
+}
+
+TEST(List_Resize, Shrink) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ list.resize(2);
+ EXPECT_EQ(list.size(), 2);
+ EXPECT_EQ(list.capacity(), 3);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+}
+
+TEST(List_Resize, Expand) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ list.resize(4);
+ EXPECT_EQ(list.size(), 4);
+ EXPECT_EQ(list.capacity(), 6);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+ EXPECT_STREQ(list[2], "Hi");
+ EXPECT_EQ(list[3].operator void *(), nullptr);
+}
+
+TEST(List_Reserve, Shrink) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ list.reserve(2);
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+ EXPECT_STREQ(list[2], "Hi");
+}
+
+TEST(List_Reserve, Expand) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ list.reserve(4);
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 6);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+ EXPECT_STREQ(list[2], "Hi");
+}
+
+TEST(List_SetItem, PodToPod) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ for (int i = 0; i < 16; ++i) {
+ list[1] = i;
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_EQ(int32_t(list[1]), i);
+ EXPECT_STREQ(list[2], "Hi");
+ }
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ TVMFFIList *list_ptr = reinterpret_cast<TVMFFIList *>(list.get());
+ EXPECT_EQ(list_ptr->list_capacity, 3);
+ EXPECT_EQ(list_ptr->list_length, 3);
+ EXPECT_EQ(list_ptr->pool_capacity, 24);
+ EXPECT_EQ(list_ptr->pool_length, 3);
+}
+
+TEST(List_SetItem, ObjToPod) {
+ Ref<List> list = {100, 1.0, "Hi"}; //
+ for (int i = 0; i < 16; ++i) {
+ list[2] = i;
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+ EXPECT_EQ(int32_t(list[2]), i);
+ }
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ TVMFFIList *list_ptr = reinterpret_cast<TVMFFIList *>(list.get());
+ EXPECT_EQ(list_ptr->list_capacity, 3);
+ EXPECT_EQ(list_ptr->list_length, 3);
+ EXPECT_EQ(list_ptr->pool_capacity, 24);
+ EXPECT_EQ(list_ptr->pool_length, 6);
+}
+
+TEST(List_SetItem, PodToObj) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ for (int i = 0; i < 1; ++i) {
+ Ref<Object> obj = Ref<Object>::New();
+ list[0] = obj;
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ EXPECT_EQ((Object *)(list[0]), obj.get());
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+ EXPECT_STREQ(list[2], "Hi");
+ }
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ TVMFFIList *list_ptr = reinterpret_cast<TVMFFIList *>(list.get());
+ EXPECT_EQ(list_ptr->list_capacity, 3);
+ EXPECT_EQ(list_ptr->list_length, 3);
+ EXPECT_EQ(list_ptr->pool_capacity, 3);
+ EXPECT_EQ(list_ptr->pool_length, 2);
+}
+
+TEST(List_SetItem, ObjToObj) {
+ Ref<List> list = {100, 1.0, "Hi"};
+ for (int i = 0; i < 1; ++i) {
+ Ref<Object> obj = Ref<Object>::New();
+ list[2] = obj;
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+ EXPECT_EQ((Object *)(list[2]), obj.get());
+ }
+ EXPECT_EQ(list.size(), 3);
+ EXPECT_EQ(list.capacity(), 3);
+ TVMFFIList *list_ptr = reinterpret_cast<TVMFFIList *>(list.get());
+ EXPECT_EQ(list_ptr->list_capacity, 3);
+ EXPECT_EQ(list_ptr->list_length, 3);
+ EXPECT_EQ(list_ptr->pool_capacity, 3);
+ EXPECT_EQ(list_ptr->pool_length, 2);
+}
+
+TEST(List_PopBack, Heterogeneous) {
+ int64_t integer = 100;
+ double fp = 1.0;
+ std::string str = "Hi";
+ DLDataType dtype{kDLInt, 32, 1};
+ DLDevice device{kDLCPU, 0};
+ Ref<Object> obj = Ref<Object>::New();
+ Ref<Object> null_obj{nullptr};
+ Ref<List> list{integer, fp, str, dtype, device, obj, null_obj};
+ int n = static_cast<int32_t>(list.size());
+ for (int i = 0; i < n; ++i) {
+ list.pop_back();
+ EXPECT_EQ(list.size(), n - 1 - i);
+ EXPECT_EQ(list.capacity(), n);
+ int m = static_cast<int32_t>(list.size());
+ if (m > 0) {
+ EXPECT_EQ(int32_t(list[0]), integer);
+ }
+ if (m > 1) {
+ EXPECT_DOUBLE_EQ(double(list[1]), fp);
+ }
+ if (m > 2) {
+ EXPECT_STREQ(list[2], str.c_str());
+ }
+ if (m > 3) {
+ EXPECT_PRED2(DTypeEqual, DLDataType(list[3]), dtype);
+ }
+ if (m > 4) {
+ EXPECT_PRED2(DeviceEqual, DLDevice(list[4]), device);
+ }
+ if (m > 5) {
+ EXPECT_EQ((Object *)(list[5]), obj.get());
+ }
+ if (m > 6) {
+ EXPECT_EQ((Object *)(list[6]), nullptr);
+ }
+ }
+ EXPECT_EQ(list.size(), 0);
+ EXPECT_EQ(list.capacity(), n);
+ EXPECT_EQ(list.empty(), true);
+ EXPECT_EQ(list.begin(), list.end());
+ try {
+ list.pop_back();
+ FAIL() << "No exception thrown";
+ } catch (TVMError &ex) {
+ EXPECT_STREQ(ex.what(), "Indexing `-1` of a list of size 0");
+ }
+}
+
+TEST(List_Erase, Front) {
+ int64_t integer = 100;
+ double fp = 1.0;
+ std::string str = "Hi";
+ DLDataType dtype{kDLInt, 32, 1};
+ DLDevice device{kDLCPU, 0};
+ Ref<Object> obj = Ref<Object>::New();
+ Ref<Object> null_obj{nullptr};
+ Ref<List> list{integer, fp, str, dtype, device, obj, null_obj};
+ list.erase(0);
+ EXPECT_EQ(list.size(), 6);
+ EXPECT_EQ(list.capacity(), 7);
+ EXPECT_EQ(double(list[0]), 1.0);
+ EXPECT_STREQ(list[1], "Hi");
+ EXPECT_PRED2(DTypeEqual, DLDataType(list[2]), dtype);
+ EXPECT_PRED2(DeviceEqual, DLDevice(list[3]), device);
+ EXPECT_EQ((Object *)(list[4]), obj.get());
+ EXPECT_EQ((Object *)(list[5]), nullptr);
+}
+
+TEST(List_Erase, Back) {
+ int64_t integer = 100;
+ double fp = 1.0;
+ std::string str = "Hi";
+ DLDataType dtype{kDLInt, 32, 1};
+ DLDevice device{kDLCPU, 0};
+ Ref<Object> obj = Ref<Object>::New();
+ Ref<Object> null_obj{nullptr};
+ Ref<List> list{integer, fp, str, dtype, device, obj, null_obj};
+ list.erase(0);
+ EXPECT_EQ(list.size(), 6);
+ EXPECT_EQ(list.capacity(), 7);
+ EXPECT_EQ(double(list[0]), 1.0);
+ EXPECT_STREQ(list[1], "Hi");
+ EXPECT_PRED2(DTypeEqual, DLDataType(list[2]), dtype);
+ EXPECT_PRED2(DeviceEqual, DLDevice(list[3]), device);
+ EXPECT_EQ((Object *)(list[4]), obj.get());
+ EXPECT_EQ((Object *)(list[5]), nullptr);
+}
+
+TEST(List_Erase, Mid) {
+ int64_t integer = 100;
+ double fp = 1.0;
+ std::string str = "Hi";
+ DLDataType dtype{kDLInt, 32, 1};
+ DLDevice device{kDLCPU, 0};
+ Ref<Object> obj = Ref<Object>::New();
+ Ref<Object> null_obj{nullptr};
+ Ref<List> list{integer, fp, str, dtype, device, obj, null_obj};
+ list.erase(3);
+ EXPECT_EQ(list.size(), 6);
+ EXPECT_EQ(list.capacity(), 7);
+ EXPECT_EQ(int32_t(list[0]), 100);
+ EXPECT_DOUBLE_EQ(double(list[1]), 1.0);
+ EXPECT_STREQ(list[2], "Hi");
+ EXPECT_PRED2(DeviceEqual, DLDevice(list[3]), device);
+ EXPECT_EQ((Object *)(list[4]), obj.get());
+ EXPECT_EQ((Object *)(list[5]), nullptr);
+}
+
+TEST(List_Iter, Test) {
+ Ref<List> list;
+ for (int i = 0; i < 16; ++i) {
+ list.push_back(i * i);
+ }
+ int i = 0;
+ for (int item : list) {
+ EXPECT_EQ(i * i, item);
+ ++i;
+ }
+}
+
+TEST(List_RevIter, Test) {
+ Ref<List> list;
+ for (int i = 0; i < 16; ++i) {
+ list.push_back(i * i);
+ }
+ int i = list.size() - 1;
+ for (auto it = list.rbegin(); it != list.rend(); ++it) {
+ EXPECT_EQ(i * i, int32_t(*it));
+ --i;
+ }
+}
+
+} // namespace
diff --git a/ffi/tests/cpp/test_ref.cc b/ffi/tests/cpp/test_ref.cc
new file mode 100644
index 0000000000..3c9dc0561d
--- /dev/null
+++ b/ffi/tests/cpp/test_ref.cc
@@ -0,0 +1,293 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+#include <unordered_map>
+
+namespace {
+using namespace tvm::ffi;
+using tvm::ffi::details::StrPad;
+using tvm::ffi::details::StrStd;
+
+using ObjDeleter = void (*)(void *);
+
+struct AllocRecorder {
+ std::unordered_map<void *, ObjDeleter> deleters;
+
+ void Alloc(void *ptr) {
+ deleters[ptr] = reinterpret_cast<TVMFFIAny *>(ptr)->deleter;
+ reinterpret_cast<TVMFFIAny *>(ptr)->deleter = AllocRecorder::Deleter;
+ }
+
+ void Delete(void *ptr) {
+ ASSERT_EQ(deleters.count(ptr), 1);
+ ObjDeleter d = this->deleters[ptr];
+ d(ptr);
+ deleters.erase(ptr);
+ }
+
+ bool IsDeletedImpl(void *ptr) { return deleters.count(ptr) == 0; }
+
+ static void Deleter(void *ptr) { AllocRecorder::Global()->Delete(ptr); }
+
+ static bool IsDeleted(void *ptr) { return AllocRecorder::Global()->IsDeletedImpl(ptr); }
+
+ static AllocRecorder *Global() {
+ static AllocRecorder inst;
+ return &inst;
+ }
+};
+
+template <typename ObjectType>
+struct TestAllocator {
+ using Allocator = typename ::tvm::ffi::GetAllocator<ObjectType>::Type;
+
+ template <typename... Args>
+ TVM_FFI_INLINE static ObjectType *New(Args &&...args) {
+ ObjectType *ret = Allocator::New(std::forward<Args>(args)...);
+ AllocRecorder::Global()->Alloc(ret);
+ return ret;
+ }
+
+ template <typename PadType, typename... Args>
+ TVM_FFI_INLINE static ObjectType *NewWithPad(size_t pad_size, Args &&...args) {
+ ObjectType *ret =
+ Allocator::template NewWithPad<PadType>(pad_size, std::forward<Args>(args)...);
+ AllocRecorder::Global()->Alloc(ret);
+ return ret;
+ }
+};
+
+int64_t FuncCall(int64_t x) { return x + 1; }
+
+int32_t GetRefCount(void *obj) { return reinterpret_cast<TVMFFIAny *>(obj)->ref_cnt; }
+
+int32_t GetTypeIndex(void *obj) { return reinterpret_cast<TVMFFIAny *>(obj)->type_index; }
+
+ObjDeleter GetDeleter(void *obj) { return reinterpret_cast<TVMFFIAny *>(obj)->deleter; }
+
+TEST(Ref_Constructor_0_Default, Default) {
+ Ref<Object> ref;
+ EXPECT_EQ(ref.get(), nullptr);
+}
+
+TEST(Ref_Constructor_1_Ptr, SameType) {
+ Object *obj = TestAllocator<Object>::New();
+ {
+ Ref<Object> ref(obj);
+ EXPECT_EQ(ref.get(), obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_1_Ptr, SubType) {
+ Str *obj = TestAllocator<Str>::New("Hello world");
+ {
+ Ref<Object> ref(obj);
+ EXPECT_EQ(ref.get(), static_cast<void *>(obj));
+ EXPECT_EQ(GetRefCount(obj), 1);
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_2_Ref, SameType_Copy) {
+ Object *obj = TestAllocator<Object>::New();
+ {
+ Ref<Object> ref1(obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ Ref<Object> ref2(ref1);
+ EXPECT_EQ(GetRefCount(obj), 2);
+ }
+ EXPECT_EQ(GetRefCount(ref1.get()), 1);
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_2_Ref, SameType_Move) {
+ Object *obj = TestAllocator<Object>::New();
+ {
+ Ref<Object> ref1(obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ Ref<Object> ref2(std::move(ref1));
+ EXPECT_EQ(GetRefCount(obj), 1);
+ EXPECT_EQ(ref1.get(), nullptr);
+ }
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_2_Ref, SubType_Copy) {
+ Str *obj = TestAllocator<Str>::New("Hello world");
+ {
+ Ref<Str> ref1(obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ Ref<Object> ref2(ref1);
+ EXPECT_EQ(GetRefCount(obj), 2);
+ }
+ EXPECT_EQ(GetRefCount(obj), 1);
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_2_Ref, SubType_Move) {
+ Str *obj = TestAllocator<Str>::New("Hello world");
+ {
+ Ref<Str> ref1(obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ Ref<Object> ref2(std::move(ref1));
+ EXPECT_EQ(GetRefCount(obj), 1);
+ EXPECT_EQ(ref1.get(), nullptr);
+ }
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_3_AnyView, Copy) {
+ Object *obj = TestAllocator<Object>::New();
+ {
+ Ref<Object> ref1(obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ AnyView view(ref1);
+ Ref<Object> ref2(view);
+ EXPECT_EQ(GetRefCount(obj), 2);
+ }
+ EXPECT_EQ(GetRefCount(obj), 1);
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_3_AnyView, Move) {
+ Object *obj = TestAllocator<Object>::New();
+ {
+ Ref<Object> ref1(obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ AnyView view(ref1);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ Ref<Object> ref2(std::move(view));
+ EXPECT_EQ(GetRefCount(obj), 2);
+ EXPECT_EQ(view.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone));
+ EXPECT_EQ(view.ref_cnt, 0);
+ EXPECT_EQ(view.v_int64, 0);
+ }
+ EXPECT_EQ(GetRefCount(obj), 1);
+ }
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_4_Any, Copy) {
+ Object *obj = TestAllocator<Object>::New();
+ {
+ Ref<Object> ref1(obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ Any any(ref1);
+ EXPECT_EQ(GetRefCount(obj), 2);
+ Ref<Object> ref2(any);
+ EXPECT_EQ(GetRefCount(obj), 3);
+ }
+ EXPECT_EQ(GetRefCount(obj), 1);
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_Constructor_4_Any, Move) {
+ Object *obj = TestAllocator<Object>::New();
+ {
+ Ref<Object> ref1(obj);
+ EXPECT_EQ(GetRefCount(obj), 1);
+ {
+ Any any(ref1);
+ EXPECT_EQ(GetRefCount(obj), 2);
+ {
+ Ref<Object> ref2(std::move(any));
+ EXPECT_EQ(GetRefCount(obj), 2);
+ EXPECT_EQ(any.type_index, static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone));
+ EXPECT_EQ(any.ref_cnt, 0);
+ EXPECT_EQ(any.v_int64, 0);
+ }
+ EXPECT_EQ(GetRefCount(obj), 1);
+ }
+ }
+ EXPECT_TRUE(AllocRecorder::IsDeleted(obj));
+}
+
+TEST(Ref_New, Object) {
+ Ref<Object> ref = Ref<Object>::New();
+ EXPECT_EQ(GetTypeIndex(ref.get()), static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject));
+ EXPECT_EQ(GetRefCount(ref.get()), 1);
+}
+
+TEST(Ref_New, Func) {
+ Ref<Func> ref = Ref<Func>::New(FuncCall);
+ EXPECT_EQ(GetTypeIndex(ref.get()), static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFunc));
+ EXPECT_EQ(GetRefCount(ref.get()), 1);
+}
+
+TEST(Ref_New, RawStr) {
+ const char *str = "Hello world";
+ Ref<Str> ref = Ref<Str>::New(str);
+ EXPECT_EQ(GetTypeIndex(ref.get()), static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_EQ(GetRefCount(ref.get()), 1);
+ EXPECT_EQ(GetDeleter(ref.get()), DefaultObjectAllocator<StrPad>::Deleter);
+}
+
+TEST(Ref_New, CharArray) {
+ const char str[18] = "Hello world";
+ Ref<Str> ref = Ref<Str>::New(str);
+ EXPECT_EQ(GetTypeIndex(ref.get()), static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_EQ(GetRefCount(ref.get()), 1);
+ EXPECT_EQ(GetDeleter(ref.get()), DefaultObjectAllocator<StrPad>::Deleter);
+ EXPECT_EQ(ref->size(), 17);
+}
+
+TEST(Ref_New, StdString_Copy) {
+ std::string str = "Hello world";
+ Ref<Str> ref = Ref<Str>::New(str);
+ EXPECT_EQ(GetTypeIndex(ref.get()), static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_EQ(GetRefCount(ref.get()), 1);
+ EXPECT_EQ(GetDeleter(ref.get()), DefaultObjectAllocator<StrPad>::Deleter);
+}
+
+TEST(Ref_New, StdString_Move) {
+ std::string str = "Hello world";
+ Ref<Str> ref = Ref<Str>::New(std::move(str));
+ EXPECT_EQ(GetTypeIndex(ref.get()), static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_EQ(GetRefCount(ref.get()), 1);
+ EXPECT_EQ(GetDeleter(ref.get()), DefaultObjectAllocator<StrStd>::Deleter);
+}
+
+TEST(Ref_Stringify, Object) {
+ std::string str = Ref<Object>::New().str()->c_str();
+ std::string expected_prefix = "object.Object@0";
+ EXPECT_GT(str.size(), expected_prefix.size());
+ EXPECT_EQ(str.substr(0, expected_prefix.size()), expected_prefix);
+}
+
+TEST(Ref_Stringify, Func) {
+ std::string str = Ref<Func>::New(FuncCall).str()->c_str();
+ std::string expected_prefix = "object.Func@0";
+ EXPECT_GT(str.size(), expected_prefix.size());
+ EXPECT_EQ(str.substr(0, expected_prefix.size()), expected_prefix);
+}
+
+TEST(Ref_Stringify, Str) {
+ std::string str = Ref<Str>::New("Hello world").str()->c_str();
+ EXPECT_EQ(str, "\"Hello world\"");
+}
+
+TEST(Ref_Misc, MoveToRaw) {
+ TVMFFIAny *str = reinterpret_cast<TVMFFIAny *>(Ref<Str>::New("Hello world").MoveToRawObjPtr());
+ EXPECT_EQ(GetTypeIndex(str), static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_EQ(GetRefCount(str), 1);
+ EXPECT_EQ(GetDeleter(str), DefaultObjectAllocator<StrPad>::Deleter);
+ str->deleter(str);
+}
+
+} // namespace
diff --git a/ffi/tests/cpp/test_str.cc b/ffi/tests/cpp/test_str.cc
new file mode 100644
index 0000000000..bd91f241f6
--- /dev/null
+++ b/ffi/tests/cpp/test_str.cc
@@ -0,0 +1,47 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+
+namespace {
+using namespace tvm::ffi;
+
+const char c_str_long[] = "Hello, World! This is an extremely long string to "
+ "avoid any on-stack optimization.";
+
+TEST(Str, CopyFromStdString) {
+ std::string std_str = "Hello, World!";
+ Ref<Str> str = Ref<Str>::New(std_str);
+ EXPECT_EQ(str->size(), std_str.size());
+ EXPECT_STREQ(str->c_str(), std_str.c_str());
+ EXPECT_STREQ(str->data(), std_str.data());
+}
+
+TEST(Str, MoveFromStdString_0) {
+ std::string std_str = c_str_long;
+ const void *data = std_str.data();
+ Ref<Str> str = Ref<Str>::New(std::move(std_str));
+ EXPECT_EQ(static_cast<const void *>(str->data()), data);
+ EXPECT_STREQ(str->c_str(), c_str_long);
+}
+
+TEST(Str, MoveFromStdString_1) {
+ Ref<Str> str = Ref<Str>::New(std::string(c_str_long));
+ EXPECT_STREQ(str->c_str(), c_str_long);
+ EXPECT_EQ(str->size(), sizeof(c_str_long) - 1);
+}
+
+TEST(Str, CopyFromCStr) {
+ Ref<Str> str = Ref<Str>::New(c_str_long);
+ EXPECT_STREQ(str->c_str(), c_str_long);
+ EXPECT_EQ(str->size(), sizeof(c_str_long) - 1);
+}
+
+TEST(Str, CopyFromCharArray) {
+ const char c_str[18] = "Hello, World!";
+ Ref<Str> str = Ref<Str>::New(c_str);
+ EXPECT_EQ(sizeof(c_str), 18);
+ EXPECT_EQ(strlen(c_str), 13);
+ EXPECT_STREQ(str->c_str(), c_str);
+ EXPECT_EQ(str->size(), 17);
+}
+
+} // namespace
diff --git a/ffi/tests/cpp/test_type_dyn.cc b/ffi/tests/cpp/test_type_dyn.cc
new file mode 100644
index 0000000000..45910528b3
--- /dev/null
+++ b/ffi/tests/cpp/test_type_dyn.cc
@@ -0,0 +1,71 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+
+#if TVM_FFI_ALLOW_DYN_TYPE == 1
+
+namespace {
+using namespace tvm::ffi;
+
+struct TestObj : public Object {
+ int x;
+ explicit TestObj(int x) : x(x) {}
+ TVM_FFI_DEF_DYN_TYPE(TestObj, Object, "test.TestObj");
+};
+
+struct SubTestObj : public TestObj {
+ int y;
+ explicit SubTestObj(int x, int y) : TestObj(x), y(y) {}
+ TVM_FFI_DEF_DYN_TYPE(SubTestObj, TestObj, "test.SubTestObj");
+};
+
+void CheckAncestor(int32_t num, const int32_t *ancestors,
+ std::vector<int32_t> expected) {
+ EXPECT_EQ(num, expected.size());
+ for (int i = 0; i < num; ++i) {
+ EXPECT_EQ(ancestors[i], expected[i]);
+ }
+}
+
+TEST(DynTypeInfo, TestObj) {
+ EXPECT_GE(TestObj::_type_index,
+ static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDynObjectBegin));
+ EXPECT_STRCASEEQ(TestObj::_type_key, "test.TestObj");
+ EXPECT_EQ(TestObj::_type_depth, 1);
+ CheckAncestor(TestObj::_type_depth, TestObj::_type_ancestors.data(),
+ {static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject)});
+}
+
+TEST(DynTypeInfo, SubTestObj) {
+ EXPECT_GE(SubTestObj::_type_index,
+ static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDynObjectBegin));
+ EXPECT_NE(SubTestObj::_type_index, TestObj::_type_index);
+ EXPECT_STRCASEEQ(SubTestObj::_type_key, "test.SubTestObj");
+ EXPECT_EQ(SubTestObj::_type_depth, 2);
+ CheckAncestor(SubTestObj::_type_depth, SubTestObj::_type_ancestors.data(),
+ {static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject),
+ TestObj::_type_index});
+}
+
+TEST(DynTypeInheritance, TestObj) {
+ Ref<TestObj> obj = Ref<TestObj>::New(10);
+ EXPECT_EQ(obj->x, 10);
+ EXPECT_TRUE(obj->IsInstance<Object>());
+ EXPECT_TRUE(obj->IsInstance<TestObj>());
+ EXPECT_FALSE(obj->IsInstance<Func>());
+ EXPECT_FALSE(obj->IsInstance<Str>());
+}
+
+TEST(DynTypeInheritance, SubTestObj) {
+ Ref<SubTestObj> obj = Ref<SubTestObj>::New(10, 20);
+ EXPECT_EQ(obj->x, 10);
+ EXPECT_EQ(obj->y, 20);
+ EXPECT_TRUE(obj->IsInstance<Object>());
+ EXPECT_TRUE(obj->IsInstance<TestObj>());
+ EXPECT_TRUE(obj->IsInstance<SubTestObj>());
+ EXPECT_FALSE(obj->IsInstance<Func>());
+ EXPECT_FALSE(obj->IsInstance<Str>());
+}
+
+} // namespace
+
+#endif
diff --git a/ffi/tests/cpp/test_type_static.cc b/ffi/tests/cpp/test_type_static.cc
new file mode 100644
index 0000000000..aebeba7c3e
--- /dev/null
+++ b/ffi/tests/cpp/test_type_static.cc
@@ -0,0 +1,119 @@
+#include <gtest/gtest.h>
+#include <tvm/ffi/ffi.hpp>
+
+namespace {
+using namespace tvm::ffi;
+
+struct SubType : public Object {
+ int data;
+ explicit SubType(int data) : data(data) {
+ if (data == 1) {
+ throw std::runtime_error("New Error");
+ }
+ }
+};
+
+int64_t FuncCall(int64_t x) { return x + 1; }
+
+void CheckAncestor(int32_t num, const int32_t *ancestors,
+ std::vector<int32_t> expected) {
+ EXPECT_EQ(num, expected.size());
+ for (int i = 0; i < num; ++i) {
+ EXPECT_EQ(ancestors[i], expected[i]);
+ }
+}
+
+static_assert(IsObject<Object>, "IsObject<Object> == true");
+static_assert(IsObject<Func>, "IsObject<Func> == true");
+static_assert(IsObject<Str>, "IsObject<Str> == true");
+
+TEST(StaticTypeInfo, Object) {
+ EXPECT_EQ(Object::_type_index,
+ static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject));
+ EXPECT_STRCASEEQ(Object::_type_key, "object.Object");
+ EXPECT_EQ(Object::_type_depth, 0);
+ CheckAncestor(Object::_type_depth, Object::_type_ancestors.data(), {});
+}
+
+TEST(StaticTypeInfo, Func) {
+ EXPECT_EQ(Func::_type_index,
+ static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFunc));
+ EXPECT_STRCASEEQ(Func::_type_key, "object.Func");
+ EXPECT_EQ(Func::_type_depth, 1);
+ CheckAncestor(Func::_type_depth, Func::_type_ancestors.data(),
+ {static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject)});
+}
+
+TEST(StaticTypeInfo, Str) {
+ EXPECT_EQ(Str::_type_index,
+ static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr));
+ EXPECT_STRCASEEQ(Str::_type_key, "object.Str");
+ EXPECT_EQ(Str::_type_depth, 1);
+ CheckAncestor(Str::_type_depth, Str::_type_ancestors.data(),
+ {static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIObject)});
+}
+
+TEST(StaticTypeInheritance, None) {
+ Ref<Object> obj;
+ // FIXME: The lines below are going to segfault
+ // EXPECT_STREQ(obj->GetTypeKey(), "None");
+ // EXPECT_FALSE(obj->IsInstance<Object>());
+ // EXPECT_FALSE(obj->IsInstance<Func>());
+ // EXPECT_FALSE(obj->IsInstance<Str>());
+}
+
+TEST(StaticTypeInheritance, Object) {
+ Ref<Object> obj = Ref<Object>::New();
+ EXPECT_STREQ(obj->GetTypeKey(), "object.Object");
+ EXPECT_TRUE(obj->IsInstance<Object>());
+ EXPECT_FALSE(obj->IsInstance<Func>());
+ EXPECT_FALSE(obj->IsInstance<Str>());
+}
+
+TEST(StaticTypeInheritance, Func_0) {
+ Ref<Func> obj = Ref<Func>::New(FuncCall);
+ EXPECT_STREQ(obj->GetTypeKey(), "object.Func");
+ EXPECT_TRUE(obj->IsInstance<Object>());
+ EXPECT_TRUE(obj->IsInstance<Func>());
+ EXPECT_FALSE(obj->IsInstance<Str>());
+}
+
+TEST(StaticTypeInheritance, Func_1) {
+ Ref<Object> obj = Ref<Func>::New(FuncCall);
+ EXPECT_STREQ(obj->GetTypeKey(), "object.Func");
+ EXPECT_TRUE(obj->IsInstance<Object>());
+ EXPECT_TRUE(obj->IsInstance<Func>());
+ EXPECT_FALSE(obj->IsInstance<Str>());
+}
+
+TEST(StaticTypeInheritance, Str_0) {
+ Ref<Str> obj = Ref<Str>::New("Hello, World!");
+ EXPECT_STREQ(obj->GetTypeKey(), "object.Str");
+ EXPECT_TRUE(obj->IsInstance<Object>());
+ EXPECT_FALSE(obj->IsInstance<Func>());
+ EXPECT_TRUE(obj->IsInstance<Str>());
+}
+
+TEST(StaticTypeInheritance, Str_1) {
+ Ref<Object> obj = Ref<Str>::New("Hello, World!");
+ EXPECT_STREQ(obj->GetTypeKey(), "object.Str");
+ EXPECT_TRUE(obj->IsInstance<Object>());
+ EXPECT_FALSE(obj->IsInstance<Func>());
+ EXPECT_TRUE(obj->IsInstance<Str>());
+}
+
+TEST(StaticTypeSubclass, NoException) {
+ Ref<SubType> obj = Ref<SubType>::New(0);
+ EXPECT_EQ(obj->data, 0);
+}
+
+TEST(StaticTypeSubclass, Exception) {
+ try {
+ Ref<SubType>::New(1);
+ FAIL() << "No exception thrown";
+ } catch (std::runtime_error &ex) {
+ EXPECT_STREQ(ex.what(), "New Error");
+ }
+}
+
+} // namespace
diff --git a/include/tvm/ffi/core/c_ffi_abi.h b/include/tvm/ffi/core/c_ffi_abi.h
new file mode 100644
index 0000000000..fadbda220e
--- /dev/null
+++ b/include/tvm/ffi/core/c_ffi_abi.h
@@ -0,0 +1,166 @@
+#ifndef TVM_FFI_C_FFI_ABI_H_
+#define TVM_FFI_C_FFI_ABI_H_
+
+#include <dlpack/dlpack.h>
+#include <stdint.h>
+
+#if !defined(TVM_FFI_API) && defined(__EMSCRIPTEN__)
+#include <emscripten/emscripten.h>
+#define TVM_FFI_API EMSCRIPTEN_KEEPALIVE
+#endif
+#if !defined(TVM_FFI_API) && defined(_MSC_VER)
+#ifdef TVM_FFI_EXPORTS
+#define TVM_FFI_API __declspec(dllexport)
+#else
+#define TVM_FFI_API __declspec(dllimport)
+#endif
+#endif
+#ifndef TVM_FFI_API
+#define TVM_FFI_API __attribute__((visibility("default")))
+#endif
+
+#ifndef TVM_FFI_ALLOW_DYN_TYPE
+#define TVM_FFI_ALLOW_DYN_TYPE 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+enum class TVMFFITypeIndex : int32_t {
+#else
+typedef enum {
+#endif
+ // [Section] On-stack POD Types: [0, kTVMFFIStaticObjectBegin)
+ // N.B. `kTVMFFIRawStr` is a string backed by a `\0`-terminated char array,
+ // which is not owned by TVMFFIAny. It is required that the following
+ // invariant holds:
+ // - `Any::type_index` is never `kTVMFFIRawStr`
+ // - `AnyView::type_index` can be `kTVMFFIRawStr`
+ kTVMFFINone = 0,
+ kTVMFFIInt = 1,
+ kTVMFFIFloat = 2,
+ kTVMFFIPtr = 3,
+ kTVMFFIDataType = 4,
+ kTVMFFIDevice = 5,
+ kTVMFFIRawStr = 6,
+ // [Section] Static Boxed: [kTVMFFIStaticObjectBegin, kTVMFFIDynObjectBegin)
+ kTVMFFIStaticObjectBegin = 64,
+ kTVMFFIObject = 64,
+ kTVMFFIList = 65,
+ kTVMFFIDict = 66,
+ kTVMFFIError = 67,
+ kTVMFFIFunc = 68,
+ kTVMFFIStr = 69,
+ // [Section] Dynamic Boxed: [kTVMFFIDynObjectBegin, +oo)
+ kTVMFFIDynObjectBegin = 128,
+#ifdef __cplusplus
+};
+#else
+} TypeIndex;
+#endif
+
+struct TVMFFIAny;
+typedef TVMFFIAny TVMFFIObject;
+typedef TVMFFIObject *TVMFFIObjectHandle;
+typedef TVMFFIAny *TVMFFIAnyHandle;
+
+typedef struct TVMFFIAny {
+ int32_t type_index;
+ union { // 4 bytes
+ int32_t ref_cnt; // reference counter for heap object
+ int32_t small_len; // length for on-stack object
+ };
+ union { // 8 bytes
+ int64_t v_int64; // integers
+ double v_float64; // floating-point numbers
+ DLDataType v_dtype; // data type
+ DLDevice v_device; // device
+ void *v_ptr; // typeless pointers
+ const char *v_str; // raw string
+ TVMFFIObjectHandle v_obj; // ref counted objects
+ void (*deleter)(void *); // Deleter of the object
+ char v_bytes[8]; // small string
+ char32_t v_char32[2]; // UCS4 string and Unicode
+ };
+} TVMFFIAny;
+
+typedef struct {
+ int32_t type_index;
+ const char *type_key;
+ int32_t type_depth;
+ int32_t *type_ancestors;
+} TVMFFITypeInfo;
+
+typedef TVMFFITypeInfo *TVMFFITypeInfoHandle;
+
+typedef struct {
+ const char *filename;
+ const char *func;
+ int32_t lineno;
+ void (*deleter)(void *);
+} TVMFFIStackFrame;
+
+typedef struct {
+ int32_t type_index;
+ int32_t ref_cnt;
+ void (*deleter)(void *);
+ const char *kind;
+ int32_t num_frames;
+ const char **linenos;
+ const char *message;
+} TVMFFIError;
+
+typedef struct {
+ int32_t type_index;
+ int32_t ref_cnt;
+ void (*deleter)(void *);
+ int64_t length;
+ char *data;
+} TVMFFIStr;
+
+typedef struct {
+ int32_t type_index;
+ int32_t ref_cnt;
+ void (*deleter)(void *);
+ void (*call)(const void *self, int32_t num_args, const TVMFFIAny *args, TVMFFIAny *ret);
+ int32_t (*safe_call)(const void *self, int32_t num_args, const TVMFFIAny *args, TVMFFIAny *ret);
+} TVMFFIFunc;
+
+typedef struct {
+ int32_t type_index;
+ int32_t ref_cnt;
+ void (*deleter)(void *);
+ int64_t list_capacity;
+ int64_t list_length;
+ int64_t pool_capacity;
+ int64_t pool_length;
+} TVMFFIList;
+
+typedef struct {
+ int32_t type_index;
+ int32_t ref_cnt;
+ void (*deleter)(void *);
+ int64_t capacity;
+ int64_t size;
+} TVMFFIDict;
+
+#if TVM_FFI_ALLOW_DYN_TYPE
+typedef void *TVMFFITypeTableHandle;
+TVM_FFI_API void TVMFFIDynTypeIndex2Info(TVMFFITypeTableHandle self, int32_t type_index,
+ TVMFFITypeInfoHandle *out_type_info);
+TVM_FFI_API void TVMFFIDynTypeDef(TVMFFITypeTableHandle self, const char *type_key,
+ int32_t type_depth, const int32_t *type_ancestors,
+ int32_t *out_type_index);
+TVM_FFI_API void TVMFFIDynTypeSetAttr(TVMFFITypeTableHandle self, int32_t type_index,
+ const char *key, TVMFFIAnyHandle value);
+TVM_FFI_API void TVMFFIDynTypeGetAttr(TVMFFITypeTableHandle self, int32_t type_index,
+ const char *key, TVMFFIAnyHandle *out_type_attr);
+#endif // TVM_FFI_ALLOW_DYN_TYPE
+
+#ifdef __cplusplus
+} // TVM_FFI_EXTERN_C
+#endif
+
+#endif // TVM_FFI_C_FFI_ABI_H_
diff --git a/include/tvm/ffi/core/core.h b/include/tvm/ffi/core/core.h
new file mode 100644
index 0000000000..608e3e4c1b
--- /dev/null
+++ b/include/tvm/ffi/core/core.h
@@ -0,0 +1,421 @@
+#ifndef TVM_FFI_CORE_H_
+#define TVM_FFI_CORE_H_
+
+#include "./c_ffi_abi.h"
+#include "./traits.h"
+#include "./utils.h"
+#include <cstring>
+#include <type_traits>
+
+namespace tvm {
+namespace ffi {
+
+#define TVM_FFI_DEF_TYPE_FRIENDS() \
+ template <typename> \
+ friend struct ::tvm::ffi::IsAnyWithExtra; \
+ template <typename> \
+ friend struct ::tvm::ffi::Ref; \
+ template <typename> \
+ friend struct ::tvm::ffi::RefBase; \
+ template <typename, typename> \
+ friend struct ::tvm::ffi::TypeTraits; \
+ template <typename> \
+ friend struct ::tvm::ffi::TypeTraitsDefaultForObject; \
+ template <typename> \
+ friend struct ::tvm::ffi::DefaultObjectAllocator
+
+/********** Section 1. Object *********/
+
+#define TVM_FFI_DEF_STATIC_TYPE(SelfType, ParentType, TypeIndex) \
+public: \
+ TVM_FFI_DEF_TYPE_FRIENDS(); \
+ friend struct ::tvm::ffi::Any; \
+ friend struct ::tvm::ffi::AnyView; \
+ [[maybe_unused]] static constexpr int32_t _type_index = static_cast<int32_t>(TypeIndex); \
+ using _type_parent [[maybe_unused]] = ParentType; \
+ [[maybe_unused]] static constexpr int32_t _type_depth = ParentType::_type_depth + 1; \
+ [[maybe_unused]] static inline constexpr std::array<int32_t, _type_depth> _type_ancestors = \
+ ::tvm::ffi::details::ObjectAncestorsConstExpr<ParentType, _type_depth>(); \
+ template <typename DerivedType> \
+ TVM_FFI_INLINE bool IsInstance() const { \
+ return ::tvm::ffi::details::IsInstanceOf<DerivedType, SelfType>(this->type_index); \
+ } \
+ TVM_FFI_INLINE const char *GetTypeKey() const { return TypeIndex2TypeKey(this->type_index); } \
+ [[maybe_unused]] static constexpr const char *_type_key = TypeIndexTraits<TypeIndex>::type_key
+
+#define TVM_FFI_DEF_DYN_TYPE(SelfType, ParentType, TypeKey) \
+public: \
+ TVM_FFI_DEF_TYPE_FRIENDS(); \
+ friend struct ::tvm::ffi::Any; \
+ friend struct ::tvm::ffi::AnyView; \
+ using _type_parent [[maybe_unused]] = ParentType; \
+ [[maybe_unused]] static constexpr int32_t _type_depth = ParentType::_type_depth + 1; \
+ [[maybe_unused]] static inline const std::array<int32_t, _type_depth> _type_ancestors = \
+ ::tvm::ffi::details::ObjectAncestors<ParentType, _type_depth>(); \
+ [[maybe_unused]] static inline int32_t _type_index = []() -> int32_t { \
+ int32_t ret; \
+ TVMFFIDynTypeDef(nullptr, TypeKey, _type_depth, _type_ancestors.data(), &ret); \
+ return ret; \
+ }(); \
+ template <typename DerivedType> \
+ TVM_FFI_INLINE bool IsInstance() const { \
+ return ::tvm::ffi::details::IsInstanceOf<DerivedType, SelfType>(this->type_index); \
+ } \
+ TVM_FFI_INLINE const char *GetTypeKey() const { return TypeIndex2TypeKey(this->type_index); } \
+ [[maybe_unused]] static constexpr const char *_type_key = TypeKey
+
+struct Object : protected TVMFFIAny {
+ TVM_FFI_DEF_STATIC_TYPE(Object, details::DummyRoot, TVMFFITypeIndex::kTVMFFIObject);
+
+ TVM_FFI_INLINE Object() : TVMFFIAny() {}
+ TVM_FFI_INLINE Object(const Object &) : TVMFFIAny() {}
+ TVM_FFI_INLINE Object(Object &&) {}
+ TVM_FFI_INLINE Object &operator=(const Object &) { return *this; }
+ TVM_FFI_INLINE Object &operator=(Object &&) { return *this; }
+ Ref<Str> str() const;
+ TVM_FFI_INLINE friend std::ostream &operator<<(std::ostream &os, const Object &src) {
+ TVMFFIAny v{};
+ v.type_index = src.type_index;
+ v.v_obj = const_cast<Object *>(&src);
+ details::AnyView2Str(os, &v);
+ return os;
+ }
+};
+
+/********** Section 2. Ref<ObjectType> *********/
+
+#define TVM_FFI_REF_DEF_DELEGATE_CONSTRUCTORS(SelfType, BaseType) \
+ template <typename Other, typename = std::is_constructible<BaseType, Other &&>> \
+ TVM_FFI_INLINE Ref(Other &&src) : BaseType(std::forward<Other>(src)) {} \
+ template <typename Other, typename = std::is_constructible<BaseType, Other &&>> \
+ TVM_FFI_DEF_ASSIGN(SelfType, const Other &)
+
+template <typename Type>
+struct RefBase {
+private:
+ using TSelf = RefBase<Type>;
+ using TSub = Ref<Type>;
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ friend struct Any;
+ friend struct AnyView;
+ template <typename T>
+ using EnableAnyOrViewOrRef = std::enable_if_t<details::IsAnyOrView<T> || details::IsRef<T>>;
+ template <typename Base, typename Derived>
+ using EnableDerivedObj =
+ typename std::enable_if_t<std::is_base_of_v<Base, Derived> ||
+ (std::is_same_v<Base, Object> && IsObject<Derived>)>;
+
+public:
+ /***** Factory: the `new` operator *****/
+ template <typename... Args>
+ TVM_FFI_INLINE static TSub New(Args &&...args) {
+ return TSub(Allocator::New(std::forward<Args>(args)...));
+ }
+ /***** Accessors *****/
+ TVM_FFI_INLINE const Type *get() const { return reinterpret_cast<const Type *>(this->data_); }
+ TVM_FFI_INLINE Type *get() { return reinterpret_cast<Type *>(data_); }
+ TVM_FFI_INLINE const Type *operator->() const { return get(); }
+ TVM_FFI_INLINE const Type &operator*() const { return *get(); }
+ TVM_FFI_INLINE Type *operator->() { return get(); }
+ TVM_FFI_INLINE Type &operator*() { return *get(); }
+ /***** Misc *****/
+ Ref<Str> str() const;
+ TVM_FFI_INLINE friend std::ostream &operator<<(std::ostream &os, const TSelf &src) {
+ TVMFFIAny v = src.AsTVMFFIAny();
+ details::AnyView2Str(os, &v);
+ return os;
+ }
+ template <typename BaseType = Type>
+ BaseType *GetRawObjPtr() const {
+ static_assert(std::is_same_v<BaseType, Object> || std::is_base_of_v<BaseType, Type>,
+ "Only downcasting is allowed");
+ return reinterpret_cast<BaseType *>(this->data_);
+ }
+ template <typename BaseType = Type>
+ BaseType *MoveToRawObjPtr() {
+ static_assert(std::is_same_v<BaseType, Object> || std::is_base_of_v<BaseType, Type>,
+ "Only downcasting is allowed");
+ BaseType *ret = reinterpret_cast<BaseType *>(this->data_);
+ this->data_ = nullptr;
+ return ret;
+ }
+
+protected:
+ using Allocator = typename GetAllocator<Type>::Type;
+ /***** Destructor *****/
+ TVM_FFI_INLINE ~RefBase() { this->DecRef(); }
+ /***** Constructor 0: default *****/
+ TVM_FFI_INLINE RefBase() : data_(nullptr) {}
+ /***** Constructor 1: From raw pointers *****/
+ TVM_FFI_INLINE RefBase(TVMFFIAny *data) : data_(data) { this->IncRef(); }
+ template <typename Derived, typename = EnableDerivedObj<Type, Derived>>
+ TVM_FFI_INLINE RefBase(Derived *data) : TSelf(reinterpret_cast<TVMFFIAny *>(data)) {}
+ TVM_FFI_DEF_ASSIGN(TSelf, TVMFFIAny *)
+ template <typename Derived, typename = EnableDerivedObj<Type, Derived>>
+ TVM_FFI_DEF_ASSIGN(TSelf, Derived *)
+ /***** Constructor 2: from RefBase<Type> *****/
+ TVM_FFI_INLINE RefBase(const TSelf &other) : data_(other.data_) {
+ this->IncRef();
+ }
+ TVM_FFI_INLINE RefBase(TSelf &&other) : data_(other.data_) { other.data_ = nullptr; }
+ TVM_FFI_DEF_ASSIGN(TSelf, const TSelf &)
+ TVM_FFI_DEF_ASSIGN(TSelf, TSelf &&)
+ /***** Constructor 3: from Ref<SubType>, Any and AnyView *****/
+ template <typename Other, typename = EnableAnyOrViewOrRef<Other>>
+ TVM_FFI_DEF_ASSIGN(TSub, Other &&)
+ template <typename Other, typename = EnableAnyOrViewOrRef<Other>>
+ TVM_FFI_DEF_ASSIGN(TSub, const Other &)
+ template <typename Other, typename = EnableAnyOrViewOrRef<Other>>
+ TVM_FFI_INLINE RefBase(const Other &src) : RefBase(src.template GetRawObjPtr<Type>()) {}
+ template <typename Other, typename = EnableAnyOrViewOrRef<Other>>
+ TVM_FFI_INLINE RefBase(Other &&src)
+ : data_(reinterpret_cast<TVMFFIAny *>(src.template MoveToRawObjPtr<Type>())) {
+ if constexpr (std::is_same_v<Other, AnyView>) {
+ this->IncRef();
+ }
+ }
+ TVM_FFI_INLINE void IncRef() { details::IncRef(this->data_); }
+ TVM_FFI_INLINE void DecRef() { details::DecRef(this->data_); }
+ TVM_FFI_INLINE void Swap(TSelf &other) { std::swap(this->data_, other.data_); }
+ TVM_FFI_INLINE TVMFFIAny AsTVMFFIAny() const {
+ if (data_ == nullptr) {
+ return TVMFFIAny();
+ }
+ TVMFFIAny ret{};
+ ret.type_index = data_->type_index;
+ ret.v_obj = data_;
+ return ret;
+ }
+
+ TVMFFIAny *data_;
+};
+
+template <typename Type>
+struct Ref : public RefBase<Type> {
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ TVM_FFI_INLINE Ref() : RefBase<Type>() {}
+ TVM_FFI_REF_DEF_DELEGATE_CONSTRUCTORS(Ref<Type>, RefBase<Type>)
+};
+
+/********** Section 3. AnyView *********/
+
+struct AnyView : public TVMFFIAny {
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ friend struct Any;
+ /***** Destructor *****/
+ TVM_FFI_INLINE ~AnyView() = default;
+ TVM_FFI_INLINE void Reset() { *(static_cast<TVMFFIAny *>(this)) = TVMFFIAny(); }
+ /***** Constructor 0: default *****/
+ TVM_FFI_INLINE AnyView() : TVMFFIAny() {}
+ /***** Constructor 1: from AnyView *****/
+ TVM_FFI_INLINE AnyView(const AnyView &src) = default;
+ TVM_FFI_INLINE AnyView &operator=(const AnyView &src) = default;
+ TVM_FFI_INLINE AnyView(AnyView &&src) : TVMFFIAny(*&src) { src.Reset(); }
+ TVM_FFI_DEF_ASSIGN(AnyView, AnyView &&)
+ /***** Constructor 2: from Any *****/
+ TVM_FFI_INLINE AnyView(const Any &src);
+ TVM_FFI_DEF_ASSIGN(AnyView, const Any &)
+ AnyView(Any &&src) = delete;
+ AnyView &operator=(Any &&src) = delete;
+ /***** Constructor 3: from Ref<T> *****/
+ template <typename T>
+ TVM_FFI_INLINE AnyView(const Ref<T> &src) : TVMFFIAny(src.AsTVMFFIAny()) {}
+ template <typename T>
+ TVM_FFI_DEF_ASSIGN(AnyView, const Ref<T> &)
+ template <typename T>
+ AnyView(Ref<T> &&src) = delete;
+ template <typename T>
+ AnyView &operator=(Ref<T> &&src) = delete;
+ /***** Constructors 4: use TypeTraits<T> *****/
+ template <typename Type, typename = HasTypeTraits<Type>>
+ TVM_FFI_INLINE AnyView(const Type &src) : TVMFFIAny() {
+ TypeTraitsNoCR<Type>::CopyFromTypeToTVMFFIAny(src, this);
+ }
+ template <typename Type, typename = HasTypeTraits<Type>>
+ TVM_FFI_DEF_ASSIGN(AnyView, const Type &)
+ /*** Converter 0: use TypeTraits<T> ***/
+ template <typename Type, typename = HasTypeTraits<Type>>
+ operator Type() const;
+ template <typename Type, typename = HasTypeTraits<Type>>
+ Type CastWithStorage(Any *storage) const;
+ /***** Misc *****/
+ Ref<Str> str() const;
+ TVM_FFI_INLINE friend std::ostream &operator<<(std::ostream &os, const AnyView &src) {
+ details::AnyView2Str(os, &src);
+ return os;
+ }
+ template <typename Type>
+ TVM_FFI_INLINE Type *GetRawObjPtr() const;
+ template <typename Type>
+ TVM_FFI_INLINE Type *MoveToRawObjPtr();
+
+protected:
+ TVM_FFI_INLINE void Swap(TVMFFIAny &src) {
+ TVMFFIAny tmp = *this;
+ *static_cast<TVMFFIAny *>(this) = src;
+ src = tmp;
+ }
+};
+
+/********** Section 4. Any *********/
+
+struct Any : public TVMFFIAny {
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ friend struct AnyView;
+ /***** Destructor *****/
+ TVM_FFI_INLINE ~Any() { this->Reset(); }
+ TVM_FFI_INLINE void Reset() {
+ this->DecRef();
+ *(static_cast<TVMFFIAny *>(this)) = TVMFFIAny();
+ }
+ /***** Constructor 0: default *****/
+ TVM_FFI_INLINE Any() : TVMFFIAny() {}
+ /***** Constructor 1: from AnyView *****/
+ TVM_FFI_INLINE Any(const AnyView &src) : TVMFFIAny(*static_cast<const TVMFFIAny *>(&src)) {
+ if (this->type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr)) {
+ // Special case: handle the case where `Any` needs to own a raw string.
+ this->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr);
+ this->v_obj = details::StrCopyFromCharArray(this->v_str, std::strlen(this->v_str));
+ }
+ this->IncRef();
+ }
+ TVM_FFI_INLINE Any(AnyView &&src) : Any(static_cast<const AnyView &>(src)) { // TODO: add test
+ src.Reset();
+ }
+ TVM_FFI_DEF_ASSIGN(Any, const AnyView &)
+ TVM_FFI_DEF_ASSIGN(Any, AnyView &&)
+ /***** Constructor 2: from Any *****/
+ TVM_FFI_INLINE Any(const Any &src) : TVMFFIAny(*static_cast<const TVMFFIAny *>(&src)) {
+ this->IncRef();
+ }
+ TVM_FFI_INLINE Any(Any &&src) : TVMFFIAny(*static_cast<const TVMFFIAny *>(&src)) {
+ *static_cast<TVMFFIAny *>(&src) = TVMFFIAny();
+ }
+ TVM_FFI_DEF_ASSIGN(Any, const Any &)
+ TVM_FFI_DEF_ASSIGN(Any, Any &&)
+ /***** Constructor 3: from Ref<T> *****/
+ template <typename T>
+ TVM_FFI_INLINE Any(const Ref<T> &src) : TVMFFIAny(src.AsTVMFFIAny()) {
+ this->IncRef();
+ }
+ template <typename T>
+ TVM_FFI_INLINE Any(Ref<T> &&src) : TVMFFIAny(src.AsTVMFFIAny()) {
+ src.data_ = nullptr;
+ }
+ template <typename T>
+ TVM_FFI_DEF_ASSIGN(Any, const Ref<T> &)
+ template <typename T>
+ TVM_FFI_DEF_ASSIGN(Any, Ref<T> &&)
+ /***** Constructors 4: use TypeTraits<T> *****/
+ template <typename Type, typename = HasTypeTraits<Type>>
+ TVM_FFI_INLINE Any(const Type &src) : Any(AnyView(src)) {}
+ template <typename Type, typename = HasTypeTraits<Type>>
+ TVM_FFI_DEF_ASSIGN(Any, const Type &)
+ /***** Constructors 5: Special handling for strings *****/
+ TVM_FFI_INLINE Any(const std::string &s) : Any(AnyView(s)) {}
+ TVM_FFI_INLINE Any(const char *s) : Any(AnyView(s)) {}
+ TVM_FFI_INLINE Any(std::string &&s) : TVMFFIAny() {
+ this->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr);
+ this->v_obj = details::StrMoveFromStdString(std::move(s));
+ this->IncRef();
+ }
+ template <size_t N>
+ TVM_FFI_INLINE Any(const CharArray<N> &s) : TVMFFIAny() {
+ this->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr);
+ this->v_obj = details::StrCopyFromCharArray(s, N - 1);
+ this->IncRef();
+ }
+ TVM_FFI_DEF_ASSIGN(Any, const std::string &)
+ TVM_FFI_DEF_ASSIGN(Any, const char *&)
+ TVM_FFI_DEF_ASSIGN(Any, std::string &&)
+ template <size_t N>
+ TVM_FFI_DEF_ASSIGN(Any, const CharArray<N> &)
+ /*** Converter 0: use TypeTraits<T> ***/
+ template <typename Type, typename = HasTypeTraits<Type>>
+ operator Type() const;
+ /***** Misc *****/
+ Ref<Str> str() const;
+ TVM_FFI_INLINE friend std::ostream &operator<<(std::ostream &os, const Any &src) {
+ details::AnyView2Str(os, &src);
+ return os;
+ }
+ template <typename Type>
+ TVM_FFI_INLINE Type *GetRawObjPtr() const {
+ return reinterpret_cast<const AnyView *>(this)->GetRawObjPtr<Type>();
+ }
+ template <typename Type>
+ TVM_FFI_INLINE Type *MoveToRawObjPtr() {
+ return reinterpret_cast<AnyView *>(this)->MoveToRawObjPtr<Type>();
+ }
+
+protected:
+ TVM_FFI_INLINE void Swap(TVMFFIAny &src) {
+ TVMFFIAny tmp = *this;
+ *static_cast<TVMFFIAny *>(this) = src;
+ src = tmp;
+ }
+ TVM_FFI_INLINE void IncRef() {
+ if (!details::IsTypeIndexPOD(this->type_index)) {
+ details::IncRef(this->v_obj);
+ }
+ }
+ TVM_FFI_INLINE void DecRef() {
+ if (!details::IsTypeIndexPOD(this->type_index)) {
+ details::DecRef(this->v_obj);
+ }
+ }
+};
+
+/********** Section 5. Type Conversion and Type Table *********/
+
+TVM_FFI_INLINE AnyView::AnyView(const Any &src) : TVMFFIAny(*&src) {}
+
+#define TVM_FFI_TRY_CONVERT(Expr, TypeStr) \
+ try { \
+ return Expr; \
+ } catch (const TemporaryTypeError &) { \
+ TVM_FFI_THROW(TypeError) << "Cannot convert from type `" \
+ << TypeIndex2TypeKey(this->type_index) << "` to `" << TypeStr << "`"; \
+ } \
+ TVM_FFI_UNREACHABLE();
+template <typename Type, typename>
+inline AnyView::operator Type() const {
+ TVM_FFI_TRY_CONVERT(TypeTraitsNoCR<Type>::CopyFromTVMFFIAnyToType(this),
+ TypeTraitsNoCR<Type>::Type2Str());
+}
+template <typename Type, typename>
+inline Type AnyView::CastWithStorage(Any *storage) const {
+ TVM_FFI_TRY_CONVERT(TypeTraitsNoCR<Type>::CopyFromTVMFFIAnyToTypeWithStorage(this, storage),
+ TypeTraitsNoCR<Type>::Type2Str());
+}
+template <typename Type, typename>
+inline Any::operator Type() const {
+ TVM_FFI_TRY_CONVERT(TypeTraitsNoCR<Type>::CopyFromTVMFFIAnyToType(this),
+ TypeTraitsNoCR<Type>::Type2Str());
+}
+template <typename Type>
+inline Type *AnyView::GetRawObjPtr() const {
+ TVM_FFI_TRY_CONVERT(TypeTraitsNoCR<Type *>::CopyFromTVMFFIAnyToRef(this), Type::_type_key);
+}
+template <typename Type>
+inline Type *AnyView::MoveToRawObjPtr() {
+ TVM_FFI_TRY_CONVERT(TypeTraitsNoCR<Type *>::MoveFromTVMFFIAnyToRef(this), Type::_type_key);
+}
+#undef TVM_FFI_TRY_CONVERT
+
+#if TVM_FFI_ALLOW_DYN_TYPE
+TVM_FFI_INLINE void TypeSetAttr(int32_t type_index, const char *attr_key, AnyView attr_value) {
+ TVMFFIDynTypeSetAttr(nullptr, type_index, attr_key, &attr_value);
+}
+
+TVM_FFI_INLINE AnyView TypeGetAttr(int32_t type_index, const char *attr_key) {
+ TVMFFIAnyHandle attr;
+ TVMFFIDynTypeGetAttr(nullptr, type_index, attr_key, &attr);
+ return AnyView(*(static_cast<AnyView *>(attr)));
+}
+#endif
+
+} // namespace ffi
+} // namespace tvm
+
+#endif // TVM_FFI_CORE_H_
diff --git a/include/tvm/ffi/core/traits.h b/include/tvm/ffi/core/traits.h
new file mode 100644
index 0000000000..8f80b77f49
--- /dev/null
+++ b/include/tvm/ffi/core/traits.h
@@ -0,0 +1,613 @@
+#ifndef TVM_FFI_TRAITS_H_
+#define TVM_FFI_TRAITS_H_
+
+#include "./utils.h"
+#include <sstream>
+#include <string>
+
+namespace tvm {
+namespace ffi {
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4702)
+#endif
+
+/*!
+ * \brief TypeTraits<T> is a template class that provides a set of static
+ * methods that are associated with a specific type `T` for compile-time
+ * dispatching.
+ *
+ * [Trait 0] Type2Str<T>: () -> std::string
+ * Returns the string representation of the type `T`
+ *
+ * [Trait 1] CopyFromTVMFFIAnyToType<T>: (const TVMFFIAny *) -> T
+ * Converts an `AnyView` or `Any` to `T`. It could incur copy when
+ * inevitable, for example, copy the content of a Str obj if it's converted
+ * to `std::string`.
+ *
+ * It is used in the following cases in the codebase:
+ * 1) `AnyView::operator Type()`
+ * 2) `Any::operator Type()`
+ *
+ * [Trait 2] CopyFromTypeToTVMFFIAny<T>: (T, TVMFFIAny *) -> void
+ * Converts a value of type T to `AnyView`.
+ *
+ * It is used in the following case in the codebase:
+ * 1) `AnyView::AnyView(const Type& src)`
+ *
+ * [Trait 3] CopyFromTVMFFIAnyToRef<T>: (const TVMFFIAny *) -> T*
+ * Converts an `AnyView` or `Any` to `TVMFFIAny*`, which is subsequently used
+ * to initialize `Ref<T>::data_`. Note that this method does not increment
+ * the reference counter, which is supposed to be later handled automatically by
+ * `Ref<T>`.
+ *
+ * It is used in the following cases in the codebase:
+ * 1) `Ref<Type>::Ref(const AnyView& src)`
+ * 2) `Ref<Type>::Ref(const Any& src)`
+ *
+ * [Trait 4] MoveFromTVMFFIAnyToRef<T>: (TVMFFIAny *) -> T*
+ * Moves an `AnyView` or `Any` to `TVMFFIAny*`, which is subsequently used
+ * to initialize `Ref<T>::data_`. Note that the reference counter will not
+ * increment throughout the process because it corresponds to move semantics.
+ *
+ * It is used in the following cases in the codebase:
+ * 1) `Ref<Type>::Ref(AnyView&& src)`
+ * 2) `Ref<Type>::Ref(Any&& src)`
+ *
+ * [Trait 5] (Optional) CopyFromTVMFFIAnyToTypeWithStorage<T>:
+ * (const TVMFFIAny *, Any *) -> T
+ * It does similar thing as [Trait 1] that converts an `AnyView` or `Any` to
+ * `T`, but is onyl used by TVM FFI's calling convention, where an additional
+ * storage `Any*` is provided to retain ownership when unpacking `AnyView[]`
+ * before calling into a C++ function.
+ *
+ * Example. When converting an `AnyView (kTVMFFIRawStr)` to `Str*`,
+ * which consists of two intermediate steps `AnyView (kTVMFFIRawStr)` to
+ * `Ref<Str>`, and `Ref<Str>` to `Str*`, this method will store the by-product
+ * `Ref<Str>` into the given storage to lifespan expiration.
+ *
+ * It is used in the following case(s) in the codebase:
+ * 1) `AnyView::CastWithStorage<Type>(Any *)`
+ */
+template <typename, typename = void>
+struct TypeTraits {
+ constexpr static bool enabled = false;
+};
+template <typename T>
+using TypeTraitsNoCR = TypeTraits<details::RemoveCR<T>>;
+template <typename T>
+using HasTypeTraits = std::enable_if_t<TypeTraitsNoCR<T>::enabled>;
+
+template <enum TVMFFITypeIndex type_index>
+struct TypeIndexTraits;
+
+#define TVM_FFI_DEF_TYPE_INDEX_TRAITS(TypeIndex_, TypeKey) \
+ template <> \
+ struct TypeIndexTraits<TypeIndex_> { \
+ static constexpr const char *type_key = TypeKey; \
+ }
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFINone, "None");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIInt, "int");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIFloat, "float");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIPtr, "Ptr");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIDataType, "dtype");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIDevice, "Device");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIRawStr, "const char *");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIObject, "object.Object");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIList, "object.List");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIDict, "object.Dict");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIError, "object.Error");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIFunc, "object.Func");
+TVM_FFI_DEF_TYPE_INDEX_TRAITS(TVMFFITypeIndex::kTVMFFIStr, "object.Str");
+#undef TVM_FFI_DEF_TYPE_INDEX_TRAITS
+
+TVM_FFI_INLINE const char *TypeIndex2TypeKey(int32_t type_index) {
+#define TVM_FFI_TYPE_INDEX_SWITCH_CASE(TypeIndex_) \
+ case TypeIndex_: \
+ return TypeIndexTraits<TypeIndex_>::type_key;
+ switch (static_cast<TVMFFITypeIndex>(type_index)) {
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFINone);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIInt);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIFloat);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIPtr);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIDataType);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIDevice);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIRawStr);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIObject);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIList);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIDict);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIError);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIFunc);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIStr);
+ default:
+#if TVM_FFI_ALLOW_DYN_TYPE
+ {
+ TVMFFITypeInfoHandle type_info;
+ TVMFFIDynTypeIndex2Info(nullptr, type_index, &type_info);
+ return type_info ? type_info->type_key : "(undefined)";
+ }
+#else
+ return "Unknown";
+#endif
+ }
+#undef TVM_FFI_TYPE_INDEX_SWITCH_CASE
+ TVM_FFI_UNREACHABLE();
+}
+
+namespace details {
+struct DummyRoot {
+ static constexpr bool _type_is_static = true;
+ static constexpr int32_t _type_depth = -1;
+ static constexpr int32_t _type_index = -1;
+ static constexpr std::array<int32_t, 0> _type_ancestors = {};
+};
+template <typename F, typename TypeTableType>
+TVM_FFI_INLINE void InitTypeTable(F f, TypeTableType *self) {
+ {
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFINone, DummyRoot>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIInt, DummyRoot>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIFloat, DummyRoot>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIPtr, DummyRoot>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIDevice, DummyRoot>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIDataType, DummyRoot>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIRawStr, DummyRoot>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIObject, Object>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIList, Object>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIDict, Object>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIError, Error>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIStr, Str>(self);
+ f.template RegisterType<TVMFFITypeIndex::kTVMFFIFunc, Func>(self);
+ }
+ {
+ f.template RegisterStr<TVMFFITypeIndex::kTVMFFINone, void *>(self);
+ f.template RegisterStr<TVMFFITypeIndex::kTVMFFIInt, int64_t>(self);
+ f.template RegisterStr<TVMFFITypeIndex::kTVMFFIFloat, double>(self);
+ f.template RegisterStr<TVMFFITypeIndex::kTVMFFIPtr, void *>(self);
+ f.template RegisterStr<TVMFFITypeIndex::kTVMFFIDevice, DLDevice>(self);
+ f.template RegisterStr<TVMFFITypeIndex::kTVMFFIDataType, DLDataType>(self);
+ f.template RegisterStr<TVMFFITypeIndex::kTVMFFIRawStr, const char *>(self);
+ f.template RegisterStr<TVMFFITypeIndex::kTVMFFIStr, const char *>(self);
+ }
+}
+
+template <typename T>
+using Identity = T;
+
+template <template <typename> typename F = Identity>
+TVM_FFI_INLINE void StaticAnyView2Str(std::ostream &os, const TVMFFIAny *v) {
+#define TVM_FFI_TYPE_INDEX_SWITCH_CASE(TypeIndex_, Type) \
+ case TypeIndex_: { \
+ using Traits = F<TypeTraits<Type>>; \
+ os << Traits::__str__(Traits::CopyFromTVMFFIAnyToType(v)); \
+ break; \
+ }
+ switch (static_cast<TVMFFITypeIndex>(v->type_index)) {
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFINone, void *);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIInt, int64_t);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIFloat, double);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIPtr, void *);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIDevice, DLDevice);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIDataType, DLDataType);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIRawStr, const char *);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIStr, const char *);
+ default: {
+ os << TypeIndex2TypeKey(v->type_index) << '@' << v->v_ptr;
+ break;
+ }
+ }
+#undef TVM_FFI_TYPE_INDEX_SWITCH_CASE
+}
+
+template <typename DerivedType>
+struct StaticIsInstanceOf {
+ static TVM_FFI_INLINE bool Check(int32_t type_index) {
+#define TVM_FFI_TYPE_INDEX_SWITCH_CASE(TypeIndex_, Type) \
+ case TypeIndex_: { \
+ return StaticIsInstanceOf<DerivedType>::CheckImpl<Type>(); \
+ }
+ switch (static_cast<TVMFFITypeIndex>(type_index)) {
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIObject, Object);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIList, List);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIDict, Dict);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIError, Error);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIFunc, Func);
+ TVM_FFI_TYPE_INDEX_SWITCH_CASE(TVMFFITypeIndex::kTVMFFIStr, Str);
+ default:
+ return false;
+ }
+#undef TVM_FFI_TYPE_INDEX_SWITCH_CASE
+ TVM_FFI_UNREACHABLE();
+ }
+
+private:
+ template <typename SelfType>
+ static constexpr bool CheckImpl() {
+ constexpr auto ancestors = SelfType::_type_ancestors;
+ constexpr int32_t ancestors_depth = static_cast<int32_t>(ancestors.size());
+ constexpr int32_t type_depth = DerivedType::_type_depth;
+ return ancestors_depth > type_depth && ancestors[type_depth] == DerivedType::_type_index;
+ }
+};
+
+template <typename DerivedType, typename SelfType = Object>
+TVM_FFI_INLINE bool IsInstanceOf(int32_t type_index) {
+ // TODO: Support non-Object types
+ if constexpr (std::is_same_v<DerivedType, Object> ||
+ std::is_base_of_v</*base=*/DerivedType, /*derived=*/SelfType>) {
+ return true;
+ }
+ // Special case: `DerivedType` is exactly the underlying type of `type_index`
+ if (type_index == DerivedType::_type_index) {
+ return true;
+ }
+ // Given an index `i = DerivedType::_type_index`,
+ // and the underlying type of `T = *this`, we wanted to check if
+ // `T::_type_ancestors[i] == DerivedType::_type_index`.
+ //
+ // There are 3 ways to reflect `T` out of `this->type_index`:
+ // (Case 1) Use `SelfType` as a surrogate type if `SelfType::_type_ancestors`
+ // is long enough, whose length is reflected by `SelfType::_type_depth`.
+ if constexpr (SelfType::_type_depth > DerivedType::_type_depth) {
+ return SelfType::_type_ancestors[DerivedType::_type_depth] == DerivedType::_type_index;
+ }
+ if constexpr (SelfType::_type_depth == DerivedType::_type_depth) {
+ return SelfType::_type_index == DerivedType::_type_index;
+ }
+ // (Case 2) If `type_index` falls in static object section, use switch case to
+ // enumerate all possibilities. It could be more efficient because checks can
+ // potentially be simplified by the compiler.
+ if (details::IsTypeIndexPOD(type_index)) {
+ return ::tvm::ffi::details::StaticIsInstanceOf<DerivedType>::Check(type_index);
+ }
+ // (Case 3) Look up the type table for type hierarchy via `type_index`.
+#if TVM_FFI_ALLOW_DYN_TYPE
+ TVMFFITypeInfoHandle info;
+ TVMFFIDynTypeIndex2Info(nullptr, type_index, &info);
+ if (info == nullptr) {
+ TVM_FFI_THROW(InternalError) << "Undefined type index: " << type_index;
+ }
+ return info->type_depth > DerivedType::_type_depth &&
+ info->type_ancestors[DerivedType::_type_depth] == DerivedType::_type_index;
+#else
+ if (type_index > static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDynObjectBegin)) {
+ TVM_FFI_THROW(BuildError) << "Dynamic type is not supported. Please set "
+ "`TVM_FFI_ALLOW_DYN_TYPE=ON` when building TVM FFI";
+ }
+ return false;
+#endif
+}
+
+} // namespace details
+
+TVM_FFI_INLINE const char *DLDeviceType2Str(DLDeviceType type) {
+ switch (type) {
+ case kDLCPU:
+ return "cpu";
+ case kDLCUDA:
+ return "cuda";
+ case kDLCUDAHost:
+ return "cuda_host";
+ case kDLOpenCL:
+ return "opencl";
+ case kDLVulkan:
+ return "vulkan";
+ case kDLMetal:
+ return "mps";
+ case kDLVPI:
+ return "vpi";
+ case kDLROCM:
+ return "rocm";
+ case kDLROCMHost:
+ return "rocm_host";
+ case kDLExtDev:
+ return "ext_dev";
+ case kDLCUDAManaged:
+ return "cuda_managed";
+ case kDLOneAPI:
+ return "oneapi";
+ case kDLWebGPU:
+ return "webgpu";
+ case kDLHexagon:
+ return "hexagon";
+ case kDLMAIA:
+ return "maia";
+ }
+ return "unknown";
+}
+
+TVM_FFI_INLINE const char *DLDataTypeCode2Str(DLDataTypeCode type_code) {
+ switch (type_code) {
+ case kDLInt:
+ return "int";
+ case kDLUInt:
+ return "uint";
+ case kDLFloat:
+ return "float";
+ case kDLOpaqueHandle:
+ return "ptr";
+ case kDLBfloat:
+ return "bfloat";
+ case kDLComplex:
+ return "complex";
+ case kDLBool:
+ return "bool";
+ }
+ return "unknown";
+}
+
+/********** TypeTraits: Object *********/
+
+template <typename ObjectType>
+struct TypeTraitsDefaultForObject {
+ static constexpr bool enabled = true;
+
+ TVM_FFI_INLINE static void CopyFromTypeToTVMFFIAny(ObjectType *src, TVMFFIAny *ret) {
+ if (src == nullptr) {
+ ret->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone);
+ ret->v_obj = nullptr;
+ } else {
+ ret->type_index = src->type_index;
+ ret->v_obj = const_cast<TVMFFIAny *>(reinterpret_cast<const TVMFFIAny *>(src));
+ }
+ }
+
+ TVM_FFI_INLINE static ObjectType *CopyFromTVMFFIAnyToType(const TVMFFIAny *v) {
+ if (details::IsTypeIndexNone(v->type_index)) {
+ return nullptr;
+ }
+ if (!details::IsTypeIndexPOD(v->type_index) &&
+ details::IsInstanceOf<ObjectType>(v->type_index)) {
+ return reinterpret_cast<ObjectType *>(v->v_obj);
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static ObjectType *CopyFromTVMFFIAnyToRef(const TVMFFIAny *v) {
+ return CopyFromTVMFFIAnyToType(v);
+ }
+
+ TVM_FFI_INLINE static ObjectType *MoveFromTVMFFIAnyToRef(TVMFFIAny *v) {
+ if (details::IsTypeIndexNone(v->type_index)) {
+ return nullptr;
+ }
+ if (!details::IsTypeIndexPOD(v->type_index) &&
+ details::IsInstanceOf<ObjectType>(v->type_index)) {
+ ObjectType *ret = reinterpret_cast<ObjectType *>(v->v_obj);
+ v->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone);
+ v->v_obj = nullptr;
+ return ret;
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static std::string Type2Str() { return ObjectType::_type_key + std::string(" *"); }
+};
+
+template <typename ObjectType>
+struct TypeTraits<ObjectType *, std::enable_if_t<IsObject<ObjectType>>>
+ : public TypeTraitsDefaultForObject<ObjectType> {};
+
+/********** TypeTraits: Integer *********/
+
+template <typename Int>
+struct TypeTraits<Int, std::enable_if_t<std::is_integral_v<Int>>> {
+ static constexpr bool enabled = true;
+ TVM_FFI_INLINE static void CopyFromTypeToTVMFFIAny(Int src, TVMFFIAny *ret) {
+ ret->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIInt);
+ ret->v_int64 = static_cast<int64_t>(src);
+ }
+
+ TVM_FFI_INLINE static Int CopyFromTVMFFIAnyToType(const TVMFFIAny *v) {
+ TVMFFITypeIndex type_index = static_cast<TVMFFITypeIndex>(v->type_index);
+ if (type_index == TVMFFITypeIndex::kTVMFFIInt) {
+ return static_cast<Int>(v->v_int64);
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static std::string Type2Str() {
+ return TypeIndexTraits<TVMFFITypeIndex::kTVMFFIInt>::type_key;
+ }
+
+ TVM_FFI_INLINE static std::string __str__(int64_t src) { return std::to_string(src); }
+};
+
+/********** TypeTraits: Float *********/
+
+template <typename Float>
+struct TypeTraits<Float, std::enable_if_t<std::is_floating_point_v<Float>>> {
+ static constexpr bool enabled = true;
+ TVM_FFI_INLINE static void CopyFromTypeToTVMFFIAny(Float src, TVMFFIAny *ret) {
+ ret->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIFloat);
+ ret->v_float64 = src;
+ }
+
+ TVM_FFI_INLINE static Float CopyFromTVMFFIAnyToType(const TVMFFIAny *v) {
+ TVMFFITypeIndex type_index = static_cast<TVMFFITypeIndex>(v->type_index);
+ if (type_index == TVMFFITypeIndex::kTVMFFIFloat) {
+ return v->v_float64;
+ } else if (type_index == TVMFFITypeIndex::kTVMFFIInt) {
+ return static_cast<Float>(v->v_int64);
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static std::string Type2Str() {
+ return TypeIndexTraits<TVMFFITypeIndex::kTVMFFIFloat>::type_key;
+ }
+
+ TVM_FFI_INLINE static std::string __str__(double src) { return std::to_string(src); }
+};
+
+/********** TypeTraits: Opaque Pointer *********/
+
+template <>
+struct TypeTraits<void *> {
+ static constexpr bool enabled = true;
+ using Ptr = void *;
+
+ TVM_FFI_INLINE static void CopyFromTypeToTVMFFIAny(Ptr src, TVMFFIAny *ret) {
+ ret->type_index = (src == nullptr) ? static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone)
+ : static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIPtr);
+ ret->v_ptr = src;
+ }
+
+ TVM_FFI_INLINE static Ptr CopyFromTVMFFIAnyToType(const TVMFFIAny *v) {
+ TVMFFITypeIndex type_index = static_cast<TVMFFITypeIndex>(v->type_index);
+ if (type_index == TVMFFITypeIndex::kTVMFFIPtr || type_index == TVMFFITypeIndex::kTVMFFIRawStr ||
+ type_index == TVMFFITypeIndex::kTVMFFINone) {
+ return v->v_ptr;
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static std::string Type2Str() {
+ return TypeIndexTraits<TVMFFITypeIndex::kTVMFFIPtr>::type_key;
+ }
+
+ TVM_FFI_INLINE static std::string __str__(Ptr src) {
+ if (src == nullptr) {
+ return "None";
+ } else {
+ std::ostringstream oss;
+ oss << src;
+ return oss.str();
+ }
+ }
+};
+
+template <>
+struct TypeTraits<std::nullptr_t> : public TypeTraits<void *> {};
+
+/********** TypeTraits: DLDevice *********/
+
+template <>
+struct TypeTraits<DLDevice> {
+ static constexpr bool enabled = true;
+ TVM_FFI_INLINE static void CopyFromTypeToTVMFFIAny(DLDevice src, TVMFFIAny *ret) {
+ ret->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDevice);
+ ret->v_device = src;
+ }
+
+ TVM_FFI_INLINE static DLDevice CopyFromTVMFFIAnyToType(const TVMFFIAny *v) {
+ TVMFFITypeIndex type_index = static_cast<TVMFFITypeIndex>(v->type_index);
+ if (type_index == TVMFFITypeIndex::kTVMFFIDevice) {
+ return v->v_device;
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static std::string Type2Str() {
+ return TypeIndexTraits<TVMFFITypeIndex::kTVMFFIDevice>::type_key;
+ }
+
+ TVM_FFI_INLINE static std::string __str__(DLDevice device) {
+ std::ostringstream os;
+ os << DLDeviceType2Str(static_cast<DLDeviceType>(device.device_type)) << ":"
+ << device.device_id;
+ return os.str();
+ }
+};
+
+/********** TypeTraits: DLDataType *********/
+
+template <>
+struct TypeTraits<DLDataType> {
+ static constexpr bool enabled = true;
+ TVM_FFI_INLINE static void CopyFromTypeToTVMFFIAny(DLDataType src, TVMFFIAny *ret) {
+ ret->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDataType);
+ ret->v_dtype = src;
+ }
+
+ TVM_FFI_INLINE static DLDataType CopyFromTVMFFIAnyToType(const TVMFFIAny *v) {
+ TVMFFITypeIndex type_index = static_cast<TVMFFITypeIndex>(v->type_index);
+ if (type_index == TVMFFITypeIndex::kTVMFFIDataType) {
+ return v->v_dtype;
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static std::string Type2Str() {
+ return TypeIndexTraits<TVMFFITypeIndex::kTVMFFIDataType>::type_key;
+ }
+
+ TVM_FFI_INLINE static std::string __str__(DLDataType dtype) {
+ DLDataTypeCode code = static_cast<DLDataTypeCode>(dtype.code);
+ int32_t bits = dtype.bits;
+ int32_t lanes = dtype.lanes;
+ if (code == kDLUInt && bits == 1 && lanes == 1) {
+ return "bool";
+ }
+ if (code == kDLOpaqueHandle && bits == 0 && lanes == 0) {
+ return "void";
+ }
+ std::ostringstream os;
+ os << DLDataTypeCode2Str(code) << bits;
+ if (lanes != 1) {
+ os << "x" << lanes;
+ }
+ return os.str();
+ }
+};
+
+/********** TypeTraits: String *********/
+
+template <>
+struct TypeTraits<const char *> {
+ static constexpr bool enabled = true;
+
+ TVM_FFI_INLINE static void CopyFromTypeToTVMFFIAny(const char *src, TVMFFIAny *ret) {
+ ret->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr);
+ ret->v_str = src;
+ }
+
+ TVM_FFI_INLINE static const char *CopyFromTVMFFIAnyToType(const TVMFFIAny *v) {
+ TVMFFITypeIndex type_index = static_cast<TVMFFITypeIndex>(v->type_index);
+ if (type_index == TVMFFITypeIndex::kTVMFFIRawStr) {
+ return v->v_str;
+ }
+ if (type_index == TVMFFITypeIndex::kTVMFFIStr) {
+ return reinterpret_cast<TVMFFIStr *>(v->v_obj)->data;
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static std::string Type2Str() {
+ return TypeIndexTraits<TVMFFITypeIndex::kTVMFFIRawStr>::type_key;
+ }
+
+ TVM_FFI_INLINE static std::string __str__(const char *src) {
+ return '"' + std::string(src) + '"';
+ }
+};
+
+template <size_t N>
+struct TypeTraits<char[N]> : public TypeTraits<const char *> {};
+
+template <>
+struct TypeTraits<std::string> {
+ static constexpr bool enabled = true;
+
+ TVM_FFI_INLINE static void CopyFromTypeToTVMFFIAny(const std::string &src, TVMFFIAny *ret) {
+ ret->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr);
+ ret->v_str = src.data();
+ }
+
+ TVM_FFI_INLINE static std::string CopyFromTVMFFIAnyToType(const TVMFFIAny *v) {
+ return TypeTraits<const char *>::CopyFromTVMFFIAnyToType(v);
+ }
+
+ TVM_FFI_INLINE static std::string Type2Str() { return "str"; }
+
+ TVM_FFI_INLINE static std::string __str__(const char *src) {
+ return '"' + std::string(src) + '"';
+ }
+};
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+} // namespace ffi
+} // namespace tvm
+
+#endif // TVM_FFI_TRAITS_H_
diff --git a/include/tvm/ffi/core/utils.h b/include/tvm/ffi/core/utils.h
new file mode 100644
index 0000000000..93630058e6
--- /dev/null
+++ b/include/tvm/ffi/core/utils.h
@@ -0,0 +1,281 @@
+#ifndef TVM_FFI_UTILS_H_
+#define TVM_FFI_UTILS_H_
+#ifdef _MSC_VER
+#include <intrin.h>
+#pragma intrinsic(_BitScanReverse64)
+#pragma intrinsic(_InterlockedIncrement)
+#pragma intrinsic(_InterlockedDecrement)
+#endif
+#if __cplusplus >= 202002L
+#include <bit>
+#endif
+#include "./c_ffi_abi.h"
+#include <array>
+#include <cstddef>
+#include <sstream>
+#include <type_traits>
+
+namespace tvm {
+namespace ffi {
+
+/********** Section 1. Macros *********/
+
+#if defined(_MSC_VER)
+#define TVM_FFI_INLINE __forceinline
+#else
+#define TVM_FFI_INLINE inline __attribute__((always_inline))
+#endif
+
+#if defined(_MSC_VER)
+#define TVM_FFI_UNREACHABLE() __assume(false)
+#else
+#define TVM_FFI_UNREACHABLE() __builtin_unreachable()
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define TVM_FFI_SYMBOL_HIDE __attribute__((visibility("hidden")))
+#else
+#define TVM_FFI_SYMBOL_HIDE
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define TVM_FFI_FUNC_SIG __PRETTY_FUNCTION__
+#elif defined(_MSC_VER)
+#define TVM_FFI_FUNC_SIG __FUNCSIG__
+#else
+#define TVM_FFI_FUNC_SIG __func__
+#endif
+
+#define TVM_FFI_DEF_ASSIGN(SelfType, SourceType) \
+ TVM_FFI_INLINE SelfType &operator=(SourceType other) { \
+ SelfType(std::move(other)).Swap(*this); \
+ return *this; \
+ }
+
+#define TVM_FFI_THROW(ErrorKind) \
+ ::tvm::ffi::details::ErrorBuilder(#ErrorKind, __FILE__, TVM_FFI_FUNC_SIG, __LINE__).Get()
+
+/********** Section 2. Any/Object: Extensions and inheritance *********/
+
+template <typename TExtra>
+struct AnyWithExtra {
+ int32_t type_index;
+ int32_t ref_cnt;
+ void (*deleter)(void *);
+ TExtra _extra;
+ TVM_FFI_INLINE AnyWithExtra() = default;
+ // TODO: add tests for copy/move constructors
+ TVM_FFI_INLINE AnyWithExtra(const AnyWithExtra &other) : _extra(other._extra) {}
+ TVM_FFI_INLINE AnyWithExtra(AnyWithExtra &&other) : _extra(std::move(other._extra)) {}
+ TVM_FFI_INLINE AnyWithExtra &operator=(const AnyWithExtra &other) {
+ this->_extra = other._extra;
+ return *this;
+ }
+ TVM_FFI_INLINE AnyWithExtra &operator=(AnyWithExtra &&other) {
+ this->_extra = std::move(other._extra);
+ return *this;
+ }
+};
+
+struct AnyView;
+struct Any;
+struct Object;
+struct Dict;
+struct Error;
+struct Str;
+struct Func;
+template <typename>
+struct Ref;
+template <typename>
+struct RefBase;
+struct List;
+
+template <typename T>
+struct IsAnyWithExtra {
+ template <typename U>
+ static auto test(AnyWithExtra<U> *) -> std::true_type;
+ static auto test(...) -> std::false_type;
+ using type = decltype(test(std::declval<T *>()));
+};
+template <typename T>
+constexpr static bool IsObject = std::is_base_of_v<Object, T> || IsAnyWithExtra<T>::type::value;
+
+template <typename ObjectType>
+struct DefaultObjectAllocator {
+ using Storage = typename std::aligned_storage<sizeof(ObjectType), alignof(ObjectType)>::type;
+
+ template <typename... Args>
+ inline static ObjectType *NewImpl(size_t num_storages, Args &&...args) {
+ Storage *data = new Storage[num_storages];
+ try {
+ new (data) ObjectType(std::forward<Args>(args)...);
+ } catch (...) {
+ delete[] data;
+ throw;
+ }
+ ObjectType *ret = reinterpret_cast<ObjectType *>(data);
+ ret->type_index = ObjectType::_type_index;
+ ret->ref_cnt = 0;
+ ret->deleter = DefaultObjectAllocator::Deleter;
+ return ret;
+ }
+
+ template <typename... Args>
+ TVM_FFI_INLINE static ObjectType *New(Args &&...args) {
+ return NewImpl(1, std::forward<Args>(args)...);
+ }
+
+ template <typename PadType, typename... Args>
+ TVM_FFI_INLINE static ObjectType *NewWithPad(size_t pad_size, Args &&...args) {
+ return NewImpl((sizeof(ObjectType) + pad_size * sizeof(PadType) + sizeof(Storage) - 1) /
+ sizeof(Storage),
+ std::forward<Args>(args)...);
+ }
+
+ static void Deleter(void *objptr) {
+ ObjectType *tptr = static_cast<ObjectType *>(objptr);
+ tptr->ObjectType::~ObjectType();
+ delete[] reinterpret_cast<Storage *>(tptr);
+ }
+};
+
+template <typename T, typename = void>
+struct GetAllocator {
+ using Type = DefaultObjectAllocator<T>;
+};
+template <typename T>
+struct GetAllocator<T, std::void_t<typename T::Allocator>> {
+ using Type = typename T::Allocator;
+};
+
+template <size_t N>
+using CharArray = const char[N];
+
+/********** Section 3. Atomic Operations *********/
+
+namespace details {
+uint64_t TVMFFIStrHash(const TVMFFIStr *str);
+int32_t TVMFFIStrCmp(const TVMFFIStr *a, const TVMFFIStr *b);
+TVM_FFI_INLINE int32_t AtomicIncrementRelaxed(int32_t *ptr) {
+#ifdef _MSC_VER
+ return _InterlockedIncrement(reinterpret_cast<volatile long *>(ptr)) - 1;
+#else
+ return __atomic_fetch_add(ptr, 1, __ATOMIC_RELAXED);
+#endif
+}
+TVM_FFI_INLINE int32_t AtomicDecrementRelAcq(int32_t *ptr) {
+#ifdef _MSC_VER
+ return _InterlockedDecrement(reinterpret_cast<volatile long *>(ptr)) + 1;
+#else
+ return __atomic_fetch_sub(ptr, 1, __ATOMIC_ACQ_REL);
+#endif
+}
+
+TVM_FFI_INLINE void IncRef(TVMFFIObject *obj) {
+ if (obj != nullptr) {
+ AtomicIncrementRelaxed(&obj->ref_cnt);
+ }
+}
+
+TVM_FFI_INLINE void DecRef(TVMFFIObject *obj) {
+ if (obj != nullptr) {
+ if (AtomicDecrementRelAcq(&obj->ref_cnt) == 1) {
+ if (obj->deleter) {
+ obj->deleter(obj);
+ }
+ }
+ }
+}
+
+TVM_FFI_INLINE bool IsTypeIndexNone(int32_t type_index) {
+ return type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone);
+}
+
+TVM_FFI_INLINE bool IsTypeIndexPOD(int32_t type_index) {
+ return type_index < static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStaticObjectBegin);
+}
+
+} // namespace details
+
+/********** Section 4. Errors *********/
+
+struct TemporaryTypeError : public std::exception {};
+
+namespace details {
+
+template <typename T>
+using RemoveCR = std::remove_const_t<std::remove_reference_t<T>>;
+template <typename T>
+static constexpr bool IsAnyOrView = std::is_same_v<T, Any> || std::is_same_v<T, AnyView>;
+
+template <typename>
+struct IsRefImpl {
+ static constexpr bool value = false;
+};
+
+template <typename T>
+struct IsRefImpl<Ref<T>> {
+ static constexpr bool value = true;
+};
+template <typename T>
+constexpr bool IsRef = IsRefImpl<T>::value;
+
+void AnyView2Str(std::ostream &os, const TVMFFIAny *v);
+TVMFFIObject *StrMoveFromStdString(std::string &&source);
+TVMFFIObject *StrCopyFromCharArray(const char *source, size_t length);
+[[noreturn]] void TVMErrorFromBuilder(std::string &&kind, std::string &&lineno,
+ std::string &&message) noexcept(false);
+
+template <typename ParentType, size_t size>
+inline constexpr std::array<int32_t, size> ObjectAncestorsConstExpr() {
+ std::array<int32_t, size> ret{};
+ for (size_t i = 0; i < size; ++i) {
+ ret[i] = (i + 1 == size) ? (ParentType::_type_index) : (ParentType::_type_ancestors[i]);
+ }
+ return ret;
+}
+template <typename ParentType, size_t size>
+inline std::array<int32_t, size> ObjectAncestors() {
+ std::array<int32_t, size> ret{};
+ for (size_t i = 0; i < size; ++i) {
+ ret[i] = (i + 1 == size) ? (ParentType::_type_index) : (ParentType::_type_ancestors[i]);
+ }
+ return ret;
+}
+
+// Disable warning about throwing in the destructor
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4722) // throw in the destructor, which is expected behavior
+#endif
+
+struct ErrorBuilder {
+ const char *kind;
+ TVMFFIStackFrame frame;
+ std::ostringstream oss;
+ std::string line_info;
+
+ explicit ErrorBuilder(const char *kind, const char *filename, const char *func, int32_t lineno)
+ : kind(kind), frame{filename, func, lineno, ErrorBuilder::StackFrameDeleter} {
+ this->line_info = std::string(filename) + ":" + std::to_string(lineno);
+ }
+
+ [[noreturn]] ~ErrorBuilder() noexcept(false) {
+ details::TVMErrorFromBuilder(this->kind, std::move(this->line_info), oss.str());
+ throw;
+ }
+
+ std::ostringstream &Get() { return this->oss; }
+
+protected:
+ static void StackFrameDeleter(void *self) { delete static_cast<TVMFFIStackFrame *>(self); }
+};
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+} // namespace details
+} // namespace ffi
+} // namespace tvm
+#endif // TVM_FFI_UTILS_H_
diff --git a/include/tvm/ffi/ext/dict.h b/include/tvm/ffi/ext/dict.h
new file mode 100644
index 0000000000..e88a29b5e6
--- /dev/null
+++ b/include/tvm/ffi/ext/dict.h
@@ -0,0 +1,515 @@
+#ifndef TVM_FFI_DICT_H_
+#define TVM_FFI_DICT_H_
+#include <iterator>
+#include <tvm/ffi/core/core.h>
+#include <type_traits>
+#include <unordered_map>
+
+namespace tvm {
+namespace ffi {
+namespace details {
+struct _DictHeader {
+ int64_t capacity;
+ int64_t size;
+};
+using DictHeader = AnyWithExtra<_DictHeader>;
+static_assert(sizeof(DictHeader) == sizeof(TVMFFIDict));
+static_assert(offsetof(DictHeader, _extra.capacity) == offsetof(TVMFFIDict, capacity));
+static_assert(offsetof(DictHeader, _extra.size) == offsetof(TVMFFIDict, size));
+int32_t CountLeadingZeros(uint64_t);
+uint64_t BitCeil(uint64_t);
+} // namespace details
+
+/*!
+ * Dict is a flat hash table that uses quadratic/triangluar probing [1] for collision resolution.
+ * Its underlying data structure is a consecutive array of `Block`s, where each `Block`
+ * contains `kCapacity = 16` key-value pairs and their metadata (see also `Block`).
+ * The capacity of a Dict is always a power of 2, and Fibonacci Hashing [2] is used to avoid integer
+ * modulo and help improve the distribution of the hash values.
+ *
+ * Probing is conducted only within a fixed set of 125 possible offsets (see also
+ * `kNextProbeLocation`), which effectively organizes the elements of collision chains in a linked
+ * list. With the help of metadata, we can easily iterate through the linked list and relocate
+ * elements whenever necessary.
+ *
+ * [1] Triangular numbers. https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/
+ * [2] Fibonacci Hashing: https://programmingpraxis.com/2018/06/19/fibonacci-hash/
+ */
+template <typename Hash, typename Equal>
+struct DictBase : protected details::DictHeader {
+protected:
+ /* clang-format off */
+ inline static const constexpr uint64_t kNextProbeLocation[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ /* triangle numbers for quadratic probing */ 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231, 253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595, 630, 666, 703, 741, 780, 820, 861, 903, 946, 990, 1035, 1081, 1128, 1176, 1225, 1275, 1326, 1378, 1431, 1485, 1540, 1596, 1653, 1711, 1770, 1830, 1891, 1953, 2016, 2080, 2145, 2211, 2278, 2346, 2415, 2485, 2556, 2628,
+ /* larger triangle numbers */ 8515, 19110, 42778, 96141, 216153, 486591, 1092981, 2458653, 5532801, 12442566, 27993903, 62983476, 141717030, 318844378, 717352503, 1614057336, 3631522476, 8170957530, 18384510628, 41364789378, 93070452520, 209408356380, 471168559170, 1060128894105, 2385289465695, 5366898840628, 12075518705635, 27169915244790, 61132312065111, 137547689707000, 309482283181501, 696335127828753, 1566753995631385, 3525196511162271, 7931691992677701, 17846306936293605, 40154 [...]
+ };
+ /* clang-format on */
+ static constexpr int32_t kBlockCapacity = 16;
+ static constexpr uint8_t kNumProbe = std::size(kNextProbeLocation);
+ static constexpr uint8_t kEmptySlot = uint8_t(0b11111111);
+ static constexpr uint8_t kProtectedSlot = uint8_t(0b11111110);
+ static constexpr uint64_t kMinSize = 7;
+ static constexpr uint8_t kNewHead = 0b00000000;
+ static constexpr uint8_t kNewTail = 0b10000000;
+ using KVPair = std::pair<TVMFFIAny, TVMFFIAny>;
+
+ struct Block {
+ /**
+ * Each block has a 8-byte key (TVMFFIAny), 8-byte value (TVMFFIAny), and a 1-byte metadata.
+ *
+ * Metadata can be one of the following three cases:
+ * - 1) Empty: 0xFF (0b11111111)_2. The slot is available and can be written in.
+ * - 2) Protected: 0xFE (0b11111110)_2. The slot is empty but not allowed to be written. It is
+ * only used during insertion when relocating certain elements.
+ * - 3) Normal: (0bXYYYYYYY)_2. The highest bit `X` indicates if it is the head of a linked
+ * list, where `0` means head, and `1` means not head. The next 7 bits `YYYYYYY` are used as
+ * the "next pointer" (i.e. pointer to the next element), where `kNextProbeLocation[YYYYYYY]`
+ * is the offset to the next element. And if `YYYYYYY == 0`, it means the end of the linked
+ * list.
+ */
+ uint8_t meta[kBlockCapacity];
+ KVPair data[kBlockCapacity];
+ };
+ static_assert(sizeof(Block) == kBlockCapacity * (1 + sizeof(TVMFFIAny) * 2), "ABI check");
+ static_assert(std::is_aggregate_v<Block>, "ABI check");
+ static_assert(std::is_trivially_destructible_v<Block>, "ABI check");
+ static_assert(std::is_standard_layout_v<Block>, "ABI check");
+ static_assert(kNumProbe == 126, "Check assumption");
+
+ struct ListIter {
+ static ListIter None() { return ListIter(0, nullptr); }
+ static ListIter FromIndex(const DictBase *self, uint64_t i) {
+ if (i >= self->Cap()) // TODO: remove this check
+ TVM_FFI_THROW(InternalError) << "Indexing " << i << " of length " << self->Cap();
+ return ListIter(i, self->Blocks() + (i / kBlockCapacity));
+ }
+ static ListIter FromHash(const DictBase *self, uint64_t h) {
+ return ListIter::FromIndex(self, (11400714819323198485ull * h) >>
+ (details::CountLeadingZeros(self->Cap()) + 1));
+ }
+ auto &Data() const { return cur->data[i % kBlockCapacity]; }
+ uint8_t &Meta() const { return cur->meta[i % kBlockCapacity]; }
+ uint64_t Offset() const { return kNextProbeLocation[Meta() & 0b01111111]; }
+ bool IsHead() const { return (Meta() & 0b10000000) == 0b00000000; }
+ void SetNext(uint8_t jump) const { (Meta() &= 0b10000000) |= jump; }
+ void Advance(const DictBase *self) { *this = WithOffset(self, Offset()); }
+ bool IsNone() const { return cur == nullptr; }
+ ListIter WithOffset(const DictBase *self, uint64_t offset) const {
+ return offset == 0 ? ListIter::None()
+ : ListIter::FromIndex(self, (i + offset) & (self->Cap() - 1));
+ }
+
+ uint64_t i;
+ Block *cur;
+
+ protected:
+ ListIter() = default;
+ explicit ListIter(uint64_t i, Block *cur) : i(i), cur(cur) {}
+ };
+
+ ListIter Head(uint64_t hash) const {
+ ListIter iter = ListIter::FromHash(this, hash);
+ return iter.IsHead() ? iter : ListIter::None();
+ }
+
+ ListIter Prev(ListIter iter) const {
+ ListIter prev = Head(Hash()(iter.Data().first));
+ ListIter next = prev;
+ for (next.Advance(this); next.i != iter.i; prev = next, next.Advance(this)) {
+ }
+ return prev;
+ }
+
+ uint64_t Cap() const { return static_cast<uint64_t>(this->_extra.capacity); }
+ uint64_t Size() const { return this->_extra.size; }
+ Block *Blocks() { return reinterpret_cast<Block *>(static_cast<DictBase *>(this) + 1); }
+ Block *Blocks() const { return const_cast<DictBase *>(this)->Blocks(); }
+
+ ListIter Probe(ListIter cur, uint8_t *result) const {
+ for (uint8_t i = 1; i < kNumProbe && kNextProbeLocation[i] < this->Size(); ++i) {
+ ListIter next = cur.WithOffset(this, kNextProbeLocation[i]);
+ if (next.Meta() == kEmptySlot) {
+ *result = i;
+ return next;
+ }
+ }
+ *result = 0;
+ return ListIter::None();
+ }
+
+ ListIter Lookup(const TVMFFIAny &key) const {
+ uint64_t hash = Hash()(key);
+ for (ListIter iter = Head(hash); !iter.IsNone(); iter.Advance(this)) {
+ if (Equal()(key, iter.Data().first)) {
+ return iter;
+ }
+ }
+ return ListIter::None();
+ }
+
+ template <typename Pred>
+ void IterateAll(Pred pred) {
+ Block *blocks_ = this->Blocks();
+ int64_t num_blocks = this->_extra.capacity / kBlockCapacity;
+ for (int64_t i = 0; i < num_blocks; ++i) {
+ for (int j = 0; j < kBlockCapacity; ++j) {
+ uint8_t &meta = blocks_[i].meta[j];
+ auto &data = blocks_[i].data[j];
+ if (meta != kEmptySlot && meta != kProtectedSlot) {
+ pred(&meta, &data.first, &data.second);
+ }
+ }
+ }
+ }
+
+ void Clear() {
+ this->IterateAll([](uint8_t *meta, TVMFFIAny *key, TVMFFIAny *value) {
+ static_cast<Any *>(key)->Reset();
+ static_cast<Any *>(value)->Reset();
+ *meta = kEmptySlot;
+ });
+ this->_extra.size = 0;
+ }
+
+ void Erase(int64_t index) {
+ ListIter iter = ListIter::FromIndex(this, index);
+ if (uint64_t offset = iter.Offset(); offset != 0) {
+ ListIter prev = iter;
+ ListIter next = iter.WithOffset(this, offset);
+ while ((offset = next.Offset()) != 0) {
+ prev = next;
+ next = next.WithOffset(this, offset);
+ }
+ iter.Data() = next.Data();
+ next.Meta() = kEmptySlot;
+ prev.SetNext(0);
+ } else {
+ if (!iter.IsHead()) {
+ Prev(iter).SetNext(0);
+ }
+ iter.Meta() = kEmptySlot;
+ auto &data = iter.Data();
+ static_cast<Any &>(data.first).Reset();
+ static_cast<Any &>(data.second).Reset();
+ }
+ this->_extra.size -= 1;
+ }
+
+public:
+ struct Iterator;
+ friend struct Iterator;
+ struct Iterator {
+ using iterator_category = std::bidirectional_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = std::pair<const Any, Any>;
+ using pointer = value_type *;
+ using reference = value_type &;
+ static_assert(sizeof(value_type) == sizeof(KVPair), "ABI check");
+ static_assert(offsetof(value_type, first) == offsetof(KVPair, first), "ABI check");
+ static_assert(offsetof(value_type, second) == offsetof(KVPair, second), "ABI check");
+
+ Iterator() = default;
+ Iterator(const Iterator &) = default;
+ Iterator(Iterator &&) = default;
+ Iterator &operator=(const Iterator &) = default;
+ Iterator &operator=(Iterator &&) = default;
+
+ Iterator &operator++() {
+ int64_t cap = self->Cap();
+ while (++index < cap) {
+ if (ListIter::FromIndex(self, index).Meta() != DictBase::kEmptySlot) {
+ return *this;
+ }
+ }
+ return *this;
+ }
+ Iterator &operator--() {
+ while (--index >= 0) {
+ if (ListIter::FromIndex(self, index).Meta() != DictBase::kEmptySlot) {
+ return *this;
+ }
+ }
+ return *this;
+ }
+ Iterator operator++(int) {
+ Iterator copy(*this);
+ this->operator++();
+ return copy;
+ }
+ Iterator operator--(int) {
+ Iterator copy(*this);
+ this->operator--();
+ return copy;
+ }
+ bool operator==(const Iterator &other) const { return index == other.index; }
+ bool operator!=(const Iterator &other) const { return index != other.index; }
+ reference operator*() const { return *(this->operator->()); }
+ pointer operator->() const {
+ return reinterpret_cast<pointer>(&ListIter::FromIndex(self, this->index).Data());
+ }
+
+ protected:
+ friend struct DictBase;
+ Iterator(int64_t index, DictBase *self) : index(index), self(self) {}
+
+ int64_t index;
+ DictBase *self;
+ };
+
+ KVPair *TryInsertOrLookup(TVMFFIAny *key) {
+ if (Cap() == Size() || Size() + 1 > Cap() * 0.99) {
+ return nullptr;
+ }
+ // `iter` starts from the head of the linked list
+ ListIter iter = ListIter::FromHash(this, Hash()(*key));
+ uint8_t new_meta = kNewHead;
+ // There are three cases over all:
+ // 1) available - `iter` points to an empty slot that we could directly write in;
+ // 2) hit - `iter` points to the head of the linked list which we want to iterate through
+ // 3) relocate - `iter` points to the body of a different linked list, and in this case, we
+ // will have to relocate the elements to make space for the new element
+ if (iter.Meta() == kEmptySlot) { // (Case 1) available
+ // Do nothing
+ } else if (iter.IsHead()) { // (Case 2) hit
+ // Point `iter` to the last element of the linked list
+ for (ListIter prev = iter;; prev = iter) {
+ if (Equal()(*key, iter.Data().first)) {
+ return &iter.Data();
+ }
+ iter.Advance(this);
+ if (iter.IsNone()) {
+ iter = prev;
+ break;
+ }
+ }
+ // Prob at the end of the linked list for the next empty slot
+ ListIter prev = iter;
+ uint8_t jump;
+ iter = Probe(iter, &jump);
+ if (jump == 0) {
+ return nullptr;
+ }
+ prev.SetNext(jump);
+ new_meta = kNewTail;
+ } else {
+ // (Case 3) relocate: Chop the list starting from `iter`, and move it to other locations.
+ ListIter next = iter, prev = Prev(iter);
+ // Loop invariant:
+ // - `next` points to the first element of the list we are going to relocate.
+ // - `prev` points to the last element of the list that we are relocating to.
+ for (uint8_t next_meta = kProtectedSlot; !next.IsNone(); next_meta = kEmptySlot) {
+ // Step 1. Prob for the next empty slot `new_next` after `prev`
+ uint8_t jump;
+ ListIter new_next = Probe(prev, &jump);
+ if (jump == 0) {
+ return nullptr;
+ }
+ // Step 2. Relocate `next` to `new_next`
+ new_next.Meta() = kNewTail;
+ new_next.Data() = {next.Data().first, next.Data().second};
+ std::swap(next_meta, next.Meta());
+ prev.SetNext(jump);
+ // Step 3. Update `prev` -> `new_next`, `next` -> `Advance(next)`
+ prev = new_next;
+ next = next.WithOffset(this, kNextProbeLocation[next_meta & 0b01111111]);
+ }
+ }
+ this->_extra.size += 1;
+ iter.Meta() = new_meta;
+ auto &data = iter.Data() = {*key, TVMFFIAny()};
+ key->type_index = 0;
+ key->v_int64 = 0;
+ return &data;
+ }
+
+ Any &at(const Any &key) {
+ ListIter iter = Lookup(key);
+ if (iter.IsNone()) {
+ TVM_FFI_THROW(KeyError) << "Key not found";
+ }
+ return static_cast<Any &>(iter.Data().second);
+ }
+
+ const Any &at(const Any &key) const {
+ ListIter iter = Lookup(key);
+ if (iter.IsNone()) {
+ TVM_FFI_THROW(KeyError) << "Key not found";
+ }
+ return static_cast<const Any &>(iter.Data().second);
+ }
+ Any &operator[](const Any &key) = delete; // May need rehashing
+ const Any &operator[](const Any &key) const { return this->at(key); }
+ int64_t size() const { return this->_extra.size; }
+ int64_t count(const Any &key) const { return Lookup(key).IsNone() ? 0 : 1; }
+ void clear() { this->Clear(); }
+ Iterator begin() { return Iterator(-1, this).operator++(); }
+ Iterator end() { return Iterator(this->Cap(), this); }
+ Iterator find(const Any &key) {
+ ListIter iter = Lookup(key);
+ return iter.IsNone() ? end() : Iterator(iter.i, this);
+ }
+ void erase(const Any &key) {
+ ListIter iter = Lookup(key);
+ if (!iter.IsNone()) {
+ Erase(iter.i);
+ } else {
+ TVM_FFI_THROW(KeyError) << "Key not found";
+ }
+ }
+};
+
+struct AnyHash {
+ uint64_t operator()(const TVMFFIAny &a) const {
+ if (a.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr)) {
+ return details::TVMFFIStrHash(reinterpret_cast<TVMFFIStr *>(a.v_obj));
+ }
+ union {
+ int64_t i64;
+ uint64_t u64;
+ } cvt;
+ cvt.i64 = a.v_int64;
+ return cvt.u64;
+ }
+};
+
+struct AnyEqual {
+ bool operator()(const TVMFFIAny &a, const TVMFFIAny &b) const {
+ if (a.type_index != b.type_index) {
+ return false;
+ }
+ if (a.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIStr)) {
+ return details::TVMFFIStrCmp(reinterpret_cast<TVMFFIStr *>(a.v_obj),
+ reinterpret_cast<TVMFFIStr *>(b.v_obj)) == 0;
+ }
+ return a.v_int64 == b.v_int64;
+ }
+};
+
+struct Dict : protected DictBase<AnyHash, AnyEqual> {
+ TVM_FFI_DEF_STATIC_TYPE(Dict, Object, TVMFFITypeIndex::kTVMFFIDict);
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ struct Allocator;
+ friend struct Allocator;
+
+protected:
+ explicit Dict(int64_t capacity) {
+ this->_extra.capacity = capacity;
+ this->_extra.size = 0;
+ for (int64_t i = 0; i < capacity / kBlockCapacity; ++i) {
+ Block &block = Blocks()[i];
+ std::memset(block.meta, kEmptySlot, sizeof(block.meta));
+ }
+ }
+ ~Dict() { this->Clear(); }
+};
+
+struct Dict::Allocator {
+ static TVM_FFI_INLINE Dict *New(int64_t capacity) {
+ if ((capacity & (capacity - 1)) != 0 || capacity % kBlockCapacity != 0) {
+ TVM_FFI_THROW(InternalError) << "capacity: " << capacity;
+ }
+ int64_t num_blocks = capacity / Dict::kBlockCapacity;
+ return DefaultObjectAllocator<Dict>::NewWithPad<Dict::Block>(num_blocks, capacity);
+ }
+ static TVM_FFI_INLINE Dict *New() { return New(0); }
+};
+
+template <>
+struct Ref<Dict> : public RefBase<Dict> {
+ using TSelf = Ref<Dict>;
+ using TBase = RefBase<Dict>;
+ using Iterator = Dict::Iterator;
+
+ TVM_FFI_REF_DEF_DELEGATE_CONSTRUCTORS(Ref<Dict>, RefBase<Dict>)
+ TVM_FFI_DEF_TYPE_FRIENDS();
+
+ Ref() : TBase(Dict::Allocator::New()) {}
+ template <typename Iter>
+ Ref(Iter begin, Iter end) : TBase(FromRange(begin, end)) {}
+ Ref(std::initializer_list<std::pair<Any, Any>> init) : TSelf(init.begin(), init.end()) {}
+ template <typename K, typename V, typename Hash, typename Equal>
+ Ref(const std::unordered_map<K, V, Hash, Equal> &source) : TSelf(source.begin(), source.end()) {}
+ const Any &at(const Any &key) const { return get()->at(key); }
+ Any &at(const Any &key) { return get()->at(key); }
+ const Any &operator[](const Any &key) const { return get()->at(key); }
+ Any &operator[](Any key) { return static_cast<Any &>(InsertOrLookup(std::move(key))->second); }
+ int64_t size() const { return get()->size(); }
+ int64_t count(const Any &key) const { return get()->count(key); }
+ bool empty() const { return size() == 0; }
+ void clear() { get()->clear(); }
+ Iterator begin() { return get()->begin(); }
+ Iterator end() { return get()->end(); }
+ Iterator find(const Any &key) { return get()->find(key); }
+ void erase(const Any &key) { get()->erase(key); }
+
+protected:
+ using TBase::operator*;
+ using TBase::operator->;
+
+ Dict::KVPair *InsertOrLookup(Any key) {
+ for (;;) {
+ if (Dict::KVPair *ret = get()->TryInsertOrLookup(&key)) {
+ return ret;
+ }
+ Ref<Dict>::FromRange(begin(), end(), get()->Cap() * 2).Swap(*this);
+ }
+ TVM_FFI_UNREACHABLE();
+ }
+
+ template <typename Iter>
+ static Ref<Dict> FromRange(Iter begin, Iter end) {
+ int64_t num = std::distance(begin, end);
+ int64_t capacity = details::BitCeil(num * 2);
+ capacity = std::max<int64_t>(
+ (capacity + Dict::kBlockCapacity - 1) & ~(Dict::kBlockCapacity - 1), Dict::kBlockCapacity);
+ Ref<Dict> dict(Dict::Allocator::New(capacity));
+ for (; begin != end; ++begin) {
+ Dict::KVPair *ret = dict.InsertOrLookup(Any(begin->first));
+ static_cast<Any &>(ret->second) = Any(begin->second);
+ }
+ return dict;
+ }
+
+ template <typename Iter>
+ static Ref<Dict> FromRange(Iter begin, Iter end, int64_t capacity) {
+ capacity = std::max<int64_t>(
+ (capacity + Dict::kBlockCapacity - 1) & ~(Dict::kBlockCapacity - 1), Dict::kBlockCapacity);
+ Ref<Dict> dict(Dict::Allocator::New(capacity));
+ for (; begin != end; ++begin) {
+ Dict::KVPair *ret = dict.InsertOrLookup(Any(begin->first));
+ static_cast<Any &>(ret->second) = Any(begin->second);
+ }
+ return dict;
+ }
+};
+
+namespace details {
+
+TVM_FFI_INLINE int32_t CountLeadingZeros(uint64_t x) {
+#if __cplusplus >= 202002L
+ return std::countl_zero(x);
+#elif defined(_MSC_VER)
+ DWORD leading_zero = 0;
+ if (_BitScanReverse64(&leading_zero, value)) {
+ return static_cast<int32_t>(63 - leading_zero);
+ } else {
+ return 64;
+ }
+#else
+ return x == 0 ? 64 : __builtin_clzll(x);
+#endif
+}
+
+TVM_FFI_INLINE uint64_t BitCeil(uint64_t x) {
+#if __cplusplus >= 202002L
+ return std::bit_ceil(x);
+#else
+ return x <= 1 ? 1 : static_cast<uint64_t>(1) << (64 - CountLeadingZeros(x - 1));
+#endif
+}
+
+} // namespace details
+} // namespace ffi
+} // namespace tvm
+#endif // TVM_FFI_DICT_H_
diff --git a/include/tvm/ffi/ext/error.h b/include/tvm/ffi/ext/error.h
new file mode 100644
index 0000000000..5d8ec47582
--- /dev/null
+++ b/include/tvm/ffi/ext/error.h
@@ -0,0 +1,89 @@
+#ifndef TVM_FFI_ERROR_H_
+#define TVM_FFI_ERROR_H_
+
+#include <string>
+#include <tvm/ffi/core/core.h>
+#include <vector>
+
+namespace tvm {
+namespace ffi {
+namespace details {
+struct _ErrorHeader {
+ const char *kind;
+ int32_t num_frames;
+ const char **linenos;
+ const char *message;
+};
+using ErrorHeader = AnyWithExtra<_ErrorHeader>;
+static_assert(sizeof(ErrorHeader) == sizeof(TVMFFIError));
+static_assert(offsetof(ErrorHeader, _extra.kind) == offsetof(TVMFFIError, kind));
+static_assert(offsetof(ErrorHeader, _extra.num_frames) == offsetof(TVMFFIError, num_frames));
+static_assert(offsetof(ErrorHeader, _extra.linenos) == offsetof(TVMFFIError, linenos));
+static_assert(offsetof(ErrorHeader, _extra.message) == offsetof(TVMFFIError, message));
+} // namespace details
+
+struct Error : private details::ErrorHeader {
+ TVM_FFI_DEF_STATIC_TYPE(Error, Object, TVMFFITypeIndex::kTVMFFIError);
+
+ std::string kind;
+ std::vector<std::string> linenos;
+ std::string message;
+ std::vector<const char *> lineno_holder;
+
+ Error(std::string kind, std::string lineno, std::string message)
+ : kind(kind), linenos({lineno}), message(message), lineno_holder() {
+ this->_extra.kind = this->kind.data();
+ this->_extra.num_frames = static_cast<int32_t>(this->linenos.size());
+ this->_extra.message = this->message.data();
+ this->RefreshLinenoHolder();
+ }
+
+ void Append(std::string lineno) {
+ linenos.push_back(lineno);
+ this->RefreshLinenoHolder();
+ }
+
+protected:
+ void RefreshLinenoHolder() {
+ this->lineno_holder.resize(linenos.size());
+ for (size_t i = 0; i < linenos.size(); ++i) {
+ this->lineno_holder[i] = linenos[i].data();
+ }
+ this->_extra.linenos = this->lineno_holder.data();
+ }
+};
+
+struct TVMError : public std::exception {
+ Ref<Error> data_;
+
+ TVMError(Ref<Error> data) : data_(data) {}
+ TVMError(const TVMError &other) : data_(other.data_) {}
+ TVMError(TVMError &&other) : data_(std::move(other.data_)) {}
+ TVMError &operator=(const TVMError &) = delete;
+ TVMError &operator=(TVMError &&) = delete;
+
+ const char *what() const noexcept(true) override {
+ if (data_.get() == nullptr) {
+ return "TVMError: Unspecified";
+ }
+ return data_->message.c_str();
+ }
+
+ void MoveToAny(Any *v) {
+ *v = data_;
+ data_ = Ref<Error>(nullptr);
+ }
+};
+
+namespace details {
+[[noreturn]] TVM_FFI_INLINE void TVMErrorFromBuilder(std::string &&kind, std::string &&lineno,
+ std::string &&message) noexcept(false) {
+ Ref<Error> ret = Ref<Error>::New(std::move(kind), std::move(lineno), std::move(message));
+ throw TVMError(ret);
+}
+} // namespace details
+
+} // namespace ffi
+} // namespace tvm
+
+#endif // TVM_FFI_ERROR_H_
diff --git a/include/tvm/ffi/ext/func.h b/include/tvm/ffi/ext/func.h
new file mode 100644
index 0000000000..96dea9a259
--- /dev/null
+++ b/include/tvm/ffi/ext/func.h
@@ -0,0 +1,390 @@
+#ifndef TVM_FFI_FUNC_H_
+#define TVM_FFI_FUNC_H_
+
+#include "./error.h"
+#include "./str.h"
+#include <functional>
+#include <sstream>
+#include <tvm/ffi/core/core.h>
+#include <type_traits>
+
+namespace tvm {
+namespace ffi {
+namespace details {
+struct FuncInfo {
+ using Call = void(const Func *, int32_t, const AnyView *, Any *);
+ using SafeCall = int32_t(const Func *, int32_t, const AnyView *, Any *);
+ Call *call;
+ SafeCall *safe_call;
+ static TVM_FFI_SYMBOL_HIDE int32_t SafeCallImpl(const Func *, int32_t, const AnyView *, Any *);
+};
+} // namespace details
+
+struct Func : private AnyWithExtra<details::FuncInfo> {
+ friend struct details::FuncInfo;
+ using Header = AnyWithExtra<details::FuncInfo>;
+ struct Allocator;
+ TVM_FFI_DEF_STATIC_TYPE(Func, Object, TVMFFITypeIndex::kTVMFFIFunc);
+
+ template <typename... Args>
+ Any operator()(Args &&...args) const;
+
+protected:
+ TVM_FFI_INLINE Func(details::FuncInfo::Call *f) : AnyWithExtra<details::FuncInfo>() {
+ this->_extra.call = f;
+ this->_extra.safe_call = details::FuncInfo::SafeCallImpl;
+ }
+};
+static_assert(sizeof(Func) == sizeof(TVMFFIFunc));
+static_assert(offsetof(Func::Header, _extra.call) == offsetof(TVMFFIFunc, call));
+static_assert(offsetof(Func::Header, _extra.safe_call) == offsetof(TVMFFIFunc, safe_call));
+
+template <>
+struct Ref<Func> : public RefBase<Func> {
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ TVM_FFI_INLINE Ref() : RefBase<Func>() {}
+ TVM_FFI_REF_DEF_DELEGATE_CONSTRUCTORS(Ref<Func>, RefBase<Func>)
+
+ template <typename... Args>
+ TVM_FFI_INLINE auto operator()(Args &&...args) const {
+ return (*get())(std::forward<Args>(args)...);
+ }
+ template <typename... Args>
+ TVM_FFI_INLINE auto operator()(Args &&...args) {
+ return (*get())(std::forward<Args>(args)...);
+ }
+};
+
+template <std::size_t N>
+struct AnyViewArray {
+ template <typename... Args>
+ void Fill(Args &&...args);
+ AnyView v[N];
+};
+
+template <>
+struct AnyViewArray<0> {
+ template <typename... Args>
+ void Fill(Args &&...args);
+ AnyView *v = nullptr;
+};
+
+template <typename... Args>
+TVM_FFI_INLINE Any Func::operator()(Args &&...args) const {
+ constexpr size_t N = sizeof...(Args);
+ AnyViewArray<N> stack_args;
+ stack_args.Fill(std::forward<Args>(args)...);
+ return ::tvm::ffi::details::FuncInvoke(this, N, stack_args.v);
+}
+
+namespace details {
+
+template <typename, typename>
+struct UnpackCall;
+template <typename T>
+struct FuncFunctor;
+
+TVM_FFI_INLINE int32_t FuncInfo::SafeCallImpl(const Func *self, int32_t num_args,
+ const AnyView *args, Any *ret) {
+ try {
+ self->_extra.call(self, num_args, args, ret);
+ return 0;
+ } catch (TVMError &err) {
+ err.MoveToAny(ret);
+ return 1;
+ } catch (std::runtime_error &err) {
+ *ret = Ref<Str>::New(err.what());
+ return 1;
+ }
+ TVM_FFI_UNREACHABLE();
+}
+
+TVM_FFI_INLINE Any FuncInvoke(const void *self, int32_t num_args, const TVMFFIAny *args) {
+ const TVMFFIFunc *func = static_cast<const TVMFFIFunc *>(self);
+ FuncInfo::SafeCall *local_safe_call = FuncInfo::SafeCallImpl;
+ Any ret;
+ if (reinterpret_cast<void *>(func->safe_call) == reinterpret_cast<void *>(local_safe_call)) {
+ func->call(func, num_args, args, &ret);
+ return ret;
+ }
+ int32_t err_code = func->safe_call(func, num_args, args, &ret);
+ if (err_code == 0) {
+ return ret;
+ }
+ if (ret.type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIError)) {
+ Ref<Error> err = Ref<Error>(std::move(ret));
+ throw TVMError(err);
+ }
+ TVM_FFI_THROW(InternalError) << "Error code: " << err_code;
+ TVM_FFI_UNREACHABLE();
+}
+
+template <typename FuncType>
+struct FuncImpl : public Func {
+ using TSelf = FuncImpl<FuncType>;
+ using Allocator = DefaultObjectAllocator<TSelf>;
+
+ TVM_FFI_INLINE static void CallPacked(const Func *obj, int32_t num_args, const AnyView *args,
+ Any *ret) {
+ static_cast<const TSelf *>(obj)->func_(num_args, args, ret);
+ }
+
+ TVM_FFI_INLINE static void CallUnpacked(const Func *obj, int32_t num_args, const AnyView *args,
+ Any *ret) {
+ using ArgType = typename FuncFunctor<FuncType>::ArgType;
+ constexpr int32_t N = std::tuple_size_v<ArgType>;
+ if (num_args != N) {
+ TVM_FFI_THROW(TypeError) << "Mismatched number of arguments when calling: `"
+ << FuncFunctor<FuncType>::Sig() << "`. Expected " << N << " but got "
+ << num_args << " arguments";
+ }
+ using IdxSeq = std::make_index_sequence<N>;
+ using RetType = typename FuncFunctor<FuncType>::RetType;
+ UnpackCall<RetType, ArgType>::template Run<FuncType>(&static_cast<const TSelf *>(obj)->func_,
+ args, ret, IdxSeq{});
+ }
+
+ TVM_FFI_INLINE explicit FuncImpl(FuncType func, details::FuncInfo::Call *f)
+ : Func(f), func_(std::forward<FuncType>(func)) {}
+
+ TVM_FFI_INLINE static Func *FromPacked(FuncType func) {
+ return Allocator::New(std::forward<FuncType>(func), TSelf::CallPacked);
+ }
+
+ TVM_FFI_INLINE static Func *FromUnpacked(FuncType func) {
+ return Allocator::New(std::forward<FuncType>(func), TSelf::CallUnpacked);
+ }
+
+ mutable std::decay_t<FuncType> func_;
+};
+
+template <typename Type>
+struct Type2Str {
+ [[maybe_unused]] static inline std::string type_str = TypeTraitsNoCR<Type>::Type2Str();
+};
+template <>
+struct Type2Str<Any> {
+ static constexpr const char *type_str = "Any";
+};
+template <>
+struct Type2Str<AnyView> {
+ static constexpr const char *type_str = "AnyView";
+};
+template <>
+struct Type2Str<void> {
+ static constexpr const char *type_str = "void";
+};
+
+template <typename ArgType>
+struct Arg2Str {
+ template <size_t i>
+ static TVM_FFI_INLINE void Apply(std::ostream &os) {
+ using Arg = std::tuple_element_t<i, ArgType>;
+ if constexpr (i != 0) {
+ os << ", ";
+ }
+ os << i << ": " << Type2Str<Arg>::type_str;
+ }
+ template <size_t... I>
+ static TVM_FFI_INLINE void Run(std::ostream &os, std::index_sequence<I...>) {
+ using TExpander = int[];
+ (void)TExpander{0, (Apply<I>(os), 0)...};
+ }
+};
+
+template <typename T>
+static constexpr bool ArgSupported = IsAnyOrView<RemoveCR<T>> || TypeTraitsNoCR<T>::enabled;
+template <typename T>
+static constexpr bool RetSupported =
+ IsAnyOrView<RemoveCR<T>> || std::is_void_v<T> || TypeTraitsNoCR<T>::enabled;
+
+template <typename R, typename... Args>
+struct FuncFunctorImpl {
+ using FType = R(Args...);
+ using ArgType = std::tuple<Args...>;
+ using RetType = R;
+ static constexpr bool packed =
+ std::is_convertible_v<FType, std::function<void(int32_t, const AnyView *, Any *)>>;
+ static constexpr bool unpacked = (ArgSupported<Args> && ...) && (RetSupported<R>);
+ static TVM_FFI_INLINE std::string Sig() {
+ using IdxSeq = std::make_index_sequence<sizeof...(Args)>;
+ std::ostringstream ss;
+ ss << "(";
+ Arg2Str<std::tuple<Args...>>::Run(ss, IdxSeq{});
+ ss << ") -> " << Type2Str<R>::type_str;
+ return ss.str();
+ }
+};
+
+template <typename R, typename... Args>
+struct FuncFunctor<R(Args...)> : FuncFunctorImpl<R, Args...> {};
+template <typename R, typename... Args>
+struct FuncFunctor<R (*)(Args...)> : FuncFunctorImpl<R, Args...> {};
+
+template <int32_t i, typename>
+struct PrependIntegerSeq;
+template <int32_t i, int32_t... Is>
+struct PrependIntegerSeq<i, std::integer_sequence<int32_t, Is...>> {
+ using type = std::integer_sequence<int32_t, i, Is...>;
+};
+template <int32_t I, typename>
+struct IndexIntSeq;
+template <int32_t I, int32_t Head, int32_t... Tail>
+struct IndexIntSeq<I, std::integer_sequence<int32_t, Head, Tail...>>
+ : IndexIntSeq<I - 1, std::integer_sequence<int32_t, Tail...>> {};
+template <int32_t Head, int32_t... Tail>
+struct IndexIntSeq<0, std::integer_sequence<int32_t, Head, Tail...>> {
+ static constexpr int32_t value = Head;
+};
+
+template <typename Function, typename StorageInfo>
+struct UnpackCallArgConverter {
+ template <typename _Type, size_t i>
+ struct AsType {
+ inline static auto Run(const AnyView &v, Any *storage) {
+ using Type = std::decay_t<_Type>;
+ constexpr int32_t storage_index = IndexIntSeq<i, typename StorageInfo::sum>::value;
+ try {
+ if constexpr (storage_index == -1) {
+ return v.operator Type();
+ } else {
+ return v.CastWithStorage<Type>(storage + storage_index);
+ }
+ } catch (const TVMError &) {
+ TVM_FFI_THROW(TypeError) << "Mismatched type on argument #" << i << " when calling: `"
+ << FuncFunctor<Function>::Sig() << "`. Expected `"
+ << Type2Str<Type>::type_str << "` but got `"
+ << TypeIndex2TypeKey(v.type_index) << "`";
+ }
+ TVM_FFI_UNREACHABLE();
+ }
+ };
+
+ template <size_t i>
+ struct AsType<AnyView, i> {
+ TVM_FFI_INLINE static AnyView Run(const AnyView &v, Any *) { return v; }
+ };
+
+ template <size_t i>
+ struct AsType<Any, i> {
+ TVM_FFI_INLINE static Any Run(const AnyView &v, Any *) { return v; }
+ };
+};
+
+template <typename T, typename = void>
+struct RequiresFFIStorage {
+ static constexpr int32_t value = 0;
+};
+template <typename T>
+struct RequiresFFIStorage<
+ T, std::void_t<decltype(TypeTraitsNoCR<T>::CopyFromTVMFFIAnyToTypeWithStorage)>> {
+ static constexpr int32_t value = 1;
+};
+
+template <typename... Args>
+struct FFIStorageInfo;
+template <>
+struct FFIStorageInfo<> {
+ constexpr static int32_t total = 0;
+ using sum = std::integer_sequence<int32_t>;
+};
+template <typename I, typename... Is>
+struct FFIStorageInfo<I, Is...> {
+private:
+ using Prev = FFIStorageInfo<Is...>;
+ constexpr static int32_t need_ffi_storage = RequiresFFIStorage<I>::value;
+
+public:
+ constexpr static int32_t total = Prev::total + need_ffi_storage;
+ using sum =
+ typename PrependIntegerSeq<(need_ffi_storage ? total : 0) - 1, typename Prev::sum>::type;
+};
+
+template <typename RetType, typename... Args>
+struct UnpackCall<RetType, std::tuple<Args...>> {
+ template <typename Function, typename FuncType, size_t... I>
+ TVM_FFI_INLINE static void Run(FuncType *func, const AnyView *args, Any *ret,
+ std::index_sequence<I...>) {
+ using Storage = FFIStorageInfo<Args...>;
+ using CVT = UnpackCallArgConverter<Function, Storage>;
+ if constexpr (Storage::total > 0 && std::is_void_v<RetType>) {
+ Any storage[Storage::total];
+ ret->Reset();
+ (*func)(CVT::template AsType<Args, I>::Run(args[I], storage)...);
+ } else if constexpr (Storage::total > 0 && !std::is_void_v<RetType>) {
+ Any storage[Storage::total];
+ *ret = (*func)(CVT::template AsType<Args, I>::Run(args[I], storage)...);
+ } else if constexpr (Storage::total == 0 && std::is_void_v<RetType>) {
+ ret->Reset();
+ (*func)(CVT::template AsType<Args, I>::Run(args[I], nullptr)...);
+ } else if constexpr (Storage::total == 0 && !std::is_void_v<RetType>) {
+ *ret = (*func)(CVT::template AsType<Args, I>::Run(args[I], nullptr)...);
+ }
+ }
+};
+
+template <typename... Args>
+struct AnyViewArrayFill {
+ template <std::size_t i, typename Arg>
+ TVM_FFI_INLINE static void Apply(AnyView *v, Arg &&arg) {
+ v[i] = AnyView(std::forward<Arg>(arg));
+ }
+ template <std::size_t... I>
+ TVM_FFI_INLINE static void Run(AnyView *v, Args &&...args, std::index_sequence<I...>) {
+ using TExpander = int[];
+ (void)TExpander{0, (Apply<I>(v, std::forward<Args>(args)), 0)...};
+ }
+};
+
+template <typename... Args>
+TVM_FFI_INLINE void FillAnyView(AnyView *v, Args &&...args) {
+ constexpr std::size_t N = sizeof...(args);
+ if constexpr (N > 0) {
+ using IdxSeq = std::make_index_sequence<N>;
+ ::tvm::ffi::details::AnyViewArrayFill<Args...>::Run(v, std::forward<Args>(args)..., IdxSeq{});
+ }
+}
+
+} // namespace details
+
+struct Func::Allocator {
+public:
+ template <typename FuncType, typename = void>
+ struct Impl {};
+
+ template <typename FuncType>
+ struct Impl<FuncType, std::enable_if_t<details::FuncFunctor<FuncType>::packed>> {
+ TVM_FFI_INLINE static Func *Run(FuncType func) {
+ return details::FuncImpl<FuncType>::FromPacked(std::forward<FuncType>(func));
+ }
+ };
+
+ template <typename FuncType>
+ struct Impl<FuncType, std::enable_if_t<details::FuncFunctor<FuncType>::unpacked>> {
+ TVM_FFI_INLINE static Func *Run(FuncType func) {
+ return details::FuncImpl<FuncType>::FromUnpacked(std::forward<FuncType>(func));
+ }
+ };
+
+ template <typename FuncType>
+ TVM_FFI_INLINE static Func *New(FuncType func) {
+ return Impl<FuncType>::Run(std::forward<FuncType>(func));
+ }
+};
+
+template <std::size_t N>
+template <typename... Args>
+TVM_FFI_INLINE void AnyViewArray<N>::Fill(Args &&...args) {
+ static_assert(sizeof...(args) == N, "Invalid number of arguments");
+ ::tvm::ffi::details::FillAnyView(v, std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+TVM_FFI_INLINE void AnyViewArray<0>::Fill(Args &&...args) {
+ static_assert(sizeof...(args) == 0, "Invalid number of arguments");
+}
+
+} // namespace ffi
+} // namespace tvm
+
+#endif // TVM_FFI_FUNC_H_
diff --git a/include/tvm/ffi/ext/list.h b/include/tvm/ffi/ext/list.h
new file mode 100644
index 0000000000..cc6daff35c
--- /dev/null
+++ b/include/tvm/ffi/ext/list.h
@@ -0,0 +1,457 @@
+#ifndef TVM_FFI_LIST_H_
+#define TVM_FFI_LIST_H_
+#include <cstddef>
+#include <cstring>
+#include <initializer_list>
+#include <iterator>
+#include <tvm/ffi/core/core.h>
+#include <vector>
+
+namespace tvm {
+namespace ffi {
+namespace details {
+struct _ListHeaeder {
+ int64_t list_capacity; // sizeof(TVMFFIAny*) per element
+ int64_t list_length;
+ int64_t pool_capacity; // sizeof(TVMFFIAny) per element
+ int64_t pool_length;
+};
+using ListHeader = AnyWithExtra<_ListHeaeder>;
+static_assert(sizeof(ListHeader) == sizeof(TVMFFIList));
+static_assert(offsetof(ListHeader, _extra.list_capacity) == offsetof(TVMFFIList, list_capacity));
+static_assert(offsetof(ListHeader, _extra.list_length) == offsetof(TVMFFIList, list_length));
+static_assert(offsetof(ListHeader, _extra.pool_capacity) == offsetof(TVMFFIList, pool_capacity));
+static_assert(offsetof(ListHeader, _extra.pool_length) == offsetof(TVMFFIList, pool_length));
+void ListRangeCheck(int64_t begin, int64_t end, int64_t length);
+} // namespace details
+
+#define TVM_FFI_LIST_EXEC_IF_POD(ListCls, TypeIndex, Exec) \
+ if constexpr (ListCls::all_items_are_pod) { \
+ Exec; \
+ } else if constexpr (!ListCls::all_items_are_obj) { \
+ if (details::IsTypeIndexPOD(TypeIndex)) { \
+ Exec; \
+ } \
+ }
+
+template <typename ListClass>
+struct ListMethods {
+ using ListItem = TVMFFIAny *;
+ using PoolItem = TVMFFIAny;
+
+private:
+ // Section 1: Iterator and utility classes
+
+ struct Iterator {
+ using iterator_category = std::random_access_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = AnyView;
+ using pointer = AnyView;
+ using reference = AnyView;
+
+ Iterator() = default;
+ Iterator(const Iterator &) = default;
+ Iterator(Iterator &&) = default;
+ Iterator &operator=(const Iterator &) = default;
+ Iterator &operator=(Iterator &&) = default;
+
+ Iterator &operator++() {
+ ++index;
+ return *this;
+ }
+ Iterator &operator--() {
+ --index;
+ return *this;
+ }
+ Iterator operator++(int) {
+ Iterator ret = *this;
+ ++index;
+ return ret;
+ }
+ Iterator operator--(int) {
+ Iterator ret = *this;
+ --index;
+ return ret;
+ }
+ Iterator operator+(difference_type n) const {
+ Iterator ret = *this;
+ ret.index += n;
+ return ret;
+ }
+ Iterator operator-(difference_type n) const {
+ Iterator ret = *this;
+ ret.index -= n;
+ return ret;
+ }
+ Iterator &operator+=(difference_type n) {
+ index += n;
+ return *this;
+ }
+ Iterator &operator-=(difference_type n) {
+ index -= n;
+ return *this;
+ }
+ AnyView operator[](difference_type n) const { return self->GetItem(index + n); }
+ difference_type operator-(const Iterator &other) const { return index - other.index; }
+ bool operator==(const Iterator &other) const { return index == other.index; }
+ bool operator!=(const Iterator &other) const { return index != other.index; }
+ reference operator*() const { return self->GetItem(index); }
+ pointer operator->() const = delete;
+
+ protected:
+ friend struct ListMethods;
+ Iterator(ListClass *self, int64_t index) : self(self), index(index) {}
+ ListClass *self;
+ int64_t index;
+ };
+
+ struct ReverseIterator : public std::reverse_iterator<Iterator> {
+ using std::reverse_iterator<Iterator>::reverse_iterator;
+ };
+
+ TVM_FFI_INLINE auto *Get() { return &(static_cast<ListClass *>(this)->_extra); }
+ TVM_FFI_INLINE auto *Get() const { return &(static_cast<const ListClass *>(this)->_extra); }
+ TVM_FFI_INLINE auto ListBegin() const {
+ return reinterpret_cast<const ListItem *>(static_cast<const ListClass *>(this) + 1);
+ }
+ TVM_FFI_INLINE auto PoolBegin() const {
+ return reinterpret_cast<const PoolItem *>(ListBegin() + Get()->list_capacity);
+ }
+ TVM_FFI_INLINE auto ListBegin() {
+ return reinterpret_cast<ListItem *>(static_cast<ListClass *>(this) + 1);
+ }
+ TVM_FFI_INLINE auto PoolBegin() {
+ return reinterpret_cast<PoolItem *>(ListBegin() + Get()->list_capacity);
+ }
+
+public:
+ // Section 2: Interfaces - exposed to Ref<ListClass>
+ void inplace_init_many(int64_t numel, Any *first) {
+ this->ReplaceRangeInplace(0, 0, numel, first);
+ }
+ void inplace_push_back(Any value) {
+ this->ReplaceRangeInplace(Get()->list_length, Get()->list_length, 1, &value);
+ }
+ void inplace_insert(int64_t index, Any value) {
+ this->ReplaceRangeInplace(index, index, 1, &value);
+ }
+ void inplace_insert_many(int64_t index, int64_t numel, Any *first) {
+ this->ReplaceRangeInplace(index, index, numel, first);
+ }
+ void inplace_move_from(ListClass *other) {
+ // Exception safety is guaranteed even if there is an exception thrown during
+ // this process, because neither `list_length` is modified.
+ int64_t &new_pool_len = Get()->pool_length;
+ int64_t &new_list_len = Get()->list_length;
+ ListItem *new_list_base = this->ListBegin();
+ PoolItem *new_pool_base = this->PoolBegin();
+ ListItem *old_list_base = other->ListBegin();
+ for (int64_t i = 0; i < other->_extra.list_length; ++i) {
+ ListItem &e = new_list_base[i] = old_list_base[i];
+ if (e) {
+ TVM_FFI_LIST_EXEC_IF_POD(ListClass, e->type_index, {
+ // Copy POD data to the new POD pool
+ e = &(new_pool_base[new_pool_len++] = *e);
+ });
+ }
+ }
+ // Critical section: no exception be thrown here. Otherwise ref counting would be wrong.
+ { std::swap(other->_extra.list_length, new_list_len); }
+ }
+ void inplace_resize(int64_t new_size) {
+ int64_t &list_length = Get()->list_length;
+ if (new_size < list_length) {
+ this->RangeDecRef(new_size, list_length);
+ } else {
+ ListItem *b = this->ListBegin();
+ for (int64_t i = list_length; i < new_size; ++i) {
+ b[i] = nullptr;
+ }
+ }
+ list_length = new_size;
+ }
+ void clear() {
+ this->RangeDecRef(0, Get()->list_length);
+ Get()->list_length = Get()->pool_length = 0;
+ }
+ void pop_back() {
+ int64_t n = Get()->list_length;
+ this->ReplaceRangeInplace(n - 1, n, 0, nullptr);
+ }
+ int64_t size() const { return Get()->list_length; }
+ int64_t capacity() const { return Get()->list_capacity; }
+ bool empty() const { return Get()->list_length == 0; }
+ void erase(int64_t index) { this->ReplaceRangeInplace(index, index + 1, 0, nullptr); }
+ void SetItem(int64_t i, Any data) { this->ReplaceRangeInplace(i, i + 1, 1, &data); }
+ AnyView GetItem(int64_t index) const {
+ details::ListRangeCheck(index, index + 1, Get()->list_length);
+ const AnyView *data = static_cast<const AnyView *>(this->ListBegin()[index]);
+ if (data == nullptr) {
+ return AnyView();
+ }
+ TVM_FFI_LIST_EXEC_IF_POD(ListClass, data->type_index, { return *data; });
+ AnyView ret;
+ ret.type_index = data->type_index;
+ ret.v_obj = const_cast<AnyView *>(data);
+ return ret;
+ }
+ Iterator begin() { return Iterator(static_cast<ListClass *>(this), 0); }
+ Iterator end() { return Iterator(static_cast<ListClass *>(this), Get()->list_length); }
+ ReverseIterator rbegin() { return ReverseIterator(end()); }
+ ReverseIterator rend() { return ReverseIterator(begin()); }
+
+public:
+ // Section 3: Core methods
+ void RangeDecRef(int64_t begin, int64_t end) {
+ if constexpr (ListClass::all_items_are_pod) {
+ return;
+ }
+ ListItem *b = this->ListBegin();
+ for (int64_t i = begin; i < end; ++i) {
+ ListItem &data = b[i];
+ if (data) {
+ TVM_FFI_LIST_EXEC_IF_POD(ListClass, data->type_index, { continue; });
+ details::DecRef(data);
+ }
+ }
+ }
+ void ReplaceRangeInplace(int64_t range_begin, int64_t range_end, int64_t numel, Any *first) {
+ int64_t &list_len = Get()->list_length;
+ int64_t &pool_len = Get()->pool_length;
+ int64_t delta = numel - (range_end - range_begin);
+ details::ListRangeCheck(range_begin, range_end, list_len);
+ RangeDecRef(range_begin, range_end);
+ ListItem *b = this->ListBegin();
+ // Step 1. Move [range_end, list_len) to [range_end + delta, list_len + delta)
+ // so there are exaclty `num_elems` free slots in [range_begin, range_end + delta)
+ std::memmove(b + range_end + delta, b + range_end, (list_len - range_end) * sizeof(ListItem));
+ list_len += delta;
+ // Step 2. Copy `num_elems` items to [range_begin, range_end + delta)
+ PoolItem *p = this->PoolBegin();
+ for (int64_t i = 0; i < numel; ++i) {
+ Any v = std::move(first[i]);
+ ListItem &d = b[range_begin + i] = nullptr;
+ if (details::IsTypeIndexNone(v.type_index)) {
+ continue;
+ }
+ TVM_FFI_LIST_EXEC_IF_POD(ListClass, v.type_index, {
+ d = &(p[pool_len++] = v);
+ continue;
+ });
+ // N.B. Transfer the ownership of `v` to the list
+ std::swap(d, v.v_obj);
+ v.type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone);
+ }
+ }
+ Ref<ListClass> MaybeMoveToMoreCapacity(int64_t list_len_delta, int64_t pool_len_delta) {
+ ListClass *self = static_cast<ListClass *>(this);
+ int64_t list_cap = self->_extra.list_capacity, pool_cap = self->_extra.pool_capacity;
+ int64_t new_list_cap = GrowCapacity(list_cap, self->_extra.list_length + list_len_delta);
+ int64_t new_pool_cap = GrowCapacity(pool_cap, self->_extra.pool_length + pool_len_delta);
+ if (new_list_cap > list_cap || new_pool_cap > pool_cap) {
+ Ref<ListClass> ret = ListClass::Allocator::New(new_list_cap, new_pool_cap);
+ ret->inplace_move_from(self);
+ return ret;
+ }
+ return Ref<ListClass>(self);
+ }
+ static int64_t GrowCapacity(int64_t current, int64_t required) {
+ for (; current < required; current = std::max(current * 2, ListClass::kMinCapacity)) {
+ }
+ return current;
+ }
+};
+
+struct List : private details::ListHeader, private ListMethods<List> {
+ TVM_FFI_DEF_STATIC_TYPE(List, Object, TVMFFITypeIndex::kTVMFFIList);
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ constexpr static int64_t kMinCapacity = 4;
+ struct Allocator;
+
+ List(int64_t list_capacity, int64_t pool_capacity) {
+ this->_extra.list_capacity = list_capacity;
+ this->_extra.list_length = 0;
+ this->_extra.pool_capacity = pool_capacity;
+ this->_extra.pool_length = 0;
+ }
+ using TBase = ListMethods<List>;
+ using TBase::begin;
+ using TBase::capacity;
+ using TBase::clear;
+ using TBase::empty;
+ using TBase::end;
+ using TBase::erase;
+ using TBase::GetItem;
+ using TBase::inplace_init_many;
+ using TBase::inplace_insert;
+ using TBase::inplace_insert_many;
+ using TBase::inplace_push_back;
+ using TBase::inplace_resize;
+ using TBase::pop_back;
+ using TBase::rbegin;
+ using TBase::rend;
+ using TBase::SetItem;
+ using TBase::size;
+
+private:
+ constexpr static bool all_items_are_pod = false;
+ constexpr static bool all_items_are_obj = false;
+ ~List() { this->clear(); }
+
+ template <typename>
+ friend struct ListMethods;
+};
+
+struct List::Allocator {
+ static TVM_FFI_INLINE List *New() { return New(0, 0); }
+ static TVM_FFI_INLINE List *New(int64_t list_capacity, int64_t pool_capacity) {
+ static_assert(sizeof(PoolItem) % sizeof(ListItem) == 0);
+ constexpr int64_t ratio = sizeof(PoolItem) / sizeof(ListItem);
+ static_assert(ratio == 2);
+ return DefaultObjectAllocator<List>::NewWithPad<ListItem>(list_capacity + pool_capacity * ratio,
+ list_capacity, pool_capacity);
+ }
+};
+
+template <>
+struct Ref<List> : public RefBase<List> {
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ TVM_FFI_REF_DEF_DELEGATE_CONSTRUCTORS(Ref<List>, RefBase<List>)
+
+private:
+ struct AccessProxy {
+ AccessProxy &operator=(Any data) {
+ self->SetItem(index, data);
+ return *this;
+ }
+ template <typename Type>
+ operator Type() const {
+ return Type(self->get()->GetItem(index));
+ }
+ operator AnyView() const { return self->get()->GetItem(index); }
+
+ friend std::ostream &operator<<(std::ostream &os, const AccessProxy &self) {
+ return os << self.self->get()->GetItem(self.index);
+ }
+
+ private:
+ friend struct Ref<List>;
+ AccessProxy(Ref<List> *self, int64_t index) : self(self), index(index) {}
+ Ref<List> *self;
+ int64_t index;
+ };
+
+public:
+ Ref() : TBase(List::Allocator::New(0, 0)) {}
+ Ref(std::initializer_list<Any> data) : Ref(data.begin(), data.end()) {}
+ template <typename Iter>
+ Ref<List>(Iter first, Iter last) : Ref(std::distance(first, last)) {
+ std::vector<Any> elems = this->EnsureCapacityWithList(first, last);
+ int64_t numel = static_cast<int64_t>(elems.size());
+ this->get()->inplace_init_many(numel, elems.data());
+ }
+ template <typename Iter>
+ void insert(int64_t index, Iter first, Iter last) {
+ std::vector<Any> elems = this->EnsureCapacityWithList(first, last);
+ int64_t numel = static_cast<int64_t>(elems.size());
+ this->get()->inplace_insert_many(index, numel, elems.data());
+ }
+ void push_back(Any data) {
+ this->EnsureCapacityWithOneMoreElement(data)->inplace_push_back(data);
+ }
+ void insert(int64_t index, Any data) {
+ this->EnsureCapacityWithOneMoreElement(data)->inplace_insert(index, data);
+ }
+ void resize(int64_t new_size) { this->EnsureCapacityAtLeast(new_size)->inplace_resize(new_size); }
+ void reserve(int64_t capacity) { this->EnsureCapacityAtLeast(capacity); }
+ void clear() { this->get()->clear(); }
+ void erase(int64_t index) { this->get()->erase(index); }
+ void pop_back() { this->get()->pop_back(); }
+ int64_t size() const { return this->get()->size(); }
+ int64_t capacity() const { return this->get()->capacity(); }
+ bool empty() const { return this->get()->empty(); }
+ AnyView front() const { return this->get()->GetItem(0); }
+ AnyView back() const { return this->get()->GetItem(this->get()->size() - 1); }
+ auto operator[](int64_t index) { return AccessProxy(this, index); }
+ auto operator[](int64_t index) const { return this->get()->GetItem(index); }
+ auto begin() { return this->get()->begin(); }
+ auto end() { return this->get()->end(); }
+ auto rbegin() { return this->get()->rbegin(); }
+ auto rend() { return this->get()->rend(); }
+ void SetItem(int64_t i, Any data) {
+ TVM_FFI_LIST_EXEC_IF_POD(List, data.type_index,
+ { this->get()->MaybeMoveToMoreCapacity(0, 1).Swap(*this); });
+ this->get()->SetItem(i, data);
+ }
+
+protected:
+ friend struct List;
+ using RefBase<List>::operator*;
+ using RefBase<List>::operator->;
+ using TSelf = Ref<List>;
+ using TBase = RefBase<List>;
+ friend struct List;
+ template <typename>
+ friend struct ListMethods;
+
+ Ref<List>(int64_t size) : TBase(List::Allocator::New(size, size)) {}
+
+ List *EnsureCapacityWithOneMoreElement(const Any &data) {
+ int64_t pool_len_delta = 0;
+ TVM_FFI_LIST_EXEC_IF_POD(List, data.type_index, { pool_len_delta = 1; });
+ this->get()->MaybeMoveToMoreCapacity(1, pool_len_delta).Swap(*this);
+ return this->get();
+ }
+
+ List *EnsureCapacityAtLeast(int64_t capacity) {
+ List *self = this->get();
+ int64_t list_len_delta = std::max<int64_t>(capacity - self->_extra.list_capacity, 0);
+ int64_t pool_len_delta = 0;
+ if constexpr (!List::all_items_are_obj) {
+ pool_len_delta = std::max<int64_t>(capacity - self->_extra.pool_capacity, 0);
+ }
+ this->get()->MaybeMoveToMoreCapacity(list_len_delta, pool_len_delta).Swap(*this);
+ return this->get();
+ }
+
+ template <typename Iter>
+ std::vector<Any> EnsureCapacityWithList(Iter first, Iter last) {
+ std::vector<Any> ret(first, last);
+ int64_t list_len_delta = ret.size();
+ int64_t pool_len_delta = 0;
+ if constexpr (List::all_items_are_pod) {
+ pool_len_delta = ret.size();
+ } else if constexpr (!List::all_items_are_obj) {
+ for (const Any &v : ret) {
+ if (details::IsTypeIndexPOD(v.type_index)) {
+ ++pool_len_delta;
+ }
+ }
+ }
+ this->get()->MaybeMoveToMoreCapacity(list_len_delta, pool_len_delta).Swap(*this);
+ return ret;
+ }
+};
+
+namespace details {
+TVM_FFI_INLINE void ListRangeCheck(int64_t begin, int64_t end, int64_t length) {
+ if (begin > end) {
+ TVM_FFI_THROW(IndexError) << "Invalid range [" << begin << ", " << end
+ << ") when indexing a list";
+ }
+ if (begin < 0 || end > length) {
+ if (begin == end || begin + 1 == end) {
+ TVM_FFI_THROW(IndexError) << "Indexing `" << begin << "` of a list of size " << length;
+ } else {
+ TVM_FFI_THROW(IndexError) << "Indexing [" << begin << ", " << end << ") of a list of size "
+ << length;
+ }
+ }
+}
+} // namespace details
+} // namespace ffi
+} // namespace tvm
+
+#undef TVM_FFI_LIST_EXEC_IF_POD
+
+#endif // TVM_FFI_LIST_H_
diff --git a/include/tvm/ffi/ext/str.h b/include/tvm/ffi/ext/str.h
new file mode 100644
index 0000000000..f5aa29391e
--- /dev/null
+++ b/include/tvm/ffi/ext/str.h
@@ -0,0 +1,245 @@
+#ifndef TVM_FFI_STR_H_
+#define TVM_FFI_STR_H_
+#include <cstring>
+#include <sstream>
+#include <tvm/ffi/core/core.h>
+
+namespace tvm {
+namespace ffi {
+namespace details {
+struct StrHeader {
+ int64_t length;
+ const char *data;
+};
+} // namespace details
+
+struct Str : private AnyWithExtra<details::StrHeader> {
+ struct Allocator;
+ using Header = AnyWithExtra<details::StrHeader>;
+ TVM_FFI_DEF_STATIC_TYPE(Str, Object, TVMFFITypeIndex::kTVMFFIStr);
+
+ TVM_FFI_INLINE const char *c_str() const { return this->_extra.data; }
+ TVM_FFI_INLINE const char *data() const { return this->_extra.data; }
+ TVM_FFI_INLINE int64_t length() const { return this->_extra.length; }
+ TVM_FFI_INLINE int64_t size() const { return this->_extra.length; }
+ inline uint64_t Hash() const {
+ return details::TVMFFIStrHash(reinterpret_cast<const TVMFFIStr *>(this));
+ }
+
+protected:
+ using Header::_extra;
+ TVM_FFI_INLINE Str() : Header() {}
+};
+
+static_assert(sizeof(Str) == sizeof(TVMFFIStr));
+static_assert(offsetof(Str::Header, _extra.length) == offsetof(TVMFFIStr, length));
+static_assert(offsetof(Str::Header, _extra.data) == offsetof(TVMFFIStr, data));
+
+namespace details {
+
+struct StrStd : public Str {
+ using Allocator = ::tvm::ffi::DefaultObjectAllocator<StrStd>;
+ template <typename>
+ friend struct ::tvm::ffi::DefaultObjectAllocator;
+
+protected:
+ TVM_FFI_INLINE StrStd(std::string &&str) : Str(), container(std::move(str)) {
+ this->_extra.length = static_cast<int64_t>(container.length());
+ this->_extra.data = container.data();
+ }
+
+ std::string container;
+};
+
+struct StrPad : public Str {
+ using Allocator = ::tvm::ffi::DefaultObjectAllocator<StrPad>;
+ template <typename>
+ friend struct ::tvm::ffi::DefaultObjectAllocator;
+
+protected:
+ TVM_FFI_INLINE StrPad(const char *str, size_t N) : Str() {
+ char *str_copy = reinterpret_cast<char *>(this) + sizeof(Str);
+ std::memcpy(str_copy, str, N);
+ str_copy[N - 1] = '\0';
+
+ this->_extra.length = static_cast<int64_t>(N) - 1;
+ this->_extra.data = str_copy;
+ }
+};
+} // namespace details
+
+struct Str::Allocator {
+ TVM_FFI_INLINE static Str *New(std::string &&str) {
+ return details::StrStd::Allocator::New(std::move(str));
+ }
+ TVM_FFI_INLINE static Str *New(const std::string &str) {
+ int64_t N = static_cast<int64_t>(str.length()) + 1;
+ return details::StrPad::Allocator::NewWithPad<char>(N, str.data(), N);
+ }
+ TVM_FFI_INLINE static Str *New(const char *str) {
+ int64_t N = static_cast<int64_t>(std::strlen(str)) + 1;
+ return details::StrPad::Allocator::NewWithPad<char>(N, str, N);
+ }
+ template <size_t N>
+ TVM_FFI_INLINE static Str *New(const CharArray<N> &str) {
+ return details::StrPad::Allocator::NewWithPad<char>(N, str, N);
+ }
+ TVM_FFI_INLINE static Str *New(const char *str, size_t N) {
+ return details::StrPad::Allocator::NewWithPad<char>(N, str, N);
+ }
+};
+
+template <>
+struct Ref<Str> : public RefBase<Str> {
+ TVM_FFI_DEF_TYPE_FRIENDS();
+ TVM_FFI_INLINE Ref() : RefBase<Str>() {}
+ TVM_FFI_REF_DEF_DELEGATE_CONSTRUCTORS(Ref<Str>, RefBase<Str>)
+
+ using RefBase<Str>::New;
+ template <size_t N>
+ TVM_FFI_INLINE static TSub New(const CharArray<N> &arg) {
+ return TSub(Str::Allocator::template New<N>(arg));
+ }
+};
+
+TVM_FFI_INLINE Ref<Str> Object::str() const {
+ std::ostringstream os;
+ os << *this;
+ return Ref<Str>::New(os.str());
+}
+
+template <typename ObjectType>
+TVM_FFI_INLINE Ref<Str> RefBase<ObjectType>::str() const {
+ std::ostringstream os;
+ os << *this;
+ return Ref<Str>::New(os.str());
+}
+
+TVM_FFI_INLINE Ref<Str> AnyView::str() const {
+ std::ostringstream os;
+ os << *this;
+ return Ref<Str>::New(os.str());
+}
+
+TVM_FFI_INLINE Ref<Str> Any::str() const {
+ std::ostringstream os;
+ os << *this;
+ return Ref<Str>::New(os.str());
+}
+
+/********** TypeTraits *********/
+
+template <>
+struct TypeTraits<Str *> : public TypeTraitsDefaultForObject<Str> {
+private:
+ using TypeTraitsDefaultForObject<Str>::MoveFromTVMFFIAnyToRef;
+ using TypeTraitsDefaultForObject<Str>::CopyFromTVMFFIAnyToRef;
+
+public:
+ TVM_FFI_INLINE static Str *CopyFromTVMFFIAnyToRef(const TVMFFIAny *v) {
+ if (details::IsTypeIndexNone(v->type_index)) {
+ return nullptr;
+ }
+ if (v->type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr)) {
+ return Str::Allocator::New(v->v_str, std::strlen(v->v_str) + 1);
+ }
+ if (!details::IsTypeIndexPOD(v->type_index) && details::IsInstanceOf<Str>(v->type_index)) {
+ return reinterpret_cast<Str *>(v->v_obj);
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static Str *CopyFromTVMFFIAnyToTypeWithStorage(const TVMFFIAny *v, Any *storage) {
+ if (details::IsTypeIndexNone(v->type_index)) {
+ return nullptr;
+ }
+ if (v->type_index == static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIRawStr)) {
+ Str *ret = Str::Allocator::New(v->v_str, std::strlen(v->v_str) + 1);
+ *storage = Ref<Str>(ret);
+ return ret;
+ }
+ if (!details::IsTypeIndexPOD(v->type_index) && details::IsInstanceOf<Str>(v->type_index)) {
+ return reinterpret_cast<Str *>(v->v_obj);
+ }
+ throw TemporaryTypeError();
+ }
+
+ TVM_FFI_INLINE static Str *MoveFromTVMFFIAnyToRef(TVMFFIAny *v) {
+ Str *ret = CopyFromTVMFFIAnyToRef(v);
+ v->type_index = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFINone);
+ v->v_obj = nullptr;
+ return ret;
+ }
+};
+
+namespace details {
+
+Any FuncInvoke(const void *func, int32_t num_args, const TVMFFIAny *args);
+
+inline int32_t TVMFFIStrCmp(const TVMFFIStr *a, const TVMFFIStr *b) {
+ if (a->length != b->length) {
+ return a->length - b->length;
+ }
+ return std::strncmp(a->data, b->data, a->length);
+}
+
+inline uint64_t TVMFFIStrHash(const TVMFFIStr *str) {
+ const constexpr uint64_t kMultiplier = 1099511628211ULL;
+ const constexpr uint64_t kMod = 2147483647ULL;
+ const char *it = str->data;
+ const char *end = it + str->length;
+ uint64_t result = 0;
+ for (; it + 8 <= end; it += 8) {
+ uint64_t b = (static_cast<uint64_t>(it[0]) << 56) | (static_cast<uint64_t>(it[1]) << 48) |
+ (static_cast<uint64_t>(it[2]) << 40) | (static_cast<uint64_t>(it[3]) << 32) |
+ (static_cast<uint64_t>(it[4]) << 24) | (static_cast<uint64_t>(it[5]) << 16) |
+ (static_cast<uint64_t>(it[6]) << 8) | static_cast<uint64_t>(it[7]);
+ result = (result * kMultiplier + b) % kMod;
+ }
+ if (it < end) {
+ uint64_t b = 0;
+ if (it + 4 <= end) {
+ b = (static_cast<uint64_t>(it[0]) << 24) | (static_cast<uint64_t>(it[1]) << 16) |
+ (static_cast<uint64_t>(it[2]) << 8) | static_cast<uint64_t>(it[3]);
+ it += 4;
+ }
+ if (it + 2 <= end) {
+ b = (b << 16) | (static_cast<uint64_t>(it[0]) << 8) | static_cast<uint64_t>(it[1]);
+ it += 2;
+ }
+ if (it + 1 <= end) {
+ b = (b << 8) | static_cast<uint64_t>(it[0]);
+ it += 1;
+ }
+ result = (result * kMultiplier + b) % kMod;
+ }
+ return result;
+}
+
+TVM_FFI_INLINE void AnyView2Str(std::ostream &os, const TVMFFIAny *v) {
+#if TVM_FFI_ALLOW_DYN_TYPE
+ AnyView attr = TypeGetAttr(v->type_index, "__str__");
+ if (details::IsTypeIndexNone(attr.type_index)) {
+ os << TypeIndex2TypeKey(v->type_index) << '@' << v->v_ptr;
+ } else {
+ os << Ref<Str>(FuncInvoke(attr.v_obj, 1, v))->c_str();
+ }
+#else
+ return StaticAnyView2Str(os, v);
+#endif
+}
+
+TVM_FFI_INLINE TVMFFIObject *StrMoveFromStdString(std::string &&source) {
+ return reinterpret_cast<TVMFFIObject *>(Str::Allocator::New(std::move(source)));
+}
+
+TVM_FFI_INLINE TVMFFIObject *StrCopyFromCharArray(const char *source, size_t length) {
+ return reinterpret_cast<TVMFFIObject *>(Str::Allocator::New(source, length + 1));
+}
+
+} // namespace details
+
+} // namespace ffi
+} // namespace tvm
+
+#endif // TVM_FFI_STR_H_
diff --git a/include/tvm/ffi/ffi.hpp b/include/tvm/ffi/ffi.hpp
new file mode 100644
index 0000000000..68532f7457
--- /dev/null
+++ b/include/tvm/ffi/ffi.hpp
@@ -0,0 +1,9 @@
+#ifndef TVM_FFI_H_
+#define TVM_FFI_H_
+#include "./core/core.h"
+#include "./ext/dict.h"
+#include "./ext/error.h"
+#include "./ext/func.h"
+#include "./ext/list.h"
+#include "./ext/str.h"
+#endif // TVM_FFI_H_
diff --git a/src/ffi/registry.cc b/src/ffi/registry.cc
new file mode 100644
index 0000000000..05c0280796
--- /dev/null
+++ b/src/ffi/registry.cc
@@ -0,0 +1,144 @@
+#include <memory>
+#include <string>
+#include <tvm/ffi/ffi.hpp>
+#include <unordered_map>
+#include <vector>
+
+namespace {
+
+using tvm::ffi::Any;
+using tvm::ffi::AnyView;
+using tvm::ffi::Func;
+using tvm::ffi::Ref;
+using tvm::ffi::TypeIndexTraits;
+using tvm::ffi::TypeTraits;
+using tvm::ffi::details::InitTypeTable;
+
+struct TypeInfoImpl : public TVMFFITypeInfo {
+ std::string type_key_data;
+ std::vector<int32_t> type_ancestors_data;
+};
+
+struct TypeTable {
+ int32_t num_types;
+ std::vector<std::shared_ptr<TypeInfoImpl>> type_table;
+ std::unordered_map<std::string, std::shared_ptr<TypeInfoImpl>> type_key_to_info;
+ std::unordered_map<std::string, std::vector<Any>> type_attrs;
+
+ static TypeTable *New();
+ static TypeTable *Global() {
+ static std::unique_ptr<TypeTable> instance(TypeTable::New());
+ return instance.get();
+ }
+};
+
+int32_t TypeDef(TypeTable *self, int32_t type_index, const char *type_key, int32_t type_depth,
+ const int32_t *type_ancestors) {
+ if (self == nullptr) {
+ TVM_FFI_THROW(ValueError) << "TypeTable is provided as nullptr.";
+ }
+ if (self->type_key_to_info.count(type_key) != 0) {
+ TVM_FFI_THROW(ValueError) << "Type key is already registered.";
+ }
+ if (type_index == -1) {
+ type_index = self->num_types++;
+ }
+ std::shared_ptr<TypeInfoImpl> info = std::make_shared<TypeInfoImpl>();
+ {
+ info->type_key_data = std::string(type_key);
+ info->type_ancestors_data = std::vector<int32_t>(type_ancestors, type_ancestors + type_depth);
+ info->type_index = type_index;
+ info->type_key = info->type_key_data.c_str();
+ info->type_depth = type_depth;
+ info->type_ancestors = info->type_ancestors_data.data();
+ }
+ if (type_index >= static_cast<int32_t>(self->type_table.size())) {
+ self->type_table.resize((type_index / 1024 + 1) * 1024);
+ }
+ self->type_table.at(type_index) = info;
+ self->type_key_to_info[type_key] = info;
+ return type_index;
+}
+
+struct InitFunctor {
+ template <enum TVMFFITypeIndex type_index, typename ObjectType>
+ void RegisterType(TypeTable *self) {
+ TypeDef(self, static_cast<int32_t>(type_index), TypeIndexTraits<type_index>::type_key,
+ std::max<int32_t>(ObjectType::_type_depth, 0), ObjectType::_type_ancestors.data());
+ }
+
+ template <enum TVMFFITypeIndex type_index, typename ObjectType>
+ void RegisterStr(TypeTable *self) {
+ Any value = Any(Ref<Func>::New(TypeTraits<ObjectType>::__str__));
+ TVMFFIDynTypeSetAttr(self, static_cast<int32_t>(type_index), "__str__", &value);
+ }
+};
+
+TypeTable *TypeTable::New() {
+ TypeTable *self = new TypeTable();
+ self->type_table.resize(1024);
+ self->type_key_to_info.reserve(1024);
+ self->num_types = static_cast<int32_t>(TVMFFITypeIndex::kTVMFFIDynObjectBegin);
+ InitTypeTable(InitFunctor(), self);
+ return self;
+}
+
+} // namespace
+
+TVM_FFI_API void TVMFFIDynTypeDef(TVMFFITypeTableHandle _self, const char *type_key,
+ int32_t type_depth, const int32_t *type_ancestors,
+ int32_t *type_index) {
+ TypeTable *self = static_cast<TypeTable *>(_self);
+ if (self == nullptr) {
+ self = TypeTable::Global();
+ }
+ *type_index = TypeDef(self, -1, type_key, type_depth, type_ancestors);
+}
+
+TVM_FFI_API void TVMFFIDynTypeIndex2Info(TVMFFITypeTableHandle _self, int32_t type_index,
+ TVMFFITypeInfoHandle *ret) {
+ TypeTable *self = static_cast<TypeTable *>(_self);
+ if (self == nullptr) {
+ self = TypeTable::Global();
+ }
+ if (type_index < 0 || type_index >= static_cast<int32_t>(self->type_table.size())) {
+ *ret = nullptr;
+ } else {
+ *ret = self->type_table.at(type_index).get();
+ }
+}
+
+TVM_FFI_API void TVMFFIDynTypeSetAttr(TVMFFITypeTableHandle _self, int32_t type_index,
+ const char *key, TVMFFIAnyHandle value) {
+ TypeTable *self = static_cast<TypeTable *>(_self);
+ if (self == nullptr) {
+ self = TypeTable::Global();
+ }
+ std::vector<Any> &attrs = self->type_attrs[key];
+ if (type_index >= static_cast<int32_t>(attrs.size())) {
+ attrs.resize((type_index / 1024 + 1) * 1024);
+ }
+ attrs.at(type_index) = Any(*static_cast<AnyView *>(value));
+}
+
+TVM_FFI_API void TVMFFIDynTypeGetAttr(TVMFFITypeTableHandle _self, int32_t type_index,
+ const char *key, TVMFFIAnyHandle *ret) {
+ TypeTable *self = static_cast<TypeTable *>(_self);
+ if (self == nullptr) {
+ self = TypeTable::Global();
+ }
+ std::vector<Any> &attrs = self->type_attrs[key];
+ if (type_index >= static_cast<int32_t>(attrs.size())) {
+ *ret = nullptr;
+ } else {
+ *ret = &attrs.at(type_index);
+ }
+}
+
+TVM_FFI_API void TVMFFIDynTypeTypeTableCreate(TVMFFITypeTableHandle *ret) {
+ *ret = TypeTable::New();
+}
+
+TVM_FFI_API void TVMFFIDynTypeTypeTableDestroy(TVMFFITypeTableHandle handle) {
+ delete static_cast<TypeTable *>(handle);
+}