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);
+}