1use std::fmt;
4use std::panic::Location;
5
6use crate::{DType, Device};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum Error {
19 Op {
21 op: &'static str,
23 at: Option<&'static Location<'static>>,
26 kind: OpErrorKind,
28 },
29 OutOfMemory(usize),
31 Backend(String),
33 UnsupportedDevice(Device),
35 Io(String),
37 InvalidArgument(String),
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43#[non_exhaustive]
44pub enum OpErrorKind {
45 ShapeMismatch {
47 lhs: Vec<i32>,
49 rhs: Vec<i32>,
51 },
52 DTypeMismatch {
54 lhs: DType,
56 rhs: DType,
58 },
59 SizeMismatch {
61 expected: usize,
63 actual: usize,
65 },
66 UnsupportedDType(DType),
68 InvalidAxis {
70 axis: i32,
72 ndim: usize,
74 },
75 InvalidShape(Vec<i64>),
78 InvalidIndex(String),
80 OutOfMemory(usize),
82 InvalidArgument(String),
84}
85
86pub type Result<T> = std::result::Result<T, Error>;
88
89impl Error {
90 pub(crate) fn op(op: &'static str, kind: OpErrorKind) -> Self {
93 Self::Op { op, at: None, kind }
94 }
95
96 pub(crate) fn shape_mismatch(op: &'static str, lhs: &[usize], rhs: &[usize]) -> Self {
98 Self::op(
99 op,
100 OpErrorKind::ShapeMismatch {
101 lhs: dims_i32(lhs),
102 rhs: dims_i32(rhs),
103 },
104 )
105 }
106
107 pub(crate) fn dtype_mismatch(op: &'static str, lhs: DType, rhs: DType) -> Self {
109 Self::op(op, OpErrorKind::DTypeMismatch { lhs, rhs })
110 }
111
112 pub(crate) fn unsupported_dtype(op: &'static str, dtype: DType) -> Self {
114 Self::op(op, OpErrorKind::UnsupportedDType(dtype))
115 }
116
117 pub(crate) fn size_mismatch(op: &'static str, expected: usize, actual: usize) -> Self {
119 Self::op(op, OpErrorKind::SizeMismatch { expected, actual })
120 }
121
122 pub(crate) fn invalid_axis(op: &'static str, axis: i64, ndim: usize) -> Self {
124 let axis = i32::try_from(axis).unwrap_or(if axis < 0 { i32::MIN } else { i32::MAX });
125 Self::op(op, OpErrorKind::InvalidAxis { axis, ndim })
126 }
127
128 pub(crate) fn invalid_shape(op: &'static str, shape: impl IntoIterator<Item = i64>) -> Self {
130 Self::op(op, OpErrorKind::InvalidShape(shape.into_iter().collect()))
131 }
132
133 pub(crate) fn invalid_index(op: &'static str, msg: impl Into<String>) -> Self {
135 Self::op(op, OpErrorKind::InvalidIndex(msg.into()))
136 }
137
138 pub(crate) fn locate(self, op: &'static str, at: &'static Location<'static>) -> Self {
143 match self {
144 Self::Op { at: None, kind, .. } => Self::Op {
145 op,
146 at: Some(at),
147 kind,
148 },
149 Self::InvalidArgument(msg) => Self::Op {
150 op,
151 at: Some(at),
152 kind: OpErrorKind::InvalidArgument(msg),
153 },
154 Self::OutOfMemory(n) => Self::Op {
155 op,
156 at: Some(at),
157 kind: OpErrorKind::OutOfMemory(n),
158 },
159 other => other,
160 }
161 }
162
163 pub fn kind(&self) -> Option<&OpErrorKind> {
165 match self {
166 Self::Op { kind, .. } => Some(kind),
167 _ => None,
168 }
169 }
170
171 pub fn location(&self) -> Option<&'static Location<'static>> {
173 match self {
174 Self::Op { at, .. } => *at,
175 _ => None,
176 }
177 }
178
179 pub fn is_oom(&self) -> bool {
182 matches!(
183 self,
184 Self::OutOfMemory(_)
185 | Self::Op {
186 kind: OpErrorKind::OutOfMemory(_),
187 ..
188 }
189 )
190 }
191
192 pub fn op_name(&self) -> Option<&'static str> {
194 match self {
195 Self::Op { op, .. } => Some(op),
196 _ => None,
197 }
198 }
199}
200
201pub(crate) fn dims_i32(shape: &[usize]) -> Vec<i32> {
203 shape
204 .iter()
205 .map(|&d| i32::try_from(d).unwrap_or(i32::MAX))
206 .collect()
207}
208
209impl fmt::Display for OpErrorKind {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 match self {
212 Self::ShapeMismatch { lhs, rhs } => write!(f, "shape mismatch, {lhs:?} vs {rhs:?}"),
213 Self::DTypeMismatch { lhs, rhs } => write!(f, "dtype mismatch, {lhs} vs {rhs}"),
214 Self::SizeMismatch { expected, actual } => {
215 write!(
216 f,
217 "size mismatch, the shape needs {expected} values, got {actual}"
218 )
219 }
220 Self::UnsupportedDType(dtype) => write!(f, "not supported for {dtype}"),
221 Self::InvalidAxis { axis, ndim } => {
222 write!(f, "invalid axis {axis} for an array of {ndim} dimensions")
223 }
224 Self::InvalidShape(shape) => write!(f, "invalid shape {shape:?}"),
225 Self::InvalidIndex(msg) => write!(f, "invalid index: {msg}"),
226 Self::OutOfMemory(n) => write!(f, "out of memory: cannot allocate {n} bytes"),
227 Self::InvalidArgument(msg) => write!(f, "invalid argument: {msg}"),
228 }
229 }
230}
231
232impl fmt::Display for Error {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 match self {
235 Self::Op { op, at: None, kind } => write!(f, "{op}: {kind}"),
236 Self::Op {
237 op,
238 at: Some(at),
239 kind,
240 } => write!(
241 f,
242 "{op} at {}:{}:{}: {kind}",
243 at.file(),
244 at.line(),
245 at.column()
246 ),
247 Self::OutOfMemory(n) => write!(f, "out of memory: cannot allocate {n} bytes"),
248 Self::Backend(msg) => write!(f, "GPU backend error: {msg}"),
249 Self::UnsupportedDevice(device) => write!(f, "no backend for device {device}"),
250 Self::Io(msg) => write!(f, "I/O error: {msg}"),
251 Self::InvalidArgument(msg) => write!(f, "invalid argument: {msg}"),
252 }
253 }
254}
255
256impl std::error::Error for Error {}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn display_with_location() {
264 let at = Location::caller();
265 let e = Error::shape_mismatch("matmul", &[2, 3], &[4, 5]).locate("matmul", at);
266 assert_eq!(
267 e.to_string(),
268 format!(
269 "matmul at {}:{}:{}: shape mismatch, [2, 3] vs [4, 5]",
270 at.file(),
271 at.line(),
272 at.column()
273 )
274 );
275 assert_eq!(e.location(), Some(at));
276 assert_eq!(e.op_name(), Some("matmul"));
277 assert!(matches!(e.kind(), Some(OpErrorKind::ShapeMismatch { .. })));
278 }
279
280 #[test]
281 fn unlocated_errors_take_the_outer_op() {
282 let inner = Error::unsupported_dtype("exp", DType::Complex64);
283 assert_eq!(inner.location(), None);
284 assert_eq!(inner.to_string(), "exp: not supported for complex64");
285 let at = Location::caller();
286 let outer = inner.locate("softmax", at);
287 assert_eq!(outer.op_name(), Some("softmax"));
288 assert_eq!(outer.location(), Some(at));
289 let again = outer.clone().locate("sum", Location::caller());
291 assert_eq!(again, outer);
292 let e = Error::InvalidArgument("bad".into()).locate("split", at);
294 assert_eq!(e.kind(), Some(&OpErrorKind::InvalidArgument("bad".into())));
295 let io = Error::Io("x".into()).locate("split", at);
296 assert_eq!(io, Error::Io("x".into()));
297 }
298
299 #[test]
300 fn out_of_memory_in_either_form() {
301 let at = Location::caller();
302 assert!(Error::OutOfMemory(8).is_oom());
303 assert!(Error::OutOfMemory(8).locate("zeros", at).is_oom());
304 for e in [
305 Error::Backend("x".into()),
306 Error::Io("x".into()),
307 Error::InvalidArgument("x".into()),
308 Error::UnsupportedDevice(Device::cpu()),
309 Error::unsupported_dtype("exp", DType::Complex64),
310 Error::InvalidArgument("x".into()).locate("split", at),
311 ] {
312 assert!(!e.is_oom(), "{e}");
313 }
314 }
315}