1use polydat::ast::Bits128;
34#[cfg(test)]
35use polydat::ast::{PolydatNode, PortType, RegLanes, Value};
36pub use polydat::library::register_view::{RegView, is_reg_port};
37pub use polydat::numeric::register::{
38 gather_f32, lane_f32, lane_i16, lane_i64, mul_i8, to_reg_f32, with_lane_f32,
39};
40#[polydat::polydat_node(category = Arithmetic)]
47fn reg_splat_f32(k: f64) -> [f32; 4] {
48 [k as f32; 4]
49}
50
51#[polydat::polydat_node(category = Arithmetic)]
53fn reg_splat_f64(k: f64) -> [f64; 2] {
54 [k; 2]
55}
56
57#[polydat::polydat_node(category = Arithmetic)]
59fn reg_splat_i8(k: u64) -> [i8; 16] {
60 [k as i8; 16]
61}
62
63#[polydat::polydat_node(category = Arithmetic)]
65fn reg_splat_i16(k: u64) -> [i16; 8] {
66 [k as i16; 8]
67}
68
69#[polydat::polydat_node(category = Arithmetic)]
71fn reg_splat_i32(k: u64) -> [i32; 4] {
72 [k as i32; 4]
73}
74
75#[polydat::polydat_node(category = Arithmetic)]
77fn reg_splat_i64(k: u64) -> [i64; 2] {
78 [k as i64; 2]
79}
80
81#[polydat::polydat_node(category = Arithmetic)]
90fn reg_gather_f32(v: &[f32], offset: u64) -> [f32; 4] {
91 gather_f32(v, offset).lanes_f32()
92}
93
94#[polydat::polydat_node(category = Conversions)]
97fn vec_to_reg_f32(v: &[f32]) -> [f32; 4] {
98 to_reg_f32(v).lanes_f32()
99}
100
101#[polydat::polydat_node(category = Conversions)]
105fn reg_to_vec_f32(r: [f32; 4]) -> Vec<f32> {
106 r.to_vec()
107}
108
109#[polydat::polydat_node(category = Arithmetic)]
115fn reg_lane_f32(r: [f32; 4], i: u64) -> f64 {
116 lane_f32(Bits128::from_lanes_f32(r), i)
117}
118
119#[polydat::polydat_node(category = Arithmetic)]
122fn reg_with_lane_f32(r: [f32; 4], i: u64, v: f64) -> [f32; 4] {
123 with_lane_f32(Bits128::from_lanes_f32(r), i, v).lanes_f32()
124}
125
126#[polydat::polydat_node(category = Arithmetic)]
128fn reg_lane_i16(r: [i16; 8], i: u64) -> i16 {
129 lane_i16(Bits128::from_lanes_i16(r), i)
130}
131
132#[polydat::polydat_node(category = Arithmetic)]
134fn reg_lane_i64(r: [i64; 2], i: u64) -> i64 {
135 lane_i64(Bits128::from_lanes_i64(r), i)
136}
137
138#[polydat::polydat_node(category = Arithmetic)]
143fn reg_add_f32(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
144 core::array::from_fn(|i| a[i] + b[i])
145}
146
147#[polydat::polydat_node(category = Arithmetic)]
148fn reg_sub_f32(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
149 core::array::from_fn(|i| a[i] - b[i])
150}
151
152#[polydat::polydat_node(category = Arithmetic)]
153fn reg_mul_f32(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
154 core::array::from_fn(|i| a[i] * b[i])
155}
156
157#[polydat::polydat_node(category = Arithmetic)]
158fn reg_add_f64(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
159 core::array::from_fn(|i| a[i] + b[i])
160}
161
162#[polydat::polydat_node(category = Arithmetic)]
163fn reg_sub_f64(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
164 core::array::from_fn(|i| a[i] - b[i])
165}
166
167#[polydat::polydat_node(category = Arithmetic)]
168fn reg_mul_f64(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
169 core::array::from_fn(|i| a[i] * b[i])
170}
171
172#[polydat::polydat_node(category = Arithmetic)]
173fn reg_add_i8(a: [i8; 16], b: [i8; 16]) -> [i8; 16] {
174 core::array::from_fn(|i| a[i].wrapping_add(b[i]))
175}
176
177#[polydat::polydat_node(category = Arithmetic)]
178fn reg_sub_i8(a: [i8; 16], b: [i8; 16]) -> [i8; 16] {
179 core::array::from_fn(|i| a[i].wrapping_sub(b[i]))
180}
181
182#[polydat::polydat_node(category = Arithmetic)]
186fn reg_mul_i8(a: [i8; 16], b: [i8; 16]) -> [i8; 16] {
187 mul_i8(Bits128::from_lanes_i8(a), Bits128::from_lanes_i8(b)).lanes_i8()
188}
189
190#[polydat::polydat_node(category = Arithmetic)]
191fn reg_add_i16(a: [i16; 8], b: [i16; 8]) -> [i16; 8] {
192 core::array::from_fn(|i| a[i].wrapping_add(b[i]))
193}
194
195#[polydat::polydat_node(category = Arithmetic)]
196fn reg_sub_i16(a: [i16; 8], b: [i16; 8]) -> [i16; 8] {
197 core::array::from_fn(|i| a[i].wrapping_sub(b[i]))
198}
199
200#[polydat::polydat_node(category = Arithmetic)]
201fn reg_mul_i16(a: [i16; 8], b: [i16; 8]) -> [i16; 8] {
202 core::array::from_fn(|i| a[i].wrapping_mul(b[i]))
203}
204
205#[polydat::polydat_node(category = Arithmetic)]
206fn reg_add_i32(a: [i32; 4], b: [i32; 4]) -> [i32; 4] {
207 core::array::from_fn(|i| a[i].wrapping_add(b[i]))
208}
209
210#[polydat::polydat_node(category = Arithmetic)]
211fn reg_sub_i32(a: [i32; 4], b: [i32; 4]) -> [i32; 4] {
212 core::array::from_fn(|i| a[i].wrapping_sub(b[i]))
213}
214
215#[polydat::polydat_node(category = Arithmetic)]
216fn reg_mul_i32(a: [i32; 4], b: [i32; 4]) -> [i32; 4] {
217 core::array::from_fn(|i| a[i].wrapping_mul(b[i]))
218}
219
220#[polydat::polydat_node(category = Arithmetic)]
221fn reg_add_i64(a: [i64; 2], b: [i64; 2]) -> [i64; 2] {
222 core::array::from_fn(|i| a[i].wrapping_add(b[i]))
223}
224
225#[polydat::polydat_node(category = Arithmetic)]
226fn reg_sub_i64(a: [i64; 2], b: [i64; 2]) -> [i64; 2] {
227 core::array::from_fn(|i| a[i].wrapping_sub(b[i]))
228}
229
230#[polydat::polydat_node(category = Arithmetic)]
231fn reg_mul_i64(a: [i64; 2], b: [i64; 2]) -> [i64; 2] {
232 core::array::from_fn(|i| a[i].wrapping_mul(b[i]))
233}
234
235#[polydat::polydat_node(category = Arithmetic)]
244fn reg_dot_f32(a: [f32; 4], b: [f32; 4]) -> f64 {
245 let p0 = a[0] * b[0];
246 let p1 = a[1] * b[1];
247 let p2 = a[2] * b[2];
248 let p3 = a[3] * b[3];
249 ((p0 + p1) + (p2 + p3)) as f64
250}
251
252#[polydat::polydat_node(category = Arithmetic, jit_constants = reg_shuffle_bytes_jit_constants)]
260fn reg_shuffle_bytes(x: Bits128, mask: polydat::derive_support::Const<Vec<u64>>) -> Bits128 {
261 let m = &*mask;
262 if m.len() != 16 {
263 panic!(
264 "reg_shuffle_bytes: mask must have exactly 16 entries, got {}",
265 m.len()
266 );
267 }
268 let src = x.to_le_bytes();
269 let mut out = [0u8; 16];
270 for (i, &idx) in m.iter().enumerate() {
271 if idx >= 16 {
272 panic!("reg_shuffle_bytes: mask[{i}] = {idx} out of range 0..16");
273 }
274 out[i] = src[idx as usize];
275 }
276 Bits128::from_le_bytes(out)
277}
278
279fn reg_shuffle_bytes_jit_constants(node: &RegShuffleBytes) -> Vec<u64> {
280 node.mask.clone()
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 fn eval1<N: PolydatNode>(node: &N, a: Value) -> Value {
288 let mut out = [Value::None];
289 node.eval(&[a], &mut out);
290 out[0].clone()
291 }
292
293 fn eval2<N: PolydatNode>(node: &N, a: Value, b: Value) -> Value {
294 let mut out = [Value::None];
295 node.eval(&[a, b], &mut out);
296 out[0].clone()
297 }
298
299 fn f32x4(l: [f32; 4]) -> Value {
300 Value::Reg128(Bits128::from_lanes_f32(l), RegLanes::F32x4)
301 }
302
303 #[test]
304 fn lane_codecs_round_trip_and_share_bits() {
305 let b = Bits128::from_lanes_f32([1.0, -2.5, 0.0, 4.0]);
306 assert_eq!(b.lanes_f32(), [1.0, -2.5, 0.0, 4.0]);
307 let as_i16 = b.lanes_i16();
310 assert_eq!(Bits128::from_lanes_i16(as_i16), b);
311 assert_eq!(Bits128::from_lanes_i8(b.lanes_i8()), b);
312 assert_eq!(Bits128::from_lanes_i64(b.lanes_i64()), b);
313 assert_eq!(Bits128::from_lanes_f64(b.lanes_f64()), b);
314 assert_eq!(Bits128::from_lanes_f16(b.lanes_f16()), b);
315 }
316
317 #[test]
318 fn reg_view_retags_without_touching_bits() {
319 let word = f32x4([1.0, 2.0, 3.0, 4.0]);
320 let raw = eval1(&RegView::new(PortType::Reg128), word.clone());
321 assert_eq!(raw.as_reg_bits(), word.as_reg_bits());
322 assert!(matches!(raw, Value::Reg128(_, RegLanes::Raw)));
323 let back = eval1(&RegView::new(PortType::RegI16x8), raw);
324 assert!(matches!(back, Value::Reg128(_, RegLanes::I16x8)));
325 assert_eq!(back.as_reg_bits(), word.as_reg_bits());
326 }
327
328 #[test]
329 fn splats_and_lane_access() {
330 let r = eval1(&RegSplatF32::new(), Value::F64(2.5));
331 assert_eq!(r.as_reg_bits().lanes_f32(), [2.5; 4]);
332
333 let r = eval1(&RegSplatI16::new(), Value::U64(0xFFFF));
334 assert_eq!(r.as_reg_bits().lanes_i16(), [-1; 8]);
336
337 let lane = eval2(
338 &RegLaneF32::new(),
339 f32x4([1.0, 2.0, 3.0, 4.0]),
340 Value::U64(2),
341 );
342 assert_eq!(lane, Value::F64(3.0));
343
344 let mut out = [Value::None];
345 RegWithLaneF32::new().eval(
346 &[f32x4([1.0, 2.0, 3.0, 4.0]), Value::U64(1), Value::F64(9.0)],
347 &mut out,
348 );
349 assert_eq!(out[0].as_reg_bits().lanes_f32(), [1.0, 9.0, 3.0, 4.0]);
350 }
351
352 #[test]
353 fn elementwise_arithmetic_and_wrapping() {
354 let sum = eval2(
355 &RegAddF32::new(),
356 f32x4([1.0, 2.0, 3.0, 4.0]),
357 f32x4([10.0, 20.0, 30.0, 40.0]),
358 );
359 assert_eq!(sum.as_reg_bits().lanes_f32(), [11.0, 22.0, 33.0, 44.0]);
360
361 let a = Value::Reg128(Bits128::from_lanes_i16([i16::MAX; 8]), RegLanes::I16x8);
363 let b = Value::Reg128(Bits128::from_lanes_i16([1; 8]), RegLanes::I16x8);
364 let wrapped = eval2(&RegAddI16::new(), a, b);
365 assert_eq!(wrapped.as_reg_bits().lanes_i16(), [i16::MIN; 8]);
366 }
367
368 #[test]
369 fn dot_uses_fixed_reduction_tree() {
370 let d = eval2(
371 &RegDotF32::new(),
372 f32x4([1.0, 2.0, 3.0, 4.0]),
373 f32x4([5.0, 6.0, 7.0, 8.0]),
374 );
375 assert_eq!(d, Value::F64(70.0));
377 }
378
379 #[test]
380 fn gather_and_vec_round_trip() {
381 use polydat::ast::SliceArc;
382 let v = Value::VecF32(SliceArc::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]));
383 let r = eval2(&RegGatherF32::new(), v, Value::U64(2));
384 assert_eq!(r.as_reg_bits().lanes_f32(), [2.0, 3.0, 4.0, 5.0]);
385
386 let back = eval1(&RegToVecF32::new(), r);
387 assert_eq!(back.as_vec_f32(), &[2.0, 3.0, 4.0, 5.0]);
388 }
389
390 #[test]
391 fn raw_state_word_byte_shuffle() {
392 let word = Bits128::from_le_bytes(core::array::from_fn(|i| i as u8));
395 let node = RegShuffleBytes::new((0..16).rev().collect());
396 let mut out = [Value::None];
397 node.eval(&[Value::Reg128(word, RegLanes::Raw)], &mut out);
398 let shuffled = out[0].as_reg_bits().to_le_bytes();
399 assert_eq!(shuffled, core::array::from_fn(|i| 15 - i as u8));
400 }
401
402 #[test]
403 fn reg_flow_p1_p2_equivalence() {
404 let src = r#"
409 input cycle: u64
410 a := reg_splat_i16(cycle)
411 b := reg_splat_i16(3)
412 s := reg_add_i16(a, b)
413 out := reg_lane_i16(s, 7)
414 "#;
415 let p1 = polydat::dsl::compile_polydat(src).unwrap();
416 let asm = polydat::dsl::compile::compile_polydat_to_assembler(src).unwrap();
417 let mut p2 = asm.try_compile_raw().expect("reg nodes are P2-eligible");
418
419 let mut k1 = p1;
420 for cycle in [0u64, 5, 0xFFFF, 0x1_0005] {
421 k1.set_inputs(&[cycle]);
422 let want = k1.pull("out").as_i64();
423 let got = p2.eval_for_slot(&[cycle], p2.resolve_output("out").unwrap());
424 assert_eq!(got as i64, want, "cycle={cycle}");
425 }
426 }
427
428 #[test]
429 fn u128_rides_p2_limb_pairs() {
430 use polydat::compile::assembly::{PolydatAssembler, WireRef};
436 let mut asm = PolydatAssembler::new(vec!["cycle".into()]);
437 asm.add_node(
438 "wide",
439 Box::new(polydat::library::polyfill_128::U64ToU128::new()),
440 vec![WireRef::input("cycle")],
441 );
442 asm.add_node(
443 "back",
444 Box::new(polydat::library::polyfill_128::U128ToU64::new()),
445 vec![WireRef::node("wide")],
446 );
447 asm.add_output("back", WireRef::node("back"));
448 let mut p2 = asm.try_compile_raw().expect("u128 nodes are P2-eligible");
449 for v in [0u64, 1, u64::MAX] {
450 let slot = p2.resolve_output("back").unwrap();
451 assert_eq!(p2.eval_for_slot(&[v], slot), v, "u128 round trip of {v}");
452 }
453 }
454
455 #[cfg(feature = "jit")]
463 #[test]
464 fn reg_ops_p1_p3_equivalence_all_lane_families() {
465 let cases = [
471 ("i8", "cycle", "hash(cycle)"),
472 ("i16", "cycle", "hash(cycle)"),
473 ("i32", "cycle", "hash(cycle)"),
474 ("i64", "cycle", "hash(cycle)"),
475 ("f32", "unit_interval(cycle)", "unit_interval(hash(cycle))"),
476 ("f64", "unit_interval(cycle)", "unit_interval(hash(cycle))"),
477 ];
478 for (fam, ea, eb) in cases {
479 for op in ["add", "sub", "mul"] {
480 let src = format!(
481 "input cycle: u64
482 a := reg_splat_{fam}({ea})
483 b := reg_splat_{fam}({eb})
484 out := reg_{op}_{fam}(a, b)"
485 );
486 let mut p1 = polydat::dsl::compile_polydat(&src).unwrap();
487
488 for cycle in [0u64, 5, 0xFFFF, 0xDEAD_BEEF] {
489 p1.set_inputs(&[cycle]);
490 let want = p1.pull("out").as_reg_bits();
491
492 let asm = polydat::dsl::compile::compile_polydat_to_assembler(&src).unwrap();
493 match asm.try_compile_pure_jit_raw() {
494 Ok(mut p3) => {
495 let slot = p3.resolve_output("out").unwrap();
496 p3.eval(&[cycle]);
497 let got = Bits128([p3.get_slot(slot), p3.get_slot(slot + 1)]);
498 assert_eq!(got, want, "P3 reg_{op}_{fam} mismatch at cycle={cycle}");
499 }
500 Err(e) => {
501 eprintln!("reg_{op}_{fam}: pure-P3 declined ({e}); checking hybrid");
504 let asm =
505 polydat::dsl::compile::compile_polydat_to_assembler(&src).unwrap();
506 let mut hy = asm.compile_hybrid().unwrap();
507 let slot = hy.resolve_output("out").unwrap();
508 hy.eval(&[cycle]);
509 let got = Bits128([hy.get_slot(slot), hy.get_slot(slot + 1)]);
510 assert_eq!(
511 got, want,
512 "hybrid reg_{op}_{fam} mismatch at cycle={cycle}"
513 );
514 }
515 }
516 }
517 }
518 }
519 }
520
521 #[test]
522 fn display_and_json_forms() {
523 let word = f32x4([1.0, 2.0, 3.0, 4.0]);
524 assert_eq!(word.to_display_string(), "[1.0, 2.0, 3.0, 4.0]");
525 assert_eq!(
526 word.to_json_value(),
527 serde_json::json!([1.0, 2.0, 3.0, 4.0])
528 );
529 let raw = Value::Reg128(Bits128::from_u128(0xDEAD), RegLanes::Raw);
530 assert_eq!(raw.to_display_string(), format!("{:032x}", 0xDEADu128));
531 }
532}