use super::{OutputSpec, TemplateArg, TemplateValue};
use crate::{DType, Error, Result};
#[derive(Debug, Clone, Copy)]
pub(super) enum TensorLabel {
Input(usize),
Output(usize),
}
impl std::fmt::Display for TensorLabel {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Input(index) => write!(formatter, "input {index}"),
Self::Output(index) => write!(formatter, "output {index}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DTypeConstraint {
Exact(DType),
Float,
Template(&'static str),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TemplateKind {
Int,
Bool,
DType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TemplateParameter {
pub name: &'static str,
pub kind: TemplateKind,
}
#[derive(Debug, Clone, Copy)]
pub struct KernelDescriptor<const INPUTS: usize, const OUTPUTS: usize> {
pub name: &'static str,
pub input_names: [&'static str; INPUTS],
pub input_dtypes: [DTypeConstraint; INPUTS],
pub output_names: [&'static str; OUTPUTS],
pub output_dtypes: [DTypeConstraint; OUTPUTS],
pub templates: &'static [TemplateParameter],
pub source: super::MetalSource,
pub header: super::MetalSource,
pub row_contiguous: bool,
pub atomic_outputs: bool,
}
pub(super) fn validate_descriptor<const INPUTS: usize, const OUTPUTS: usize>(
descriptor: &KernelDescriptor<INPUTS, OUTPUTS>,
) -> Result<()> {
let invalid_name = descriptor
.input_names
.iter()
.chain(&descriptor.output_names)
.any(|name| name.is_empty() || name.contains('\x1f'));
if descriptor.name.is_empty() || descriptor.source.code().is_empty() || invalid_name {
return Err(Error::InvalidKernel(format!(
"{} from {} contains an empty or reserved field",
descriptor.name,
descriptor.source.origin()
)));
}
Ok(())
}
pub(super) fn validate_templates(
kernel: &str,
parameters: &[TemplateParameter],
arguments: &[TemplateArg],
) -> Result<()> {
if parameters.len() != arguments.len() {
return Err(Error::InvalidDispatch(format!(
"{kernel} expects {} template arguments, received {}",
parameters.len(),
arguments.len()
)));
}
for parameter in parameters {
let matches = arguments.iter().filter(|argument| argument.name == parameter.name).count();
if matches != 1 {
return Err(Error::InvalidDispatch(format!(
"{kernel} requires exactly one `{}` template argument",
parameter.name
)));
}
let argument = arguments
.iter()
.find(|argument| argument.name == parameter.name)
.ok_or_else(|| Error::InvalidDispatch("validated template disappeared".into()))?;
if template_kind(argument.value) != parameter.kind {
return Err(Error::InvalidDispatch(format!(
"{kernel} template `{}` has the wrong kind",
parameter.name
)));
}
}
Ok(())
}
pub(super) fn validate_dtype(
kernel: &str,
label: TensorLabel,
actual: DType,
constraint: DTypeConstraint,
templates: &[TemplateArg],
) -> Result<()> {
let expected = match constraint {
DTypeConstraint::Exact(expected) => expected,
DTypeConstraint::Float => {
if matches!(actual, DType::Float16 | DType::Bfloat16 | DType::Float32) {
return Ok(());
}
return Err(Error::InvalidDispatch(format!(
"{kernel} {label} dtype {actual:?} is not floating point"
)));
},
DTypeConstraint::Template(name) => template_dtype(name, templates)?,
};
if actual != expected {
return Err(Error::InvalidDispatch(format!(
"{kernel} {label} dtype {actual:?} does not match declared {expected:?}"
)));
}
Ok(())
}
pub(super) fn validate_outputs<const OUTPUTS: usize>(
kernel: &str,
outputs: &[OutputSpec; OUTPUTS],
constraints: [DTypeConstraint; OUTPUTS],
templates: &[TemplateArg],
) -> Result<()> {
for (index, (output, constraint)) in outputs.iter().zip(constraints).enumerate() {
validate_dtype(kernel, TensorLabel::Output(index), output.dtype, constraint, templates)?;
}
Ok(())
}
fn template_dtype(name: &str, templates: &[TemplateArg]) -> Result<DType> {
templates
.iter()
.find_map(|argument| match argument {
TemplateArg {
name: actual,
value: TemplateValue::DType(dtype),
} if *actual == name => Some(*dtype),
_ => None,
})
.ok_or_else(|| Error::InvalidDispatch(format!("missing dtype template `{name}`")))
}
const fn template_kind(value: TemplateValue) -> TemplateKind {
match value {
TemplateValue::Int(_) => TemplateKind::Int,
TemplateValue::Bool(_) => TemplateKind::Bool,
TemplateValue::DType(_) => TemplateKind::DType,
}
}