use std::{convert::Infallible, fmt::Display};
use crate::{DType, DeviceLocation, Layout, Shape};
#[derive(Debug, Clone)]
pub struct MatMulUnexpectedStriding {
pub lhs_l: Layout,
pub rhs_l: Layout,
pub bmnk: (usize, usize, usize, usize),
pub msg: &'static str,
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self}")
}
}
#[derive(thiserror::Error)]
pub enum Error {
#[error("{msg}, expected: {expected:?}, got: {got:?}")]
UnexpectedDType {
msg: &'static str,
expected: DType,
got: DType,
},
#[error("dtype mismatch in {op}, lhs: {lhs:?}, rhs: {rhs:?}")]
DTypeMismatchBinaryOp {
lhs: DType,
rhs: DType,
op: &'static str,
},
#[error("unsupported dtype {0:?} for op {1}")]
UnsupportedDTypeForOp(DType, &'static str),
#[error("no zero scalar for dtype {0:?} (scale format has no exact zero)")]
NoZeroScalar(DType),
#[error("no one scalar for dtype {0:?}")]
NoOneScalar(DType),
#[error("value {1} not representable as a scalar of dtype {0:?}")]
ScalarUnrepresentable(DType, f64),
#[error("dtype {0:?} is a packed element format with no scalar representation")]
PackedElementHasNoScalar(DType),
#[error("{op}: dimension index {dim} out of range for shape {shape:?}")]
DimOutOfRange {
shape: Box<Shape>,
dim: i32,
op: &'static str,
},
#[error("{op}: duplicate dim index {dims:?} for shape {shape:?}")]
DuplicateDimIndex {
shape: Box<Shape>,
dims: Vec<usize>,
op: &'static str,
},
#[error("unexpected rank, expected: {expected}, got: {got} ({shape:?})")]
UnexpectedNumberOfDims {
expected: usize,
got: usize,
shape: Box<Shape>,
},
#[error("{msg}, expected: {expected:?}, got: {got:?}")]
UnexpectedShape {
msg: String,
expected: Box<Shape>,
got: Box<Shape>,
},
#[error(
"Shape mismatch, got buffer of size {buffer_size} which is compatible with shape {shape:?}"
)]
ShapeMismatch {
buffer_size: usize,
shape: Box<Shape>,
},
#[error("shape mismatch in {op}, lhs: {lhs:?}, rhs: {rhs:?}")]
ShapeMismatchBinaryOp {
lhs: Box<Shape>,
rhs: Box<Shape>,
op: &'static str,
},
#[error(
"shape mismatch in cat for dim {dim}, shape for arg 1: {first_shape:?} shape for arg {n}: {nth_shape:?}"
)]
ShapeMismatchCat {
dim: usize,
first_shape: Box<Shape>,
n: usize,
nth_shape: Box<Shape>,
},
#[error("Cannot divide tensor of shape {shape:?} equally along dim {dim} into {n_parts}")]
ShapeMismatchSplit {
shape: Box<Shape>,
dim: usize,
n_parts: usize,
},
#[error("{op} can only be performed on a single dimension")]
OnlySingleDimension { op: &'static str, dims: Vec<usize> },
#[error("empty tensor for {op}")]
EmptyTensor { op: &'static str },
#[error("device mismatch in {op}, lhs: {lhs:?}, rhs: {rhs:?}")]
DeviceMismatchBinaryOp {
lhs: DeviceLocation,
rhs: DeviceLocation,
op: &'static str,
},
#[error("narrow invalid args {msg}: {shape:?}, dim: {dim}, start: {start}, len:{len}")]
NarrowInvalidArgs {
shape: Box<Shape>,
dim: usize,
start: usize,
len: usize,
msg: &'static str,
},
#[error(
"conv1d invalid args {msg}: inp: {inp_shape:?}, k: {k_shape:?}, pad: {padding}, stride: {stride}"
)]
Conv1dInvalidArgs {
inp_shape: Box<Shape>,
k_shape: Box<Shape>,
padding: usize,
stride: usize,
msg: &'static str,
},
#[error("{op} invalid index {index} with dim size {size}")]
InvalidIndex {
op: &'static str,
index: usize,
size: usize,
},
#[error("cannot broadcast {src_shape:?} to {dst_shape:?}")]
BroadcastIncompatibleShapes {
src_shape: Box<Shape>,
dst_shape: Box<Shape>,
},
#[error("cannot set variable {msg}")]
CannotSetVar { msg: &'static str },
#[error("{0:?}")]
MatMulUnexpectedStriding(Box<MatMulUnexpectedStriding>),
#[error("{op} only supports contiguous tensors")]
RequiresContiguous { op: &'static str },
#[error("{op} expects at least one tensor")]
OpRequiresAtLeastOneTensor { op: &'static str },
#[error("{op} expects at least two tensors")]
OpRequiresAtLeastTwoTensors { op: &'static str },
#[error("backward is not supported for {op}")]
BackwardNotSupported { op: &'static str },
#[error(
"no backend supports {op} on {dtypes:?}; available backends: \
{available_backends:?}; supported (op, dtypes) by backend: \
{supported_combinations:?}"
)]
NoBackendForOp {
op: crate::dispatch::OpKind,
dtypes: Vec<DType>,
available_backends: Vec<crate::probe::BackendId>,
supported_combinations: Vec<(crate::probe::BackendId, crate::dispatch::OpKind, Vec<DType>)>,
},
#[error("unsupported transfer from {from:?} to {to:?}; available paths: {available_paths:?}")]
UnsupportedTransfer {
from: DeviceLocation,
to: DeviceLocation,
available_paths: Vec<crate::backend::TransferPath>,
},
#[error("alignment mismatch on {backend:?}: required {required} bytes, got {actual} bytes")]
AlignmentMismatch {
backend: crate::probe::BackendId,
required: usize,
actual: usize,
},
#[error(
"execution plan was built against topology generation {plan_generation}, \
but generation {current_generation} is now current; rebuild and retry"
)]
TopologyChanged {
plan_generation: u64,
current_generation: u64,
},
#[error("the fuel crate has not been built with cuda support")]
NotCompiledWithCudaSupport,
#[error("the fuel crate has not been built with metal support")]
NotCompiledWithMetalSupport,
#[error("the fuel crate has not been built with vulkan support")]
NotCompiledWithVulkanSupport,
#[error("cannot find tensor {path}")]
CannotFindTensor { path: String },
#[error(transparent)]
Cuda(Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
Metal(Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
Vulkan(Box<dyn std::error::Error + Send + Sync>),
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios"), feature = "ug"))]
#[error(transparent)]
Ug(Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
TryFromIntError(#[from] core::num::TryFromIntError),
#[error("npy/npz error {0}")]
Npy(String),
#[error(transparent)]
Zip(#[from] zip::result::ZipError),
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
#[error(transparent)]
FromUtf8(#[from] std::string::FromUtf8Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
SafeTensor(#[from] safetensors::SafeTensorError),
#[error("unsupported safetensor dtype {0:?}")]
UnsupportedSafeTensorDtype(safetensors::Dtype),
#[error("{0}")]
Wrapped(Box<dyn std::fmt::Display + Send + Sync>),
#[error("{wrapped:?}\n{context:?}")]
WrappedContext {
wrapped: Box<dyn std::error::Error + Send + Sync>,
context: String,
},
#[error("{context}\n{inner}")]
Context {
inner: Box<Self>,
context: Box<dyn std::fmt::Display + Send + Sync>,
},
#[error("path: {path:?} {inner}")]
WithPath {
inner: Box<Self>,
path: std::path::PathBuf,
},
#[error("{inner}\n{backtrace}")]
WithBacktrace {
inner: Box<Self>,
backtrace: Box<std::backtrace::Backtrace>,
},
#[error("{0}")]
Msg(String),
#[error("unwrap none")]
UnwrapNone,
#[error(
"ranker hard filter `{filter}` rejected all {available_alternatives} candidate(s); \
context: {ctx_summary}"
)]
FilterRejected {
filter: &'static str,
ctx_summary: String,
available_alternatives: usize,
},
#[error("invalid branch: {reason}")]
InvalidBranch {
reason: String,
},
}
pub type Result<T> = std::result::Result<T, Error>;
const _: () = assert!(
std::mem::size_of::<Error>() <= 96,
"fuel_ir::Error exceeded its size budget: a Result<T> hot-path regression. \
Box the large field, or consciously raise the bound (see the comment above)."
);
impl Error {
pub fn wrap(err: impl std::fmt::Display + Send + Sync + 'static) -> Self {
Self::Wrapped(Box::new(err)).bt()
}
pub fn msg(err: impl std::fmt::Display) -> Self {
Self::Msg(err.to_string()).bt()
}
pub fn debug(err: impl std::fmt::Debug) -> Self {
Self::Msg(format!("{err:?}")).bt()
}
pub fn cuda(err: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::Cuda(Box::new(err)).bt()
}
pub fn metal(err: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::Metal(Box::new(err)).bt()
}
pub fn vulkan(err: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::Vulkan(Box::new(err)).bt()
}
pub fn bt(self) -> Self {
let backtrace = std::backtrace::Backtrace::capture();
match backtrace.status() {
std::backtrace::BacktraceStatus::Disabled
| std::backtrace::BacktraceStatus::Unsupported => self,
_ => Self::WithBacktrace {
inner: Box::new(self),
backtrace: Box::new(backtrace),
},
}
}
pub fn with_path<P: AsRef<std::path::Path>>(self, p: P) -> Self {
Self::WithPath {
inner: Box::new(self),
path: p.as_ref().to_path_buf(),
}
}
pub fn context(self, c: impl std::fmt::Display + Send + Sync + 'static) -> Self {
Self::Context {
inner: Box::new(self),
context: Box::new(c),
}
}
}
#[macro_export]
macro_rules! bail {
($msg:literal $(,)?) => {
return Err($crate::Error::Msg(format!($msg).into()).bt())
};
($err:expr $(,)?) => {
return Err($crate::Error::Msg(format!($err).into()).bt())
};
($fmt:expr, $($arg:tt)*) => {
return Err($crate::Error::Msg(format!($fmt, $($arg)*).into()).bt())
};
}
pub fn zip<T, U>(r1: Result<T>, r2: Result<U>) -> Result<(T, U)> {
match (r1, r2) {
(Ok(r1), Ok(r2)) => Ok((r1, r2)),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
}
}
#[doc(hidden)]
pub mod private {
pub trait Sealed {}
impl<T, E> Sealed for std::result::Result<T, E> where E: std::error::Error {}
impl<T> Sealed for Option<T> {}
}
pub trait Context<T, E>: private::Sealed {
fn context<C>(self, context: C) -> std::result::Result<T, Error>
where
C: Display + Send + Sync + 'static;
fn with_context<C, F>(self, f: F) -> std::result::Result<T, Error>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C;
}
impl<T, E> Context<T, E> for std::result::Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn context<C>(self, context: C) -> std::result::Result<T, Error>
where
C: Display + Send + Sync + 'static,
{
self.map_err(|e| Error::WrappedContext {
wrapped: Box::new(e),
context: context.to_string(),
})
}
fn with_context<C, F>(self, context: F) -> std::result::Result<T, Error>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
match self {
Ok(ok) => Ok(ok),
Err(error) => Err(Error::WrappedContext {
wrapped: Box::new(error),
context: context().to_string(),
}
.bt()),
}
}
}
impl<T> Context<T, Infallible> for Option<T> {
fn context<C>(self, context: C) -> std::result::Result<T, Error>
where
C: Display + Send + Sync + 'static,
{
match self {
Some(ok) => Ok(ok),
None => Err(Error::msg(context).bt()),
}
}
fn with_context<C, F>(self, context: F) -> std::result::Result<T, Error>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
match self {
Some(v) => Ok(v),
None => Err(Error::UnwrapNone.context(context()).bt()),
}
}
}