use crate::element_wise::{ElementWise, ElementWiseKer};
use crate::isa::{Arch, Isa, IsaReq, IsaSet, LEVEL_BOOST};
use crate::lut::Lut;
use crate::reduce::{MapReduce, MapReduceKer, Reduce, ReduceKer};
use tract_data::internal::*;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
pub enum Func {
Sigmoid,
Tanh,
Silu,
Gelu,
Erf,
Ln,
Exp,
Hardswish,
LeakyRelu,
MulByScalar,
ReduceMax,
ReduceMin,
ReduceSum,
Softmax2,
RmsNorm,
Lut,
BinByScalar(crate::BinOp),
BinUnicast(crate::BinOp),
DepthwiseW,
}
impl Func {
const BIN: [Func; 12] = {
use crate::BinOp::*;
[
Func::BinByScalar(Min),
Func::BinByScalar(Max),
Func::BinByScalar(Add),
Func::BinByScalar(Mul),
Func::BinByScalar(Sub),
Func::BinByScalar(SubF),
Func::BinUnicast(Min),
Func::BinUnicast(Max),
Func::BinUnicast(Add),
Func::BinUnicast(Mul),
Func::BinUnicast(Sub),
Func::BinUnicast(SubF),
]
};
pub const ALL: [Func; 29] = [
Func::Sigmoid,
Func::Tanh,
Func::Silu,
Func::Gelu,
Func::Erf,
Func::Ln,
Func::Exp,
Func::Hardswish,
Func::ReduceMax,
Func::ReduceMin,
Func::ReduceSum,
Func::Softmax2,
Func::RmsNorm,
Func::Lut,
Func::LeakyRelu,
Func::MulByScalar,
Func::BIN[0],
Func::BIN[1],
Func::BIN[2],
Func::BIN[3],
Func::BIN[4],
Func::BIN[5],
Func::BIN[6],
Func::BIN[7],
Func::BIN[8],
Func::BIN[9],
Func::BIN[10],
Func::BIN[11],
Func::DepthwiseW,
];
fn slot(self) -> usize {
match self {
Func::Sigmoid => 0,
Func::Tanh => 1,
Func::Silu => 2,
Func::Gelu => 3,
Func::Erf => 4,
Func::Ln => 5,
Func::Exp => 6,
Func::Hardswish => 7,
Func::ReduceMax => 8,
Func::ReduceMin => 9,
Func::ReduceSum => 10,
Func::Softmax2 => 11,
Func::RmsNorm => 12,
Func::Lut => 13,
Func::LeakyRelu => 14,
Func::MulByScalar => 15,
Func::BinByScalar(op) => 16 + op as usize,
Func::BinUnicast(op) => 22 + op as usize,
Func::DepthwiseW => 28,
}
}
pub fn name(&self) -> &'static str {
match self {
Func::Sigmoid => "sigmoid",
Func::Tanh => "tanh",
Func::Silu => "silu",
Func::Gelu => "gelu",
Func::Erf => "erf",
Func::Ln => "ln",
Func::Exp => "exp",
Func::Hardswish => "hardswish",
Func::LeakyRelu => "leaky_relu",
Func::MulByScalar => "mul_by_scalar",
Func::ReduceMax => "reduce_max",
Func::ReduceMin => "reduce_min",
Func::ReduceSum => "reduce_sum",
Func::Softmax2 => "softmax2",
Func::RmsNorm => "rms_norm",
Func::Lut => "lut",
Func::BinByScalar(op) => match op {
crate::BinOp::Min => "by_scalar_min",
crate::BinOp::Max => "by_scalar_max",
crate::BinOp::Add => "by_scalar_add",
crate::BinOp::Mul => "by_scalar_mul",
crate::BinOp::Sub => "by_scalar_sub",
crate::BinOp::SubF => "by_scalar_subf",
},
Func::BinUnicast(op) => match op {
crate::BinOp::Min => "unicast_min",
crate::BinOp::Max => "unicast_max",
crate::BinOp::Add => "unicast_add",
crate::BinOp::Mul => "unicast_mul",
crate::BinOp::Sub => "unicast_sub",
crate::BinOp::SubF => "unicast_subf",
},
Func::DepthwiseW => "depthwise_w",
}
}
fn best_here(self, dt: DatumType) -> TractResult<&'static Routine> {
native_best(self, dt).with_context(|| {
format!("No {} kernel for {dt:?} on {:?}", self.name(), crate::isa::native())
})
}
pub fn ew_f32(self) -> TractResult<Box<dyn ElementWise<f32>>> {
match self.best_here(DatumType::F32)?.factory {
RoutineFactory::F32(f) => Ok(f()),
_ => bail!("{} is not a plain element-wise kernel", self.name()),
}
}
pub fn ew_f16(self) -> TractResult<Box<dyn ElementWise<f16>>> {
match self.best_here(DatumType::F16)?.factory {
RoutineFactory::F16(f) => Ok(f()),
_ => bail!("{} is not a plain element-wise kernel", self.name()),
}
}
pub fn ew_f32_param(self) -> TractResult<Box<dyn ElementWise<f32, f32>>> {
match self.best_here(DatumType::F32)?.factory {
RoutineFactory::F32Param(f) => Ok(f()),
_ => bail!("{} is not a scalar-parameter kernel", self.name()),
}
}
pub fn ew_f16_param(self) -> TractResult<Box<dyn ElementWise<f16, f16>>> {
match self.best_here(DatumType::F16)?.factory {
RoutineFactory::F16Param(f) => Ok(f()),
_ => bail!("{} is not a scalar-parameter kernel", self.name()),
}
}
pub fn reduce_f32(self) -> TractResult<Box<dyn Reduce<f32>>> {
match self.best_here(DatumType::F32)?.factory {
RoutineFactory::F32Reduce(f) => Ok(f()),
_ => bail!("{} is not a reduction", self.name()),
}
}
pub fn reduce_f16(self) -> TractResult<Box<dyn Reduce<f16>>> {
match self.best_here(DatumType::F16)?.factory {
RoutineFactory::F16Reduce(f) => Ok(f()),
_ => bail!("{} is not a reduction", self.name()),
}
}
pub fn map_reduce_f32(self) -> TractResult<Box<dyn MapReduce<f32, f32>>> {
match self.best_here(DatumType::F32)?.factory {
RoutineFactory::F32MapReduce(f) => Ok(f()),
_ => bail!("{} is not a map-reduction", self.name()),
}
}
pub fn bin(self, dt: DatumType) -> Option<Box<crate::BinFn>> {
match native_best(self, dt)?.factory {
RoutineFactory::BinF32 { make, .. } | RoutineFactory::BinF16 { make, .. } => {
Some(make())
}
_ => None,
}
}
}
pub type DepthwiseWF32 = unsafe fn(*const f32, *mut f32, &[f32], &[isize], f32, usize, isize);
#[allow(clippy::type_complexity)]
pub enum RoutineFactory {
F32(fn() -> Box<dyn ElementWise<f32>>),
F16(fn() -> Box<dyn ElementWise<f16>>),
F32Param(fn() -> Box<dyn ElementWise<f32, f32>>),
F16Param(fn() -> Box<dyn ElementWise<f16, f16>>),
F32Reduce(fn() -> Box<dyn Reduce<f32>>),
F16Reduce(fn() -> Box<dyn Reduce<f16>>),
F32MapReduce(fn() -> Box<dyn MapReduce<f32, f32>>),
RmsNormF32 {
name: &'static str,
run: fn(&mut [f32], f32),
},
LutU8 {
name: fn() -> &'static str,
make: fn(&[u8]) -> Box<dyn Lut>,
},
BinF32 {
name: fn() -> &'static str,
make: fn() -> Box<crate::BinFn>,
},
BinF16 {
name: fn() -> &'static str,
make: fn() -> Box<crate::BinFn>,
},
DepthwiseWF32 {
name: &'static str,
run: DepthwiseWF32,
},
}
pub struct Routine {
pub func: Func,
pub arch: Option<Arch>,
pub isa: IsaReq,
pub boost: isize,
pub round_trip: bool,
pub factory: RoutineFactory,
}
inventory::collect!(Routine);
impl Routine {
pub fn dt(&self) -> DatumType {
match self.factory {
RoutineFactory::F32(_)
| RoutineFactory::F32Param(_)
| RoutineFactory::F32Reduce(_)
| RoutineFactory::F32MapReduce(_)
| RoutineFactory::RmsNormF32 { .. }
| RoutineFactory::DepthwiseWF32 { .. } => DatumType::F32,
RoutineFactory::F16(_) | RoutineFactory::F16Param(_) | RoutineFactory::F16Reduce(_) => {
DatumType::F16
}
RoutineFactory::LutU8 { .. } => DatumType::U8,
RoutineFactory::BinF32 { .. } => DatumType::F32,
RoutineFactory::BinF16 { .. } => DatumType::F16,
}
}
pub fn name(&self) -> &'static str {
match self.factory {
RoutineFactory::F32(f) => f().name(),
RoutineFactory::F16(f) => f().name(),
RoutineFactory::F32Param(f) => f().name(),
RoutineFactory::F16Param(f) => f().name(),
RoutineFactory::F32Reduce(f) => f().name(),
RoutineFactory::F16Reduce(f) => f().name(),
RoutineFactory::F32MapReduce(f) => f().name(),
RoutineFactory::RmsNormF32 { name, .. } => name,
RoutineFactory::DepthwiseWF32 { name, .. } => name,
RoutineFactory::LutU8 { name, .. } => name(),
RoutineFactory::BinF32 { name, .. } | RoutineFactory::BinF16 { name, .. } => name(),
}
}
pub fn runnable_on(&self, isa: &IsaSet) -> bool {
self.arch.is_none_or(|a| Some(a) == isa.arch()) && self.isa.satisfied_by(*isa)
}
fn preference(&self) -> isize {
self.isa.level() as isize * LEVEL_BOOST + self.boost
}
}
pub fn declared() -> impl Iterator<Item = &'static Routine> {
inventory::iter::<Routine>()
}
pub fn best_for(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static Routine> {
declared()
.filter(|r| r.func == func && r.dt() == dt && r.runnable_on(isa))
.max_by_key(|r| (r.arch.is_some(), r.preference(), r.name()))
}
pub struct Settled {
pub isa: Isa,
pub func: Func,
pub dt: DatumType,
pub kernel: &'static str,
pub why: &'static str,
}
inventory::collect!(Settled);
impl Settled {
pub fn covers(&self, isa: &IsaSet) -> bool {
Some(self.isa.arch()) == isa.arch()
&& isa.has(self.isa)
&& best_for(self.func, self.dt, isa).is_some_and(|r| r.name() == self.kernel)
}
}
pub fn settlements() -> impl Iterator<Item = &'static Settled> {
inventory::iter::<Settled>()
}
pub fn settled_for(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static Settled> {
settlements().find(|s| s.func == func && s.dt == dt && s.covers(isa))
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Standing {
Missing,
Dedicated,
Unspecialized,
Emulated,
Settled,
}
fn unsettled(func: Func, dt: DatumType, isa: &IsaSet) -> Standing {
let Some(routine) = best_for(func, dt, isa) else { return Standing::Missing };
if routine.arch.is_none() && dt == DatumType::F16 {
Standing::Emulated
} else if routine.round_trip && !isa.fp16_arithmetic() {
Standing::Settled
} else if routine.arch.is_some() && routine.isa.level() == isa.level() {
Standing::Dedicated
} else {
Standing::Unspecialized
}
}
pub fn standing(func: Func, dt: DatumType, isa: &IsaSet) -> Standing {
let answer = unsettled(func, dt, isa);
let settleable = matches!(answer, Standing::Unspecialized | Standing::Emulated);
if settleable && settled_for(func, dt, isa).is_some() { Standing::Settled } else { answer }
}
const NO_FP16_ARITHMETIC: &str = "no f16 arithmetic here: a chunk through an f32 kernel is it";
pub fn settled_why(func: Func, dt: DatumType, isa: &IsaSet) -> Option<&'static str> {
if standing(func, dt, isa) != Standing::Settled {
return None;
}
Some(settled_for(func, dt, isa).map_or(NO_FP16_ARITHMETIC, |settled| settled.why))
}
fn native_best(func: Func, dt: DatumType) -> Option<&'static Routine> {
const SLOTS: usize = Func::ALL.len() * 3;
static NATIVE: std::sync::OnceLock<[Option<&'static Routine>; SLOTS]> =
std::sync::OnceLock::new();
let dt_slot = match dt {
DatumType::F32 => 0,
DatumType::F16 => 1,
DatumType::U8 => 2,
_ => return None,
};
NATIVE.get_or_init(|| {
let isa = crate::isa::native();
let mut table = [None; SLOTS];
for func in Func::ALL {
for (dt_slot, dt) in [DatumType::F32, DatumType::F16, DatumType::U8].iter().enumerate()
{
table[func.slot() * 3 + dt_slot] = best_for(func, *dt, &isa);
}
}
table
})[func.slot() * 3 + dt_slot]
}
pub fn rms_norm_f32() -> TractResult<fn(&mut [f32], f32)> {
match Func::RmsNorm.best_here(DatumType::F32)?.factory {
RoutineFactory::RmsNormF32 { run, .. } => Ok(run),
_ => bail!("rms_norm is not a plain function"),
}
}
pub fn depthwise_w_f32() -> Option<DepthwiseWF32> {
match native_best(Func::DepthwiseW, DatumType::F32)?.factory {
RoutineFactory::DepthwiseWF32 { run, .. } => Some(run),
_ => None,
}
}
pub fn lut_u8(table: &[u8]) -> TractResult<Box<dyn Lut>> {
match Func::Lut.best_here(DatumType::U8)?.factory {
RoutineFactory::LutU8 { make, .. } => Ok(make(table)),
_ => bail!("lut is not a table kernel"),
}
}
macro_rules! submit_routine {
(arm; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Arm); $($rest)*); };
(aarch64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Aarch64); $($rest)*); };
(x86_64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::X86_64); $($rest)*); };
(riscv64; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::RiscV64); $($rest)*); };
(wasm32; $($rest:tt)*) => { submit_routine!(@ Some($crate::isa::Arch::Wasm32Simd128); $($rest)*); };
(generic; $($rest:tt)*) => { submit_routine!(@ None; $($rest)*); };
($factory:ident, $($rest:tt)*) => { submit_routine!(@ None; $factory, $($rest)*); };
(@ $arch:expr; RmsNormF32, $func:ident, $name:literal, $run:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, $func,
$crate::routines::RoutineFactory::RmsNormF32 { name: $name, run: $run }
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; DepthwiseWF32, $func:ident, $name:literal, $run:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, $func,
$crate::routines::RoutineFactory::DepthwiseWF32 { name: $name, run: $run }
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; BinF32, BinByScalar($op:ident), $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, BinByScalar($crate::BinOp::$op),
$crate::routines::RoutineFactory::BinF32 {
name: <$ker as $crate::element_wise::ElementWiseKer<f32, f32>>::name,
make: <$ker as $crate::by_scalar::ByScalarKer<f32>>::bin,
}
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; BinF16, BinByScalar($op:ident), $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, BinByScalar($crate::BinOp::$op),
$crate::routines::RoutineFactory::BinF16 {
name: <$ker as $crate::element_wise::ElementWiseKer<$crate::f16, $crate::f16>>::name,
make: <$ker as $crate::by_scalar::ByScalarKer<$crate::f16>>::bin,
}
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; BinF32, BinUnicast($op:ident), $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, BinUnicast($crate::BinOp::$op),
$crate::routines::RoutineFactory::BinF32 {
name: <$ker as $crate::unicast::UnicastKer<f32>>::name,
make: <$ker as $crate::unicast::UnicastKer<f32>>::bin,
}
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; BinF16, BinUnicast($op:ident), $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, BinUnicast($crate::BinOp::$op),
$crate::routines::RoutineFactory::BinF16 {
name: <$ker as $crate::unicast::UnicastKer<$crate::f16>>::name,
make: <$ker as $crate::unicast::UnicastKer<$crate::f16>>::bin,
}
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; LutU8, $func:ident, $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, $func,
$crate::routines::RoutineFactory::LutU8 {
name: <$ker as $crate::lut::LutKer>::name,
make: |table| $crate::routines::lut_of::<$ker>(table),
}
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; F32Reduce, $func:ident, $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, $func,
$crate::routines::RoutineFactory::F32Reduce(|| $crate::routines::reduce_of::<$ker, _>())
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; F16Reduce, $func:ident, $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, $func,
$crate::routines::RoutineFactory::F16Reduce(|| $crate::routines::reduce_of::<$ker, _>())
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; F32MapReduce, $func:ident, $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))?) => {
submit_routine!(@@ $arch, $func,
$crate::routines::RoutineFactory::F32MapReduce(
|| $crate::routines::map_reduce_of::<$ker, _>()
)
$(, isa($($isa),+))? $(, boost($boost))?);
};
(@ $arch:expr; $factory:ident, $func:ident, $ker:path
$(, isa($($isa:ident),+))? $(, boost($boost:expr))? $(, round_trip($round_trip:expr))?) => {
submit_routine!(@@ $arch, $func,
$crate::routines::RoutineFactory::$factory(
|| $crate::routines::factory_of::<$ker, _, _>()
)
$(, isa($($isa),+))? $(, boost($boost))? $(, round_trip($round_trip))?);
};
(@@ $arch:expr, $func:ident $(($($payload:tt)*))?, $factory:expr
$(, isa($($isa:ident),+))? $(, boost($boost:expr))? $(, round_trip($round_trip:expr))?) => {
inventory::submit! {
$crate::routines::Routine {
func: $crate::routines::Func::$func $(($($payload)*))?,
arch: $arch,
isa: $crate::isa::IsaReq::ANY $(.needing(&[$($crate::isa::Isa::$isa),+]))?,
boost: {
#[allow(unused_mut, unused_assignments)]
let mut boost = 0;
$(boost = $boost;)?
boost
},
round_trip: {
#[allow(unused_mut, unused_assignments)]
let mut round_trip = false;
$(round_trip = $round_trip;)?
round_trip
},
factory: $factory,
}
}
};
}
macro_rules! settled {
($isa:ident, $func:ident $(($op:ident))?, $dt:ident, $kernel:ident, $why:literal) => {
inventory::submit! {
$crate::routines::Settled {
isa: $crate::isa::Isa::$isa,
func: $crate::routines::Func::$func $(($crate::BinOp::$op))?,
dt: tract_data::prelude::DatumType::$dt,
kernel: stringify!($kernel),
why: $why,
}
}
};
}
pub fn lut_of<K: crate::lut::LutKer + 'static>(table: &[u8]) -> Box<dyn Lut> {
Box::new(crate::lut::LutImpl::<K>::new(table))
}
pub fn reduce_of<K, T>() -> Box<dyn Reduce<T>>
where
T: crate::LADatum,
K: ReduceKer<T> + Clone,
{
K::red()
}
pub fn map_reduce_of<K, T>() -> Box<dyn MapReduce<T, T>>
where
T: crate::LADatum,
K: MapReduceKer<T, T> + Clone,
{
K::red()
}
pub fn factory_of<K, T, P>() -> Box<dyn ElementWise<T, P>>
where
T: crate::LADatum,
P: Copy + Send + Sync + std::fmt::Debug + 'static + Default,
K: ElementWiseKer<T, P> + Clone,
{
K::ew()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_func_owns_a_slot() {
let mut slots = std::collections::HashSet::new();
for func in Func::ALL {
assert!(func.slot() < Func::ALL.len(), "{} is out of the table", func.name());
assert!(slots.insert(func.slot()), "{} shares a slot", func.name());
}
assert_eq!(slots.len(), Func::ALL.len());
let isa = crate::isa::native();
for func in Func::ALL {
for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
assert_eq!(
native_best(func, dt).map(|r| r.name()),
best_for(func, dt, &isa).map(|r| r.name()),
"{} {dt:?}",
func.name()
);
}
}
}
#[test]
fn a_kernel_nothing_can_choose_says_so() {
let mut chosen = std::collections::HashSet::new();
for isa in IsaSet::every_ladder() {
for func in Func::ALL {
for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
if let Some(r) = best_for(func, dt, &isa) {
chosen.insert((func, dt, r.name()));
}
}
}
}
for r in declared() {
assert!(
chosen.contains(&(r.func, r.dt(), r.name())) || r.boost < 0,
"{} {:?} {} can never be chosen, and does not decline",
r.func.name(),
r.dt(),
r.name()
);
}
}
#[test]
fn an_unfilled_pair_fails() {
let err = Func::Erf.ew_f16().unwrap_err().to_string();
assert!(err.starts_with("No erf kernel for F16 on "), "{err}");
let err = Func::ReduceMin.reduce_f16().unwrap_err().to_string();
assert!(err.starts_with("No reduce_min kernel for F16 on "), "{err}");
let err = Func::ReduceMax.ew_f32().unwrap_err().to_string();
assert_eq!(err, "reduce_max is not a plain element-wise kernel");
}
#[test]
fn what_this_machine_declares_it_can_build() {
let isa = crate::isa::native();
for func in Func::ALL {
for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
let Some(routine) = best_for(func, dt, &isa) else { continue };
let built = match routine.factory {
RoutineFactory::F32(_) => func.ew_f32().map(|k| k.name()),
RoutineFactory::F16(_) => func.ew_f16().map(|k| k.name()),
RoutineFactory::F32Param(_) => func.ew_f32_param().map(|k| k.name()),
RoutineFactory::F16Param(_) => func.ew_f16_param().map(|k| k.name()),
RoutineFactory::F32Reduce(_) => func.reduce_f32().map(|k| k.name()),
RoutineFactory::F16Reduce(_) => func.reduce_f16().map(|k| k.name()),
RoutineFactory::F32MapReduce(_) => func.map_reduce_f32().map(|k| k.name()),
RoutineFactory::RmsNormF32 { name, .. }
| RoutineFactory::DepthwiseWF32 { name, .. } => Ok(name),
RoutineFactory::LutU8 { name, .. } => lut_u8(&[0u8; 256]).map(|_| name()),
RoutineFactory::BinF32 { name, .. } | RoutineFactory::BinF16 { name, .. } => {
func.bin(dt).map(|_| name()).ok_or_else(|| format_err!("no bin kernel"))
}
};
assert_eq!(
built.map_err(|e| e.to_string()),
Ok(routine.name()),
"{} {dt:?}",
func.name()
);
}
}
}
#[test]
fn every_settlement_closes_a_cell() {
for s in settlements() {
assert!(
IsaSet::every_ladder().any(|m| s.covers(&m)
&& matches!(
unsettled(s.func, s.dt, &m),
Standing::Unspecialized | Standing::Emulated
)),
"{} {:?} on {} settles nothing, {} being what it keeps",
s.func.name(),
s.dt,
s.isa,
s.kernel
);
}
}
#[test]
fn a_cell_is_settled_once() {
for isa in IsaSet::every_ladder() {
for func in Func::ALL {
for dt in [DatumType::F32, DatumType::F16, DatumType::U8] {
let count = settlements()
.filter(|s| s.func == func && s.dt == dt && s.covers(&isa))
.count();
assert!(
count <= 1,
"{} {dt:?} on {isa:?} is settled {count} times",
func.name()
);
}
}
}
}
}