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