Skip to main content

oxicuda_driver/
loader.rs

1//! Dynamic CUDA driver library loader.
2//!
3//! This module is the architectural foundation of `oxicuda-driver`. It locates
4//! and loads the CUDA driver shared library (`libcuda.so` on Linux,
5//! `nvcuda.dll` on Windows) **at runtime** via [`libloading`], so that no CUDA
6//! SDK is required at build time.
7//!
8//! # Platform support
9//!
10//! | Platform | Library names tried              | Notes                            |
11//! |----------|----------------------------------|----------------------------------|
12//! | Linux    | `libcuda.so.1`, `libcuda.so`     | Installed by NVIDIA driver       |
13//! | Windows  | `nvcuda.dll`                     | Ships with the display driver    |
14//! | macOS    | —                                | Returns `UnsupportedPlatform`    |
15//!
16//! # Usage
17//!
18//! Application code should **not** interact with [`DriverApi`] directly.
19//! Instead, call [`try_driver`] to obtain a reference to the lazily-
20//! initialised global singleton:
21//!
22//! ```rust,no_run
23//! # use oxicuda_driver::loader::try_driver;
24//! let api = try_driver()?;
25//! // api.cu_init, api.cu_device_get, …
26//! # Ok::<(), oxicuda_driver::error::CudaError>(())
27//! ```
28//!
29//! The singleton is stored in a [`OnceLock`] so that the (relatively
30//! expensive) `dlopen` + symbol resolution only happens once, and all
31//! subsequent accesses are a single atomic load.
32
33use std::ffi::{c_char, c_int, c_void};
34use std::sync::OnceLock;
35
36use libloading::Library;
37
38use crate::error::{CudaError, CudaResult, DriverLoadError};
39use crate::ffi::*;
40
41// ---------------------------------------------------------------------------
42// Global singleton
43// ---------------------------------------------------------------------------
44
45/// Global singleton for the driver API function table.
46///
47/// Initialised lazily on the first call to [`try_driver`].
48static DRIVER: OnceLock<Result<DriverApi, DriverLoadError>> = OnceLock::new();
49
50// ---------------------------------------------------------------------------
51// load_sym! helper macro
52// ---------------------------------------------------------------------------
53
54/// Load a single symbol from the shared library and transmute it to the
55/// requested function-pointer type.
56///
57/// # Safety
58///
59/// The caller must ensure that the symbol name matches the actual ABI of the
60/// function pointer type expected at the call site.
61#[cfg(not(target_os = "macos"))]
62macro_rules! load_sym {
63    ($lib:expr, $name:literal) => {{
64        // `Library::get` requires the name as a byte slice.  We request the
65        // most general function-pointer type and then transmute to the
66        // concrete signature stored in DriverApi.
67        let sym = unsafe { $lib.get::<unsafe extern "C" fn()>($name.as_bytes()) }.map_err(|e| {
68            DriverLoadError::SymbolNotFound {
69                symbol: $name,
70                reason: e.to_string(),
71            }
72        })?;
73        // SAFETY: we trust that the CUDA driver exports the symbol with the
74        // ABI described by the target field type.  The type is inferred from
75        // the DriverApi field this expression is assigned to, so explicit
76        // transmute annotations would require repeating the function-pointer
77        // type at every call site inside a macro — we suppress that lint here.
78        #[allow(clippy::missing_transmute_annotations)]
79        let result = unsafe { std::mem::transmute(*sym) };
80        result
81    }};
82}
83
84/// Load a symbol from the shared library, returning `Some(fn_ptr)` on success
85/// or `None` if the symbol is not found. Used for optional API entry points
86/// that may not be present in older driver versions.
87///
88/// # Safety
89///
90/// Same safety requirements as [`load_sym!`].
91#[cfg(not(target_os = "macos"))]
92macro_rules! load_sym_optional {
93    ($lib:expr, $name:literal) => {{
94        match unsafe { $lib.get::<unsafe extern "C" fn()>($name.as_bytes()) } {
95            Ok(sym) => {
96                // SAFETY: the target type is inferred from the DriverApi field
97                // this value is assigned to.  Suppressing the lint here avoids
98                // repeating the function-pointer type at every call site.
99                #[allow(clippy::missing_transmute_annotations)]
100                let fp = unsafe { std::mem::transmute(*sym) };
101                Some(fp)
102            }
103            Err(_) => {
104                tracing::debug!(concat!("optional symbol not found: ", $name));
105                None
106            }
107        }
108    }};
109}
110
111// ---------------------------------------------------------------------------
112// DriverApi
113// ---------------------------------------------------------------------------
114
115/// Complete function-pointer table for the CUDA Driver API.
116///
117/// An instance of this struct is produced by [`DriverApi::load`] and kept
118/// alive for the lifetime of the process inside the `DRIVER` singleton.
119/// The embedded [`Library`] handle ensures the shared object is not unloaded.
120///
121/// # Function pointer groups
122///
123/// The fields are organised into logical groups mirroring the CUDA Driver API
124/// documentation:
125///
126/// * **Initialisation** — [`cu_init`](Self::cu_init)
127/// * **Device management** — `cu_device_*`
128/// * **Context management** — `cu_ctx_*`
129/// * **Module management** — `cu_module_*`
130/// * **Memory management** — `cu_mem_*`, `cu_memcpy_*`, `cu_memset_*`
131/// * **Stream management** — `cu_stream_*`
132/// * **Event management** — `cu_event_*`
133/// * **Kernel launch** — [`cu_launch_kernel`](Self::cu_launch_kernel)
134/// * **Occupancy queries** — `cu_occupancy_*`
135pub struct DriverApi {
136    // Keep the shared library handle alive.
137    _lib: Library,
138
139    // -- Initialisation ----------------------------------------------------
140    /// `cuInit(flags) -> CUresult`
141    ///
142    /// Initialises the CUDA driver API.  Must be called before any other
143    /// driver function.  Passing `0` for *flags* is the only documented
144    /// value.
145    pub cu_init: unsafe extern "C" fn(flags: u32) -> CUresult,
146
147    // -- Version query -------------------------------------------------------
148    /// `cuDriverGetVersion(driverVersion*) -> CUresult`
149    ///
150    /// Returns the CUDA driver version as `major*1000 + minor*10`.
151    pub cu_driver_get_version: unsafe extern "C" fn(version: *mut c_int) -> CUresult,
152
153    // -- Device management -------------------------------------------------
154    /// `cuDeviceGet(device*, ordinal) -> CUresult`
155    ///
156    /// Returns a handle to a compute device.
157    pub cu_device_get: unsafe extern "C" fn(device: *mut CUdevice, ordinal: c_int) -> CUresult,
158
159    /// `cuDeviceGetCount(count*) -> CUresult`
160    ///
161    /// Returns the number of compute-capable devices.
162    pub cu_device_get_count: unsafe extern "C" fn(count: *mut c_int) -> CUresult,
163
164    /// `cuDeviceGetName(name*, len, dev) -> CUresult`
165    ///
166    /// Returns an ASCII string identifying the device.
167    pub cu_device_get_name:
168        unsafe extern "C" fn(name: *mut c_char, len: c_int, dev: CUdevice) -> CUresult,
169
170    /// `cuDeviceGetAttribute(pi*, attrib, dev) -> CUresult`
171    ///
172    /// Returns information about the device.
173    pub cu_device_get_attribute:
174        unsafe extern "C" fn(pi: *mut c_int, attrib: CUdevice_attribute, dev: CUdevice) -> CUresult,
175
176    /// `cuDeviceTotalMem_v2(bytes*, dev) -> CUresult`
177    ///
178    /// Returns the total amount of memory on the device.
179    pub cu_device_total_mem_v2: unsafe extern "C" fn(bytes: *mut usize, dev: CUdevice) -> CUresult,
180
181    /// `cuDeviceCanAccessPeer(canAccessPeer*, dev, peerDev) -> CUresult`
182    ///
183    /// Queries if a device may directly access a peer device's memory.
184    pub cu_device_can_access_peer:
185        unsafe extern "C" fn(can_access: *mut c_int, dev: CUdevice, peer_dev: CUdevice) -> CUresult,
186
187    // -- Primary context management ----------------------------------------
188    /// `cuDevicePrimaryCtxRetain(pctx*, dev) -> CUresult`
189    ///
190    /// Retains the primary context on the device, creating it if necessary.
191    pub cu_device_primary_ctx_retain:
192        unsafe extern "C" fn(pctx: *mut CUcontext, dev: CUdevice) -> CUresult,
193
194    /// `cuDevicePrimaryCtxRelease_v2(dev) -> CUresult`
195    ///
196    /// Releases the primary context on the device.
197    pub cu_device_primary_ctx_release_v2: unsafe extern "C" fn(dev: CUdevice) -> CUresult,
198
199    /// `cuDevicePrimaryCtxSetFlags_v2(dev, flags) -> CUresult`
200    ///
201    /// Sets flags for the primary context.
202    pub cu_device_primary_ctx_set_flags_v2:
203        unsafe extern "C" fn(dev: CUdevice, flags: u32) -> CUresult,
204
205    /// `cuDevicePrimaryCtxGetState(dev, flags*, active*) -> CUresult`
206    ///
207    /// Returns the state (flags and active status) of the primary context.
208    pub cu_device_primary_ctx_get_state:
209        unsafe extern "C" fn(dev: CUdevice, flags: *mut u32, active: *mut c_int) -> CUresult,
210
211    /// `cuDevicePrimaryCtxReset_v2(dev) -> CUresult`
212    ///
213    /// Resets the primary context on the device.
214    pub cu_device_primary_ctx_reset_v2: unsafe extern "C" fn(dev: CUdevice) -> CUresult,
215
216    // -- Context management ------------------------------------------------
217    /// `cuCtxCreate_v2(pctx*, flags, dev) -> CUresult`
218    ///
219    /// Creates a new CUDA context and associates it with the calling thread.
220    pub cu_ctx_create_v2:
221        unsafe extern "C" fn(pctx: *mut CUcontext, flags: u32, dev: CUdevice) -> CUresult,
222
223    /// `cuCtxDestroy_v2(ctx) -> CUresult`
224    ///
225    /// Destroys a CUDA context.
226    pub cu_ctx_destroy_v2: unsafe extern "C" fn(ctx: CUcontext) -> CUresult,
227
228    /// `cuCtxSetCurrent(ctx) -> CUresult`
229    ///
230    /// Binds the specified CUDA context to the calling CPU thread.
231    pub cu_ctx_set_current: unsafe extern "C" fn(ctx: CUcontext) -> CUresult,
232
233    /// `cuCtxGetCurrent(pctx*) -> CUresult`
234    ///
235    /// Returns the CUDA context bound to the calling CPU thread.
236    pub cu_ctx_get_current: unsafe extern "C" fn(pctx: *mut CUcontext) -> CUresult,
237
238    /// `cuCtxPopCurrent_v2(pctx*) -> CUresult`
239    ///
240    /// Pops the current CUDA context off the calling thread's context stack and
241    /// (optionally) writes the popped handle to `pctx`. Used to turn a
242    /// just-created context into a "floating" context that is not bound to any
243    /// thread until an explicit `cuCtxSetCurrent`.
244    pub cu_ctx_pop_current_v2: unsafe extern "C" fn(pctx: *mut CUcontext) -> CUresult,
245
246    /// `cuCtxSynchronize() -> CUresult`
247    ///
248    /// Blocks until the device has completed all preceding requested tasks.
249    pub cu_ctx_synchronize: unsafe extern "C" fn() -> CUresult,
250
251    // -- Module management -------------------------------------------------
252    /// `cuModuleLoadData(module*, image*) -> CUresult`
253    ///
254    /// Loads a module from a PTX or cubin image in host memory.
255    pub cu_module_load_data:
256        unsafe extern "C" fn(module: *mut CUmodule, image: *const c_void) -> CUresult,
257
258    /// `cuModuleLoadDataEx(module*, image*, numOptions, options*, optionValues*) -> CUresult`
259    ///
260    /// Loads a module with JIT compiler options.
261    pub cu_module_load_data_ex: unsafe extern "C" fn(
262        module: *mut CUmodule,
263        image: *const c_void,
264        num_options: u32,
265        options: *mut CUjit_option,
266        option_values: *mut *mut c_void,
267    ) -> CUresult,
268
269    /// `cuModuleGetFunction(hfunc*, hmod, name*) -> CUresult`
270    ///
271    /// Returns a handle to a function within a module.
272    pub cu_module_get_function: unsafe extern "C" fn(
273        hfunc: *mut CUfunction,
274        hmod: CUmodule,
275        name: *const c_char,
276    ) -> CUresult,
277
278    /// `cuModuleUnload(hmod) -> CUresult`
279    ///
280    /// Unloads a module from the current context.
281    pub cu_module_unload: unsafe extern "C" fn(hmod: CUmodule) -> CUresult,
282
283    // -- Memory management -------------------------------------------------
284    /// `cuMemAlloc_v2(dptr*, bytesize) -> CUresult`
285    ///
286    /// Allocates device memory.
287    pub cu_mem_alloc_v2: unsafe extern "C" fn(dptr: *mut CUdeviceptr, bytesize: usize) -> CUresult,
288
289    /// `cuMemFree_v2(dptr) -> CUresult`
290    ///
291    /// Frees device memory.
292    pub cu_mem_free_v2: unsafe extern "C" fn(dptr: CUdeviceptr) -> CUresult,
293
294    /// `cuMemcpyHtoD_v2(dst, src*, bytesize) -> CUresult`
295    ///
296    /// Copies data from host memory to device memory.
297    pub cu_memcpy_htod_v2:
298        unsafe extern "C" fn(dst: CUdeviceptr, src: *const c_void, bytesize: usize) -> CUresult,
299
300    /// `cuMemcpyDtoH_v2(dst*, src, bytesize) -> CUresult`
301    ///
302    /// Copies data from device memory to host memory.
303    pub cu_memcpy_dtoh_v2:
304        unsafe extern "C" fn(dst: *mut c_void, src: CUdeviceptr, bytesize: usize) -> CUresult,
305
306    /// `cuMemcpyDtoD_v2(dst, src, bytesize) -> CUresult`
307    ///
308    /// Copies data from device memory to device memory.
309    pub cu_memcpy_dtod_v2:
310        unsafe extern "C" fn(dst: CUdeviceptr, src: CUdeviceptr, bytesize: usize) -> CUresult,
311
312    /// `cuMemcpyHtoDAsync_v2(dst, src*, bytesize, stream) -> CUresult`
313    ///
314    /// Asynchronously copies data from host to device memory.
315    pub cu_memcpy_htod_async_v2: unsafe extern "C" fn(
316        dst: CUdeviceptr,
317        src: *const c_void,
318        bytesize: usize,
319        stream: CUstream,
320    ) -> CUresult,
321
322    /// `cuMemcpyDtoHAsync_v2(dst*, src, bytesize, stream) -> CUresult`
323    ///
324    /// Asynchronously copies data from device to host memory.
325    pub cu_memcpy_dtoh_async_v2: unsafe extern "C" fn(
326        dst: *mut c_void,
327        src: CUdeviceptr,
328        bytesize: usize,
329        stream: CUstream,
330    ) -> CUresult,
331
332    /// `cuMemAllocHost_v2(pp*, bytesize) -> CUresult`
333    ///
334    /// Allocates page-locked (pinned) host memory.
335    pub cu_mem_alloc_host_v2:
336        unsafe extern "C" fn(pp: *mut *mut c_void, bytesize: usize) -> CUresult,
337
338    /// `cuMemFreeHost(p*) -> CUresult`
339    ///
340    /// Frees page-locked host memory.
341    pub cu_mem_free_host: unsafe extern "C" fn(p: *mut c_void) -> CUresult,
342
343    /// `cuMemAllocManaged(dptr*, bytesize, flags) -> CUresult`
344    ///
345    /// Allocates unified memory accessible from both host and device.
346    pub cu_mem_alloc_managed:
347        unsafe extern "C" fn(dptr: *mut CUdeviceptr, bytesize: usize, flags: u32) -> CUresult,
348
349    /// `cuMemsetD8_v2(dst, value, count) -> CUresult`
350    ///
351    /// Sets device memory to a value (byte granularity).
352    pub cu_memset_d8_v2:
353        unsafe extern "C" fn(dst: CUdeviceptr, value: u8, count: usize) -> CUresult,
354
355    /// `cuMemsetD32_v2(dst, value, count) -> CUresult`
356    ///
357    /// Sets device memory to a value (32-bit granularity).
358    pub cu_memset_d32_v2:
359        unsafe extern "C" fn(dst: CUdeviceptr, value: u32, count: usize) -> CUresult,
360
361    /// `cuMemGetInfo_v2(free*, total*) -> CUresult`
362    ///
363    /// Returns free and total memory for the current context's device.
364    pub cu_mem_get_info_v2: unsafe extern "C" fn(free: *mut usize, total: *mut usize) -> CUresult,
365
366    /// `cuMemHostRegister_v2(p*, bytesize, flags) -> CUresult`
367    ///
368    /// Registers an existing host memory range for use by CUDA.
369    pub cu_mem_host_register_v2:
370        unsafe extern "C" fn(p: *mut c_void, bytesize: usize, flags: u32) -> CUresult,
371
372    /// `cuMemHostUnregister(p*) -> CUresult`
373    ///
374    /// Unregisters a memory range that was registered with cuMemHostRegister.
375    pub cu_mem_host_unregister: unsafe extern "C" fn(p: *mut c_void) -> CUresult,
376
377    /// `cuMemHostGetDevicePointer_v2(pdptr*, p*, flags) -> CUresult`
378    ///
379    /// Returns the device pointer mapped to a registered host pointer.
380    pub cu_mem_host_get_device_pointer_v2:
381        unsafe extern "C" fn(pdptr: *mut CUdeviceptr, p: *mut c_void, flags: u32) -> CUresult,
382
383    /// `cuPointerGetAttribute(data*, attribute, ptr) -> CUresult`
384    ///
385    /// Returns information about a pointer.
386    pub cu_pointer_get_attribute:
387        unsafe extern "C" fn(data: *mut c_void, attribute: u32, ptr: CUdeviceptr) -> CUresult,
388
389    /// `cuMemAdvise(devPtr, count, advice, device) -> CUresult`
390    ///
391    /// Advises the unified memory subsystem about usage patterns.
392    pub cu_mem_advise: unsafe extern "C" fn(
393        dev_ptr: CUdeviceptr,
394        count: usize,
395        advice: u32,
396        device: CUdevice,
397    ) -> CUresult,
398
399    /// `cuMemPrefetchAsync(devPtr, count, dstDevice, hStream) -> CUresult`
400    ///
401    /// Prefetches unified memory to the specified device.
402    pub cu_mem_prefetch_async: unsafe extern "C" fn(
403        dev_ptr: CUdeviceptr,
404        count: usize,
405        dst_device: CUdevice,
406        hstream: CUstream,
407    ) -> CUresult,
408
409    // -- Stream management -------------------------------------------------
410    /// `cuStreamCreate(phStream*, flags) -> CUresult`
411    ///
412    /// Creates a stream.
413    pub cu_stream_create: unsafe extern "C" fn(phstream: *mut CUstream, flags: u32) -> CUresult,
414
415    /// `cuStreamCreateWithPriority(phStream*, flags, priority) -> CUresult`
416    ///
417    /// Creates a stream with the given priority.
418    pub cu_stream_create_with_priority:
419        unsafe extern "C" fn(phstream: *mut CUstream, flags: u32, priority: c_int) -> CUresult,
420
421    /// `cuStreamDestroy_v2(hStream) -> CUresult`
422    ///
423    /// Destroys a stream.
424    pub cu_stream_destroy_v2: unsafe extern "C" fn(hstream: CUstream) -> CUresult,
425
426    /// `cuStreamSynchronize(hStream) -> CUresult`
427    ///
428    /// Waits until a stream's tasks are completed.
429    pub cu_stream_synchronize: unsafe extern "C" fn(hstream: CUstream) -> CUresult,
430
431    /// `cuStreamWaitEvent(hStream, hEvent, flags) -> CUresult`
432    ///
433    /// Makes all future work submitted to the stream wait for the event.
434    pub cu_stream_wait_event:
435        unsafe extern "C" fn(hstream: CUstream, hevent: CUevent, flags: u32) -> CUresult,
436
437    /// `cuStreamQuery(hStream) -> CUresult`
438    ///
439    /// Returns `CUDA_SUCCESS` if all operations in the stream have completed,
440    /// `CUDA_ERROR_NOT_READY` if still pending.
441    pub cu_stream_query: unsafe extern "C" fn(hstream: CUstream) -> CUresult,
442
443    /// `cuStreamGetPriority(hStream, priority*) -> CUresult`
444    ///
445    /// Query the priority of `hStream`.
446    pub cu_stream_get_priority:
447        unsafe extern "C" fn(hstream: CUstream, priority: *mut std::ffi::c_int) -> CUresult,
448
449    /// `cuStreamGetFlags(hStream, flags*) -> CUresult`
450    ///
451    /// Query the flags of `hStream`.
452    pub cu_stream_get_flags: unsafe extern "C" fn(hstream: CUstream, flags: *mut u32) -> CUresult,
453
454    // -- Event management --------------------------------------------------
455    /// `cuEventCreate(phEvent*, flags) -> CUresult`
456    ///
457    /// Creates an event.
458    pub cu_event_create: unsafe extern "C" fn(phevent: *mut CUevent, flags: u32) -> CUresult,
459
460    /// `cuEventDestroy_v2(hEvent) -> CUresult`
461    ///
462    /// Destroys an event.
463    pub cu_event_destroy_v2: unsafe extern "C" fn(hevent: CUevent) -> CUresult,
464
465    /// `cuEventRecord(hEvent, hStream) -> CUresult`
466    ///
467    /// Records an event in a stream.
468    pub cu_event_record: unsafe extern "C" fn(hevent: CUevent, hstream: CUstream) -> CUresult,
469
470    /// `cuEventQuery(hEvent) -> CUresult`
471    ///
472    /// Queries the status of an event. Returns `CUDA_SUCCESS` if complete,
473    /// `CUDA_ERROR_NOT_READY` if still pending.
474    pub cu_event_query: unsafe extern "C" fn(hevent: CUevent) -> CUresult,
475
476    /// `cuEventSynchronize(hEvent) -> CUresult`
477    ///
478    /// Waits until an event completes.
479    pub cu_event_synchronize: unsafe extern "C" fn(hevent: CUevent) -> CUresult,
480
481    /// `cuEventElapsedTime(pMilliseconds*, hStart, hEnd) -> CUresult`
482    ///
483    /// Computes the elapsed time between two events.
484    pub cu_event_elapsed_time:
485        unsafe extern "C" fn(pmilliseconds: *mut f32, hstart: CUevent, hend: CUevent) -> CUresult,
486
487    // -- Kernel launch -----------------------------------------------------
488
489    // -- Peer memory access ------------------------------------------------
490    /// `cuMemcpyPeer(dstDevice, dstContext, srcDevice, srcContext, count) -> CUresult`
491    ///
492    /// Copies device memory between two primary contexts.
493    pub cu_memcpy_peer: unsafe extern "C" fn(
494        dst_device: u64,
495        dst_ctx: CUcontext,
496        src_device: u64,
497        src_ctx: CUcontext,
498        count: usize,
499    ) -> CUresult,
500
501    /// `cuMemcpyPeerAsync(..., hStream) -> CUresult`
502    ///
503    /// Asynchronous cross-device copy.
504    pub cu_memcpy_peer_async: unsafe extern "C" fn(
505        dst_device: u64,
506        dst_ctx: CUcontext,
507        src_device: u64,
508        src_ctx: CUcontext,
509        count: usize,
510        stream: CUstream,
511    ) -> CUresult,
512
513    /// `cuCtxEnablePeerAccess(peerContext, flags) -> CUresult`
514    ///
515    /// Enables peer access between two contexts.
516    pub cu_ctx_enable_peer_access:
517        unsafe extern "C" fn(peer_context: CUcontext, flags: u32) -> CUresult,
518
519    /// `cuCtxDisablePeerAccess(peerContext) -> CUresult`
520    ///
521    /// Disables peer access to a context.
522    pub cu_ctx_disable_peer_access: unsafe extern "C" fn(peer_context: CUcontext) -> CUresult,
523    /// `cuLaunchKernel(f, gridDimX, gridDimY, gridDimZ, blockDimX, blockDimY,
524    ///   blockDimZ, sharedMemBytes, hStream, kernelParams**, extra**) -> CUresult`
525    ///
526    /// Launches a CUDA kernel.
527    #[allow(clippy::type_complexity)]
528    pub cu_launch_kernel: unsafe extern "C" fn(
529        f: CUfunction,
530        grid_dim_x: u32,
531        grid_dim_y: u32,
532        grid_dim_z: u32,
533        block_dim_x: u32,
534        block_dim_y: u32,
535        block_dim_z: u32,
536        shared_mem_bytes: u32,
537        hstream: CUstream,
538        kernel_params: *mut *mut c_void,
539        extra: *mut *mut c_void,
540    ) -> CUresult,
541
542    /// `cuLaunchCooperativeKernel(f, gridDimX, gridDimY, gridDimZ, blockDimX,
543    ///   blockDimY, blockDimZ, sharedMemBytes, hStream, kernelParams**) -> CUresult`
544    ///
545    /// Launches a cooperative CUDA kernel (CUDA 9.0+).
546    #[allow(clippy::type_complexity)]
547    pub cu_launch_cooperative_kernel: unsafe extern "C" fn(
548        f: CUfunction,
549        grid_dim_x: u32,
550        grid_dim_y: u32,
551        grid_dim_z: u32,
552        block_dim_x: u32,
553        block_dim_y: u32,
554        block_dim_z: u32,
555        shared_mem_bytes: u32,
556        hstream: CUstream,
557        kernel_params: *mut *mut c_void,
558    ) -> CUresult,
559
560    /// `cuLaunchCooperativeKernelMultiDevice(launchParamsList*, numDevices,
561    ///   flags) -> CUresult`
562    ///
563    /// Launches a cooperative kernel across multiple devices (CUDA 9.0+).
564    pub cu_launch_cooperative_kernel_multi_device: unsafe extern "C" fn(
565        launch_params_list: *mut c_void,
566        num_devices: u32,
567        flags: u32,
568    ) -> CUresult,
569
570    // -- Occupancy ---------------------------------------------------------
571    /// `cuOccupancyMaxActiveBlocksPerMultiprocessor(numBlocks*, func, blockSize,
572    ///   dynamicSMemSize) -> CUresult`
573    ///
574    /// Returns the number of the maximum active blocks per streaming
575    /// multiprocessor.
576    pub cu_occupancy_max_active_blocks_per_multiprocessor: unsafe extern "C" fn(
577        num_blocks: *mut c_int,
578        func: CUfunction,
579        block_size: c_int,
580        dynamic_smem_size: usize,
581    ) -> CUresult,
582
583    /// `cuOccupancyMaxPotentialBlockSize(minGridSize*, blockSize*, func,
584    ///   blockSizeToDynamicSMemSize, dynamicSMemSize, blockSizeLimit) -> CUresult`
585    ///
586    /// Suggests a launch configuration with reasonable occupancy.
587    #[allow(clippy::type_complexity)]
588    pub cu_occupancy_max_potential_block_size: unsafe extern "C" fn(
589        min_grid_size: *mut c_int,
590        block_size: *mut c_int,
591        func: CUfunction,
592        block_size_to_dynamic_smem_size: Option<unsafe extern "C" fn(c_int) -> usize>,
593        dynamic_smem_size: usize,
594        block_size_limit: c_int,
595    ) -> CUresult,
596
597    /// `cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags(numBlocks*, func,
598    ///   blockSize, dynamicSMemSize, flags) -> CUresult`
599    ///
600    /// Like `cuOccupancyMaxActiveBlocksPerMultiprocessor` but with flags
601    /// to control caching behaviour (CUDA 9.0+).
602    pub cu_occupancy_max_active_blocks_per_multiprocessor_with_flags:
603        unsafe extern "C" fn(
604            num_blocks: *mut c_int,
605            func: CUfunction,
606            block_size: c_int,
607            dynamic_smem_size: usize,
608            flags: u32,
609        ) -> CUresult,
610
611    // -- Memory management (optional) -----------------------------------------
612    /// `cuMemcpyDtoDAsync_v2(dst, src, bytesize, stream) -> CUresult`
613    ///
614    /// Asynchronously copies data from device memory to device memory.
615    pub cu_memcpy_dtod_async_v2: Option<
616        unsafe extern "C" fn(
617            dst: CUdeviceptr,
618            src: CUdeviceptr,
619            bytesize: usize,
620            stream: CUstream,
621        ) -> CUresult,
622    >,
623
624    /// `cuMemsetD16_v2(dst, value, count) -> CUresult`
625    ///
626    /// Sets device memory to a value (16-bit granularity).
627    pub cu_memset_d16_v2:
628        Option<unsafe extern "C" fn(dst: CUdeviceptr, value: u16, count: usize) -> CUresult>,
629
630    /// `cuMemsetD32Async(dst, value, count, stream) -> CUresult`
631    ///
632    /// Asynchronously sets device memory to a value (32-bit granularity).
633    pub cu_memset_d32_async: Option<
634        unsafe extern "C" fn(
635            dst: CUdeviceptr,
636            value: u32,
637            count: usize,
638            stream: CUstream,
639        ) -> CUresult,
640    >,
641
642    // -- Context management (optional) ----------------------------------------
643    /// `cuCtxGetLimit(value*, limit) -> CUresult`
644    ///
645    /// Returns the value of a context limit.
646    pub cu_ctx_get_limit: Option<unsafe extern "C" fn(value: *mut usize, limit: u32) -> CUresult>,
647
648    /// `cuCtxSetLimit(limit, value) -> CUresult`
649    ///
650    /// Sets a context limit.
651    pub cu_ctx_set_limit: Option<unsafe extern "C" fn(limit: u32, value: usize) -> CUresult>,
652
653    /// `cuCtxGetCacheConfig(config*) -> CUresult`
654    ///
655    /// Returns the current cache configuration for the context.
656    pub cu_ctx_get_cache_config: Option<unsafe extern "C" fn(config: *mut u32) -> CUresult>,
657
658    /// `cuCtxSetCacheConfig(config) -> CUresult`
659    ///
660    /// Sets the cache configuration for the current context.
661    pub cu_ctx_set_cache_config: Option<unsafe extern "C" fn(config: u32) -> CUresult>,
662
663    /// `cuCtxGetSharedMemConfig(config*) -> CUresult`
664    ///
665    /// Returns the shared memory configuration for the context.
666    pub cu_ctx_get_shared_mem_config: Option<unsafe extern "C" fn(config: *mut u32) -> CUresult>,
667
668    /// `cuCtxSetSharedMemConfig(config) -> CUresult`
669    ///
670    /// Sets the shared memory configuration for the current context.
671    pub cu_ctx_set_shared_mem_config: Option<unsafe extern "C" fn(config: u32) -> CUresult>,
672
673    // -- Event with flags (optional, CUDA 11.1+) ------------------------------
674    /// `cuEventRecordWithFlags(hEvent, hStream, flags) -> CUresult`
675    ///
676    /// Records an event in a stream with additional flags (CUDA 11.1+).
677    /// Falls back to `cu_event_record` when `None`.
678    pub cu_event_record_with_flags:
679        Option<unsafe extern "C" fn(hevent: CUevent, hstream: CUstream, flags: u32) -> CUresult>,
680
681    // -- Function attributes (optional) ---------------------------------------
682    /// `cuFuncGetAttribute(value*, attrib, func) -> CUresult`
683    ///
684    /// Returns information about a function.
685    pub cu_func_get_attribute: Option<
686        unsafe extern "C" fn(value: *mut c_int, attrib: c_int, func: CUfunction) -> CUresult,
687    >,
688
689    /// `cuFuncSetCacheConfig(func, config) -> CUresult`
690    ///
691    /// Sets the cache configuration for a device function.
692    pub cu_func_set_cache_config:
693        Option<unsafe extern "C" fn(func: CUfunction, config: u32) -> CUresult>,
694
695    /// `cuFuncSetSharedMemConfig(func, config) -> CUresult`
696    ///
697    /// Sets the shared memory configuration for a device function.
698    pub cu_func_set_shared_mem_config:
699        Option<unsafe extern "C" fn(func: CUfunction, config: u32) -> CUresult>,
700
701    /// `cuFuncSetAttribute(func, attrib, value) -> CUresult`
702    ///
703    /// Sets an attribute value for a device function.
704    pub cu_func_set_attribute:
705        Option<unsafe extern "C" fn(func: CUfunction, attrib: c_int, value: c_int) -> CUresult>,
706
707    // -- Profiler (optional) --------------------------------------------------
708    /// `cuProfilerStart() -> CUresult`
709    ///
710    /// Starts the CUDA profiler.
711    pub cu_profiler_start: Option<unsafe extern "C" fn() -> CUresult>,
712
713    /// `cuProfilerStop() -> CUresult`
714    ///
715    /// Stops the CUDA profiler.
716    pub cu_profiler_stop: Option<unsafe extern "C" fn() -> CUresult>,
717
718    // -- CUDA 12.x extended launch (optional) ---------------------------------
719    /// `cuLaunchKernelEx(config*, f, kernelParams**, extra**) -> CUresult`
720    ///
721    /// Extended kernel launch with cluster dimensions and other CUDA 12.0+
722    /// attributes. Available only when the driver is CUDA 12.0 or newer.
723    ///
724    /// When `None`, fall back to [`cu_launch_kernel`](Self::cu_launch_kernel).
725    #[allow(clippy::type_complexity)]
726    pub cu_launch_kernel_ex: Option<
727        unsafe extern "C" fn(
728            config: *const CuLaunchConfig,
729            f: CUfunction,
730            kernel_params: *mut *mut std::ffi::c_void,
731            extra: *mut *mut std::ffi::c_void,
732        ) -> CUresult,
733    >,
734
735    /// `cuTensorMapEncodeTiled(tensorMap*, ...) -> CUresult`
736    ///
737    /// Creates a TMA tensor map descriptor for tiled access patterns.
738    /// Available on CUDA 12.0+ with sm_90+ (Hopper/Blackwell).
739    ///
740    /// When `None`, TMA is not supported by the loaded driver.
741    #[allow(clippy::type_complexity)]
742    pub cu_tensor_map_encode_tiled: Option<
743        unsafe extern "C" fn(
744            tensor_map: *mut std::ffi::c_void,
745            tensor_data_type: u32,
746            tensor_rank: u32,
747            global_address: *mut std::ffi::c_void,
748            global_dim: *const u64,
749            global_strides: *const u64,
750            box_dim: *const u32,
751            element_strides: *const u32,
752            interleave: u32,
753            swizzle: u32,
754            l2_promotion: u32,
755            oob_fill: u32,
756        ) -> CUresult,
757    >,
758
759    // -- CUDA 12.8+ extended API (optional) -----------------------------------
760    /// `cuTensorMapEncodeTiledMemref(tensorMap*, ...) -> CUresult`
761    ///
762    /// Extended TMA encoding using memref descriptors (CUDA 12.8+,
763    /// Blackwell sm_100/sm_120). When `None`, fall back to
764    /// [`cu_tensor_map_encode_tiled`](Self::cu_tensor_map_encode_tiled).
765    #[allow(clippy::type_complexity)]
766    pub cu_tensor_map_encode_tiled_memref: Option<
767        unsafe extern "C" fn(
768            tensor_map: *mut c_void,
769            tensor_data_type: u32,
770            tensor_rank: u32,
771            global_address: *mut c_void,
772            global_dim: *const u64,
773            global_strides: *const u64,
774            box_dim: *const u32,
775            element_strides: *const u32,
776            interleave: u32,
777            swizzle: u32,
778            l2_promotion: u32,
779            oob_fill: u32,
780            flags: u64,
781        ) -> CUresult,
782    >,
783
784    /// `cuKernelGetLibrary(pLib*, kernel) -> CUresult`
785    ///
786    /// Returns the library handle that owns a given kernel handle
787    /// (CUDA 12.8+). When `None`, the driver does not support the JIT
788    /// library API.
789    pub cu_kernel_get_library:
790        Option<unsafe extern "C" fn(p_lib: *mut CUlibrary, kernel: CUkernel) -> CUresult>,
791
792    /// `cuMulticastGetGranularity(granularity*, desc*, option) -> CUresult`
793    ///
794    /// Queries the recommended memory granularity for an NVLink multicast
795    /// object (CUDA 12.8+). When `None`, multicast memory is not supported.
796    pub cu_multicast_get_granularity: Option<
797        unsafe extern "C" fn(granularity: *mut usize, desc: *const c_void, option: u32) -> CUresult,
798    >,
799
800    /// `cuMulticastCreate(mcHandle*, desc*) -> CUresult`
801    ///
802    /// Creates an NVLink multicast object for cross-GPU broadcast memory
803    /// (CUDA 12.8+). When `None`, multicast memory is not supported.
804    pub cu_multicast_create: Option<
805        unsafe extern "C" fn(mc_handle: *mut CUmulticastObject, desc: *const c_void) -> CUresult,
806    >,
807
808    /// `cuMulticastAddDevice(mcHandle, dev) -> CUresult`
809    ///
810    /// Adds a device to an NVLink multicast group (CUDA 12.8+). When
811    /// `None`, multicast memory is not supported.
812    pub cu_multicast_add_device:
813        Option<unsafe extern "C" fn(mc_handle: CUmulticastObject, dev: CUdevice) -> CUresult>,
814
815    /// `cuMemcpyBatchAsync(dsts*, srcs*, sizes*, count, attrs*, attrsIdxs*,
816    /// numAttrs, failIdx*, stream) -> CUresult`
817    ///
818    /// Issues *count* asynchronous memory copies (H2D, D2H, or D2D) in a
819    /// single driver call (CUDA 12.8+). When `None`, issue individual
820    /// `cuMemcpyAsync` calls as a fallback.
821    ///
822    /// The signature mirrors the real CUDA 12.8 export exactly: `dsts`/`srcs`
823    /// are arrays of `CUdeviceptr`, `attrs` is an array of
824    /// [`CUmemcpyAttributes`] indexed via `attrs_idxs`, and `fail_idx` receives
825    /// the index of the first failed copy. (CUDA 13.x changed this ABI again by
826    /// dropping `failIdx`; callers must gate on driver version before invoking.)
827    #[allow(clippy::type_complexity)]
828    pub cu_memcpy_batch_async: Option<
829        unsafe extern "C" fn(
830            dsts: *mut CUdeviceptr,
831            srcs: *mut CUdeviceptr,
832            sizes: *mut usize,
833            count: usize,
834            attrs: *mut CUmemcpyAttributes,
835            attrs_idxs: *mut usize,
836            num_attrs: usize,
837            fail_idx: *mut usize,
838            stream: CUstream,
839        ) -> CUresult,
840    >,
841
842    // -- Texture / Surface memory (optional) ----------------------------------
843    /// `cuArrayCreate_v2(pHandle*, pAllocateArray*) -> CUresult`
844    ///
845    /// Allocates a 1-D or 2-D CUDA array. When `None`, CUDA array allocation
846    /// is not supported by the loaded driver.
847    pub cu_array_create_v2: Option<
848        unsafe extern "C" fn(
849            p_handle: *mut CUarray,
850            p_allocate_array: *const CUDA_ARRAY_DESCRIPTOR,
851        ) -> CUresult,
852    >,
853
854    /// `cuArrayDestroy(hArray) -> CUresult`
855    ///
856    /// Frees a CUDA array previously allocated by `cuArrayCreate_v2`.
857    pub cu_array_destroy: Option<unsafe extern "C" fn(h_array: CUarray) -> CUresult>,
858
859    /// `cuArrayGetDescriptor_v2(pArrayDescriptor*, hArray) -> CUresult`
860    ///
861    /// Returns the descriptor of a 1-D or 2-D CUDA array.
862    pub cu_array_get_descriptor_v2: Option<
863        unsafe extern "C" fn(
864            p_array_descriptor: *mut CUDA_ARRAY_DESCRIPTOR,
865            h_array: CUarray,
866        ) -> CUresult,
867    >,
868
869    /// `cuArray3DCreate_v2(pHandle*, pAllocateArray*) -> CUresult`
870    ///
871    /// Allocates a 3-D CUDA array (also supports layered and cubemap arrays).
872    pub cu_array3d_create_v2: Option<
873        unsafe extern "C" fn(
874            p_handle: *mut CUarray,
875            p_allocate_array: *const CUDA_ARRAY3D_DESCRIPTOR,
876        ) -> CUresult,
877    >,
878
879    /// `cuArray3DGetDescriptor_v2(pArrayDescriptor*, hArray) -> CUresult`
880    ///
881    /// Returns the descriptor of a 3-D CUDA array.
882    pub cu_array3d_get_descriptor_v2: Option<
883        unsafe extern "C" fn(
884            p_array_descriptor: *mut CUDA_ARRAY3D_DESCRIPTOR,
885            h_array: CUarray,
886        ) -> CUresult,
887    >,
888
889    /// `cuMemcpyHtoA_v2(dstArray, dstOffset, srcHost*, ByteCount) -> CUresult`
890    ///
891    /// Synchronously copies host memory into a CUDA array.
892    pub cu_memcpy_htoa_v2: Option<
893        unsafe extern "C" fn(
894            dst_array: CUarray,
895            dst_offset: usize,
896            src_host: *const c_void,
897            byte_count: usize,
898        ) -> CUresult,
899    >,
900
901    /// `cuMemcpyAtoH_v2(dstHost*, srcArray, srcOffset, ByteCount) -> CUresult`
902    ///
903    /// Synchronously copies data from a CUDA array into host memory.
904    pub cu_memcpy_atoh_v2: Option<
905        unsafe extern "C" fn(
906            dst_host: *mut c_void,
907            src_array: CUarray,
908            src_offset: usize,
909            byte_count: usize,
910        ) -> CUresult,
911    >,
912
913    /// `cuMemcpyHtoAAsync_v2(dstArray, dstOffset, srcHost*, byteCount, stream) -> CUresult`
914    ///
915    /// Asynchronously copies host memory into a CUDA array on a stream.
916    pub cu_memcpy_htoa_async_v2: Option<
917        unsafe extern "C" fn(
918            dst_array: CUarray,
919            dst_offset: usize,
920            src_host: *const c_void,
921            byte_count: usize,
922            stream: CUstream,
923        ) -> CUresult,
924    >,
925
926    /// `cuMemcpyAtoHAsync_v2(dstHost*, srcArray, srcOffset, byteCount, stream) -> CUresult`
927    ///
928    /// Asynchronously copies data from a CUDA array into host memory on a stream.
929    pub cu_memcpy_atoh_async_v2: Option<
930        unsafe extern "C" fn(
931            dst_host: *mut c_void,
932            src_array: CUarray,
933            src_offset: usize,
934            byte_count: usize,
935            stream: CUstream,
936        ) -> CUresult,
937    >,
938
939    /// `cuTexObjectCreate(pTexObject*, pResDesc*, pTexDesc*, pResViewDesc*) -> CUresult`
940    ///
941    /// Creates a texture object from a resource descriptor, texture descriptor,
942    /// and optional resource-view descriptor (CUDA 5.0+).
943    pub cu_tex_object_create: Option<
944        unsafe extern "C" fn(
945            p_tex_object: *mut CUtexObject,
946            p_res_desc: *const CUDA_RESOURCE_DESC,
947            p_tex_desc: *const CUDA_TEXTURE_DESC,
948            p_res_view_desc: *const CUDA_RESOURCE_VIEW_DESC,
949        ) -> CUresult,
950    >,
951
952    /// `cuTexObjectDestroy(texObject) -> CUresult`
953    ///
954    /// Destroys a texture object created by `cuTexObjectCreate`.
955    pub cu_tex_object_destroy: Option<unsafe extern "C" fn(tex_object: CUtexObject) -> CUresult>,
956
957    /// `cuTexObjectGetResourceDesc(pResDesc*, texObject) -> CUresult`
958    ///
959    /// Returns the resource descriptor of a texture object.
960    pub cu_tex_object_get_resource_desc: Option<
961        unsafe extern "C" fn(
962            p_res_desc: *mut CUDA_RESOURCE_DESC,
963            tex_object: CUtexObject,
964        ) -> CUresult,
965    >,
966
967    /// `cuSurfObjectCreate(pSurfObject*, pResDesc*) -> CUresult`
968    ///
969    /// Creates a surface object from a resource descriptor (CUDA 5.0+).
970    /// The resource type must be `Array` (surface-capable CUDA arrays only).
971    pub cu_surf_object_create: Option<
972        unsafe extern "C" fn(
973            p_surf_object: *mut CUsurfObject,
974            p_res_desc: *const CUDA_RESOURCE_DESC,
975        ) -> CUresult,
976    >,
977
978    /// `cuSurfObjectDestroy(surfObject) -> CUresult`
979    ///
980    /// Destroys a surface object created by `cuSurfObjectCreate`.
981    pub cu_surf_object_destroy: Option<unsafe extern "C" fn(surf_object: CUsurfObject) -> CUresult>,
982
983    // -- JIT linker (optional) ----------------------------------------------
984    /// `cuLinkCreate_v2(numOptions, options*, optionValues**, stateOut*) -> CUresult`
985    ///
986    /// Creates a pending JIT linker invocation.  When `None`, the driver does
987    /// not expose the linker API.
988    pub cu_link_create: Option<
989        unsafe extern "C" fn(
990            num_options: u32,
991            options: *mut CUjit_option,
992            option_values: *mut *mut c_void,
993            state_out: *mut CUlinkState,
994        ) -> CUresult,
995    >,
996
997    /// `cuLinkAddData_v2(state, type, data*, size, name*, numOptions, options*, optionValues**) -> CUresult`
998    ///
999    /// Adds an input PTX/cubin/fatbin to a pending linker invocation.  When
1000    /// `None`, the driver does not expose the linker API.
1001    #[allow(clippy::type_complexity)]
1002    pub cu_link_add_data: Option<
1003        unsafe extern "C" fn(
1004            state: CUlinkState,
1005            input_type: CUjitInputType,
1006            data: *mut c_void,
1007            size: usize,
1008            name: *const c_char,
1009            num_options: u32,
1010            options: *mut CUjit_option,
1011            option_values: *mut *mut c_void,
1012        ) -> CUresult,
1013    >,
1014
1015    /// `cuLinkComplete(state, cubinOut**, sizeOut*) -> CUresult`
1016    ///
1017    /// Finalises a JIT linker invocation and returns the resulting cubin
1018    /// pointer / size.  When `None`, the driver does not expose the linker
1019    /// API.
1020    pub cu_link_complete: Option<
1021        unsafe extern "C" fn(
1022            state: CUlinkState,
1023            cubin_out: *mut *mut c_void,
1024            size_out: *mut usize,
1025        ) -> CUresult,
1026    >,
1027
1028    /// `cuLinkDestroy(state) -> CUresult`
1029    ///
1030    /// Destroys a linker state previously created by `cuLinkCreate`.  When
1031    /// `None`, the driver does not expose the linker API.
1032    pub cu_link_destroy: Option<unsafe extern "C" fn(state: CUlinkState) -> CUresult>,
1033
1034    // -- 2-D memory copy (optional) -----------------------------------------
1035    /// `cuMemcpy2D_v2(pCopy*) -> CUresult`
1036    ///
1037    /// Performs a 2-D memory copy described by [`CUDA_MEMCPY2D`].  When
1038    /// `None`, fall back to issuing per-row 1-D `cuMemcpyXXX_v2` calls.
1039    pub cu_memcpy_2d: Option<unsafe extern "C" fn(p_copy: *const CUDA_MEMCPY2D) -> CUresult>,
1040
1041    // -- Virtual memory management (optional, CUDA 11.2+) -------------------
1042    /// `cuMemAddressReserve(ptr*, size, alignment, addr, flags) -> CUresult`
1043    ///
1044    /// Reserves a contiguous range of virtual addresses on the device for
1045    /// later mapping by `cuMemMap`.  When `None`, the VMM API is not
1046    /// supported.
1047    pub cu_mem_address_reserve: Option<
1048        unsafe extern "C" fn(
1049            ptr: *mut CUdeviceptr,
1050            size: usize,
1051            alignment: usize,
1052            addr: CUdeviceptr,
1053            flags: u64,
1054        ) -> CUresult,
1055    >,
1056
1057    /// `cuMemAddressFree(ptr, size) -> CUresult`
1058    ///
1059    /// Releases a virtual-address range previously obtained from
1060    /// `cuMemAddressReserve`.  When `None`, the VMM API is not supported.
1061    pub cu_mem_address_free:
1062        Option<unsafe extern "C" fn(ptr: CUdeviceptr, size: usize) -> CUresult>,
1063
1064    /// `cuMemCreate(handle*, size, prop*, flags) -> CUresult`
1065    ///
1066    /// Creates a new generic VMM allocation handle.  When `None`, the VMM
1067    /// API is not supported.
1068    pub cu_mem_create: Option<
1069        unsafe extern "C" fn(
1070            handle: *mut CUmemGenericAllocationHandle,
1071            size: usize,
1072            prop: *const CUmemAllocationProp,
1073            flags: u64,
1074        ) -> CUresult,
1075    >,
1076
1077    /// `cuMemRelease(handle) -> CUresult`
1078    ///
1079    /// Releases a generic VMM allocation handle.  When `None`, the VMM
1080    /// API is not supported.
1081    pub cu_mem_release:
1082        Option<unsafe extern "C" fn(handle: CUmemGenericAllocationHandle) -> CUresult>,
1083
1084    /// `cuMemMap(ptr, size, offset, handle, flags) -> CUresult`
1085    ///
1086    /// Maps a VMM allocation onto a previously reserved virtual address
1087    /// range.  When `None`, the VMM API is not supported.
1088    pub cu_mem_map: Option<
1089        unsafe extern "C" fn(
1090            ptr: CUdeviceptr,
1091            size: usize,
1092            offset: usize,
1093            handle: CUmemGenericAllocationHandle,
1094            flags: u64,
1095        ) -> CUresult,
1096    >,
1097
1098    /// `cuMemUnmap(ptr, size) -> CUresult`
1099    ///
1100    /// Unmaps a VMM allocation from a virtual address range.  When `None`,
1101    /// the VMM API is not supported.
1102    pub cu_mem_unmap: Option<unsafe extern "C" fn(ptr: CUdeviceptr, size: usize) -> CUresult>,
1103
1104    /// `cuMemSetAccess(ptr, size, desc*, count) -> CUresult`
1105    ///
1106    /// Sets per-location access permissions for a VMM mapping.  When `None`,
1107    /// the VMM API is not supported.
1108    pub cu_mem_set_access: Option<
1109        unsafe extern "C" fn(
1110            ptr: CUdeviceptr,
1111            size: usize,
1112            desc: *const CUmemAccessDesc,
1113            count: usize,
1114        ) -> CUresult,
1115    >,
1116
1117    // -- Stream-ordered memory pools (optional, CUDA 11.2+) -----------------
1118    /// `cuMemPoolCreate(pool*, poolProps*) -> CUresult`
1119    ///
1120    /// Creates a stream-ordered memory pool.  When `None`, the memory pool
1121    /// API is not supported.
1122    pub cu_mem_pool_create: Option<
1123        unsafe extern "C" fn(
1124            pool: *mut CUmemoryPool,
1125            pool_props: *const CUmemPoolProps,
1126        ) -> CUresult,
1127    >,
1128
1129    /// `cuMemPoolDestroy(pool) -> CUresult`
1130    ///
1131    /// Destroys a stream-ordered memory pool.  When `None`, the memory pool
1132    /// API is not supported.
1133    pub cu_mem_pool_destroy: Option<unsafe extern "C" fn(pool: CUmemoryPool) -> CUresult>,
1134
1135    /// `cuMemAllocFromPoolAsync(dptr*, bytesize, pool, hStream) -> CUresult`
1136    ///
1137    /// Asynchronously allocates memory from a pool on a stream.  When `None`,
1138    /// the memory pool API is not supported.
1139    pub cu_mem_alloc_from_pool_async: Option<
1140        unsafe extern "C" fn(
1141            dptr: *mut CUdeviceptr,
1142            bytesize: usize,
1143            pool: CUmemoryPool,
1144            hstream: CUstream,
1145        ) -> CUresult,
1146    >,
1147
1148    /// `cuMemFreeAsync(dptr, hStream) -> CUresult`
1149    ///
1150    /// Asynchronously frees memory on a stream.  When `None`, the
1151    /// stream-ordered memory API is not supported.
1152    pub cu_mem_free_async:
1153        Option<unsafe extern "C" fn(dptr: CUdeviceptr, hstream: CUstream) -> CUresult>,
1154
1155    /// `cuMemAllocAsync(dptr*, bytesize, hStream) -> CUresult`
1156    ///
1157    /// Asynchronously allocates memory from the current context's default
1158    /// pool on a stream.  When `None`, the stream-ordered memory API is not
1159    /// supported.
1160    pub cu_mem_alloc_async: Option<
1161        unsafe extern "C" fn(
1162            dptr: *mut CUdeviceptr,
1163            bytesize: usize,
1164            hstream: CUstream,
1165        ) -> CUresult,
1166    >,
1167
1168    /// `cuMemPoolTrimTo(pool, minBytesToKeep) -> CUresult`
1169    ///
1170    /// Releases freed memory back to the OS, keeping at least
1171    /// `minBytesToKeep` bytes reserved.  When `None`, the memory pool API
1172    /// is not supported.
1173    pub cu_mem_pool_trim_to:
1174        Option<unsafe extern "C" fn(pool: CUmemoryPool, min_bytes_to_keep: usize) -> CUresult>,
1175
1176    /// `cuMemPoolSetAttribute(pool, attr, value*) -> CUresult`
1177    ///
1178    /// Sets a writable attribute on a memory pool.  When `None`, the memory
1179    /// pool API is not supported.
1180    pub cu_mem_pool_set_attribute: Option<
1181        unsafe extern "C" fn(
1182            pool: CUmemoryPool,
1183            attr: CUmemPoolAttribute,
1184            value: *mut c_void,
1185        ) -> CUresult,
1186    >,
1187
1188    /// `cuMemPoolGetAttribute(pool, attr, value*) -> CUresult`
1189    ///
1190    /// Reads an attribute from a memory pool.  When `None`, the memory pool
1191    /// API is not supported.
1192    pub cu_mem_pool_get_attribute: Option<
1193        unsafe extern "C" fn(
1194            pool: CUmemoryPool,
1195            attr: CUmemPoolAttribute,
1196            value: *mut c_void,
1197        ) -> CUresult,
1198    >,
1199
1200    /// `cuMemPoolSetAccess(pool, map*, count) -> CUresult`
1201    ///
1202    /// Controls the per-device visibility of allocations from a memory pool.
1203    /// When `None`, the memory pool API is not supported.
1204    pub cu_mem_pool_set_access: Option<
1205        unsafe extern "C" fn(
1206            pool: CUmemoryPool,
1207            map: *const CUmemAccessDesc,
1208            count: usize,
1209        ) -> CUresult,
1210    >,
1211
1212    /// `cuDeviceGetDefaultMemPool(pool*, dev) -> CUresult`
1213    ///
1214    /// Returns the default stream-ordered memory pool of a device.  When
1215    /// `None`, the memory pool API is not supported.
1216    pub cu_device_get_default_mem_pool:
1217        Option<unsafe extern "C" fn(pool: *mut CUmemoryPool, dev: CUdevice) -> CUresult>,
1218
1219    // -- CUDA Graph API (optional, CUDA 10.0+) ------------------------------
1220    /// `cuGraphCreate(phGraph*, flags) -> CUresult`
1221    ///
1222    /// Creates an empty CUDA graph.  When `None`, the graph API is not
1223    /// supported by the loaded driver.
1224    pub cu_graph_create:
1225        Option<unsafe extern "C" fn(ph_graph: *mut CUgraph, flags: u32) -> CUresult>,
1226
1227    /// `cuGraphDestroy(hGraph) -> CUresult`
1228    ///
1229    /// Destroys a CUDA graph.  When `None`, the graph API is not supported.
1230    pub cu_graph_destroy: Option<unsafe extern "C" fn(h_graph: CUgraph) -> CUresult>,
1231
1232    /// `cuGraphAddKernelNode(phGraphNode*, hGraph, dependencies*, numDependencies, nodeParams*) -> CUresult`
1233    ///
1234    /// Adds a kernel-launch node to a graph.  When `None`, the graph API is
1235    /// not supported.
1236    pub cu_graph_add_kernel_node: Option<
1237        unsafe extern "C" fn(
1238            ph_graph_node: *mut CUgraphNode,
1239            h_graph: CUgraph,
1240            dependencies: *const CUgraphNode,
1241            num_dependencies: usize,
1242            node_params: *const CUDA_KERNEL_NODE_PARAMS,
1243        ) -> CUresult,
1244    >,
1245
1246    /// `cuGraphAddMemcpyNode(phGraphNode*, hGraph, dependencies*, numDependencies, copyParams*, ctx) -> CUresult`
1247    ///
1248    /// Adds a memory-copy node to a graph.  When `None`, the graph API is
1249    /// not supported.
1250    pub cu_graph_add_memcpy_node: Option<
1251        unsafe extern "C" fn(
1252            ph_graph_node: *mut CUgraphNode,
1253            h_graph: CUgraph,
1254            dependencies: *const CUgraphNode,
1255            num_dependencies: usize,
1256            copy_params: *const CUDA_MEMCPY3D,
1257            ctx: CUcontext,
1258        ) -> CUresult,
1259    >,
1260
1261    /// `cuGraphAddMemsetNode(phGraphNode*, hGraph, dependencies*, numDependencies, memsetParams*, ctx) -> CUresult`
1262    ///
1263    /// Adds a memset node to a graph.  When `None`, the graph API is not
1264    /// supported.
1265    pub cu_graph_add_memset_node: Option<
1266        unsafe extern "C" fn(
1267            ph_graph_node: *mut CUgraphNode,
1268            h_graph: CUgraph,
1269            dependencies: *const CUgraphNode,
1270            num_dependencies: usize,
1271            memset_params: *const CUDA_MEMSET_NODE_PARAMS,
1272            ctx: CUcontext,
1273        ) -> CUresult,
1274    >,
1275
1276    /// `cuGraphAddEmptyNode(phGraphNode*, hGraph, dependencies*, numDependencies) -> CUresult`
1277    ///
1278    /// Adds an empty (no-op) node to a graph.  When `None`, the graph API
1279    /// is not supported.
1280    pub cu_graph_add_empty_node: Option<
1281        unsafe extern "C" fn(
1282            ph_graph_node: *mut CUgraphNode,
1283            h_graph: CUgraph,
1284            dependencies: *const CUgraphNode,
1285            num_dependencies: usize,
1286        ) -> CUresult,
1287    >,
1288
1289    /// `cuGraphInstantiateWithFlags(phGraphExec*, hGraph, flags) -> CUresult`
1290    ///
1291    /// Instantiates a graph into an executable form (CUDA 11.4+).  When
1292    /// `None`, fall back to [`cu_graph_instantiate`](Self::cu_graph_instantiate).
1293    pub cu_graph_instantiate_with_flags: Option<
1294        unsafe extern "C" fn(
1295            ph_graph_exec: *mut CUgraphExec,
1296            h_graph: CUgraph,
1297            flags: u64,
1298        ) -> CUresult,
1299    >,
1300
1301    /// `cuGraphInstantiate_v2(phGraphExec*, hGraph, phErrorNode*, logBuffer*, bufferSize) -> CUresult`
1302    ///
1303    /// Instantiates a graph into an executable form (legacy signature).
1304    /// When `None`, the graph API is not supported.
1305    pub cu_graph_instantiate: Option<
1306        unsafe extern "C" fn(
1307            ph_graph_exec: *mut CUgraphExec,
1308            h_graph: CUgraph,
1309            ph_error_node: *mut CUgraphNode,
1310            log_buffer: *mut c_char,
1311            buffer_size: usize,
1312        ) -> CUresult,
1313    >,
1314
1315    /// `cuGraphExecDestroy(hGraphExec) -> CUresult`
1316    ///
1317    /// Destroys an executable graph.  When `None`, the graph API is not
1318    /// supported.
1319    pub cu_graph_exec_destroy: Option<unsafe extern "C" fn(h_graph_exec: CUgraphExec) -> CUresult>,
1320
1321    /// `cuGraphLaunch(hGraphExec, hStream) -> CUresult`
1322    ///
1323    /// Submits an executable graph to a stream.  When `None`, the graph API
1324    /// is not supported.
1325    pub cu_graph_launch:
1326        Option<unsafe extern "C" fn(h_graph_exec: CUgraphExec, h_stream: CUstream) -> CUresult>,
1327
1328    /// `cuStreamBeginCapture_v2(hStream, mode) -> CUresult`
1329    ///
1330    /// Begins recording the GPU work submitted to `hStream` into a graph.
1331    /// When `None`, the loaded driver predates stream capture (pre-CUDA 10.0).
1332    pub cu_stream_begin_capture:
1333        Option<unsafe extern "C" fn(h_stream: CUstream, mode: CUstreamCaptureMode) -> CUresult>,
1334
1335    /// `cuStreamEndCapture(hStream, phGraph*) -> CUresult`
1336    ///
1337    /// Ends capture on `hStream` and writes the captured `CUgraph` to
1338    /// `phGraph`.  When `None`, stream capture is not supported.
1339    pub cu_stream_end_capture:
1340        Option<unsafe extern "C" fn(h_stream: CUstream, ph_graph: *mut CUgraph) -> CUresult>,
1341
1342    /// `cuStreamIsCapturing(hStream, captureStatus*) -> CUresult`
1343    ///
1344    /// Reports whether `hStream` is currently capturing.
1345    pub cu_stream_is_capturing: Option<
1346        unsafe extern "C" fn(h_stream: CUstream, status: *mut CUstreamCaptureStatus) -> CUresult,
1347    >,
1348
1349    /// `cuGraphGetNodes(hGraph, nodes*, numNodes*) -> CUresult`
1350    ///
1351    /// Queries a graph's nodes; with a null `nodes` pointer it returns only
1352    /// the node count in `*numNodes`.  Used to size a captured graph.
1353    pub cu_graph_get_nodes: Option<
1354        unsafe extern "C" fn(
1355            h_graph: CUgraph,
1356            nodes: *mut CUgraphNode,
1357            num_nodes: *mut usize,
1358        ) -> CUresult,
1359    >,
1360}
1361
1362// SAFETY: All fields are plain function pointers (which are Send + Sync) and
1363// the Library handle is kept alive but never mutated.
1364unsafe impl Send for DriverApi {}
1365unsafe impl Sync for DriverApi {}
1366
1367// ---------------------------------------------------------------------------
1368// DriverApi — construction
1369// ---------------------------------------------------------------------------
1370
1371impl DriverApi {
1372    /// Attempt to dynamically load the CUDA driver shared library and resolve
1373    /// every required symbol.
1374    ///
1375    /// # Platform behaviour
1376    ///
1377    /// * **macOS** — immediately returns [`DriverLoadError::UnsupportedPlatform`].
1378    /// * **Linux** — tries `libcuda.so.1` then `libcuda.so`.
1379    /// * **Windows** — tries `nvcuda.dll`.
1380    ///
1381    /// # Errors
1382    ///
1383    /// * [`DriverLoadError::UnsupportedPlatform`] on macOS.
1384    /// * [`DriverLoadError::LibraryNotFound`] if none of the candidate library
1385    ///   names could be opened.
1386    /// * [`DriverLoadError::SymbolNotFound`] if a required CUDA entry point is
1387    ///   missing from the loaded library.
1388    pub fn load() -> Result<Self, DriverLoadError> {
1389        // macOS: CUDA is not and will not be supported.
1390        #[cfg(target_os = "macos")]
1391        {
1392            Err(DriverLoadError::UnsupportedPlatform)
1393        }
1394
1395        // Linux library search order.
1396        #[cfg(target_os = "linux")]
1397        let lib_names: &[&str] = &["libcuda.so.1", "libcuda.so"];
1398
1399        // Windows library search order.
1400        #[cfg(target_os = "windows")]
1401        let lib_names: &[&str] = &["nvcuda.dll"];
1402
1403        #[cfg(not(target_os = "macos"))]
1404        {
1405            let lib = Self::load_library(lib_names)?;
1406            let api = Self::load_symbols(lib)?;
1407            // `cuInit(0)` must be called before any other CUDA driver API.
1408            // This mirrors what `libcudart` does internally on the first CUDA
1409            // Runtime call. We call it unconditionally here so that all
1410            // `try_driver()` callers get a fully initialised driver without
1411            // each needing to call `cuInit` themselves.
1412            //
1413            // SAFETY: `api.cu_init` was just resolved from the shared library.
1414            // Passing flags=0 is the only documented value.
1415            let rc = unsafe { (api.cu_init)(0) };
1416            if rc != 0 {
1417                // Propagate the error; the OnceLock will store this Err and
1418                // return CudaError::NotInitialized on every subsequent
1419                // try_driver() call — matching behaviour on no-GPU machines.
1420                return Err(DriverLoadError::InitializationFailed { code: rc });
1421            }
1422            Ok(api)
1423        }
1424    }
1425
1426    /// Try each candidate library name in order, returning the first that
1427    /// loads successfully.
1428    ///
1429    /// # Errors
1430    ///
1431    /// Returns [`DriverLoadError::LibraryNotFound`] if **all** candidates
1432    /// fail to load, capturing the last OS-level error message.
1433    #[cfg(not(target_os = "macos"))]
1434    fn load_library(names: &[&str]) -> Result<Library, DriverLoadError> {
1435        let mut last_error = String::new();
1436        for name in names {
1437            // SAFETY: loading a shared library has side-effects (running its
1438            // init routines), but the CUDA driver library is designed for
1439            // this.
1440            match unsafe { Library::new(*name) } {
1441                Ok(lib) => {
1442                    tracing::debug!("loaded CUDA driver library: {name}");
1443                    return Ok(lib);
1444                }
1445                Err(e) => {
1446                    tracing::debug!("failed to load {name}: {e}");
1447                    last_error = e.to_string();
1448                }
1449            }
1450        }
1451
1452        Err(DriverLoadError::LibraryNotFound {
1453            candidates: names.iter().map(|s| (*s).to_string()).collect(),
1454            last_error,
1455        })
1456    }
1457
1458    /// Resolve every required CUDA driver symbol from the loaded library and
1459    /// assemble the [`DriverApi`] function table.
1460    ///
1461    /// # Errors
1462    ///
1463    /// Returns [`DriverLoadError::SymbolNotFound`] if any symbol cannot be
1464    /// resolved.
1465    #[cfg(not(target_os = "macos"))]
1466    fn load_symbols(lib: Library) -> Result<Self, DriverLoadError> {
1467        Ok(Self {
1468            // -- Initialisation ------------------------------------------------
1469            cu_init: load_sym!(lib, "cuInit"),
1470
1471            // -- Version query -------------------------------------------------
1472            cu_driver_get_version: load_sym!(lib, "cuDriverGetVersion"),
1473
1474            // -- Device management ---------------------------------------------
1475            cu_device_get: load_sym!(lib, "cuDeviceGet"),
1476            cu_device_get_count: load_sym!(lib, "cuDeviceGetCount"),
1477            cu_device_get_name: load_sym!(lib, "cuDeviceGetName"),
1478            cu_device_get_attribute: load_sym!(lib, "cuDeviceGetAttribute"),
1479            cu_device_total_mem_v2: load_sym!(lib, "cuDeviceTotalMem_v2"),
1480            cu_device_can_access_peer: load_sym!(lib, "cuDeviceCanAccessPeer"),
1481
1482            // -- Primary context management ------------------------------------
1483            cu_device_primary_ctx_retain: load_sym!(lib, "cuDevicePrimaryCtxRetain"),
1484            cu_device_primary_ctx_release_v2: load_sym!(lib, "cuDevicePrimaryCtxRelease_v2"),
1485            cu_device_primary_ctx_set_flags_v2: load_sym!(lib, "cuDevicePrimaryCtxSetFlags_v2"),
1486            cu_device_primary_ctx_get_state: load_sym!(lib, "cuDevicePrimaryCtxGetState"),
1487            cu_device_primary_ctx_reset_v2: load_sym!(lib, "cuDevicePrimaryCtxReset_v2"),
1488
1489            // -- Context management --------------------------------------------
1490            cu_ctx_create_v2: load_sym!(lib, "cuCtxCreate_v2"),
1491            cu_ctx_destroy_v2: load_sym!(lib, "cuCtxDestroy_v2"),
1492            cu_ctx_set_current: load_sym!(lib, "cuCtxSetCurrent"),
1493            cu_ctx_get_current: load_sym!(lib, "cuCtxGetCurrent"),
1494            cu_ctx_pop_current_v2: load_sym!(lib, "cuCtxPopCurrent_v2"),
1495            cu_ctx_synchronize: load_sym!(lib, "cuCtxSynchronize"),
1496
1497            // -- Module management ---------------------------------------------
1498            cu_module_load_data: load_sym!(lib, "cuModuleLoadData"),
1499            cu_module_load_data_ex: load_sym!(lib, "cuModuleLoadDataEx"),
1500            cu_module_get_function: load_sym!(lib, "cuModuleGetFunction"),
1501            cu_module_unload: load_sym!(lib, "cuModuleUnload"),
1502
1503            // -- Memory management ---------------------------------------------
1504            cu_mem_alloc_v2: load_sym!(lib, "cuMemAlloc_v2"),
1505            cu_mem_free_v2: load_sym!(lib, "cuMemFree_v2"),
1506            cu_memcpy_htod_v2: load_sym!(lib, "cuMemcpyHtoD_v2"),
1507            cu_memcpy_dtoh_v2: load_sym!(lib, "cuMemcpyDtoH_v2"),
1508            cu_memcpy_dtod_v2: load_sym!(lib, "cuMemcpyDtoD_v2"),
1509            cu_memcpy_htod_async_v2: load_sym!(lib, "cuMemcpyHtoDAsync_v2"),
1510            cu_memcpy_dtoh_async_v2: load_sym!(lib, "cuMemcpyDtoHAsync_v2"),
1511            cu_mem_alloc_host_v2: load_sym!(lib, "cuMemAllocHost_v2"),
1512            cu_mem_free_host: load_sym!(lib, "cuMemFreeHost"),
1513            cu_mem_alloc_managed: load_sym!(lib, "cuMemAllocManaged"),
1514            cu_memset_d8_v2: load_sym!(lib, "cuMemsetD8_v2"),
1515            cu_memset_d32_v2: load_sym!(lib, "cuMemsetD32_v2"),
1516            cu_mem_get_info_v2: load_sym!(lib, "cuMemGetInfo_v2"),
1517            cu_mem_host_register_v2: load_sym!(lib, "cuMemHostRegister_v2"),
1518            cu_mem_host_unregister: load_sym!(lib, "cuMemHostUnregister"),
1519            cu_mem_host_get_device_pointer_v2: load_sym!(lib, "cuMemHostGetDevicePointer_v2"),
1520            cu_pointer_get_attribute: load_sym!(lib, "cuPointerGetAttribute"),
1521            cu_mem_advise: load_sym!(lib, "cuMemAdvise"),
1522            cu_mem_prefetch_async: load_sym!(lib, "cuMemPrefetchAsync"),
1523
1524            // -- Stream management ---------------------------------------------
1525            cu_stream_create: load_sym!(lib, "cuStreamCreate"),
1526            cu_stream_create_with_priority: load_sym!(lib, "cuStreamCreateWithPriority"),
1527            cu_stream_destroy_v2: load_sym!(lib, "cuStreamDestroy_v2"),
1528            cu_stream_synchronize: load_sym!(lib, "cuStreamSynchronize"),
1529            cu_stream_wait_event: load_sym!(lib, "cuStreamWaitEvent"),
1530            cu_stream_query: load_sym!(lib, "cuStreamQuery"),
1531            cu_stream_get_priority: load_sym!(lib, "cuStreamGetPriority"),
1532            cu_stream_get_flags: load_sym!(lib, "cuStreamGetFlags"),
1533
1534            // -- Event management ----------------------------------------------
1535            cu_event_create: load_sym!(lib, "cuEventCreate"),
1536            cu_event_destroy_v2: load_sym!(lib, "cuEventDestroy_v2"),
1537            cu_event_record: load_sym!(lib, "cuEventRecord"),
1538            cu_event_query: load_sym!(lib, "cuEventQuery"),
1539            cu_event_synchronize: load_sym!(lib, "cuEventSynchronize"),
1540            cu_event_elapsed_time: load_sym!(lib, "cuEventElapsedTime"),
1541            cu_event_record_with_flags: load_sym_optional!(lib, "cuEventRecordWithFlags"),
1542
1543            // -- Peer memory access -------------------------------------------
1544            cu_memcpy_peer: load_sym!(lib, "cuMemcpyPeer"),
1545            cu_memcpy_peer_async: load_sym!(lib, "cuMemcpyPeerAsync"),
1546            cu_ctx_enable_peer_access: load_sym!(lib, "cuCtxEnablePeerAccess"),
1547            cu_ctx_disable_peer_access: load_sym!(lib, "cuCtxDisablePeerAccess"),
1548
1549            // -- Kernel launch -------------------------------------------------
1550            cu_launch_kernel: load_sym!(lib, "cuLaunchKernel"),
1551            cu_launch_cooperative_kernel: load_sym!(lib, "cuLaunchCooperativeKernel"),
1552            cu_launch_cooperative_kernel_multi_device: load_sym!(
1553                lib,
1554                "cuLaunchCooperativeKernelMultiDevice"
1555            ),
1556
1557            // -- Occupancy -----------------------------------------------------
1558            cu_occupancy_max_active_blocks_per_multiprocessor: load_sym!(
1559                lib,
1560                "cuOccupancyMaxActiveBlocksPerMultiprocessor"
1561            ),
1562            cu_occupancy_max_potential_block_size: load_sym!(
1563                lib,
1564                "cuOccupancyMaxPotentialBlockSize"
1565            ),
1566            cu_occupancy_max_active_blocks_per_multiprocessor_with_flags: load_sym!(
1567                lib,
1568                "cuOccupancyMaxActiveBlocksPerMultiprocessorWithFlags"
1569            ),
1570
1571            // -- Memory management (optional) ---------------------------------
1572            cu_memcpy_dtod_async_v2: load_sym_optional!(lib, "cuMemcpyDtoDAsync_v2"),
1573            cu_memset_d16_v2: load_sym_optional!(lib, "cuMemsetD16_v2"),
1574            cu_memset_d32_async: load_sym_optional!(lib, "cuMemsetD32Async"),
1575
1576            // -- Context management (optional) --------------------------------
1577            cu_ctx_get_limit: load_sym_optional!(lib, "cuCtxGetLimit"),
1578            cu_ctx_set_limit: load_sym_optional!(lib, "cuCtxSetLimit"),
1579            cu_ctx_get_cache_config: load_sym_optional!(lib, "cuCtxGetCacheConfig"),
1580            cu_ctx_set_cache_config: load_sym_optional!(lib, "cuCtxSetCacheConfig"),
1581            cu_ctx_get_shared_mem_config: load_sym_optional!(lib, "cuCtxGetSharedMemConfig"),
1582            cu_ctx_set_shared_mem_config: load_sym_optional!(lib, "cuCtxSetSharedMemConfig"),
1583
1584            // -- Function attributes (optional) -------------------------------
1585            cu_func_get_attribute: load_sym_optional!(lib, "cuFuncGetAttribute"),
1586            cu_func_set_cache_config: load_sym_optional!(lib, "cuFuncSetCacheConfig"),
1587            cu_func_set_shared_mem_config: load_sym_optional!(lib, "cuFuncSetSharedMemConfig"),
1588            cu_func_set_attribute: load_sym_optional!(lib, "cuFuncSetAttribute"),
1589
1590            // -- Profiler (optional) ------------------------------------------
1591            cu_profiler_start: load_sym_optional!(lib, "cuProfilerStart"),
1592            cu_profiler_stop: load_sym_optional!(lib, "cuProfilerStop"),
1593
1594            // -- CUDA 12.x extended launch (optional) -------------------------
1595            cu_launch_kernel_ex: load_sym_optional!(lib, "cuLaunchKernelEx"),
1596            cu_tensor_map_encode_tiled: load_sym_optional!(lib, "cuTensorMapEncodeTiled"),
1597
1598            // -- CUDA 12.8+ extended API (optional) ---------------------------
1599            cu_tensor_map_encode_tiled_memref: load_sym_optional!(
1600                lib,
1601                "cuTensorMapEncodeTiledMemref"
1602            ),
1603            cu_kernel_get_library: load_sym_optional!(lib, "cuKernelGetLibrary"),
1604            cu_multicast_get_granularity: load_sym_optional!(lib, "cuMulticastGetGranularity"),
1605            cu_multicast_create: load_sym_optional!(lib, "cuMulticastCreate"),
1606            cu_multicast_add_device: load_sym_optional!(lib, "cuMulticastAddDevice"),
1607            cu_memcpy_batch_async: load_sym_optional!(lib, "cuMemcpyBatchAsync"),
1608
1609            // -- Texture / Surface memory (optional) ---------------------------
1610            cu_array_create_v2: load_sym_optional!(lib, "cuArrayCreate_v2"),
1611            cu_array_destroy: load_sym_optional!(lib, "cuArrayDestroy"),
1612            cu_array_get_descriptor_v2: load_sym_optional!(lib, "cuArrayGetDescriptor_v2"),
1613            cu_array3d_create_v2: load_sym_optional!(lib, "cuArray3DCreate_v2"),
1614            cu_array3d_get_descriptor_v2: load_sym_optional!(lib, "cuArray3DGetDescriptor_v2"),
1615            cu_memcpy_htoa_v2: load_sym_optional!(lib, "cuMemcpyHtoA_v2"),
1616            cu_memcpy_atoh_v2: load_sym_optional!(lib, "cuMemcpyAtoH_v2"),
1617            cu_memcpy_htoa_async_v2: load_sym_optional!(lib, "cuMemcpyHtoAAsync_v2"),
1618            cu_memcpy_atoh_async_v2: load_sym_optional!(lib, "cuMemcpyAtoHAsync_v2"),
1619            cu_tex_object_create: load_sym_optional!(lib, "cuTexObjectCreate"),
1620            cu_tex_object_destroy: load_sym_optional!(lib, "cuTexObjectDestroy"),
1621            cu_tex_object_get_resource_desc: load_sym_optional!(lib, "cuTexObjectGetResourceDesc"),
1622            cu_surf_object_create: load_sym_optional!(lib, "cuSurfObjectCreate"),
1623            cu_surf_object_destroy: load_sym_optional!(lib, "cuSurfObjectDestroy"),
1624
1625            // -- JIT linker (optional) ----------------------------------------
1626            cu_link_create: load_sym_optional!(lib, "cuLinkCreate_v2"),
1627            cu_link_add_data: load_sym_optional!(lib, "cuLinkAddData_v2"),
1628            cu_link_complete: load_sym_optional!(lib, "cuLinkComplete"),
1629            cu_link_destroy: load_sym_optional!(lib, "cuLinkDestroy"),
1630
1631            // -- 2-D memory copy (optional) -----------------------------------
1632            cu_memcpy_2d: load_sym_optional!(lib, "cuMemcpy2D_v2"),
1633
1634            // -- VMM (optional, CUDA 11.2+) -----------------------------------
1635            cu_mem_address_reserve: load_sym_optional!(lib, "cuMemAddressReserve"),
1636            cu_mem_address_free: load_sym_optional!(lib, "cuMemAddressFree"),
1637            cu_mem_create: load_sym_optional!(lib, "cuMemCreate"),
1638            cu_mem_release: load_sym_optional!(lib, "cuMemRelease"),
1639            cu_mem_map: load_sym_optional!(lib, "cuMemMap"),
1640            cu_mem_unmap: load_sym_optional!(lib, "cuMemUnmap"),
1641            cu_mem_set_access: load_sym_optional!(lib, "cuMemSetAccess"),
1642
1643            // -- Stream-ordered memory pools (optional, CUDA 11.2+) -----------
1644            cu_mem_pool_create: load_sym_optional!(lib, "cuMemPoolCreate"),
1645            cu_mem_pool_destroy: load_sym_optional!(lib, "cuMemPoolDestroy"),
1646            cu_mem_alloc_from_pool_async: load_sym_optional!(lib, "cuMemAllocFromPoolAsync"),
1647            cu_mem_free_async: load_sym_optional!(lib, "cuMemFreeAsync"),
1648            cu_mem_alloc_async: load_sym_optional!(lib, "cuMemAllocAsync"),
1649            cu_mem_pool_trim_to: load_sym_optional!(lib, "cuMemPoolTrimTo"),
1650            cu_mem_pool_set_attribute: load_sym_optional!(lib, "cuMemPoolSetAttribute"),
1651            cu_mem_pool_get_attribute: load_sym_optional!(lib, "cuMemPoolGetAttribute"),
1652            cu_mem_pool_set_access: load_sym_optional!(lib, "cuMemPoolSetAccess"),
1653            cu_device_get_default_mem_pool: load_sym_optional!(lib, "cuDeviceGetDefaultMemPool"),
1654
1655            // -- CUDA Graph API (optional, CUDA 10.0+) ------------------------
1656            cu_graph_create: load_sym_optional!(lib, "cuGraphCreate"),
1657            cu_graph_destroy: load_sym_optional!(lib, "cuGraphDestroy"),
1658            cu_graph_add_kernel_node: load_sym_optional!(lib, "cuGraphAddKernelNode"),
1659            cu_graph_add_memcpy_node: load_sym_optional!(lib, "cuGraphAddMemcpyNode"),
1660            cu_graph_add_memset_node: load_sym_optional!(lib, "cuGraphAddMemsetNode"),
1661            cu_graph_add_empty_node: load_sym_optional!(lib, "cuGraphAddEmptyNode"),
1662            cu_graph_instantiate_with_flags: load_sym_optional!(lib, "cuGraphInstantiateWithFlags"),
1663            cu_graph_instantiate: load_sym_optional!(lib, "cuGraphInstantiate_v2"),
1664            cu_graph_exec_destroy: load_sym_optional!(lib, "cuGraphExecDestroy"),
1665            cu_graph_launch: load_sym_optional!(lib, "cuGraphLaunch"),
1666            cu_stream_begin_capture: load_sym_optional!(lib, "cuStreamBeginCapture_v2"),
1667            cu_stream_end_capture: load_sym_optional!(lib, "cuStreamEndCapture"),
1668            cu_stream_is_capturing: load_sym_optional!(lib, "cuStreamIsCapturing"),
1669            cu_graph_get_nodes: load_sym_optional!(lib, "cuGraphGetNodes"),
1670
1671            // Keep the library handle alive.
1672            _lib: lib,
1673        })
1674    }
1675}
1676
1677// ---------------------------------------------------------------------------
1678// Global accessor
1679// ---------------------------------------------------------------------------
1680
1681/// Get a reference to the lazily-loaded CUDA driver API function table.
1682///
1683/// On the first call, this function dynamically loads the CUDA shared library
1684/// and resolves all required symbols.  Subsequent calls return the cached
1685/// result with only an atomic load.
1686///
1687/// # Errors
1688///
1689/// Returns [`CudaError::NotInitialized`] if the driver could not be loaded —
1690/// for instance, on macOS, or on a system without an NVIDIA GPU driver
1691/// installed.
1692///
1693/// # Examples
1694///
1695/// ```rust,no_run
1696/// # use oxicuda_driver::loader::try_driver;
1697/// let api = try_driver()?;
1698/// let result = unsafe { (api.cu_init)(0) };
1699/// # Ok::<(), oxicuda_driver::error::CudaError>(())
1700/// ```
1701pub fn try_driver() -> CudaResult<&'static DriverApi> {
1702    let result = DRIVER.get_or_init(DriverApi::load);
1703    match result {
1704        Ok(api) => Ok(api),
1705        Err(e) => {
1706            // Emit the underlying diagnostic once (which library/symbol or
1707            // cuInit code failed) so the reason is not lost behind the coarse
1708            // `NotInitialized` mapping. Gated by `Once` to avoid log spam from
1709            // the many `try_driver()` call sites hitting the cached error.
1710            static LOGGED: std::sync::Once = std::sync::Once::new();
1711            LOGGED.call_once(|| {
1712                tracing::warn!(error = %e, "CUDA driver load failed");
1713            });
1714            Err(CudaError::NotInitialized)
1715        }
1716    }
1717}
1718
1719/// Returns the cached [`DriverLoadError`] from the first driver-load attempt,
1720/// if the driver failed to load.
1721///
1722/// This lets callers programmatically inspect *why* the driver is unavailable
1723/// (which library was missing, which symbol failed to resolve, or the `cuInit`
1724/// error code) rather than only seeing the coarse
1725/// [`CudaError::NotInitialized`] returned by [`try_driver`]. Returns `None` if
1726/// the driver loaded successfully or has not been probed yet.
1727pub fn driver_load_error() -> Option<&'static DriverLoadError> {
1728    DRIVER.get().and_then(|r| r.as_ref().err())
1729}
1730
1731// ---------------------------------------------------------------------------
1732// Tests
1733// ---------------------------------------------------------------------------
1734
1735#[cfg(test)]
1736mod tests {
1737    use super::*;
1738
1739    /// On macOS, loading should always fail with `UnsupportedPlatform`.
1740    #[cfg(target_os = "macos")]
1741    #[test]
1742    fn load_returns_unsupported_on_macos() {
1743        let result = DriverApi::load();
1744        assert!(result.is_err(), "expected Err on macOS");
1745        let err = match result {
1746            Err(e) => e,
1747            Ok(_) => panic!("expected Err on macOS"),
1748        };
1749        assert!(
1750            matches!(err, DriverLoadError::UnsupportedPlatform),
1751            "expected UnsupportedPlatform, got {err:?}"
1752        );
1753    }
1754
1755    /// `try_driver` should return `Err(NotInitialized)` on platforms without
1756    /// a CUDA driver (including macOS).
1757    #[cfg(target_os = "macos")]
1758    #[test]
1759    fn try_driver_returns_not_initialized_on_macos() {
1760        let result = try_driver();
1761        assert!(result.is_err(), "expected Err on macOS");
1762        let err = match result {
1763            Err(e) => e,
1764            Ok(_) => panic!("expected Err on macOS"),
1765        };
1766        assert!(
1767            matches!(err, CudaError::NotInitialized),
1768            "expected NotInitialized, got {err:?}"
1769        );
1770    }
1771
1772    // -----------------------------------------------------------------------
1773    // Task 1 — CUDA 12.8+ DriverApi struct layout tests
1774    //
1775    // These tests verify that the DriverApi struct contains the expected
1776    // Option<fn(...)> fields for the new CUDA 12.8+ API entry points.
1777    // They compile and run without a GPU because they only inspect type
1778    // layout and field presence, never calling the function pointers.
1779    // -----------------------------------------------------------------------
1780
1781    /// Verify that the `cu_tensor_map_encode_tiled_memref` field exists and
1782    /// is an `Option` type.  The driver will return `None` on older versions.
1783    #[test]
1784    fn driver_v12_8_api_fields_present() {
1785        // The simplest way to prove a field exists at the correct type is to
1786        // construct a value that fits in that position.  We use a local
1787        // DriverApi value on macOS (where load() always returns Err) by
1788        // manufacturing a dummy function pointer and verifying the type
1789        // annotation compiles.
1790        //
1791        // On non-macOS platforms we simply verify the field is accessible on
1792        // the type via a None literal assignment (compile-time check).
1793        type TensorMapEncodeTiledFn = unsafe extern "C" fn(
1794            tensor_map: *mut std::ffi::c_void,
1795            tensor_data_type: u32,
1796            tensor_rank: u32,
1797            global_address: *mut std::ffi::c_void,
1798            global_dim: *const u64,
1799            global_strides: *const u64,
1800            box_dim: *const u32,
1801            element_strides: *const u32,
1802            interleave: u32,
1803            swizzle: u32,
1804            l2_promotion: u32,
1805            oob_fill: u32,
1806            flags: u64,
1807        ) -> CUresult;
1808        let _none: Option<TensorMapEncodeTiledFn> = None;
1809        // Field name check: accessing the field compiles only if it exists.
1810        // We use a trait-object-based field-name probe: the macro produces a
1811        // compile error if the identifier does not exist.
1812        let _field_exists = |api: &DriverApi| api.cu_tensor_map_encode_tiled_memref.is_none();
1813        // Suppress unused variable warnings.
1814        let _ = _none;
1815        let _ = _field_exists;
1816    }
1817
1818    /// Verify that `cu_multicast_create` and `cu_multicast_add_device` fields
1819    /// exist with the correct Option<fn(...)> types (CUDA 12.8+ multicast).
1820    #[test]
1821    fn driver_v12_8_multicast_fields_present() {
1822        let _probe_create = |api: &DriverApi| api.cu_multicast_create.is_none();
1823        let _probe_add = |api: &DriverApi| api.cu_multicast_add_device.is_none();
1824        let _probe_gran = |api: &DriverApi| api.cu_multicast_get_granularity.is_none();
1825        let _ = (_probe_create, _probe_add, _probe_gran);
1826    }
1827
1828    /// Verify that `cu_memcpy_batch_async` field exists with the correct
1829    /// Option<fn(...)> type (CUDA 12.8+ batch memcpy).
1830    #[test]
1831    fn driver_v12_8_batch_memcpy_field_present() {
1832        let _probe = |api: &DriverApi| api.cu_memcpy_batch_async.is_none();
1833        let _ = _probe;
1834    }
1835
1836    /// Verify that `cu_kernel_get_library` field exists (CUDA 12.8+ JIT libs).
1837    #[test]
1838    fn driver_v12_8_kernel_get_library_field_present() {
1839        let _probe = |api: &DriverApi| api.cu_kernel_get_library.is_none();
1840        let _ = _probe;
1841    }
1842
1843    // -----------------------------------------------------------------------
1844    // Wave 1 — Extended Driver API field-presence tests
1845    // -----------------------------------------------------------------------
1846
1847    /// All four `cuLink*` JIT linker fields are present and `Option`.
1848    #[test]
1849    fn driver_link_api_fields_present() {
1850        let _probe_create = |api: &DriverApi| api.cu_link_create.is_none();
1851        let _probe_add = |api: &DriverApi| api.cu_link_add_data.is_none();
1852        let _probe_complete = |api: &DriverApi| api.cu_link_complete.is_none();
1853        let _probe_destroy = |api: &DriverApi| api.cu_link_destroy.is_none();
1854        let _ = (_probe_create, _probe_add, _probe_complete, _probe_destroy);
1855    }
1856
1857    /// The `cuMemcpy2D_v2` field is present and `Option`.
1858    #[test]
1859    fn driver_memcpy_2d_field_present() {
1860        let _probe = |api: &DriverApi| api.cu_memcpy_2d.is_none();
1861        let _ = _probe;
1862    }
1863
1864    /// All seven VMM (CUDA 11.2+) fields are present and `Option`.
1865    #[test]
1866    fn driver_vmm_api_fields_present() {
1867        let _probe_reserve = |api: &DriverApi| api.cu_mem_address_reserve.is_none();
1868        let _probe_free = |api: &DriverApi| api.cu_mem_address_free.is_none();
1869        let _probe_create = |api: &DriverApi| api.cu_mem_create.is_none();
1870        let _probe_release = |api: &DriverApi| api.cu_mem_release.is_none();
1871        let _probe_map = |api: &DriverApi| api.cu_mem_map.is_none();
1872        let _probe_unmap = |api: &DriverApi| api.cu_mem_unmap.is_none();
1873        let _probe_set_access = |api: &DriverApi| api.cu_mem_set_access.is_none();
1874        let _ = (
1875            _probe_reserve,
1876            _probe_free,
1877            _probe_create,
1878            _probe_release,
1879            _probe_map,
1880            _probe_unmap,
1881            _probe_set_access,
1882        );
1883    }
1884
1885    /// All four memory-pool (CUDA 11.2+) fields are present and `Option`.
1886    #[test]
1887    fn driver_mem_pool_api_fields_present() {
1888        let _probe_create = |api: &DriverApi| api.cu_mem_pool_create.is_none();
1889        let _probe_destroy = |api: &DriverApi| api.cu_mem_pool_destroy.is_none();
1890        let _probe_alloc = |api: &DriverApi| api.cu_mem_alloc_from_pool_async.is_none();
1891        let _probe_free = |api: &DriverApi| api.cu_mem_free_async.is_none();
1892        let _ = (_probe_create, _probe_destroy, _probe_alloc, _probe_free);
1893    }
1894
1895    /// The extended stream-ordered memory-pool fields are present and `Option`.
1896    #[test]
1897    fn driver_stream_ordered_pool_extended_fields_present() {
1898        let _probe_alloc_async = |api: &DriverApi| api.cu_mem_alloc_async.is_none();
1899        let _probe_trim = |api: &DriverApi| api.cu_mem_pool_trim_to.is_none();
1900        let _probe_set_attr = |api: &DriverApi| api.cu_mem_pool_set_attribute.is_none();
1901        let _probe_get_attr = |api: &DriverApi| api.cu_mem_pool_get_attribute.is_none();
1902        let _probe_set_access = |api: &DriverApi| api.cu_mem_pool_set_access.is_none();
1903        let _probe_default = |api: &DriverApi| api.cu_device_get_default_mem_pool.is_none();
1904        let _ = (
1905            _probe_alloc_async,
1906            _probe_trim,
1907            _probe_set_attr,
1908            _probe_get_attr,
1909            _probe_set_access,
1910            _probe_default,
1911        );
1912    }
1913
1914    /// All CUDA Graph API fields are present and `Option`.
1915    #[test]
1916    fn driver_graph_api_fields_present() {
1917        let _probe_create = |api: &DriverApi| api.cu_graph_create.is_none();
1918        let _probe_destroy = |api: &DriverApi| api.cu_graph_destroy.is_none();
1919        let _probe_kernel = |api: &DriverApi| api.cu_graph_add_kernel_node.is_none();
1920        let _probe_memcpy = |api: &DriverApi| api.cu_graph_add_memcpy_node.is_none();
1921        let _probe_memset = |api: &DriverApi| api.cu_graph_add_memset_node.is_none();
1922        let _probe_empty = |api: &DriverApi| api.cu_graph_add_empty_node.is_none();
1923        let _probe_inst_flags = |api: &DriverApi| api.cu_graph_instantiate_with_flags.is_none();
1924        let _probe_inst = |api: &DriverApi| api.cu_graph_instantiate.is_none();
1925        let _probe_exec_destroy = |api: &DriverApi| api.cu_graph_exec_destroy.is_none();
1926        let _probe_launch = |api: &DriverApi| api.cu_graph_launch.is_none();
1927        let _ = (
1928            _probe_create,
1929            _probe_destroy,
1930            _probe_kernel,
1931            _probe_memcpy,
1932            _probe_memset,
1933            _probe_empty,
1934            _probe_inst_flags,
1935            _probe_inst,
1936            _probe_exec_destroy,
1937            _probe_launch,
1938        );
1939    }
1940}