use std::fmt;
use std::sync::Arc;
use std::ops::Deref;
pub struct SliceArc<T: 'static> {
_owner: Arc<dyn std::any::Any + Send + Sync>,
ptr: *const T,
len: usize,
}
unsafe impl<T: Send + Sync + 'static> Send for SliceArc<T> {}
unsafe impl<T: Send + Sync + 'static> Sync for SliceArc<T> {}
#[allow(dead_code)]
pub(crate) struct OwnedSlice<T: 'static>(pub(crate) Arc<[T]>);
impl<T: Send + Sync + 'static> SliceArc<T> {
pub fn from_vec(v: Vec<T>) -> Self {
let arc: Arc<[T]> = Arc::from(v);
let ptr = arc.as_ptr();
let len = arc.len();
let owner: Arc<dyn std::any::Any + Send + Sync> = Arc::new(OwnedSlice(arc));
Self { _owner: owner, ptr, len }
}
pub unsafe fn from_borrowed(
owner: Arc<dyn std::any::Any + Send + Sync>,
slice: &[T],
) -> Self {
Self {
_owner: owner,
ptr: slice.as_ptr(),
len: slice.len(),
}
}
}
impl<T: 'static> SliceArc<T> {
pub fn as_slice(&self) -> &[T] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
impl<T: Send + Sync + 'static> Clone for SliceArc<T> {
fn clone(&self) -> Self {
Self {
_owner: self._owner.clone(),
ptr: self.ptr,
len: self.len,
}
}
}
impl<T: 'static> Deref for SliceArc<T> {
type Target = [T];
fn deref(&self) -> &[T] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
impl<T: PartialEq + 'static> PartialEq for SliceArc<T> {
fn eq(&self, other: &Self) -> bool {
if std::ptr::eq(self.ptr, other.ptr) && self.len == other.len {
return true;
}
self.as_slice() == other.as_slice()
}
}
impl<T: fmt::Debug + 'static> fmt::Debug for SliceArc<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SliceArc")
.field("len", &self.len)
.field("first", &self.as_slice().first())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Bits128(pub [u64; 2]);
impl Bits128 {
#[inline]
pub fn from_u128(v: u128) -> Self {
Self([v as u64, (v >> 64) as u64])
}
#[inline]
pub fn from_i128(v: i128) -> Self {
Self::from_u128(v as u128)
}
#[inline]
pub fn as_u128(self) -> u128 {
(self.0[0] as u128) | ((self.0[1] as u128) << 64)
}
#[inline]
pub fn as_i128(self) -> i128 {
self.as_u128() as i128
}
#[inline]
pub fn to_le_bytes(self) -> [u8; 16] {
self.as_u128().to_le_bytes()
}
#[inline]
pub fn from_le_bytes(b: [u8; 16]) -> Self {
Self::from_u128(u128::from_le_bytes(b))
}
}
macro_rules! bits128_lanes {
($to:ident, $from:ident, $t:ty, $n:expr) => {
impl Bits128 {
#[inline]
pub fn $to(self) -> [$t; $n] {
let b = self.to_le_bytes();
let mut out = [<$t>::default(); $n];
let w = core::mem::size_of::<$t>();
for (i, lane) in out.iter_mut().enumerate() {
let mut lb = [0u8; core::mem::size_of::<$t>()];
lb.copy_from_slice(&b[i * w..(i + 1) * w]);
*lane = <$t>::from_le_bytes(lb);
}
out
}
#[inline]
pub fn $from(lanes: [$t; $n]) -> Self {
let mut b = [0u8; 16];
let w = core::mem::size_of::<$t>();
for (i, lane) in lanes.iter().enumerate() {
b[i * w..(i + 1) * w].copy_from_slice(&lane.to_le_bytes());
}
Self::from_le_bytes(b)
}
}
};
}
bits128_lanes!(lanes_i8, from_lanes_i8, i8, 16);
bits128_lanes!(lanes_i16, from_lanes_i16, i16, 8);
bits128_lanes!(lanes_i32, from_lanes_i32, i32, 4);
bits128_lanes!(lanes_i64, from_lanes_i64, i64, 2);
bits128_lanes!(lanes_f32, from_lanes_f32, f32, 4);
bits128_lanes!(lanes_f64, from_lanes_f64, f64, 2);
impl Bits128 {
#[inline]
pub fn lanes_f16(self) -> [half::f16; 8] {
self.lanes_i16().map(|b| half::f16::from_bits(b as u16))
}
#[inline]
pub fn from_lanes_f16(lanes: [half::f16; 8]) -> Self {
Self::from_lanes_i16(lanes.map(|f| f.to_bits() as i16))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegLanes {
Raw,
I8x16,
I16x8,
I32x4,
I64x2,
F16x8,
F32x4,
F64x2,
}
#[derive(Debug, Clone)]
pub enum Value {
U64(u64),
U128(Bits128),
I128(Bits128),
Reg128(Bits128, RegLanes),
I64(i64),
F64(f64),
Bool(bool),
Str(Arc<str>),
Bytes(Arc<[u8]>),
Json(Arc<serde_json::Value>),
Ext(Box<dyn ReflectedValue>),
Handle(Arc<dyn std::any::Any + Send + Sync>),
VecF32(SliceArc<f32>),
VecI32(SliceArc<i32>),
VecF64(SliceArc<f64>),
VecI64(SliceArc<i64>),
VecF16(SliceArc<half::f16>),
VecI16(SliceArc<i16>),
VecI8(SliceArc<i8>),
None,
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::U64(a), Value::U64(b)) => a == b,
(Value::I64(a), Value::I64(b)) => a == b,
(Value::U128(a), Value::U128(b)) => a == b,
(Value::I128(a), Value::I128(b)) => a == b,
(Value::Reg128(a, av), Value::Reg128(b, bv)) => a == b && av == bv,
(Value::F64(a), Value::F64(b)) => a == b,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Str(a), Value::Str(b)) => Arc::ptr_eq(a, b) || a == b,
(Value::Bytes(a), Value::Bytes(b)) => Arc::ptr_eq(a, b) || a == b,
(Value::Json(a), Value::Json(b)) => Arc::ptr_eq(a, b) || a == b,
(Value::None, Value::None) => true,
(Value::Ext(a), Value::Ext(b)) => {
a.type_name() == b.type_name() && a.display() == b.display()
}
(Value::Handle(a), Value::Handle(b)) => Arc::ptr_eq(a, b),
(Value::VecF32(a), Value::VecF32(b)) => a == b,
(Value::VecI32(a), Value::VecI32(b)) => a == b,
(Value::VecF64(a), Value::VecF64(b)) => a == b,
(Value::VecI64(a), Value::VecI64(b)) => a == b,
(Value::VecF16(a), Value::VecF16(b)) => a == b,
(Value::VecI16(a), Value::VecI16(b)) => a == b,
(Value::VecI8(a), Value::VecI8(b)) => a == b,
_ => false,
}
}
}
pub trait ReflectedValue: Send + Sync + std::fmt::Debug {
fn type_name(&self) -> &str;
fn display(&self) -> String;
fn to_json_value(&self) -> serde_json::Value {
serde_json::Value::String(self.display())
}
fn try_as_str(&self) -> Option<String> {
Some(self.display())
}
fn try_as_u64(&self) -> Option<u64> { None }
fn try_as_f64(&self) -> Option<f64> { None }
fn try_as_bytes(&self) -> Option<&[u8]> { None }
fn as_any(&self) -> &dyn std::any::Any;
fn clone_reflected(&self) -> Box<dyn ReflectedValue>;
}
impl Clone for Box<dyn ReflectedValue> {
fn clone(&self) -> Self {
self.clone_reflected()
}
}
impl Value {
pub fn as_u64(&self) -> u64 {
match self {
Value::U64(v) => *v,
_ => panic!("expected U64, got {:?}", self.port_type()),
}
}
pub fn as_i64(&self) -> i64 {
match self {
Value::I64(v) => *v,
Value::U64(v) => *v as i64,
_ => panic!("expected I64, got {:?}", self.port_type()),
}
}
pub fn as_u128(&self) -> u128 {
match self {
Value::U128(b) => b.as_u128(),
Value::U64(v) => *v as u128,
_ => panic!("expected U128, got {:?}", self.port_type()),
}
}
pub fn as_i128(&self) -> i128 {
match self {
Value::I128(b) => b.as_i128(),
Value::I64(v) => *v as i128,
Value::U64(v) => *v as i128,
_ => panic!("expected I128, got {:?}", self.port_type()),
}
}
pub fn as_reg_bits(&self) -> Bits128 {
match self {
Value::Reg128(b, _) => *b,
_ => panic!("expected Reg128, got {:?}", self.port_type()),
}
}
pub fn as_f64(&self) -> f64 {
match self {
Value::F64(v) => *v,
_ => panic!("expected F64, got {:?}", self.port_type()),
}
}
pub fn as_bool(&self) -> bool {
match self {
Value::Bool(v) => *v,
_ => panic!("expected Bool, got {:?}", self.port_type()),
}
}
pub fn as_str(&self) -> &str {
match self {
Value::Str(v) => v,
_ => panic!("expected Str, got {:?}", self.port_type()),
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
Value::Bytes(v) => v,
_ => panic!("expected Bytes, got {:?}", self.port_type()),
}
}
pub fn as_json(&self) -> &serde_json::Value {
match self {
Value::Json(v) => v,
_ => panic!("expected Json, got {:?}", self.port_type()),
}
}
pub fn as_json_arc(&self) -> &Arc<serde_json::Value> {
match self {
Value::Json(v) => v,
_ => panic!("expected Json, got {:?}", self.port_type()),
}
}
pub fn port_type(&self) -> PortType {
match self {
Value::U64(_) => PortType::U64,
Value::I64(_) => PortType::I64,
Value::U128(_) => PortType::U128,
Value::I128(_) => PortType::I128,
Value::Reg128(_, v) => match v {
RegLanes::Raw => PortType::Reg128,
RegLanes::I8x16 => PortType::RegI8x16,
RegLanes::I16x8 => PortType::RegI16x8,
RegLanes::I32x4 => PortType::RegI32x4,
RegLanes::I64x2 => PortType::RegI64x2,
RegLanes::F16x8 => PortType::RegF16x8,
RegLanes::F32x4 => PortType::RegF32x4,
RegLanes::F64x2 => PortType::RegF64x2,
},
Value::F64(_) => PortType::F64,
Value::Bool(_) => PortType::Bool,
Value::Str(_) => PortType::Str,
Value::Bytes(_) => PortType::Bytes,
Value::Json(_) => PortType::Json,
Value::Ext(_) => PortType::Ext,
Value::Handle(_) => PortType::Handle,
Value::VecF32(_) => PortType::VecF32,
Value::VecI32(_) => PortType::VecI32,
Value::VecF64(_) => PortType::VecF64,
Value::VecI64(_) => PortType::VecI64,
Value::VecF16(_) => PortType::VecF16,
Value::VecI16(_) => PortType::VecI16,
Value::VecI8(_) => PortType::VecI8,
Value::None => PortType::U64, }
}
pub fn as_vec_f32(&self) -> &[f32] {
match self {
Value::VecF32(arc) => arc,
_ => panic!("expected VecF32, got {:?}", self.port_type()),
}
}
pub fn satisfies_slot(&self, slot_type: PortType) -> bool {
if matches!(self, Value::None) {
return true;
}
let value_type = self.port_type();
if value_type == slot_type {
return true;
}
matches!(
(value_type, slot_type),
(PortType::U64, PortType::U32 | PortType::I64 | PortType::I32
| PortType::U8 | PortType::U16 | PortType::I8 | PortType::I16
| PortType::F16)
| (PortType::F64, PortType::F32 | PortType::F16)
| (PortType::I64, PortType::I32 | PortType::I8 | PortType::I16)
| (
PortType::Reg128 | PortType::RegI8x16 | PortType::RegI16x8
| PortType::RegI32x4 | PortType::RegI64x2
| PortType::RegF16x8 | PortType::RegF32x4 | PortType::RegF64x2,
PortType::Reg128 | PortType::RegI8x16 | PortType::RegI16x8
| PortType::RegI32x4 | PortType::RegI64x2
| PortType::RegF16x8 | PortType::RegF32x4 | PortType::RegF64x2,
)
)
}
pub fn as_vec_i32(&self) -> &[i32] {
match self {
Value::VecI32(arc) => arc,
_ => panic!("expected VecI32, got {:?}", self.port_type()),
}
}
pub fn as_vec_f64(&self) -> &[f64] {
match self {
Value::VecF64(arc) => arc,
_ => panic!("expected VecF64, got {:?}", self.port_type()),
}
}
pub fn as_vec_i64(&self) -> &[i64] {
match self {
Value::VecI64(arc) => arc,
_ => panic!("expected VecI64, got {:?}", self.port_type()),
}
}
pub fn as_vec_f16(&self) -> &[half::f16] {
match self {
Value::VecF16(arc) => arc,
_ => panic!("expected VecF16, got {:?}", self.port_type()),
}
}
pub fn as_vec_i16(&self) -> &[i16] {
match self {
Value::VecI16(arc) => arc,
_ => panic!("expected VecI16, got {:?}", self.port_type()),
}
}
pub fn as_vec_i8(&self) -> &[i8] {
match self {
Value::VecI8(arc) => arc,
_ => panic!("expected VecI8, got {:?}", self.port_type()),
}
}
pub fn as_handle<T: std::any::Any + Send + Sync>(&self) -> &T {
match self {
Value::Handle(arc) => arc.downcast_ref::<T>().unwrap_or_else(|| {
panic!(
"Handle downcast failed: expected {}",
std::any::type_name::<T>()
)
}),
_ => panic!("expected Handle, got {:?}", self.port_type()),
}
}
pub fn handle<T: std::any::Any + Send + Sync>(arc: Arc<T>) -> Self {
Value::Handle(arc as Arc<dyn std::any::Any + Send + Sync>)
}
pub fn to_display_string(&self) -> String {
match self {
Value::U64(v) => v.to_string(),
Value::I64(v) => v.to_string(),
Value::U128(b) => b.as_u128().to_string(),
Value::I128(b) => b.as_i128().to_string(),
Value::Reg128(b, view) => match view {
RegLanes::Raw => format!("{:032x}", b.as_u128()),
RegLanes::I8x16 => format!("{:?}", b.lanes_i8()),
RegLanes::I16x8 => format!("{:?}", b.lanes_i16()),
RegLanes::I32x4 => format!("{:?}", b.lanes_i32()),
RegLanes::I64x2 => format!("{:?}", b.lanes_i64()),
RegLanes::F16x8 => format!("{:?}", b.lanes_f16().map(|f| f.to_f32())),
RegLanes::F32x4 => format!("{:?}", b.lanes_f32()),
RegLanes::F64x2 => format!("{:?}", b.lanes_f64()),
},
Value::F64(v) => format!("{v:?}"),
Value::Bool(v) => v.to_string(),
Value::Str(v) => v.to_string(),
Value::Bytes(v) => v.iter().map(|b| format!("{b:02x}")).collect(),
Value::Json(v) => v.to_string(),
Value::Ext(v) => v.display(),
Value::Handle(arc) => format!("<handle:{:?}>", arc.type_id()),
Value::VecF32(arc) => {
let mut s = String::with_capacity(arc.len() * 8 + 2);
s.push('[');
let mut first = true;
for v in arc.iter() {
if !first { s.push(','); }
first = false;
use std::fmt::Write;
let _ = write!(&mut s, "{v:?}");
}
s.push(']');
s
}
Value::VecI32(arc) => {
let mut s = String::with_capacity(arc.len() * 4 + 2);
s.push('[');
let mut first = true;
for v in arc.iter() {
if !first { s.push(','); }
first = false;
use std::fmt::Write;
let _ = write!(&mut s, "{v}");
}
s.push(']');
s
}
Value::VecF64(arc) => {
let mut s = String::with_capacity(arc.len() * 8 + 2);
s.push('[');
let mut first = true;
for v in arc.iter() {
if !first { s.push(','); }
first = false;
use std::fmt::Write;
let _ = write!(&mut s, "{v:?}");
}
s.push(']');
s
}
Value::VecI64(arc) => {
let mut s = String::with_capacity(arc.len() * 4 + 2);
s.push('[');
let mut first = true;
for v in arc.iter() {
if !first { s.push(','); }
first = false;
use std::fmt::Write;
let _ = write!(&mut s, "{v}");
}
s.push(']');
s
}
Value::VecF16(arc) => {
let mut s = String::with_capacity(arc.len() * 6 + 2);
s.push('[');
let mut first = true;
for v in arc.iter() {
if !first { s.push(','); }
first = false;
use std::fmt::Write;
let _ = write!(&mut s, "{:?}", v.to_f32());
}
s.push(']');
s
}
Value::VecI16(arc) => {
let mut s = String::with_capacity(arc.len() * 4 + 2);
s.push('[');
let mut first = true;
for v in arc.iter() {
if !first { s.push(','); }
first = false;
use std::fmt::Write;
let _ = write!(&mut s, "{v}");
}
s.push(']');
s
}
Value::VecI8(arc) => {
let mut s = String::with_capacity(arc.len() * 4 + 2);
s.push('[');
let mut first = true;
for v in arc.iter() {
if !first { s.push(','); }
first = false;
use std::fmt::Write;
let _ = write!(&mut s, "{v}");
}
s.push(']');
s
}
Value::None => String::new(),
}
}
pub fn to_display_strict(&self) -> Option<String> {
match self {
Value::None => None,
other => Some(other.to_display_string()),
}
}
pub fn to_json_value(&self) -> serde_json::Value {
match self {
Value::U64(v) => serde_json::Value::from(*v),
Value::I64(v) => serde_json::Value::from(*v),
Value::U128(b) => serde_json::Value::String(b.as_u128().to_string()),
Value::I128(b) => serde_json::Value::String(b.as_i128().to_string()),
Value::Reg128(b, view) => match view {
RegLanes::Raw => serde_json::Value::String(format!("{:032x}", b.as_u128())),
RegLanes::I8x16 => serde_json::Value::Array(
b.lanes_i8().iter().map(|i| serde_json::Value::from(*i as i32)).collect()),
RegLanes::I16x8 => serde_json::Value::Array(
b.lanes_i16().iter().map(|i| serde_json::Value::from(*i as i32)).collect()),
RegLanes::I32x4 => serde_json::Value::Array(
b.lanes_i32().iter().map(|i| serde_json::Value::from(*i)).collect()),
RegLanes::I64x2 => serde_json::Value::Array(
b.lanes_i64().iter().map(|i| serde_json::Value::from(*i)).collect()),
RegLanes::F16x8 => serde_json::Value::Array(
b.lanes_f16().iter().map(|f| serde_json::json!(f.to_f32())).collect()),
RegLanes::F32x4 => serde_json::Value::Array(
b.lanes_f32().iter().map(|f| serde_json::json!(*f)).collect()),
RegLanes::F64x2 => serde_json::Value::Array(
b.lanes_f64().iter().map(|f| serde_json::json!(*f)).collect()),
},
Value::F64(v) => serde_json::json!(*v),
Value::Bool(v) => serde_json::Value::from(*v),
Value::Str(v) => serde_json::Value::from(&**v),
Value::Bytes(v) => serde_json::Value::from(v.iter().map(|b| format!("{b:02x}")).collect::<String>()),
Value::Json(v) => (**v).clone(),
Value::Ext(v) => v.to_json_value(),
Value::Handle(_) => serde_json::Value::Null,
Value::VecF32(arc) => serde_json::Value::Array(
arc.iter().map(|f| serde_json::json!(*f)).collect()
),
Value::VecI32(arc) => serde_json::Value::Array(
arc.iter().map(|i| serde_json::Value::from(*i)).collect()
),
Value::VecF64(arc) => serde_json::Value::Array(
arc.iter().map(|f| serde_json::json!(*f)).collect()
),
Value::VecI64(arc) => serde_json::Value::Array(
arc.iter().map(|i| serde_json::Value::from(*i)).collect()
),
Value::VecF16(arc) => serde_json::Value::Array(
arc.iter().map(|f| serde_json::json!(f.to_f32())).collect()
),
Value::VecI16(arc) => serde_json::Value::Array(
arc.iter().map(|i| serde_json::Value::from(*i as i32)).collect()
),
Value::VecI8(arc) => serde_json::Value::Array(
arc.iter().map(|i| serde_json::Value::from(*i as i32)).collect()
),
Value::None => serde_json::Value::Null,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PortType {
U64,
F64,
U32,
I32,
I64,
F32,
U8,
I8,
U16,
I16,
F16,
U128,
I128,
Reg128,
RegI8x16,
RegI16x8,
RegI32x4,
RegI64x2,
RegF16x8,
RegF32x4,
RegF64x2,
Bool,
Str,
Bytes,
Json,
Ext,
Handle,
VecF32,
VecI32,
VecF64,
VecI64,
VecF16,
VecI16,
VecI8,
}
impl fmt::Display for PortType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PortType::U64 => write!(f, "u64"),
PortType::F64 => write!(f, "f64"),
PortType::U32 => write!(f, "u32"),
PortType::I32 => write!(f, "i32"),
PortType::I64 => write!(f, "i64"),
PortType::F32 => write!(f, "f32"),
PortType::U8 => write!(f, "u8"),
PortType::I8 => write!(f, "i8"),
PortType::U16 => write!(f, "u16"),
PortType::I16 => write!(f, "i16"),
PortType::F16 => write!(f, "f16"),
PortType::U128 => write!(f, "u128"),
PortType::I128 => write!(f, "i128"),
PortType::Reg128 => write!(f, "reg128"),
PortType::RegI8x16 => write!(f, "reg_i8x16"),
PortType::RegI16x8 => write!(f, "reg_i16x8"),
PortType::RegI32x4 => write!(f, "reg_i32x4"),
PortType::RegI64x2 => write!(f, "reg_i64x2"),
PortType::RegF16x8 => write!(f, "reg_f16x8"),
PortType::RegF32x4 => write!(f, "reg_f32x4"),
PortType::RegF64x2 => write!(f, "reg_f64x2"),
PortType::Bool => write!(f, "bool"),
PortType::Str => write!(f, "String"),
PortType::Bytes => write!(f, "bytes"),
PortType::Json => write!(f, "json"),
PortType::Ext => write!(f, "ext"),
PortType::Handle => write!(f, "handle"),
PortType::VecF32 => write!(f, "vec_f32"),
PortType::VecI32 => write!(f, "vec_i32"),
PortType::VecF64 => write!(f, "vec_f64"),
PortType::VecI64 => write!(f, "vec_i64"),
PortType::VecF16 => write!(f, "vec_f16"),
PortType::VecI16 => write!(f, "vec_i16"),
PortType::VecI8 => write!(f, "vec_i8"),
}
}
}
impl PortType {
pub fn to_keyword(&self) -> &'static str {
match self {
Self::U64 => "u64",
Self::F64 => "f64",
Self::U32 => "u32",
Self::I32 => "i32",
Self::I64 => "i64",
Self::F32 => "f32",
Self::U8 => "u8",
Self::I8 => "i8",
Self::U16 => "u16",
Self::I16 => "i16",
Self::F16 => "f16",
Self::U128 => "u128",
Self::I128 => "i128",
Self::Reg128 => "reg128",
Self::RegI8x16 => "reg_i8x16",
Self::RegI16x8 => "reg_i16x8",
Self::RegI32x4 => "reg_i32x4",
Self::RegI64x2 => "reg_i64x2",
Self::RegF16x8 => "reg_f16x8",
Self::RegF32x4 => "reg_f32x4",
Self::RegF64x2 => "reg_f64x2",
Self::Bool => "bool",
Self::Str => "str",
Self::Bytes => "bytes",
Self::Json => "json",
Self::Ext => "ext",
Self::Handle => "handle",
Self::VecF32 => "vec_f32",
Self::VecI32 => "vec_i32",
Self::VecF64 => "vec_f64",
Self::VecI64 => "vec_i64",
Self::VecF16 => "vec_f16",
Self::VecI16 => "vec_i16",
Self::VecI8 => "vec_i8",
}
}
pub fn from_keyword(name: &str) -> Option<Self> {
match name {
"u64" => Some(Self::U64),
"f64" => Some(Self::F64),
"u32" => Some(Self::U32),
"i32" => Some(Self::I32),
"i64" => Some(Self::I64),
"f32" => Some(Self::F32),
"u8" => Some(Self::U8),
"i8" => Some(Self::I8),
"u16" => Some(Self::U16),
"i16" => Some(Self::I16),
"f16" => Some(Self::F16),
"u128" => Some(Self::U128),
"i128" => Some(Self::I128),
"reg128" => Some(Self::Reg128),
"reg_i8x16" => Some(Self::RegI8x16),
"reg_i16x8" => Some(Self::RegI16x8),
"reg_i32x4" => Some(Self::RegI32x4),
"reg_i64x2" => Some(Self::RegI64x2),
"reg_f16x8" => Some(Self::RegF16x8),
"reg_f32x4" => Some(Self::RegF32x4),
"reg_f64x2" => Some(Self::RegF64x2),
"bool" => Some(Self::Bool),
"str" | "Str" | "String" => Some(Self::Str),
"bytes" => Some(Self::Bytes),
"json" | "Json" => Some(Self::Json),
"ext" | "Ext" => Some(Self::Ext),
"handle" => Some(Self::Handle),
"vec_f32" => Some(Self::VecF32),
"vec_i32" => Some(Self::VecI32),
"vec_f64" => Some(Self::VecF64),
"vec_i64" => Some(Self::VecI64),
"vec_f16" => Some(Self::VecF16),
"vec_i16" => Some(Self::VecI16),
"vec_i8" => Some(Self::VecI8),
_ => None,
}
}
pub fn slot_color(&self) -> SlotColor {
match self {
Self::U128 | Self::I128
| Self::Reg128 | Self::RegI8x16 | Self::RegI16x8
| Self::RegI32x4 | Self::RegI64x2 | Self::RegF16x8
| Self::RegF32x4 | Self::RegF64x2 => SlotColor::Imm2,
Self::VecF32 | Self::VecI32 | Self::VecF64
| Self::VecI64 | Self::VecF16 | Self::VecI16
| Self::VecI8 => SlotColor::Ref2,
_ => SlotColor::Imm1,
}
}
pub fn slot_width(&self) -> usize {
match self.slot_color() {
SlotColor::Imm1 => 1,
SlotColor::Imm2 | SlotColor::Ref2 => 2,
}
}
pub fn from_workload_name(name: &str) -> Option<Self> {
match Self::from_keyword(name)? {
Self::Handle | Self::Ext => None,
pt => Some(pt),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Lifecycle {
Cycle,
Init,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WireCost {
#[default]
Data,
Config,
}
#[derive(Debug, Clone)]
pub struct Port {
pub name: String,
pub typ: PortType,
pub lifecycle: Lifecycle,
pub wire_cost: WireCost,
pub constraint: Option<crate::dsl::const_constraints::ConstConstraint>,
}
impl Port {
pub fn new(name: impl Into<String>, typ: PortType) -> Self {
Self {
name: name.into(),
typ,
lifecycle: Lifecycle::Cycle,
wire_cost: WireCost::Data,
constraint: None,
}
}
pub fn with_lifecycle(name: impl Into<String>, typ: PortType, lifecycle: Lifecycle) -> Self {
Self {
name: name.into(),
typ,
lifecycle,
wire_cost: WireCost::Data,
constraint: None,
}
}
pub fn u64(name: impl Into<String>) -> Self {
Self::new(name, PortType::U64)
}
pub fn f64(name: impl Into<String>) -> Self {
Self::new(name, PortType::F64)
}
pub fn str(name: impl Into<String>) -> Self {
Self::new(name, PortType::Str)
}
pub fn bool(name: impl Into<String>) -> Self {
Self::new(name, PortType::Bool)
}
pub fn json(name: impl Into<String>) -> Self {
Self::new(name, PortType::Json)
}
pub fn handle(name: impl Into<String>) -> Self {
Self::new(name, PortType::Handle)
}
pub fn vec_f32(name: impl Into<String>) -> Self {
Self::new(name, PortType::VecF32)
}
pub fn vec_i32(name: impl Into<String>) -> Self {
Self::new(name, PortType::VecI32)
}
pub fn init(name: impl Into<String>, typ: PortType) -> Self {
Self::with_lifecycle(name, typ, Lifecycle::Init)
}
pub fn with_constraint(mut self, c: crate::dsl::const_constraints::ConstConstraint) -> Self {
self.constraint = Some(c);
self
}
pub fn config(mut self) -> Self {
self.wire_cost = WireCost::Config;
self
}
pub fn with_cost(mut self, cost: WireCost) -> Self {
self.wire_cost = cost;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SlotType {
Wire,
ConstU64,
ConstF64,
ConstStr,
ConstVecU64,
ConstVecF64,
ConstVec,
}
impl SlotType {
pub fn is_const(self) -> bool {
!matches!(self, SlotType::Wire)
}
pub fn is_wire(self) -> bool {
matches!(self, SlotType::Wire)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JitType {
U64,
I64,
F64,
Bool,
Str,
Bytes,
U8,
U16,
U32,
I8,
I16,
I32,
F32,
F16,
U128,
I128,
Reg128,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConstValue {
U64(u64),
F64(f64),
Str(String),
VecU64(Vec<u64>),
VecF64(Vec<f64>),
}
impl ConstValue {
pub fn slot_type(&self) -> SlotType {
match self {
ConstValue::U64(_) => SlotType::ConstU64,
ConstValue::F64(_) => SlotType::ConstF64,
ConstValue::Str(_) => SlotType::ConstStr,
ConstValue::VecU64(_) => SlotType::ConstVecU64,
ConstValue::VecF64(_) => SlotType::ConstVecF64,
}
}
pub fn to_jit_u64s(&self) -> Vec<u64> {
match self {
ConstValue::U64(v) => vec![*v],
ConstValue::F64(v) => vec![v.to_bits()],
ConstValue::Str(_) => vec![],
ConstValue::VecU64(v) => v.clone(),
ConstValue::VecF64(v) => v.iter().map(|f| f.to_bits()).collect(),
}
}
}
#[derive(Debug, Clone)]
pub enum Slot {
Wire(Port),
Const {
name: String,
value: ConstValue,
},
}
impl Slot {
pub fn slot_type(&self) -> SlotType {
match self {
Slot::Wire(_) => SlotType::Wire,
Slot::Const { value, .. } => value.slot_type(),
}
}
pub fn wire(port: Port) -> Self { Slot::Wire(port) }
pub fn const_u64(name: impl Into<String>, v: u64) -> Self {
Slot::Const { name: name.into(), value: ConstValue::U64(v) }
}
pub fn const_f64(name: impl Into<String>, v: f64) -> Self {
Slot::Const { name: name.into(), value: ConstValue::F64(v) }
}
pub fn const_str(name: impl Into<String>, v: impl Into<String>) -> Self {
Slot::Const { name: name.into(), value: ConstValue::Str(v.into()) }
}
pub fn const_vec_u64(name: impl Into<String>, v: Vec<u64>) -> Self {
Slot::Const { name: name.into(), value: ConstValue::VecU64(v) }
}
pub fn const_vec_f64(name: impl Into<String>, v: Vec<f64>) -> Self {
Slot::Const { name: name.into(), value: ConstValue::VecF64(v) }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Default)]
pub enum Commutativity {
#[default]
Positional,
AllCommutative,
Groups(Vec<Vec<usize>>),
}
#[derive(Debug, Clone)]
pub struct NodeMeta {
pub name: String,
pub ins: Vec<Slot>,
pub outs: Vec<Port>,
}
impl NodeMeta {
pub fn wire_inputs(&self) -> Vec<&Port> {
self.ins.iter().filter_map(|s| match s {
Slot::Wire(p) => Some(p),
Slot::Const { .. } => None,
}).collect()
}
pub fn const_slots(&self) -> Vec<(&str, &ConstValue)> {
self.ins.iter().filter_map(|s| match s {
Slot::Const { name, value } => Some((name.as_str(), value)),
Slot::Wire(_) => None,
}).collect()
}
pub fn jit_constants_from_slots(&self) -> Vec<u64> {
self.const_slots().iter()
.flat_map(|(_, v)| v.to_jit_u64s())
.collect()
}
}
pub type CompiledU64Op = Box<dyn Fn(&[u64], &mut [u64]) + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScratchElem {
F32,
F64,
F16,
I8,
I16,
I32,
I64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotColor {
Imm1,
Imm2,
Ref2,
}
#[derive(Debug)]
pub enum ScratchBuf {
F32(Vec<f32>),
F64(Vec<f64>),
F16(Vec<half::f16>),
I8(Vec<i8>),
I16(Vec<i16>),
I32(Vec<i32>),
I64(Vec<i64>),
}
impl ScratchBuf {
pub fn ptr_len(&self) -> (u64, u64) {
match self {
ScratchBuf::F32(v) => (v.as_ptr() as usize as u64, v.len() as u64),
ScratchBuf::F64(v) => (v.as_ptr() as usize as u64, v.len() as u64),
ScratchBuf::F16(v) => (v.as_ptr() as usize as u64, v.len() as u64),
ScratchBuf::I8(v) => (v.as_ptr() as usize as u64, v.len() as u64),
ScratchBuf::I16(v) => (v.as_ptr() as usize as u64, v.len() as u64),
ScratchBuf::I32(v) => (v.as_ptr() as usize as u64, v.len() as u64),
ScratchBuf::I64(v) => (v.as_ptr() as usize as u64, v.len() as u64),
}
}
pub fn new(elem: ScratchElem) -> Self {
match elem {
ScratchElem::F32 => ScratchBuf::F32(Vec::new()),
ScratchElem::F64 => ScratchBuf::F64(Vec::new()),
ScratchElem::F16 => ScratchBuf::F16(Vec::new()),
ScratchElem::I8 => ScratchBuf::I8(Vec::new()),
ScratchElem::I16 => ScratchBuf::I16(Vec::new()),
ScratchElem::I32 => ScratchBuf::I32(Vec::new()),
ScratchElem::I64 => ScratchBuf::I64(Vec::new()),
}
}
}
pub type CompiledSlotOp =
Box<dyn Fn(&[u64], &mut [u64], &mut [ScratchBuf]) + Send + Sync>;
pub struct CompiledSlotKit {
pub op: CompiledSlotOp,
pub scratch: Vec<ScratchElem>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Purity {
Pure,
SideChannel { sink: SideChannelSink },
Nondeterministic { reason: &'static str },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SideChannelSink {
Stderr,
Stdout,
LogBuffer,
File,
Network,
Other,
}
pub trait PolydatNode: Send + Sync {
fn meta(&self) -> &NodeMeta;
fn eval(&self, inputs: &[Value], outputs: &mut [Value]);
fn commutativity(&self) -> Commutativity {
Commutativity::Positional
}
fn accepts_none_inputs(&self) -> bool {
false
}
fn compiled_u64(&self) -> Option<CompiledU64Op> {
None
}
fn compiled_slot(&self) -> Option<CompiledSlotKit> {
None
}
fn jit_constants(&self) -> Vec<u64> {
Vec::new()
}
fn purity(&self) -> Purity {
Purity::Pure
}
fn fusion_subgraph(&self) -> Option<FusionSubgraph<'_>> {
None
}
}
pub struct FusionSubgraph<'a> {
pub members: &'a [Box<dyn PolydatNode>],
pub wiring: &'a [Vec<crate::kernel::WireSource>],
pub out_ports: &'a [(usize, usize)],
}
pub fn compile_level_of(node: &dyn PolydatNode) -> CompileLevel {
#[cfg(feature = "jit")]
{
let jit_op = crate::compile::jit::classify_node(node);
if !matches!(jit_op, crate::compile::jit::JitOp::Fallback) {
return CompileLevel::Phase3;
}
}
if node.compiled_u64().is_some() {
CompileLevel::Phase2
} else {
CompileLevel::Phase1
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompileLevel {
Phase1,
Phase2,
Phase3,
}
#[cfg(test)]
mod purity_tests {
use super::*;
struct DefaultPureNode {
meta: NodeMeta,
}
impl PolydatNode for DefaultPureNode {
fn meta(&self) -> &NodeMeta { &self.meta }
fn eval(&self, _inputs: &[Value], outputs: &mut [Value]) {
outputs[0] = Value::U64(42);
}
}
struct SideChannelNode {
meta: NodeMeta,
}
impl PolydatNode for SideChannelNode {
fn meta(&self) -> &NodeMeta { &self.meta }
fn eval(&self, _inputs: &[Value], _outputs: &mut [Value]) {}
fn purity(&self) -> Purity {
Purity::SideChannel { sink: SideChannelSink::Stderr }
}
}
struct StatefulNode {
meta: NodeMeta,
}
impl PolydatNode for StatefulNode {
fn meta(&self) -> &NodeMeta { &self.meta }
fn eval(&self, _inputs: &[Value], _outputs: &mut [Value]) {}
fn purity(&self) -> Purity {
Purity::Nondeterministic { reason: "test fixture" }
}
}
fn empty_meta() -> NodeMeta {
NodeMeta {
name: "test".into(),
ins: vec![],
outs: vec![Port::u64("out")],
}
}
#[test]
fn default_purity_is_pure() {
let n = DefaultPureNode { meta: empty_meta() };
assert_eq!(n.purity(), Purity::Pure);
}
#[test]
fn side_channel_declaration_is_observable() {
let n = SideChannelNode { meta: empty_meta() };
match n.purity() {
Purity::SideChannel { sink } => assert_eq!(sink, SideChannelSink::Stderr),
other => panic!("expected SideChannel, got {other:?}"),
}
}
#[test]
fn stateful_declaration_is_observable() {
let n = StatefulNode { meta: empty_meta() };
match n.purity() {
Purity::Nondeterministic { reason } => assert_eq!(reason, "test fixture"),
other => panic!("expected Stateful, got {other:?}"),
}
}
#[test]
fn inspect_node_declares_stderr_side_channel() {
let n = crate::library::diagnostic::Inspect::new(PortType::U64, "x".to_string());
match n.purity() {
Purity::SideChannel { sink } => assert_eq!(sink, SideChannelSink::Stderr),
other => panic!("inspect should declare Stderr SideChannel, got {other:?}"),
}
}
#[test]
fn log_passthrough_declares_log_buffer_side_channel() {
let n = crate::library::log_levels::LogInfo::new(PortType::U64);
match n.purity() {
Purity::SideChannel { sink } => assert_eq!(sink, SideChannelSink::LogBuffer),
other => panic!("log_passthrough should declare LogBuffer SideChannel, got {other:?}"),
}
}
}
#[cfg(test)]
mod value_size_probe {
#[test]
fn value_fits_size_envelope() {
assert!(
std::mem::size_of::<super::Value>() <= 40,
"Value grew past the 40-byte envelope: {}",
std::mem::size_of::<super::Value>()
);
assert_eq!(std::mem::align_of::<super::Value>(), 8,
"Value alignment must stay 8 — a 16-aligned payload \
(raw u128/i128?) snuck in");
}
}