@@ -18,6 +18,7 @@ from cuda.bindings cimport cynvjitlink
1818
1919from ._rt cimport (
2020 as_cu,
21+ as_intptr,
2122 as_py,
2223 create_culink_handle,
2324 create_nvjitlink_handle,
@@ -101,7 +102,9 @@ cdef class Linker:
101102 Parameters
102103 ----------
103104 target_type : ObjectCodeFormatType | str
104- The type of the target output. Must be either "cubin" or "ptx".
105+ The type of the target output. Must be "cubin", "ptx", or
106+ "ltoir". Linked LTOIR output requires
107+ ``link_time_optimization = True `` and nvJitLink 13.3 or newer.
105108
106109 Returns
107110 -------
@@ -112,6 +115,16 @@ cdef class Linker:
112115
113116 Ensure that input object codes were compiled with appropriate
114117 flags for linking (e.g., relocatable device code enabled ).
118+
119+ A CUBIN produced with ``incremental = True `` can be passed directly
120+ to another :class:`Linker`, but it can still contain unresolved
121+ device references and should be finalized before execution.
122+
123+ ``"ltoir"`` output contains only the LTOIR carried by the inputs.
124+ Direct PTX and CUBIN inputs are rejected because they carry no
125+ LTOIR. FATBIN , host object , and library inputs are accepted but
126+ may carry no LTOIR , in which case they contribute nothing to the
127+ output.
115128 """
116129 Linker_check_open(self )
117130 return Linker_link(self , str(target_type ))
@@ -256,6 +269,11 @@ class LinkerOptions:
256269 link_time_optimization : bool , optional
257270 Perform link time optimization.
258271 Default: False.
272+ incremental : bool , optional
273+ Perform an incremental link. The result can be passed
274+ directly to a later :class:`Linker`. Requires nvJitLink 13.2 or newer
275+ and is not supported by the driver linker backend.
276+ Default: False.
259277 ptx : bool , optional
260278 Emit PTX after linking instead of CUBIN; only supported with ``link_time_optimization = True ``.
261279 Default: False.
@@ -336,6 +354,7 @@ class LinkerOptions:
336354 split_compile_extended: int | None = None
337355 no_cache: bool | None = None
338356 numba_debug: bool | None = None
357+ incremental: bool | None = None
339358
340359 def __post_init__(self ) -> None:
341360 _lazy_init()
@@ -371,6 +390,8 @@ class LinkerOptions:
371390 options.append("-verbose")
372391 if self.link_time_optimization:
373392 options.append("-lto")
393+ if self.incremental:
394+ options.append("-r")
374395 if self.ptx:
375396 options.append("-ptx")
376397 if self.optimization_level is not None:
@@ -450,6 +471,8 @@ class LinkerOptions:
450471 if self.link_time_optimization:
451472 formatted_options.append(1)
452473 option_keys.append(_driver.CUjit_option.CU_JIT_LTO )
474+ if self.incremental:
475+ raise ValueError("incremental option is not supported by the driver API")
453476 if self.ptx:
454477 raise ValueError("ptx option is not supported by the driver API")
455478 if self.optimization_level is not None:
@@ -532,8 +555,13 @@ cdef inline int Linker_init(Linker self, tuple object_codes, object options) exc
532555 cdef void** c_drv_jit_values_ptr
533556
534557 self._options = options = check_or_create_options(LinkerOptions, options, " Linker options" )
558+ self._has_ptx_or_cubin_input = False
559+ if options.incremental and options.ptx:
560+ raise ValueError("incremental and ptx output options cannot be used together")
535561
536562 if _use_nvjitlink_backend:
563+ if options.incremental:
564+ _require_nvjitlink_version((13, 2), "incremental linking")
537565 self._use_nvjitlink = True
538566 options_bytes = options._prepare_nvjitlink_options(as_bytes = True )
539567 c_num_opts = len (options_bytes)
@@ -639,17 +667,34 @@ cdef inline void Linker_add_code_object(Linker self, object object_code) except
639667 Linker_annotate_error_log(self , e)
640668 raise
641669
670+ if object_code.code_type in (" ptx" , " cubin" ):
671+ self ._has_ptx_or_cubin_input = True
672+
642673
643674cdef inline object Linker_link(Linker self , str target_type):
644675 """ Complete linking and return the result as ObjectCode."""
645- if target_type not in (" cubin" , " ptx" ):
676+ if target_type not in (" cubin" , " ptx" , " ltoir " ):
646677 raise ValueError (f" Unsupported target type: {target_type}" )
678+ if self ._options.incremental and target_type == " ptx" :
679+ raise ValueError (" PTX output is not supported for incremental linking" )
680+ if target_type == " ltoir" :
681+ if not self ._use_nvjitlink:
682+ raise ValueError (" LTOIR output is not supported by the driver API" )
683+ if not self ._options.link_time_optimization:
684+ raise ValueError (" LTOIR output requires link_time_optimization=True" )
685+ if self ._has_ptx_or_cubin_input:
686+ raise ValueError (
687+ ' LTOIR output is not supported with "ptx" or "cubin" inputs; '
688+ " they carry no LTOIR and would be omitted from the output"
689+ )
690+ nvjitlink_module = _linked_ltoir_output_module()
647691
648692 cdef cynvjitlink.nvJitLinkHandle c_nvjitlink_h
649693 cdef cydriver.CUlinkState c_culink_state
650694 cdef size_t c_output_size = 0
651695 cdef char * c_code_ptr
652696 cdef void * c_cubin_out = NULL
697+ cdef intptr_t c_handle
653698
654699 if self ._use_nvjitlink:
655700 c_nvjitlink_h = as_cu(self ._nvjitlink_handle)
@@ -663,14 +708,19 @@ cdef inline object Linker_link(Linker self, str target_type):
663708 with nogil:
664709 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h,
665710 cynvjitlink.nvJitLinkGetLinkedCubin(c_nvjitlink_h, c_code_ptr))
666- else :
711+ elif target_type == " ptx " :
667712 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h,
668713 cynvjitlink.nvJitLinkGetLinkedPtxSize(c_nvjitlink_h, & c_output_size))
669714 code = bytearray(c_output_size)
670715 c_code_ptr = < char * > (< bytearray> code)
671716 with nogil:
672717 HANDLE_RETURN_NVJITLINK(c_nvjitlink_h,
673718 cynvjitlink.nvJitLinkGetLinkedPtx(c_nvjitlink_h, c_code_ptr))
719+ else :
720+ c_handle = as_intptr(self ._nvjitlink_handle)
721+ output_size = nvjitlink_module.get_linked_ltoir_size(c_handle)
722+ code = bytearray(output_size)
723+ nvjitlink_module.get_linked_ltoir(c_handle, code)
674724 else :
675725 c_culink_state = as_cu(self ._culink_handle)
676726 try :
@@ -702,6 +752,8 @@ cdef inline void Linker_annotate_error_log(Linker self, object e):
702752
703753# TODO: revisit this treatment for py313t builds
704754_driver = None # populated if nvJitLink cannot be used
755+ _nvjitlink = None # populated if nvJitLink can be used
756+ _nvjitlink_version = None
705757_inited = False
706758_use_nvjitlink_backend = None # set by _decide_nvjitlink_or_driver()
707759
@@ -710,6 +762,31 @@ _nvjitlink_input_types = None
710762_driver_input_types = None
711763
712764
765+ def _require_nvjitlink_version (minimum_version: tuple[int , int], feature: str ) -> None:
766+ """Check that the cached nvJitLink runtime meets a feature's requirement."""
767+ if _nvjitlink_version < minimum_version:
768+ required = " ." .join(str (component) for component in minimum_version)
769+ detected = " ." .join(str (component) for component in _nvjitlink_version)
770+ raise RuntimeError(f"{feature} requires nvJitLink {required} or newer; found {detected}")
771+
772+
773+ # TODO(#2783): Replace this Python- level dispatch with direct cimports once
774+ # the cuda-bindings runtime floor includes the linked-LTOIR getters.
775+ def _linked_ltoir_output_module ():
776+ """ Return bindings that can retrieve linked LTOIR without a Cython dependency."""
777+ _require_nvjitlink_version((13 , 3 ), " LTOIR output" )
778+ missing = [
779+ name
780+ for name in (" get_linked_ltoir_size" , " get_linked_ltoir" )
781+ if not hasattr (_nvjitlink, name)
782+ ]
783+ if missing:
784+ raise RuntimeError (
785+ " LTOIR output requires cuda-bindings with " + " and " .join(missing)
786+ )
787+ return _nvjitlink
788+
789+
713790def _nvjitlink_has_version_symbol (nvjitlink ) -> bool:
714791 # This condition is equivalent to testing for version >= 12.3
715792 return bool(nvjitlink._inspect_function_pointer("__nvJitLinkVersion"))
@@ -718,10 +795,13 @@ def _nvjitlink_has_version_symbol(nvjitlink) -> bool:
718795# Note: this function is reused in the tests
719796def _decide_nvjitlink_or_driver() -> bool:
720797 """Return True if falling back to the cuLink* driver APIs."""
721- global _driver , _use_nvjitlink_backend
798+ global _driver , _nvjitlink , _nvjitlink_version , _use_nvjitlink_backend
722799 if _use_nvjitlink_backend is not None:
723800 return not _use_nvjitlink_backend
724801
802+ _nvjitlink = None
803+ _nvjitlink_version = None
804+
725805 warn_txt_common = (
726806 " the driver APIs will be used instead, which do not support"
727807 " minor version compatibility or linking LTO IRs."
@@ -742,6 +822,9 @@ def _decide_nvjitlink_or_driver() -> bool:
742822 )
743823 else:
744824 if has_version_symbol:
825+ detected_version = nvjitlink_module.version()
826+ _nvjitlink = nvjitlink_module
827+ _nvjitlink_version = detected_version
745828 _use_nvjitlink_backend = True
746829 return False # Use nvjitlink
747830 warn_txt = (
0 commit comments