use std::fmt;
use std::ops::Deref;
use std::sync::Arc;
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> {
#[inline]
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 {
#[inline]
pub fn as_u64(&self) -> u64 {
match self {
Value::U64(v) => *v,
_ => panic!("expected U64, got {:?}", self.port_type()),
}
}
#[inline]
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()),
}
}
#[inline]
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()),
}
}
#[inline]
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()),
}
}
#[inline]
pub fn as_reg_bits(&self) -> Bits128 {
match self {
Value::Reg128(b, _) => *b,
_ => panic!("expected Reg128, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_f64(&self) -> f64 {
match self {
Value::F64(v) => *v,
_ => panic!("expected F64, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_bool(&self) -> bool {
match self {
Value::Bool(v) => *v,
_ => panic!("expected Bool, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_str(&self) -> &str {
match self {
Value::Str(v) => v,
_ => panic!("expected Str, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
match self {
Value::Bytes(v) => v,
_ => panic!("expected Bytes, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_json(&self) -> &serde_json::Value {
match self {
Value::Json(v) => v,
_ => panic!("expected Json, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_json_arc(&self) -> &Arc<serde_json::Value> {
match self {
Value::Json(v) => v,
_ => panic!("expected Json, got {:?}", self.port_type()),
}
}
#[inline]
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, }
}
#[inline]
pub fn as_vec_f32(&self) -> &[f32] {
match self {
Value::VecF32(arc) => arc,
_ => panic!("expected VecF32, got {:?}", self.port_type()),
}
}
#[inline]
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::F32 | 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,
)
)
}
#[inline]
pub fn as_vec_i32(&self) -> &[i32] {
match self {
Value::VecI32(arc) => arc,
_ => panic!("expected VecI32, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_vec_f64(&self) -> &[f64] {
match self {
Value::VecF64(arc) => arc,
_ => panic!("expected VecF64, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_vec_i64(&self) -> &[i64] {
match self {
Value::VecI64(arc) => arc,
_ => panic!("expected VecI64, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_vec_f16(&self) -> &[half::f16] {
match self {
Value::VecF16(arc) => arc,
_ => panic!("expected VecF16, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_vec_i16(&self) -> &[i16] {
match self {
Value::VecI16(arc) => arc,
_ => panic!("expected VecI16, got {:?}", self.port_type()),
}
}
#[inline]
pub fn as_vec_i8(&self) -> &[i8] {
match self {
Value::VecI8(arc) => arc,
_ => panic!("expected VecI8, got {:?}", self.port_type()),
}
}
#[inline]
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,
}
}
}
pub use polydat_grammar::PortType;
pub trait SlotShape {
fn slot_color(&self) -> SlotColor;
fn scratch_elem(&self) -> Option<ScratchElem>;
fn slot_width(&self) -> usize;
}
impl SlotShape for PortType {
#[inline]
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
| Self::Str
| Self::Bytes
| Self::Json
| Self::Ext
| Self::Handle => SlotColor::Ref2,
_ => SlotColor::Imm1,
}
}
#[inline]
fn scratch_elem(&self) -> Option<ScratchElem> {
Some(match self {
Self::VecF32 => ScratchElem::F32,
Self::VecF64 => ScratchElem::F64,
Self::VecF16 => ScratchElem::F16,
Self::VecI8 => ScratchElem::I8,
Self::VecI16 => ScratchElem::I16,
Self::VecI32 => ScratchElem::I32,
Self::VecI64 => ScratchElem::I64,
Self::Str => ScratchElem::Str,
Self::Bytes => ScratchElem::Bytes,
Self::Json | Self::Ext | Self::Handle => ScratchElem::Value,
_ => return None,
})
}
#[inline]
fn slot_width(&self) -> usize {
match self.slot_color() {
SlotColor::Imm1 => 1,
SlotColor::Imm2 | SlotColor::Ref2 => 2,
}
}
}
#[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,
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, 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,
Str,
Bytes,
Value,
Slots,
Kernels,
State,
}
#[derive(Default)]
pub struct NodeState(Option<Box<dyn std::any::Any + Send + Sync>>);
impl NodeState {
pub fn get_or_insert_with<T: std::any::Any + Send + Sync>(
&mut self,
init: impl FnOnce() -> T,
) -> &mut T {
if !self.0.as_ref().is_some_and(|b| b.is::<T>()) {
self.0 = Some(Box::new(init()));
}
self.0
.as_mut()
.and_then(|b| b.downcast_mut::<T>())
.expect("the entry holds a T")
}
pub fn get<T: std::any::Any + Send + Sync>(&self) -> Option<&T> {
self.0.as_ref().and_then(|b| b.downcast_ref::<T>())
}
}
impl Clone for NodeState {
fn clone(&self) -> Self {
NodeState(None)
}
}
impl std::fmt::Debug for NodeState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"NodeState({})",
if self.0.is_some() { "filled" } else { "empty" }
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotColor {
Imm1,
Imm2,
Ref2,
}
#[derive(Debug, Clone)]
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>),
Str(Vec<u8>),
Bytes(Vec<u8>),
Value(Vec<Value>),
Slots(Vec<u64>),
Kernels(crate::library::tile_render::BodyKernels),
State(NodeState),
}
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),
ScratchBuf::Str(v) | ScratchBuf::Bytes(v) => {
(v.as_ptr() as usize as u64, v.len() as u64)
}
ScratchBuf::Value(v) => (v.as_ptr() as usize as u64, v.len() as u64),
ScratchBuf::Slots(v) => (v.as_ptr() as usize as u64, v.len() as u64),
ScratchBuf::Kernels(_) | ScratchBuf::State(_) => (0, 0),
}
}
pub fn to_value(&self) -> Value {
match self {
ScratchBuf::F32(v) => Value::VecF32(SliceArc::from_vec(v.clone())),
ScratchBuf::F64(v) => Value::VecF64(SliceArc::from_vec(v.clone())),
ScratchBuf::F16(v) => Value::VecF16(SliceArc::from_vec(v.clone())),
ScratchBuf::I8(v) => Value::VecI8(SliceArc::from_vec(v.clone())),
ScratchBuf::I16(v) => Value::VecI16(SliceArc::from_vec(v.clone())),
ScratchBuf::I32(v) => Value::VecI32(SliceArc::from_vec(v.clone())),
ScratchBuf::I64(v) => Value::VecI64(SliceArc::from_vec(v.clone())),
ScratchBuf::Str(v) => {
Value::Str(Arc::from(unsafe { std::str::from_utf8_unchecked(v) }))
}
ScratchBuf::Bytes(v) => Value::Bytes(Arc::from(&v[..])),
ScratchBuf::Value(v) => v.first().cloned().unwrap_or(Value::None),
ScratchBuf::Slots(_) => panic!("a slot buffer is not a value"),
ScratchBuf::Kernels(_) => panic!("a body kernel set is not a value"),
ScratchBuf::State(_) => panic!("a node's own state is not a value"),
}
}
pub fn node_state(&mut self) -> &mut NodeState {
match self {
ScratchBuf::State(s) => s,
other => panic!("scratch entry holds {other:?}, not a node's state"),
}
}
#[inline]
pub fn set_str(&mut self, s: &str) {
match self {
ScratchBuf::Str(v) => {
v.clear();
v.extend_from_slice(s.as_bytes());
}
other => panic!("scratch entry holds {other:?}, not a string"),
}
}
#[inline]
pub fn set_bytes(&mut self, b: &[u8]) {
match self {
ScratchBuf::Bytes(v) => {
v.clear();
v.extend_from_slice(b);
}
other => panic!("scratch entry holds {other:?}, not a byte string"),
}
}
#[inline]
pub fn set_value(&mut self, value: Value) {
match self {
ScratchBuf::Value(v) => {
v.clear();
v.push(value);
}
other => panic!("scratch entry holds {other:?}, not a value"),
}
}
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()),
ScratchElem::Str => ScratchBuf::Str(Vec::new()),
ScratchElem::Bytes => ScratchBuf::Bytes(Vec::new()),
ScratchElem::Value => ScratchBuf::Value(Vec::new()),
ScratchElem::Slots => ScratchBuf::Slots(Vec::new()),
ScratchElem::Kernels => ScratchBuf::Kernels(Default::default()),
ScratchElem::State => ScratchBuf::State(NodeState::default()),
}
}
}
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,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SimdVariant {
pub vector_node: &'static str,
pub exact: bool,
pub total: bool,
pub lane_independent: bool,
}
impl SimdVariant {
pub const fn exact_total(vector_node: &'static str) -> Self {
Self {
vector_node,
exact: true,
total: true,
lane_independent: true,
}
}
pub const fn exact_fallible(vector_node: &'static str) -> Self {
Self {
vector_node,
exact: true,
total: false,
lane_independent: true,
}
}
}
pub trait PolydatNode: Send + Sync {
fn meta(&self) -> &NodeMeta;
fn eval(&self, inputs: &[Value], outputs: &mut [Value]);
fn scratch_layout(&self) -> Vec<ScratchElem> {
Vec::new()
}
fn eval_in(&self, scratch: &mut [ScratchBuf], inputs: &[Value], outputs: &mut [Value]) {
let _ = scratch;
self.eval(inputs, outputs)
}
fn commutativity(&self) -> Commutativity {
Commutativity::Positional
}
fn accepts_none_inputs(&self) -> bool {
false
}
fn compiled_u64(&self) -> Option<CompiledU64Op> {
None
}
fn compiled_slot(&self, _wire_types: &[PortType]) -> Option<CompiledSlotKit> {
None
}
fn jit_constants(&self) -> Vec<u64> {
Vec::new()
}
fn purity(&self) -> Purity {
Purity::Pure
}
fn simd_variant(&self) -> Option<SimdVariant> {
None
}
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"
);
}
}
#[derive(Clone, Copy, Debug)]
pub enum ValueRef<'a> {
U64(u64),
I64(i64),
F64(f64),
Bool(bool),
Str(&'a str),
Bytes(&'a [u8]),
Json(&'a serde_json::Value),
None,
Other(&'a Value),
}
impl<'a> From<&'a Value> for ValueRef<'a> {
fn from(v: &'a Value) -> Self {
match v {
Value::U64(x) => ValueRef::U64(*x),
Value::I64(x) => ValueRef::I64(*x),
Value::F64(x) => ValueRef::F64(*x),
Value::Bool(b) => ValueRef::Bool(*b),
Value::Str(s) => ValueRef::Str(s),
Value::Bytes(b) => ValueRef::Bytes(b),
Value::Json(j) => ValueRef::Json(j),
Value::None => ValueRef::None,
other => ValueRef::Other(other),
}
}
}
impl<'a> ValueRef<'a> {
pub fn port_type(&self) -> PortType {
match self {
ValueRef::U64(_) => PortType::U64,
ValueRef::I64(_) => PortType::I64,
ValueRef::F64(_) => PortType::F64,
ValueRef::Bool(_) => PortType::Bool,
ValueRef::Str(_) => PortType::Str,
ValueRef::Bytes(_) => PortType::Bytes,
ValueRef::Json(_) => PortType::Json,
ValueRef::None => Value::None.port_type(),
ValueRef::Other(v) => v.port_type(),
}
}
pub fn display(&self) -> std::borrow::Cow<'a, str> {
use std::borrow::Cow;
match self {
ValueRef::Str(s) => Cow::Borrowed(s),
ValueRef::U64(v) => Cow::Owned(v.to_string()),
ValueRef::I64(v) => Cow::Owned(v.to_string()),
ValueRef::F64(v) => Cow::Owned(format!("{v:?}")),
ValueRef::Bool(v) => Cow::Owned(v.to_string()),
ValueRef::Bytes(b) => Cow::Owned(b.iter().map(|b| format!("{b:02x}")).collect()),
ValueRef::Json(j) => Cow::Owned(j.to_string()),
ValueRef::None => Cow::Owned(Value::None.to_display_string()),
ValueRef::Other(v) => Cow::Owned(v.to_display_string()),
}
}
pub fn to_display_string(&self) -> String {
self.display().into_owned()
}
pub fn to_json_value(&self) -> serde_json::Value {
match self {
ValueRef::U64(v) => serde_json::Value::from(*v),
ValueRef::I64(v) => serde_json::Value::from(*v),
ValueRef::F64(v) => serde_json::json!(*v),
ValueRef::Bool(v) => serde_json::Value::from(*v),
ValueRef::Str(s) => serde_json::Value::from(*s),
ValueRef::Bytes(b) => {
serde_json::Value::from(b.iter().map(|b| format!("{b:02x}")).collect::<String>())
}
ValueRef::Json(j) => (*j).clone(),
ValueRef::None => Value::None.to_json_value(),
ValueRef::Other(v) => v.to_json_value(),
}
}
}
#[cfg(test)]
mod satisfies_slot_tests {
use super::*;
#[test]
fn float_slots_accept_the_bit_stuffed_and_materialised_forms() {
let f32_bits = Value::U64(1.5f32.to_bits() as u64);
let f16_bits = Value::U64(half::f16::from_f32(1.5).to_bits() as u64);
assert!(f32_bits.satisfies_slot(PortType::F32));
assert!(f16_bits.satisfies_slot(PortType::F16));
assert!(Value::F64(1.5).satisfies_slot(PortType::F32));
assert!(Value::F64(1.5).satisfies_slot(PortType::F16));
assert!(!Value::F64(1.5).satisfies_slot(PortType::U64));
assert!(!Value::Str("1.5".into()).satisfies_slot(PortType::F32));
}
}