use std::sync::Arc;
use crate::ast::SlotShape;
use crate::ast::{PortType, ReflectedValue, SliceArc, Value};
pub trait Wire: Sized + 'static {
const PORT: PortType;
const RESOLVER: Option<crate::dsl::registry::DefaultResolver> = None;
const WIRE_COST: crate::ast::WireCost = crate::ast::WireCost::Data;
fn extract(v: &Value) -> Self;
fn inject(self) -> Value;
}
#[cold]
#[inline(never)]
pub fn extract_mismatch(wire: &str, expected: PortType, got: &Value) -> ! {
panic!(
"Wire<{wire}>::extract: expected {expected}, got {}; the compiler's \
typing admits only {expected} to this port, so this is an internal error",
got.type_name()
)
}
impl Wire for u64 {
const PORT: PortType = PortType::U64;
fn extract(v: &Value) -> Self {
v.as_u64()
}
fn inject(self) -> Value {
Value::U64(self)
}
}
impl Wire for u32 {
const PORT: PortType = PortType::U32;
fn extract(v: &Value) -> Self {
v.as_u64() as u32
}
fn inject(self) -> Value {
Value::U64(self as u64)
}
}
impl Wire for i32 {
const PORT: PortType = PortType::I32;
fn extract(v: &Value) -> Self {
v.as_i64() as i32
}
fn inject(self) -> Value {
Value::I64(self as i64)
}
}
impl Wire for i64 {
const PORT: PortType = PortType::I64;
fn extract(v: &Value) -> Self {
v.as_i64()
}
fn inject(self) -> Value {
Value::I64(self)
}
}
impl Wire for u8 {
const PORT: PortType = PortType::U8;
fn extract(v: &Value) -> Self {
v.as_u64() as u8
}
fn inject(self) -> Value {
Value::U64(self as u64)
}
}
impl Wire for u16 {
const PORT: PortType = PortType::U16;
fn extract(v: &Value) -> Self {
v.as_u64() as u16
}
fn inject(self) -> Value {
Value::U64(self as u64)
}
}
impl Wire for i8 {
const PORT: PortType = PortType::I8;
fn extract(v: &Value) -> Self {
v.as_i64() as i8
}
fn inject(self) -> Value {
Value::I64(self as i64)
}
}
impl Wire for i16 {
const PORT: PortType = PortType::I16;
fn extract(v: &Value) -> Self {
v.as_i64() as i16
}
fn inject(self) -> Value {
Value::I64(self as i64)
}
}
impl Wire for u128 {
const PORT: PortType = PortType::U128;
fn extract(v: &Value) -> Self {
v.as_u128()
}
fn inject(self) -> Value {
Value::U128(crate::ast::Bits128::from_u128(self))
}
}
impl Wire for i128 {
const PORT: PortType = PortType::I128;
fn extract(v: &Value) -> Self {
v.as_i128()
}
fn inject(self) -> Value {
Value::I128(crate::ast::Bits128::from_i128(self))
}
}
impl Wire for crate::ast::Bits128 {
const PORT: PortType = PortType::Reg128;
fn extract(v: &Value) -> Self {
v.as_reg_bits()
}
fn inject(self) -> Value {
Value::Reg128(self, crate::ast::RegLanes::Raw)
}
}
macro_rules! impl_wire_reg {
($arr:ty, $port:ident, $view:ident, $to:ident, $from:ident) => {
impl Wire for $arr {
const PORT: PortType = PortType::$port;
fn extract(v: &Value) -> Self {
v.as_reg_bits().$to()
}
fn inject(self) -> Value {
Value::Reg128(
crate::ast::Bits128::$from(self),
crate::ast::RegLanes::$view,
)
}
}
};
}
impl_wire_reg!([i8; 16], RegI8x16, I8x16, lanes_i8, from_lanes_i8);
impl_wire_reg!([i16; 8], RegI16x8, I16x8, lanes_i16, from_lanes_i16);
impl_wire_reg!([i32; 4], RegI32x4, I32x4, lanes_i32, from_lanes_i32);
impl_wire_reg!([i64; 2], RegI64x2, I64x2, lanes_i64, from_lanes_i64);
impl_wire_reg!([half::f16; 8], RegF16x8, F16x8, lanes_f16, from_lanes_f16);
impl_wire_reg!([f32; 4], RegF32x4, F32x4, lanes_f32, from_lanes_f32);
impl_wire_reg!([f64; 2], RegF64x2, F64x2, lanes_f64, from_lanes_f64);
impl Wire for f64 {
const PORT: PortType = PortType::F64;
fn extract(v: &Value) -> Self {
v.as_f64()
}
fn inject(self) -> Value {
Value::F64(self)
}
}
impl Wire for f32 {
const PORT: PortType = PortType::F32;
fn extract(v: &Value) -> Self {
f32::from_bits(v.as_u64() as u32)
}
fn inject(self) -> Value {
Value::U64(self.to_bits() as u64)
}
}
impl Wire for half::f16 {
const PORT: PortType = PortType::F16;
fn extract(v: &Value) -> Self {
half::f16::from_bits(v.as_u64() as u16)
}
fn inject(self) -> Value {
Value::U64(self.to_bits() as u64)
}
}
impl Wire for bool {
const PORT: PortType = PortType::Bool;
fn extract(v: &Value) -> Self {
match v {
Value::Bool(b) => *b,
Value::U64(n) => *n != 0,
other => extract_mismatch("bool", Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Bool(self)
}
}
impl Wire for String {
const PORT: PortType = PortType::Str;
fn extract(v: &Value) -> Self {
match v {
Value::Str(s) => s.to_string(),
other => extract_mismatch("String", Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Str(self.into())
}
}
impl Wire for std::sync::Arc<str> {
const PORT: PortType = PortType::Str;
fn extract(v: &Value) -> Self {
match v {
Value::Str(s) => s.clone(),
other => extract_mismatch("Arc<str>", Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Str(self)
}
}
impl Wire for std::sync::Arc<dyn std::any::Any + Send + Sync> {
const PORT: PortType = PortType::Handle;
fn extract(v: &Value) -> Self {
match v {
Value::Handle(arc) => arc.clone(),
other => extract_mismatch("Arc<dyn Any>", Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Handle(self)
}
}
impl Wire for Box<dyn ReflectedValue> {
const PORT: PortType = PortType::Ext;
fn extract(v: &Value) -> Self {
match v {
Value::Ext(b) => b.clone_reflected(),
other => extract_mismatch("Box<dyn ReflectedValue>", Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Ext(self)
}
}
impl Wire for Arc<[u8]> {
const PORT: PortType = PortType::Bytes;
fn extract(v: &Value) -> Self {
match v {
Value::Bytes(b) => b.clone(),
other => extract_mismatch("Arc<[u8]>", Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Bytes(self)
}
}
impl Wire for Vec<u8> {
const PORT: PortType = PortType::Bytes;
fn extract(v: &Value) -> Self {
match v {
Value::Bytes(b) => b.to_vec(),
other => extract_mismatch("Vec<u8>", Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Bytes(self.into())
}
}
impl Wire for Arc<serde_json::Value> {
const PORT: PortType = PortType::Json;
fn extract(v: &Value) -> Self {
match v {
Value::Json(j) => j.clone(),
other => extract_mismatch("Arc<Json>", Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Json(self)
}
}
macro_rules! impl_wire_vec {
($elem:ty, $variant:ident, $port:ident) => {
impl Wire for SliceArc<$elem> {
const PORT: PortType = PortType::$port;
fn extract(v: &Value) -> Self {
match v {
Value::$variant(arc) => arc.clone(),
other => extract_mismatch(
concat!("SliceArc<", stringify!($elem), ">"),
Self::PORT,
other,
),
}
}
fn inject(self) -> Value {
Value::$variant(self)
}
}
impl Wire for Vec<$elem> {
const PORT: PortType = PortType::$port;
fn extract(v: &Value) -> Self {
match v {
Value::$variant(arc) => arc.as_slice().to_vec(),
other => {
extract_mismatch(concat!("Vec<", stringify!($elem), ">"), Self::PORT, other)
}
}
}
fn inject(self) -> Value {
Value::$variant(SliceArc::from_vec(self))
}
}
};
}
impl_wire_vec!(f32, VecF32, VecF32);
impl_wire_vec!(i32, VecI32, VecI32);
impl_wire_vec!(f64, VecF64, VecF64);
impl_wire_vec!(i64, VecI64, VecI64);
impl_wire_vec!(half::f16, VecF16, VecF16);
impl_wire_vec!(i16, VecI16, VecI16);
impl_wire_vec!(i8, VecI8, VecI8);
impl<T: Wire> Wire for Option<T> {
const PORT: PortType = T::PORT;
fn extract(v: &Value) -> Self {
match v {
Value::None => None,
_ => Some(T::extract(v)),
}
}
fn inject(self) -> Value {
match self {
None => Value::None,
Some(t) => t.inject(),
}
}
}
#[derive(Clone)]
pub struct Ext<T>(pub T);
impl<T> std::ops::Deref for Ext<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for Ext<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: ReflectedValue + Clone + 'static> Wire for Ext<T> {
const PORT: PortType = PortType::Ext;
fn extract(v: &Value) -> Self {
match v {
Value::Ext(boxed) => {
let any = boxed.as_any();
match any.downcast_ref::<T>() {
Some(t) => Ext(t.clone()),
None => panic!(
"Wire<Ext<{}>>::extract: ReflectedValue downcast failed; \
got runtime type {:?}",
std::any::type_name::<T>(),
boxed.type_name()
),
}
}
other => extract_mismatch(std::any::type_name::<Self>(), Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Ext(Box::new(self.0))
}
}
pub struct DynamicOutputs<T>(pub Vec<T>);
impl<T> std::ops::Deref for DynamicOutputs<T> {
type Target = Vec<T>;
fn deref(&self) -> &Vec<T> {
&self.0
}
}
pub struct Config<T>(pub T);
impl<T> std::ops::Deref for Config<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: Wire> Wire for Config<T> {
const PORT: PortType = T::PORT;
const RESOLVER: Option<crate::dsl::registry::DefaultResolver> = T::RESOLVER;
const WIRE_COST: crate::ast::WireCost = crate::ast::WireCost::Config;
fn extract(v: &Value) -> Self {
Config(T::extract(v))
}
fn inject(self) -> Value {
self.0.inject()
}
}
pub struct Resolved<R: ResolverKind, T: 'static + Send + Sync> {
inner: std::sync::Arc<T>,
_r: std::marker::PhantomData<fn() -> R>,
}
impl<R: ResolverKind, T: 'static + Send + Sync> std::ops::Deref for Resolved<R, T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<R: ResolverKind, T: 'static + Send + Sync> Resolved<R, T> {
pub fn from_arc(inner: std::sync::Arc<T>) -> Self {
Self {
inner,
_r: std::marker::PhantomData,
}
}
pub fn as_arc(&self) -> &std::sync::Arc<T> {
&self.inner
}
}
pub trait ResolverKind: 'static {
const RESOLVER: crate::dsl::registry::DefaultResolver;
}
pub struct GroupResolver;
impl ResolverKind for GroupResolver {
const RESOLVER: crate::dsl::registry::DefaultResolver =
crate::dsl::registry::DefaultResolver::Group;
}
impl<R: ResolverKind, T: 'static + Send + Sync> Wire for Resolved<R, T> {
const PORT: PortType = PortType::Handle;
const RESOLVER: Option<crate::dsl::registry::DefaultResolver> =
Some(<R as ResolverKind>::RESOLVER);
fn extract(v: &Value) -> Self {
match v {
Value::Handle(arc) => {
let inner = arc.clone().downcast::<T>().unwrap_or_else(|_| {
panic!(
"Wire<Resolved<_, {}>>::extract: Handle downcast failed",
std::any::type_name::<T>()
)
});
Resolved {
inner,
_r: std::marker::PhantomData,
}
}
Value::None => panic!(
"a resolved handle is None — the upstream open failed to \
resolve. The audit log carries the underlying error and \
the name it was opening: a catalog miss, a facet missing \
on disk, or a transport failure. This is the most common \
fault when a workload runs on a system whose catalog is \
not configured for the source it asks for."
),
other => extract_mismatch(std::any::type_name::<Self>(), Self::PORT, other),
}
}
fn inject(self) -> Value {
Value::Handle(self.inner)
}
}
pub struct Const<T>(pub T);
impl<T> std::ops::Deref for Const<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for Const<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
pub trait PolydatSetup {}
#[inline]
pub fn ref_value(slots: &[u64]) -> &Value {
static NONE: Value = Value::None;
if slots.get(1).copied().unwrap_or(0) == 0 {
return &NONE;
}
unsafe { &*(slots[0] as usize as *const Value) }
}
pub fn buffer_for<T>(n: u64, what: &str) -> Vec<T> {
try_buffer_for(n, what).unwrap_or_else(|e| panic!("{e}"))
}
pub fn try_buffer_for<T>(n: u64, what: &str) -> Result<Vec<T>, String> {
let mut v = Vec::new();
match usize::try_from(n) {
Ok(k) if v.try_reserve_exact(k).is_ok() => Ok(v),
_ => Err(size_refusal(n, what)),
}
}
pub fn string_for(n: u64, what: &str) -> String {
let mut s = String::new();
match usize::try_from(n) {
Ok(n) if s.try_reserve_exact(n).is_ok() => s,
_ => refuse_size(n, what),
}
}
pub fn reserve_for<T>(out: &mut Vec<T>, n: u64, what: &str) {
out.clear();
match usize::try_from(n) {
Ok(n) if out.try_reserve_exact(n).is_ok() => {}
_ => refuse_size(n, what),
}
}
#[cold]
fn refuse_size(n: u64, what: &str) -> ! {
panic!("{}", size_refusal(n, what))
}
fn size_refusal(n: u64, what: &str) -> String {
format!("{what}: a buffer of {n} elements cannot be allocated on this machine")
}
#[inline]
pub fn read_poly(ty: PortType, slots: &[u64]) -> Value {
crate::compile::marshal::decode_slot(slots, ty)
}
#[inline]
pub fn write_poly(
ty: PortType,
v: Value,
scratch: &mut [crate::ast::ScratchBuf],
outputs: &mut [u64],
) {
if ty != PortType::Dyn && v.port_type() != crate::compile::marshal::carrier_port(ty) {
panic!(
"a node produced a {:?} on an output the graph typed {:?}; a compiled engine \
cannot carry a value of another type than the slot's (engines.md §3.4)",
v.port_type(),
ty
);
}
match ty.slot_color() {
crate::ast::SlotColor::Imm1 => outputs[0] = carrier_slot_bits(&v),
crate::ast::SlotColor::Imm2 => {
let words = match v {
Value::U128(b) | Value::I128(b) | Value::Reg128(b, _) => b.0,
other => unreachable!("a {:?} is not a two-slot carrier", other.port_type()),
};
outputs[0] = words[0];
outputs[1] = words[1];
}
crate::ast::SlotColor::Ref2 => {
scratch[0].set_from_value(&v);
let (p, l) = scratch[0].ptr_len();
outputs[0] = p;
outputs[1] = l;
}
}
}
#[inline]
fn carrier_slot_bits(v: &Value) -> u64 {
match v {
Value::U64(x) => *x,
Value::I64(x) => *x as u64,
Value::F64(x) => x.to_bits(),
Value::Bool(b) => *b as u64,
other => panic!(
"a {:?} value is not a one-slot carrier; the graph coloured its output Imm1",
other.port_type()
),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn refusal(f: impl FnOnce() + std::panic::UnwindSafe) -> String {
let err = std::panic::catch_unwind(f).expect_err("the size must be refused");
err.downcast_ref::<String>().cloned().unwrap_or_default()
}
#[test]
fn an_impossible_size_is_refused_not_aborted() {
let m = refusal(|| drop(buffer_for::<u32>(u64::MAX, "probe")));
assert!(
m.starts_with("probe: a buffer of 18446744073709551615"),
"{m}"
);
let m = refusal(|| drop(string_for((1 << 53) + 1, "probe")));
assert!(m.contains("cannot be allocated"), "{m}");
let m = refusal(|| reserve_for(&mut vec![1.0f32; 4], u64::MAX, "probe"));
assert!(m.contains("cannot be allocated"), "{m}");
}
#[derive(Debug, Clone)]
struct Probe;
impl ReflectedValue for Probe {
fn type_name(&self) -> &str {
"probe"
}
fn display(&self) -> String {
"probe".into()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn clone_reflected(&self) -> Box<dyn ReflectedValue> {
Box::new(self.clone())
}
}
fn sample(ty: PortType) -> Value {
use crate::ast::{Bits128, RegLanes};
let limbs = Bits128([0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210]);
let reg = |lanes| Value::Reg128(limbs, lanes);
match ty {
PortType::U64 | PortType::U32 | PortType::U16 | PortType::U8 => Value::U64(200),
PortType::F32 => Value::U64(1.5f32.to_bits() as u64),
PortType::F16 => Value::U64(half::f16::from_f32(1.5).to_bits() as u64),
PortType::I64 | PortType::I32 | PortType::I16 | PortType::I8 => Value::I64(-7),
PortType::F64 => Value::F64(-2.25),
PortType::Bool => Value::Bool(true),
PortType::U128 => Value::U128(limbs),
PortType::I128 => Value::I128(limbs),
PortType::Reg128 => reg(RegLanes::Raw),
PortType::RegI8x16 => reg(RegLanes::I8x16),
PortType::RegI16x8 => reg(RegLanes::I16x8),
PortType::RegI32x4 => reg(RegLanes::I32x4),
PortType::RegI64x2 => reg(RegLanes::I64x2),
PortType::RegF16x8 => reg(RegLanes::F16x8),
PortType::RegF32x4 => reg(RegLanes::F32x4),
PortType::RegF64x2 => reg(RegLanes::F64x2),
PortType::Str => Value::Str("héllo".into()),
PortType::Bytes => Value::Bytes(vec![0u8, 1, 255].into()),
PortType::Json => Value::Json(Arc::new(serde_json::json!({"k": [1, 2]}))),
PortType::Ext => Value::Ext(Box::new(Probe)),
PortType::Handle => Value::Handle(Arc::new(42u32)),
PortType::VecF32 => Value::VecF32(SliceArc::from_vec(vec![1.0, -0.5])),
PortType::VecI32 => Value::VecI32(SliceArc::from_vec(vec![-3, 4])),
PortType::VecF64 => Value::VecF64(SliceArc::from_vec(vec![1e300, -0.0])),
PortType::VecI64 => Value::VecI64(SliceArc::from_vec(vec![i64::MIN, 9])),
PortType::VecF16 => Value::VecF16(SliceArc::from_vec(vec![half::f16::from_f32(0.5)])),
PortType::VecI16 => Value::VecI16(SliceArc::from_vec(vec![-300i16, 300])),
PortType::VecI8 => Value::VecI8(SliceArc::from_vec(vec![-8i8, 8])),
PortType::Dyn => Value::Str("any".into()),
}
}
#[test]
fn every_port_type_reads_back_what_was_written() {
for &ty in PortType::ALL {
let v = sample(ty);
if ty != PortType::Dyn {
assert_eq!(
v.port_type(),
crate::compile::marshal::carrier_port(ty),
"{ty:?}: the sample is not what the port carries"
);
}
let mut scratch: Vec<crate::ast::ScratchBuf> = ty
.scratch_elem()
.map(crate::ast::ScratchBuf::new)
.into_iter()
.collect();
let mut slots = [0u64; 2];
write_poly(ty, v.clone(), &mut scratch, &mut slots);
let back = read_poly(ty, &slots[..ty.slot_width()]);
assert_eq!(back, v, "{ty:?}");
}
}
#[test]
fn a_feasible_size_is_reserved() {
let v: Vec<u8> = buffer_for(1000, "probe");
assert!(v.is_empty() && v.capacity() >= 1000);
let mut w = vec![7u8; 3];
reserve_for(&mut w, 64, "probe");
assert!(w.is_empty() && w.capacity() >= 64);
}
}