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/// A driver entry point that failed, named by what was called.
19///
20/// The status is kept as a number rather than decoded: each API numbers its
21/// own enum and neither table belongs here. Naming the entry point is what
22/// makes the number searchable in the vendor's headers.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct DriverError {
25    op: &'static str,
26    status: u32,
27}
28
29impl DriverError {
30    /// A failure a binding already turned into an error type of its own, named
31    /// by the entry point that produced it.
32    ///
33    /// [`checked`] is the answer wherever the status is still a number. A
34    /// binding that returns a typed result — cudarc's `CUresult` wrapper — has
35    /// already consumed the number, and this is how the entry point's name is
36    /// put back on it.
37    pub fn new(op: &'static str, status: u32) -> Self {
38        Self { op, status }
39    }
40
41    /// The entry point that failed.
42    pub fn op(&self) -> &'static str {
43        self.op
44    }
45
46    /// The driver's own status code.
47    pub fn status(&self) -> u32 {
48        self.status
49    }
50}
51
52impl core::fmt::Display for DriverError {
53    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        write!(f, "{} failed with status {}", self.op, self.status)
55    }
56}
57
58impl core::error::Error for DriverError {}
59
60impl From<DriverError> for ServerError {
61    fn from(error: DriverError) -> Self {
62        ServerError::Generic {
63            reason: error.to_string(),
64            backtrace: BackTrace::capture(),
65        }
66    }
67}
68
69impl From<DriverError> for IoError {
70    fn from(error: DriverError) -> Self {
71        IoError::Unknown {
72            description: error.to_string(),
73            backtrace: BackTrace::capture(),
74        }
75    }
76}
77
78impl From<DriverError> for CompilationError {
79    fn from(error: DriverError) -> Self {
80        CompilationError::Generic {
81            reason: error.to_string(),
82            backtrace: BackTrace::capture(),
83        }
84    }
85}
86
87impl From<DriverError> for LaunchError {
88    fn from(error: DriverError) -> Self {
89        LaunchError::Unknown {
90            reason: error.to_string(),
91            backtrace: BackTrace::capture(),
92        }
93    }
94}
95
96/// `Ok` when `status` says the call to `op` succeeded.
97///
98/// Success is zero, which the runtime and the JIT compiler of both the CUDA
99/// and HIP families agree on even though their failures are numbered
100/// differently. `op` is what tells a reader which numbering a code belongs to.
101///
102/// # Errors
103///
104/// [`DriverError`], which `?` turns into whichever error the caller returns.
105pub fn checked(op: &'static str, status: u32) -> Result<(), DriverError> {
106    match status {
107        0 => Ok(()),
108        status => Err(DriverError::new(op, status)),
109    }
110}