Skip to main content

cubecl_server/
driver.rs

1//! Turning a driver's status code into an error the runtime understands.
2//!
3//! The C-family device APIs — the CUDA and HIP runtimes, and the JIT compilers
4//! beside them — all answer the same way: an integer, zero for success, an
5//! enum of their own otherwise. Every backend over one of them has to turn
6//! that into whichever error its caller expects, and doing it by hand at each
7//! call site produces one wording per site and, sooner or later, a panic where
8//! the neighbours report.
9//!
10//! [`checked`] is the one answer, and the `From` implementations are how a
11//! `?` turns it into whichever error the caller's signature already promises.
12
13use crate::compiler::CompilationError;
14use crate::server::{IoError, LaunchError, ServerError};
15use alloc::string::ToString;
16use cubecl_environment::backtrace::BackTrace;
17
18/// The payload every error type carries once the device is poisoned, re-exported.
19pub use cubecl_runtime::poison::DevicePoison;
20
21/// A driver entry point that failed, named by what was called.
22///
23/// The status is kept as a number rather than decoded: each API numbers its
24/// own enum and neither table belongs here. Naming the entry point is what
25/// makes the number searchable in the vendor's headers.
26///
27/// A backend also says whether the status poisoned the device: some faults (e.g. an
28/// illegal address) leave the context unusable, and every later call on it fails with
29/// the same code.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct DriverError {
32    op: &'static str,
33    status: u32,
34    poisoned: bool,
35}
36
37impl DriverError {
38    /// A failure a binding already turned into an error type of its own, named
39    /// by the entry point that produced it.
40    ///
41    /// [`checked`] is the answer wherever the status is still a number. A
42    /// binding that returns a typed result — cudarc's `CUresult` wrapper — has
43    /// already consumed the number, and this is how the entry point's name is
44    /// put back on it.
45    pub fn new(op: &'static str, status: u32) -> Self {
46        Self {
47            op,
48            status,
49            poisoned: false,
50        }
51    }
52
53    /// A failure rendering the device unusable.
54    pub fn poisoned(op: &'static str, status: u32) -> Self {
55        Self {
56            op,
57            status,
58            poisoned: true,
59        }
60    }
61
62    /// Whether this failure poisoned the device.
63    pub fn is_device_poisoned(&self) -> bool {
64        self.poisoned
65    }
66
67    /// The entry point that failed.
68    pub fn op(&self) -> &'static str {
69        self.op
70    }
71
72    /// The driver's own status code.
73    pub fn status(&self) -> u32 {
74        self.status
75    }
76}
77
78impl core::fmt::Display for DriverError {
79    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
80        write!(f, "{} failed with status {}", self.op, self.status)
81    }
82}
83
84impl core::error::Error for DriverError {}
85
86impl From<DriverError> for ServerError {
87    fn from(error: DriverError) -> Self {
88        let reason = error.to_string();
89        match error.poisoned {
90            true => DevicePoison::new(reason).into(),
91            false => ServerError::Generic {
92                reason,
93                backtrace: BackTrace::capture(),
94            },
95        }
96    }
97}
98
99impl From<DriverError> for IoError {
100    fn from(error: DriverError) -> Self {
101        let reason = error.to_string();
102        match error.poisoned {
103            true => DevicePoison::new(reason).into(),
104            false => IoError::Unknown {
105                description: reason,
106                backtrace: BackTrace::capture(),
107            },
108        }
109    }
110}
111
112impl From<DriverError> for CompilationError {
113    fn from(error: DriverError) -> Self {
114        let reason = error.to_string();
115        match error.poisoned {
116            true => DevicePoison::new(reason).into(),
117            false => CompilationError::Generic {
118                reason,
119                backtrace: BackTrace::capture(),
120            },
121        }
122    }
123}
124
125impl From<DriverError> for LaunchError {
126    fn from(error: DriverError) -> Self {
127        let reason = error.to_string();
128        match error.poisoned {
129            true => DevicePoison::new(reason).into(),
130            false => LaunchError::Unknown {
131                reason,
132                backtrace: BackTrace::capture(),
133            },
134        }
135    }
136}
137
138/// `Ok` when `status` says the call to `op` succeeded.
139///
140/// Success is zero, which the runtime and the JIT compiler of both the CUDA
141/// and HIP families agree on even though their failures are numbered
142/// differently. `op` is what tells a reader which numbering a code belongs to.
143///
144/// # Errors
145///
146/// [`DriverError`], which `?` turns into whichever error the caller returns.
147pub fn checked(op: &'static str, status: u32) -> Result<(), DriverError> {
148    match status {
149        0 => Ok(()),
150        status => Err(DriverError::new(op, status)),
151    }
152}