1use std::sync::Arc;
49
50use vyre_foundation::ir::model::expr::Ident;
51use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
52
53pub const OP_ID: &str = "vyre-primitives::decode::rle_segment_lengths";
55
56pub const BINDING_SEGMENTS_IN: u32 = 0;
58pub const BINDING_SEGMENT_LENGTHS_OUT: u32 = 1;
60pub const BINDING_SEGMENT_VALUES_OUT: u32 = 2;
62
63pub const MAX_SEGMENT_LENGTH: u32 = (1 << 24) - 1;
66
67pub const MAX_SEGMENT_VALUE: u32 = 0xFF;
69
70pub const RLE_SEGMENT_LENGTHS_WORKGROUP_SIZE: [u32; 3] = [256, 1, 1];
72
73#[must_use]
75pub const fn rle_segment_lengths_dispatch_grid(segment_count: u32) -> [u32; 3] {
76 let lanes_per_block = RLE_SEGMENT_LENGTHS_WORKGROUP_SIZE[0];
77 let full_blocks = segment_count / lanes_per_block;
78 let tail_block = if segment_count % lanes_per_block == 0 {
79 0
80 } else {
81 1
82 };
83 let blocks = full_blocks + tail_block;
84 [if blocks == 0 { 1 } else { blocks }, 1, 1]
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89#[non_exhaustive]
90pub enum PackError {
91 LengthTooLarge {
93 segment: usize,
95 length: u32,
97 },
98 ValueTooLarge {
100 segment: usize,
102 value: u32,
104 },
105 AllocationFailed {
107 message: String,
109 },
110}
111
112#[must_use]
122pub fn rle_segment_lengths(segment_count: u32) -> Program {
123 if segment_count == 0 {
124 return crate::invalid_output_program(
125 OP_ID,
126 "segment_lengths_out",
127 DataType::U32,
128 "Fix: rle_segment_lengths requires segment_count > 0, got 0.".to_string(),
129 );
130 }
131
132 let body = vec![
133 Node::let_bind("seg_idx", Expr::InvocationId { axis: 0 }),
134 Node::if_then(
135 Expr::lt(Expr::var("seg_idx"), Expr::u32(segment_count)),
136 vec![
137 Node::let_bind("packed", Expr::load("segments_in", Expr::var("seg_idx"))),
138 Node::let_bind("length", Expr::shr(Expr::var("packed"), Expr::u32(8))),
139 Node::let_bind("value", Expr::bitand(Expr::var("packed"), Expr::u32(0xFF))),
140 Node::store(
141 "segment_lengths_out",
142 Expr::var("seg_idx"),
143 Expr::var("length"),
144 ),
145 Node::store(
146 "segment_values_out",
147 Expr::var("seg_idx"),
148 Expr::var("value"),
149 ),
150 ],
151 ),
152 ];
153
154 let buffers = vec![
155 BufferDecl::storage(
156 "segments_in",
157 BINDING_SEGMENTS_IN,
158 BufferAccess::ReadOnly,
159 DataType::U32,
160 )
161 .with_count(segment_count),
162 BufferDecl::storage(
163 "segment_lengths_out",
164 BINDING_SEGMENT_LENGTHS_OUT,
165 BufferAccess::ReadWrite,
166 DataType::U32,
167 )
168 .with_count(segment_count),
169 BufferDecl::storage(
170 "segment_values_out",
171 BINDING_SEGMENT_VALUES_OUT,
172 BufferAccess::ReadWrite,
173 DataType::U32,
174 )
175 .with_count(segment_count),
176 ];
177
178 let entry = vec![Node::Region {
179 generator: Ident::from(OP_ID),
180 source_region: None,
181 body: Arc::new(body),
182 }];
183 Program::wrapped(buffers, RLE_SEGMENT_LENGTHS_WORKGROUP_SIZE, entry)
184}
185
186pub fn pack_rle_segments(segments: &[(u32, u8)]) -> Result<Vec<u32>, PackError> {
194 let mut packed = Vec::with_capacity(segments.len());
195 try_pack_rle_segments_into(segments, &mut packed)?;
196 Ok(packed)
197}
198
199pub fn pack_rle_segments_into(segments: &[(u32, u8)], out: &mut Vec<u32>) -> Result<(), PackError> {
208 try_pack_rle_segments_into(segments, out)
209}
210
211pub fn try_pack_rle_segments_into(
216 segments: &[(u32, u8)],
217 out: &mut Vec<u32>,
218) -> Result<(), PackError> {
219 if segments.len() > out.capacity() {
220 reserve_items(out, segments.len(), "RLE segment packer", "packed segments")
221 .map_err(|message| PackError::AllocationFailed { message })?;
222 }
223 out.clear();
224 for (idx, (length, value)) in segments.iter().enumerate() {
225 if *length > MAX_SEGMENT_LENGTH {
226 return Err(PackError::LengthTooLarge {
227 segment: idx,
228 length: *length,
229 });
230 }
231 let value_u32 = u32::from(*value);
232 if value_u32 > MAX_SEGMENT_VALUE {
233 return Err(PackError::ValueTooLarge {
236 segment: idx,
237 value: value_u32,
238 });
239 }
240 out.push((length << 8) | value_u32);
241 }
242 Ok(())
243}
244
245#[must_use]
248#[cfg(any(test, feature = "cpu-parity"))]
249pub fn rle_segment_lengths_cpu(segments_in: &[u32]) -> (Vec<u32>, Vec<u32>) {
250 let mut lengths = Vec::new();
251 let mut values = Vec::new();
252 if let Err(error) = try_rle_segment_lengths_cpu_into(segments_in, &mut lengths, &mut values) {
253 panic!("vyre-primitives RLE segment-lengths reference failed: {error}");
256 }
257 (lengths, values)
258}
259
260#[cfg(any(test, feature = "cpu-parity"))]
264pub fn rle_segment_lengths_cpu_into(
265 segments_in: &[u32],
266 lengths: &mut Vec<u32>,
267 values: &mut Vec<u32>,
268) {
269 if let Err(error) = try_rle_segment_lengths_cpu_into(segments_in, lengths, values) {
270 panic!("vyre-primitives RLE segment-lengths reference failed: {error}");
271 }
272}
273
274#[cfg(any(test, feature = "cpu-parity"))]
276pub fn try_rle_segment_lengths_cpu_into(
277 segments_in: &[u32],
278 lengths: &mut Vec<u32>,
279 values: &mut Vec<u32>,
280) -> Result<(), String> {
281 reserve_u32_output(
282 lengths,
283 segments_in.len(),
284 "RLE segment lengths CPU oracle",
285 "lengths",
286 )?;
287 reserve_u32_output(
288 values,
289 segments_in.len(),
290 "RLE segment lengths CPU oracle",
291 "values",
292 )?;
293 lengths.clear();
294 values.clear();
295 for packed in segments_in {
296 lengths.push(packed >> 8);
297 values.push(packed & 0xFF);
298 }
299 Ok(())
300}
301
302#[must_use]
309#[cfg(any(test, feature = "cpu-parity"))]
310pub fn rle_segment_start_offsets_cpu(segment_lengths: &[u32]) -> (Vec<u32>, u32) {
311 let mut offsets = Vec::new();
312 let total = match try_rle_segment_start_offsets_cpu_into(segment_lengths, &mut offsets) {
313 Ok(total) => total,
314 Err(error) => panic!("vyre-primitives RLE start-offsets reference failed: {error}"),
317 };
318 (offsets, total)
319}
320
321#[cfg(any(test, feature = "cpu-parity"))]
326pub fn rle_segment_start_offsets_cpu_into(segment_lengths: &[u32], offsets: &mut Vec<u32>) -> u32 {
327 match try_rle_segment_start_offsets_cpu_into(segment_lengths, offsets) {
328 Ok(total) => total,
329 Err(error) => panic!("vyre-primitives RLE start-offsets reference failed: {error}"),
330 }
331}
332
333#[cfg(any(test, feature = "cpu-parity"))]
335pub fn try_rle_segment_start_offsets_cpu_into(
336 segment_lengths: &[u32],
337 offsets: &mut Vec<u32>,
338) -> Result<u32, String> {
339 reserve_u32_output(
340 offsets,
341 segment_lengths.len(),
342 "RLE segment offset CPU oracle",
343 "start offsets",
344 )?;
345 offsets.clear();
346 let mut acc: u32 = 0;
347 for length in segment_lengths {
348 offsets.push(acc);
349 acc = acc.saturating_add(*length);
350 }
351 Ok(acc)
352}
353
354#[must_use]
358#[cfg(any(test, feature = "cpu-parity"))]
359pub fn rle_decode_cpu(segments_in: &[u32]) -> Vec<u8> {
360 let mut output = Vec::new();
361 if let Err(error) = try_rle_decode_cpu_into(segments_in, &mut output) {
362 panic!("vyre-primitives RLE decode reference failed: {error}");
365 }
366 output
367}
368
369#[cfg(any(test, feature = "cpu-parity"))]
375pub fn rle_decode_cpu_into(segments_in: &[u32], output: &mut Vec<u8>) {
376 if let Err(error) = try_rle_decode_cpu_into(segments_in, output) {
377 panic!("vyre-primitives RLE decode reference failed: {error}");
378 }
379}
380
381#[cfg(any(test, feature = "cpu-parity"))]
383pub fn try_rle_decode_cpu_into(segments_in: &[u32], output: &mut Vec<u8>) -> Result<(), String> {
384 let total = decoded_len(segments_in);
385 if total > output.capacity() {
386 reserve_items(output, total, "RLE decode CPU oracle", "decoded output")?;
387 }
388 output.clear();
389 for packed in segments_in {
390 let length = (packed >> 8) as usize;
391 let value = (packed & 0xFF) as u8;
392 let new_len = output.len().saturating_add(length);
393 output.resize(new_len, value);
394 }
395 Ok(())
396}
397
398#[cfg(any(test, feature = "cpu-parity"))]
399fn decoded_len(segments_in: &[u32]) -> usize {
400 segments_in
401 .iter()
402 .map(|packed| packed >> 8)
403 .fold(0_u32, u32::saturating_add) as usize
404}
405
406#[cfg(any(test, feature = "cpu-parity"))]
407fn reserve_u32_output(
408 out: &mut Vec<u32>,
409 len: usize,
410 context: &str,
411 name: &str,
412) -> Result<(), String> {
413 if len > out.capacity() {
414 reserve_items(out, len, context, name)?;
415 }
416 Ok(())
417}
418
419fn reserve_items<T>(out: &mut Vec<T>, len: usize, context: &str, name: &str) -> Result<(), String> {
420 if len > out.capacity() {
421 out.try_reserve(len - out.len()).map_err(|err| {
422 format!(
423 "{context}: failed to reserve {len} items for {name}: {err}. Fix: shard the RLE stream before CPU parity evaluation."
424 )
425 })?;
426 }
427 Ok(())
428}
429
430#[cfg(feature = "inventory-registry")]
431fn fixture_u32(words: &[u32]) -> Vec<u8> {
432 crate::wire::pack_u32_slice(words)
433}
434
435#[cfg(feature = "inventory-registry")]
436inventory::submit! {
437 vyre_foundation::operation::OperationRegistration::primitive(
438 OP_ID,
439 || rle_segment_lengths(3),
440 Some(|| {
441 let packed = pack_rle_segments(&[(2, b'A'), (0, b'X'), (3, b'B')])
442 .unwrap_or_else(|_| unreachable!("fixture RLE segments fit the 24-bit length field"));
443 vec![vec![
444 fixture_u32(&packed),
445 fixture_u32(&[0, 0, 0]),
446 fixture_u32(&[0, 0, 0]),
447 ]]
448 }),
449 Some(|| vec![vec![
450 fixture_u32(&[2, 0, 3]),
451 fixture_u32(&[u32::from(b'A'), u32::from(b'X'), u32::from(b'B')]),
452 ]]),
453 )
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn pack_then_unpack_round_trips_simple_segments() {
462 let segments = [(1u32, 0xABu8), (5u32, 0xCDu8)];
463 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
464 let (lengths, values) = rle_segment_lengths_cpu(&packed);
465 assert_eq!(lengths, vec![1, 5]);
466 assert_eq!(values, vec![0xAB, 0xCD]);
467 }
468
469 #[test]
470 fn pack_rejects_length_at_field_boundary() {
471 let segments = [(1u32 << 24, 0u8)]; match pack_rle_segments(&segments) {
473 Err(PackError::LengthTooLarge { segment: 0, length }) => {
474 assert_eq!(length, 1u32 << 24);
475 }
476 other => panic!("expected LengthTooLarge at the 24-bit boundary; got {other:?}"),
477 }
478 }
479
480 #[test]
481 fn pack_handles_max_representable_length() {
482 let segments = [(MAX_SEGMENT_LENGTH, 0xFFu8)];
483 let packed = pack_rle_segments(&segments).expect("Fix: max-length must pack");
484 let (lengths, values) = rle_segment_lengths_cpu(&packed);
485 assert_eq!(lengths, vec![MAX_SEGMENT_LENGTH]);
486 assert_eq!(values, vec![0xFF]);
487 }
488
489 #[test]
490 fn pack_handles_zero_length_segment_as_no_op() {
491 let segments = [(0u32, 0xABu8)];
494 let packed = pack_rle_segments(&segments).expect("Fix: zero-length must pack");
495 let (lengths, _values) = rle_segment_lengths_cpu(&packed);
496 assert_eq!(lengths, vec![0]);
497 }
498
499 #[test]
500 fn pack_preserves_per_segment_index_in_error() {
501 let mut segments: Vec<(u32, u8)> = (0..10).map(|i| (i as u32, 0u8)).collect();
503 segments[3].0 = 1u32 << 25; match pack_rle_segments(&segments) {
505 Err(PackError::LengthTooLarge { segment: 3, .. }) => {}
506 other => panic!("expected error at segment 3; got {other:?}"),
507 }
508 }
509
510 #[test]
511 fn start_offsets_are_exclusive_prefix_sum() {
512 let lengths = [3u32, 5, 2, 7];
513 let (offsets, total) = rle_segment_start_offsets_cpu(&lengths);
514 assert_eq!(offsets, vec![0, 3, 8, 10]);
515 assert_eq!(total, 17, "sum of lengths");
516 }
517
518 #[test]
519 fn start_offsets_handle_zero_length_runs_correctly() {
520 let lengths = [3u32, 0, 5, 0, 2];
521 let (offsets, total) = rle_segment_start_offsets_cpu(&lengths);
522 assert_eq!(offsets, vec![0, 3, 3, 8, 8]);
523 assert_eq!(total, 10);
524 }
525
526 #[test]
527 fn start_offsets_handle_empty_input() {
528 let (offsets, total) = rle_segment_start_offsets_cpu(&[]);
529 assert!(offsets.is_empty());
530 assert_eq!(total, 0);
531 }
532
533 #[test]
534 fn end_to_end_decode_expands_runs_in_order() {
535 let segments = [(3u32, b'A'), (2u32, b'B'), (1u32, b'C')];
537 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
538 let decoded = rle_decode_cpu(&packed);
539 assert_eq!(decoded, b"AAABBC".to_vec());
540 }
541
542 #[test]
543 fn end_to_end_decode_handles_long_run() {
544 let segments = [(1000u32, 0x42u8)];
546 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
547 let decoded = rle_decode_cpu(&packed);
548 assert_eq!(decoded.len(), 1000);
549 assert!(decoded.iter().all(|&b| b == 0x42));
550 }
551
552 #[test]
553 fn end_to_end_decode_handles_alternating_short_runs() {
554 let mut segments = Vec::with_capacity(256);
556 for i in 0..256 {
557 segments.push((1u32, if i % 2 == 0 { 0xAAu8 } else { 0xBBu8 }));
558 }
559 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
560 let decoded = rle_decode_cpu(&packed);
561 assert_eq!(decoded.len(), 256);
562 for (i, byte) in decoded.iter().enumerate() {
563 let expected = if i % 2 == 0 { 0xAA } else { 0xBB };
564 assert_eq!(*byte, expected);
565 }
566 }
567
568 #[test]
569 fn end_to_end_decode_handles_empty_input() {
570 let decoded = rle_decode_cpu(&[]);
571 assert!(decoded.is_empty());
572 }
573
574 #[test]
575 fn end_to_end_decode_handles_zero_length_segments_as_skips() {
576 let segments = [(2u32, b'A'), (0u32, b'X'), (3u32, b'B')];
577 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
578 let decoded = rle_decode_cpu(&packed);
579 assert_eq!(decoded, b"AABBB".to_vec());
580 }
581
582 #[test]
583 fn pack_into_reuses_existing_capacity() {
584 let segments = [(2u32, b'A'), (4u32, b'B')];
585 let mut out = Vec::with_capacity(64);
586 let before = out.capacity();
587 pack_rle_segments_into(&segments, &mut out).expect("Fix: pack_into must succeed");
588 assert_eq!(out.len(), 2);
589 assert_eq!(
590 out.capacity(),
591 before,
592 "pack_into must reuse caller-owned capacity"
593 );
594 }
595
596 #[test]
597 fn pack_into_truncates_stale_tail_without_reallocating() {
598 let segments = [(2u32, b'A'), (4u32, b'B')];
599 let mut out = Vec::with_capacity(64);
600 out.extend([0xFFFF_FFFF; 16]);
601 let ptr = out.as_ptr();
602
603 try_pack_rle_segments_into(&segments, &mut out).expect("Fix: pack_into must succeed");
604
605 assert_eq!(
606 out,
607 vec![(2u32 << 8) | u32::from(b'A'), (4u32 << 8) | u32::from(b'B')]
608 );
609 assert_eq!(out.as_ptr(), ptr);
610 }
611
612 #[test]
613 fn cpu_unpack_into_reuses_existing_capacity() {
614 let segments = [(2u32, b'A'), (4u32, b'B')];
615 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
616 let mut lengths = Vec::with_capacity(64);
617 let mut values = Vec::with_capacity(64);
618 let lengths_capacity = lengths.capacity();
619 let values_capacity = values.capacity();
620
621 rle_segment_lengths_cpu_into(&packed, &mut lengths, &mut values);
622
623 assert_eq!(lengths, vec![2, 4]);
624 assert_eq!(values, vec![u32::from(b'A'), u32::from(b'B')]);
625 assert_eq!(lengths.capacity(), lengths_capacity);
626 assert_eq!(values.capacity(), values_capacity);
627 }
628
629 #[test]
630 fn cpu_unpack_into_truncates_stale_tail_without_reallocating() {
631 let segments = [(2u32, b'A'), (4u32, b'B')];
632 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
633 let mut lengths = Vec::with_capacity(64);
634 let mut values = Vec::with_capacity(64);
635 lengths.extend([99u32; 16]);
636 values.extend([99u32; 16]);
637 let lengths_ptr = lengths.as_ptr();
638 let values_ptr = values.as_ptr();
639
640 try_rle_segment_lengths_cpu_into(&packed, &mut lengths, &mut values).unwrap();
641
642 assert_eq!(lengths, vec![2, 4]);
643 assert_eq!(values, vec![u32::from(b'A'), u32::from(b'B')]);
644 assert_eq!(lengths.as_ptr(), lengths_ptr);
645 assert_eq!(values.as_ptr(), values_ptr);
646 }
647
648 #[test]
649 fn start_offsets_into_reuses_existing_capacity() {
650 let mut offsets = Vec::with_capacity(64);
651 let capacity = offsets.capacity();
652 let total = rle_segment_start_offsets_cpu_into(&[2, 0, 4], &mut offsets);
653
654 assert_eq!(offsets, vec![0, 2, 2]);
655 assert_eq!(total, 6);
656 assert_eq!(offsets.capacity(), capacity);
657 }
658
659 #[test]
660 fn start_offsets_into_truncates_stale_tail_without_reallocating() {
661 let mut offsets = Vec::with_capacity(64);
662 offsets.extend([99u32; 16]);
663 let ptr = offsets.as_ptr();
664
665 let total = try_rle_segment_start_offsets_cpu_into(&[2, 0, 4], &mut offsets).unwrap();
666
667 assert_eq!(offsets, vec![0, 2, 2]);
668 assert_eq!(total, 6);
669 assert_eq!(offsets.as_ptr(), ptr);
670 }
671
672 #[test]
673 fn decode_into_reuses_existing_capacity_without_intermediate_vectors() {
674 let segments = [(2u32, b'A'), (0u32, b'X'), (3u32, b'B')];
675 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
676 let mut decoded = Vec::with_capacity(64);
677 let capacity = decoded.capacity();
678
679 rle_decode_cpu_into(&packed, &mut decoded);
680
681 assert_eq!(decoded, b"AABBB".to_vec());
682 assert_eq!(decoded.capacity(), capacity);
683 }
684
685 #[test]
686 fn decode_into_truncates_stale_tail_without_reallocating() {
687 let segments = [(2u32, b'A'), (0u32, b'X'), (3u32, b'B')];
688 let packed = pack_rle_segments(&segments).expect("Fix: pack must succeed");
689 let mut decoded = Vec::with_capacity(64);
690 decoded.extend([0xFFu8; 16]);
691 let ptr = decoded.as_ptr();
692
693 try_rle_decode_cpu_into(&packed, &mut decoded).unwrap();
694
695 assert_eq!(decoded, b"AABBB".to_vec());
696 assert_eq!(decoded.as_ptr(), ptr);
697 }
698
699 #[test]
700 fn generated_pack_unpack_offsets_and_decode_match_independent_reference() {
701 let mut state = 0xA17E_D15C_u32;
702 for case in 0..4096u32 {
703 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
704 let count = match case {
705 0 => 0,
706 1 => 1,
707 2 => 256,
708 3 => 257,
709 4 => 1025,
710 _ => state % 4097,
711 } as usize;
712 let mut segments = Vec::with_capacity(count);
713 for idx in 0..count {
714 state = state.rotate_left(5) ^ (idx as u32).wrapping_mul(0x9E37_79B9);
715 segments.push((state % 9, state.rotate_right(8) as u8));
716 }
717 let packed = pack_rle_segments(&segments).unwrap();
718 let mut lengths = Vec::with_capacity(count + 3);
719 let mut values = Vec::with_capacity(count + 3);
720 let mut offsets = Vec::with_capacity(count + 3);
721 let mut decoded = Vec::with_capacity(
722 segments.iter().map(|(len, _)| *len as usize).sum::<usize>() + 3,
723 );
724
725 try_rle_segment_lengths_cpu_into(&packed, &mut lengths, &mut values).unwrap();
726 let total = try_rle_segment_start_offsets_cpu_into(&lengths, &mut offsets).unwrap();
727 try_rle_decode_cpu_into(&packed, &mut decoded).unwrap();
728
729 let expected_lengths: Vec<u32> = segments.iter().map(|(len, _)| *len).collect();
730 let expected_values: Vec<u32> = segments
731 .iter()
732 .map(|(_, value)| u32::from(*value))
733 .collect();
734 let mut expected_offsets = Vec::with_capacity(count);
735 let mut expected_total = 0u32;
736 let mut expected_decoded = Vec::new();
737 for &(len, value) in &segments {
738 expected_offsets.push(expected_total);
739 expected_total = expected_total.saturating_add(len);
740 expected_decoded.extend(std::iter::repeat_n(value, len as usize));
741 }
742
743 assert_eq!(lengths, expected_lengths, "case {case}");
744 assert_eq!(values, expected_values, "case {case}");
745 assert_eq!(offsets, expected_offsets, "case {case}");
746 assert_eq!(total, expected_total, "case {case}");
747 assert_eq!(decoded, expected_decoded, "case {case}");
748 }
749 }
750
751 #[test]
752 fn build_program_returns_well_formed_program() {
753 let program = rle_segment_lengths(8);
754 assert_eq!(
755 program.buffers().len(),
756 3,
757 "segments_in + lengths_out + values_out"
758 );
759 assert_eq!(program.workgroup_size(), RLE_SEGMENT_LENGTHS_WORKGROUP_SIZE);
760 }
761
762 #[test]
763 fn dispatch_grid_packs_segment_lanes_into_workgroups() {
764 assert_eq!(rle_segment_lengths_dispatch_grid(0), [1, 1, 1]);
765 assert_eq!(rle_segment_lengths_dispatch_grid(1), [1, 1, 1]);
766 assert_eq!(rle_segment_lengths_dispatch_grid(256), [1, 1, 1]);
767 assert_eq!(rle_segment_lengths_dispatch_grid(257), [2, 1, 1]);
768 assert_eq!(rle_segment_lengths_dispatch_grid(1025), [5, 1, 1]);
769 }
770
771 #[test]
772 fn zero_segment_count_traps() {
773 let program = rle_segment_lengths(0);
774 assert!(program.stats().trap());
775 }
776
777 #[test]
778 fn build_program_is_deterministic_across_calls() {
779 let p1 = rle_segment_lengths(32);
780 let p2 = rle_segment_lengths(32);
781 assert_eq!(p1.buffers().len(), p2.buffers().len());
782 assert_eq!(p1.workgroup_size(), p2.workgroup_size());
783 }
784
785 #[test]
786 fn op_id_is_canonical_and_stable() {
787 assert_eq!(OP_ID, "vyre-primitives::decode::rle_segment_lengths");
788 }
789
790 #[test]
791 fn binding_indices_are_canonical_and_stable() {
792 assert_eq!(BINDING_SEGMENTS_IN, 0);
793 assert_eq!(BINDING_SEGMENT_LENGTHS_OUT, 1);
794 assert_eq!(BINDING_SEGMENT_VALUES_OUT, 2);
795 }
796
797 #[test]
798 fn max_segment_length_is_canonical_24_bit_field_max() {
799 assert_eq!(MAX_SEGMENT_LENGTH, (1u32 << 24) - 1);
800 assert_eq!(MAX_SEGMENT_VALUE, 0xFF);
801 }
802}
803
804#[cfg(test)]
805mod non_panicking_wrapper_tests {
806 use super::*;
807
808 #[test]
809 fn compatibility_wrappers_match_fallible_references() {
810 let packed = pack_rle_segments(&[(3, b'a'), (0, b'b'), (2, b'c')])
811 .expect("Fix: unit-test oracle precondition - valid RLE headers must pack");
812
813 let mut lengths = Vec::new();
814 let mut values = Vec::new();
815 try_rle_segment_lengths_cpu_into(&packed, &mut lengths, &mut values)
816 .expect("Fix: unit-test oracle precondition - fallible length/value oracle must accept valid packed input");
817 assert_eq!(
818 rle_segment_lengths_cpu(&packed),
819 (lengths.clone(), values.clone())
820 );
821
822 lengths.fill(u32::MAX);
823 values.fill(u32::MAX);
824 rle_segment_lengths_cpu_into(&packed, &mut lengths, &mut values);
825 assert_eq!(lengths, vec![3, 0, 2]);
826 assert_eq!(
827 values,
828 vec![u32::from(b'a'), u32::from(b'b'), u32::from(b'c')]
829 );
830
831 let mut offsets = Vec::new();
832 let total = try_rle_segment_start_offsets_cpu_into(&lengths, &mut offsets).expect(
833 "Fix: unit-test oracle precondition - fallible offset oracle must accept valid lengths",
834 );
835 assert_eq!(
836 rle_segment_start_offsets_cpu(&lengths),
837 (offsets.clone(), total)
838 );
839
840 offsets.fill(u32::MAX);
841 let total = rle_segment_start_offsets_cpu_into(&lengths, &mut offsets);
842 assert_eq!(offsets, vec![0, 3, 3]);
843 assert_eq!(total, 5);
844
845 let mut decoded = Vec::new();
846 try_rle_decode_cpu_into(&packed, &mut decoded)
847 .expect("Fix: unit-test oracle precondition - fallible decode oracle must accept valid packed input");
848 assert_eq!(rle_decode_cpu(&packed), decoded);
849
850 decoded.fill(0);
851 rle_decode_cpu_into(&packed, &mut decoded);
852 assert_eq!(decoded, b"aaacc");
853 }
854}