1use std::marker::PhantomData;
50
51#[repr(u32)]
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum RaspPermission {
56 None = 0,
57 Read = 1 << 0,
58 Write = 1 << 1,
59 Execute = 1 << 2,
60}
61
62const SEALED_BIT_U32: u32 = 1 << 31;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum RaspError {
71 OutOfBounds,
72 PermissionDenied,
73 Sealed,
74 AddressOverflow,
75 LayoutTooWide,
78 FeatureNotSupported,
82}
83
84pub struct RaspBatch<T> {
91 ptrs: Vec<u64>,
92 bases: Vec<u64>,
93 lengths: Vec<u32>,
94 perms: Vec<u32>,
95 _phantom: PhantomData<*const T>,
96}
97
98unsafe impl<T: Send> Send for RaspBatch<T> {}
99unsafe impl<T: Sync> Sync for RaspBatch<T> {}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub struct RaspBatchIndex<T> {
104 idx: u32,
105 _phantom: PhantomData<T>,
106}
107
108impl<T> RaspBatchIndex<T> {
109 pub const fn new(idx: u32) -> Self {
110 Self { idx, _phantom: PhantomData }
111 }
112 pub const fn raw(&self) -> u32 { self.idx }
113}
114
115impl<T> Default for RaspBatch<T> {
116 fn default() -> Self { Self::new() }
117}
118
119impl<T> RaspBatch<T> {
120 pub fn new() -> Self {
121 Self {
122 ptrs: Vec::new(),
123 bases: Vec::new(),
124 lengths: Vec::new(),
125 perms: Vec::new(),
126 _phantom: PhantomData,
127 }
128 }
129
130 pub fn with_capacity(n: usize) -> Self {
131 Self {
132 ptrs: Vec::with_capacity(n),
133 bases: Vec::with_capacity(n),
134 lengths: Vec::with_capacity(n),
135 perms: Vec::with_capacity(n),
136 _phantom: PhantomData,
137 }
138 }
139
140 #[inline]
141 pub fn len(&self) -> usize { self.ptrs.len() }
142 #[inline]
143 pub fn is_empty(&self) -> bool { self.ptrs.is_empty() }
144 pub fn capacity(&self) -> usize { self.ptrs.capacity() }
145
146 pub fn push_from_slice<'a>(
150 &mut self,
151 slice: &'a [T],
152 perms: u32,
153 ) -> Result<(RaspBatchIndex<T>, &'a [T]), RaspError> {
154 let ptr_usize = slice.as_ptr() as usize;
155 let length_bytes = std::mem::size_of_val(slice);
156 if length_bytes > u32::MAX as usize {
157 return Err(RaspError::LayoutTooWide);
158 }
159 let idx = self.ptrs.len();
160 if idx >= u32::MAX as usize {
161 return Err(RaspError::LayoutTooWide);
162 }
163 self.ptrs.push(ptr_usize as u64);
164 self.bases.push(ptr_usize as u64);
165 self.lengths.push(length_bytes as u32);
166 self.perms.push(perms);
167 Ok((RaspBatchIndex::new(idx as u32), slice))
168 }
169
170 pub fn push_raw(
172 &mut self,
173 ptr: u64,
174 base: u64,
175 length: u32,
176 perms: u32,
177 ) -> Result<RaspBatchIndex<T>, RaspError> {
178 let idx = self.ptrs.len();
179 if idx >= u32::MAX as usize {
180 return Err(RaspError::LayoutTooWide);
181 }
182 if ptr < base {
183 return Err(RaspError::OutOfBounds);
184 }
185 let access_end = (ptr as usize).checked_add(std::mem::size_of::<T>())
186 .ok_or(RaspError::AddressOverflow)?;
187 let region_end = (base as usize).checked_add(length as usize)
188 .ok_or(RaspError::AddressOverflow)?;
189 if access_end > region_end {
190 return Err(RaspError::OutOfBounds);
191 }
192 self.ptrs.push(ptr);
193 self.bases.push(base);
194 self.lengths.push(length);
195 self.perms.push(perms);
196 Ok(RaspBatchIndex::new(idx as u32))
197 }
198
199 pub fn check_read_scalar(
202 &self,
203 idx: RaspBatchIndex<T>,
204 ) -> Result<(), RaspError> {
205 let i = idx.idx as usize;
206 if i >= self.ptrs.len() {
207 return Err(RaspError::OutOfBounds);
208 }
209 let p = self.perms[i];
210 if p & SEALED_BIT_U32 != 0 {
211 return Err(RaspError::Sealed);
212 }
213 if p & (RaspPermission::Read as u32) == 0 {
214 return Err(RaspError::PermissionDenied);
215 }
216 let ptr_u = self.ptrs[i] as usize;
217 let base_u = self.bases[i] as usize;
218 if ptr_u < base_u {
219 return Err(RaspError::OutOfBounds);
220 }
221 let access_end = ptr_u.checked_add(std::mem::size_of::<T>())
222 .ok_or(RaspError::AddressOverflow)?;
223 let region_end = base_u.checked_add(self.lengths[i] as usize)
224 .ok_or(RaspError::AddressOverflow)?;
225 if access_end > region_end {
226 return Err(RaspError::OutOfBounds);
227 }
228 Ok(())
229 }
230
231 pub fn raw_ptr(&self, idx: RaspBatchIndex<T>) -> Option<*const T> {
235 let i = idx.idx as usize;
236 if i >= self.ptrs.len() { return None; }
237 Some(self.ptrs[i] as usize as *const T)
238 }
239
240 pub unsafe fn read_at(&self, idx: RaspBatchIndex<T>) -> Result<T, RaspError>
252 where T: Copy,
253 {
254 self.check_read_scalar(idx)?;
255 let i = idx.idx as usize;
256 let p = self.ptrs[i] as usize as *const T;
257 Ok(unsafe { *p })
261 }
262
263 pub fn check_read_all_scalar(&self) -> Vec<Result<(), RaspError>> {
265 let n = self.len();
266 let mut out = Vec::with_capacity(n);
267 for i in 0..n {
268 out.push(self.check_read_scalar(RaspBatchIndex::new(i as u32)));
269 }
270 out
271 }
272
273 pub fn count_valid_scalar(&self) -> u32 {
276 let n = self.len();
277 let mut count = 0u32;
278 for i in 0..n {
279 if self.check_read_scalar(RaspBatchIndex::new(i as u32)).is_ok() {
280 count += 1;
281 }
282 }
283 count
284 }
285
286 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
294 #[target_feature(enable = "avx2")]
295 #[inline]
296 pub unsafe fn count_valid_avx2(&self) -> u32 {
297 #[cfg(target_arch = "x86_64")]
298 use std::arch::x86_64::*;
299 #[cfg(target_arch = "x86")]
300 use std::arch::x86::*;
301
302 let n = self.len();
303 if n == 0 {
304 return 0;
305 }
306 let chunks = n / 4;
307 let size_t = std::mem::size_of::<T>() as u64;
308 let size_t_vec = _mm256_set1_epi64x(size_t as i64);
309 let read_bit_vec = _mm_set1_epi32(RaspPermission::Read as i32);
310 let zero128 = _mm_setzero_si128();
311
312 let mut count_in_simd = 0u32;
313 for c in 0..chunks {
314 let off = c * 4;
315 let v_ptrs = unsafe {
318 _mm256_loadu_si256(self.ptrs.as_ptr().add(off) as *const __m256i)
319 };
320 let v_bases = unsafe {
321 _mm256_loadu_si256(self.bases.as_ptr().add(off) as *const __m256i)
322 };
323 let lengths_xmm = unsafe {
324 _mm_loadu_si128(self.lengths.as_ptr().add(off) as *const __m128i)
325 };
326 let perms_xmm = unsafe {
327 _mm_loadu_si128(self.perms.as_ptr().add(off) as *const __m128i)
328 };
329
330 let cmp_lower = _mm256_cmpgt_epi64(v_bases, v_ptrs);
332
333 let v_lengths_u64 = _mm256_cvtepu32_epi64(lengths_xmm);
335 let v_region_end = _mm256_add_epi64(v_bases, v_lengths_u64);
336 let v_access_end = _mm256_add_epi64(v_ptrs, size_t_vec);
337 let cmp_upper = _mm256_cmpgt_epi64(v_access_end, v_region_end);
338
339 let sealed_mask4 =
343 _mm_movemask_ps(_mm_castsi128_ps(perms_xmm)) as u32;
344
345 let read_anded = _mm_and_si128(perms_xmm, read_bit_vec);
348 let read_eq_zero = _mm_cmpeq_epi32(read_anded, zero128);
349 let no_read_mask4 =
350 _mm_movemask_ps(_mm_castsi128_ps(read_eq_zero)) as u32;
351
352 let oob_lower_4 =
357 _mm256_movemask_pd(_mm256_castsi256_pd(cmp_lower)) as u32;
358 let oob_upper_4 =
359 _mm256_movemask_pd(_mm256_castsi256_pd(cmp_upper)) as u32;
360
361 let any_fail = sealed_mask4 | no_read_mask4 | oob_lower_4 | oob_upper_4;
364 let valid_in_chunk = 4 - (any_fail & 0xF).count_ones();
366 count_in_simd += valid_in_chunk;
367 }
368
369 let mut count_scalar = 0u32;
371 for i in (chunks * 4)..n {
372 if self.check_read_scalar(RaspBatchIndex::new(i as u32)).is_ok() {
373 count_scalar += 1;
374 }
375 }
376 count_in_simd + count_scalar
377 }
378
379 #[cfg(target_arch = "x86_64")]
388 #[target_feature(enable = "avx512f")]
389 #[inline]
390 pub unsafe fn count_valid_avx512(&self) -> u32 {
391 use std::arch::x86_64::*;
392
393 let n = self.len();
394 if n == 0 {
395 return 0;
396 }
397 let chunks = n / 8;
398 let size_t = std::mem::size_of::<T>() as u64;
399 let size_t_vec = _mm512_set1_epi64(size_t as i64);
400 let read_bit_vec = _mm256_set1_epi32(RaspPermission::Read as i32);
401 let zero256 = _mm256_setzero_si256();
402
403 let mut count_in_simd = 0u32;
404 for c in 0..chunks {
405 let off = c * 8;
406 let v_ptrs = unsafe {
408 _mm512_loadu_si512(self.ptrs.as_ptr().add(off) as *const __m512i)
409 };
410 let v_bases = unsafe {
411 _mm512_loadu_si512(self.bases.as_ptr().add(off) as *const __m512i)
412 };
413 let lengths_ymm = unsafe {
414 _mm256_loadu_si256(self.lengths.as_ptr().add(off) as *const __m256i)
415 };
416 let perms_ymm = unsafe {
417 _mm256_loadu_si256(self.perms.as_ptr().add(off) as *const __m256i)
418 };
419
420 let oob_lower_mask: u8 = _mm512_cmpgt_epi64_mask(v_bases, v_ptrs);
423
424 let v_lengths_u64 = _mm512_cvtepu32_epi64(lengths_ymm);
426 let v_region_end = _mm512_add_epi64(v_bases, v_lengths_u64);
427 let v_access_end = _mm512_add_epi64(v_ptrs, size_t_vec);
428 let oob_upper_mask: u8 =
429 _mm512_cmpgt_epi64_mask(v_access_end, v_region_end);
430
431 let sealed_mask8 =
435 _mm256_movemask_ps(_mm256_castsi256_ps(perms_ymm)) as u32 & 0xFF;
436
437 let read_anded = _mm256_and_si256(perms_ymm, read_bit_vec);
439 let read_eq_zero = _mm256_cmpeq_epi32(read_anded, zero256);
440 let no_read_mask8 =
441 _mm256_movemask_ps(_mm256_castsi256_ps(read_eq_zero)) as u32 & 0xFF;
442
443 let any_fail = (oob_lower_mask as u32)
444 | (oob_upper_mask as u32)
445 | sealed_mask8
446 | no_read_mask8;
447 let valid_in_chunk = 8 - (any_fail & 0xFF).count_ones();
448 count_in_simd += valid_in_chunk;
449 }
450
451 let mut count_scalar = 0u32;
453 for i in (chunks * 8)..n {
454 if self.check_read_scalar(RaspBatchIndex::new(i as u32)).is_ok() {
455 count_scalar += 1;
456 }
457 }
458 count_in_simd + count_scalar
459 }
460
461 pub fn count_valid(&self) -> u32 {
465 #[cfg(target_arch = "x86_64")]
466 {
467 if std::is_x86_feature_detected!("avx512f") {
468 return unsafe { self.count_valid_avx512() };
470 }
471 }
472 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
473 {
474 if std::is_x86_feature_detected!("avx2") {
475 return unsafe { self.count_valid_avx2() };
477 }
478 }
479 self.count_valid_scalar()
480 }
481
482 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
491 #[target_feature(enable = "avx2")]
492 #[inline]
493 pub unsafe fn check_read_all_avx2(&self) -> Vec<Result<(), RaspError>> {
494 #[cfg(target_arch = "x86_64")]
495 use std::arch::x86_64::*;
496 #[cfg(target_arch = "x86")]
497 use std::arch::x86::*;
498
499 let n = self.len();
500 let mut results: Vec<Result<(), RaspError>> = vec![Ok(()); n];
501 if n == 0 {
502 return results;
503 }
504 let chunks = n / 4;
505 let size_t = std::mem::size_of::<T>() as u64;
506 let size_t_vec = _mm256_set1_epi64x(size_t as i64);
507 let read_bit_vec = _mm_set1_epi32(RaspPermission::Read as i32);
508 let zero128 = _mm_setzero_si128();
509
510 for c in 0..chunks {
511 let off = c * 4;
512 let v_ptrs = unsafe {
514 _mm256_loadu_si256(self.ptrs.as_ptr().add(off) as *const __m256i)
515 };
516 let v_bases = unsafe {
517 _mm256_loadu_si256(self.bases.as_ptr().add(off) as *const __m256i)
518 };
519 let lengths_xmm = unsafe {
520 _mm_loadu_si128(self.lengths.as_ptr().add(off) as *const __m128i)
521 };
522 let perms_xmm = unsafe {
523 _mm_loadu_si128(self.perms.as_ptr().add(off) as *const __m128i)
524 };
525
526 let cmp_lower = _mm256_cmpgt_epi64(v_bases, v_ptrs);
527 let v_lengths_u64 = _mm256_cvtepu32_epi64(lengths_xmm);
528 let v_region_end = _mm256_add_epi64(v_bases, v_lengths_u64);
529 let v_access_end = _mm256_add_epi64(v_ptrs, size_t_vec);
530 let cmp_upper = _mm256_cmpgt_epi64(v_access_end, v_region_end);
531
532 let sealed_mask4 =
533 _mm_movemask_ps(_mm_castsi128_ps(perms_xmm)) as u32;
534 let read_anded = _mm_and_si128(perms_xmm, read_bit_vec);
535 let read_eq_zero = _mm_cmpeq_epi32(read_anded, zero128);
536 let no_read_mask4 =
537 _mm_movemask_ps(_mm_castsi128_ps(read_eq_zero)) as u32;
538
539 let oob_lower_4 =
542 _mm256_movemask_pd(_mm256_castsi256_pd(cmp_lower)) as u32;
543 let oob_upper_4 =
544 _mm256_movemask_pd(_mm256_castsi256_pd(cmp_upper)) as u32;
545
546 for i in 0..4 {
547 let bit = 1u32 << i;
548 if sealed_mask4 & bit != 0 {
549 results[off + i] = Err(RaspError::Sealed);
550 } else if no_read_mask4 & bit != 0 {
551 results[off + i] = Err(RaspError::PermissionDenied);
552 } else if (oob_lower_4 | oob_upper_4) & bit != 0 {
553 results[off + i] = Err(RaspError::OutOfBounds);
554 }
555 }
556 }
557 #[allow(clippy::needless_range_loop)]
561 for i in (chunks * 4)..n {
562 results[i] = self.check_read_scalar(RaspBatchIndex::new(i as u32));
563 }
564 results
565 }
566
567 #[cfg(target_arch = "x86_64")]
575 #[target_feature(enable = "avx512f")]
576 #[inline]
577 pub unsafe fn check_read_all_avx512(&self) -> Vec<Result<(), RaspError>> {
578 use std::arch::x86_64::*;
579
580 let n = self.len();
581 let mut results: Vec<Result<(), RaspError>> = vec![Ok(()); n];
582 if n == 0 {
583 return results;
584 }
585 let chunks = n / 8;
586 let size_t = std::mem::size_of::<T>() as u64;
587 let size_t_vec = _mm512_set1_epi64(size_t as i64);
588 let read_bit_vec = _mm256_set1_epi32(RaspPermission::Read as i32);
589 let zero256 = _mm256_setzero_si256();
590
591 for c in 0..chunks {
592 let off = c * 8;
593 let v_ptrs = unsafe {
595 _mm512_loadu_si512(self.ptrs.as_ptr().add(off) as *const __m512i)
596 };
597 let v_bases = unsafe {
598 _mm512_loadu_si512(self.bases.as_ptr().add(off) as *const __m512i)
599 };
600 let lengths_ymm = unsafe {
601 _mm256_loadu_si256(self.lengths.as_ptr().add(off) as *const __m256i)
602 };
603 let perms_ymm = unsafe {
604 _mm256_loadu_si256(self.perms.as_ptr().add(off) as *const __m256i)
605 };
606
607 let oob_lower_mask: u8 = _mm512_cmpgt_epi64_mask(v_bases, v_ptrs);
608 let v_lengths_u64 = _mm512_cvtepu32_epi64(lengths_ymm);
609 let v_region_end = _mm512_add_epi64(v_bases, v_lengths_u64);
610 let v_access_end = _mm512_add_epi64(v_ptrs, size_t_vec);
611 let oob_upper_mask: u8 =
612 _mm512_cmpgt_epi64_mask(v_access_end, v_region_end);
613
614 let sealed_mask8 =
615 _mm256_movemask_ps(_mm256_castsi256_ps(perms_ymm)) as u32 & 0xFF;
616 let read_anded = _mm256_and_si256(perms_ymm, read_bit_vec);
617 let read_eq_zero = _mm256_cmpeq_epi32(read_anded, zero256);
618 let no_read_mask8 =
619 _mm256_movemask_ps(_mm256_castsi256_ps(read_eq_zero)) as u32 & 0xFF;
620
621 for i in 0..8 {
622 let bit = 1u32 << i;
623 if sealed_mask8 & bit != 0 {
624 results[off + i] = Err(RaspError::Sealed);
625 } else if no_read_mask8 & bit != 0 {
626 results[off + i] = Err(RaspError::PermissionDenied);
627 } else if ((oob_lower_mask as u32) | (oob_upper_mask as u32)) & bit
628 != 0
629 {
630 results[off + i] = Err(RaspError::OutOfBounds);
631 }
632 }
633 }
634 #[allow(clippy::needless_range_loop)]
636 for i in (chunks * 8)..n {
637 results[i] = self.check_read_scalar(RaspBatchIndex::new(i as u32));
638 }
639 results
640 }
641
642 pub fn check_read_all(&self) -> Vec<Result<(), RaspError>> {
644 #[cfg(target_arch = "x86_64")]
645 {
646 if std::is_x86_feature_detected!("avx512f") {
647 return unsafe { self.check_read_all_avx512() };
649 }
650 }
651 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
652 {
653 if std::is_x86_feature_detected!("avx2") {
654 return unsafe { self.check_read_all_avx2() };
656 }
657 }
658 self.check_read_all_scalar()
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665
666 #[test]
667 fn empty_batch_returns_zero_count() {
668 let b: RaspBatch<u8> = RaspBatch::new();
669 assert!(b.is_empty());
670 assert_eq!(b.count_valid(), 0);
671 assert_eq!(b.check_read_all().len(), 0);
672 }
673
674 #[test]
675 fn push_from_slice_and_read_single() {
676 let storage: Vec<u64> = vec![42; 8];
677 let mut b: RaspBatch<u64> = RaspBatch::new();
678 let (idx, _anchor) = b.push_from_slice(
679 &storage, RaspPermission::Read as u32,
680 ).unwrap();
681 assert_eq!(b.len(), 1);
682 assert_eq!(b.check_read_scalar(idx), Ok(()));
683 }
684
685 #[test]
686 fn count_valid_scalar_matches_check_all() {
687 let storages: Vec<Vec<u64>> = (0..8).map(|_| vec![0; 8]).collect();
688 let mut b: RaspBatch<u64> = RaspBatch::new();
689 for (i, s) in storages.iter().enumerate() {
690 let perms = if i % 2 == 0 {
691 RaspPermission::Read as u32
692 } else {
693 RaspPermission::None as u32
694 };
695 b.push_from_slice(s, perms).unwrap();
696 }
697 let count = b.count_valid_scalar();
698 let all = b.check_read_all_scalar();
699 let oks = all.iter().filter(|r| r.is_ok()).count() as u32;
700 assert_eq!(count, oks);
701 assert_eq!(count, 4);
702 }
703
704 #[test]
705 fn count_valid_avx2_matches_scalar_oracle() {
706 if !std::is_x86_feature_detected!("avx2") {
707 return;
708 }
709 let storages: Vec<Vec<u64>> = (0..16).map(|_| vec![0; 8]).collect();
710 let perms_r = RaspPermission::Read as u32;
711 let sealed = perms_r | SEALED_BIT_U32;
712 let perms_none = RaspPermission::None as u32;
713 let perms_rw =
714 (RaspPermission::Read as u32) | (RaspPermission::Write as u32);
715 let choices = [perms_r, sealed, perms_none, perms_rw];
716
717 let mut b: RaspBatch<u64> = RaspBatch::new();
718 for (i, s) in storages.iter().enumerate() {
719 b.push_from_slice(s, choices[i % 4]).unwrap();
720 }
721 let scalar = b.count_valid_scalar();
722 let avx2 = unsafe { b.count_valid_avx2() };
724 let dispatched = b.count_valid();
725 assert_eq!(scalar, avx2, "AVX2 count mismatch");
726 assert_eq!(scalar, dispatched, "Dispatched count mismatch");
727 assert_eq!(scalar, 8);
731 }
732
733 #[test]
734 fn check_read_all_avx2_per_lane_results_match_scalar() {
735 if !std::is_x86_feature_detected!("avx2") {
736 return;
737 }
738 let storages: Vec<Vec<u64>> = (0..12).map(|_| vec![0; 8]).collect();
739 let perms_r = RaspPermission::Read as u32;
740 let sealed = perms_r | SEALED_BIT_U32;
741 let perms_none = RaspPermission::None as u32;
742 let choices = [perms_r, sealed, perms_none];
743
744 let mut b: RaspBatch<u64> = RaspBatch::new();
745 for (i, s) in storages.iter().enumerate() {
746 b.push_from_slice(s, choices[i % 3]).unwrap();
747 }
748 let scalar = b.check_read_all_scalar();
749 let avx2 = unsafe { b.check_read_all_avx2() };
750 assert_eq!(scalar.len(), avx2.len());
751 for (i, (s, v)) in scalar.iter().zip(avx2.iter()).enumerate() {
752 assert_eq!(s, v, "Mismatch at lane {i}");
753 }
754 }
755
756 #[test]
757 fn remainder_lanes_handled_by_scalar_fallback() {
758 if !std::is_x86_feature_detected!("avx2") {
759 return;
760 }
761 let storages: Vec<Vec<u64>> = (0..13).map(|_| vec![0; 8]).collect();
764 let mut b: RaspBatch<u64> = RaspBatch::new();
765 for s in &storages {
766 b.push_from_slice(s, RaspPermission::Read as u32).unwrap();
767 }
768 let scalar = b.count_valid_scalar();
769 let avx2 = unsafe { b.count_valid_avx2() };
770 assert_eq!(scalar, avx2);
771 assert_eq!(scalar, 13);
772 }
773
774 #[test]
775 fn push_raw_validates_bounds_at_construction() {
776 let mut b: RaspBatch<u8> = RaspBatch::new();
777 let r = b.push_raw(0x0FFF, 0x1000, 16, RaspPermission::Read as u32);
779 assert_eq!(r.err(), Some(RaspError::OutOfBounds));
780 let r = b.push_raw(0x1010, 0x1000, 8, RaspPermission::Read as u32);
782 assert_eq!(r.err(), Some(RaspError::OutOfBounds));
783 let r = b.push_raw(0x1000, 0x1000, 16, RaspPermission::Read as u32);
785 assert!(r.is_ok());
786 }
787
788 #[test]
789 fn sealed_bit_blocks_read_in_simd_path() {
790 if !std::is_x86_feature_detected!("avx2") {
791 return;
792 }
793 let storages: Vec<Vec<u64>> = (0..4).map(|_| vec![0; 8]).collect();
794 let mut b: RaspBatch<u64> = RaspBatch::new();
795 let perms_r = RaspPermission::Read as u32;
796 let sealed = perms_r | SEALED_BIT_U32;
797 for (i, s) in storages.iter().enumerate() {
798 let p = if i == 2 { sealed } else { perms_r };
799 b.push_from_slice(s, p).unwrap();
800 }
801 let results = unsafe { b.check_read_all_avx2() };
803 assert_eq!(results[0], Ok(()));
804 assert_eq!(results[1], Ok(()));
805 assert_eq!(results[2], Err(RaspError::Sealed));
806 assert_eq!(results[3], Ok(()));
807 }
808
809 #[test]
810 fn no_read_permission_caught_in_simd_path() {
811 if !std::is_x86_feature_detected!("avx2") {
812 return;
813 }
814 let storages: Vec<Vec<u64>> = (0..4).map(|_| vec![0; 8]).collect();
815 let mut b: RaspBatch<u64> = RaspBatch::new();
816 let perms_r = RaspPermission::Read as u32;
817 let perms_w_only = RaspPermission::Write as u32;
818 for (i, s) in storages.iter().enumerate() {
819 let p = if i == 1 { perms_w_only } else { perms_r };
820 b.push_from_slice(s, p).unwrap();
821 }
822 let results = unsafe { b.check_read_all_avx2() };
823 assert_eq!(results[0], Ok(()));
824 assert_eq!(results[1], Err(RaspError::PermissionDenied));
825 assert_eq!(results[2], Ok(()));
826 assert_eq!(results[3], Ok(()));
827 }
828
829 #[test]
830 fn large_batch_simd_vs_scalar_parity() {
831 if !std::is_x86_feature_detected!("avx2") {
832 return;
833 }
834 let storages: Vec<Vec<u64>> = (0..1024).map(|_| vec![0; 8]).collect();
836 let mut b: RaspBatch<u64> = RaspBatch::new();
837 let perms_r = RaspPermission::Read as u32;
838 let sealed = perms_r | SEALED_BIT_U32;
839 let perms_none = RaspPermission::None as u32;
840 let choices = [perms_r, sealed, perms_none, perms_r];
841 for (i, s) in storages.iter().enumerate() {
842 b.push_from_slice(s, choices[i % 4]).unwrap();
843 }
844 let scalar = b.count_valid_scalar();
845 let avx2 = unsafe { b.count_valid_avx2() };
846 assert_eq!(scalar, avx2);
847 assert_eq!(scalar, 512);
850 }
851}