Skip to main content

vortex_compute/lane_kernels/
map_into.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Out-of-place lane kernels: read from an [`IndexedSource`] and write into a
5//! caller-provided `&mut [MaybeUninit<R>]`.
6
7use 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
15/// Extension trait providing out-of-place lane-kernel methods on any [`IndexedSource`].
16///
17/// All methods have default implementations and are inherited via the blanket
18/// `impl<S: IndexedSource> IndexedSourceExt for S` below. Bring the trait into
19/// scope (`use vortex_compute::lane_kernels::IndexedSourceExt;`) to call
20/// them with method syntax: `values.try_map_masked_into(&mask, &mut out, f)`.
21pub trait IndexedSourceExt: IndexedSource + Sized {
22    /// Fallible map with mask-aware error attribution. `f` returns `Option<R>`;
23    /// `None` indicates a per-lane failure (e.g. range overflow on a narrowing cast).
24    ///
25    /// **Null-lane failures are filtered automatically.** The closure is called on
26    /// every lane regardless of validity; if a null lane's stored value causes `f(v)`
27    /// to return `None`, the kernel does *not* propagate that as `Err`. The per-lane
28    /// `is_none()` flags are bit-packed into a `u64` at the lane's position, then
29    /// AND-combined with the chunk's validity bitmap — null-lane bits vanish.
30    ///
31    /// The closure shape is the same as [`try_map_into`] (`FnMut(Item) -> Option<R>`);
32    /// the mask parameter is what makes this kernel mask-aware. Callers that need to
33    /// distinguish null lanes inside the closure (e.g. to short-circuit an expensive
34    /// computation) should construct their own per-lane validity check externally; for
35    /// the common case, the kernel's automatic filter is sufficient.
36    ///
37    /// On failure returns `Err(failing_lane_index)`. Lanes whose `f` returned `None`
38    /// write `R::default()` into `out`, but the contents of `out` must not be relied
39    /// upon when this function returns `Err`.
40    ///
41    /// [`try_map_into`]: IndexedSourceExt::try_map_into
42    ///
43    /// # Panics
44    ///
45    /// Panics if `self.len() != mask.len()` or `out.len() != self.len()`.
46    #[inline]
47    fn try_map_masked_into<R, F>(
48        self,
49        mask: &BitBuffer,
50        out: &mut [MaybeUninit<R>],
51        mut f: F,
52    ) -> Result<(), usize>
53    where
54        R: Copy + Default,
55        F: FnMut(Self::Item) -> Option<R>,
56    {
57        #[inline(always)]
58        fn chunk<S, R, F>(
59            values: &S,
60            out: &mut [MaybeUninit<R>],
61            f: &mut F,
62            src_chunk: u64,
63            base: usize,
64            count: usize,
65        ) -> Option<usize>
66        where
67            S: IndexedSource,
68            R: Copy + Default,
69            F: FnMut(S::Item) -> Option<R>,
70        {
71            let mut fail_bits: u64 = 0;
72            for bit_idx in 0..count {
73                let idx = base + bit_idx;
74                // SAFETY: caller guarantees base + count <= len.
75                let val = unsafe { values.get_unchecked(idx) };
76                let opt = f(val);
77                fail_bits |= (opt.is_none() as u64) << bit_idx;
78                let result = opt.unwrap_or_default();
79                unsafe { out.get_unchecked_mut(idx).write(result) };
80            }
81            let valid_failures = fail_bits & src_chunk;
82            (valid_failures != 0).then_some(base + valid_failures.trailing_zeros() as usize)
83        }
84
85        let values = self;
86        let len = values.len();
87        assert_eq!(len, mask.len(), "values and mask must have the same length");
88        assert_eq!(out.len(), len, "out must have the same length as values");
89
90        let chunks = mask.chunks();
91        let chunks_count = len / 64;
92        let remainder = len % 64;
93
94        for (chunk_idx, src_chunk) in chunks.iter().enumerate() {
95            if let Some(idx) = chunk(&values, out, &mut f, src_chunk, chunk_idx * 64, 64) {
96                return Err(idx);
97            }
98        }
99        if remainder != 0
100            && let Some(idx) = chunk(
101                &values,
102                out,
103                &mut f,
104                chunks.remainder_bits(),
105                chunks_count * 64,
106                remainder,
107            )
108        {
109            return Err(idx);
110        }
111        Ok(())
112    }
113
114    /// Apply `f(value)` lane-by-lane with **no validity awareness at all** — every
115    /// closure invocation is treated as "happened", regardless of whether the lane
116    /// is null. Use this only when the input is known non-nullable.
117    ///
118    /// # Panics
119    ///
120    /// Panics if `out.len() != self.len()`.
121    #[inline]
122    fn map_into<R, F>(self, out: &mut [MaybeUninit<R>], mut f: F)
123    where
124        F: FnMut(Self::Item) -> R,
125    {
126        #[inline(always)]
127        fn chunk<S, R, F>(
128            values: &S,
129            out: &mut [MaybeUninit<R>],
130            f: &mut F,
131            base: usize,
132            count: usize,
133        ) where
134            S: IndexedSource,
135            F: FnMut(S::Item) -> R,
136        {
137            for bit_idx in 0..count {
138                let idx = base + bit_idx;
139                // SAFETY: caller guarantees base + count <= len.
140                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, &mut f, chunk_idx * CHUNK_LEN, CHUNK_LEN);
154        }
155        if remainder != 0 {
156            chunk(&values, out, &mut f, chunks_count * CHUNK_LEN, remainder);
157        }
158    }
159
160    /// Apply the predicate `f(value)` lane-by-lane and bit-pack the results into
161    /// `words`, LSB-first, 64 lanes per `u64`.
162    ///
163    /// This is the kernel shape behind comparison operators: each lane read is an
164    /// independent indexed load (drive two columns via [`LaneZip`]) and the 64
165    /// per-lane booleans of a chunk reduce into a single word with `OR + shift`,
166    /// which the autovectorizer lowers to a vector compare plus movemask.
167    ///
168    /// Words are written with `=` (not `|=`), so `words` need not be
169    /// zero-initialised. Bits at positions `>= self.len()` in the last word are
170    /// written as zero.
171    ///
172    /// Like [`map_into`], this kernel has no validity awareness; pair the packed
173    /// bits with a separately computed validity mask.
174    ///
175    /// [`LaneZip`]: crate::lane_kernels::LaneZip
176    /// [`map_into`]: IndexedSourceExt::map_into
177    ///
178    /// # Panics
179    ///
180    /// Panics if `words.len() < self.len().div_ceil(64)`.
181    #[inline]
182    fn map_bits_into<F>(self, words: &mut [u64], mut f: F)
183    where
184        F: FnMut(Self::Item) -> bool,
185    {
186        #[inline(always)]
187        fn chunk<S, F>(values: &S, f: &mut F, base: usize, count: usize) -> u64
188        where
189            S: IndexedSource,
190            F: FnMut(S::Item) -> bool,
191        {
192            let mut packed: u64 = 0;
193            for bit_idx in 0..count {
194                // SAFETY: caller guarantees base + count <= len.
195                let val = unsafe { values.get_unchecked(base + bit_idx) };
196                packed |= (f(val) as u64) << bit_idx;
197            }
198            packed
199        }
200
201        let values = self;
202        let len = values.len();
203        let num_words = len.div_ceil(64);
204        assert!(
205            words.len() >= num_words,
206            "words slice has {} entries, need at least {num_words}",
207            words.len(),
208        );
209
210        let full = len / 64;
211        let remainder = len % 64;
212
213        for word_idx in 0..full {
214            words[word_idx] = chunk(&values, &mut f, word_idx * 64, 64);
215        }
216        if remainder != 0 {
217            words[full] = chunk(&values, &mut f, full * 64, remainder);
218        }
219    }
220
221    /// Split value/failure map with **no validity awareness at all**: write every lane's value
222    /// unconditionally and OR-reduce its failure evidence into the return.
223    ///
224    /// The fastest checked shape, running at the speed of the unchecked [`map_into`] in exchange
225    /// for reporting only _that_ some lane failed and never exiting early. Re-run the now known
226    /// cold input through [`try_map_into`] or [`try_map_masked_into`] to attribute the failure or
227    /// to drop the null-lane ones. The evidence reduces inside the kernel because a captured `&mut`
228    /// becomes a loop-carried memory dependence that blocks vectorization.
229    ///
230    /// Anything other than [`Default`] means failure, and `bool` is the ordinary `Fail`. Wider
231    /// words exist for operations where deriving a `bool` costs the vectorization it guards.
232    /// **`Fail` must be no wider than `R`**, asserted below, or the reduction rather than the
233    /// operation decides how many lanes fit in a vector.
234    ///
235    /// [`map_into`]: IndexedSourceExt::map_into
236    /// [`try_map_into`]: IndexedSourceExt::try_map_into
237    /// [`try_map_masked_into`]: IndexedSourceExt::try_map_masked_into
238    ///
239    /// # Panics
240    ///
241    /// Panics if `out.len() != self.len()`.
242    #[inline]
243    fn map_checked_into<R, Fail, Apply>(self, out: &mut [MaybeUninit<R>], mut apply: Apply) -> Fail
244    where
245        Fail: Copy + Default + BitOrAssign,
246        Apply: FnMut(Self::Item) -> (R, Fail),
247    {
248        const {
249            assert!(
250                size_of::<Fail>() <= size_of::<R>(),
251                "failure evidence must be no wider than the value, or it bounds the vector width"
252            )
253        };
254
255        let values = self;
256        let len = values.len();
257        assert_eq!(out.len(), len, "out must have the same length as values");
258
259        let mut failed = Fail::default();
260        for idx in 0..len {
261            // SAFETY: idx < len by the loop bound, and out.len() == len.
262            let val = unsafe { values.get_unchecked(idx) };
263
264            let (result, failure) = apply(val);
265            failed |= failure;
266
267            // SAFETY: idx < len == out.len().
268            unsafe { out.get_unchecked_mut(idx).write(result) };
269        }
270        failed
271    }
272
273    /// Fallible map with **no validity awareness at all** — every `None` returned
274    /// by the closure is treated as a failure, even at null lanes.
275    ///
276    /// # Use this only for non-nullable inputs.
277    ///
278    /// For nullable inputs with a fallible closure, use [`try_map_masked_into`] —
279    /// it has the same value-only closure shape (and the same perf win) but
280    /// **correctly suppresses null-lane failures** via per-chunk
281    /// `fail_bits & mask_chunk`.
282    ///
283    /// Using this kernel on a nullable input where a null lane's stored value
284    /// would cause `f` to return `None` will produce a spurious `Err`. This is a
285    /// correctness footgun on purpose — the name and this doc are how the API
286    /// signals "you must know your input has no nulls."
287    ///
288    /// On failure returns `Err(failing_lane_index)`.
289    ///
290    /// [`try_map_masked_into`]: IndexedSourceExt::try_map_masked_into
291    ///
292    /// # Panics
293    ///
294    /// Panics if `out.len() != self.len()`.
295    #[inline]
296    fn try_map_into<R, F>(self, out: &mut [MaybeUninit<R>], mut f: F) -> Result<(), usize>
297    where
298        R: Copy + Default,
299        F: FnMut(Self::Item) -> Option<R>,
300    {
301        /// Returns `true` if any lane in `[base, base+count)` failed (OR-reduced);
302        /// the cold attribution path is called at the kernel level so it can be
303        /// inlined separately for full vs remainder.
304        #[inline(always)]
305        fn chunk<S, R, F>(
306            values: &S,
307            out: &mut [MaybeUninit<R>],
308            f: &mut F,
309            base: usize,
310            count: usize,
311        ) -> bool
312        where
313            S: IndexedSource,
314            R: Copy + Default,
315            F: FnMut(S::Item) -> Option<R>,
316        {
317            let mut fail_acc: u64 = 0;
318            for bit_idx in 0..count {
319                let idx = base + bit_idx;
320                // SAFETY: caller guarantees base + count <= len.
321                let val = unsafe { values.get_unchecked(idx) };
322                let opt = f(val);
323                fail_acc |= opt.is_none() as u64;
324                let result = opt.unwrap_or_default();
325                unsafe { out.get_unchecked_mut(idx).write(result) };
326            }
327            fail_acc != 0
328        }
329
330        let values = self;
331        let len = values.len();
332        assert_eq!(out.len(), len, "out must have the same length as values");
333
334        let chunks_count = len / CHUNK_LEN;
335        let remainder = len % CHUNK_LEN;
336
337        for chunk_idx in 0..chunks_count {
338            let base = chunk_idx * CHUNK_LEN;
339            if chunk(&values, out, &mut f, base, CHUNK_LEN) {
340                return Err(attribute_failure_no_mask(&values, base, CHUNK_LEN, &mut f));
341            }
342        }
343        if remainder != 0 {
344            let base = chunks_count * CHUNK_LEN;
345            if chunk(&values, out, &mut f, base, remainder) {
346                return Err(attribute_failure_no_mask(&values, base, remainder, &mut f));
347            }
348        }
349        Ok(())
350    }
351}
352
353impl<S: IndexedSource> IndexedSourceExt for S {}
354
355/// Shared cold scan: walks a chunk, returns the first lane index where
356/// `lane_fails(bit_idx, value)` returns `true`. Used by
357/// [`attribute_failure_no_mask`].
358///
359/// Caller guarantees `base + chunk_len <= values.len()`.
360#[cold]
361#[inline(never)]
362fn cold_scan<S>(
363    values: &S,
364    base: usize,
365    chunk_len: usize,
366    mut lane_fails: impl FnMut(usize /* bit_idx */, S::Item) -> bool,
367) -> usize
368where
369    S: IndexedSource,
370{
371    for bit_idx in 0..chunk_len {
372        let idx = base + bit_idx;
373        // SAFETY: caller guarantees idx < values.len().
374        let val = unsafe { values.get_unchecked(idx) };
375        if lane_fails(bit_idx, val) {
376            return idx;
377        }
378    }
379    unreachable!("cold_scan called without a failing lane")
380}
381
382/// Cold attribution for the no-mask variant. Replays `f` over the chunk to find
383/// the first lane that returns `None`.
384#[inline]
385fn attribute_failure_no_mask<S, R, F>(values: &S, base: usize, chunk_len: usize, f: &mut F) -> usize
386where
387    S: IndexedSource,
388    F: FnMut(S::Item) -> Option<R>,
389{
390    cold_scan(values, base, chunk_len, |_bit_idx, val| f(val).is_none())
391}
392
393#[cfg(test)]
394#[allow(clippy::cast_possible_truncation)]
395mod tests {
396    use vortex_buffer::BitBuffer;
397    use vortex_buffer::BitBufferMut;
398
399    use super::*;
400
401    fn write_t<T: Copy>(out: Vec<MaybeUninit<T>>) -> Vec<T> {
402        // SAFETY: tests always fully initialize the buffer.
403        unsafe { std::mem::transmute(out) }
404    }
405
406    #[test]
407    fn try_map_masked_into_all_ok() {
408        let values: Vec<u64> = (0..200).collect();
409        let mask = BitBuffer::new_set(200);
410        let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
411        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
412            (v <= u32::MAX as u64).then_some(v as u32)
413        });
414        assert!(res.is_ok());
415        let got = write_t(out);
416        assert_eq!(got, (0..200u32).collect::<Vec<_>>());
417    }
418
419    #[test]
420    fn try_map_masked_into_overflow_fails() {
421        let mut values: Vec<u64> = (0..200).collect();
422        values[137] = (u32::MAX as u64) + 1;
423        let mask = BitBuffer::new_set(200);
424        let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
425        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
426            (v <= u32::MAX as u64).then_some(v as u32)
427        });
428        assert_eq!(res, Err(137));
429    }
430
431    #[test]
432    fn try_map_masked_into_overflow_reports_first_failing_lane() {
433        let mut values: Vec<u64> = (0..200).collect();
434        values[50] = u64::MAX;
435        values[51] = u64::MAX;
436        values[137] = u64::MAX;
437        let mask = BitBuffer::new_set(200);
438        let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
439        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
440            (v <= u32::MAX as u64).then_some(v as u32)
441        });
442        assert_eq!(res, Err(50));
443    }
444
445    #[test]
446    fn try_map_masked_into_value_only_closure_filters_null_overflow() {
447        let mut values: Vec<u64> = (0..200).collect();
448        values[5] = u64::MAX;
449        values[42] = u64::MAX;
450        let mask = {
451            let mut m = BitBufferMut::with_capacity(200);
452            for i in 0..200 {
453                m.append(i != 5 && i != 42);
454            }
455            m.freeze()
456        };
457        let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
458        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
459            (v <= u32::MAX as u64).then_some(v as u32)
460        });
461        assert!(
462            res.is_ok(),
463            "null-lane overflow should be filtered by the cold path"
464        );
465    }
466
467    #[test]
468    fn try_map_masked_into_value_only_closure_reports_first_valid_failure() {
469        let mut values: Vec<u64> = (0..200).collect();
470        values[5] = u64::MAX;
471        values[42] = u64::MAX;
472        values[77] = u64::MAX;
473        values[100] = u64::MAX;
474        let mask = {
475            let mut m = BitBufferMut::with_capacity(200);
476            for i in 0..200 {
477                m.append(i != 5 && i != 42);
478            }
479            m.freeze()
480        };
481        let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
482        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
483            (v <= u32::MAX as u64).then_some(v as u32)
484        });
485        assert_eq!(res, Err(77));
486    }
487
488    #[test]
489    fn try_map_masked_into_null_lane_bypasses_check() {
490        let mut values: Vec<u64> = (0..200).collect();
491        values[5] = u64::MAX;
492        let mask = {
493            let mut m = BitBufferMut::with_capacity(200);
494            for i in 0..200 {
495                m.append(i != 5);
496            }
497            m.freeze()
498        };
499        let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
500        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
501            (v <= u32::MAX as u64).then_some(v as u32)
502        });
503        assert!(res.is_ok());
504        let got = write_t(out);
505        assert_eq!(got[5], 0);
506        assert_eq!(got[6], 6);
507    }
508
509    #[test]
510    fn try_map_masked_into_branchful_matches_branchless() {
511        let mut values: Vec<u64> = (0..130).map(|i| i as u64 * 7).collect();
512        values[2] = u64::MAX;
513        values[65] = u32::MAX as u64;
514        let mask = {
515            let mut m = BitBufferMut::with_capacity(130);
516            for i in 0..130 {
517                m.append(!matches!(i, 2 | 17 | 99));
518            }
519            m.freeze()
520        };
521
522        let mut branchless = vec![MaybeUninit::<u32>::uninit(); 130];
523        let mut branchful = vec![MaybeUninit::<u32>::uninit(); 130];
524        values
525            .as_slice()
526            .try_map_masked_into(&mask, &mut branchless, |v| {
527                (v <= u32::MAX as u64).then_some(v as u32)
528            })
529            .unwrap();
530        values
531            .as_slice()
532            .try_map_masked_into(&mask, &mut branchful, |v| u32::try_from(v).ok())
533            .unwrap();
534
535        assert_eq!(write_t(branchful), write_t(branchless));
536    }
537
538    #[test]
539    fn try_map_masked_into_partial_chunk() {
540        let values: Vec<u64> = (0..130).collect();
541        let mask = BitBuffer::new_set(130);
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());
547        let got = write_t(out);
548        assert_eq!(got.len(), 130);
549        assert_eq!(got[129], 129);
550    }
551
552    #[test]
553    fn try_map_masked_into_sliced_mask_unaligned_offset() {
554        let big = BitBuffer::new_set(256);
555        let mask = big.slice(13..143);
556        assert_eq!(mask.len(), 130);
557
558        let values: Vec<u64> = (0..130).collect();
559        let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
560        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
561            (v <= u32::MAX as u64).then_some(v as u32)
562        });
563        assert!(res.is_ok());
564        let got = write_t(out);
565        assert_eq!(got, (0..130u32).collect::<Vec<_>>());
566    }
567
568    #[test]
569    fn try_map_masked_into_sliced_mask_with_overflow() {
570        let big = BitBuffer::new_set(256);
571        let mask = big.slice(13..143);
572        assert_eq!(mask.len(), 130);
573
574        let mut values: Vec<u64> = (0..130).collect();
575        values[77] = u64::MAX;
576        let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
577        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
578            (v <= u32::MAX as u64).then_some(v as u32)
579        });
580        assert_eq!(res, Err(77));
581    }
582
583    #[test]
584    fn try_map_masked_into_sliced_mask_null_lanes() {
585        let mut m = BitBufferMut::with_capacity(256);
586        for i in 0..256 {
587            m.append(i % 3 != 0);
588        }
589        let big = m.freeze();
590        let mask = big.slice(13..143);
591        assert_eq!(mask.len(), 130);
592
593        let mut values: Vec<u64> = (0..130).collect();
594        values[2] = u64::MAX;
595        let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
596        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
597            (v <= u32::MAX as u64).then_some(v as u32)
598        });
599        assert!(res.is_ok(), "null lane should bypass the range check");
600    }
601
602    #[test]
603    fn map_checked_into_writes_all_lanes_and_reduces_flag() {
604        let mut values: Vec<u64> = (0..130).collect();
605        let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
606        let failed = values
607            .as_slice()
608            .map_checked_into(&mut out, |v| (v as u32, v > u32::MAX as u64));
609        assert!(!failed);
610        assert_eq!(write_t(out), (0..130u32).collect::<Vec<_>>());
611
612        values[77] = (u32::MAX as u64) + 1;
613        let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
614        let failed = values
615            .as_slice()
616            .map_checked_into(&mut out, |v| (v as u32, v > u32::MAX as u64));
617        assert!(failed);
618        // Failing lanes still write their (wrapped) value.
619        assert_eq!(write_t(out)[76], 76);
620    }
621
622    #[test]
623    fn map_bits_into_packs_full_and_remainder_words() {
624        let values: Vec<u32> = (0..130).collect();
625        let mut words = vec![u64::MAX; 3];
626        values.as_slice().map_bits_into(&mut words, |v| v % 2 == 0);
627
628        for idx in 0..130 {
629            let bit = (words[idx / 64] >> (idx % 64)) & 1;
630            assert_eq!(bit == 1, idx % 2 == 0, "lane {idx}");
631        }
632        // Bits past `len` in the remainder word must be written as zero.
633        assert_eq!(words[2] >> 2, 0);
634    }
635
636    #[test]
637    fn map_bits_into_lane_zip_compare() {
638        use crate::lane_kernels::LaneZip;
639
640        let lhs: Vec<i64> = (0..100).collect();
641        let rhs: Vec<i64> = (0..100).rev().collect();
642        let mut words = vec![0u64; 2];
643        LaneZip::new(lhs.as_slice(), rhs.as_slice()).map_bits_into(&mut words, |(a, b)| a >= b);
644
645        for idx in 0..100 {
646            let bit = (words[idx / 64] >> (idx % 64)) & 1;
647            assert_eq!(bit == 1, lhs[idx] >= rhs[idx], "lane {idx}");
648        }
649    }
650
651    #[test]
652    #[should_panic(expected = "words slice has 1 entries")]
653    fn map_bits_into_words_too_short_panics() {
654        let values: Vec<u32> = (0..65).collect();
655        let mut words = vec![0u64; 1];
656        values.as_slice().map_bits_into(&mut words, |v| v > 0);
657    }
658
659    #[test]
660    fn try_map_masked_into_overflow_in_remainder() {
661        let mut values: Vec<u64> = (0..130).collect();
662        values[129] = (u32::MAX as u64) + 1;
663        let mask = BitBuffer::new_set(130);
664        let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
665        let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
666            (v <= u32::MAX as u64).then_some(v as u32)
667        });
668        assert_eq!(res, Err(129));
669    }
670}