1use std::fmt;
13
14use crate::ast::{Lifecycle, PolydatNode, PortType, Purity};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub enum SimdLaneKind {
19 UnsignedInteger,
21 SignedInteger,
23 Float,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub struct SimdTypeShape {
30 pub scalar: PortType,
32 pub register: PortType,
34 pub lane_kind: SimdLaneKind,
36 pub lane_bits: u8,
38 pub lanes: u8,
40}
41
42impl SimdTypeShape {
43 pub const fn packet_bits(self) -> u16 {
45 self.lane_bits as u16 * self.lanes as u16
46 }
47}
48
49pub const fn promotable_type_shape(scalar: PortType) -> Option<SimdTypeShape> {
58 use PortType::*;
59 use SimdLaneKind::*;
60
61 let shape = match scalar {
62 U8 => SimdTypeShape {
63 scalar,
64 register: RegI8x16,
65 lane_kind: UnsignedInteger,
66 lane_bits: 8,
67 lanes: 16,
68 },
69 I8 => SimdTypeShape {
70 scalar,
71 register: RegI8x16,
72 lane_kind: SignedInteger,
73 lane_bits: 8,
74 lanes: 16,
75 },
76 U16 => SimdTypeShape {
77 scalar,
78 register: RegI16x8,
79 lane_kind: UnsignedInteger,
80 lane_bits: 16,
81 lanes: 8,
82 },
83 I16 => SimdTypeShape {
84 scalar,
85 register: RegI16x8,
86 lane_kind: SignedInteger,
87 lane_bits: 16,
88 lanes: 8,
89 },
90 U32 => SimdTypeShape {
91 scalar,
92 register: RegI32x4,
93 lane_kind: UnsignedInteger,
94 lane_bits: 32,
95 lanes: 4,
96 },
97 I32 => SimdTypeShape {
98 scalar,
99 register: RegI32x4,
100 lane_kind: SignedInteger,
101 lane_bits: 32,
102 lanes: 4,
103 },
104 U64 => SimdTypeShape {
105 scalar,
106 register: RegI64x2,
107 lane_kind: UnsignedInteger,
108 lane_bits: 64,
109 lanes: 2,
110 },
111 I64 => SimdTypeShape {
112 scalar,
113 register: RegI64x2,
114 lane_kind: SignedInteger,
115 lane_bits: 64,
116 lanes: 2,
117 },
118 F32 => SimdTypeShape {
119 scalar,
120 register: RegF32x4,
121 lane_kind: Float,
122 lane_bits: 32,
123 lanes: 4,
124 },
125 F64 => SimdTypeShape {
126 scalar,
127 register: RegF64x2,
128 lane_kind: Float,
129 lane_bits: 64,
130 lanes: 2,
131 },
132 F16 | U128 | I128 | Bool | Str | Bytes | Json | Ext | Handle | VecF32 | VecF64 | VecF16
136 | VecI8 | VecI16 | VecI32 | VecI64 | Reg128 | RegI8x16 | RegI16x8 | RegI32x4 | RegI64x2
137 | RegF16x8 | RegF32x4 | RegF64x2 | Dyn => return None,
138 };
139 Some(shape)
140}
141
142#[derive(Clone, Debug, PartialEq, Eq)]
145pub struct ValidatedSimdVariant {
146 pub scalar_node: String,
148 pub vector_node: &'static str,
150 pub shape: SimdTypeShape,
152 pub wire_inputs: u8,
154}
155
156#[derive(Clone, Debug, PartialEq, Eq)]
157pub enum SimdVariantError {
159 Undeclared,
161 ScalarNodeNotPure,
163 VariantNotExact,
165 VariantNotTotal,
167 VariantNotLaneIndependent,
169 UnsupportedScalarShape(PortType),
171 ScalarSignatureNotUniform,
173 ScalarLifecycleNotCycle,
175 VectorNodeUnavailable(String),
177 VectorNodeNotPure,
179 VectorSignatureMismatch,
181 VectorLifecycleNotCycle,
183}
184
185impl fmt::Display for SimdVariantError {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 match self {
188 Self::Undeclared => f.write_str("scalar node declares no SIMD variant"),
189 Self::ScalarNodeNotPure => f.write_str("scalar node is not pure"),
190 Self::VariantNotExact => f.write_str("SIMD variant is not exact"),
191 Self::VariantNotTotal => f.write_str("SIMD variant is not total"),
192 Self::VariantNotLaneIndependent => f.write_str("SIMD variant is not lane-independent"),
193 Self::UnsupportedScalarShape(t) => write!(f, "unsupported scalar SIMD shape {t}"),
194 Self::ScalarSignatureNotUniform => {
195 f.write_str("scalar node inputs and output do not share one scalar lane type")
196 }
197 Self::ScalarLifecycleNotCycle => {
198 f.write_str("scalar node has an init-lifecycle data port")
199 }
200 Self::VectorNodeUnavailable(e) => write!(f, "register variant unavailable: {e}"),
201 Self::VectorNodeNotPure => f.write_str("register variant is not pure"),
202 Self::VectorSignatureMismatch => {
203 f.write_str("register variant signature does not match the scalar lane shape")
204 }
205 Self::VectorLifecycleNotCycle => {
206 f.write_str("register variant has an init-lifecycle data port")
207 }
208 }
209 }
210}
211
212impl std::error::Error for SimdVariantError {}
213
214pub fn validate_simd_variant(
221 scalar: &dyn PolydatNode,
222) -> Result<ValidatedSimdVariant, SimdVariantError> {
223 let variant = scalar.simd_variant().ok_or(SimdVariantError::Undeclared)?;
224 if scalar.purity() != Purity::Pure {
225 return Err(SimdVariantError::ScalarNodeNotPure);
226 }
227 if !variant.exact {
228 return Err(SimdVariantError::VariantNotExact);
229 }
230 if !variant.total {
231 return Err(SimdVariantError::VariantNotTotal);
232 }
233 if !variant.lane_independent {
234 return Err(SimdVariantError::VariantNotLaneIndependent);
235 }
236
237 let scalar_meta = scalar.meta();
238 let [scalar_output] = scalar_meta.outs.as_slice() else {
239 return Err(SimdVariantError::ScalarSignatureNotUniform);
240 };
241 let shape = promotable_type_shape(scalar_output.typ)
242 .ok_or(SimdVariantError::UnsupportedScalarShape(scalar_output.typ))?;
243 let scalar_inputs = scalar_meta.wire_inputs();
244 if scalar_inputs.is_empty() || scalar_inputs.iter().any(|port| port.typ != shape.scalar) {
245 return Err(SimdVariantError::ScalarSignatureNotUniform);
246 }
247 if scalar_output.lifecycle != Lifecycle::Cycle
248 || scalar_inputs
249 .iter()
250 .any(|port| port.lifecycle != Lifecycle::Cycle)
251 {
252 return Err(SimdVariantError::ScalarLifecycleNotCycle);
253 }
254
255 let wires = vec![crate::compile::assembly::WireRef::input("__simd_probe"); scalar_inputs.len()];
256 let wire_types = vec![shape.register; scalar_inputs.len()];
257 let vector = crate::dsl::factory::build_node(
260 &crate::dsl::factory::BuildContext::default(),
261 variant.vector_node,
262 &wires,
263 &wire_types,
264 &[],
265 )
266 .map_err(|e| SimdVariantError::VectorNodeUnavailable(e.to_string()))?;
267 if vector.purity() != Purity::Pure {
268 return Err(SimdVariantError::VectorNodeNotPure);
269 }
270 let vector_meta = vector.meta();
271 let [vector_output] = vector_meta.outs.as_slice() else {
272 return Err(SimdVariantError::VectorSignatureMismatch);
273 };
274 let vector_inputs = vector_meta.wire_inputs();
275 if vector_output.typ != shape.register
276 || vector_inputs.len() != scalar_inputs.len()
277 || vector_inputs.iter().any(|port| port.typ != shape.register)
278 {
279 return Err(SimdVariantError::VectorSignatureMismatch);
280 }
281 if vector_output.lifecycle != Lifecycle::Cycle
282 || vector_inputs
283 .iter()
284 .any(|port| port.lifecycle != Lifecycle::Cycle)
285 {
286 return Err(SimdVariantError::VectorLifecycleNotCycle);
287 }
288
289 Ok(ValidatedSimdVariant {
290 scalar_node: scalar_meta.name.clone(),
291 vector_node: variant.vector_node,
292 shape,
293 wire_inputs: scalar_inputs.len() as u8,
294 })
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn common_numeric_shapes_cover_one_128_bit_word() {
303 for ty in [
304 PortType::U8,
305 PortType::I8,
306 PortType::U16,
307 PortType::I16,
308 PortType::U32,
309 PortType::I32,
310 PortType::U64,
311 PortType::I64,
312 PortType::F32,
313 PortType::F64,
314 ] {
315 assert_eq!(promotable_type_shape(ty).unwrap().packet_bits(), 128);
316 }
317 assert!(promotable_type_shape(PortType::F16).is_none());
318 assert!(promotable_type_shape(PortType::U128).is_none());
319 assert!(promotable_type_shape(PortType::Str).is_none());
320 }
321}