use std::mem::MaybeUninit;
use vortex_buffer::BitBuffer;
use crate::lane_kernels::CHUNK_LEN;
use crate::lane_kernels::source::IndexedSource;
pub trait IndexedSourceExt: IndexedSource + Sized {
#[inline]
fn try_map_masked_into<R, F>(
self,
mask: &BitBuffer,
out: &mut [MaybeUninit<R>],
mut f: F,
) -> Result<(), usize>
where
R: Copy + Default,
F: FnMut(Self::Item) -> Option<R>,
{
#[inline(always)]
fn chunk<S, R, F>(
values: &S,
out: &mut [MaybeUninit<R>],
f: &mut F,
src_chunk: u64,
base: usize,
count: usize,
) -> Option<usize>
where
S: IndexedSource,
R: Copy + Default,
F: FnMut(S::Item) -> Option<R>,
{
let mut fail_bits: u64 = 0;
for bit_idx in 0..count {
let idx = base + bit_idx;
let val = unsafe { values.get_unchecked(idx) };
let opt = f(val);
fail_bits |= (opt.is_none() as u64) << bit_idx;
let result = opt.unwrap_or_default();
unsafe { out.get_unchecked_mut(idx).write(result) };
}
let valid_failures = fail_bits & src_chunk;
(valid_failures != 0).then_some(base + valid_failures.trailing_zeros() as usize)
}
let values = self;
let len = values.len();
assert_eq!(len, mask.len(), "values and mask must have the same length");
assert_eq!(out.len(), len, "out must have the same length as values");
let chunks = mask.chunks();
let chunks_count = len / 64;
let remainder = len % 64;
for (chunk_idx, src_chunk) in chunks.iter().enumerate() {
if let Some(idx) = chunk(&values, out, &mut f, src_chunk, chunk_idx * 64, 64) {
return Err(idx);
}
}
if remainder != 0
&& let Some(idx) = chunk(
&values,
out,
&mut f,
chunks.remainder_bits(),
chunks_count * 64,
remainder,
)
{
return Err(idx);
}
Ok(())
}
#[inline]
fn map_into<R, F>(self, out: &mut [MaybeUninit<R>], mut f: F)
where
F: FnMut(Self::Item) -> R,
{
#[inline(always)]
fn chunk<S, R, F>(
values: &S,
out: &mut [MaybeUninit<R>],
f: &mut F,
base: usize,
count: usize,
) where
S: IndexedSource,
F: FnMut(S::Item) -> R,
{
for bit_idx in 0..count {
let idx = base + bit_idx;
let val = unsafe { values.get_unchecked(idx) };
unsafe { out.get_unchecked_mut(idx).write(f(val)) };
}
}
let values = self;
let len = values.len();
assert_eq!(out.len(), len, "out must have the same length as values");
let chunks_count = len / CHUNK_LEN;
let remainder = len % CHUNK_LEN;
for chunk_idx in 0..chunks_count {
chunk(&values, out, &mut f, chunk_idx * CHUNK_LEN, CHUNK_LEN);
}
if remainder != 0 {
chunk(&values, out, &mut f, chunks_count * CHUNK_LEN, remainder);
}
}
#[inline]
fn map_bits_into<F>(self, words: &mut [u64], mut f: F)
where
F: FnMut(Self::Item) -> bool,
{
#[inline(always)]
fn chunk<S, F>(values: &S, f: &mut F, base: usize, count: usize) -> u64
where
S: IndexedSource,
F: FnMut(S::Item) -> bool,
{
let mut packed: u64 = 0;
for bit_idx in 0..count {
let val = unsafe { values.get_unchecked(base + bit_idx) };
packed |= (f(val) as u64) << bit_idx;
}
packed
}
let values = self;
let len = values.len();
let num_words = len.div_ceil(64);
assert!(
words.len() >= num_words,
"words slice has {} entries, need at least {num_words}",
words.len(),
);
let full = len / 64;
let remainder = len % 64;
for word_idx in 0..full {
words[word_idx] = chunk(&values, &mut f, word_idx * 64, 64);
}
if remainder != 0 {
words[full] = chunk(&values, &mut f, full * 64, remainder);
}
}
#[inline]
fn try_map_into<R, F>(self, out: &mut [MaybeUninit<R>], mut f: F) -> Result<(), usize>
where
R: Copy + Default,
F: FnMut(Self::Item) -> Option<R>,
{
#[inline(always)]
fn chunk<S, R, F>(
values: &S,
out: &mut [MaybeUninit<R>],
f: &mut F,
base: usize,
count: usize,
) -> bool
where
S: IndexedSource,
R: Copy + Default,
F: FnMut(S::Item) -> Option<R>,
{
let mut fail_acc: u64 = 0;
for bit_idx in 0..count {
let idx = base + bit_idx;
let val = unsafe { values.get_unchecked(idx) };
let opt = f(val);
fail_acc |= opt.is_none() as u64;
let result = opt.unwrap_or_default();
unsafe { out.get_unchecked_mut(idx).write(result) };
}
fail_acc != 0
}
let values = self;
let len = values.len();
assert_eq!(out.len(), len, "out must have the same length as values");
let chunks_count = len / CHUNK_LEN;
let remainder = len % CHUNK_LEN;
for chunk_idx in 0..chunks_count {
let base = chunk_idx * CHUNK_LEN;
if chunk(&values, out, &mut f, base, CHUNK_LEN) {
return Err(attribute_failure_no_mask(&values, base, CHUNK_LEN, &mut f));
}
}
if remainder != 0 {
let base = chunks_count * CHUNK_LEN;
if chunk(&values, out, &mut f, base, remainder) {
return Err(attribute_failure_no_mask(&values, base, remainder, &mut f));
}
}
Ok(())
}
}
impl<S: IndexedSource> IndexedSourceExt for S {}
#[cold]
#[inline(never)]
fn cold_scan<S>(
values: &S,
base: usize,
chunk_len: usize,
mut lane_fails: impl FnMut(usize /* bit_idx */, S::Item) -> bool,
) -> usize
where
S: IndexedSource,
{
for bit_idx in 0..chunk_len {
let idx = base + bit_idx;
let val = unsafe { values.get_unchecked(idx) };
if lane_fails(bit_idx, val) {
return idx;
}
}
unreachable!("cold_scan called without a failing lane")
}
#[inline]
fn attribute_failure_no_mask<S, R, F>(values: &S, base: usize, chunk_len: usize, f: &mut F) -> usize
where
S: IndexedSource,
F: FnMut(S::Item) -> Option<R>,
{
cold_scan(values, base, chunk_len, |_bit_idx, val| f(val).is_none())
}
#[cfg(test)]
#[allow(clippy::cast_possible_truncation)]
mod tests {
use vortex_buffer::BitBuffer;
use vortex_buffer::BitBufferMut;
use super::*;
fn write_t<T: Copy>(out: Vec<MaybeUninit<T>>) -> Vec<T> {
unsafe { std::mem::transmute(out) }
}
#[test]
fn try_map_masked_into_all_ok() {
let values: Vec<u64> = (0..200).collect();
let mask = BitBuffer::new_set(200);
let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert!(res.is_ok());
let got = write_t(out);
assert_eq!(got, (0..200u32).collect::<Vec<_>>());
}
#[test]
fn try_map_masked_into_overflow_fails() {
let mut values: Vec<u64> = (0..200).collect();
values[137] = (u32::MAX as u64) + 1;
let mask = BitBuffer::new_set(200);
let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert_eq!(res, Err(137));
}
#[test]
fn try_map_masked_into_overflow_reports_first_failing_lane() {
let mut values: Vec<u64> = (0..200).collect();
values[50] = u64::MAX;
values[51] = u64::MAX;
values[137] = u64::MAX;
let mask = BitBuffer::new_set(200);
let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert_eq!(res, Err(50));
}
#[test]
fn try_map_masked_into_value_only_closure_filters_null_overflow() {
let mut values: Vec<u64> = (0..200).collect();
values[5] = u64::MAX;
values[42] = u64::MAX;
let mask = {
let mut m = BitBufferMut::with_capacity(200);
for i in 0..200 {
m.append(i != 5 && i != 42);
}
m.freeze()
};
let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert!(
res.is_ok(),
"null-lane overflow should be filtered by the cold path"
);
}
#[test]
fn try_map_masked_into_value_only_closure_reports_first_valid_failure() {
let mut values: Vec<u64> = (0..200).collect();
values[5] = u64::MAX;
values[42] = u64::MAX;
values[77] = u64::MAX;
values[100] = u64::MAX;
let mask = {
let mut m = BitBufferMut::with_capacity(200);
for i in 0..200 {
m.append(i != 5 && i != 42);
}
m.freeze()
};
let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert_eq!(res, Err(77));
}
#[test]
fn try_map_masked_into_null_lane_bypasses_check() {
let mut values: Vec<u64> = (0..200).collect();
values[5] = u64::MAX;
let mask = {
let mut m = BitBufferMut::with_capacity(200);
for i in 0..200 {
m.append(i != 5);
}
m.freeze()
};
let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert!(res.is_ok());
let got = write_t(out);
assert_eq!(got[5], 0);
assert_eq!(got[6], 6);
}
#[test]
fn try_map_masked_into_branchful_matches_branchless() {
let mut values: Vec<u64> = (0..130).map(|i| i as u64 * 7).collect();
values[2] = u64::MAX;
values[65] = u32::MAX as u64;
let mask = {
let mut m = BitBufferMut::with_capacity(130);
for i in 0..130 {
m.append(!matches!(i, 2 | 17 | 99));
}
m.freeze()
};
let mut branchless = vec![MaybeUninit::<u32>::uninit(); 130];
let mut branchful = vec![MaybeUninit::<u32>::uninit(); 130];
values
.as_slice()
.try_map_masked_into(&mask, &mut branchless, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
})
.unwrap();
values
.as_slice()
.try_map_masked_into(&mask, &mut branchful, |v| u32::try_from(v).ok())
.unwrap();
assert_eq!(write_t(branchful), write_t(branchless));
}
#[test]
fn try_map_masked_into_partial_chunk() {
let values: Vec<u64> = (0..130).collect();
let mask = BitBuffer::new_set(130);
let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert!(res.is_ok());
let got = write_t(out);
assert_eq!(got.len(), 130);
assert_eq!(got[129], 129);
}
#[test]
fn try_map_masked_into_sliced_mask_unaligned_offset() {
let big = BitBuffer::new_set(256);
let mask = big.slice(13..143);
assert_eq!(mask.len(), 130);
let values: Vec<u64> = (0..130).collect();
let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert!(res.is_ok());
let got = write_t(out);
assert_eq!(got, (0..130u32).collect::<Vec<_>>());
}
#[test]
fn try_map_masked_into_sliced_mask_with_overflow() {
let big = BitBuffer::new_set(256);
let mask = big.slice(13..143);
assert_eq!(mask.len(), 130);
let mut values: Vec<u64> = (0..130).collect();
values[77] = u64::MAX;
let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert_eq!(res, Err(77));
}
#[test]
fn try_map_masked_into_sliced_mask_null_lanes() {
let mut m = BitBufferMut::with_capacity(256);
for i in 0..256 {
m.append(i % 3 != 0);
}
let big = m.freeze();
let mask = big.slice(13..143);
assert_eq!(mask.len(), 130);
let mut values: Vec<u64> = (0..130).collect();
values[2] = u64::MAX;
let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert!(res.is_ok(), "null lane should bypass the range check");
}
#[test]
fn map_bits_into_packs_full_and_remainder_words() {
let values: Vec<u32> = (0..130).collect();
let mut words = vec![u64::MAX; 3];
values.as_slice().map_bits_into(&mut words, |v| v % 2 == 0);
for idx in 0..130 {
let bit = (words[idx / 64] >> (idx % 64)) & 1;
assert_eq!(bit == 1, idx % 2 == 0, "lane {idx}");
}
assert_eq!(words[2] >> 2, 0);
}
#[test]
fn map_bits_into_lane_zip_compare() {
use crate::lane_kernels::LaneZip;
let lhs: Vec<i64> = (0..100).collect();
let rhs: Vec<i64> = (0..100).rev().collect();
let mut words = vec![0u64; 2];
LaneZip::new(lhs.as_slice(), rhs.as_slice()).map_bits_into(&mut words, |(a, b)| a >= b);
for idx in 0..100 {
let bit = (words[idx / 64] >> (idx % 64)) & 1;
assert_eq!(bit == 1, lhs[idx] >= rhs[idx], "lane {idx}");
}
}
#[test]
#[should_panic(expected = "words slice has 1 entries")]
fn map_bits_into_words_too_short_panics() {
let values: Vec<u32> = (0..65).collect();
let mut words = vec![0u64; 1];
values.as_slice().map_bits_into(&mut words, |v| v > 0);
}
#[test]
fn try_map_masked_into_overflow_in_remainder() {
let mut values: Vec<u64> = (0..130).collect();
values[129] = (u32::MAX as u64) + 1;
let mask = BitBuffer::new_set(130);
let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
(v <= u32::MAX as u64).then_some(v as u32)
});
assert_eq!(res, Err(129));
}
}