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

spectrometerHBH 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 ebc9348bd7 [REFACTOR][PYTHON] Slim tvm.libinfo to info-only helpers 
(#19719)
ebc9348bd7 is described below

commit ebc9348bd727d8cbd132f7ab27e8d939d3c1c75a
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Jun 10 13:41:37 2026 -0400

    [REFACTOR][PYTHON] Slim tvm.libinfo to info-only helpers (#19719)
    
    This PR slims `tvm.libinfo` into a thin *info* layer that delegates path
    discovery to the `tvm_ffi.libinfo` primitives and never loads libraries.
    Loading responsibilities move to `tvm.base`, and the various ad-hoc
    path-finding helpers are phased out in favor of the tvm-ffi resolvers.
    
    ## Changes
    
    - **libinfo**: add `find_libtvm_runtime()` (resolves `libtvm_runtime`
    via
      `_find_library_by_basename` + `_resolve_and_validate`) and
    `find_tvm_include_path()` (TVM's own `include/`). `find_include_path()`
    now
    returns `[find_tvm_include_path(), *tvm_ffi.libinfo.include_paths()]`,
    folding
    in the FFI + dlpack + python-helper include dirs. Remove
    `find_lib_path`,
      `get_dll_directories`, `use_runtime_lib`, `split_env_var`, and
      `load_backend_libs`.
    - **base**: receive `load_backend_libs` and the backend DSO list; the
    runtime-only switch becomes a strict `TVM_USE_RUNTIME_LIB == "1"` check.
    - **rpc**: `with_minrpc` uses `find_libtvm_runtime()` (the `runtime`
    kwarg is
    retained as an inert back-compat parameter); the rpc server
    `load_library`
    resolves the literal library name against the current working directory.
    - **wasm**: move the `web/dist` asset search into `emcc.find_wasm_lib`,
    used by
      `emcc.create_tvmjs_wasm` and the tvmjs asset lookup.
    - **hexagon**: fix a latent bug where `_get_hexagon_rpc_lib_dir` called
    a
    non-existent `tvm_ffi.libinfo.find_lib_path`; it now relies solely on
    the
      `HEXAGON_RPC_LIB_DIR` environment variable.
---
 python/tvm/base.py                                 |  29 +-
 python/tvm/contrib/hexagon/build.py                |  11 +-
 python/tvm/contrib/tvmjs.py                        |   7 +-
 python/tvm/libinfo.py                              | 311 +++------------------
 python/tvm/rpc/minrpc.py                           |  14 +-
 python/tvm/rpc/server.py                           |  11 +-
 python/tvm/runtime/module.py                       |  10 +-
 python/tvm/support/emcc.py                         |  46 ++-
 .../python/relax/test_frontend_nn_extern_module.py |   9 +-
 9 files changed, 143 insertions(+), 305 deletions(-)

diff --git a/python/tvm/base.py b/python/tvm/base.py
index 11601a9dcc..f3f42263b4 100644
--- a/python/tvm/base.py
+++ b/python/tvm/base.py
@@ -21,6 +21,7 @@
 
 import os
 import sys
+from pathlib import Path
 
 from tvm_ffi.libinfo import load_lib_ctypes
 
@@ -37,6 +38,26 @@ if not (sys.version_info[0] >= 3 and sys.version_info[1] >= 
9):
 # library loading
 # ----------------------------
 
+# Known per-backend runtime DSOs that, when present, are loaded with
+# RTLD_GLOBAL so their static initializers register the device backend.
+_BACKEND_RUNTIME_LIBS = ["cuda", "vulkan", "opencl", "metal", "rocm", 
"hexagon", "extra"]
+
+
+def load_backend_libs(runtime_lib_path: str) -> None:
+    """Try to load each known backend runtime DSO; failures are silent."""
+    runtime_dir = Path(runtime_lib_path).resolve().parent
+    for backend in _BACKEND_RUNTIME_LIBS:
+        try:
+            load_lib_ctypes(
+                package="tvm",
+                target_name=f"tvm_runtime_{backend}",
+                mode="RTLD_GLOBAL",
+                extra_lib_paths=[runtime_dir],
+            )
+        except (OSError, FileNotFoundError, RuntimeError):
+            pass
+
+
 # The TVM C++ side is split into two shared libraries:
 #
 # - ``libtvm_runtime`` — runtime-only sources. Loaded with ``RTLD_GLOBAL`` so
@@ -46,8 +67,8 @@ if not (sys.version_info[0] >= 3 and sys.version_info[1] >= 
9):
 #   ``libtvm_runtime``. Loaded with ``RTLD_LOCAL`` so compiler internals
 #   don't leak into the global symbol namespace.
 #
-# If the environment variable ``TVM_USE_RUNTIME_LIB`` is truthy, or the
-# compiler library is simply not present (runtime-only wheel), only the
+# If the environment variable ``TVM_USE_RUNTIME_LIB`` is set to ``"1"``, or
+# the compiler library is simply not present (runtime-only wheel), only the
 # runtime is loaded and ``_LIB`` aliases ``_LIB_RUNTIME``.
 _extra_lib_paths = libinfo.package_lib_paths()
 _LIB_RUNTIME = load_lib_ctypes(
@@ -59,9 +80,9 @@ _LIB_RUNTIME = load_lib_ctypes(
 # with RTLD_GLOBAL so their static initializers register device backends.
 # Failures are swallowed silently — a missing driver just means that backend
 # is unavailable, not an error.
-libinfo.load_backend_libs(_LIB_RUNTIME._name)
+load_backend_libs(_LIB_RUNTIME._name)
 
-_RUNTIME_ONLY = libinfo.use_runtime_lib()
+_RUNTIME_ONLY = os.environ.get("TVM_USE_RUNTIME_LIB") == "1"
 if _RUNTIME_ONLY:
     _LIB = _LIB_RUNTIME
 else:
diff --git a/python/tvm/contrib/hexagon/build.py 
b/python/tvm/contrib/hexagon/build.py
index 6320377798..30b9c48458 100644
--- a/python/tvm/contrib/hexagon/build.py
+++ b/python/tvm/contrib/hexagon/build.py
@@ -34,8 +34,6 @@ import subprocess
 import sys
 import tempfile
 
-from tvm_ffi import libinfo
-
 from tvm.contrib.hexagon.hexagon_profiler import HexagonProfiler
 
 from .session import Session
@@ -74,14 +72,9 @@ def _get_hexagon_rpc_lib_dir() -> pathlib.Path:
         The path to the Hexagon API directory.
     """
     global HEXAGON_RPC_LIB_DIR
+    HEXAGON_RPC_LIB_DIR = os.environ.get("HEXAGON_RPC_LIB_DIR")
     if HEXAGON_RPC_LIB_DIR is None:
-        for path in libinfo.find_lib_path():
-            rpc_dir = os.path.join(os.path.dirname(path), "hexagon_api_output")
-            if os.path.isdir(rpc_dir):
-                HEXAGON_RPC_LIB_DIR = rpc_dir
-                break
-        else:
-            raise RuntimeError("hexagon_api binaries not found, please define 
HEXAGON_RPC_LIB_DIR")
+        raise RuntimeError("hexagon_api binaries not found, please define 
HEXAGON_RPC_LIB_DIR")
     return pathlib.Path(HEXAGON_RPC_LIB_DIR)
 
 
diff --git a/python/tvm/contrib/tvmjs.py b/python/tvm/contrib/tvmjs.py
index 46cda68106..acd86c4445 100644
--- a/python/tvm/contrib/tvmjs.py
+++ b/python/tvm/contrib/tvmjs.py
@@ -37,9 +37,8 @@ except ImportError:
     ml_dtypes = None
 
 import tvm
-from tvm.libinfo import find_lib_path
 from tvm.runtime import DataType
-from tvm.support.emcc import create_tvmjs_wasm
+from tvm.support.emcc import create_tvmjs_wasm, find_wasm_lib
 
 
 def _convert_f32_to_bf16(value):
@@ -409,11 +408,11 @@ def export_runtime(runtime_dir):
         + " cd /path/to/tvm/web; make; npm run bundle"
     )
 
-    jsbundle = find_lib_path("tvmjs.bundle.js", optional=True)
+    jsbundle = find_wasm_lib("tvmjs.bundle.js", optional=True)
     if not jsbundle:
         raise RuntimeError("Cannot find tvmjs.bundle.js, " + web_hint)
 
-    wasi = find_lib_path("tvmjs_runtime.wasi.js", optional=True)
+    wasi = find_wasm_lib("tvmjs_runtime.wasi.js", optional=True)
     if not wasi:
         raise RuntimeError("Cannot find tvmjs_runtime.wasi.js, " + web_hint)
 
diff --git a/python/tvm/libinfo.py b/python/tvm/libinfo.py
index d91db1f572..d56021df4e 100644
--- a/python/tvm/libinfo.py
+++ b/python/tvm/libinfo.py
@@ -14,24 +14,24 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-"""Library information."""
+"""Library and include path information for TVM."""
 
 from __future__ import annotations
 
 import os
-import sys
 from pathlib import Path
 
-from tvm_ffi.libinfo import load_lib_ctypes
+from tvm_ffi import libinfo as tvm_ffi_libinfo
 
 
-def use_runtime_lib() -> bool:
-    """Whether ``TVM_USE_RUNTIME_LIB`` requests runtime-only mode.
+def _rel_top_directory() -> Path:
+    """Top directory in the installed (wheel) layout: ``python/tvm/``."""
+    return Path(__file__).parent
 
-    Recognises ``1`` / ``true`` / ``yes`` (case-insensitive) as truthy.
-    Anything else — including ``0`` and the unset case — is False.
-    """
-    return os.environ.get("TVM_USE_RUNTIME_LIB", "0").lower() in ("1", "true", 
"yes")
+
+def _dev_top_directory() -> Path:
+    """Top directory in the source/dev layout: the worktree root."""
+    return _rel_top_directory() / ".." / ".."
 
 
 def package_lib_paths() -> list[Path]:
@@ -44,283 +44,50 @@ def package_lib_paths() -> list[Path]:
     pick the basenames they want (e.g. ``libtvm_runtime.so``) and the load
     mode; this function only returns the search path.
     """
-    pkg = Path(__file__).parent  # python/tvm/
+    pkg = _rel_top_directory()  # python/tvm/
     paths: list[Path] = []
     if os.environ.get("TVM_LIBRARY_PATH"):
         paths.append(Path(os.environ["TVM_LIBRARY_PATH"]))
     paths += [
         pkg / "lib",  # wheel layout
-        pkg.parent.parent / "build" / "lib",  # dev: <worktree>/build/lib
-        pkg.parent.parent / "lib",  # dev: <worktree>/lib
+        _dev_top_directory() / "build" / "lib",  # dev: <worktree>/build/lib
+        _dev_top_directory() / "lib",  # dev: <worktree>/lib
     ]
     return paths
 
 
-_BACKEND_RUNTIME_LIBS = ["cuda", "vulkan", "opencl", "metal", "rocm", 
"hexagon", "extra"]
-
-
-def load_backend_libs(runtime_lib_path: str) -> None:
-    """Try to load each known backend runtime DSO; failures are silent."""
-    runtime_dir = Path(runtime_lib_path).resolve().parent
-    for backend in _BACKEND_RUNTIME_LIBS:
-        try:
-            load_lib_ctypes(
-                package="tvm",
-                target_name=f"tvm_runtime_{backend}",
-                mode="RTLD_GLOBAL",
-                extra_lib_paths=[runtime_dir],
-            )
-        except (OSError, FileNotFoundError, RuntimeError):
-            pass
-
-
-def split_env_var(env_var, split):
-    """Splits environment variable string.
-
-    Parameters
-    ----------
-    env_var : str
-        Name of environment variable.
-
-    split : str
-        String to split env_var on.
+def find_libtvm_runtime() -> str:
+    """Return the path to the ``libtvm_runtime`` shared library.
 
     Returns
     -------
-    splits : list(string)
-        If env_var exists, split env_var. Otherwise, empty list.
-    """
-    if os.environ.get(env_var, None):
-        return [p.strip() for p in os.environ[env_var].split(split)]
-    return []
-
-
-def get_dll_directories():
-    """Get the possible dll directories"""
-    # NB: This will either be the source directory (if TVM is run
-    # inplace) or the install directory (if TVM is installed).
-    # An installed TVM's curr_path will look something like:
-    #   $PREFIX/lib/python3.6/site-packages/tvm/_ffi
-    ffi_dir = os.path.dirname(os.path.realpath(os.path.expanduser(__file__)))
-    source_dir = os.path.join(ffi_dir, "..", "..")
-    install_lib_dir = os.path.join(ffi_dir, "..", "..", "..")
-
-    dll_path = []
-
-    if os.environ.get("TVM_LIBRARY_PATH", None):
-        dll_path.append(os.environ["TVM_LIBRARY_PATH"])
-
-    if sys.platform.startswith("linux") or sys.platform.startswith("freebsd"):
-        dll_path.extend(split_env_var("LD_LIBRARY_PATH", ":"))
-        dll_path.extend(split_env_var("PATH", ":"))
-    elif sys.platform.startswith("darwin"):
-        dll_path.extend(split_env_var("DYLD_LIBRARY_PATH", ":"))
-        dll_path.extend(split_env_var("PATH", ":"))
-    elif sys.platform.startswith("win32"):
-        dll_path.extend(split_env_var("PATH", ";"))
-
-    # Pip lib directory
-    dll_path.append(ffi_dir)
-    dll_path.append(os.path.join(ffi_dir, "lib"))
-    # Default CMake build directory: shared libs are placed under build/lib/
-    # to mirror the tvm-ffi layout (so wheel install + dev-mode dlopen find
-    # them via the same `lib/` subdir).
-    dll_path.append(os.path.join(source_dir, "build", "lib"))
-    dll_path.append(os.path.join(source_dir, "build", "lib", "Release"))
-    dll_path.append(os.path.join(source_dir, "build"))
-    dll_path.append(os.path.join(source_dir, "build", "Release"))
-    # Default make build directory
-    dll_path.append(os.path.join(source_dir, "lib"))
-
-    dll_path.append(install_lib_dir)
-
-    # use extra TVM_HOME environment for finding libraries.
-    if os.environ.get("TVM_HOME", None):
-        tvm_source_home_dir = os.environ["TVM_HOME"]
-    else:
-        tvm_source_home_dir = source_dir
-
-    if os.path.isdir(tvm_source_home_dir):
-        dll_path.append(os.path.join(tvm_source_home_dir, "web", "dist", 
"wasm"))
-        dll_path.append(os.path.join(tvm_source_home_dir, "web", "dist"))
-
-    dll_path = [os.path.realpath(x) for x in dll_path]
-    return [x for x in dll_path if os.path.isdir(x)]
-
-
-def find_lib_path(name=None, search_path=None, optional=False):
-    """Find dynamic library files.
-
-    Parameters
-    ----------
-    name : list of str
-        List of names to be found.
+    path : str
+        The resolved path to the TVM runtime shared library.
 
-    Returns
-    -------
-    lib_path : list(string)
-        List of all found path to the libraries
+    Raises
+    ------
+    RuntimeError
+        If the runtime library cannot be found.
     """
-    use_runtime = use_runtime_lib()
-    dll_path = get_dll_directories()
-    # When the caller asks for a specific ``name`` we honour it directly
-    # regardless of TVM_USE_RUNTIME_LIB; that env var is interpreted by
-    # ``base.py::_load_lib`` to choose which name to ask for. This avoids
-    # the runtime/compiler dual-list logic below from making `name` paths
-    # unreachable when the user sets TVM_USE_RUNTIME_LIB.
-    if name is not None:
-        use_runtime = False
-
-    if search_path is not None:
-        if isinstance(search_path, list):
-            dll_path = dll_path + search_path
-        else:
-            dll_path.append(search_path)
-
-    if name is not None:
-        if isinstance(name, list):
-            lib_dll_path = []
-            for n in name:
-                lib_dll_path += [os.path.join(p, n) for p in dll_path]
-        else:
-            lib_dll_path = [os.path.join(p, name) for p in dll_path]
-        runtime_dll_path = []
-        ext_lib_dll_path = []
-    else:
-        if sys.platform.startswith("win32"):
-            lib_dll_names = ["libtvm_compiler.dll", "tvm_compiler.dll"]
-            runtime_dll_names = ["libtvm_runtime.dll", "tvm_runtime.dll"]
-            ext_lib_dll_names = [
-                
"3rdparty/cutlass_fpA_intB_gemm/cutlass_kernels/libfpA_intB_gemm.dll",
-                "3rdparty/libflash_attn/src/libflash_attn.dll",
-            ]
-        elif sys.platform.startswith("darwin"):
-            lib_dll_names = ["libtvm_compiler.dylib"]
-            runtime_dll_names = ["libtvm_runtime.dylib"]
-            ext_lib_dll_names = []
-        else:
-            lib_dll_names = ["libtvm_compiler.so"]
-            runtime_dll_names = ["libtvm_runtime.so"]
-            ext_lib_dll_names = [
-                
"3rdparty/cutlass_fpA_intB_gemm/cutlass_kernels/libfpA_intB_gemm.so",
-                "3rdparty/libflash_attn/src/libflash_attn.so",
-            ]
-
-        name = lib_dll_names + runtime_dll_names + ext_lib_dll_names
-        lib_dll_path = [
-            os.path.join(p, name)
-            for name in lib_dll_names
-            for p in dll_path
-            if not p.endswith("python/tvm")
-        ]
-        runtime_dll_path = [
-            os.path.join(p, name)
-            for name in runtime_dll_names
-            for p in dll_path
-            if not p.endswith("python/tvm")
-        ]
-        ext_lib_dll_path = [os.path.join(p, name) for name in 
ext_lib_dll_names for p in dll_path]
-    if not use_runtime:
-        # try to find lib_dll_path
-        lib_found = [p for p in lib_dll_path if os.path.exists(p) and 
os.path.isfile(p)]
-        lib_found += [p for p in runtime_dll_path if os.path.exists(p) and 
os.path.isfile(p)]
-        lib_found += [p for p in ext_lib_dll_path if os.path.exists(p) and 
os.path.isfile(p)]
-    else:
-        # try to find runtime_dll_path
-        use_runtime = True
-        lib_found = [p for p in runtime_dll_path if os.path.exists(p) and 
os.path.isfile(p)]
-
-    if not lib_found:
-        if not optional:
-            message = (
-                f"Cannot find libraries: {name}\n"
-                + "List of candidates:\n"
-                + "\n".join(lib_dll_path + runtime_dll_path)
-            )
-            raise RuntimeError(message)
-        return None
-
-    if use_runtime:
-        sys.stderr.write(f"Loading runtime library {lib_found[0]}... exec 
only\n")
-        sys.stderr.flush()
-    return lib_found
-
-
-def find_include_path(name=None, search_path=None, optional=False):
-    """Find header files for C compilation.
-
-    Parameters
-    ----------
-    name : list of str
-        List of directory names to be searched.
-
-    Returns
-    -------
-    include_path : list(string)
-        List of all found paths to header files.
-    """
-    if os.environ.get("TVM_SOURCE_DIR", None):
-        source_dir = os.environ["TVM_SOURCE_DIR"]
-    elif os.environ.get("TVM_HOME", None):
-        source_dir = os.environ["TVM_HOME"]
-    else:
-        ffi_dir = 
os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
-        for source_dir in ["..", "../..", "../../.."]:
-            source_dir = os.path.join(ffi_dir, source_dir)
-            if os.path.isdir(os.path.join(source_dir, "include")):
-                break
-        else:
-            raise AssertionError(f"Cannot find the source directory given 
ffi_dir: {ffi_dir}")
-    third_party_dir = os.path.join(source_dir, "3rdparty")
-
-    header_path = []
-
-    if os.environ.get("TVM_INCLUDE_PATH", None):
-        header_path.append(os.environ["TVM_INCLUDE_PATH"])
-
-    header_path.append(source_dir)
-    header_path.append(third_party_dir)
-
-    header_path = [os.path.abspath(x) for x in header_path]
-    if search_path is not None:
-        if isinstance(search_path, list):
-            header_path = header_path + search_path
-        else:
-            header_path.append(search_path)
-    if name is not None:
-        if isinstance(name, list):
-            tvm_include_path = []
-            for n in name:
-                tvm_include_path += [os.path.join(p, n) for p in header_path]
-        else:
-            tvm_include_path = [os.path.join(p, name) for p in header_path]
-        dlpack_include_path = []
-    else:
-        tvm_include_path = [os.path.join(p, "include") for p in header_path]
-        tvm_ffi_include_path = [
-            os.path.join(p, "3rdparty", "tvm-ffi", "include") for p in 
header_path
-        ]
-        dlpack_include_path = [
-            os.path.join(p, "3rdparty", "tvm-ffi", "3rdparty", "dlpack", 
"include")
-            for p in header_path
-        ]
-
-        # try to find include path
-        include_found = [p for p in tvm_include_path if os.path.exists(p) and 
os.path.isdir(p)]
-        include_found += [p for p in tvm_ffi_include_path if os.path.exists(p) 
and os.path.isdir(p)]
-        include_found += [p for p in dlpack_include_path if os.path.exists(p) 
and os.path.isdir(p)]
-
-    if not include_found:
-        message = (
-            "Cannot find the files.\n"
-            + "List of candidates:\n"
-            + str("\n".join(tvm_include_path + dlpack_include_path))
-        )
-        if not optional:
-            raise RuntimeError(message)
-        return None
-
-    return include_found
+    candidate = tvm_ffi_libinfo._find_library_by_basename(
+        "tvm", "tvm_runtime", extra_lib_paths=package_lib_paths()
+    )
+    if ret := tvm_ffi_libinfo._resolve_and_validate([candidate], cond=lambda 
_: True):
+        return ret
+    raise RuntimeError("Cannot find libtvm_runtime")
+
+
+def find_include_path() -> str:
+    """Return the path to TVM's own ``include/`` directory."""
+    if ret := tvm_ffi_libinfo._resolve_and_validate(
+        paths=[
+            _rel_top_directory() / "include",
+            _dev_top_directory() / "include",
+        ],
+        cond=lambda p: (p / "tvm" / "runtime").is_dir(),
+    ):
+        return ret
+    raise RuntimeError("Cannot find TVM include path.")
 
 
 # The version is written by setuptools_scm into _version.py at build time
diff --git a/python/tvm/rpc/minrpc.py b/python/tvm/rpc/minrpc.py
index f3819c2791..d46f2a2faf 100644
--- a/python/tvm/rpc/minrpc.py
+++ b/python/tvm/rpc/minrpc.py
@@ -48,7 +48,7 @@ def find_minrpc_server_libpath(server="posix_popen_server"):
     return minrpc_dir, path
 
 
-def with_minrpc(compile_func, server="posix_popen_server", 
runtime="libtvm_runtime"):
+def with_minrpc(compile_func, server="posix_popen_server"):
     """Attach the compiler function with minrpc related options.
 
     Parameters
@@ -59,16 +59,13 @@ def with_minrpc(compile_func, server="posix_popen_server", 
runtime="libtvm_runti
     server : str
         The server type.
 
-    runtime : str
-        The runtime library.
-
     Returns
     -------
     fcompile : function
         The return compilation.
     """
     minrpc_dir, server_path = find_minrpc_server_libpath(server)
-    runtime_path = libinfo.find_lib_path([runtime, runtime + ".so", runtime + 
".dylib"])[0]
+    runtime_path = libinfo.find_libtvm_runtime()
     tvm_ffi_path = tvm_ffi.libinfo.find_libtvm_ffi()
 
     runtime_dir = os.path.abspath(os.path.dirname(runtime_path))
@@ -86,7 +83,12 @@ def with_minrpc(compile_func, server="posix_popen_server", 
runtime="libtvm_runti
     # inside libtvm_runtime; with the default ``--as-needed`` those
     # registrations would never run.
     options += ["-Wl,--no-as-needed"]
-    options += ["-I" + path for path in libinfo.find_include_path()]
+    default_include_paths = [
+        libinfo.find_include_path(),
+        tvm_ffi.libinfo.find_include_path(),
+        tvm_ffi.libinfo.find_dlpack_include_path(),
+    ]
+    options += ["-I" + path for path in default_include_paths]
     options += ["-I" + minrpc_dir]
     fcompile = cc.cross_compiler(
         compile_func, options=options, add_files=[server_path, runtime_path, 
tvm_ffi_path]
diff --git a/python/tvm/rpc/server.py b/python/tvm/rpc/server.py
index 099cb8f1f1..4a28da6726 100644
--- a/python/tvm/rpc/server.py
+++ b/python/tvm/rpc/server.py
@@ -38,11 +38,11 @@ import struct
 import sys
 import threading
 import time
+from pathlib import Path
 
 import tvm_ffi
 
 from tvm.base import py_str
-from tvm.libinfo import find_lib_path
 from tvm.runtime.module import load_module as _load_module
 from tvm.support import utils
 from tvm.support.popen_pool import PopenWorker
@@ -115,7 +115,14 @@ def _server_env(load_library, work_path=None):
     libs = []
     load_library = load_library.split(":") if load_library else []
     for file_name in load_library:
-        file_name = find_lib_path(file_name)[0]
+        # Resolve the literal library file name against the current working
+        # directory, preserving the exact filename the caller requested.
+        resolved = tvm_ffi.libinfo._resolve_and_validate(
+            [Path.cwd() / file_name], cond=lambda p: p.is_file()
+        )
+        if resolved is None:
+            raise RuntimeError(f"Cannot find library {file_name}")
+        file_name = resolved
         libs.append(ctypes.CDLL(file_name, ctypes.RTLD_GLOBAL))
         logger.info("Load additional library %s", file_name)
     temp.libs = libs
diff --git a/python/tvm/runtime/module.py b/python/tvm/runtime/module.py
index 8ecda773b9..8fde8cc443 100644
--- a/python/tvm/runtime/module.py
+++ b/python/tvm/runtime/module.py
@@ -28,6 +28,7 @@ import numpy as np
 from tvm_ffi import (
     Module as _Module,
 )
+from tvm_ffi import libinfo as tvm_ffi_libinfo
 from tvm_ffi import (
     load_module as _load_module,
 )
@@ -38,8 +39,8 @@ from tvm_ffi import (
     system_lib,
 )
 
+import tvm.libinfo
 from tvm.base import _RUNTIME_ONLY
-from tvm.libinfo import find_include_path
 
 from . import _ffi_api
 
@@ -311,7 +312,12 @@ class Module(_Module):
             if "options" in kwargs:
                 opts = kwargs["options"]
                 options = opts if isinstance(opts, list | tuple) else [opts]
-            opts = options + ["-I" + path for path in find_include_path()]
+            default_include_paths = [
+                tvm.libinfo.find_include_path(),
+                tvm_ffi_libinfo.find_include_path(),
+                tvm_ffi_libinfo.find_dlpack_include_path(),
+            ]
+            opts = options + ["-I" + path for path in default_include_paths]
             kwargs.update({"options": opts})
 
         return fcompile(file_name, files, **kwargs)
diff --git a/python/tvm/support/emcc.py b/python/tvm/support/emcc.py
index c2f5e6dbbc..9bd6d24036 100644
--- a/python/tvm/support/emcc.py
+++ b/python/tvm/support/emcc.py
@@ -21,8 +21,46 @@ import os
 import subprocess
 from pathlib import Path
 
+from tvm import libinfo
 from tvm.base import py_str
-from tvm.libinfo import find_lib_path
+
+
+def find_wasm_lib(name, optional=False):
+    """Find a wasm/web asset (e.g. a ``.bc`` or ``.js`` file) under 
``web/dist``.
+
+    Specializes the asset search to the wasm build outputs that live under
+    ``web/dist/wasm`` and ``web/dist``, anchored on ``TVM_HOME`` (when set) or
+    the TVM source root otherwise.
+
+    Parameters
+    ----------
+    name : str
+        The asset file name to look for.
+
+    optional : bool
+        When True, return None instead of raising if the asset is not found.
+
+    Returns
+    -------
+    found : list(str) or None
+        List of matching paths, or None when ``optional`` and nothing is found.
+    """
+    # use extra TVM_HOME environment for finding libraries.
+    if os.environ.get("TVM_HOME", None):
+        tvm_source_home_dir = os.environ["TVM_HOME"]
+    else:
+        tvm_source_home_dir = os.fspath(libinfo._dev_top_directory())
+
+    dll_path = [
+        os.path.join(tvm_source_home_dir, "web", "dist", "wasm"),
+        os.path.join(tvm_source_home_dir, "web", "dist"),
+    ]
+    found = [os.path.join(p, name) for p in dll_path if 
os.path.isfile(os.path.join(p, name))]
+    if not found:
+        if optional:
+            return None
+        raise RuntimeError(f"Cannot find {name}; searched {dll_path}")
+    return found
 
 
 def create_tvmjs_wasm(output, objects, options=None, cc="emcc", libs=None):
@@ -72,10 +110,10 @@ def create_tvmjs_wasm(output, objects, options=None, 
cc="emcc", libs=None):
 
     all_libs = []
     if not with_runtime:
-        all_libs += [find_lib_path("wasm_runtime.bc")[0]]
+        all_libs += [find_wasm_lib("wasm_runtime.bc")[0]]
 
-    all_libs += [find_lib_path("tvmjs_support.bc")[0]]
-    all_libs += [find_lib_path("webgpu_runtime.bc")[0]]
+    all_libs += [find_wasm_lib("tvmjs_support.bc")[0]]
+    all_libs += [find_wasm_lib("webgpu_runtime.bc")[0]]
 
     if libs:
         if not isinstance(libs, list):
diff --git a/tests/python/relax/test_frontend_nn_extern_module.py 
b/tests/python/relax/test_frontend_nn_extern_module.py
index a304766399..dba87c3fde 100644
--- a/tests/python/relax/test_frontend_nn_extern_module.py
+++ b/tests/python/relax/test_frontend_nn_extern_module.py
@@ -24,6 +24,7 @@ import numpy as np
 import tvm_ffi
 
 import tvm
+import tvm.libinfo
 import tvm.testing
 from tvm import relax
 from tvm.relax.frontend import nn
@@ -122,12 +123,16 @@ def _check_ir_equality(mod):
 def _compile_cc(src: Path, dst: Path):
     # pylint: disable=import-outside-toplevel
     from tvm.base import py_str
-    from tvm.libinfo import find_include_path
 
     # pylint: enable=import-outside-toplevel
 
     cmd = ["g++", str(src)]
-    for include_path in find_include_path():
+    default_include_paths = [
+        tvm.libinfo.find_include_path(),
+        tvm_ffi.libinfo.find_include_path(),
+        tvm_ffi.libinfo.find_dlpack_include_path(),
+    ]
+    for include_path in default_include_paths:
         cmd += ["-I", include_path]
     cmd += [
         "-c",

Reply via email to