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

@LeSingh1 LeSingh1 commented Aug 9, 2026

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=False enables timing

        if self.time is not None:
            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=False enables 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

        if self.kernels_used is not None:
            if isinstance(self.kernels_used, str):
                options.append(f"-kernels-used={self.kernels_used}")
            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 (:363), already uses is_sequence().

Worth noting: tests/test_linker.py:83,86 already 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-pr-bot Bot commented Aug 9, 2026

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-actions github-actions Bot added the cuda.core Everything related to the cuda.core module label Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda.core Everything related to the cuda.core module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant