diff --git a/cuda_core/cuda/core/_linker.pyx b/cuda_core/cuda/core/_linker.pyx index e89e780b34..00c9371e08 100644 --- a/cuda_core/cuda/core/_linker.pyx +++ b/cuda_core/cuda/core/_linker.pyx @@ -167,10 +167,22 @@ cdef class Linker: else: return as_py(self._culink_handle) - @property - def backend(self) -> str: - """Return this Linker instance's underlying backend.""" - return "nvJitLink" if self._use_nvjitlink else "driver" + @classmethod + def backend(cls) -> str: + """Return which linking backend will be used. + + Returns ``"nvJitLink"`` when the nvJitLink library is available + and meets the minimum version requirement, otherwise ``"driver"``. + + .. note:: + + Prefer letting :class:`Linker` decide. Query ``backend()`` + only when you need to dispatch based on input format (for + example: choose PTX vs. LTOIR before constructing a + ``Linker``). The returned string names an implementation + detail whose support matrix may shift across CTK releases. + """ + return "driver" if _decide_nvjitlink_or_driver() else "nvJitLink" # ============================================================================= diff --git a/cuda_core/cuda/core/_program.pyx b/cuda_core/cuda/core/_program.pyx index cfc66451c8..16254b2643 100644 --- a/cuda_core/cuda/core/_program.pyx +++ b/cuda_core/cuda/core/_program.pyx @@ -649,7 +649,7 @@ cdef inline int Program_init(Program self, object code, str code_type, object op self._linker = Linker( ObjectCode._init(code.encode(), code_type), options=_translate_program_options(options) ) - self._backend = self._linker.backend + self._backend = self._linker.backend() elif code_type == "nvvm": _get_nvvm_module() # Validate NVVM availability diff --git a/cuda_core/docs/source/release/1.0.0-notes.rst b/cuda_core/docs/source/release/1.0.0-notes.rst index 3f61a30ec1..a5502f74a7 100644 --- a/cuda_core/docs/source/release/1.0.0-notes.rst +++ b/cuda_core/docs/source/release/1.0.0-notes.rst @@ -13,12 +13,6 @@ Highlights - TBD -New features ------------- - -- TBD - - Breaking changes ---------------- @@ -113,6 +107,18 @@ Breaking changes ``CUgraphConditionalHandle`` value. Previously, ``.handle`` had to be extracted explicitly. +- :meth:`Linker.backend` is now a classmethod instead of an instance property. + Call sites must use ``Linker.backend()`` (with parentheses) instead of + ``linker.backend``. This allows querying the linking backend without + constructing a ``Linker`` instance — for example, to choose between PTX and + LTOIR input before linking. + + +New features +------------ + +- TBD + Fixes and enhancements ----------------------- diff --git a/cuda_core/tests/test_linker.py b/cuda_core/tests/test_linker.py index 0d4ff91dcd..9b3d33e891 100644 --- a/cuda_core/tests/test_linker.py +++ b/cuda_core/tests/test_linker.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 +import inspect + import pytest from cuda.core import Device, Linker, LinkerOptions, Program, ProgramOptions, _linker @@ -92,7 +94,7 @@ def test_linker_init(compile_ptx_functions, options): linker = Linker(*compile_ptx_functions, options=options) object_code = linker.link("cubin") assert isinstance(object_code, ObjectCode) - assert linker.backend == ("driver" if is_culink_backend else "nvJitLink") + assert linker.backend() == ("driver" if is_culink_backend else "nvJitLink") def test_linker_init_invalid_arch(compile_ptx_functions): @@ -242,3 +244,40 @@ def test_linker_options_nvjitlink_options_as_str(): assert f"-arch={ARCH}" in options assert "-g" in options assert "-lineinfo" in options + + +class TestBackendClassmethod: + def test_backend_returns_nvjitlink(self, monkeypatch): + monkeypatch.setattr(_linker, "_use_nvjitlink_backend", True) + assert Linker.backend() == "nvJitLink" + + def test_backend_returns_driver(self, monkeypatch): + monkeypatch.setattr(_linker, "_use_nvjitlink_backend", False) + assert Linker.backend() == "driver" + + def test_backend_invokes_probe_when_not_memoised(self, monkeypatch): + monkeypatch.setattr(_linker, "_use_nvjitlink_backend", None) + called = [] + + def fake_decide(): + called.append(True) + return False # False = not falling back to driver = nvJitLink + + monkeypatch.setattr(_linker, "_decide_nvjitlink_or_driver", fake_decide) + result = Linker.backend() + assert result == "nvJitLink" + assert called, "_decide_nvjitlink_or_driver was not called" + + def test_backend_is_classmethod(self): + attr = inspect.getattr_static(Linker, "backend") + assert isinstance(attr, classmethod) + + def test_backend_is_not_property(self): + """backend is a classmethod, not a property. + + This is an intentional breaking change from the prior property API. + Attribute-style access (``linker.backend``) now returns a bound method, + not a string. All call sites must use parens: ``Linker.backend()``. + """ + attr = inspect.getattr_static(Linker, "backend") + assert not isinstance(attr, property)