1use std::mem::MaybeUninit;
8use std::ops::BitOrAssign;
9
10use vortex_buffer::BitBuffer;
11
12use crate::lane_kernels::CHUNK_LEN;
13use crate::lane_kernels::source::IndexedSource;
14
15pub trait IndexedSourceExt: IndexedSource + Sized {
25 #[inline]
50 fn try_map_masked_into<R, F>(
51 self,
52 mask: &BitBuffer,
53 out: &mut [MaybeUninit<R>],
54 f: F,
55 ) -> Result<(), usize>
56 where
57 R: Copy + Default,
58 F: Fn(Self::Item) -> Option<R>,
59 {
60 #[allow(clippy::inline_always)]
61 #[inline(always)]
62 fn chunk<S, R, F>(
63 values: &S,
64 out: &mut [MaybeUninit<R>],
65 f: &F,
66 src_chunk: u64,
67 base: usize,
68 count: usize,
69 ) -> Option<usize>
70 where
71 S: IndexedSource,
72 R: Copy + Default,
73 F: Fn(S::Item) -> Option<R>,
74 {
75 let mut fail_bits: u64 = 0;
76 for bit_idx in 0..count {
77 let idx = base + bit_idx;
78 let val = unsafe { values.get_unchecked(idx) };
80 let opt = f(val);
81 fail_bits |= (opt.is_none() as u64) << bit_idx;
82 let result = opt.unwrap_or_default();
83 unsafe { out.get_unchecked_mut(idx).write(result) };
84 }
85 let valid_failures = fail_bits & src_chunk;
86 (valid_failures != 0).then_some(base + valid_failures.trailing_zeros() as usize)
87 }
88
89 let values = self;
90 let len = values.len();
91 assert_eq!(len, mask.len(), "values and mask must have the same length");
92 assert_eq!(out.len(), len, "out must have the same length as values");
93
94 let chunks = mask.chunks();
95 let chunks_count = len / 64;
96 let remainder = len % 64;
97
98 for (chunk_idx, src_chunk) in chunks.iter().enumerate() {
99 if let Some(idx) = chunk(&values, out, &f, src_chunk, chunk_idx * 64, 64) {
100 return Err(idx);
101 }
102 }
103 if remainder != 0
104 && let Some(idx) = chunk(
105 &values,
106 out,
107 &f,
108 chunks.remainder_bits(),
109 chunks_count * 64,
110 remainder,
111 )
112 {
113 return Err(idx);
114 }
115 Ok(())
116 }
117
118 #[inline]
126 fn map_into<R, F>(self, out: &mut [MaybeUninit<R>], f: F)
127 where
128 F: Fn(Self::Item) -> R,
129 {
130 #[allow(clippy::inline_always)]
131 #[inline(always)]
132 fn chunk<S, R, F>(values: &S, out: &mut [MaybeUninit<R>], f: &F, base: usize, count: usize)
133 where
134 S: IndexedSource,
135 F: Fn(S::Item) -> R,
136 {
137 for bit_idx in 0..count {
138 let idx = base + bit_idx;
139 let val = unsafe { values.get_unchecked(idx) };
141 unsafe { out.get_unchecked_mut(idx).write(f(val)) };
142 }
143 }
144
145 let values = self;
146 let len = values.len();
147 assert_eq!(out.len(), len, "out must have the same length as values");
148
149 let chunks_count = len / CHUNK_LEN;
150 let remainder = len % CHUNK_LEN;
151
152 for chunk_idx in 0..chunks_count {
153 chunk(&values, out, &f, chunk_idx * CHUNK_LEN, CHUNK_LEN);
154 }
155 if remainder != 0 {
156 chunk(&values, out, &f, chunks_count * CHUNK_LEN, remainder);
157 }
158 }
159
160 #[inline]
182 fn map_bits_into<F>(self, words: &mut [u64], f: F)
183 where
184 F: Fn(Self::Item) -> bool,
185 {
186 #[allow(clippy::inline_always)]
187 #[inline(always)]
188 fn chunk<S, F>(values: &S, f: &F, base: usize, count: usize) -> u64
189 where
190 S: IndexedSource,
191 F: Fn(S::Item) -> bool,
192 {
193 let mut packed: u64 = 0;
194 for bit_idx in 0..count {
195 let val = unsafe { values.get_unchecked(base + bit_idx) };
197 packed |= (f(val) as u64) << bit_idx;
198 }
199 packed
200 }
201
202 let values = self;
203 let len = values.len();
204 let num_words = len.div_ceil(64);
205 assert!(
206 words.len() >= num_words,
207 "words slice has {} entries, need at least {num_words}",
208 words.len(),
209 );
210
211 let full = len / 64;
212 let remainder = len % 64;
213
214 for word_idx in 0..full {
215 words[word_idx] = chunk(&values, &f, word_idx * 64, 64);
216 }
217 if remainder != 0 {
218 words[full] = chunk(&values, &f, full * 64, remainder);
219 }
220 }
221
222 #[inline]
244 fn map_checked_into<R, Fail, Apply>(self, out: &mut [MaybeUninit<R>], apply: Apply) -> Fail
245 where
246 Fail: Copy + Default + BitOrAssign,
247 Apply: Fn(Self::Item) -> (R, Fail),
248 {
249 const {
250 assert!(
251 size_of::<Fail>() <= size_of::<R>(),
252 "failure evidence must be no wider than the value, or it bounds the vector width"
253 )
254 };
255
256 let values = self;
257 let len = values.len();
258 assert_eq!(out.len(), len, "out must have the same length as values");
259
260 let mut failed = Fail::default();
261 for idx in 0..len {
262 let val = unsafe { values.get_unchecked(idx) };
264
265 let (result, failure) = apply(val);
266 failed |= failure;
267
268 unsafe { out.get_unchecked_mut(idx).write(result) };
270 }
271 failed
272 }
273
274 #[inline]
297 fn try_map_into<R, F>(self, out: &mut [MaybeUninit<R>], f: F) -> Result<(), usize>
298 where
299 R: Copy + Default,
300 F: Fn(Self::Item) -> Option<R>,
301 {
302 #[allow(clippy::inline_always)]
306 #[inline(always)]
307 fn chunk<S, R, F>(
308 values: &S,
309 out: &mut [MaybeUninit<R>],
310 f: &F,
311 base: usize,
312 count: usize,
313 ) -> bool
314 where
315 S: IndexedSource,
316 R: Copy + Default,
317 F: Fn(S::Item) -> Option<R>,
318 {
319 let mut fail_acc: u64 = 0;
320 for bit_idx in 0..count {
321 let idx = base + bit_idx;
322 let val = unsafe { values.get_unchecked(idx) };
324 let opt = f(val);
325 fail_acc |= opt.is_none() as u64;
326 let result = opt.unwrap_or_default();
327 unsafe { out.get_unchecked_mut(idx).write(result) };
328 }
329 fail_acc != 0
330 }
331
332 let values = self;
333 let len = values.len();
334 assert_eq!(out.len(), len, "out must have the same length as values");
335
336 let chunks_count = len / CHUNK_LEN;
337 let remainder = len % CHUNK_LEN;
338
339 for chunk_idx in 0..chunks_count {
340 let base = chunk_idx * CHUNK_LEN;
341 if chunk(&values, out, &f, base, CHUNK_LEN) {
342 return Err(attribute_failure_no_mask(&values, base, CHUNK_LEN, &f));
343 }
344 }
345 if remainder != 0 {
346 let base = chunks_count * CHUNK_LEN;
347 if chunk(&values, out, &f, base, remainder) {
348 return Err(attribute_failure_no_mask(&values, base, remainder, &f));
349 }
350 }
351 Ok(())
352 }
353}
354
355impl<S: IndexedSource> IndexedSourceExt for S {}
356
357#[cold]
363#[inline(never)]
364fn cold_scan<S>(
365 values: &S,
366 base: usize,
367 chunk_len: usize,
368 lane_fails: impl Fn(usize , S::Item) -> bool,
369) -> usize
370where
371 S: IndexedSource,
372{
373 for bit_idx in 0..chunk_len {
374 let idx = base + bit_idx;
375 let val = unsafe { values.get_unchecked(idx) };
377 if lane_fails(bit_idx, val) {
378 return idx;
379 }
380 }
381 unreachable!("cold_scan called without a failing lane")
382}
383
384#[inline]
387fn attribute_failure_no_mask<S, R, F>(values: &S, base: usize, chunk_len: usize, f: &F) -> usize
388where
389 S: IndexedSource,
390 F: Fn(S::Item) -> Option<R>,
391{
392 cold_scan(values, base, chunk_len, |_bit_idx, val| f(val).is_none())
393}
394
395#[cfg(test)]
396#[allow(clippy::cast_possible_truncation)]
397mod tests {
398 use vortex_buffer::BitBuffer;
399 use vortex_buffer::BitBufferMut;
400
401 use super::*;
402
403 fn write_t<T: Copy>(out: Vec<MaybeUninit<T>>) -> Vec<T> {
404 unsafe { std::mem::transmute(out) }
406 }
407
408 #[test]
409 fn try_map_masked_into_all_ok() {
410 let values: Vec<u64> = (0..200).collect();
411 let mask = BitBuffer::new_set(200);
412 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
413 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
414 (v <= u32::MAX as u64).then_some(v as u32)
415 });
416 assert!(res.is_ok());
417 let got = write_t(out);
418 assert_eq!(got, (0..200u32).collect::<Vec<_>>());
419 }
420
421 #[test]
422 fn try_map_masked_into_overflow_fails() {
423 let mut values: Vec<u64> = (0..200).collect();
424 values[137] = (u32::MAX as u64) + 1;
425 let mask = BitBuffer::new_set(200);
426 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
427 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
428 (v <= u32::MAX as u64).then_some(v as u32)
429 });
430 assert_eq!(res, Err(137));
431 }
432
433 #[test]
434 fn try_map_masked_into_overflow_reports_first_failing_lane() {
435 let mut values: Vec<u64> = (0..200).collect();
436 values[50] = u64::MAX;
437 values[51] = u64::MAX;
438 values[137] = u64::MAX;
439 let mask = BitBuffer::new_set(200);
440 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
441 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
442 (v <= u32::MAX as u64).then_some(v as u32)
443 });
444 assert_eq!(res, Err(50));
445 }
446
447 #[test]
448 fn try_map_masked_into_value_only_closure_filters_null_overflow() {
449 let mut values: Vec<u64> = (0..200).collect();
450 values[5] = u64::MAX;
451 values[42] = u64::MAX;
452 let mask = {
453 let mut m = BitBufferMut::with_capacity(200);
454 for i in 0..200 {
455 m.append(i != 5 && i != 42);
456 }
457 m.freeze()
458 };
459 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
460 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
461 (v <= u32::MAX as u64).then_some(v as u32)
462 });
463 assert!(
464 res.is_ok(),
465 "null-lane overflow should be filtered by the cold path"
466 );
467 }
468
469 #[test]
470 fn try_map_masked_into_value_only_closure_reports_first_valid_failure() {
471 let mut values: Vec<u64> = (0..200).collect();
472 values[5] = u64::MAX;
473 values[42] = u64::MAX;
474 values[77] = u64::MAX;
475 values[100] = u64::MAX;
476 let mask = {
477 let mut m = BitBufferMut::with_capacity(200);
478 for i in 0..200 {
479 m.append(i != 5 && i != 42);
480 }
481 m.freeze()
482 };
483 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
484 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
485 (v <= u32::MAX as u64).then_some(v as u32)
486 });
487 assert_eq!(res, Err(77));
488 }
489
490 #[test]
491 fn try_map_masked_into_null_lane_bypasses_check() {
492 let mut values: Vec<u64> = (0..200).collect();
493 values[5] = u64::MAX;
494 let mask = {
495 let mut m = BitBufferMut::with_capacity(200);
496 for i in 0..200 {
497 m.append(i != 5);
498 }
499 m.freeze()
500 };
501 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
502 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
503 (v <= u32::MAX as u64).then_some(v as u32)
504 });
505 assert!(res.is_ok());
506 let got = write_t(out);
507 assert_eq!(got[5], 0);
508 assert_eq!(got[6], 6);
509 }
510
511 #[test]
512 fn try_map_masked_into_branchful_matches_branchless() {
513 let mut values: Vec<u64> = (0..130).map(|i| i as u64 * 7).collect();
514 values[2] = u64::MAX;
515 values[65] = u32::MAX as u64;
516 let mask = {
517 let mut m = BitBufferMut::with_capacity(130);
518 for i in 0..130 {
519 m.append(!matches!(i, 2 | 17 | 99));
520 }
521 m.freeze()
522 };
523
524 let mut branchless = vec![MaybeUninit::<u32>::uninit(); 130];
525 let mut branchful = vec![MaybeUninit::<u32>::uninit(); 130];
526 values
527 .as_slice()
528 .try_map_masked_into(&mask, &mut branchless, |v| {
529 (v <= u32::MAX as u64).then_some(v as u32)
530 })
531 .unwrap();
532 values
533 .as_slice()
534 .try_map_masked_into(&mask, &mut branchful, |v| u32::try_from(v).ok())
535 .unwrap();
536
537 assert_eq!(write_t(branchful), write_t(branchless));
538 }
539
540 #[test]
541 fn try_map_masked_into_partial_chunk() {
542 let values: Vec<u64> = (0..130).collect();
543 let mask = BitBuffer::new_set(130);
544 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
545 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
546 (v <= u32::MAX as u64).then_some(v as u32)
547 });
548 assert!(res.is_ok());
549 let got = write_t(out);
550 assert_eq!(got.len(), 130);
551 assert_eq!(got[129], 129);
552 }
553
554 #[test]
555 fn try_map_masked_into_sliced_mask_unaligned_offset() {
556 let big = BitBuffer::new_set(256);
557 let mask = big.slice(13..143);
558 assert_eq!(mask.len(), 130);
559
560 let values: Vec<u64> = (0..130).collect();
561 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
562 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
563 (v <= u32::MAX as u64).then_some(v as u32)
564 });
565 assert!(res.is_ok());
566 let got = write_t(out);
567 assert_eq!(got, (0..130u32).collect::<Vec<_>>());
568 }
569
570 #[test]
571 fn try_map_masked_into_sliced_mask_with_overflow() {
572 let big = BitBuffer::new_set(256);
573 let mask = big.slice(13..143);
574 assert_eq!(mask.len(), 130);
575
576 let mut values: Vec<u64> = (0..130).collect();
577 values[77] = u64::MAX;
578 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
579 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
580 (v <= u32::MAX as u64).then_some(v as u32)
581 });
582 assert_eq!(res, Err(77));
583 }
584
585 #[test]
586 fn try_map_masked_into_sliced_mask_null_lanes() {
587 let mut m = BitBufferMut::with_capacity(256);
588 for i in 0..256 {
589 m.append(i % 3 != 0);
590 }
591 let big = m.freeze();
592 let mask = big.slice(13..143);
593 assert_eq!(mask.len(), 130);
594
595 let mut values: Vec<u64> = (0..130).collect();
596 values[2] = u64::MAX;
597 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
598 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
599 (v <= u32::MAX as u64).then_some(v as u32)
600 });
601 assert!(res.is_ok(), "null lane should bypass the range check");
602 }
603
604 #[test]
605 fn map_checked_into_writes_all_lanes_and_reduces_flag() {
606 let mut values: Vec<u64> = (0..130).collect();
607 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
608 let failed = values
609 .as_slice()
610 .map_checked_into(&mut out, |v| (v as u32, v > u32::MAX as u64));
611 assert!(!failed);
612 assert_eq!(write_t(out), (0..130u32).collect::<Vec<_>>());
613
614 values[77] = (u32::MAX as u64) + 1;
615 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
616 let failed = values
617 .as_slice()
618 .map_checked_into(&mut out, |v| (v as u32, v > u32::MAX as u64));
619 assert!(failed);
620 assert_eq!(write_t(out)[76], 76);
622 }
623
624 #[test]
625 fn map_bits_into_packs_full_and_remainder_words() {
626 let values: Vec<u32> = (0..130).collect();
627 let mut words = vec![u64::MAX; 3];
628 values.as_slice().map_bits_into(&mut words, |v| v % 2 == 0);
629
630 for idx in 0..130 {
631 let bit = (words[idx / 64] >> (idx % 64)) & 1;
632 assert_eq!(bit == 1, idx % 2 == 0, "lane {idx}");
633 }
634 assert_eq!(words[2] >> 2, 0);
636 }
637
638 #[test]
639 fn map_bits_into_lane_zip_compare() {
640 use crate::lane_kernels::LaneZip;
641
642 let lhs: Vec<i64> = (0..100).collect();
643 let rhs: Vec<i64> = (0..100).rev().collect();
644 let mut words = vec![0u64; 2];
645 LaneZip::new(lhs.as_slice(), rhs.as_slice()).map_bits_into(&mut words, |(a, b)| a >= b);
646
647 for idx in 0..100 {
648 let bit = (words[idx / 64] >> (idx % 64)) & 1;
649 assert_eq!(bit == 1, lhs[idx] >= rhs[idx], "lane {idx}");
650 }
651 }
652
653 #[test]
654 #[should_panic(expected = "words slice has 1 entries")]
655 fn map_bits_into_words_too_short_panics() {
656 let values: Vec<u32> = (0..65).collect();
657 let mut words = vec![0u64; 1];
658 values.as_slice().map_bits_into(&mut words, |v| v > 0);
659 }
660
661 #[test]
662 fn try_map_masked_into_overflow_in_remainder() {
663 let mut values: Vec<u64> = (0..130).collect();
664 values[129] = (u32::MAX as u64) + 1;
665 let mask = BitBuffer::new_set(130);
666 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
667 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
668 (v <= u32::MAX as u64).then_some(v as u32)
669 });
670 assert_eq!(res, Err(129));
671 }
672}