# 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. """Backend loading and public alias support.""" from __future__ import annotations import importlib import importlib.util import sys import types from typing import Any _LOADED_BACKENDS: dict[str, Any] = {} class _AliasModule(types.ModuleType): """Module object that exposes a backend module under a public alias.""" def __init__(self, fullname: str, module): super().__init__(fullname, getattr(module, "__doc__", None)) self.__dict__["__tvm_backend_module__"] = module self.__dict__["__package__"] = fullname.rpartition(".")[0] if hasattr(module, "__all__"): self.__dict__["__all__"] = module.__all__ if hasattr(module, "__path__"): self.__dict__["__path__"] = [] def __getattr__(self, name: str): return getattr(self.__dict__["__tvm_backend_module__"], name) def __setattr__(self, name: str, value): setattr(self.__dict__["__tvm_backend_module__"], name, value) def __delattr__(self, name: str): delattr(self.__dict__["__tvm_backend_module__"], name) def __dir__(self): return sorted(set(super().__dir__()) | set(dir(self.__dict__["__tvm_backend_module__"]))) class _AliasLoader: """Loader that returns an already-resolved module for an alias spec.""" def __init__(self, fullname: str, module): self._fullname = fullname self._module = module def create_module(self, spec): return _get_alias_module(self._fullname, self._module) def exec_module(self, module): _set_module_alias(self._fullname, self._module) return None def is_package(self, fullname): return hasattr(self._module, "__path__") def _redirect_tirx_backend_alias(fullname: str) -> str | None: prefix = "tvm.tirx." if not fullname.startswith(prefix): return None rest = fullname[len(prefix) :] backend_name, sep, tail = rest.partition(".") if not sep or backend_name not in _LOADED_BACKENDS: return None return f"tvm.backend.{backend_name}.{tail}" class _BackendAliasFinder: """Redirect ``tvm.tirx..*`` imports to ``tvm.backend..*``.""" @classmethod def find_spec(cls, fullname, path, target=None): redirected = _redirect_tirx_backend_alias(fullname) if redirected is None: return None module = importlib.import_module(redirected) _set_module_alias(fullname, module) loader = _AliasLoader(fullname, module) spec = importlib.util.spec_from_loader( fullname, loader, is_package=hasattr(module, "__path__") ) if spec is not None and hasattr(module, "__path__"): spec.submodule_search_locations = [] return spec if not any(isinstance(finder, _BackendAliasFinder) for finder in sys.meta_path): sys.meta_path.insert(0, _BackendAliasFinder()) def _get_alias_module(alias: str, module): existing = sys.modules.get(alias) if ( isinstance(existing, _AliasModule) and existing.__dict__.get("__tvm_backend_module__") is module ): return existing return _AliasModule(alias, module) def _set_module_alias(alias: str, module, *, direct: bool = False) -> None: alias_module = module if direct else _get_alias_module(alias, module) sys.modules[alias] = alias_module parent_name, _, child_name = alias.rpartition(".") parent = sys.modules.get(parent_name) if parent is not None: setattr(parent, child_name, alias_module) def _alias_loaded_backend_modules(name: str) -> None: backend_prefix = f"tvm.backend.{name}" public_prefix = f"tvm.tirx.{name}" for module_name, module in sorted(list(sys.modules.items())): if module_name == backend_prefix or module_name.startswith(f"{backend_prefix}."): public_name = f"{public_prefix}{module_name[len(backend_prefix) :]}" _set_module_alias(public_name, module, direct=module_name == backend_prefix) def _import_backend(name: str): module_name = f"tvm.backend.{name}" try: return importlib.import_module(module_name) except ModuleNotFoundError as err: if err.name == module_name: raise ImportError( f"Cannot load TVM backend {name!r}: expected Python package {module_name!r}. " "Install the backend package or check the backend name." ) from err raise def load(name: str) -> None: """Load a backend's Python registration hooks. Loading is idempotent. A backend package must live at ``tvm.backend.`` and expose ``register_backend()``. """ if name in _LOADED_BACKENDS: return None module = _import_backend(name) register_backend = getattr(module, "register_backend", None) if register_backend is None: raise AttributeError(f"Backend package 'tvm.backend.{name}' has no register_backend()") import tvm.tirx as tirx # pylint: disable=import-outside-toplevel setattr(tirx, name, module) sys.modules[f"tvm.tirx.{name}"] = module _LOADED_BACKENDS[name] = module try: register_backend() _alias_loaded_backend_modules(name) except Exception: _LOADED_BACKENDS.pop(name, None) if getattr(tirx, name, None) is module: delattr(tirx, name) if sys.modules.get(f"tvm.tirx.{name}") is module: sys.modules.pop(f"tvm.tirx.{name}", None) raise return None def is_loaded(name: str) -> bool: """Return whether a backend has been loaded.""" return name in _LOADED_BACKENDS __all__ = ["is_loaded", "load"]