Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions cuda_core/cuda/core/_linker.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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"


# =============================================================================
Expand Down
2 changes: 1 addition & 1 deletion cuda_core/cuda/core/_program.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions cuda_core/docs/source/release/0.8.0-notes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.. SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
.. SPDX-License-Identifier: Apache-2.0

.. currentmodule:: cuda.core

``cuda.core`` 0.8.0 Release Notes
=================================


Breaking Changes
----------------

- :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.
2 changes: 1 addition & 1 deletion cuda_core/tests/test_linker.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,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):
Expand Down
57 changes: 57 additions & 0 deletions cuda_core/tests/test_linker_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

"""GPU-free tests for Linker.backend classmethod.

These live in a separate file from test_linker.py because that module calls
Device() at import time, which requires a GPU. These tests use monkeypatch
to set module-level flags and never touch CUDA devices.
"""

import inspect

import cuda.core._linker as _linker
from cuda.core._linker import Linker


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_idempotent(self, monkeypatch):
monkeypatch.setattr(_linker, "_use_nvjitlink_backend", True)
results = [Linker.backend() for _ in range(3)]
assert results == ["nvJitLink", "nvJitLink", "nvJitLink"]

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)
Loading