Coverage for cuda/core/_linker.pyx: 80.05%
391 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
4"""Linking machinery for combining object codes.
6This module provides :class:`Linker` for linking one or more
7:class:`~cuda.core.ObjectCode` objects, with :class:`LinkerOptions` for
8configuration.
9"""
11from __future__ import annotations
13from cpython.bytearray cimport PyByteArray_AS_STRING
14from libc.stdint cimport intptr_t, uint32_t
15from libcpp.vector cimport vector
16from cuda.bindings cimport cydriver
17from cuda.bindings cimport cynvjitlink
19from ._resource_handles cimport (
20 as_cu,
21 as_py,
22 create_culink_handle,
23 create_nvjitlink_handle,
24)
25from cuda.core._utils.cuda_utils cimport HANDLE_RETURN, HANDLE_RETURN_NVJITLINK
27import sys
28from dataclasses import dataclass
29from typing import TYPE_CHECKING, Union
30from warnings import warn
32from cuda.pathfinder import DynamicLibNotFoundError
33from cuda.pathfinder._optional_cuda_import import _optional_cuda_import
34from cuda.core._device import Device
35from cuda.core._module import ObjectCode
36from cuda.core._utils.clear_error_support import assert_type
37from cuda.core._utils.cuda_utils import (
38 CUDAError,
39 check_or_create_options,
40 driver,
41 is_sequence,
42)
43from cuda.core.typing import CompilerBackendType, ObjectCodeFormatType
45if TYPE_CHECKING:
46 import cuda.bindings.driver # no-cython-lint
47 import cuda.bindings.nvjitlink # no-cython-lint
49# Module-level annotations to ensure stubgen-pyx keeps the above imports in
50# the generated `.pyi` so that the LinkerHandleT forward references resolve.
51# These names are not assigned, so they only affect __annotations__.
52_keep_driver_in_stub: "cuda.bindings.driver.CUlinkState"
53_keep_nvjitlink_in_stub: "cuda.bindings.nvjitlink.nvJitLinkHandle"
55ctypedef const char* const_char_ptr
57__all__ = ["Linker", "LinkerOptions"]
59LinkerHandleT = Union["cuda.bindings.nvjitlink.nvJitLinkHandle", "cuda.bindings.driver.CUlinkState"]
62# =============================================================================
63# Principal class
64# =============================================================================
67cdef inline int Linker_check_open(Linker self) except -1:
68 if self.is_closed: 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFmnocadefghbi
69 raise RuntimeError("Linker has been closed") 1j
70 return 0 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFmnocadefghbi
73cdef class Linker:
74 """Represent a linking machinery to link one or more object codes into
75 :class:`~cuda.core.ObjectCode`.
77 This object provides a unified interface to multiple underlying
78 linker libraries (such as nvJitLink or cuLink* from the CUDA driver).
80 Parameters
81 ----------
82 object_codes : :class:`~cuda.core.ObjectCode`
83 One or more ObjectCode objects to be linked.
84 options : :class:`LinkerOptions`, optional
85 Options for the linker. If not provided, default options will be used.
86 """
88 def __init__(self, *object_codes: ObjectCode, options: LinkerOptions | None = None):
89 Linker_init(self, object_codes, options) 1j@TuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
91 @property
92 def is_closed(self) -> bool:
93 """Whether this linker has been closed."""
94 if self._use_nvjitlink: 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFmnocadefghbi
95 return self._nvjitlink_handle.get() == NULL 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFmnocadefghbi
96 return self._culink_handle.get() == NULL
98 def link(self, target_type: ObjectCodeFormatType | str) -> ObjectCode:
99 """Link the provided object codes into a single output of the specified target type.
101 Parameters
102 ----------
103 target_type : ObjectCodeFormatType | str
104 The type of the target output. Must be either "cubin" or "ptx".
106 Returns
107 -------
108 :class:`~cuda.core.ObjectCode`
109 The linked object code of the specified target type.
111 .. note::
113 Ensure that input object codes were compiled with appropriate
114 flags for linking (e.g., relocatable device code enabled).
115 """
116 Linker_check_open(self) 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFmnocadefghbi
117 return Linker_link(self, str(target_type)) 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFmnocadefghbi
119 def get_error_log(self) -> str:
120 """Get the error log generated by the linker.
122 Returns
123 -------
124 str
125 The error log.
126 """
127 # After link(), the decoded log is cached here.
128 if self._error_log is not None: 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
129 return self._error_log 1jt
130 Linker_check_open(self) 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
131 cdef cynvjitlink.nvJitLinkHandle c_h
132 cdef size_t c_log_size = 0 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
133 cdef char* c_log_ptr
134 if self._use_nvjitlink: 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
135 c_h = as_cu(self._nvjitlink_handle) 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
136 HANDLE_RETURN_NVJITLINK(c_h, cynvjitlink.nvJitLinkGetErrorLogSize(c_h, &c_log_size)) 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
137 log = bytearray(c_log_size) 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
138 if c_log_size > 0: 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
139 c_log_ptr = <char*>(<bytearray>log) 1T
140 HANDLE_RETURN_NVJITLINK(c_h, cynvjitlink.nvJitLinkGetErrorLog(c_h, c_log_ptr)) 1T
141 return log.decode("utf-8", errors="backslashreplace") 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
142 else:
143 return (<bytearray>self._drv_log_bufs[2]).decode(
144 "utf-8", errors="backslashreplace").rstrip('\x00')
146 def get_info_log(self) -> str:
147 """Get the info log generated by the linker.
149 Returns
150 -------
151 str
152 The info log.
153 """
154 # After link(), the decoded log is cached here.
155 if self._info_log is not None: 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
156 return self._info_log 1jut
157 Linker_check_open(self) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
158 cdef cynvjitlink.nvJitLinkHandle c_h
159 cdef size_t c_log_size = 0 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
160 cdef char* c_log_ptr
161 if self._use_nvjitlink: 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
162 c_h = as_cu(self._nvjitlink_handle) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
163 HANDLE_RETURN_NVJITLINK(c_h, cynvjitlink.nvJitLinkGetInfoLogSize(c_h, &c_log_size)) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
164 log = bytearray(c_log_size) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
165 if c_log_size > 0: 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
166 c_log_ptr = <char*>(<bytearray>log) 1vwxpklab
167 HANDLE_RETURN_NVJITLINK(c_h, cynvjitlink.nvJitLinkGetInfoLog(c_h, c_log_ptr)) 1vwxpklab
168 return log.decode("utf-8", errors="backslashreplace") 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
169 else:
170 return (<bytearray>self._drv_log_bufs[0]).decode(
171 "utf-8", errors="backslashreplace").rstrip('\x00')
173 def close(self) -> None:
174 """Destroy this linker."""
175 cdef vector[cydriver.CUjit_option] empty_keys
176 cdef vector[void*] empty_values
177 if self._use_nvjitlink: 1jVcadefghbi
178 self._nvjitlink_handle.reset() 1jVcadefghbi
179 else:
180 if self._drv_log_bufs is not None:
181 if self._info_log is None:
182 self._info_log = self.get_info_log()
183 if self._error_log is None:
184 self._error_log = self.get_error_log()
185 # Destroy the CUlinkState before releasing storage referenced by it.
186 self._culink_handle.reset()
187 self._drv_jit_keys.swap(empty_keys)
188 self._drv_jit_values.swap(empty_values)
189 self._drv_log_bufs = None
191 @property
192 def handle(self) -> LinkerHandleT:
193 """Return the underlying handle object.
195 .. note::
197 The type of the returned object depends on the backend.
199 .. caution::
201 This handle is a Python object. To get the memory address of the underlying C
202 handle, call ``int(Linker.handle)``.
203 """
204 if self._use_nvjitlink: 1WV
205 return as_py(self._nvjitlink_handle) 1WV
206 else:
207 return as_py(self._culink_handle)
209 @classmethod
210 def which_backend(cls) -> CompilerBackendType:
211 """Return which linking backend will be used.
213 Returns :attr:`~CompilerBackendType.NVJITLINK` when the nvJitLink
214 library is available and meets the minimum version requirement,
215 otherwise :attr:`~CompilerBackendType.DRIVER`.
217 .. note::
219 Prefer letting :class:`Linker` decide. Query ``which_backend()``
220 only when you need to dispatch based on input format (for
221 example: choose PTX vs. LTOIR before constructing a
222 ``Linker``). The returned value names an implementation
223 detail whose support matrix may shift across CTK releases.
224 """
225 return CompilerBackendType.DRIVER if _decide_nvjitlink_or_driver() else CompilerBackendType.NVJITLINK 2+ * vb[ ] R v G H w I x J p k q K L M N O P y r z A s B Q V m n o c a d e f g h b i
228# =============================================================================
229# Supporting classes
230# =============================================================================
232@dataclass
233class LinkerOptions:
234 """Customizable options for configuring :class:`Linker`.
236 Since the linker may choose to use nvJitLink or the driver APIs as the linking backend,
237 not all options are applicable. When the system's installed nvJitLink is too old (<12.3),
238 or not installed, the driver APIs (cuLink) will be used instead.
240 Attributes
241 ----------
242 name : str, optional
243 Name of the linker. If the linking succeeds, the name is passed down to the generated :class:`ObjectCode`.
244 arch : str, optional
245 Pass the SM architecture value, such as ``sm_<CC>`` (for generating CUBIN) or
246 ``compute_<CC>`` (for generating PTX). If not provided, the current device's architecture
247 will be used.
248 max_register_count : int, optional
249 Maximum register count.
250 time : bool, optional
251 Print timing information to the info log.
252 Default: False.
253 verbose : bool, optional
254 Print verbose messages to the info log.
255 Default: False.
256 link_time_optimization : bool, optional
257 Perform link time optimization.
258 Default: False.
259 ptx : bool, optional
260 Emit PTX after linking instead of CUBIN; only supported with ``link_time_optimization=True``.
261 Default: False.
262 optimization_level : int, optional
263 Set optimization level. Only 0 and 3 are accepted.
264 debug : bool, optional
265 Generate debug information.
266 Default: False.
267 lineinfo : bool, optional
268 Generate line information.
269 Default: False.
270 ftz : bool, optional
271 Flush denormal values to zero.
272 Default: False.
273 prec_div : bool, optional
274 Use precise division.
275 Default: True.
276 prec_sqrt : bool, optional
277 Use precise square root.
278 Default: True.
279 fma : bool, optional
280 Use fast multiply-add.
281 Default: True.
282 kernels_used : [str | tuple[str] | list[str]], optional
283 Pass a kernel or sequence of kernels that are used; any not in the list can be removed.
284 variables_used : [str | tuple[str] | list[str]], optional
285 Pass a variable or sequence of variables that are used; any not in the list can be removed.
286 optimize_unused_variables : bool, optional
287 Assume that if a variable is not referenced in device code, it can be removed.
288 Default: False.
289 ptxas_options : [str | tuple[str] | list[str]], optional
290 Pass options to PTXAS.
291 split_compile : int, optional
292 Split compilation maximum thread count. Use 0 to use all available processors. Value of 1 disables split
293 compilation (default).
294 Default: 1.
295 split_compile_extended : int, optional
296 A more aggressive form of split compilation available in LTO mode only. Accepts a maximum thread count value.
297 Use 0 to use all available processors. Value of 1 disables extended split compilation (default). Note: This
298 option can potentially impact performance of the compiled binary.
299 Default: 1.
300 no_cache : bool, optional
301 Do not cache the intermediate steps of nvJitLink.
302 Default: False.
303 numba_debug : bool, optional
304 Non-functional. ``numba_debug`` is an NVVM/NVRTC *compiler* option;
305 neither nvJitLink nor the driver's cuLink API recognizes it, so no
306 linking backend can honor it and the value is ignored.
307 Default: None.
309 .. deprecated:: 1.2.0
310 Setting this option emits a :class:`DeprecationWarning`. It has never
311 had an effect on any linking backend and will be removed in
312 ``cuda.core`` 2.0.0. Use
313 :attr:`ProgramOptions.numba_debug` on an NVVM or NVRTC compilation
314 path instead.
315 """
317 name: str | None = "<default linker>"
318 arch: str | None = None
319 max_register_count: int | None = None
320 time: bool | None = None
321 verbose: bool | None = None
322 link_time_optimization: bool | None = None
323 ptx: bool | None = None
324 optimization_level: int | None = None
325 debug: bool | None = None
326 lineinfo: bool | None = None
327 ftz: bool | None = None
328 prec_div: bool | None = None
329 prec_sqrt: bool | None = None
330 fma: bool | None = None
331 kernels_used: str | tuple[str] | list[str] | None = None
332 variables_used: str | tuple[str] | list[str] | None = None
333 optimize_unused_variables: bool | None = None
334 ptxas_options: str | tuple[str] | list[str] | None = None
335 split_compile: int | None = None
336 split_compile_extended: int | None = None
337 no_cache: bool | None = None
338 numba_debug: bool | None = None
340 def __post_init__(self) -> None:
341 _lazy_init() 1S:jTuWYCUDlt=?0%Z'!#X1234567)($98EFVmnocadefghbi
342 self._name = self.name.encode() 1S:jTuWYCUDlt=?0%Z'!#X1234567)($98EFVmnocadefghbi
343 # No linking backend reads ``numba_debug``, so warn where the value is
344 # supplied rather than in the option builders -- the user learns once,
345 # at the call site that set it, instead of once per link. The gate is
346 # ``is not None`` (unlike the ignore-warning on the PTX compile path):
347 # it is the *field* that is going away, so any explicit value earns the
348 # notice, including ``False``.
349 if self.numba_debug is not None: 1S:jTuWYCUDlt=?0%Z'!#X1234567)($98EFVmnocadefghbi
350 warn( 1Z!#
351 "numba_debug is not supported by any linking backend and is ignored. "
352 "LinkerOptions.numba_debug is deprecated and will be removed in "
353 "cuda.core 2.0.0; use ProgramOptions.numba_debug on an NVVM or NVRTC "
354 "compilation path instead.",
355 DeprecationWarning, 1Z!#
356 stacklevel=3,
357 )
359 def _prepare_nvjitlink_options(self, as_bytes: bool = False) -> list[bytes] | list[str]:
360 options = [] 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
362 if self.arch is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
363 options.append(f"-arch={self.arch}") 1jTuWvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
364 else:
365 options.append("-arch=sm_" + "".join(f"{i}" for i in Device().compute_capability)) 1R
366 if self.max_register_count is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
367 options.append(f"-maxrregcount={self.max_register_count}") 1G0c
368 if self.time is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
369 options.append("-time") 1xb
370 if self.verbose: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
371 options.append("-verbose") 1v
372 if self.link_time_optimization: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
373 options.append("-lto") 1l
374 if self.ptx: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
375 options.append("-ptx") 1Yl
376 if self.optimization_level is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
377 options.append(f"-O{self.optimization_level}") 1H
378 if self.debug: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
379 options.append("-g") 1w0%'!#a
380 if self.lineinfo: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
381 options.append("-lineinfo") 1I%d
382 if self.ftz is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
383 options.append(f"-ftz={'true' if self.ftz else 'false'}") 1M0e
384 if self.prec_div is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
385 options.append(f"-prec-div={'true' if self.prec_div else 'false'}") 1Nf
386 if self.prec_sqrt is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
387 options.append(f"-prec-sqrt={'true' if self.prec_sqrt else 'false'}") 1Og
388 if self.fma is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
389 options.append(f"-fma={'true' if self.fma else 'false'}") 1Ph
390 if self.kernels_used is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
391 if isinstance(self.kernels_used, str): 1yrz
392 options.append(f"-kernels-used={self.kernels_used}") 1y
393 elif isinstance(self.kernels_used, list): 1rz
394 for kernel in self.kernels_used: 1r
395 options.append(f"-kernels-used={kernel}") 1r
396 if self.variables_used is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
397 if isinstance(self.variables_used, str): 1AsB
398 options.append(f"-variables-used={self.variables_used}") 1A
399 elif isinstance(self.variables_used, list): 1sB
400 for variable in self.variables_used: 1s
401 options.append(f"-variables-used={variable}") 1s
402 if self.optimize_unused_variables is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
403 options.append("-optimize-unused-variables") 1J
404 if self.ptxas_options is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
405 if isinstance(self.ptxas_options, str): 1pkq
406 options.append(f"-Xptxas={self.ptxas_options}") 1p
407 elif is_sequence(self.ptxas_options): 1kq
408 for opt in self.ptxas_options: 1kq
409 options.append(f"-Xptxas={opt}") 1kq
410 if self.split_compile is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
411 options.append(f"-split-compile={self.split_compile}") 1Ki
412 if self.split_compile_extended is not None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
413 options.append(f"-split-compile-extended={self.split_compile_extended}") 1L
414 if self.no_cache is True: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
415 options.append("-no-cache") 1Q
417 if as_bytes: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0%'!#EFVmnocadefghbi
418 return [o.encode() for o in options] 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDlt0'!#EFVmnocadefghbi
419 else:
420 return options 1%
422 def _prepare_driver_options(self) -> tuple[list[object], list[object]]:
423 formatted_options = [] 1ZX1234567)($98
424 option_keys = [] 1ZX1234567)($98
426 # allocate a fixed-sized buffer for each info/error log
427 size = 4194304 1ZX1234567)($98
428 formatted_options.extend((bytearray(size), size, bytearray(size), size)) 1ZX1234567)($98
429 option_keys.extend( 1ZX1234567)($98
430 (
431 _driver.CUjit_option.CU_JIT_INFO_LOG_BUFFER, 1ZX1234567)($98
432 _driver.CUjit_option.CU_JIT_INFO_LOG_BUFFER_SIZE_BYTES, 1ZX1234567)($98
433 _driver.CUjit_option.CU_JIT_ERROR_LOG_BUFFER, 1ZX1234567)($98
434 _driver.CUjit_option.CU_JIT_ERROR_LOG_BUFFER_SIZE_BYTES, 1ZX1234567)($98
435 )
436 )
438 if self.arch is not None: 1ZX1234567)($98
439 arch = self.arch.split("_")[-1].upper() 1ZX
440 formatted_options.append(getattr(_driver.CUjit_target, f"CU_TARGET_COMPUTE_{arch}")) 1ZX
441 option_keys.append(_driver.CUjit_option.CU_JIT_TARGET) 1ZX
442 if self.max_register_count is not None: 1ZX1234567)($98
443 formatted_options.append(self.max_register_count) 1X
444 option_keys.append(_driver.CUjit_option.CU_JIT_MAX_REGISTERS) 1X
445 if self.time is not None: 1ZX1234567)($98
446 raise ValueError("time option is not supported by the driver API") 1)
447 if self.verbose: 1ZX1234567($98
448 formatted_options.append(1) 1X
449 option_keys.append(_driver.CUjit_option.CU_JIT_LOG_VERBOSE) 1X
450 if self.link_time_optimization: 1ZX1234567($98
451 formatted_options.append(1) 1X
452 option_keys.append(_driver.CUjit_option.CU_JIT_LTO) 1X
453 if self.ptx: 1ZX1234567($98
454 raise ValueError("ptx option is not supported by the driver API") 1(
455 if self.optimization_level is not None: 1ZX1234567$98
456 formatted_options.append(self.optimization_level) 1X
457 option_keys.append(_driver.CUjit_option.CU_JIT_OPTIMIZATION_LEVEL) 1X
458 if self.debug: 1ZX1234567$98
459 formatted_options.append(1) 1X
460 option_keys.append(_driver.CUjit_option.CU_JIT_GENERATE_DEBUG_INFO) 1X
461 if self.lineinfo: 1ZX1234567$98
462 formatted_options.append(1) 1X
463 option_keys.append(_driver.CUjit_option.CU_JIT_GENERATE_LINE_INFO) 1X
464 if self.ftz is not None: 1ZX1234567$98
465 warn("ftz option is deprecated in the driver API", DeprecationWarning, stacklevel=3) 11
466 if self.prec_div is not None: 1ZX1234567$98
467 warn("prec_div option is deprecated in the driver API", DeprecationWarning, stacklevel=3) 12
468 if self.prec_sqrt is not None: 1ZX1234567$98
469 warn("prec_sqrt option is deprecated in the driver API", DeprecationWarning, stacklevel=3) 13
470 if self.fma is not None: 1ZX1234567$98
471 warn("fma options is deprecated in the driver API", DeprecationWarning, stacklevel=3) 14
472 if self.kernels_used is not None: 1ZX1234567$98
473 warn("kernels_used is deprecated in the driver API", DeprecationWarning, stacklevel=3) 15
474 if self.variables_used is not None: 1ZX1234567$98
475 warn("variables_used is deprecated in the driver API", DeprecationWarning, stacklevel=3) 16
476 if self.optimize_unused_variables is not None: 1ZX1234567$98
477 warn("optimize_unused_variables is deprecated in the driver API", DeprecationWarning, stacklevel=3) 17
478 if self.ptxas_options is not None: 1ZX1234567$98
479 raise ValueError("ptxas_options option is not supported by the driver API") 1$
480 if self.split_compile is not None: 1ZX123456798
481 raise ValueError("split_compile option is not supported by the driver API") 19
482 if self.split_compile_extended is not None: 1ZX12345678
483 raise ValueError("split_compile_extended option is not supported by the driver API") 18
484 if self.no_cache is True: 1ZX1234567
485 formatted_options.append(_driver.CUjit_cacheMode.CU_JIT_CACHE_OPTION_NONE) 1X
486 option_keys.append(_driver.CUjit_option.CU_JIT_CACHE_MODE) 1X
488 return formatted_options, option_keys 1ZX1234567
490 def as_bytes(self, backend: str = "nvjitlink") -> list[bytes]:
491 """Convert linker options to bytes format for the nvjitlink backend.
493 Parameters
494 ----------
495 backend : str, optional
496 The linker backend. Only "nvjitlink" is supported. Default is "nvjitlink".
498 Returns
499 -------
500 list[bytes]
501 List of option strings encoded as bytes.
503 Raises
504 ------
505 ValueError
506 If an unsupported backend is specified.
507 RuntimeError
508 If nvJitLink backend is not available.
509 """
510 backend = backend.lower() 1:=?0
511 if backend != "nvjitlink": 1:=?0
512 raise ValueError(f"as_bytes() only supports 'nvjitlink' backend, got '{backend}'") 1=?
513 if not _use_nvjitlink_backend: 1:0
514 raise RuntimeError("nvJitLink backend is not available") 1:
515 return self._prepare_nvjitlink_options(as_bytes=True) 10
518# =============================================================================
519# Private implementation: cdef inline helpers
520# =============================================================================
522cdef inline int Linker_init(Linker self, tuple object_codes, object options) except -1:
523 """Initialize a Linker instance."""
524 if len(object_codes) == 0: 1j@TuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
525 raise ValueError("At least one ObjectCode object must be provided") 1@
527 cdef cynvjitlink.nvJitLinkHandle c_raw_nvjitlink
528 cdef cydriver.CUlinkState c_raw_culink
529 cdef Py_ssize_t c_num_opts, i
530 cdef vector[const_char_ptr] c_str_opts
531 cdef cydriver.CUjit_option* c_drv_jit_keys_ptr
532 cdef void** c_drv_jit_values_ptr
534 self._options = options = check_or_create_options(LinkerOptions, options, "Linker options") 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
536 if _use_nvjitlink_backend: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
537 self._use_nvjitlink = True 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
538 options_bytes = options._prepare_nvjitlink_options(as_bytes=True) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
539 c_num_opts = len(options_bytes) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
540 c_str_opts.resize(c_num_opts) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
541 for i in range(c_num_opts): 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
542 c_str_opts[i] = <const char*>(<bytes>options_bytes[i]) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
543 with nogil: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
544 HANDLE_RETURN_NVJITLINK(NULL, cynvjitlink.nvJitLinkCreate( 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQYCUDltEFVmnocadefghbi
545 &c_raw_nvjitlink, <uint32_t>c_num_opts, c_str_opts.data()))
546 self._nvjitlink_handle = create_nvjitlink_handle(c_raw_nvjitlink) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
547 else:
548 self._use_nvjitlink = False
549 formatted_options, option_keys = options._prepare_driver_options()
550 # Keep the formatted_options list alive: it contains bytearrays that
551 # the driver writes into via raw pointers during linking operations.
552 self._drv_log_bufs = formatted_options
553 c_num_opts = len(option_keys)
554 self._drv_jit_keys.resize(c_num_opts)
555 self._drv_jit_values.resize(c_num_opts)
556 for i in range(c_num_opts):
557 self._drv_jit_keys[i] = <cydriver.CUjit_option><int>option_keys[i]
558 val = formatted_options[i]
559 if isinstance(val, bytearray):
560 self._drv_jit_values[i] = <void*>PyByteArray_AS_STRING(val)
561 else:
562 self._drv_jit_values[i] = <void*><intptr_t>int(val)
563 c_drv_jit_keys_ptr = self._drv_jit_keys.data()
564 c_drv_jit_values_ptr = self._drv_jit_values.data()
565 try:
566 with nogil:
567 HANDLE_RETURN(cydriver.cuLinkCreate(
568 <unsigned int>c_num_opts,
569 c_drv_jit_keys_ptr,
570 c_drv_jit_values_ptr,
571 &c_raw_culink))
572 except CUDAError as e:
573 Linker_annotate_error_log(self, e)
574 raise
575 self._culink_handle = create_culink_handle(c_raw_culink)
577 for code in object_codes: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
578 assert_type(code, ObjectCode) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
579 Linker_add_code_object(self, code) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
580 return 0 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
583cdef inline void Linker_add_code_object(Linker self, object object_code) except *:
584 """Add a single ObjectCode to the linker."""
585 data = object_code.code 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
586 cdef cynvjitlink.nvJitLinkHandle c_nvjitlink_h
587 cdef cydriver.CUlinkState c_culink_state
588 cdef cynvjitlink.nvJitLinkInputType c_nv_input_type
589 cdef cydriver.CUjitInputType c_drv_input_type
590 cdef const char* c_data_ptr
591 cdef size_t c_data_size
592 cdef const char* c_file_ptr
594 name_bytes = f"{object_code.name}".encode() 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
595 cdef const char* c_name_ptr = <const char*>name_bytes 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
597 input_types = _nvjitlink_input_types if self._use_nvjitlink else _driver_input_types 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
598 py_input_type = input_types.get(object_code.code_type) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
599 if py_input_type is None: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
600 raise ValueError(f"Unknown code_type associated with ObjectCode: {object_code.code_type}")
602 if self._use_nvjitlink: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
603 c_nvjitlink_h = as_cu(self._nvjitlink_handle) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
604 c_nv_input_type = <cynvjitlink.nvJitLinkInputType><int>py_input_type 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
605 if isinstance(data, bytes): 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
606 c_data_ptr = <const char*>(<bytes>data) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
607 c_data_size = len(data) 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
608 with nogil: 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
609 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h, cynvjitlink.nvJitLinkAddData( 1jTuWRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFVmnocadefghbi
610 c_nvjitlink_h, c_nv_input_type, <const void*>c_data_ptr, c_data_size, c_name_ptr))
611 elif isinstance(data, str):
612 file_bytes = data.encode()
613 c_file_ptr = <const char*>file_bytes
614 with nogil:
615 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h, cynvjitlink.nvJitLinkAddFile(
616 c_nvjitlink_h, c_nv_input_type, c_file_ptr))
617 else:
618 raise TypeError(f"Expected bytes or str, but got {type(data).__name__}")
619 else:
620 c_culink_state = as_cu(self._culink_handle)
621 c_drv_input_type = <cydriver.CUjitInputType><int>py_input_type
622 try:
623 if isinstance(data, bytes):
624 c_data_ptr = <const char*>(<bytes>data)
625 c_data_size = len(data)
626 with nogil:
627 HANDLE_RETURN(cydriver.cuLinkAddData(
628 c_culink_state, c_drv_input_type, <void*>c_data_ptr, c_data_size, c_name_ptr,
629 0, NULL, NULL))
630 elif isinstance(data, str):
631 file_bytes = data.encode()
632 c_file_ptr = <const char*>file_bytes
633 with nogil:
634 HANDLE_RETURN(cydriver.cuLinkAddFile(
635 c_culink_state, c_drv_input_type, c_file_ptr, 0, NULL, NULL))
636 else:
637 raise TypeError(f"Expected bytes or str, but got {type(data).__name__}")
638 except CUDAError as e:
639 Linker_annotate_error_log(self, e)
640 raise
643cdef inline object Linker_link(Linker self, str target_type):
644 """Complete linking and return the result as ObjectCode."""
645 if target_type not in ("cubin", "ptx"): 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCUDltEFmnocadefghbi
646 raise ValueError(f"Unsupported target type: {target_type}") 1U
648 cdef cynvjitlink.nvJitLinkHandle c_nvjitlink_h
649 cdef cydriver.CUlinkState c_culink_state
650 cdef size_t c_output_size = 0 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
651 cdef char* c_code_ptr
652 cdef void* c_cubin_out = NULL 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
654 if self._use_nvjitlink: 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
655 c_nvjitlink_h = as_cu(self._nvjitlink_handle) 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
656 with nogil: 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
657 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h, cynvjitlink.nvJitLinkComplete(c_nvjitlink_h)) 1jTuRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
658 if target_type == "cubin": 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
659 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h, 1juRvGHwIxJpkqKLMNOPyrzAsBQCDtEFmnocadefghbi
660 cynvjitlink.nvJitLinkGetLinkedCubinSize(c_nvjitlink_h, &c_output_size)) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDtEFmnocadefghbi
661 code = bytearray(c_output_size) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDtEFmnocadefghbi
662 c_code_ptr = <char*>(<bytearray>code) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDtEFmnocadefghbi
663 with nogil: 1juRvGHwIxJpkqKLMNOPyrzAsBQCDtEFmnocadefghbi
664 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h, 1juRvGHwIxJpkqKLMNOPyrzAsBQCDtEFmnocadefghbi
665 cynvjitlink.nvJitLinkGetLinkedCubin(c_nvjitlink_h, c_code_ptr)) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDtEFmnocadefghbi
666 else:
667 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h, 1l
668 cynvjitlink.nvJitLinkGetLinkedPtxSize(c_nvjitlink_h, &c_output_size)) 1l
669 code = bytearray(c_output_size) 1l
670 c_code_ptr = <char*>(<bytearray>code) 1l
671 with nogil: 1l
672 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h, 1l
673 cynvjitlink.nvJitLinkGetLinkedPtx(c_nvjitlink_h, c_code_ptr)) 1l
674 else:
675 c_culink_state = as_cu(self._culink_handle)
676 try:
677 with nogil:
678 HANDLE_RETURN(cydriver.cuLinkComplete(c_culink_state, &c_cubin_out, &c_output_size))
679 except CUDAError as e:
680 Linker_annotate_error_log(self, e)
681 raise
682 code = (<char*>c_cubin_out)[:c_output_size]
684 # Linking is complete; cache the decoded logs. cuLinkDestroy may still
685 # dereference the raw log-buffer pointers, so retain them until close().
686 self._info_log = self.get_info_log() 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
687 self._error_log = self.get_error_log() 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
689 return ObjectCode._init(bytes(code), target_type, name=self._options.name) 1juRvGHwIxJpkqKLMNOPyrzAsBQCDltEFmnocadefghbi
692cdef inline void Linker_annotate_error_log(Linker self, object e):
693 """Annotate a CUDAError with the driver linker error log."""
694 error_log = self.get_error_log()
695 if error_log:
696 e.args = (e.args[0] + f"\nLinker error log: {error_log}", *e.args[1:])
699# =============================================================================
700# Private implementation: module-level state and initialization
701# =============================================================================
703# TODO: revisit this treatment for py313t builds
704_driver = None # populated if nvJitLink cannot be used
705_inited = False
706_use_nvjitlink_backend = None # set by _decide_nvjitlink_or_driver()
708# Input type mappings populated by _lazy_init() with C-level enum ints.
709_nvjitlink_input_types = None
710_driver_input_types = None
713def _nvjitlink_has_version_symbol(nvjitlink) -> bool:
714 # This condition is equivalent to testing for version >= 12.3
715 return bool(nvjitlink._inspect_function_pointer("__nvJitLinkVersion"))
718# Note: this function is reused in the tests
719def _decide_nvjitlink_or_driver() -> bool:
720 """Return True if falling back to the cuLink* driver APIs."""
721 global _driver, _use_nvjitlink_backend
722 if _use_nvjitlink_backend is not None: 2S + * [ ] R v G H w I x J p k q K L M N O P y r z A s B Q . - ; , ^ / V m n o c a d e f g h b i _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbub
723 return not _use_nvjitlink_backend 2S [ ] R v G H w I x J p k q K L M N O P y r z A s B Q V m n o c a d e f g h b i _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbub
725 warn_txt_common = (
726 "the driver APIs will be used instead, which do not support" 1S+*.-;,^/
727 " minor version compatibility or linking LTO IRs."
728 " For best results, consider upgrading to a recent version of"
729 )
731 nvjitlink_module = _optional_cuda_import("cuda.bindings.nvjitlink") 1S+*.-;,^/
732 if nvjitlink_module is None: 1S+*.-;,/
733 warn_txt = f"cuda.bindings.nvjitlink is not available, therefore {warn_txt_common} cuda-bindings." 1;
734 else:
735 from cuda.bindings._internal import nvjitlink 1S+*.-,/
737 try: 1S+*.-,/
738 has_version_symbol = _nvjitlink_has_version_symbol(nvjitlink) 1S+*.-,/
739 except DynamicLibNotFoundError: 1+-
740 warn_txt = (
741 f"cuda.bindings.nvjitlink is not available, therefore {warn_txt_common} cuda-bindings." 1+-
742 )
743 else:
744 if has_version_symbol: 1S*.,/
745 _use_nvjitlink_backend = True 1S./
746 return False # Use nvjitlink 1S./
747 warn_txt = (
748 f"{'nvJitLink*.dll' if sys.platform == 'win32' else 'libnvJitLink.so*'} is too old (<12.3)." 1*,
749 f" Therefore cuda.bindings.nvjitlink is not usable and {warn_txt_common} nvJitLink." 1*,
750 )
752 warn(warn_txt, stacklevel=2, category=RuntimeWarning) 1+*-;,
753 _driver = driver 1+*-;,
754 _use_nvjitlink_backend = False 1+*-;,
755 return True 1+*-;,
758def _lazy_init() -> None:
759 global _inited, _nvjitlink_input_types, _driver_input_types
760 if _inited: 1S:jTuWYCUDlt=?0%Z'!#X1234567)($98EFVmnocadefghbi
761 return 1S:jTuWYCUDlt=?0%Z'!#X1234567)($98EFVmnocadefghbi
763 _decide_nvjitlink_or_driver()
764 if _use_nvjitlink_backend:
765 _nvjitlink_input_types = {
766 "ptx": <int>cynvjitlink.NVJITLINK_INPUT_PTX,
767 "cubin": <int>cynvjitlink.NVJITLINK_INPUT_CUBIN,
768 "fatbin": <int>cynvjitlink.NVJITLINK_INPUT_FATBIN,
769 "ltoir": <int>cynvjitlink.NVJITLINK_INPUT_LTOIR,
770 "object": <int>cynvjitlink.NVJITLINK_INPUT_OBJECT,
771 "library": <int>cynvjitlink.NVJITLINK_INPUT_LIBRARY,
772 }
773 else:
774 _driver_input_types = {
775 "ptx": <int>cydriver.CU_JIT_INPUT_PTX,
776 "cubin": <int>cydriver.CU_JIT_INPUT_CUBIN,
777 "fatbin": <int>cydriver.CU_JIT_INPUT_FATBINARY,
778 "object": <int>cydriver.CU_JIT_INPUT_OBJECT,
779 "library": <int>cydriver.CU_JIT_INPUT_LIBRARY,
780 }
781 _inited = True