use std::fmt;
use tenferro_core_ops::{descriptor, DTypePolicy, PrimitiveOpKind};
use crate::DType;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BackendId {
Cpu,
Cuda,
WebGpu,
Other(&'static str),
}
impl BackendId {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Cpu => "cpu",
Self::Cuda => "cuda",
Self::WebGpu => "webgpu",
Self::Other(name) => name,
}
}
}
impl fmt::Display for BackendId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum SupportLevel {
Unsupported,
FallbackCopy,
Native,
}
impl SupportLevel {
#[must_use]
pub const fn is_supported(self) -> bool {
!matches!(self, Self::Unsupported)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CapabilityAxis {
OwnedResult,
ReadInputs,
WriteOutput,
StridedOutput,
Accumulation,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CapabilityQuery {
pub op: PrimitiveOpKind,
pub dtype: DType,
}
impl CapabilityQuery {
#[must_use]
pub const fn new(op: PrimitiveOpKind, dtype: DType) -> Self {
Self { op, dtype }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct OperationCapability {
pub backend: BackendId,
pub op: PrimitiveOpKind,
pub dtype: DType,
pub output_dtype: DType,
pub result: SupportLevel,
pub read_inputs: SupportLevel,
pub write_output: SupportLevel,
pub strided_output: SupportLevel,
pub accumulation: SupportLevel,
}
impl OperationCapability {
#[must_use]
pub const fn axis(&self, axis: CapabilityAxis) -> SupportLevel {
match axis {
CapabilityAxis::OwnedResult => self.result,
CapabilityAxis::ReadInputs => self.read_inputs,
CapabilityAxis::WriteOutput => self.write_output,
CapabilityAxis::StridedOutput => self.strided_output,
CapabilityAxis::Accumulation => self.accumulation,
}
}
}
pub trait TensorBackendCapability {
fn backend_id(&self) -> BackendId;
fn capabilities(&self) -> &'static [OperationCapability];
#[must_use]
fn capability(&self, query: CapabilityQuery) -> Option<OperationCapability> {
self.capabilities().iter().copied().find(|entry| {
entry.backend == self.backend_id() && entry.op == query.op && entry.dtype == query.dtype
})
}
fn require_capability(
&self,
query: CapabilityQuery,
axis: CapabilityAxis,
) -> crate::Result<OperationCapability> {
let entry = self.capability(query).ok_or_else(|| {
crate::Error::unsupported_dtype(
descriptor(query.op).name,
query.dtype,
format!(
"backend {} does not support this operation/dtype",
self.backend_id()
),
)
})?;
if entry.axis(axis).is_supported() {
Ok(entry)
} else {
Err(crate::Error::unsupported_dtype(
descriptor(query.op).name,
query.dtype,
format!(
"backend {} does not support this operation/dtype",
self.backend_id()
),
))
}
}
}
#[must_use]
pub fn capability_output_dtype(op: PrimitiveOpKind, dtype: DType) -> Option<DType> {
let policy = descriptor(op).dtype_policy;
match policy {
DTypePolicy::SameAny => Some(dtype),
DTypePolicy::SameNumeric => numeric_dtype(dtype).then_some(dtype),
DTypePolicy::SameFloat => float_dtype(dtype).then_some(dtype),
DTypePolicy::AbsToReal => match dtype {
DType::F32 => Some(DType::F32),
DType::F64 => Some(DType::F64),
DType::I32 => Some(DType::I32),
DType::I64 => Some(DType::I64),
DType::C32 => Some(DType::F32),
DType::C64 => Some(DType::F64),
DType::Bool => None,
},
DTypePolicy::SameFloatOrComplex => float_or_complex_dtype(dtype).then_some(dtype),
DTypePolicy::CompareToBool => comparable_dtype(dtype).then_some(DType::Bool),
DTypePolicy::BoolSelect => Some(dtype),
DTypePolicy::Convert | DTypePolicy::Shape | DTypePolicy::Constant => Some(dtype),
}
}
const fn numeric_dtype(dtype: DType) -> bool {
matches!(
dtype,
DType::F32 | DType::F64 | DType::I32 | DType::I64 | DType::C32 | DType::C64
)
}
const fn float_dtype(dtype: DType) -> bool {
matches!(dtype, DType::F32 | DType::F64)
}
const fn float_or_complex_dtype(dtype: DType) -> bool {
matches!(dtype, DType::F32 | DType::F64 | DType::C32 | DType::C64)
}
const fn comparable_dtype(dtype: DType) -> bool {
matches!(
dtype,
DType::F32 | DType::F64 | DType::I32 | DType::I64 | DType::Bool
)
}