Skip to main content

oxicuda_driver/
error.rs

1//! Error types for the OxiCUDA driver crate.
2//!
3//! This module provides [`CudaError`], the primary error type returned by
4//! driver API wrappers, [`DriverLoadError`] for library-loading failures,
5//! the [`check`] function for converting raw result codes, and the
6//! `cuda_call!` macro for ergonomic unsafe FFI calls.
7
8use crate::ffi;
9use crate::module::JitLog;
10
11// =========================================================================
12// CudaError — one variant per CUDA Driver API error code
13// =========================================================================
14
15/// Primary error type for CUDA driver API calls.
16///
17/// Each variant maps to a specific `CUresult` code from the CUDA Driver API.
18/// The [`Unknown`](CudaError::Unknown) variant is the catch-all for codes not
19/// explicitly listed.
20///
21/// Note: this enum intentionally does **not** implement `Copy` because the
22/// [`JitFailed`](CudaError::JitFailed) variant carries heap-allocated
23/// diagnostic data.
24#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
25pub enum CudaError {
26    // ----- Basic errors (1-8) -----
27    /// `CUDA_ERROR_INVALID_VALUE` (1)
28    #[error("CUDA: invalid value")]
29    InvalidValue,
30
31    /// `CUDA_ERROR_OUT_OF_MEMORY` (2)
32    #[error("CUDA: out of device memory")]
33    OutOfMemory,
34
35    /// `CUDA_ERROR_NOT_INITIALIZED` (3)
36    #[error("CUDA: not initialized")]
37    NotInitialized,
38
39    /// `CUDA_ERROR_DEINITIALIZED` (4)
40    #[error("CUDA: deinitialized")]
41    Deinitialized,
42
43    /// `CUDA_ERROR_PROFILER_DISABLED` (5)
44    #[error("CUDA: profiler disabled")]
45    ProfilerDisabled,
46
47    /// `CUDA_ERROR_PROFILER_NOT_INITIALIZED` (6)
48    #[error("CUDA: profiler not initialized (deprecated)")]
49    ProfilerNotInitialized,
50
51    /// `CUDA_ERROR_PROFILER_ALREADY_STARTED` (7)
52    #[error("CUDA: profiler already started (deprecated)")]
53    ProfilerAlreadyStarted,
54
55    /// `CUDA_ERROR_PROFILER_ALREADY_STOPPED` (8)
56    #[error("CUDA: profiler already stopped (deprecated)")]
57    ProfilerAlreadyStopped,
58
59    // ----- Stub / unavailable (34, 46) -----
60    /// `CUDA_ERROR_STUB_LIBRARY` (34)
61    #[error("CUDA: stub library loaded instead of real driver")]
62    StubLibrary,
63
64    /// `CUDA_ERROR_DEVICE_UNAVAILABLE` (46)
65    #[error("CUDA: device unavailable")]
66    DeviceUnavailable,
67
68    // ----- Device errors (100-102) -----
69    /// `CUDA_ERROR_NO_DEVICE` (100)
70    #[error("CUDA: no device found")]
71    NoDevice,
72
73    /// `CUDA_ERROR_INVALID_DEVICE` (101)
74    #[error("CUDA: invalid device")]
75    InvalidDevice,
76
77    /// `CUDA_ERROR_DEVICE_NOT_LICENSED` (102)
78    #[error("CUDA: device not licensed")]
79    DeviceNotLicensed,
80
81    // ----- Image / context / map errors (200-225) -----
82    /// `CUDA_ERROR_INVALID_IMAGE` (200)
83    #[error("CUDA: invalid PTX/cubin image")]
84    InvalidImage,
85
86    /// `CUDA_ERROR_INVALID_CONTEXT` (201)
87    #[error("CUDA: invalid context")]
88    InvalidContext,
89
90    /// `CUDA_ERROR_CONTEXT_ALREADY_CURRENT` (202)
91    #[error("CUDA: context already current (deprecated)")]
92    ContextAlreadyCurrent,
93
94    /// `CUDA_ERROR_MAP_FAILED` (205)
95    #[error("CUDA: map failed")]
96    MapFailed,
97
98    /// `CUDA_ERROR_UNMAP_FAILED` (206)
99    #[error("CUDA: unmap failed")]
100    UnmapFailed,
101
102    /// `CUDA_ERROR_ARRAY_IS_MAPPED` (207)
103    #[error("CUDA: array is mapped")]
104    ArrayIsMapped,
105
106    /// `CUDA_ERROR_ALREADY_MAPPED` (208)
107    #[error("CUDA: already mapped")]
108    AlreadyMapped,
109
110    /// `CUDA_ERROR_NO_BINARY_FOR_GPU` (209)
111    #[error("CUDA: no binary for GPU")]
112    NoBinaryForGpu,
113
114    /// `CUDA_ERROR_ALREADY_ACQUIRED` (210)
115    #[error("CUDA: already acquired")]
116    AlreadyAcquired,
117
118    /// `CUDA_ERROR_NOT_MAPPED` (211)
119    #[error("CUDA: not mapped")]
120    NotMapped,
121
122    /// `CUDA_ERROR_NOT_MAPPED_AS_ARRAY` (212)
123    #[error("CUDA: not mapped as array")]
124    NotMappedAsArray,
125
126    /// `CUDA_ERROR_NOT_MAPPED_AS_POINTER` (213)
127    #[error("CUDA: not mapped as pointer")]
128    NotMappedAsPointer,
129
130    /// `CUDA_ERROR_ECC_UNCORRECTABLE` (214)
131    #[error("CUDA: uncorrectable ECC error")]
132    EccUncorrectable,
133
134    /// `CUDA_ERROR_UNSUPPORTED_LIMIT` (215)
135    #[error("CUDA: unsupported limit")]
136    UnsupportedLimit,
137
138    /// `CUDA_ERROR_CONTEXT_ALREADY_IN_USE` (216)
139    #[error("CUDA: context already in use")]
140    ContextAlreadyInUse,
141
142    /// `CUDA_ERROR_PEER_ACCESS_UNSUPPORTED` (217)
143    #[error("CUDA: peer access unsupported")]
144    PeerAccessUnsupported,
145
146    /// `CUDA_ERROR_INVALID_PTX` (218)
147    #[error("CUDA: invalid PTX")]
148    InvalidPtx,
149
150    /// `CUDA_ERROR_INVALID_GRAPHICS_CONTEXT` (219)
151    #[error("CUDA: invalid graphics context")]
152    InvalidGraphicsContext,
153
154    /// `CUDA_ERROR_NVLINK_UNCORRECTABLE` (220)
155    #[error("CUDA: NVLINK uncorrectable error")]
156    NvlinkUncorrectable,
157
158    /// `CUDA_ERROR_JIT_COMPILER_NOT_FOUND` (221)
159    #[error("CUDA: JIT compiler not found")]
160    JitCompilerNotFound,
161
162    /// `CUDA_ERROR_UNSUPPORTED_PTX_VERSION` (222)
163    #[error("CUDA: unsupported PTX version")]
164    UnsupportedPtxVersion,
165
166    /// `CUDA_ERROR_JIT_COMPILATION_DISABLED` (223)
167    #[error("CUDA: JIT compilation disabled")]
168    JitCompilationDisabled,
169
170    /// `CUDA_ERROR_UNSUPPORTED_EXEC_AFFINITY` (224)
171    #[error("CUDA: unsupported exec affinity")]
172    UnsupportedExecAffinity,
173
174    /// `CUDA_ERROR_UNSUPPORTED_DEVSIDE_SYNC` (225)
175    #[error("CUDA: unsupported device-side sync")]
176    UnsupportedDevsideSync,
177
178    // ----- Source / file / shared-object errors (300-304) -----
179    /// `CUDA_ERROR_INVALID_SOURCE` (300)
180    #[error("CUDA: invalid source")]
181    InvalidSource,
182
183    /// `CUDA_ERROR_FILE_NOT_FOUND` (301)
184    #[error("CUDA: file not found")]
185    FileNotFound,
186
187    /// `CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND` (302)
188    #[error("CUDA: shared object symbol not found")]
189    SharedObjectSymbolNotFound,
190
191    /// `CUDA_ERROR_SHARED_OBJECT_INIT_FAILED` (303)
192    #[error("CUDA: shared object init failed")]
193    SharedObjectInitFailed,
194
195    /// `CUDA_ERROR_OPERATING_SYSTEM` (304)
196    #[error("CUDA: operating system error")]
197    OperatingSystem,
198
199    // ----- Handle / state errors (400-402) -----
200    /// `CUDA_ERROR_INVALID_HANDLE` (400)
201    #[error("CUDA: invalid handle")]
202    InvalidHandle,
203
204    /// `CUDA_ERROR_ILLEGAL_STATE` (401)
205    #[error("CUDA: illegal state")]
206    IllegalState,
207
208    /// `CUDA_ERROR_LOSSY_QUERY` (402)
209    #[error("CUDA: lossy query")]
210    LossyQuery,
211
212    // ----- Lookup error (500) -----
213    /// `CUDA_ERROR_NOT_FOUND` (500)
214    #[error("CUDA: symbol not found")]
215    NotFound,
216
217    // ----- Readiness error (600) -----
218    /// `CUDA_ERROR_NOT_READY` (600)
219    #[error("CUDA: not ready (async operation pending)")]
220    NotReady,
221
222    // ----- Launch / address / peer errors (700-720) -----
223    /// `CUDA_ERROR_ILLEGAL_ADDRESS` (700)
224    #[error("CUDA: illegal memory address")]
225    IllegalAddress,
226
227    /// `CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES` (701)
228    #[error("CUDA: kernel launch out of resources (registers/shared memory)")]
229    LaunchOutOfResources,
230
231    /// `CUDA_ERROR_LAUNCH_TIMEOUT` (702)
232    #[error("CUDA: kernel launch timeout")]
233    LaunchTimeout,
234
235    /// `CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING` (703)
236    #[error("CUDA: launch incompatible texturing")]
237    LaunchIncompatibleTexturing,
238
239    /// `CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED` (704)
240    #[error("CUDA: peer access already enabled")]
241    PeerAccessAlreadyEnabled,
242
243    /// `CUDA_ERROR_PEER_ACCESS_NOT_ENABLED` (705)
244    #[error("CUDA: peer access not enabled")]
245    PeerAccessNotEnabled,
246
247    /// `CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE` (708)
248    #[error("CUDA: primary context active")]
249    PrimaryContextActive,
250
251    /// `CUDA_ERROR_CONTEXT_IS_DESTROYED` (709)
252    #[error("CUDA: context is destroyed")]
253    ContextIsDestroyed,
254
255    /// `CUDA_ERROR_ASSERT` (710)
256    #[error("CUDA: device-side assert triggered")]
257    Assert,
258
259    /// `CUDA_ERROR_TOO_MANY_PEERS` (711)
260    #[error("CUDA: too many peers")]
261    TooManyPeers,
262
263    /// `CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED` (712)
264    #[error("CUDA: host memory already registered")]
265    HostMemoryAlreadyRegistered,
266
267    /// `CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED` (713)
268    #[error("CUDA: host memory not registered")]
269    HostMemoryNotRegistered,
270
271    /// `CUDA_ERROR_HARDWARE_STACK_ERROR` (714)
272    #[error("CUDA: hardware stack error")]
273    HardwareStackError,
274
275    /// `CUDA_ERROR_ILLEGAL_INSTRUCTION` (715)
276    #[error("CUDA: illegal instruction")]
277    IllegalInstruction,
278
279    /// `CUDA_ERROR_MISALIGNED_ADDRESS` (716)
280    #[error("CUDA: misaligned address")]
281    MisalignedAddress,
282
283    /// `CUDA_ERROR_INVALID_ADDRESS_SPACE` (717)
284    #[error("CUDA: invalid address space")]
285    InvalidAddressSpace,
286
287    /// `CUDA_ERROR_INVALID_PC` (718)
288    #[error("CUDA: invalid program counter")]
289    InvalidPc,
290
291    /// `CUDA_ERROR_LAUNCH_FAILED` (719)
292    #[error("CUDA: kernel launch failed")]
293    LaunchFailed,
294
295    /// `CUDA_ERROR_COOPERATIVE_LAUNCH_TOO_LARGE` (720)
296    #[error("CUDA: cooperative launch too large")]
297    CooperativeLaunchTooLarge,
298
299    // ----- Permission / support errors (800-812) -----
300    /// `CUDA_ERROR_NOT_PERMITTED` (800)
301    #[error("CUDA: not permitted")]
302    NotPermitted,
303
304    /// `CUDA_ERROR_NOT_SUPPORTED` (801)
305    #[error("CUDA: not supported")]
306    NotSupported,
307
308    /// `CUDA_ERROR_SYSTEM_NOT_READY` (802)
309    #[error("CUDA: system not ready")]
310    SystemNotReady,
311
312    /// `CUDA_ERROR_SYSTEM_DRIVER_MISMATCH` (803)
313    #[error("CUDA: system driver mismatch")]
314    SystemDriverMismatch,
315
316    /// `CUDA_ERROR_COMPAT_NOT_SUPPORTED_ON_DEVICE` (804)
317    #[error("CUDA: compat not supported on device")]
318    CompatNotSupportedOnDevice,
319
320    /// `CUDA_ERROR_MPS_CONNECTION_FAILED` (805)
321    #[error("CUDA: MPS connection failed")]
322    MpsConnectionFailed,
323
324    /// `CUDA_ERROR_MPS_RPC_FAILURE` (806)
325    #[error("CUDA: MPS RPC failure")]
326    MpsRpcFailure,
327
328    /// `CUDA_ERROR_MPS_SERVER_NOT_READY` (807)
329    #[error("CUDA: MPS server not ready")]
330    MpsServerNotReady,
331
332    /// `CUDA_ERROR_MPS_MAX_CLIENTS_REACHED` (808)
333    #[error("CUDA: MPS max clients reached")]
334    MpsMaxClientsReached,
335
336    /// `CUDA_ERROR_MPS_MAX_CONNECTIONS_REACHED` (809)
337    #[error("CUDA: MPS max connections reached")]
338    MpsMaxConnectionsReached,
339
340    /// `CUDA_ERROR_MPS_CLIENT_TERMINATED` (810)
341    #[error("CUDA: MPS client terminated")]
342    MpsClientTerminated,
343
344    /// `CUDA_ERROR_CDP_NOT_SUPPORTED` (811)
345    #[error("CUDA: CDP not supported")]
346    CdpNotSupported,
347
348    /// `CUDA_ERROR_CDP_VERSION_MISMATCH` (812)
349    #[error("CUDA: CDP version mismatch")]
350    CdpVersionMismatch,
351
352    // ----- Stream capture / graph errors (900-915) -----
353    /// `CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED` (900)
354    #[error("CUDA: stream capture unsupported")]
355    StreamCaptureUnsupported,
356
357    /// `CUDA_ERROR_STREAM_CAPTURE_INVALIDATED` (901)
358    #[error("CUDA: stream capture invalidated")]
359    StreamCaptureInvalidated,
360
361    /// `CUDA_ERROR_STREAM_CAPTURE_MERGE` (902)
362    #[error("CUDA: stream capture merge not permitted")]
363    StreamCaptureMerge,
364
365    /// `CUDA_ERROR_STREAM_CAPTURE_UNMATCHED` (903)
366    #[error("CUDA: stream capture unmatched")]
367    StreamCaptureUnmatched,
368
369    /// `CUDA_ERROR_STREAM_CAPTURE_UNJOINED` (904)
370    #[error("CUDA: stream capture unjoined")]
371    StreamCaptureUnjoined,
372
373    /// `CUDA_ERROR_STREAM_CAPTURE_ISOLATION` (905)
374    #[error("CUDA: stream capture isolation violation")]
375    StreamCaptureIsolation,
376
377    /// `CUDA_ERROR_STREAM_CAPTURE_IMPLICIT` (906)
378    #[error("CUDA: implicit stream in graph capture")]
379    StreamCaptureImplicit,
380
381    /// `CUDA_ERROR_CAPTURED_EVENT` (907)
382    #[error("CUDA: captured event error")]
383    CapturedEvent,
384
385    /// `CUDA_ERROR_STREAM_CAPTURE_WRONG_THREAD` (908)
386    #[error("CUDA: stream capture wrong thread")]
387    StreamCaptureWrongThread,
388
389    /// `CUDA_ERROR_TIMEOUT` (909)
390    #[error("CUDA: async operation timed out")]
391    Timeout,
392
393    /// `CUDA_ERROR_GRAPH_EXEC_UPDATE_FAILURE` (910)
394    #[error("CUDA: graph exec update failure")]
395    GraphExecUpdateFailure,
396
397    /// `CUDA_ERROR_EXTERNAL_DEVICE` (911)
398    #[error("CUDA: external device error")]
399    ExternalDevice,
400
401    /// `CUDA_ERROR_INVALID_CLUSTER_SIZE` (912)
402    #[error("CUDA: invalid cluster size")]
403    InvalidClusterSize,
404
405    /// `CUDA_ERROR_FUNCTION_NOT_LOADED` (913)
406    #[error("CUDA: function not loaded")]
407    FunctionNotLoaded,
408
409    /// `CUDA_ERROR_INVALID_RESOURCE_TYPE` (914)
410    #[error("CUDA: invalid resource type")]
411    InvalidResourceType,
412
413    /// `CUDA_ERROR_INVALID_RESOURCE_CONFIGURATION` (915)
414    #[error("CUDA: invalid resource configuration")]
415    InvalidResourceConfiguration,
416
417    // ----- JIT diagnostic failure -----
418    /// JIT compilation failed with structured diagnostic output.
419    ///
420    /// The `log` field carries the full parsed JIT compiler output so that
421    /// callers can inspect warnings, errors, and informational messages.
422    /// The `source` field holds the original [`CudaError`] that triggered
423    /// the failure (e.g. [`InvalidPtx`](CudaError::InvalidPtx)).
424    ///
425    /// `Box` wrappers keep the enum size from inflating — `JitLog` can be
426    /// large when compilation produces verbose output.
427    #[error("JIT compilation failed: {diagnostic_count} diagnostic(s); see attached log")]
428    JitFailed {
429        /// Structured log from the JIT compiler.
430        log: Box<JitLog>,
431        /// Number of diagnostics in the log (pre-computed to avoid
432        /// re-parsing on every `Display` call).
433        diagnostic_count: usize,
434        /// The underlying CUDA error that triggered the failure.
435        #[source]
436        source: Box<CudaError>,
437    },
438
439    // ----- Catch-all -----
440    /// Unknown error code not covered by any other variant.
441    #[error("CUDA: unknown error (code {0})")]
442    Unknown(u32),
443}
444
445impl CudaError {
446    /// Convert a raw `CUresult` code into the corresponding [`CudaError`] variant.
447    ///
448    /// This should not be called with `CUDA_SUCCESS` (0); prefer using [`check`]
449    /// which returns `Ok(())` for success.
450    #[allow(clippy::too_many_lines)]
451    pub fn from_raw(code: u32) -> Self {
452        match code {
453            ffi::CUDA_ERROR_INVALID_VALUE => Self::InvalidValue,
454            ffi::CUDA_ERROR_OUT_OF_MEMORY => Self::OutOfMemory,
455            ffi::CUDA_ERROR_NOT_INITIALIZED => Self::NotInitialized,
456            ffi::CUDA_ERROR_DEINITIALIZED => Self::Deinitialized,
457            ffi::CUDA_ERROR_PROFILER_DISABLED => Self::ProfilerDisabled,
458            ffi::CUDA_ERROR_PROFILER_NOT_INITIALIZED => Self::ProfilerNotInitialized,
459            ffi::CUDA_ERROR_PROFILER_ALREADY_STARTED => Self::ProfilerAlreadyStarted,
460            ffi::CUDA_ERROR_PROFILER_ALREADY_STOPPED => Self::ProfilerAlreadyStopped,
461            ffi::CUDA_ERROR_STUB_LIBRARY => Self::StubLibrary,
462            ffi::CUDA_ERROR_DEVICE_UNAVAILABLE => Self::DeviceUnavailable,
463            ffi::CUDA_ERROR_NO_DEVICE => Self::NoDevice,
464            ffi::CUDA_ERROR_INVALID_DEVICE => Self::InvalidDevice,
465            ffi::CUDA_ERROR_DEVICE_NOT_LICENSED => Self::DeviceNotLicensed,
466            ffi::CUDA_ERROR_INVALID_IMAGE => Self::InvalidImage,
467            ffi::CUDA_ERROR_INVALID_CONTEXT => Self::InvalidContext,
468            ffi::CUDA_ERROR_CONTEXT_ALREADY_CURRENT => Self::ContextAlreadyCurrent,
469            ffi::CUDA_ERROR_MAP_FAILED => Self::MapFailed,
470            ffi::CUDA_ERROR_UNMAP_FAILED => Self::UnmapFailed,
471            ffi::CUDA_ERROR_ARRAY_IS_MAPPED => Self::ArrayIsMapped,
472            ffi::CUDA_ERROR_ALREADY_MAPPED => Self::AlreadyMapped,
473            ffi::CUDA_ERROR_NO_BINARY_FOR_GPU => Self::NoBinaryForGpu,
474            ffi::CUDA_ERROR_ALREADY_ACQUIRED => Self::AlreadyAcquired,
475            ffi::CUDA_ERROR_NOT_MAPPED => Self::NotMapped,
476            ffi::CUDA_ERROR_NOT_MAPPED_AS_ARRAY => Self::NotMappedAsArray,
477            ffi::CUDA_ERROR_NOT_MAPPED_AS_POINTER => Self::NotMappedAsPointer,
478            ffi::CUDA_ERROR_ECC_UNCORRECTABLE => Self::EccUncorrectable,
479            ffi::CUDA_ERROR_UNSUPPORTED_LIMIT => Self::UnsupportedLimit,
480            ffi::CUDA_ERROR_CONTEXT_ALREADY_IN_USE => Self::ContextAlreadyInUse,
481            ffi::CUDA_ERROR_PEER_ACCESS_UNSUPPORTED => Self::PeerAccessUnsupported,
482            ffi::CUDA_ERROR_INVALID_PTX => Self::InvalidPtx,
483            ffi::CUDA_ERROR_INVALID_GRAPHICS_CONTEXT => Self::InvalidGraphicsContext,
484            ffi::CUDA_ERROR_NVLINK_UNCORRECTABLE => Self::NvlinkUncorrectable,
485            ffi::CUDA_ERROR_JIT_COMPILER_NOT_FOUND => Self::JitCompilerNotFound,
486            ffi::CUDA_ERROR_UNSUPPORTED_PTX_VERSION => Self::UnsupportedPtxVersion,
487            ffi::CUDA_ERROR_JIT_COMPILATION_DISABLED => Self::JitCompilationDisabled,
488            ffi::CUDA_ERROR_UNSUPPORTED_EXEC_AFFINITY => Self::UnsupportedExecAffinity,
489            ffi::CUDA_ERROR_UNSUPPORTED_DEVSIDE_SYNC => Self::UnsupportedDevsideSync,
490            ffi::CUDA_ERROR_INVALID_SOURCE => Self::InvalidSource,
491            ffi::CUDA_ERROR_FILE_NOT_FOUND => Self::FileNotFound,
492            ffi::CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND => Self::SharedObjectSymbolNotFound,
493            ffi::CUDA_ERROR_SHARED_OBJECT_INIT_FAILED => Self::SharedObjectInitFailed,
494            ffi::CUDA_ERROR_OPERATING_SYSTEM => Self::OperatingSystem,
495            ffi::CUDA_ERROR_INVALID_HANDLE => Self::InvalidHandle,
496            ffi::CUDA_ERROR_ILLEGAL_STATE => Self::IllegalState,
497            ffi::CUDA_ERROR_LOSSY_QUERY => Self::LossyQuery,
498            ffi::CUDA_ERROR_NOT_FOUND => Self::NotFound,
499            ffi::CUDA_ERROR_NOT_READY => Self::NotReady,
500            ffi::CUDA_ERROR_ILLEGAL_ADDRESS => Self::IllegalAddress,
501            ffi::CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES => Self::LaunchOutOfResources,
502            ffi::CUDA_ERROR_LAUNCH_TIMEOUT => Self::LaunchTimeout,
503            ffi::CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING => Self::LaunchIncompatibleTexturing,
504            ffi::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED => Self::PeerAccessAlreadyEnabled,
505            ffi::CUDA_ERROR_PEER_ACCESS_NOT_ENABLED => Self::PeerAccessNotEnabled,
506            ffi::CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE => Self::PrimaryContextActive,
507            ffi::CUDA_ERROR_CONTEXT_IS_DESTROYED => Self::ContextIsDestroyed,
508            ffi::CUDA_ERROR_ASSERT => Self::Assert,
509            ffi::CUDA_ERROR_TOO_MANY_PEERS => Self::TooManyPeers,
510            ffi::CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED => Self::HostMemoryAlreadyRegistered,
511            ffi::CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED => Self::HostMemoryNotRegistered,
512            ffi::CUDA_ERROR_HARDWARE_STACK_ERROR => Self::HardwareStackError,
513            ffi::CUDA_ERROR_ILLEGAL_INSTRUCTION => Self::IllegalInstruction,
514            ffi::CUDA_ERROR_MISALIGNED_ADDRESS => Self::MisalignedAddress,
515            ffi::CUDA_ERROR_INVALID_ADDRESS_SPACE => Self::InvalidAddressSpace,
516            ffi::CUDA_ERROR_INVALID_PC => Self::InvalidPc,
517            ffi::CUDA_ERROR_LAUNCH_FAILED => Self::LaunchFailed,
518            ffi::CUDA_ERROR_COOPERATIVE_LAUNCH_TOO_LARGE => Self::CooperativeLaunchTooLarge,
519            ffi::CUDA_ERROR_NOT_PERMITTED => Self::NotPermitted,
520            ffi::CUDA_ERROR_NOT_SUPPORTED => Self::NotSupported,
521            ffi::CUDA_ERROR_SYSTEM_NOT_READY => Self::SystemNotReady,
522            ffi::CUDA_ERROR_SYSTEM_DRIVER_MISMATCH => Self::SystemDriverMismatch,
523            ffi::CUDA_ERROR_COMPAT_NOT_SUPPORTED_ON_DEVICE => Self::CompatNotSupportedOnDevice,
524            ffi::CUDA_ERROR_MPS_CONNECTION_FAILED => Self::MpsConnectionFailed,
525            ffi::CUDA_ERROR_MPS_RPC_FAILURE => Self::MpsRpcFailure,
526            ffi::CUDA_ERROR_MPS_SERVER_NOT_READY => Self::MpsServerNotReady,
527            ffi::CUDA_ERROR_MPS_MAX_CLIENTS_REACHED => Self::MpsMaxClientsReached,
528            ffi::CUDA_ERROR_MPS_MAX_CONNECTIONS_REACHED => Self::MpsMaxConnectionsReached,
529            ffi::CUDA_ERROR_MPS_CLIENT_TERMINATED => Self::MpsClientTerminated,
530            ffi::CUDA_ERROR_CDP_NOT_SUPPORTED => Self::CdpNotSupported,
531            ffi::CUDA_ERROR_CDP_VERSION_MISMATCH => Self::CdpVersionMismatch,
532            ffi::CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED => Self::StreamCaptureUnsupported,
533            ffi::CUDA_ERROR_STREAM_CAPTURE_INVALIDATED => Self::StreamCaptureInvalidated,
534            ffi::CUDA_ERROR_STREAM_CAPTURE_MERGE => Self::StreamCaptureMerge,
535            ffi::CUDA_ERROR_STREAM_CAPTURE_UNMATCHED => Self::StreamCaptureUnmatched,
536            ffi::CUDA_ERROR_STREAM_CAPTURE_UNJOINED => Self::StreamCaptureUnjoined,
537            ffi::CUDA_ERROR_STREAM_CAPTURE_ISOLATION => Self::StreamCaptureIsolation,
538            ffi::CUDA_ERROR_STREAM_CAPTURE_IMPLICIT => Self::StreamCaptureImplicit,
539            ffi::CUDA_ERROR_CAPTURED_EVENT => Self::CapturedEvent,
540            ffi::CUDA_ERROR_STREAM_CAPTURE_WRONG_THREAD => Self::StreamCaptureWrongThread,
541            ffi::CUDA_ERROR_TIMEOUT => Self::Timeout,
542            ffi::CUDA_ERROR_GRAPH_EXEC_UPDATE_FAILURE => Self::GraphExecUpdateFailure,
543            ffi::CUDA_ERROR_EXTERNAL_DEVICE => Self::ExternalDevice,
544            ffi::CUDA_ERROR_INVALID_CLUSTER_SIZE => Self::InvalidClusterSize,
545            ffi::CUDA_ERROR_FUNCTION_NOT_LOADED => Self::FunctionNotLoaded,
546            ffi::CUDA_ERROR_INVALID_RESOURCE_TYPE => Self::InvalidResourceType,
547            ffi::CUDA_ERROR_INVALID_RESOURCE_CONFIGURATION => Self::InvalidResourceConfiguration,
548            ffi::CUDA_ERROR_UNKNOWN => Self::Unknown(999),
549            other => Self::Unknown(other),
550        }
551    }
552
553    /// Returns `true` for errors that indicate a **fatal**, non-recoverable
554    /// GPU state — meaning the current CUDA context is likely corrupted and
555    /// should be destroyed before attempting further GPU work.
556    ///
557    /// # Rationale
558    ///
559    /// Most CUDA errors are caused by incorrect API usage (e.g. bad argument,
560    /// unsupported feature) and do not invalidate the GPU context. A small
561    /// subset — hardware faults, illegal instructions, or illegal memory
562    /// accesses — leave the device in an indeterminate state.
563    ///
564    /// # Examples
565    ///
566    /// ```
567    /// use oxicuda_driver::CudaError;
568    ///
569    /// // Hardware fault — fatal
570    /// assert!(CudaError::IllegalAddress.is_fatal());
571    /// assert!(CudaError::LaunchFailed.is_fatal());
572    /// assert!(CudaError::HardwareStackError.is_fatal());
573    ///
574    /// // Bad argument — recoverable
575    /// assert!(!CudaError::InvalidValue.is_fatal());
576    /// assert!(!CudaError::OutOfMemory.is_fatal());
577    /// assert!(!CudaError::NotReady.is_fatal());
578    /// ```
579    #[must_use]
580    pub fn is_fatal(&self) -> bool {
581        matches!(
582            self,
583            // Hardware / device-side faults that corrupt context state.
584            Self::IllegalAddress
585                | Self::LaunchFailed
586                | Self::HardwareStackError
587                | Self::IllegalInstruction
588                | Self::MisalignedAddress
589                | Self::InvalidAddressSpace
590                | Self::InvalidPc
591                | Self::Assert
592                | Self::EccUncorrectable
593                | Self::NvlinkUncorrectable
594                // Driver / context state is unrecoverable.
595                | Self::ContextIsDestroyed
596                | Self::Deinitialized
597        )
598    }
599
600    /// Returns `true` for errors that are clearly caused by incorrect API
601    /// usage rather than hardware faults or resource exhaustion.
602    ///
603    /// These errors indicate the caller passed bad arguments and the GPU
604    /// state is intact.
605    ///
606    /// ```
607    /// use oxicuda_driver::CudaError;
608    ///
609    /// assert!(CudaError::InvalidValue.is_usage_error());
610    /// assert!(CudaError::InvalidDevice.is_usage_error());
611    /// assert!(!CudaError::OutOfMemory.is_usage_error());
612    /// ```
613    #[must_use]
614    pub fn is_usage_error(&self) -> bool {
615        matches!(
616            self,
617            Self::InvalidValue
618                | Self::InvalidDevice
619                | Self::InvalidContext
620                | Self::InvalidHandle
621                | Self::InvalidImage
622                | Self::InvalidPtx
623                | Self::InvalidSource
624                | Self::InvalidClusterSize
625                | Self::NoDevice
626                | Self::UnsupportedLimit
627                | Self::NotSupported
628                | Self::NotPermitted
629        )
630    }
631
632    /// Convert this error back to its raw `CUresult` code.
633    #[allow(clippy::too_many_lines)]
634    pub fn as_raw(&self) -> u32 {
635        match self {
636            Self::InvalidValue => ffi::CUDA_ERROR_INVALID_VALUE,
637            Self::OutOfMemory => ffi::CUDA_ERROR_OUT_OF_MEMORY,
638            Self::NotInitialized => ffi::CUDA_ERROR_NOT_INITIALIZED,
639            Self::Deinitialized => ffi::CUDA_ERROR_DEINITIALIZED,
640            Self::ProfilerDisabled => ffi::CUDA_ERROR_PROFILER_DISABLED,
641            Self::ProfilerNotInitialized => ffi::CUDA_ERROR_PROFILER_NOT_INITIALIZED,
642            Self::ProfilerAlreadyStarted => ffi::CUDA_ERROR_PROFILER_ALREADY_STARTED,
643            Self::ProfilerAlreadyStopped => ffi::CUDA_ERROR_PROFILER_ALREADY_STOPPED,
644            Self::StubLibrary => ffi::CUDA_ERROR_STUB_LIBRARY,
645            Self::DeviceUnavailable => ffi::CUDA_ERROR_DEVICE_UNAVAILABLE,
646            Self::NoDevice => ffi::CUDA_ERROR_NO_DEVICE,
647            Self::InvalidDevice => ffi::CUDA_ERROR_INVALID_DEVICE,
648            Self::DeviceNotLicensed => ffi::CUDA_ERROR_DEVICE_NOT_LICENSED,
649            Self::InvalidImage => ffi::CUDA_ERROR_INVALID_IMAGE,
650            Self::InvalidContext => ffi::CUDA_ERROR_INVALID_CONTEXT,
651            Self::ContextAlreadyCurrent => ffi::CUDA_ERROR_CONTEXT_ALREADY_CURRENT,
652            Self::MapFailed => ffi::CUDA_ERROR_MAP_FAILED,
653            Self::UnmapFailed => ffi::CUDA_ERROR_UNMAP_FAILED,
654            Self::ArrayIsMapped => ffi::CUDA_ERROR_ARRAY_IS_MAPPED,
655            Self::AlreadyMapped => ffi::CUDA_ERROR_ALREADY_MAPPED,
656            Self::NoBinaryForGpu => ffi::CUDA_ERROR_NO_BINARY_FOR_GPU,
657            Self::AlreadyAcquired => ffi::CUDA_ERROR_ALREADY_ACQUIRED,
658            Self::NotMapped => ffi::CUDA_ERROR_NOT_MAPPED,
659            Self::NotMappedAsArray => ffi::CUDA_ERROR_NOT_MAPPED_AS_ARRAY,
660            Self::NotMappedAsPointer => ffi::CUDA_ERROR_NOT_MAPPED_AS_POINTER,
661            Self::EccUncorrectable => ffi::CUDA_ERROR_ECC_UNCORRECTABLE,
662            Self::UnsupportedLimit => ffi::CUDA_ERROR_UNSUPPORTED_LIMIT,
663            Self::ContextAlreadyInUse => ffi::CUDA_ERROR_CONTEXT_ALREADY_IN_USE,
664            Self::PeerAccessUnsupported => ffi::CUDA_ERROR_PEER_ACCESS_UNSUPPORTED,
665            Self::InvalidPtx => ffi::CUDA_ERROR_INVALID_PTX,
666            Self::InvalidGraphicsContext => ffi::CUDA_ERROR_INVALID_GRAPHICS_CONTEXT,
667            Self::NvlinkUncorrectable => ffi::CUDA_ERROR_NVLINK_UNCORRECTABLE,
668            Self::JitCompilerNotFound => ffi::CUDA_ERROR_JIT_COMPILER_NOT_FOUND,
669            Self::UnsupportedPtxVersion => ffi::CUDA_ERROR_UNSUPPORTED_PTX_VERSION,
670            Self::JitCompilationDisabled => ffi::CUDA_ERROR_JIT_COMPILATION_DISABLED,
671            Self::UnsupportedExecAffinity => ffi::CUDA_ERROR_UNSUPPORTED_EXEC_AFFINITY,
672            Self::UnsupportedDevsideSync => ffi::CUDA_ERROR_UNSUPPORTED_DEVSIDE_SYNC,
673            Self::InvalidSource => ffi::CUDA_ERROR_INVALID_SOURCE,
674            Self::FileNotFound => ffi::CUDA_ERROR_FILE_NOT_FOUND,
675            Self::SharedObjectSymbolNotFound => ffi::CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND,
676            Self::SharedObjectInitFailed => ffi::CUDA_ERROR_SHARED_OBJECT_INIT_FAILED,
677            Self::OperatingSystem => ffi::CUDA_ERROR_OPERATING_SYSTEM,
678            Self::InvalidHandle => ffi::CUDA_ERROR_INVALID_HANDLE,
679            Self::IllegalState => ffi::CUDA_ERROR_ILLEGAL_STATE,
680            Self::LossyQuery => ffi::CUDA_ERROR_LOSSY_QUERY,
681            Self::NotFound => ffi::CUDA_ERROR_NOT_FOUND,
682            Self::NotReady => ffi::CUDA_ERROR_NOT_READY,
683            Self::IllegalAddress => ffi::CUDA_ERROR_ILLEGAL_ADDRESS,
684            Self::LaunchOutOfResources => ffi::CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES,
685            Self::LaunchTimeout => ffi::CUDA_ERROR_LAUNCH_TIMEOUT,
686            Self::LaunchIncompatibleTexturing => ffi::CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING,
687            Self::PeerAccessAlreadyEnabled => ffi::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED,
688            Self::PeerAccessNotEnabled => ffi::CUDA_ERROR_PEER_ACCESS_NOT_ENABLED,
689            Self::PrimaryContextActive => ffi::CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE,
690            Self::ContextIsDestroyed => ffi::CUDA_ERROR_CONTEXT_IS_DESTROYED,
691            Self::Assert => ffi::CUDA_ERROR_ASSERT,
692            Self::TooManyPeers => ffi::CUDA_ERROR_TOO_MANY_PEERS,
693            Self::HostMemoryAlreadyRegistered => ffi::CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED,
694            Self::HostMemoryNotRegistered => ffi::CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED,
695            Self::HardwareStackError => ffi::CUDA_ERROR_HARDWARE_STACK_ERROR,
696            Self::IllegalInstruction => ffi::CUDA_ERROR_ILLEGAL_INSTRUCTION,
697            Self::MisalignedAddress => ffi::CUDA_ERROR_MISALIGNED_ADDRESS,
698            Self::InvalidAddressSpace => ffi::CUDA_ERROR_INVALID_ADDRESS_SPACE,
699            Self::InvalidPc => ffi::CUDA_ERROR_INVALID_PC,
700            Self::LaunchFailed => ffi::CUDA_ERROR_LAUNCH_FAILED,
701            Self::CooperativeLaunchTooLarge => ffi::CUDA_ERROR_COOPERATIVE_LAUNCH_TOO_LARGE,
702            Self::NotPermitted => ffi::CUDA_ERROR_NOT_PERMITTED,
703            Self::NotSupported => ffi::CUDA_ERROR_NOT_SUPPORTED,
704            Self::SystemNotReady => ffi::CUDA_ERROR_SYSTEM_NOT_READY,
705            Self::SystemDriverMismatch => ffi::CUDA_ERROR_SYSTEM_DRIVER_MISMATCH,
706            Self::CompatNotSupportedOnDevice => ffi::CUDA_ERROR_COMPAT_NOT_SUPPORTED_ON_DEVICE,
707            Self::MpsConnectionFailed => ffi::CUDA_ERROR_MPS_CONNECTION_FAILED,
708            Self::MpsRpcFailure => ffi::CUDA_ERROR_MPS_RPC_FAILURE,
709            Self::MpsServerNotReady => ffi::CUDA_ERROR_MPS_SERVER_NOT_READY,
710            Self::MpsMaxClientsReached => ffi::CUDA_ERROR_MPS_MAX_CLIENTS_REACHED,
711            Self::MpsMaxConnectionsReached => ffi::CUDA_ERROR_MPS_MAX_CONNECTIONS_REACHED,
712            Self::MpsClientTerminated => ffi::CUDA_ERROR_MPS_CLIENT_TERMINATED,
713            Self::CdpNotSupported => ffi::CUDA_ERROR_CDP_NOT_SUPPORTED,
714            Self::CdpVersionMismatch => ffi::CUDA_ERROR_CDP_VERSION_MISMATCH,
715            Self::StreamCaptureUnsupported => ffi::CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED,
716            Self::StreamCaptureInvalidated => ffi::CUDA_ERROR_STREAM_CAPTURE_INVALIDATED,
717            Self::StreamCaptureMerge => ffi::CUDA_ERROR_STREAM_CAPTURE_MERGE,
718            Self::StreamCaptureUnmatched => ffi::CUDA_ERROR_STREAM_CAPTURE_UNMATCHED,
719            Self::StreamCaptureUnjoined => ffi::CUDA_ERROR_STREAM_CAPTURE_UNJOINED,
720            Self::StreamCaptureIsolation => ffi::CUDA_ERROR_STREAM_CAPTURE_ISOLATION,
721            Self::StreamCaptureImplicit => ffi::CUDA_ERROR_STREAM_CAPTURE_IMPLICIT,
722            Self::CapturedEvent => ffi::CUDA_ERROR_CAPTURED_EVENT,
723            Self::StreamCaptureWrongThread => ffi::CUDA_ERROR_STREAM_CAPTURE_WRONG_THREAD,
724            Self::Timeout => ffi::CUDA_ERROR_TIMEOUT,
725            Self::GraphExecUpdateFailure => ffi::CUDA_ERROR_GRAPH_EXEC_UPDATE_FAILURE,
726            Self::ExternalDevice => ffi::CUDA_ERROR_EXTERNAL_DEVICE,
727            Self::InvalidClusterSize => ffi::CUDA_ERROR_INVALID_CLUSTER_SIZE,
728            Self::FunctionNotLoaded => ffi::CUDA_ERROR_FUNCTION_NOT_LOADED,
729            Self::InvalidResourceType => ffi::CUDA_ERROR_INVALID_RESOURCE_TYPE,
730            Self::InvalidResourceConfiguration => ffi::CUDA_ERROR_INVALID_RESOURCE_CONFIGURATION,
731            // JitFailed delegates to the inner source error's code.
732            Self::JitFailed { source, .. } => source.as_raw(),
733            Self::Unknown(code) => *code,
734        }
735    }
736}
737
738// =========================================================================
739// CudaResult type alias
740// =========================================================================
741
742/// Convenience result alias used throughout the crate.
743pub type CudaResult<T> = Result<T, CudaError>;
744
745// =========================================================================
746// check() — convert raw CUresult to CudaResult
747// =========================================================================
748
749/// Convert a raw [`CUresult`](ffi::CUresult) into a [`CudaResult<()>`].
750///
751/// Returns `Ok(())` for `CUDA_SUCCESS` (0), otherwise wraps the code in the
752/// corresponding [`CudaError`] variant.
753#[inline(always)]
754pub fn check(result: u32) -> CudaResult<()> {
755    if result == 0 {
756        Ok(())
757    } else {
758        Err(CudaError::from_raw(result))
759    }
760}
761
762// =========================================================================
763// cuda_call! macro
764// =========================================================================
765
766/// Invoke a raw CUDA Driver API function and convert the result to [`CudaResult`].
767///
768/// The expression is evaluated inside an `unsafe` block and its `CUresult`
769/// return value is passed through [`check`].
770///
771/// # Examples
772///
773/// ```ignore
774/// cuda_call!(cuInit(0))?;
775/// ```
776#[macro_export]
777macro_rules! cuda_call {
778    ($expr:expr) => {
779        $crate::error::check(unsafe { $expr })
780    };
781}
782
783// =========================================================================
784// DriverLoadError
785// =========================================================================
786
787/// Errors that can occur while dynamically loading `libcuda.so` / `nvcuda.dll`.
788#[derive(Debug, thiserror::Error)]
789pub enum DriverLoadError {
790    /// The CUDA driver library was not found on the system.
791    #[error("CUDA driver library not found (tried: {candidates:?}): {last_error}")]
792    LibraryNotFound {
793        /// Library names that were attempted.
794        candidates: Vec<String>,
795        /// OS-level error from the last attempt.
796        last_error: String,
797    },
798
799    /// The shared library was loaded but a required symbol was missing.
800    #[error("failed to load symbol '{symbol}': {reason}")]
801    SymbolNotFound {
802        /// Name of the missing symbol (e.g. `"cuInit"`).
803        symbol: &'static str,
804        /// OS-level error description.
805        reason: String,
806    },
807
808    /// The CUDA driver library loaded and all symbols resolved, but `cuInit(0)`
809    /// returned a non-zero error code.
810    ///
811    /// This typically happens when there is no CUDA-capable GPU installed
812    /// (`CUDA_ERROR_NO_DEVICE` = 100) or when the installed driver is
813    /// incompatible with the hardware.
814    #[error("cuInit(0) failed with CUDA error code {code}")]
815    InitializationFailed {
816        /// Raw CUDA error code returned by `cuInit`.
817        code: u32,
818    },
819
820    /// CUDA is not supported on this operating system (e.g. macOS).
821    #[error("CUDA driver not supported on this platform")]
822    UnsupportedPlatform,
823}
824
825// =========================================================================
826// Tests
827// =========================================================================
828
829#[cfg(test)]
830mod tests {
831    use super::*;
832
833    #[test]
834    fn test_check_success() {
835        assert!(check(0).is_ok());
836    }
837
838    #[test]
839    fn test_check_error() {
840        let result = check(1);
841        assert!(result.is_err());
842        assert_eq!(result.err(), Some(CudaError::InvalidValue));
843    }
844
845    #[test]
846    fn test_check_out_of_memory() {
847        let result = check(2);
848        assert_eq!(result.err(), Some(CudaError::OutOfMemory));
849    }
850
851    #[test]
852    fn test_check_not_initialized() {
853        let result = check(3);
854        assert_eq!(result.err(), Some(CudaError::NotInitialized));
855    }
856
857    #[test]
858    fn test_from_raw_roundtrip() {
859        // Test a representative sample of error codes round-trip correctly
860        let codes: &[u32] = &[
861            1, 2, 3, 4, 5, 6, 7, 8, 34, 46, 100, 101, 102, 200, 201, 202, 205, 206, 207, 208, 209,
862            210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 300,
863            301, 302, 303, 304, 400, 401, 402, 500, 600, 700, 701, 702, 703, 704, 705, 708, 709,
864            710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 800, 801, 802, 803, 804, 805,
865            806, 807, 808, 809, 810, 811, 812, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909,
866            910, 911, 912, 913, 914, 915, 999,
867        ];
868        for &code in codes {
869            let err = CudaError::from_raw(code);
870            assert_eq!(
871                err.as_raw(),
872                code,
873                "round-trip failed for code {code}: got {:?}",
874                err
875            );
876        }
877    }
878
879    #[test]
880    fn test_unknown_code() {
881        let err = CudaError::from_raw(12345);
882        assert_eq!(err, CudaError::Unknown(12345));
883        assert_eq!(err.as_raw(), 12345);
884    }
885
886    #[test]
887    fn test_unknown_code_display() {
888        let err = CudaError::from_raw(12345);
889        let msg = format!("{err}");
890        assert!(
891            msg.contains("12345"),
892            "display should contain the code: {msg}"
893        );
894    }
895
896    #[test]
897    fn test_error_display_messages() {
898        assert_eq!(
899            format!("{}", CudaError::InvalidValue),
900            "CUDA: invalid value"
901        );
902        assert_eq!(
903            format!("{}", CudaError::OutOfMemory),
904            "CUDA: out of device memory"
905        );
906        assert_eq!(
907            format!("{}", CudaError::LaunchFailed),
908            "CUDA: kernel launch failed"
909        );
910        assert_eq!(
911            format!("{}", CudaError::NotReady),
912            "CUDA: not ready (async operation pending)"
913        );
914    }
915
916    #[test]
917    fn test_error_is_clone() {
918        let err = CudaError::InvalidValue;
919        let cloned = err.clone();
920        assert_eq!(err, cloned);
921    }
922
923    #[test]
924    fn test_error_implements_std_error() {
925        fn assert_error<T: std::error::Error>() {}
926        assert_error::<CudaError>();
927        assert_error::<DriverLoadError>();
928    }
929
930    #[test]
931    fn test_driver_load_error_display() {
932        let err = DriverLoadError::LibraryNotFound {
933            candidates: vec!["libcuda.so".to_string()],
934            last_error: "no such file or directory".to_string(),
935        };
936        let msg = format!("{err}");
937        assert!(msg.contains("libcuda.so"));
938
939        let err = DriverLoadError::SymbolNotFound {
940            symbol: "cuInit",
941            reason: "not found".to_string(),
942        };
943        let msg = format!("{err}");
944        assert!(msg.contains("cuInit"));
945
946        let err = DriverLoadError::UnsupportedPlatform;
947        let msg = format!("{err}");
948        assert!(msg.contains("not supported"));
949    }
950
951    #[test]
952    fn test_cuda_call_macro() {
953        // Simulate a successful call via a function that returns CUresult
954        unsafe fn fake_success() -> u32 {
955            0
956        }
957        unsafe fn fake_launch_failed() -> u32 {
958            719
959        }
960
961        let result = cuda_call!(fake_success());
962        assert!(result.is_ok());
963
964        let result = cuda_call!(fake_launch_failed());
965        assert_eq!(result.err(), Some(CudaError::LaunchFailed));
966    }
967
968    #[test]
969    fn test_error_hash() {
970        use std::collections::HashSet;
971        let mut set = HashSet::new();
972        set.insert(CudaError::InvalidValue);
973        set.insert(CudaError::OutOfMemory);
974        set.insert(CudaError::InvalidValue); // duplicate
975        assert_eq!(set.len(), 2);
976    }
977
978    #[test]
979    fn test_cuda_result_type_alias() {
980        fn returns_ok() -> CudaResult<i32> {
981            Ok(42)
982        }
983        fn returns_err() -> CudaResult<i32> {
984            Err(CudaError::NoDevice)
985        }
986        assert_eq!(returns_ok().ok(), Some(42));
987        assert_eq!(returns_err().err(), Some(CudaError::NoDevice));
988    }
989
990    // =========================================================================
991    // Error injection test suite
992    // =========================================================================
993
994    /// Verifies every commonly-used CudaError variant has a non-empty Display.
995    #[test]
996    fn test_all_common_cuda_error_variants_have_display() {
997        let errors: &[CudaError] = &[
998            // Basic errors (1-8)
999            CudaError::InvalidValue,
1000            CudaError::OutOfMemory,
1001            CudaError::NotInitialized,
1002            CudaError::Deinitialized,
1003            CudaError::ProfilerDisabled,
1004            CudaError::ProfilerNotInitialized,
1005            CudaError::ProfilerAlreadyStarted,
1006            CudaError::ProfilerAlreadyStopped,
1007            // Stub / unavailable
1008            CudaError::StubLibrary,
1009            CudaError::DeviceUnavailable,
1010            // Device errors
1011            CudaError::NoDevice,
1012            CudaError::InvalidDevice,
1013            CudaError::DeviceNotLicensed,
1014            // Image / context errors
1015            CudaError::InvalidImage,
1016            CudaError::InvalidContext,
1017            CudaError::ContextAlreadyCurrent,
1018            CudaError::MapFailed,
1019            CudaError::UnmapFailed,
1020            CudaError::EccUncorrectable,
1021            // Source / file errors
1022            CudaError::InvalidSource,
1023            CudaError::FileNotFound,
1024            CudaError::SharedObjectSymbolNotFound,
1025            // Handle errors
1026            CudaError::InvalidHandle,
1027            CudaError::IllegalState,
1028            CudaError::NotFound,
1029            // Readiness
1030            CudaError::NotReady,
1031            // Launch / address errors
1032            CudaError::IllegalAddress,
1033            CudaError::LaunchOutOfResources,
1034            CudaError::LaunchTimeout,
1035            CudaError::LaunchFailed,
1036            CudaError::CooperativeLaunchTooLarge,
1037            // Permission errors
1038            CudaError::NotPermitted,
1039            CudaError::NotSupported,
1040            // Hardware faults
1041            CudaError::HardwareStackError,
1042            CudaError::IllegalInstruction,
1043            CudaError::MisalignedAddress,
1044            CudaError::InvalidAddressSpace,
1045            CudaError::InvalidPc,
1046            // Peer access
1047            CudaError::PeerAccessAlreadyEnabled,
1048            CudaError::PeerAccessNotEnabled,
1049            CudaError::ContextIsDestroyed,
1050            // Stream capture
1051            CudaError::StreamCaptureUnsupported,
1052            CudaError::StreamCaptureInvalidated,
1053            CudaError::InvalidClusterSize,
1054            CudaError::FunctionNotLoaded,
1055            // Catch-all
1056            CudaError::Unknown(99999),
1057        ];
1058        for err in errors {
1059            let display = format!("{err}");
1060            assert!(
1061                !display.is_empty(),
1062                "CudaError::{err:?} has an empty Display string"
1063            );
1064        }
1065    }
1066
1067    /// Verifies the code→error→code round-trip for the most critical codes.
1068    #[test]
1069    fn test_cuda_error_from_result_code() {
1070        let pairs: &[(u32, CudaError)] = &[
1071            (1, CudaError::InvalidValue),
1072            (2, CudaError::OutOfMemory),
1073            (3, CudaError::NotInitialized),
1074            (4, CudaError::Deinitialized),
1075            (100, CudaError::NoDevice),
1076            (101, CudaError::InvalidDevice),
1077            (200, CudaError::InvalidImage),
1078            (201, CudaError::InvalidContext),
1079            (400, CudaError::InvalidHandle),
1080            (500, CudaError::NotFound),
1081            (600, CudaError::NotReady),
1082            (700, CudaError::IllegalAddress),
1083            (701, CudaError::LaunchOutOfResources),
1084            (702, CudaError::LaunchTimeout),
1085            (719, CudaError::LaunchFailed),
1086            (800, CudaError::NotPermitted),
1087            (801, CudaError::NotSupported),
1088            (912, CudaError::InvalidClusterSize),
1089            (999, CudaError::Unknown(999)),
1090        ];
1091        for &(code, ref expected) in pairs {
1092            let got = CudaError::from_raw(code);
1093            assert_eq!(
1094                &got, expected,
1095                "from_raw({code}) should be {expected:?}, got {got:?}"
1096            );
1097            assert_eq!(
1098                got.as_raw(),
1099                code,
1100                "as_raw() round-trip failed for code {code}"
1101            );
1102        }
1103    }
1104
1105    /// Verifies the `is_fatal` classification for a representative sample.
1106    #[test]
1107    fn test_cuda_error_is_fatal() {
1108        // Fatal — hardware fault / context corruption.
1109        let fatal: &[CudaError] = &[
1110            CudaError::IllegalAddress,
1111            CudaError::LaunchFailed,
1112            CudaError::HardwareStackError,
1113            CudaError::IllegalInstruction,
1114            CudaError::MisalignedAddress,
1115            CudaError::InvalidAddressSpace,
1116            CudaError::InvalidPc,
1117            CudaError::Assert,
1118            CudaError::EccUncorrectable,
1119            CudaError::NvlinkUncorrectable,
1120            CudaError::ContextIsDestroyed,
1121            CudaError::Deinitialized,
1122        ];
1123        for err in fatal {
1124            assert!(
1125                err.is_fatal(),
1126                "CudaError::{err:?} should be classified as fatal"
1127            );
1128        }
1129
1130        // Non-fatal — recoverable errors or usage mistakes.
1131        let non_fatal: &[CudaError] = &[
1132            CudaError::InvalidValue,
1133            CudaError::OutOfMemory,
1134            CudaError::NotInitialized,
1135            CudaError::NoDevice,
1136            CudaError::NotReady,
1137            CudaError::LaunchOutOfResources,
1138            CudaError::LaunchTimeout,
1139            CudaError::NotPermitted,
1140            CudaError::NotSupported,
1141            CudaError::InvalidClusterSize,
1142            CudaError::Unknown(99),
1143        ];
1144        for err in non_fatal {
1145            assert!(
1146                !err.is_fatal(),
1147                "CudaError::{err:?} should NOT be classified as fatal"
1148            );
1149        }
1150    }
1151
1152    /// Verifies the `is_usage_error` classification.
1153    #[test]
1154    fn test_cuda_error_is_usage_error() {
1155        let usage: &[CudaError] = &[
1156            CudaError::InvalidValue,
1157            CudaError::InvalidDevice,
1158            CudaError::InvalidContext,
1159            CudaError::InvalidHandle,
1160            CudaError::NoDevice,
1161        ];
1162        for err in usage {
1163            assert!(
1164                err.is_usage_error(),
1165                "CudaError::{err:?} should be a usage error"
1166            );
1167        }
1168
1169        let non_usage: &[CudaError] = &[
1170            CudaError::OutOfMemory,
1171            CudaError::LaunchFailed,
1172            CudaError::IllegalAddress,
1173            CudaError::NotReady,
1174        ];
1175        for err in non_usage {
1176            assert!(
1177                !err.is_usage_error(),
1178                "CudaError::{err:?} should NOT be a usage error"
1179            );
1180        }
1181    }
1182
1183    /// Verifies that `is_fatal` and `is_usage_error` are mutually exclusive.
1184    #[test]
1185    fn test_fatal_and_usage_error_are_disjoint() {
1186        // A sample of errors that should not be both fatal AND usage errors.
1187        let all_errors: &[CudaError] = &[
1188            CudaError::InvalidValue,
1189            CudaError::OutOfMemory,
1190            CudaError::LaunchFailed,
1191            CudaError::IllegalAddress,
1192            CudaError::HardwareStackError,
1193            CudaError::NotReady,
1194            CudaError::InvalidDevice,
1195        ];
1196        for err in all_errors {
1197            assert!(
1198                !(err.is_fatal() && err.is_usage_error()),
1199                "CudaError::{err:?} cannot be both fatal and a usage error"
1200            );
1201        }
1202    }
1203
1204    /// Simulates error injection: build a Result from a raw error code and
1205    /// verify downstream handling branches on the correct classification.
1206    #[test]
1207    fn test_error_injection_simulation() {
1208        // Inject CUDA_ERROR_ILLEGAL_ADDRESS (700) — should trigger fatal handling.
1209        let injected = check(700);
1210        let err = injected.expect_err("error code 700 must be an Err");
1211        assert_eq!(err, CudaError::IllegalAddress);
1212        assert!(err.is_fatal(), "IllegalAddress must be fatal");
1213        assert!(!err.is_usage_error());
1214
1215        // Inject CUDA_ERROR_INVALID_VALUE (1) — should trigger usage-error handling.
1216        let injected = check(1);
1217        let err = injected.expect_err("error code 1 must be an Err");
1218        assert_eq!(err, CudaError::InvalidValue);
1219        assert!(!err.is_fatal(), "InvalidValue must not be fatal");
1220        assert!(err.is_usage_error());
1221
1222        // Inject unknown code — should propagate as Unknown.
1223        let injected = check(55555);
1224        let err = injected.expect_err("unknown code must be an Err");
1225        assert!(matches!(err, CudaError::Unknown(55555)));
1226        assert!(!err.is_fatal());
1227        assert!(!err.is_usage_error());
1228    }
1229}