Skip to content

Commit a18022c

Browse files
[NVVM] Expose nvvm version detection in cuda.bindings.utils. (#1837)
* refactor cuda bindings utils for nvvm * [pre-commit.ci] auto code formatting * refresh * renaming * exceptions * refresh * [pre-commit.ci] auto code formatting * refresh * [pre-commit.ci] auto code formatting * refresh * refresh * refresh * [pre-commit.ci] auto code formatting * refresh * [pre-commit.ci] auto code formatting * refresh --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent b991295 commit a18022c

File tree

4 files changed

+178
-92
lines changed

4 files changed

+178
-92
lines changed

cuda_bindings/cuda/bindings/utils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
33
from typing import Any, Callable
44

5+
from ._nvvm_utils import check_nvvm_compiler_options
56
from ._ptx_utils import get_minimal_required_cuda_ver_from_ptx_ver, get_ptx_ver
67
from ._version_check import warn_if_cuda_major_version_mismatch
78

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
3+
4+
from typing import Sequence
5+
6+
_PRECHECK_NVVM_IR = """target triple = "nvptx64-unknown-cuda"
7+
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-i128:128:128-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64"
8+
9+
define void @dummy_kernel() {{
10+
entry:
11+
ret void
12+
}}
13+
14+
!nvvm.annotations = !{{!0}}
15+
!0 = !{{void ()* @dummy_kernel, !"kernel", i32 1}}
16+
17+
!nvvmir.version = !{{!1}}
18+
!1 = !{{i32 {major}, i32 {minor}, i32 {debug_major}, i32 {debug_minor}}}
19+
"""
20+
21+
22+
def check_nvvm_compiler_options(options: Sequence[str]) -> bool:
23+
"""
24+
Abstracted from https://github.com/NVIDIA/numba-cuda/pull/681
25+
26+
Check if the specified options are supported by the current libNVVM version.
27+
28+
The options are a list of strings, each representing a compiler option.
29+
30+
If the test program fails to compile, the options are not supported and False
31+
is returned.
32+
33+
If the test program compiles successfully, True is returned.
34+
35+
cuda.bindings.nvvm returns exceptions instead of return codes.
36+
37+
Parameters
38+
----------
39+
options : Sequence[str]
40+
List of compiler options as strings (e.g., ["-arch=compute_90", "-g"]).
41+
42+
Returns
43+
-------
44+
bool
45+
True if the options are supported, False otherwise.
46+
47+
Examples
48+
--------
49+
>>> from cuda.bindings.utils import check_nvvm_compiler_options
50+
>>> check_nvvm_compiler_options(["-arch=compute_90", "-g"])
51+
True
52+
"""
53+
try:
54+
from cuda.bindings import nvvm
55+
except ModuleNotFoundError as exc:
56+
if exc.name == "nvvm":
57+
return False
58+
raise
59+
60+
from cuda.bindings._internal.nvvm import _inspect_function_pointer
61+
62+
if _inspect_function_pointer("__nvvmCreateProgram") == 0:
63+
return False
64+
65+
program = nvvm.create_program()
66+
try:
67+
major, minor, debug_major, debug_minor = nvvm.ir_version()
68+
precheck_ir = _PRECHECK_NVVM_IR.format(
69+
major=major,
70+
minor=minor,
71+
debug_major=debug_major,
72+
debug_minor=debug_minor,
73+
)
74+
precheck_ir_bytes = precheck_ir.encode("utf-8")
75+
nvvm.add_module_to_program(
76+
program,
77+
precheck_ir_bytes,
78+
len(precheck_ir_bytes),
79+
"precheck.ll",
80+
)
81+
try:
82+
nvvm.compile_program(program, len(options), options)
83+
except nvvm.nvvmError as e:
84+
if e.status == nvvm.Result.ERROR_INVALID_OPTION:
85+
return False
86+
raise
87+
finally:
88+
nvvm.destroy_program(program)
89+
return True

cuda_bindings/tests/test_utils.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,29 @@
1111

1212
from cuda.bindings import driver, runtime
1313
from cuda.bindings._internal.utils import get_c_compiler
14-
from cuda.bindings.utils import get_cuda_native_handle, get_minimal_required_cuda_ver_from_ptx_ver, get_ptx_ver
14+
from cuda.bindings.utils import (
15+
check_nvvm_compiler_options,
16+
get_cuda_native_handle,
17+
get_minimal_required_cuda_ver_from_ptx_ver,
18+
get_ptx_ver,
19+
)
1520

1621
have_cufile = importlib.util.find_spec("cuda.bindings.cufile") is not None
1722

23+
24+
def _is_libnvvm_available() -> bool:
25+
from cuda.bindings._internal.nvvm import _inspect_function_pointer
26+
from cuda.pathfinder import DynamicLibNotFoundError
27+
28+
try:
29+
return _inspect_function_pointer("__nvvmCreateProgram") != 0
30+
except DynamicLibNotFoundError:
31+
return False
32+
33+
34+
_libnvvm_available = _is_libnvvm_available()
35+
_skip_no_libnvvm = pytest.mark.skipif(not _libnvvm_available, reason="libNVVM not available")
36+
1837
ptx_88_kernel = r"""
1938
.version 8.8
2039
.target sm_75
@@ -118,3 +137,35 @@ def test_get_c_compiler():
118137
c_compiler = get_c_compiler()
119138
prefix = ("GCC", "Clang", "MSVC", "Unknown")
120139
assert sum(c_compiler.startswith(p) for p in prefix) == 1
140+
141+
142+
@_skip_no_libnvvm
143+
def test_check_nvvm_compiler_options_valid():
144+
assert check_nvvm_compiler_options(["-arch=compute_90"]) is True
145+
146+
147+
@_skip_no_libnvvm
148+
def test_check_nvvm_compiler_options_invalid():
149+
assert check_nvvm_compiler_options(["--this-is-not-a-valid-option"]) is False
150+
151+
152+
@_skip_no_libnvvm
153+
def test_check_nvvm_compiler_options_empty():
154+
assert check_nvvm_compiler_options([]) is True
155+
156+
157+
@_skip_no_libnvvm
158+
def test_check_nvvm_compiler_options_multiple_valid():
159+
assert check_nvvm_compiler_options(["-arch=compute_90", "-opt=3", "-g"]) is True
160+
161+
162+
@_skip_no_libnvvm
163+
def test_check_nvvm_compiler_options_arch_detection():
164+
assert check_nvvm_compiler_options(["-arch=compute_90"]) is True
165+
assert check_nvvm_compiler_options(["-arch=compute_99999"]) is False
166+
167+
168+
def test_check_nvvm_compiler_options_no_libnvvm():
169+
if _libnvvm_available:
170+
pytest.skip("libNVVM is available; this test targets the fallback path")
171+
assert check_nvvm_compiler_options(["-arch=compute_90"]) is False

cuda_core/tests/test_program.py

Lines changed: 35 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -70,81 +70,25 @@ def _has_nvrtc_pch_apis_for_tests():
7070
)
7171

7272

73-
_libnvvm_version = None
74-
_libnvvm_version_attempted = False
75-
76-
precheck_nvvm_ir = """target triple = "nvptx64-unknown-cuda"
77-
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-i128:128:128-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64"
78-
79-
define void @dummy_kernel() {{
80-
entry:
81-
ret void
82-
}}
83-
84-
!nvvm.annotations = !{{!0}}
85-
!0 = !{{void ()* @dummy_kernel, !"kernel", i32 1}}
86-
87-
!nvvmir.version = !{{!1}}
88-
!1 = !{{i32 {major}, i32 {minor}, i32 {debug_major}, i32 {debug_minor}}}
89-
"""
90-
91-
92-
def _get_libnvvm_version_for_tests():
93-
"""
94-
Detect libNVVM version by compiling dummy IR and analyzing the PTX output.
95-
96-
Workaround for the lack of direct libNVVM version API (nvbugs 5312315).
97-
The approach:
98-
- Compile a small dummy NVVM IR to PTX
99-
- Use PTX version analysis APIs if available to infer libNVVM version
100-
- Cache the result for future use
101-
"""
102-
global _libnvvm_version, _libnvvm_version_attempted
73+
def _has_check_nvvm_compiler_options():
74+
try:
75+
import cuda.bindings.utils as utils
76+
except ModuleNotFoundError:
77+
return False
78+
return hasattr(utils, "check_nvvm_compiler_options")
10379

104-
if _libnvvm_version_attempted:
105-
return _libnvvm_version
10680

107-
_libnvvm_version_attempted = True
81+
has_nvvm_option_checker = pytest.mark.skipif(
82+
not _has_check_nvvm_compiler_options(),
83+
reason="cuda.bindings.utils.check_nvvm_compiler_options not available (cuda-bindings too old?)",
84+
)
10885

109-
try:
110-
from cuda.core._program import _get_nvvm_module
11186

112-
nvvm = _get_nvvm_module()
113-
114-
try:
115-
from cuda.bindings.utils import get_minimal_required_cuda_ver_from_ptx_ver, get_ptx_ver
116-
except ImportError:
117-
_libnvvm_version = None
118-
return _libnvvm_version
119-
120-
program = nvvm.create_program()
121-
try:
122-
major, minor, debug_major, debug_minor = nvvm.ir_version()
123-
global precheck_nvvm_ir
124-
precheck_nvvm_ir = precheck_nvvm_ir.format(
125-
major=major, minor=minor, debug_major=debug_major, debug_minor=debug_minor
126-
)
127-
precheck_ir_bytes = precheck_nvvm_ir.encode("utf-8")
128-
nvvm.add_module_to_program(program, precheck_ir_bytes, len(precheck_ir_bytes), "precheck.ll")
129-
130-
options = ["-arch=compute_90"]
131-
nvvm.verify_program(program, len(options), options)
132-
nvvm.compile_program(program, len(options), options)
133-
134-
ptx_size = nvvm.get_compiled_result_size(program)
135-
ptx_data = bytearray(ptx_size)
136-
nvvm.get_compiled_result(program, ptx_data)
137-
ptx_str = ptx_data.decode("utf-8")
138-
ptx_version = get_ptx_ver(ptx_str)
139-
cuda_version = get_minimal_required_cuda_ver_from_ptx_ver(ptx_version)
140-
_libnvvm_version = cuda_version
141-
return _libnvvm_version
142-
finally:
143-
nvvm.destroy_program(program)
87+
def _check_nvvm_arch(arch: str) -> bool:
88+
"""Check if the given NVVM arch is supported by the installed libNVVM."""
89+
from cuda.bindings.utils import check_nvvm_compiler_options
14490

145-
except Exception:
146-
_libnvvm_version = None
147-
return _libnvvm_version
91+
return check_nvvm_compiler_options([f"-arch={arch}"])
14892

14993

15094
@pytest.fixture(scope="session")
@@ -524,10 +468,13 @@ def test_nvvm_compile_invalid_ir():
524468
),
525469
pytest.param(
526470
ProgramOptions(name="test_sm110_1", arch="sm_110", device_code_optimize=False),
527-
marks=pytest.mark.skipif(
528-
(_get_libnvvm_version_for_tests() or 0) < 13000,
529-
reason="Compute capability 110 requires libNVVM >= 13.0",
530-
),
471+
marks=[
472+
has_nvvm_option_checker,
473+
pytest.mark.skipif(
474+
_has_check_nvvm_compiler_options() and not _check_nvvm_arch("compute_110"),
475+
reason="Compute capability 110 not supported by installed libNVVM",
476+
),
477+
],
531478
),
532479
pytest.param(
533480
ProgramOptions(
@@ -539,17 +486,23 @@ def test_nvvm_compile_invalid_ir():
539486
fma=True,
540487
device_code_optimize=True,
541488
),
542-
marks=pytest.mark.skipif(
543-
(_get_libnvvm_version_for_tests() or 0) < 13000,
544-
reason="Compute capability 110 requires libNVVM >= 13.0",
545-
),
489+
marks=[
490+
has_nvvm_option_checker,
491+
pytest.mark.skipif(
492+
_has_check_nvvm_compiler_options() and not _check_nvvm_arch("compute_110"),
493+
reason="Compute capability 110 not supported by installed libNVVM",
494+
),
495+
],
546496
),
547497
pytest.param(
548498
ProgramOptions(name="test_sm110_3", arch="sm_110", link_time_optimization=True),
549-
marks=pytest.mark.skipif(
550-
(_get_libnvvm_version_for_tests() or 0) < 13000,
551-
reason="Compute capability 110 requires libNVVM >= 13.0",
552-
),
499+
marks=[
500+
has_nvvm_option_checker,
501+
pytest.mark.skipif(
502+
_has_check_nvvm_compiler_options() and not _check_nvvm_arch("compute_110"),
503+
reason="Compute capability 110 not supported by installed libNVVM",
504+
),
505+
],
553506
),
554507
],
555508
)
@@ -729,12 +682,8 @@ def test_program_options_as_bytes_nvrtc():
729682
"""Test ProgramOptions.as_bytes() for NVRTC backend"""
730683
options = ProgramOptions(arch="sm_80", debug=True, lineinfo=True, ftz=True)
731684
nvrtc_options = options.as_bytes("nvrtc")
732-
733-
# Should return list of bytes
734685
assert isinstance(nvrtc_options, list)
735686
assert all(isinstance(opt, bytes) for opt in nvrtc_options)
736-
737-
# Decode to check content
738687
options_str = [opt.decode() for opt in nvrtc_options]
739688
assert "-arch=sm_80" in options_str
740689
assert "--device-debug" in options_str
@@ -747,12 +696,8 @@ def test_program_options_as_bytes_nvvm():
747696
"""Test ProgramOptions.as_bytes() for NVVM backend"""
748697
options = ProgramOptions(arch="sm_80", debug=True, ftz=True, device_code_optimize=True)
749698
nvvm_options = options.as_bytes("nvvm")
750-
751-
# Should return list of bytes (same as other backends)
752699
assert isinstance(nvvm_options, list)
753700
assert all(isinstance(opt, bytes) for opt in nvvm_options)
754-
755-
# Decode to check content
756701
options_str = [opt.decode() for opt in nvvm_options]
757702
assert "-arch=compute_80" in options_str
758703
assert "-g" in options_str

0 commit comments

Comments
 (0)