Skip to content

Make check_nvvm_compiler_options return False when nvvm is unavailable - #2541

Open
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:nvvm-optional-import
Open

Make check_nvvm_compiler_options return False when nvvm is unavailable#2541
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:nvvm-optional-import

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

check_nvvm_compiler_options() has two guards meant to answer "options not supported"
rather than fail when nvvm is unavailable. Neither one works.

1. The optional import is not optional

    try:
        from cuda.bindings import nvvm
    except ModuleNotFoundError as exc:
        if exc.name == "nvvm":
            return False
        raise

Unreachable, for two independent reasons:

  1. from <pkg> import <sub> does not raise ModuleNotFoundError when the submodule
    is missing. importlib._bootstrap._handle_fromlist() swallows it and the IMPORT_FROM
    opcode raises a plain ImportError — a different exception type, so the handler never
    runs.
  2. ModuleNotFoundError.name is always the fully qualified name. Even where one is
    raised it is "cuda.bindings.nvvm", never "nvvm".

Reproduced in a source tree where the nvvm extension has not been built:

>>> from cuda.bindings.utils import check_nvvm_compiler_options
>>> check_nvvm_compiler_options(["-arch=compute_90"])
ImportError: cannot import name 'nvvm' from 'cuda.bindings' (.../cuda/bindings/__init__.py)

Fix: import through importlib.import_module("cuda.bindings.nvvm"), which does raise
ModuleNotFoundError with name == "cuda.bindings.nvvm", and compare against the fully
qualified name. Same shape as cuda.pathfinder._optional_cuda_import(), which already
draws the "is the target module itself missing, or one of its dependencies?" distinction
this way. The re-raise for a missing dependency is preserved — that half of the original
intent was already correct.

2. The libNVVM probe only covers half the failure

    if _inspect_function_pointer("__nvvmCreateProgram") == 0:
        return False

A zero pointer means "libNVVM is loaded but does not export the symbol".
_inspect_function_pointer() loads libNVVM lazily
(_inspect_function_pointers()_check_or_init_nvvm()load_library()
load_nvidia_dynamic_lib("nvvm")), so when libNVVM is not installed at all the call
raises DynamicLibNotFoundError instead of returning 0.

tests/test_utils.py already knows this — its _is_libnvvm_available() helper wraps the
identical call in except DynamicLibNotFoundError. But check_nvvm_compiler_options()
does not, so it propagates the exception.

This is exactly what the existing test_check_nvvm_compiler_options_no_libnvvm asserts:

def test_check_nvvm_compiler_options_no_libnvvm():
    if _libnvvm_available:
        pytest.skip("libNVVM is available; this test targets the fallback path")
    assert check_nvvm_compiler_options(["-arch=compute_90"]) is False

On a machine without libNVVM that test errors instead of passing. It is on the
always-skipped list in #2077, which is why it has never been seen to fail. I did not touch
that test — this PR makes the code satisfy it.

Fix: catch DynamicLibNotFoundError around the probe and return False.

Tests

Three added to cuda_bindings/tests/test_utils.py. All three simulate the failure
conditions, so they run in CI on a normal machine with everything built — no GPU, no
unbuilt tree, no libNVVM-less host required:

  • test_check_nvvm_compiler_options_without_the_nvvm_binding — hides cuda.bindings.nvvm
    behind a sys.meta_path finder (plus the parent-package attribute and the sys.modules
    entry, which both short-circuit the import system). Fails on main.
  • test_check_nvvm_compiler_options_without_libnvvm — substitutes an
    _inspect_function_pointer that raises DynamicLibNotFoundError, i.e. the real
    no-libNVVM condition. Fails on main.
  • test_check_nvvm_compiler_options_does_not_mask_a_missing_dependency — passes on main
    too, on purpose: it pins down that widening the guards did not start swallowing real
    problems.

Verified against upstream/main: the first two fail, the third passes; all three pass with
the change. ruff check and ruff format --check clean.

@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.bindings Everything related to the cuda.bindings module label Aug 9, 2026
check_nvvm_compiler_options() has two guards meant to answer "options not
supported" instead of failing when nvvm is missing. Neither one works.

1. The optional import.

       try:
           from cuda.bindings import nvvm
       except ModuleNotFoundError as exc:
           if exc.name == "nvvm":
               return False
           raise

   `from <pkg> import <sub>` never raises ModuleNotFoundError for a missing
   submodule: importlib swallows it in _handle_fromlist() and the IMPORT_FROM
   opcode raises a plain ImportError. And ModuleNotFoundError.name is always
   the fully qualified name, so even where one is raised it is
   "cuda.bindings.nvvm", never "nvvm". In a tree where the nvvm extension has
   not been built, the public API therefore raises

       ImportError: cannot import name 'nvvm' from 'cuda.bindings'

   Import via importlib.import_module(), which does raise
   ModuleNotFoundError with name == "cuda.bindings.nvvm", and compare against
   that. This mirrors cuda.pathfinder._optional_cuda_import, which already
   uses the fully qualified name for the same "is the target module itself
   missing, or one of its dependencies?" distinction. A missing dependency
   still propagates, unchanged.

2. The libNVVM probe.

       if _inspect_function_pointer("__nvvmCreateProgram") == 0:
           return False

   A zero pointer only covers "libNVVM is loaded but does not export the
   symbol". _inspect_function_pointer() loads libNVVM lazily, so when it is
   not installed at all the call raises DynamicLibNotFoundError instead of
   returning 0. tests/test_utils.py already knows this: its
   _is_libnvvm_available() helper wraps the identical call in
   `except DynamicLibNotFoundError`. Catch it here too.

   This is what test_check_nvvm_compiler_options_no_libnvvm asserts, and that
   test errors out on a machine without libNVVM today. It is on the
   always-skipped list in NVIDIA#2077, which is why nobody has seen it fail.

Adds three tests that simulate both conditions without needing an unbuilt
tree or a machine without libNVVM, so they run in CI.
@LeSingh1
LeSingh1 force-pushed the nvvm-optional-import branch from 85580d8 to a8ebc34 Compare August 9, 2026 01:42
@LeSingh1 LeSingh1 changed the title Make the optional nvvm import in check_nvvm_compiler_options actually optional Make check_nvvm_compiler_options return False when nvvm is unavailable Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda.bindings Everything related to the cuda.bindings module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant