use vortex_buffer::BitBuffer;
use crate::lane_kernels::CHUNK_LEN;
use crate::lane_kernels::sink::IndexedSink;
pub trait IndexedSinkExt: IndexedSink + Sized {
#[inline]
fn map_into_in_place<F>(self, mut f: F)
where
F: FnMut(Self::Item) -> Self::Write,
{
#[inline(always)]
fn chunk<S, F>(values: &mut S, f: &mut F, base: usize, count: usize)
where
S: IndexedSink,
F: FnMut(S::Item) -> S::Write,
{
for bit_idx in 0..count {
let idx = base + bit_idx;
let val = unsafe { values.get_unchecked(idx) };
let result = f(val);
unsafe { values.set_unchecked(idx, result) };
}
}
let mut values = self;
let len = values.len();
let chunks_count = len / CHUNK_LEN;
let remainder = len % CHUNK_LEN;
for chunk_idx in 0..chunks_count {
chunk(&mut values, &mut f, chunk_idx * CHUNK_LEN, CHUNK_LEN);
}
if remainder != 0 {
chunk(&mut values, &mut f, chunks_count * CHUNK_LEN, remainder);
}
}
#[inline]
fn try_map_in_place<F>(self, mut f: F) -> Result<(), usize>
where
Self::Write: Default,
F: FnMut(Self::Item) -> Option<Self::Write>,
{
#[inline(always)]
fn chunk<S, F>(values: &mut S, base: usize, count: usize, f: &mut F) -> Option<usize>
where
S: IndexedSink,
S::Write: Default,
F: FnMut(S::Item) -> Option<S::Write>,
{
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 { values.set_unchecked(idx, result) };
}
(fail_bits != 0).then_some(base + fail_bits.trailing_zeros() as usize)
}
let mut values = self;
let len = values.len();
let chunks_count = len / 64;
let remainder = len % 64;
for chunk_idx in 0..chunks_count {
if let Some(failing) = chunk(&mut values, chunk_idx * 64, 64, &mut f) {
return Err(failing);
}
}
if remainder != 0
&& let Some(failing) = chunk(&mut values, chunks_count * 64, remainder, &mut f)
{
return Err(failing);
}
Ok(())
}
#[inline]
fn try_map_masked_in_place<F>(self, mask: &BitBuffer, mut f: F) -> Result<(), usize>
where
Self::Write: Default,
F: FnMut(Self::Item) -> Option<Self::Write>,
{
#[inline(always)]
fn chunk<S, F>(
values: &mut S,
src_chunk: u64,
base: usize,
count: usize,
f: &mut F,
) -> Option<usize>
where
S: IndexedSink,
S::Write: Default,
F: FnMut(S::Item) -> Option<S::Write>,
{
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 { values.set_unchecked(idx, result) };
}
let valid_failures = fail_bits & src_chunk;
(valid_failures != 0).then_some(base + valid_failures.trailing_zeros() as usize)
}
let mut values = self;
let len = values.len();
assert_eq!(len, mask.len(), "values and mask must have the same length");
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(failing) = chunk(&mut values, src_chunk, chunk_idx * 64, 64, &mut f) {
return Err(failing);
}
}
if remainder != 0
&& let Some(failing) = chunk(
&mut values,
chunks.remainder_bits(),
chunks_count * 64,
remainder,
&mut f,
)
{
return Err(failing);
}
Ok(())
}
}
impl<S: IndexedSink> IndexedSinkExt for S {}
#[cfg(test)]
#[allow(clippy::cast_possible_truncation)]
mod tests {
use vortex_buffer::BitBuffer;
use vortex_buffer::BitBufferMut;
use super::*;
use crate::lane_kernels::sink::ReinterpretSink;
#[test]
fn try_map_masked_in_place_all_ok() {
let mut values: Vec<u32> = (0..200).collect();
let mask = BitBuffer::new_set(200);
let res = values
.as_mut_slice()
.try_map_masked_in_place(&mask, |v| v.checked_mul(2));
assert!(res.is_ok());
let expected: Vec<u32> = (0..200u32).map(|v| v * 2).collect();
assert_eq!(values, expected);
}
#[test]
fn try_map_masked_in_place_first_failing_chunk_wins() {
let mut values: Vec<u32> = (0..200).collect();
values[83] = u32::MAX;
values[150] = u32::MAX;
let mask = BitBuffer::new_set(200);
let res = values
.as_mut_slice()
.try_map_masked_in_place(&mask, |v| v.checked_mul(2));
assert_eq!(res, Err(83));
}
#[test]
fn try_map_masked_in_place_within_chunk_reports_lowest() {
let mut values: Vec<u32> = (0..200).collect();
values[80] = u32::MAX;
values[100] = u32::MAX;
let mask = BitBuffer::new_set(200);
let res = values
.as_mut_slice()
.try_map_masked_in_place(&mask, |v| v.checked_mul(2));
assert_eq!(res, Err(80));
}
#[test]
fn try_map_masked_in_place_single_failure_lane_exact() {
let mut values: Vec<u32> = (0..200).collect();
values[42] = u32::MAX;
let mask = BitBuffer::new_set(200);
let res = values
.as_mut_slice()
.try_map_masked_in_place(&mask, |v| v.checked_mul(2));
assert_eq!(res, Err(42));
}
#[test]
fn try_map_masked_in_place_null_bypass() {
let mut values: Vec<u32> = (0..200).collect();
values[5] = u32::MAX;
let mask = {
let mut m = BitBufferMut::with_capacity(200);
for i in 0..200 {
m.append(i != 5);
}
m.freeze()
};
let res = values
.as_mut_slice()
.try_map_masked_in_place(&mask, |v| v.checked_mul(2));
assert!(res.is_ok(), "null-lane overflow should be filtered");
assert_eq!(values[5], 0);
assert_eq!(values[6], 12);
}
#[test]
fn try_map_masked_in_place_remainder_overflow() {
let mut values: Vec<u32> = (0..130).collect();
values[129] = u32::MAX;
let mask = BitBuffer::new_set(130);
let res = values
.as_mut_slice()
.try_map_masked_in_place(&mask, |v| v.checked_mul(2));
assert_eq!(res, Err(129));
}
#[test]
fn try_map_masked_in_place_sliced_mask() {
let big = BitBuffer::new_set(256);
let mask = big.slice(13..143);
assert_eq!(mask.len(), 130);
let mut values: Vec<u32> = (0..130).collect();
values[77] = u32::MAX;
let res = values
.as_mut_slice()
.try_map_masked_in_place(&mask, |v| v.checked_mul(2));
assert_eq!(res, Err(77));
}
#[test]
fn reinterpret_sink_same_width_f32_u32() {
let mut buf: Vec<f32> = (0..130).map(|i| i as f32).collect();
let mask = BitBuffer::new_set(130);
ReinterpretSink::<f32, u32>::new(buf.as_mut_slice())
.try_map_masked_in_place(&mask, |f| Some(f.to_bits().wrapping_add(1)))
.unwrap();
let as_u32: &[u32] =
unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u32, buf.len()) };
for (i, &got) in as_u32.iter().enumerate() {
assert_eq!(got, (i as f32).to_bits().wrapping_add(1), "lane {i}");
}
}
#[test]
fn reinterpret_sink_failure_reports_lane() {
let mut buf: Vec<f32> = (0..200).map(|i| i as f32).collect();
let mask = BitBuffer::new_set(200);
let res = ReinterpretSink::<f32, u32>::new(buf.as_mut_slice()).try_map_masked_in_place(
&mask,
|f| {
if f as u32 == 137 {
None
} else {
Some(f as u32)
}
},
);
assert_eq!(res, Err(137));
}
#[test]
fn try_map_masked_in_place_partial_chunk_success() {
let mut values: Vec<u32> = (0..130).collect();
let mask = BitBuffer::new_set(130);
let res = values
.as_mut_slice()
.try_map_masked_in_place(&mask, |v| Some(v + 1));
assert!(res.is_ok());
assert_eq!(values[0], 1);
assert_eq!(values[63], 64);
assert_eq!(values[64], 65);
assert_eq!(values[129], 130);
}
}