Skip to content

fix(core): four LinkerOptions fields that don't behave as documented - #2582

Open
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:linker-options-documented-behaviour
Open

fix(core): four LinkerOptions fields that don't behave as documented#2582
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:linker-options-documented-behaviour

Conversation

@LeSingh1

Copy link
Copy Markdown
Contributor

Four LinkerOptions fields whose behaviour contradicts their own annotation and docstring. Grouped: one class, one file, and the first three are literally the same mistake in the same function.

1. time=Falseenables timing

ifself.timeisnotNone:
options.append("-time")

-time is a valueless nvJitLink switch, so the gate must be truthiness. Every other valueless flag in _prepare_nvjitlink_options gets that right — if self.verbose: (:326), -lto (:328), -ptx (:330), -g (:334), -lineinfo (:336), if self.no_cache is True: (:370). is not None is only correct for the flags that emit an explicit value, e.g. -ftz=true|false (:338).

_prepare_driver_options has the mirror image (:401): a user who explicitly disabled timing gets ValueError: time option is not supported by the driver API.

2. optimize_unused_variables=Falseenables the optimization

Same shape (:358), same valueless switch — and this one silently changes the linked binary: the linker drops device variables the caller asked it to keep. The driver path (:432) also emits a DeprecationWarning for the disabled value.

3. kernels_used / variables_used silently ignore the documented tuple form

ifself.kernels_usedisnotNone:
ifisinstance(self.kernels_used, str):
options.append(f"-kernels-used={self.kernels_used}")
elifisinstance(self.kernels_used, list):
...

Both are annotated and documented str | tuple[str] | list[str]. A tuple matches neither branch, so no option is emitted and nothing is raised — the link keeps every kernel/variable the caller meant to filter out. ptxas_options, eleven lines below (:363), already uses is_sequence().

Worth noting: tests/test_linker.py:83,86already parametrize the tuple form — but only assert that linking succeeds, which it does, with no filtering applied. That is why this has gone unnoticed.

4. LinkerOptions(name=None) raises AttributeError

def__post_init__(self) ->None:
_lazy_init()
self._name=self.name.encode()

name: str | None = "<default linker>". ProgramOptions had the identical bug and was fixed in #2517 by falling back to its documented default; this does the same.

(Observation, deliberately not changed here to keep the diff a straight mirror of #2517: LinkerOptions._name is written on this line and never read — grep -n '\._name\b' over cuda_core/ shows the only readers are ProgramOptions._name in _program.pyx:778,810,978 and the unrelated ObjectCode._name. Happy to drop the dead store in a follow-up if you want it gone.)

Compatibility

All four are error-path or option-emission changes. No configuration that linked correctly before links differently now: True still emits, None still emits nothing, str/list are unchanged. The inputs whose behaviour changes are exactly time=False, optimize_unused_variables=False, tuple sequences, and name=None — each of which is currently doing the opposite of, or nothing about, what the caller asked for.

Tests

Five new cases in cuda_core/tests/test_linker.py, next to the existing _prepare_driver_options unit tests:

  • test_valueless_flags_are_gated_on_truthinessTrue emits, False and unset do not (parametrised over both flags).
  • test_sequence_options_accept_tuples — parametrised over list/tuple × kernels_used/variables_used.
  • test_linker_options_accepts_name_none.
  • test_prepare_driver_options_ignores_disabled_flags — a disabled flag neither raises nor warns on the driver backend.

The existing test_prepare_driver_options_deprecated_warnings / _unsupported_raises parametrisations all use True and are unchanged.

What I ran

Environment: macOS, no CUDA driver and no CUDA toolkit, so cuda.core cannot be built or imported here.

  • Did not run: the new tests or the rest of test_linker.pyLinkerOptions.__post_init__ calls _lazy_init(), which probes nvJitLink, so the class cannot even be constructed without a toolkit.
  • Ran: a reduction of the three gate expressions and __post_init__, verbatim, before and after:
 time=False before -> ['-time'] after -> []
time=True before -> ['-time'] after -> ['-time']
optimize_unused_variables=False before -> ['-optimize-unused-variables'] after -> []
optimize_unused_variables=True before -> ['-optimize-unused-variables'] after -> ['-optimize-unused-variables']
kernels_used=['C','B'] before -> ['-kernels-used=C', '-kernels-used=B'] after -> same
kernels_used=('C','B') before -> [] after -> ['-kernels-used=C', '-kernels-used=B']
kernels_used='A' before -> ['-kernels-used=A'] after -> same
LinkerOptions(name=None) before -> AttributeError after -> ok
  • Ran:python -m py_compile, ruff check, ruff format --check on cuda_core/tests/test_linker.py — clean, no new findings against a main baseline. (ruff cannot parse .pyx, so _linker.pyx was reviewed by hand.)
  • Checked:is_sequence is isinstance(obj, Sequence) (_utils/cuda_utils.pyx:292), so str matches it too — which is why the isinstance(..., str) branch must stay first, exactly as ptxas_options already orders it. is_sequence was already imported and used in _linker.pyx.
  • Checked: no existing test asserts any of the old behaviours — grep for time=, optimize_unused_variables, kernels_used, variables_used, LinkerOptions(name across cuda_core/tests/ shows only True/str/list/tuple inputs and name="ABC".

Refs #2517

1. time=False ENABLES timing.
if self.time is not None:
options.append("-time")
`-time` is a valueless nvJitLink switch, so the gate has to be truthiness.
Every other valueless flag in the same function gets that right (verbose,
-lto, -ptx, -g, -lineinfo, and `no_cache is True`); `is not None` is only
correct for the flags that emit a value, e.g. `-ftz=true|false`. And on the
driver backend the mirror-image gate raises "time option is not supported by
the driver API" for a user who explicitly DISABLED it.
2. optimize_unused_variables=False ENABLES the optimization.
Same shape, same valueless switch. This one silently changes the linked
binary: the linker drops device variables the caller asked to keep. The
driver path also emits a DeprecationWarning for the disabled value.
3. kernels_used / variables_used silently ignore the documented tuple form.
if isinstance(self.kernels_used, str): ...
elif isinstance(self.kernels_used, list): ...
Both are annotated (and documented) `str | tuple[str] | list[str]`. A tuple
matches neither branch, so no option is emitted and nothing is raised -- the
link keeps every kernel/variable the caller meant to filter out.
`ptxas_options`, eleven lines below, already uses `is_sequence()`.
tests/test_linker.py:83,86 already parametrize the tuple form, but only
assert that linking succeeds, which it does -- with no filtering.
4. LinkerOptions(name=None) raises AttributeError.
self._name = self.name.encode()
`name` is annotated `str | None`. ProgramOptions had the identical bug and
was fixed in NVIDIA#2517 by falling back to its documented default; do the same
here.
All four are error-path or option-emission changes; no configuration that
linked correctly before links differently now.
@copy-pr-bot

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actionsgithub-actionsBot added the cuda.core Everything related to the cuda.core module label Aug 9, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda.coreEverything related to the cuda.core module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@LeSingh1