1use std::mem::MaybeUninit;
8
9use vortex_buffer::BitBuffer;
10
11use crate::lane_kernels::CHUNK_LEN;
12use crate::lane_kernels::source::IndexedSource;
13
14pub trait IndexedSourceExt: IndexedSource + Sized {
21 #[inline]
46 fn try_map_masked_into<R, F>(
47 self,
48 mask: &BitBuffer,
49 out: &mut [MaybeUninit<R>],
50 mut f: F,
51 ) -> Result<(), usize>
52 where
53 R: Copy + Default,
54 F: FnMut(Self::Item) -> Option<R>,
55 {
56 #[inline(always)]
57 fn chunk<S, R, F>(
58 values: &S,
59 out: &mut [MaybeUninit<R>],
60 f: &mut F,
61 src_chunk: u64,
62 base: usize,
63 count: usize,
64 ) -> Option<usize>
65 where
66 S: IndexedSource,
67 R: Copy + Default,
68 F: FnMut(S::Item) -> Option<R>,
69 {
70 let mut fail_bits: u64 = 0;
71 for bit_idx in 0..count {
72 let idx = base + bit_idx;
73 let val = unsafe { values.get_unchecked(idx) };
75 let opt = f(val);
76 fail_bits |= (opt.is_none() as u64) << bit_idx;
77 let result = opt.unwrap_or_default();
78 unsafe { out.get_unchecked_mut(idx).write(result) };
79 }
80 let valid_failures = fail_bits & src_chunk;
81 (valid_failures != 0).then_some(base + valid_failures.trailing_zeros() as usize)
82 }
83
84 let values = self;
85 let len = values.len();
86 assert_eq!(len, mask.len(), "values and mask must have the same length");
87 assert_eq!(out.len(), len, "out must have the same length as values");
88
89 let chunks = mask.chunks();
90 let chunks_count = len / 64;
91 let remainder = len % 64;
92
93 for (chunk_idx, src_chunk) in chunks.iter().enumerate() {
94 if let Some(idx) = chunk(&values, out, &mut f, src_chunk, chunk_idx * 64, 64) {
95 return Err(idx);
96 }
97 }
98 if remainder != 0
99 && let Some(idx) = chunk(
100 &values,
101 out,
102 &mut f,
103 chunks.remainder_bits(),
104 chunks_count * 64,
105 remainder,
106 )
107 {
108 return Err(idx);
109 }
110 Ok(())
111 }
112
113 #[inline]
121 fn map_into<R, F>(self, out: &mut [MaybeUninit<R>], mut f: F)
122 where
123 F: FnMut(Self::Item) -> R,
124 {
125 #[inline(always)]
126 fn chunk<S, R, F>(
127 values: &S,
128 out: &mut [MaybeUninit<R>],
129 f: &mut F,
130 base: usize,
131 count: usize,
132 ) where
133 S: IndexedSource,
134 F: FnMut(S::Item) -> R,
135 {
136 for bit_idx in 0..count {
137 let idx = base + bit_idx;
138 let val = unsafe { values.get_unchecked(idx) };
140 unsafe { out.get_unchecked_mut(idx).write(f(val)) };
141 }
142 }
143
144 let values = self;
145 let len = values.len();
146 assert_eq!(out.len(), len, "out must have the same length as values");
147
148 let chunks_count = len / CHUNK_LEN;
149 let remainder = len % CHUNK_LEN;
150
151 for chunk_idx in 0..chunks_count {
152 chunk(&values, out, &mut f, chunk_idx * CHUNK_LEN, CHUNK_LEN);
153 }
154 if remainder != 0 {
155 chunk(&values, out, &mut f, chunks_count * CHUNK_LEN, remainder);
156 }
157 }
158
159 #[inline]
181 fn map_bits_into<F>(self, words: &mut [u64], mut f: F)
182 where
183 F: FnMut(Self::Item) -> bool,
184 {
185 #[inline(always)]
186 fn chunk<S, F>(values: &S, f: &mut F, base: usize, count: usize) -> u64
187 where
188 S: IndexedSource,
189 F: FnMut(S::Item) -> bool,
190 {
191 let mut packed: u64 = 0;
192 for bit_idx in 0..count {
193 let val = unsafe { values.get_unchecked(base + bit_idx) };
195 packed |= (f(val) as u64) << bit_idx;
196 }
197 packed
198 }
199
200 let values = self;
201 let len = values.len();
202 let num_words = len.div_ceil(64);
203 assert!(
204 words.len() >= num_words,
205 "words slice has {} entries, need at least {num_words}",
206 words.len(),
207 );
208
209 let full = len / 64;
210 let remainder = len % 64;
211
212 for word_idx in 0..full {
213 words[word_idx] = chunk(&values, &mut f, word_idx * 64, 64);
214 }
215 if remainder != 0 {
216 words[full] = chunk(&values, &mut f, full * 64, remainder);
217 }
218 }
219
220 #[inline]
243 fn try_map_into<R, F>(self, out: &mut [MaybeUninit<R>], mut f: F) -> Result<(), usize>
244 where
245 R: Copy + Default,
246 F: FnMut(Self::Item) -> Option<R>,
247 {
248 #[inline(always)]
252 fn chunk<S, R, F>(
253 values: &S,
254 out: &mut [MaybeUninit<R>],
255 f: &mut F,
256 base: usize,
257 count: usize,
258 ) -> bool
259 where
260 S: IndexedSource,
261 R: Copy + Default,
262 F: FnMut(S::Item) -> Option<R>,
263 {
264 let mut fail_acc: u64 = 0;
265 for bit_idx in 0..count {
266 let idx = base + bit_idx;
267 let val = unsafe { values.get_unchecked(idx) };
269 let opt = f(val);
270 fail_acc |= opt.is_none() as u64;
271 let result = opt.unwrap_or_default();
272 unsafe { out.get_unchecked_mut(idx).write(result) };
273 }
274 fail_acc != 0
275 }
276
277 let values = self;
278 let len = values.len();
279 assert_eq!(out.len(), len, "out must have the same length as values");
280
281 let chunks_count = len / CHUNK_LEN;
282 let remainder = len % CHUNK_LEN;
283
284 for chunk_idx in 0..chunks_count {
285 let base = chunk_idx * CHUNK_LEN;
286 if chunk(&values, out, &mut f, base, CHUNK_LEN) {
287 return Err(attribute_failure_no_mask(&values, base, CHUNK_LEN, &mut f));
288 }
289 }
290 if remainder != 0 {
291 let base = chunks_count * CHUNK_LEN;
292 if chunk(&values, out, &mut f, base, remainder) {
293 return Err(attribute_failure_no_mask(&values, base, remainder, &mut f));
294 }
295 }
296 Ok(())
297 }
298}
299
300impl<S: IndexedSource> IndexedSourceExt for S {}
301
302#[cold]
308#[inline(never)]
309fn cold_scan<S>(
310 values: &S,
311 base: usize,
312 chunk_len: usize,
313 mut lane_fails: impl FnMut(usize , S::Item) -> bool,
314) -> usize
315where
316 S: IndexedSource,
317{
318 for bit_idx in 0..chunk_len {
319 let idx = base + bit_idx;
320 let val = unsafe { values.get_unchecked(idx) };
322 if lane_fails(bit_idx, val) {
323 return idx;
324 }
325 }
326 unreachable!("cold_scan called without a failing lane")
327}
328
329#[inline]
332fn attribute_failure_no_mask<S, R, F>(values: &S, base: usize, chunk_len: usize, f: &mut F) -> usize
333where
334 S: IndexedSource,
335 F: FnMut(S::Item) -> Option<R>,
336{
337 cold_scan(values, base, chunk_len, |_bit_idx, val| f(val).is_none())
338}
339
340#[cfg(test)]
341#[allow(clippy::cast_possible_truncation)]
342mod tests {
343 use vortex_buffer::BitBuffer;
344 use vortex_buffer::BitBufferMut;
345
346 use super::*;
347
348 fn write_t<T: Copy>(out: Vec<MaybeUninit<T>>) -> Vec<T> {
349 unsafe { std::mem::transmute(out) }
351 }
352
353 #[test]
354 fn try_map_masked_into_all_ok() {
355 let values: Vec<u64> = (0..200).collect();
356 let mask = BitBuffer::new_set(200);
357 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
358 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
359 (v <= u32::MAX as u64).then_some(v as u32)
360 });
361 assert!(res.is_ok());
362 let got = write_t(out);
363 assert_eq!(got, (0..200u32).collect::<Vec<_>>());
364 }
365
366 #[test]
367 fn try_map_masked_into_overflow_fails() {
368 let mut values: Vec<u64> = (0..200).collect();
369 values[137] = (u32::MAX as u64) + 1;
370 let mask = BitBuffer::new_set(200);
371 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
372 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
373 (v <= u32::MAX as u64).then_some(v as u32)
374 });
375 assert_eq!(res, Err(137));
376 }
377
378 #[test]
379 fn try_map_masked_into_overflow_reports_first_failing_lane() {
380 let mut values: Vec<u64> = (0..200).collect();
381 values[50] = u64::MAX;
382 values[51] = u64::MAX;
383 values[137] = u64::MAX;
384 let mask = BitBuffer::new_set(200);
385 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
386 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
387 (v <= u32::MAX as u64).then_some(v as u32)
388 });
389 assert_eq!(res, Err(50));
390 }
391
392 #[test]
393 fn try_map_masked_into_value_only_closure_filters_null_overflow() {
394 let mut values: Vec<u64> = (0..200).collect();
395 values[5] = u64::MAX;
396 values[42] = u64::MAX;
397 let mask = {
398 let mut m = BitBufferMut::with_capacity(200);
399 for i in 0..200 {
400 m.append(i != 5 && i != 42);
401 }
402 m.freeze()
403 };
404 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
405 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
406 (v <= u32::MAX as u64).then_some(v as u32)
407 });
408 assert!(
409 res.is_ok(),
410 "null-lane overflow should be filtered by the cold path"
411 );
412 }
413
414 #[test]
415 fn try_map_masked_into_value_only_closure_reports_first_valid_failure() {
416 let mut values: Vec<u64> = (0..200).collect();
417 values[5] = u64::MAX;
418 values[42] = u64::MAX;
419 values[77] = u64::MAX;
420 values[100] = u64::MAX;
421 let mask = {
422 let mut m = BitBufferMut::with_capacity(200);
423 for i in 0..200 {
424 m.append(i != 5 && i != 42);
425 }
426 m.freeze()
427 };
428 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
429 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
430 (v <= u32::MAX as u64).then_some(v as u32)
431 });
432 assert_eq!(res, Err(77));
433 }
434
435 #[test]
436 fn try_map_masked_into_null_lane_bypasses_check() {
437 let mut values: Vec<u64> = (0..200).collect();
438 values[5] = u64::MAX;
439 let mask = {
440 let mut m = BitBufferMut::with_capacity(200);
441 for i in 0..200 {
442 m.append(i != 5);
443 }
444 m.freeze()
445 };
446 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
447 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
448 (v <= u32::MAX as u64).then_some(v as u32)
449 });
450 assert!(res.is_ok());
451 let got = write_t(out);
452 assert_eq!(got[5], 0);
453 assert_eq!(got[6], 6);
454 }
455
456 #[test]
457 fn try_map_masked_into_branchful_matches_branchless() {
458 let mut values: Vec<u64> = (0..130).map(|i| i as u64 * 7).collect();
459 values[2] = u64::MAX;
460 values[65] = u32::MAX as u64;
461 let mask = {
462 let mut m = BitBufferMut::with_capacity(130);
463 for i in 0..130 {
464 m.append(!matches!(i, 2 | 17 | 99));
465 }
466 m.freeze()
467 };
468
469 let mut branchless = vec![MaybeUninit::<u32>::uninit(); 130];
470 let mut branchful = vec![MaybeUninit::<u32>::uninit(); 130];
471 values
472 .as_slice()
473 .try_map_masked_into(&mask, &mut branchless, |v| {
474 (v <= u32::MAX as u64).then_some(v as u32)
475 })
476 .unwrap();
477 values
478 .as_slice()
479 .try_map_masked_into(&mask, &mut branchful, |v| u32::try_from(v).ok())
480 .unwrap();
481
482 assert_eq!(write_t(branchful), write_t(branchless));
483 }
484
485 #[test]
486 fn try_map_masked_into_partial_chunk() {
487 let values: Vec<u64> = (0..130).collect();
488 let mask = BitBuffer::new_set(130);
489 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
490 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
491 (v <= u32::MAX as u64).then_some(v as u32)
492 });
493 assert!(res.is_ok());
494 let got = write_t(out);
495 assert_eq!(got.len(), 130);
496 assert_eq!(got[129], 129);
497 }
498
499 #[test]
500 fn try_map_masked_into_sliced_mask_unaligned_offset() {
501 let big = BitBuffer::new_set(256);
502 let mask = big.slice(13..143);
503 assert_eq!(mask.len(), 130);
504
505 let values: Vec<u64> = (0..130).collect();
506 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
507 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
508 (v <= u32::MAX as u64).then_some(v as u32)
509 });
510 assert!(res.is_ok());
511 let got = write_t(out);
512 assert_eq!(got, (0..130u32).collect::<Vec<_>>());
513 }
514
515 #[test]
516 fn try_map_masked_into_sliced_mask_with_overflow() {
517 let big = BitBuffer::new_set(256);
518 let mask = big.slice(13..143);
519 assert_eq!(mask.len(), 130);
520
521 let mut values: Vec<u64> = (0..130).collect();
522 values[77] = u64::MAX;
523 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
524 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
525 (v <= u32::MAX as u64).then_some(v as u32)
526 });
527 assert_eq!(res, Err(77));
528 }
529
530 #[test]
531 fn try_map_masked_into_sliced_mask_null_lanes() {
532 let mut m = BitBufferMut::with_capacity(256);
533 for i in 0..256 {
534 m.append(i % 3 != 0);
535 }
536 let big = m.freeze();
537 let mask = big.slice(13..143);
538 assert_eq!(mask.len(), 130);
539
540 let mut values: Vec<u64> = (0..130).collect();
541 values[2] = u64::MAX;
542 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
543 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
544 (v <= u32::MAX as u64).then_some(v as u32)
545 });
546 assert!(res.is_ok(), "null lane should bypass the range check");
547 }
548
549 #[test]
550 fn map_bits_into_packs_full_and_remainder_words() {
551 let values: Vec<u32> = (0..130).collect();
552 let mut words = vec![u64::MAX; 3];
553 values.as_slice().map_bits_into(&mut words, |v| v % 2 == 0);
554
555 for idx in 0..130 {
556 let bit = (words[idx / 64] >> (idx % 64)) & 1;
557 assert_eq!(bit == 1, idx % 2 == 0, "lane {idx}");
558 }
559 assert_eq!(words[2] >> 2, 0);
561 }
562
563 #[test]
564 fn map_bits_into_lane_zip_compare() {
565 use crate::lane_kernels::LaneZip;
566
567 let lhs: Vec<i64> = (0..100).collect();
568 let rhs: Vec<i64> = (0..100).rev().collect();
569 let mut words = vec![0u64; 2];
570 LaneZip::new(lhs.as_slice(), rhs.as_slice()).map_bits_into(&mut words, |(a, b)| a >= b);
571
572 for idx in 0..100 {
573 let bit = (words[idx / 64] >> (idx % 64)) & 1;
574 assert_eq!(bit == 1, lhs[idx] >= rhs[idx], "lane {idx}");
575 }
576 }
577
578 #[test]
579 #[should_panic(expected = "words slice has 1 entries")]
580 fn map_bits_into_words_too_short_panics() {
581 let values: Vec<u32> = (0..65).collect();
582 let mut words = vec![0u64; 1];
583 values.as_slice().map_bits_into(&mut words, |v| v > 0);
584 }
585
586 #[test]
587 fn try_map_masked_into_overflow_in_remainder() {
588 let mut values: Vec<u64> = (0..130).collect();
589 values[129] = (u32::MAX as u64) + 1;
590 let mask = BitBuffer::new_set(130);
591 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
592 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
593 (v <= u32::MAX as u64).then_some(v as u32)
594 });
595 assert_eq!(res, Err(129));
596 }
597}