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

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


The following commit(s) were added to refs/heads/main by this push:
     new eef7dc9b11 [CMake] Organize optional modules and build both runtime 
libraries (#20728)
eef7dc9b11 is described below

commit eef7dc9b115412b4617d7cdbb759cc8c8beefaf8
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Oct 14 15:41:00 2026 -0400

    [CMake] Organize optional modules and build both runtime libraries (#20728)
    
    Keep core compiler/runtime source selection and target assembly in the
    root CMakeLists.txt while moving optional runtime-extra assembly and C++
    test setup into dedicated files. OpenCL collects wrapper sources
    independently of Hexagon.
    
    Build the shared runtime and `libtvm_runtime_static.a` together from
    common runtime objects, with their respective TVM-FFI dependencies.
    Remove the static-only option and update its callers and archive-linking
    guidance while preserving existing shared-library and wheel layouts.
    Remove the unused ABI-change debug option and its build definitions
    while retaining normal Debug and RelWithDebInfo behavior.
---
 CMakeLists.txt                   | 428 +++++++++++----------------------------
 apps/cpp_rpc/CMakeLists.txt      |  18 +-
 cmake/config.cmake               |  12 +-
 cmake/modules/OpenCL.cmake       |   3 +-
 cmake/modules/RuntimeExtra.cmake | 103 ++++++++++
 tests/cpp/CMakeLists.txt         | 109 ++++++++++
 6 files changed, 339 insertions(+), 334 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 27c636396b..329e04237d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -70,14 +70,12 @@ tvm_option(USE_RPC "Build with RPC" ON)
 tvm_option(USE_THREADS "Build with thread support" ON)
 tvm_option(USE_LLVM "Build with LLVM, can be set to specific llvm-config path" 
OFF)
 tvm_option(USE_OPENMP "Build with OpenMP thread pool implementation" OFF)
-tvm_option(TVM_DEBUG_WITH_ABI_CHANGE "Enable debug code that may cause ABI 
changes" OFF)
 tvm_option(TVM_LOG_BEFORE_THROW "Whether log before throw, for debugging 
purposes" OFF)
 tvm_option(USE_RTTI "Build with RTTI" ON)
 tvm_option(USE_MSVC_MT "Build with MT" OFF)
 tvm_option(INSTALL_DEV "Install compiler infrastructure" OFF)
 tvm_option(HIDE_PRIVATE_SYMBOLS "Compile with -fvisibility=hidden." ON)
 tvm_option(INDEX_DEFAULT_I64 "Defaults the index datatype to int64" ON)
-tvm_option(BUILD_STATIC_RUNTIME "Build static version of libtvm_runtime" OFF)
 tvm_option(USE_GTEST "Use GoogleTest for C++ sanity tests" AUTO)
 tvm_option(USE_CUSTOM_LOGGING "Use user-defined custom logging, 
tvm::runtime::detail::LogFatalImpl and tvm::runtime::detail::LogMessageImpl 
must be implemented" OFF)
 tvm_option(USE_ALTERNATIVE_LINKER "Use 'mold' or 'lld' if found when invoking 
compiler to link artifact" AUTO)
@@ -292,6 +290,7 @@ assign_source_group("Source" ${GROUP_SOURCE})
 assign_source_group("Include" ${GROUP_INCLUDE})
 
 # Source file lists
+# Core compiler sources, extended by the backend modules.
 tvm_file_glob(GLOB_RECURSE COMPILER_SRCS
     src/ir/*.cc
     src/sym/*.cc
@@ -364,7 +363,7 @@ tvm_file_glob(GLOB CODEGEN_SRCS
     )
 
 list(APPEND COMPILER_SRCS ${CODEGEN_SRCS})
-
+# Core runtime sources, extended by the backend modules.
 tvm_file_glob(GLOB RUNTIME_SRCS
   src/runtime/*.cc
   src/runtime/rpc/minrpc/*.cc
@@ -376,11 +375,15 @@ list(APPEND RUNTIME_SRCS ${RUNTIME_VM_SRCS})
 # Note: src/backend/*/runtime sources move to per-backend DSOs.
 set(TVM_RUNTIME_EXT_OBJS "")
 
+if(USE_RPC)
+  message(STATUS "Build with RPC support...")
+  tvm_file_glob(GLOB RUNTIME_RPC_SRCS src/runtime/rpc/*.cc)
+  list(APPEND RUNTIME_SRCS ${RUNTIME_RPC_SRCS})
+endif(USE_RPC)
+
 if(BUILD_FOR_HEXAGON)
-  if(NOT BUILD_STATIC_RUNTIME)
-    # Allow undefined symbols (there will be some from libc).
-    set(TVM_NO_UNDEFINED_SYMBOLS "")
-  endif()
+  # Allow undefined symbols in the shared runtime (there will be some from 
libc).
+  set(TVM_NO_UNDEFINED_SYMBOLS "")
 
   add_definitions(-D_MACH_I32=int)
 endif()
@@ -393,52 +396,6 @@ if(INDEX_DEFAULT_I64)
   add_definitions(-DTVM_INDEX_DEFAULT_I64=1)
 endif()
 
-if(USE_RPC)
-  message(STATUS "Build with RPC support...")
-  tvm_file_glob(GLOB RUNTIME_RPC_SRCS src/runtime/rpc/*.cc)
-  list(APPEND RUNTIME_SRCS ${RUNTIME_RPC_SRCS})
-endif(USE_RPC)
-# Note: disco/**, NCCL, NVSHMEM, RCCL all move to libtvm_runtime_extra
-# (assembled inline below after all contrib cmake files).
-
-# Enable ctest if gtest is available
-if(USE_GTEST)
-  # Check env var for backward compatibility. A better way to specify package
-  # locations is to use CMAKE_PREFIX_PATH or other standard CMake mechanism
-  # (see CMake documentation for `find_package`).
-  set(GTEST_ROOT "$ENV{GTEST_LIB}")
-  if("${USE_GTEST}" STREQUAL "AUTO")
-    # If USE_GTEST is AUTO, treat GTest as optional: enable if found.
-    find_package(GTest)
-  elseif("${USE_GTEST}" MATCHES ${IS_TRUE_PATTERN})
-    # USE_GTEST is set to ON, TRUE, etc. Treat GTest as a required package.
-    find_package(GTest REQUIRED)
-  endif()
-  if(GTEST_FOUND)
-    if(NOT TARGET GTest::gmock)
-      # GMock is formally supported in CMake 3.20; for now, expect libgmock.a 
in the same directory,
-      # and require that folks compiling against GTest::gmock also link 
against GTest::GTest
-      # (for the includes dir).
-      add_library(GTest::gmock STATIC IMPORTED GLOBAL)
-      get_target_property(GTEST_LIB_PATH GTest::GTest IMPORTED_LOCATION)
-      if("${GTEST_LIB_PATH}" STREQUAL "GTEST_LIB_PATH-NOTFOUND")
-        # CMake >= 3.20 makes GTest::GTest into a compatibility target. The 
real import location is in
-        # GTest::gtest.
-        get_target_property(GTEST_LIB_PATH GTest::gtest IMPORTED_LOCATION)
-        if("${GTEST_LIB_PATH}" STREQUAL "GTEST_LIB_PATH-NOTFOUND")
-          message(FATAL_ERROR "Neither GTest::GTest nor GTest::gtest targets 
defined IMPORTED_LOCATION")
-        endif()
-      endif()
-      get_filename_component(GTEST_LIB_DIR "${GTEST_LIB_PATH}" DIRECTORY)
-      set_target_properties(GTest::gmock PROPERTIES
-          IMPORTED_LOCATION "${GTEST_LIB_DIR}/libgmock.a")
-    endif()
-
-    enable_testing()
-    include(CTest)
-  endif()
-endif()
-
 if(USE_KALLOC_ALIGNMENT)
   message(STATUS "Build Alloc alignment set to ${USE_KALLOC_ALIGNMENT}")
   add_definitions(-DTVM_KALLOC_ALIGNMENT=${USE_KALLOC_ALIGNMENT})
@@ -479,139 +436,107 @@ include(cmake/modules/contrib/ExampleNPU.cmake)
 include(cmake/modules/contrib/vllm.cmake)
 include(cmake/modules/Git.cmake)
 
-# ---- libtvm_runtime_extra assembly ----
-# Disco core sources.
-tvm_file_glob(GLOB _disco_core_srcs src/runtime/extra/disco/*.cc)
-add_library(tvm_disco_objs OBJECT ${_disco_core_srcs})
-target_link_libraries(tvm_disco_objs PRIVATE tvm_runtime_extra_defs)
-target_link_libraries(tvm_runtime_extra PRIVATE tvm_disco_objs)
+include(cmake/modules/RuntimeExtra.cmake)
+add_subdirectory(3rdparty/tvm-ffi)
+include(GNUInstallDirs)
 
-# Distributed disco (disabled for Hexagon cross-compile).
-if(NOT BUILD_FOR_HEXAGON)
-  tvm_file_glob(GLOB _disco_dist_srcs src/runtime/extra/disco/distributed/*.cc)
-  add_library(tvm_disco_distributed_objs OBJECT ${_disco_dist_srcs})
-  target_link_libraries(tvm_disco_distributed_objs PRIVATE 
tvm_runtime_extra_defs)
-  target_link_libraries(tvm_runtime_extra PRIVATE tvm_disco_distributed_objs)
-endif()
-
-# NCCL / cuda_ipc — requires CUDA + NCCL.
-if(USE_CUDA AND USE_NCCL)
-  find_nccl(${USE_NCCL})
-  include_directories(SYSTEM ${NCCL_INCLUDE_DIR})
-  tvm_file_glob(GLOB _nccl_srcs src/runtime/extra/disco/nccl/*.cc 
src/runtime/extra/disco/cuda_ipc/*.cc 3rdparty/tensorrt_llm/*.cu)
-  set_source_files_properties(src/runtime/extra/disco/nccl/nccl.cc PROPERTIES 
COMPILE_DEFINITIONS "TVM_NCCL_RCCL_SWITCH=0")
-  add_library(tvm_nccl_objs OBJECT ${_nccl_srcs})
-  target_link_libraries(tvm_nccl_objs PRIVATE tvm_runtime_extra_defs)
-  find_library(LIBRT rt)
-  target_link_libraries(tvm_runtime_extra PRIVATE tvm_nccl_objs nccl ${LIBRT})
-endif()
-
-# NVSHMEM.
-if(USE_CUDA AND USE_NVSHMEM)
-  find_nvshmem(${USE_NVSHMEM})
-  if(NOT NVSHMEM_FOUND)
-    message(FATAL_ERROR "Cannot find NVSHMEM, USE_NVSHMEM=" ${USE_NVSHMEM})
-  endif()
-  set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)
-  set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-  tvm_file_glob(GLOB _nvshmem_srcs src/runtime/extra/contrib/nvshmem/*.cc 
src/runtime/extra/contrib/nvshmem/*.cu)
-  add_library(tvm_nvshmem_objs OBJECT ${_nvshmem_srcs})
-  target_link_libraries(tvm_nvshmem_objs PRIVATE tvm_runtime_extra_defs)
-  target_include_directories(tvm_nvshmem_objs PUBLIC ${NVSHMEM_INCLUDE_DIR})
-  # CUDA 13 moved libcu++ under include/cccl. NVSHMEM's headers include
-  # <cuda/std/...>, which nvcc resolves on its own -- the host compiler
-  # building this target's .cc sources does not, so name the directory.
-  if(EXISTS "${CUDA_TOOLKIT_ROOT_DIR}/include/cccl")
-    target_include_directories(tvm_nvshmem_objs SYSTEM PUBLIC
-                               "${CUDA_TOOLKIT_ROOT_DIR}/include/cccl")
-  endif()
-  find_library(NVSHMEM_HOST nvshmem_host ${NVSHMEM_LIB_DIR})
-  find_library(NVSHMEM_DEVICE nvshmem_device ${NVSHMEM_LIB_DIR})
-  target_link_libraries(tvm_runtime_extra PRIVATE tvm_nvshmem_objs 
${NVSHMEM_HOST} ${NVSHMEM_DEVICE})
-  set_target_properties(tvm_runtime_extra PROPERTIES 
CUDA_SEPARABLE_COMPILATION ON)
+if(USE_THREADS AND NOT BUILD_FOR_HEXAGON)
+  message(STATUS "Build with thread support...")
+  set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
+  set(THREADS_PREFER_PTHREAD_FLAG TRUE)
+  find_package(Threads REQUIRED)
 endif()
 
-# RCCL.
-if(USE_ROCM AND USE_RCCL)
-  find_rccl(${USE_RCCL})
-  include_directories(SYSTEM ${RCCL_INCLUDE_DIR})
-  tvm_file_glob(GLOB _rccl_srcs src/runtime/extra/disco/nccl/*.cc)
-  set_source_files_properties(src/runtime/extra/disco/nccl/nccl.cc PROPERTIES 
COMPILE_DEFINITIONS "TVM_NCCL_RCCL_SWITCH=1")
-  add_library(tvm_rccl_objs OBJECT ${_rccl_srcs})
-  target_link_libraries(tvm_rccl_objs PRIVATE tvm_runtime_extra_defs)
-  target_link_libraries(tvm_runtime_extra PRIVATE tvm_rccl_objs rccl)
+set(TVM_IS_DEBUG_BUILD OFF)
+if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL 
"RelWithDebInfo" OR CMAKE_CXX_FLAGS MATCHES "-g")
+  set(TVM_IS_DEBUG_BUILD ON)
+  set(FILE_PREFIX_MAP_FLAG "-ffile-prefix-map=..=${CMAKE_CURRENT_SOURCE_DIR}")
+  CHECK_CXX_COMPILER_FLAG("${FILE_PREFIX_MAP_FLAG}" FILE_PREFIX_MAP_SUPPORTED)
 endif()
 
-target_link_libraries(tvm_runtime_extra PUBLIC tvm_runtime)
-
-# If disco/cuda_ipc is included, link the CUDA DSO.
-if(USE_CUDA)
-  target_link_libraries(tvm_runtime_extra PUBLIC tvm_runtime_cuda)
-  target_link_libraries(tvm_runtime_extra PRIVATE ${CUDA_CUDART_LIBRARY} 
${CUDA_CUDA_LIBRARY})
+# Core runtime objects are shared by the dynamic and static libraries.
+add_library(tvm_runtime_objs OBJECT ${RUNTIME_SRCS})
+target_link_libraries(tvm_runtime_objs PUBLIC tvm_ffi_header)
+if(TVM_VISIBILITY_FLAG)
+  target_compile_options(tvm_runtime_objs PRIVATE "${TVM_VISIBILITY_FLAG}")
 endif()
-
-if(USE_ROCM)
-  target_link_libraries(tvm_runtime_extra PRIVATE ${ROCM_HIPHCC_LIBRARY})
+if(MSVC)
+  # Runtime objects export only runtime/FFI symbols, never compiler symbols.
+  target_compile_definitions(tvm_runtime_objs PRIVATE TVM_RUNTIME_EXPORTS 
TVM_FFI_EXPORTS)
 endif()
 
-# CUTLASS fpA_intB_gemm and flash_attn are separate shared libs.
-if(USE_CUDA AND USE_CUTLASS)
-  target_link_libraries(tvm_runtime_extra PRIVATE fpA_intB_gemm 
fpA_intB_gemm_tvm)
-  target_link_libraries(tvm_runtime_extra PRIVATE -Wl,--no-as-needed 
flash_attn)
+# Loaded with RTLD_GLOBAL so downstream runtime modules can resolve its 
symbols.
+add_library(tvm_runtime SHARED
+  $<TARGET_OBJECTS:tvm_runtime_objs>
+  ${TVM_RUNTIME_EXT_OBJS}
+)
+target_link_libraries(tvm_runtime PUBLIC tvm_ffi_shared)
+set_property(TARGET tvm_runtime APPEND PROPERTY LINK_OPTIONS 
"${TVM_NO_UNDEFINED_SYMBOLS}")
+set_property(TARGET tvm_runtime APPEND PROPERTY LINK_OPTIONS 
"${TVM_VISIBILITY_FLAG}")
+
+# Static consumers must retain registration objects from both archives, with
+# tvm_ffi_static before tvm_runtime_static so FFI type attributes initialize 
first:
+#   -Wl,--whole-archive tvm_ffi_static tvm_runtime_static 
-Wl,--no-whole-archive
+# Use the corresponding archive-retention flags on other platforms.
+add_library(tvm_runtime_static STATIC
+  $<TARGET_OBJECTS:tvm_runtime_objs>
+  ${TVM_RUNTIME_EXT_OBJS}
+)
+target_link_libraries(tvm_runtime_static PUBLIC tvm_ffi_static)
+if(MSVC)
+  # Static consumers reference ordinary symbols rather than DLL import stubs.
+  target_compile_definitions(tvm_runtime_static INTERFACE
+    TVM_RUNTIME_DLL= TVM_RUNTIME_DLL_EXPORT= TVM_FFI_DLL= TVM_FFI_DLL_EXPORT=)
 endif()
 
-if(TVM_VISIBILITY_FLAG)
-  set_property(TARGET tvm_runtime_extra APPEND PROPERTY LINK_OPTIONS 
"${TVM_VISIBILITY_FLAG}")
+foreach(runtime_target tvm_runtime tvm_runtime_static)
+  target_include_directories(${runtime_target} PUBLIC 
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
+  target_link_libraries(${runtime_target} PRIVATE ${TVM_RUNTIME_LINKER_LIBS})
+  if(USE_THREADS AND NOT BUILD_FOR_HEXAGON)
+    target_link_libraries(${runtime_target} PUBLIC Threads::Threads)
+  endif()
+endforeach()
+
+set_target_properties(tvm_runtime tvm_runtime_static PROPERTIES
+  ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+  LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+)
+if(MSVC)
+  foreach(config_type Release RELEASE Debug DEBUG RelWithDebInfo 
RELWITHDEBINFO MinSizeRel MINSIZEREL)
+    set_target_properties(tvm_runtime tvm_runtime_static PROPERTIES
+      ARCHIVE_OUTPUT_DIRECTORY_${config_type} "${CMAKE_BINARY_DIR}/lib"
+      LIBRARY_OUTPUT_DIRECTORY_${config_type} "${CMAKE_BINARY_DIR}/lib"
+      RUNTIME_OUTPUT_DIRECTORY_${config_type} "${CMAKE_BINARY_DIR}/lib"
+    )
+  endforeach()
 endif()
 
-tvm_configure_target_library(tvm_runtime_extra RUNTIME_MODULE)
+if(TVM_IS_DEBUG_BUILD AND FILE_PREFIX_MAP_SUPPORTED)
+  target_compile_options(tvm_runtime PRIVATE 
$<$<COMPILE_LANGUAGE:CXX>:${FILE_PREFIX_MAP_FLAG}>)
+  target_compile_options(tvm_runtime_objs PRIVATE 
$<$<COMPILE_LANGUAGE:CXX>:${FILE_PREFIX_MAP_FLAG}>)
+endif()
 
+tvm_ffi_add_apple_dsymutil(tvm_runtime)
+tvm_configure_target_library(tvm_runtime)
+add_custom_target(runtime DEPENDS tvm_runtime tvm_runtime_static)
+install(TARGETS tvm_runtime DESTINATION lib${LIB_SUFFIX})
+# Keep wheel contents unchanged; the archive is available to native consumers.
+if(NOT TVM_BUILD_PYTHON_MODULE)
+  install(TARGETS tvm_runtime_static DESTINATION lib${LIB_SUFFIX})
+endif()
 add_library(tvm_objs OBJECT ${COMPILER_SRCS})
-add_library(tvm_runtime_objs OBJECT ${RUNTIME_SRCS})
 target_link_libraries(tvm_objs PUBLIC tvm_ffi_header)
-target_link_libraries(tvm_runtime_objs PUBLIC tvm_ffi_header)
 if(TARGET tvm_llvm_header)
   target_link_libraries(tvm_objs PUBLIC tvm_llvm_header)
 endif()
-
-include(GNUInstallDirs)
-
-# Runtime library: built from runtime sources only. Loaded with RTLD_GLOBAL on
-# Linux/macOS so its symbols are exposed to downstream loads (e.g. NVRTC 
kernels
-# and other dynamically loaded modules that resolve runtime symbols at link
-# time).
-if(BUILD_STATIC_RUNTIME)
-  add_library(tvm_runtime STATIC
-    $<TARGET_OBJECTS:tvm_runtime_objs>
-    ${TVM_RUNTIME_EXT_OBJS}
-  )
-  set(NOTICE_MULTILINE
-    "You have build static version of the TVM runtime library. Make "
-    "sure to use --whole-archive when linking it into your project.")
-  string(CONCAT NOTICE ${NOTICE_MULTILINE})
-  add_custom_command(TARGET tvm_runtime POST_BUILD
-    COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --yellow --bold ${NOTICE})
-  target_link_libraries(tvm_runtime PUBLIC tvm_ffi_static)
-else()
-  add_library(tvm_runtime SHARED
-    $<TARGET_OBJECTS:tvm_runtime_objs>
-    ${TVM_RUNTIME_EXT_OBJS}
-  )
-  set_property(TARGET tvm_runtime APPEND PROPERTY LINK_OPTIONS 
"${TVM_NO_UNDEFINED_SYMBOLS}")
-  target_link_libraries(tvm_runtime PUBLIC tvm_ffi_shared)
-endif()
-
-target_include_directories(tvm_runtime PUBLIC 
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
-set_property(TARGET tvm_runtime APPEND PROPERTY LINK_OPTIONS 
"${TVM_VISIBILITY_FLAG}")
-# ``-fvisibility=hidden`` only affects symbol export when applied at COMPILE
-# time. Setting it as a link option (above) is preserved for back-compat but
-# is effectively a no-op for visibility — the per-TU compile flag is what
-# actually hides symbols. Apply it to the OBJECT libs that feed both shared
-# libraries.
 if(TVM_VISIBILITY_FLAG)
-  target_compile_options(tvm_runtime_objs PRIVATE "${TVM_VISIBILITY_FLAG}")
   target_compile_options(tvm_objs PRIVATE "${TVM_VISIBILITY_FLAG}")
 endif()
+if(MSVC)
+  # Compiler objects import runtime symbols from the separate runtime DLL.
+  target_compile_definitions(tvm_objs PRIVATE TVM_EXPORTS TVM_FFI_EXPORTS)
+endif()
 
 # Compiler library: built from compiler-only sources.
 # Links against tvm_runtime (and tvm_ffi_shared transitively). Loaded with
@@ -643,14 +568,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT 
"${USE_LLVM}" MATCHES ${IS_FALSE_P
   set_property(TARGET tvm_compiler APPEND PROPERTY LINK_OPTIONS 
"-Wl,--as-needed")
 endif()
 
-# Place runtime/compiler/allvisible artifacts under build/lib/ to mirror the
-# tvm-ffi layout and make tvm_ffi.libinfo.load_lib_ctypes(package="tvm") able
-# to discover them in dev / editable builds.
-set_target_properties(tvm_runtime PROPERTIES
-  ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
-  LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
-  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
-)
 set_target_properties(tvm_compiler PROPERTIES
   ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
   LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
@@ -658,11 +575,6 @@ set_target_properties(tvm_compiler PROPERTIES
 )
 if(MSVC)
   foreach(config_type Release RELEASE Debug DEBUG RelWithDebInfo 
RELWITHDEBINFO MinSizeRel MINSIZEREL)
-    set_target_properties(tvm_runtime PROPERTIES
-      ARCHIVE_OUTPUT_DIRECTORY_${config_type} "${CMAKE_BINARY_DIR}/lib"
-      LIBRARY_OUTPUT_DIRECTORY_${config_type} "${CMAKE_BINARY_DIR}/lib"
-      RUNTIME_OUTPUT_DIRECTORY_${config_type} "${CMAKE_BINARY_DIR}/lib"
-    )
     set_target_properties(tvm_compiler PROPERTIES
       ARCHIVE_OUTPUT_DIRECTORY_${config_type} "${CMAKE_BINARY_DIR}/lib"
       LIBRARY_OUTPUT_DIRECTORY_${config_type} "${CMAKE_BINARY_DIR}/lib"
@@ -671,6 +583,22 @@ if(MSVC)
   endforeach()
 endif()
 
+if(USE_THREADS AND NOT BUILD_FOR_HEXAGON)
+  target_link_libraries(tvm_compiler PUBLIC Threads::Threads)
+endif()
+target_link_libraries(tvm_compiler PRIVATE ${TVM_LINKER_LIBS} 
${TVM_RUNTIME_LINKER_LIBS})
+
+if(TVM_IS_DEBUG_BUILD)
+  target_compile_options(tvm_compiler PRIVATE "${FILE_PREFIX_MAP_FLAG}")
+  if(FILE_PREFIX_MAP_SUPPORTED)
+    target_compile_options(tvm_compiler PRIVATE 
$<$<COMPILE_LANGUAGE:CXX>:${FILE_PREFIX_MAP_FLAG}>)
+    target_compile_options(tvm_objs PRIVATE 
$<$<COMPILE_LANGUAGE:CXX>:${FILE_PREFIX_MAP_FLAG}>)
+  endif()
+endif()
+
+tvm_ffi_add_apple_dsymutil(tvm_compiler)
+tvm_configure_target_library(tvm_compiler)
+install(TARGETS tvm_compiler DESTINATION lib${LIB_SUFFIX})
 
 # logging option for libbacktrace
 include(cmake/modules/Logging.cmake)
@@ -687,95 +615,9 @@ if(USE_IOS_RPC)
   add_subdirectory("apps/ios_rpc")
 endif()
 
-add_subdirectory(3rdparty/tvm-ffi)
-
-if(TVM_DEBUG_WITH_ABI_CHANGE)
-  message(STATUS "Building with debug code that may cause ABI changes...")
-  target_compile_definitions(tvm_objs PRIVATE "TVM_DEBUG_WITH_ABI_CHANGE")
-  target_compile_definitions(tvm_runtime_objs PRIVATE 
"TVM_DEBUG_WITH_ABI_CHANGE")
-endif(TVM_DEBUG_WITH_ABI_CHANGE)
-
-if(USE_THREADS AND NOT BUILD_FOR_HEXAGON)
-  message(STATUS "Build with thread support...")
-  set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
-  set(THREADS_PREFER_PTHREAD_FLAG TRUE)
-  find_package(Threads REQUIRED)
-  target_link_libraries(tvm_compiler PUBLIC Threads::Threads)
-  target_link_libraries(tvm_runtime PUBLIC Threads::Threads)
-endif()
-
-target_link_libraries(tvm_compiler PRIVATE ${TVM_LINKER_LIBS})
-
-target_link_libraries(tvm_compiler PRIVATE ${TVM_RUNTIME_LINKER_LIBS})
-target_link_libraries(tvm_runtime PRIVATE ${TVM_RUNTIME_LINKER_LIBS})
-
 # Set flags for clang
 include(cmake/modules/ClangFlags.cmake)
-set(TVM_TEST_LIBRARY_NAME tvm_compiler)
-
-# Create the `cpptest` target if we can find GTest.  If not, we create dummy
-# targets that give the user an informative error message.
-if(GTEST_FOUND)
-  tvm_file_glob(GLOB_RECURSE TEST_SRCS tests/cpp/*.cc)
-  add_executable(cpptest ${TEST_SRCS})
-  # include runtime files for unit testing
-  target_link_libraries(cpptest PRIVATE ${TVM_TEST_LIBRARY_NAME} GTest::GTest 
GTest::Main GTest::gmock pthread dl)
-  if(DEFINED LLVM_LIBS)
-    # The TVM library is linked with LLVM libraries. If the LLVM libraries are
-    # static and the symbols are not hidden, then don't link them again into
-    # cpptest since cpptest is itself linked against the TVM library. If static
-    # LLVM libraries are linked in twice, it can cause issues with global
-    # variable initialization (cl::opt).
-    # If the LLVM libraries are dynamic, we have to link them again, since the
-    # TVM library will not contain any LLVM definitions.
-    unset(LLVM_SO)
-    foreach(L IN LISTS LLVM_LIBS)
-      if(L MATCHES "libLLVM.*\.so")
-        set(LLVM_SO TRUE)
-        break()
-      endif()
-    endforeach()
-    if(DEFINED LLVM_SO OR HIDE_PRIVATE_SYMBOLS)
-      target_link_libraries(cpptest PRIVATE ${LLVM_LIBS})
-    endif()
-  endif()
-  set_target_properties(cpptest PROPERTIES EXCLUDE_FROM_ALL 1)
-  set_target_properties(cpptest PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1)
-  # cpptest is built into ${CMAKE_BINARY_DIR} but the TVM shared libs it
-  # links against (libtvm_compiler.so / libtvm_runtime.so) now live in
-  # ${CMAKE_BINARY_DIR}/lib. CMake's default behaviour bakes the
-  # absolute path of build/lib/ into the binary's RUNPATH, which works in tree
-  # but is not relocatable. Set $ORIGIN/lib (or @loader_path/lib on macOS)
-  # explicitly so the test binary finds its TVM libs relative to its own
-  # location, mirroring the install-time RPATH setup.
-  if(APPLE)
-    set_target_properties(cpptest PROPERTIES
-      BUILD_RPATH "@loader_path/lib"
-      INSTALL_RPATH "@loader_path/lib"
-    )
-  elseif(UNIX)
-    set_target_properties(cpptest PROPERTIES
-      BUILD_RPATH "\$ORIGIN/lib"
-      INSTALL_RPATH "\$ORIGIN/lib"
-    )
-  endif()
-  target_compile_definitions(cpptest PRIVATE "NDEBUG")
-  if(TVM_DEBUG_WITH_ABI_CHANGE)
-    target_compile_definitions(cpptest PRIVATE "TVM_DEBUG_WITH_ABI_CHANGE")
-  endif(TVM_DEBUG_WITH_ABI_CHANGE)
-
-  # For some reason, compile definitions are not propagated correctly, so we 
manually add them here
-  target_compile_definitions(cpptest PUBLIC 
$<TARGET_PROPERTY:tvm_compiler,INTERFACE_COMPILE_DEFINITIONS>)
-  gtest_discover_tests(cpptest)
-endif()
-
-# Custom targets
-add_custom_target(runtime DEPENDS tvm_runtime)
-
 # Installation rules
-install(TARGETS tvm_compiler DESTINATION lib${LIB_SUFFIX})
-install(TARGETS tvm_runtime DESTINATION lib${LIB_SUFFIX})
-
 if (INSTALL_DEV)
   install(
     DIRECTORY "include/" DESTINATION "include"
@@ -812,42 +654,6 @@ install(
   "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
   DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
 
-# More target definitions
-if(MSVC)
-  # tvm_objs builds libtvm_compiler.dll → TVM_DLL=dllexport,
-  # but TVM_RUNTIME_DLL=dllimport (runtime symbols come from 
libtvm_runtime.dll).
-  target_compile_definitions(tvm_objs PRIVATE -DTVM_EXPORTS -DTVM_FFI_EXPORTS)
-  # tvm_runtime_objs builds libtvm_runtime.dll → TVM_RUNTIME_DLL=dllexport.
-  # No TVM_EXPORTS here: the runtime side never defines compiler-exported
-  # symbols, so leaving TVM_DLL as dllimport correctly fails any accidental
-  # use of compiler-side ``TVM_DLL`` symbols from a runtime TU.
-  target_compile_definitions(tvm_runtime_objs PRIVATE -DTVM_RUNTIME_EXPORTS 
-DTVM_FFI_EXPORTS)
-endif()
-
-set(TVM_IS_DEBUG_BUILD OFF)
-if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL 
"RelWithDebInfo" OR CMAKE_CXX_FLAGS MATCHES "-g")
-  set(TVM_IS_DEBUG_BUILD ON)
-endif()
-
-# Change relative paths in backtrace to absolute ones
-if(TVM_IS_DEBUG_BUILD)
-  set(FILE_PREFIX_MAP_FLAG "-ffile-prefix-map=..=${CMAKE_CURRENT_SOURCE_DIR}")
-  target_compile_options(tvm_compiler PRIVATE "${FILE_PREFIX_MAP_FLAG}")
-  CHECK_CXX_COMPILER_FLAG("${FILE_PREFIX_MAP_FLAG}" FILE_PREFIX_MAP_SUPPORTED)
-  if(FILE_PREFIX_MAP_SUPPORTED)
-    target_compile_options(tvm_compiler PRIVATE 
$<$<COMPILE_LANGUAGE:CXX>:${FILE_PREFIX_MAP_FLAG}>)
-    target_compile_options(tvm_objs PRIVATE 
$<$<COMPILE_LANGUAGE:CXX>:${FILE_PREFIX_MAP_FLAG}>)
-    target_compile_options(tvm_runtime PRIVATE 
$<$<COMPILE_LANGUAGE:CXX>:${FILE_PREFIX_MAP_FLAG}>)
-    target_compile_options(tvm_runtime_objs PRIVATE 
$<$<COMPILE_LANGUAGE:CXX>:${FILE_PREFIX_MAP_FLAG}>)
-  endif()
-endif()
-
-tvm_ffi_add_apple_dsymutil(tvm_compiler)
-# Only run dsymutil on shared libraries, not static libraries
-if(NOT BUILD_STATIC_RUNTIME)
-  tvm_ffi_add_apple_dsymutil(tvm_runtime)
-endif()
-
 if(BUILD_FOR_HEXAGON)
   # Wrap pthread_create to allow setting custom stack size.
   set_property(TARGET tvm_runtime APPEND PROPERTY LINK_FLAGS
@@ -856,7 +662,8 @@ if(BUILD_FOR_HEXAGON)
   # as a part of the "runtime" target.
   if(USE_HEXAGON_RPC)
     target_link_libraries(
-      hexagon_rpc_skel -Wl,--whole-archive tvm_runtime tvm_ffi_static 
-Wl,--no-whole-archive)
+      hexagon_rpc_skel -Wl,--whole-archive tvm_ffi_static tvm_runtime_static 
-Wl,--no-whole-archive)
+    target_link_options(hexagon_rpc_skel PRIVATE "-Wl,--wrap=pthread_create")
     add_dependencies(runtime hexagon_rpc_skel)
   endif()
 endif()
@@ -873,23 +680,20 @@ if(USE_CUDA AND USE_CUTLASS)
   install(TARGETS fpA_intB_gemm EXPORT ${PROJECT_NAME}Targets DESTINATION 
lib${LIB_SUFFIX})
   install(TARGETS flash_attn EXPORT ${PROJECT_NAME}Targets DESTINATION 
lib${LIB_SUFFIX})
   # fpA_intB_gemm, fpA_intB_gemm_tvm, and flash_attn are linked by
-  # tvm_runtime_extra (see the inline assembly block above); no link needed 
here.
+  # tvm_runtime_extra (see RuntimeExtra.cmake); no link needed here.
 endif()
 
 if(USE_CUDA AND USE_NVTX)
   add_compile_definitions(TVM_NVTX_ENABLED=1)
 endif()
 
-# Note: NCCL, NVSHMEM, RCCL target_link_libraries are handled in the inline
-# libtvm_runtime_extra assembly block above.
-
-# Keep the core shared libraries relocatable. A relative rpath
-# ($ORIGIN / @loader_path) is correct in any build because the sibling runtime
-# DSOs are installed next to each other, so apply it unconditionally rather 
than
-# only when building the Python wheel. (tvm_runtime_extra already gets its 
rpath
-# where it is defined above.)
-tvm_configure_target_library(tvm_compiler)
-tvm_configure_target_library(tvm_runtime)
+# Tests inherit the final project linker flags and compile definitions.
+add_subdirectory(tests/cpp)
+if(GTEST_FOUND)
+  # CTest must also be enabled at the project root for subdirectory discovery.
+  enable_testing()
+  include(CTest)
+endif()
 
 # Python package installation configuration
 # This section ensures that all necessary files are installed for the Python 
wheel
diff --git a/apps/cpp_rpc/CMakeLists.txt b/apps/cpp_rpc/CMakeLists.txt
index f8e0a05690..e24c1c9ccb 100644
--- a/apps/cpp_rpc/CMakeLists.txt
+++ b/apps/cpp_rpc/CMakeLists.txt
@@ -61,22 +61,10 @@ if (BUILD_FOR_ANDROID AND USE_HEXAGON)
   list(APPEND TVM_RPC_LINKER_LIBS cdsprpc log)
 endif()
 
-if(BUILD_STATIC_RUNTIME)
-  foreach(lib ${TVM_RUNTIME_BACKEND_LIBS})
-    if(MSVC)
-      list(APPEND TVM_RPC_LINKER_LIBS "/WHOLEARCHIVE:$<TARGET_FILE:${lib}>")
-    elseif(APPLE)
-      list(APPEND TVM_RPC_LINKER_LIBS "-Wl,-force_load,$<TARGET_FILE:${lib}>")
-    else()
-      list(APPEND TVM_RPC_LINKER_LIBS "-Wl,--whole-archive" "${lib}" 
"-Wl,--no-whole-archive")
-    endif()
-  endforeach()
+if(NOT MSVC AND NOT APPLE)
+  list(APPEND TVM_RPC_LINKER_LIBS tvm_runtime "-Wl,--no-as-needed" 
${TVM_RUNTIME_BACKEND_LIBS} "-Wl,--as-needed")
 else()
-  if(NOT MSVC AND NOT APPLE)
-    list(APPEND TVM_RPC_LINKER_LIBS tvm_runtime "-Wl,--no-as-needed" 
${TVM_RUNTIME_BACKEND_LIBS} "-Wl,--as-needed")
-  else()
-    list(APPEND TVM_RPC_LINKER_LIBS tvm_runtime ${TVM_RUNTIME_BACKEND_LIBS})
-  endif()
+  list(APPEND TVM_RPC_LINKER_LIBS tvm_runtime ${TVM_RUNTIME_BACKEND_LIBS})
 endif()
 
 target_link_libraries(tvm_rpc PRIVATE ${TVM_RPC_LINKER_LIBS})
diff --git a/cmake/config.cmake b/cmake/config.cmake
index 83e81aa137..247b00d319 100644
--- a/cmake/config.cmake
+++ b/cmake/config.cmake
@@ -241,17 +241,17 @@ set(USE_HEXAGON_ARCH "v68")
 # Whether to use QHL library
 set(USE_HEXAGON_QHL OFF)
 
-# Whether to build static libtvm_runtime.a, the default is to build the dynamic
-# version: libtvm_runtime.so.
+# Both libtvm_runtime.so and libtvm_runtime_static.a are built by default.
 #
-# The static runtime library needs to be linked into executables with the 
linker
-# option --whole-archive (or its equivalent). The reason is that the TVM 
registry
-# mechanism relies on global constructors being executed at program startup.
+# Link both static archives with tvm_ffi_static before tvm_runtime_static so
+# FFI type attributes initialize before runtime registrations. Retain both with
+# --whole-archive (or its equivalent), for example:
+#   -Wl,--whole-archive -ltvm_ffi_static -ltvm_runtime_static 
-Wl,--no-whole-archive
+# The TVM registry relies on global constructors being executed at program 
startup.
 # Global constructors alone are not sufficient for the linker to consider a
 # library member to be used, and some of such library members (object files) 
may
 # not be included in the final executable. This would make the corresponding
 # runtime functions to be unavailable to the program.
-set(BUILD_STATIC_RUNTIME OFF)
 
 # Caches the build so that building is faster when switching between branches.
 # If you switch branches, build and then encounter a linking error, you may
diff --git a/cmake/modules/OpenCL.cmake b/cmake/modules/OpenCL.cmake
index becbfa7a1d..4a398ae79f 100644
--- a/cmake/modules/OpenCL.cmake
+++ b/cmake/modules/OpenCL.cmake
@@ -21,9 +21,10 @@ if(USE_OPENCL)
   set(_opencl_libs "")
   if(${USE_OPENCL} MATCHES ${IS_TRUE_PATTERN})
     message(STATUS "Enabled runtime search for OpenCL library location")
-    file_glob_append(RUNTIME_OPENCL_SRCS
+    tvm_file_glob(GLOB OPENCL_WRAPPER_SRCS
       "src/backend/opencl/runtime/opencl_wrapper/opencl_wrapper.cc"
     )
+    list(APPEND RUNTIME_OPENCL_SRCS ${OPENCL_WRAPPER_SRCS})
     set(_opencl_include_dirs 
"${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/OpenCL-Headers")
   else()
     find_opencl(${USE_OPENCL})
diff --git a/cmake/modules/RuntimeExtra.cmake b/cmake/modules/RuntimeExtra.cmake
new file mode 100644
index 0000000000..a1bcb50174
--- /dev/null
+++ b/cmake/modules/RuntimeExtra.cmake
@@ -0,0 +1,103 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Assemble the runtime-extra target declared before the backend modules.
+# Disco core sources.
+tvm_file_glob(GLOB _disco_core_srcs src/runtime/extra/disco/*.cc)
+add_library(tvm_disco_objs OBJECT ${_disco_core_srcs})
+target_link_libraries(tvm_disco_objs PRIVATE tvm_runtime_extra_defs)
+target_link_libraries(tvm_runtime_extra PRIVATE tvm_disco_objs)
+
+# Distributed disco (disabled for Hexagon cross-compile).
+if(NOT BUILD_FOR_HEXAGON)
+  tvm_file_glob(GLOB _disco_dist_srcs src/runtime/extra/disco/distributed/*.cc)
+  add_library(tvm_disco_distributed_objs OBJECT ${_disco_dist_srcs})
+  target_link_libraries(tvm_disco_distributed_objs PRIVATE 
tvm_runtime_extra_defs)
+  target_link_libraries(tvm_runtime_extra PRIVATE tvm_disco_distributed_objs)
+endif()
+
+# NCCL / cuda_ipc — requires CUDA + NCCL.
+if(USE_CUDA AND USE_NCCL)
+  find_nccl(${USE_NCCL})
+  include_directories(SYSTEM ${NCCL_INCLUDE_DIR})
+  tvm_file_glob(GLOB _nccl_srcs src/runtime/extra/disco/nccl/*.cc 
src/runtime/extra/disco/cuda_ipc/*.cc 3rdparty/tensorrt_llm/*.cu)
+  set_source_files_properties(src/runtime/extra/disco/nccl/nccl.cc PROPERTIES 
COMPILE_DEFINITIONS "TVM_NCCL_RCCL_SWITCH=0")
+  add_library(tvm_nccl_objs OBJECT ${_nccl_srcs})
+  target_link_libraries(tvm_nccl_objs PRIVATE tvm_runtime_extra_defs)
+  find_library(LIBRT rt)
+  target_link_libraries(tvm_runtime_extra PRIVATE tvm_nccl_objs nccl ${LIBRT})
+endif()
+
+# NVSHMEM.
+if(USE_CUDA AND USE_NVSHMEM)
+  find_nvshmem(${USE_NVSHMEM})
+  if(NOT NVSHMEM_FOUND)
+    message(FATAL_ERROR "Cannot find NVSHMEM, USE_NVSHMEM=" ${USE_NVSHMEM})
+  endif()
+  set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)
+  set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+  tvm_file_glob(GLOB _nvshmem_srcs src/runtime/extra/contrib/nvshmem/*.cc 
src/runtime/extra/contrib/nvshmem/*.cu)
+  add_library(tvm_nvshmem_objs OBJECT ${_nvshmem_srcs})
+  target_link_libraries(tvm_nvshmem_objs PRIVATE tvm_runtime_extra_defs)
+  target_include_directories(tvm_nvshmem_objs PUBLIC ${NVSHMEM_INCLUDE_DIR})
+  # CUDA 13 moved libcu++ under include/cccl. NVSHMEM's headers include
+  # <cuda/std/...>, which nvcc resolves on its own -- the host compiler
+  # building this target's .cc sources does not, so name the directory.
+  if(EXISTS "${CUDA_TOOLKIT_ROOT_DIR}/include/cccl")
+    target_include_directories(tvm_nvshmem_objs SYSTEM PUBLIC
+                               "${CUDA_TOOLKIT_ROOT_DIR}/include/cccl")
+  endif()
+  find_library(NVSHMEM_HOST nvshmem_host ${NVSHMEM_LIB_DIR})
+  find_library(NVSHMEM_DEVICE nvshmem_device ${NVSHMEM_LIB_DIR})
+  target_link_libraries(tvm_runtime_extra PRIVATE tvm_nvshmem_objs 
${NVSHMEM_HOST} ${NVSHMEM_DEVICE})
+  set_target_properties(tvm_runtime_extra PROPERTIES 
CUDA_SEPARABLE_COMPILATION ON)
+endif()
+
+# RCCL.
+if(USE_ROCM AND USE_RCCL)
+  find_rccl(${USE_RCCL})
+  include_directories(SYSTEM ${RCCL_INCLUDE_DIR})
+  tvm_file_glob(GLOB _rccl_srcs src/runtime/extra/disco/nccl/*.cc)
+  set_source_files_properties(src/runtime/extra/disco/nccl/nccl.cc PROPERTIES 
COMPILE_DEFINITIONS "TVM_NCCL_RCCL_SWITCH=1")
+  add_library(tvm_rccl_objs OBJECT ${_rccl_srcs})
+  target_link_libraries(tvm_rccl_objs PRIVATE tvm_runtime_extra_defs)
+  target_link_libraries(tvm_runtime_extra PRIVATE tvm_rccl_objs rccl)
+endif()
+
+target_link_libraries(tvm_runtime_extra PUBLIC tvm_runtime)
+
+# If disco/cuda_ipc is included, link the CUDA DSO.
+if(USE_CUDA)
+  target_link_libraries(tvm_runtime_extra PUBLIC tvm_runtime_cuda)
+  target_link_libraries(tvm_runtime_extra PRIVATE ${CUDA_CUDART_LIBRARY} 
${CUDA_CUDA_LIBRARY})
+endif()
+
+if(USE_ROCM)
+  target_link_libraries(tvm_runtime_extra PRIVATE ${ROCM_HIPHCC_LIBRARY})
+endif()
+
+# CUTLASS fpA_intB_gemm and flash_attn are separate shared libs.
+if(USE_CUDA AND USE_CUTLASS)
+  target_link_libraries(tvm_runtime_extra PRIVATE fpA_intB_gemm 
fpA_intB_gemm_tvm)
+  target_link_libraries(tvm_runtime_extra PRIVATE -Wl,--no-as-needed 
flash_attn)
+endif()
+
+if(TVM_VISIBILITY_FLAG)
+  set_property(TARGET tvm_runtime_extra APPEND PROPERTY LINK_OPTIONS 
"${TVM_VISIBILITY_FLAG}")
+endif()
+
+tvm_configure_target_library(tvm_runtime_extra RUNTIME_MODULE)
diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt
new file mode 100644
index 0000000000..35c9fadb9e
--- /dev/null
+++ b/tests/cpp/CMakeLists.txt
@@ -0,0 +1,109 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Enable ctest if gtest is available
+if(USE_GTEST)
+  # Check env var for backward compatibility. A better way to specify package
+  # locations is to use CMAKE_PREFIX_PATH or other standard CMake mechanism
+  # (see CMake documentation for `find_package`).
+  set(GTEST_ROOT "$ENV{GTEST_LIB}")
+  if("${USE_GTEST}" STREQUAL "AUTO")
+    # If USE_GTEST is AUTO, treat GTest as optional: enable if found.
+    find_package(GTest)
+  elseif("${USE_GTEST}" MATCHES ${IS_TRUE_PATTERN})
+    # USE_GTEST is set to ON, TRUE, etc. Treat GTest as a required package.
+    find_package(GTest REQUIRED)
+  endif()
+  if(GTEST_FOUND)
+    if(NOT TARGET GTest::gmock)
+      # GMock is formally supported in CMake 3.20; for now, expect libgmock.a 
in the same directory,
+      # and require that folks compiling against GTest::gmock also link 
against GTest::GTest
+      # (for the includes dir).
+      add_library(GTest::gmock STATIC IMPORTED GLOBAL)
+      get_target_property(GTEST_LIB_PATH GTest::GTest IMPORTED_LOCATION)
+      if("${GTEST_LIB_PATH}" STREQUAL "GTEST_LIB_PATH-NOTFOUND")
+        # CMake >= 3.20 makes GTest::GTest into a compatibility target. The 
real import location is in
+        # GTest::gtest.
+        get_target_property(GTEST_LIB_PATH GTest::gtest IMPORTED_LOCATION)
+        if("${GTEST_LIB_PATH}" STREQUAL "GTEST_LIB_PATH-NOTFOUND")
+          message(FATAL_ERROR "Neither GTest::GTest nor GTest::gtest targets 
defined IMPORTED_LOCATION")
+        endif()
+      endif()
+      get_filename_component(GTEST_LIB_DIR "${GTEST_LIB_PATH}" DIRECTORY)
+      set_target_properties(GTest::gmock PROPERTIES
+          IMPORTED_LOCATION "${GTEST_LIB_DIR}/libgmock.a")
+    endif()
+
+    enable_testing()
+    include(GoogleTest)
+    set(GTEST_FOUND TRUE PARENT_SCOPE)
+  endif()
+endif()
+
+# Create the `cpptest` target when GTest is available.
+if(GTEST_FOUND)
+  tvm_file_glob(GLOB_RECURSE TEST_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/*.cc")
+  add_executable(cpptest ${TEST_SRCS})
+  # Preserve the executable location used before this became a subdirectory.
+  set_target_properties(cpptest PROPERTIES RUNTIME_OUTPUT_DIRECTORY 
"${PROJECT_BINARY_DIR}")
+  # include runtime files for unit testing
+  target_link_libraries(cpptest PRIVATE tvm_compiler GTest::GTest GTest::Main 
GTest::gmock pthread dl)
+  if(DEFINED LLVM_LIBS)
+    # The TVM library is linked with LLVM libraries. If the LLVM libraries are
+    # static and the symbols are not hidden, then don't link them again into
+    # cpptest since cpptest is itself linked against the TVM library. If static
+    # LLVM libraries are linked in twice, it can cause issues with global
+    # variable initialization (cl::opt).
+    # If the LLVM libraries are dynamic, we have to link them again, since the
+    # TVM library will not contain any LLVM definitions.
+    unset(LLVM_SO)
+    foreach(L IN LISTS LLVM_LIBS)
+      if(L MATCHES "libLLVM.*\.so")
+        set(LLVM_SO TRUE)
+        break()
+      endif()
+    endforeach()
+    if(DEFINED LLVM_SO OR HIDE_PRIVATE_SYMBOLS)
+      target_link_libraries(cpptest PRIVATE ${LLVM_LIBS})
+    endif()
+  endif()
+  set_target_properties(cpptest PROPERTIES EXCLUDE_FROM_ALL 1)
+  set_target_properties(cpptest PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1)
+  # cpptest is built into ${CMAKE_BINARY_DIR} but the TVM shared libs it
+  # links against (libtvm_compiler.so / libtvm_runtime.so) now live in
+  # ${CMAKE_BINARY_DIR}/lib. CMake's default behaviour bakes the
+  # absolute path of build/lib/ into the binary's RUNPATH, which works in tree
+  # but is not relocatable. Set $ORIGIN/lib (or @loader_path/lib on macOS)
+  # explicitly so the test binary finds its TVM libs relative to its own
+  # location, mirroring the install-time RPATH setup.
+  if(APPLE)
+    set_target_properties(cpptest PROPERTIES
+      BUILD_RPATH "@loader_path/lib"
+      INSTALL_RPATH "@loader_path/lib"
+    )
+  elseif(UNIX)
+    set_target_properties(cpptest PROPERTIES
+      BUILD_RPATH "\$ORIGIN/lib"
+      INSTALL_RPATH "\$ORIGIN/lib"
+    )
+  endif()
+  target_compile_definitions(cpptest PRIVATE "NDEBUG")
+
+  # For some reason, compile definitions are not propagated correctly, so we 
manually add them here
+  target_compile_definitions(cpptest PUBLIC 
$<TARGET_PROPERTY:tvm_compiler,INTERFACE_COMPILE_DEFINITIONS>)
+  gtest_discover_tests(cpptest WORKING_DIRECTORY "${PROJECT_BINARY_DIR}")
+endif()

Reply via email to