vortex_compute/lane_kernels/
map_in_place.rs1use vortex_buffer::BitBuffer;
10
11use crate::lane_kernels::CHUNK_LEN;
12use crate::lane_kernels::sink::IndexedSink;
13
14pub trait IndexedSinkExt: IndexedSink + Sized {
23 #[inline]
36 fn map_into_in_place<F>(self, mut f: F)
37 where
38 F: FnMut(Self::Item) -> Self::Write,
39 {
40 #[inline(always)]
41 fn chunk<S, F>(values: &mut S, f: &mut F, base: usize, count: usize)
42 where
43 S: IndexedSink,
44 F: FnMut(S::Item) -> S::Write,
45 {
46 for bit_idx in 0..count {
47 let idx = base + bit_idx;
48 let val = unsafe { values.get_unchecked(idx) };
50 let result = f(val);
51 unsafe { values.set_unchecked(idx, result) };
53 }
54 }
55
56 let mut values = self;
57 let len = values.len();
58 let chunks_count = len / CHUNK_LEN;
59 let remainder = len % CHUNK_LEN;
60
61 for chunk_idx in 0..chunks_count {
62 chunk(&mut values, &mut f, chunk_idx * CHUNK_LEN, CHUNK_LEN);
63 }
64 if remainder != 0 {
65 chunk(&mut values, &mut f, chunks_count * CHUNK_LEN, remainder);
66 }
67 }
68
69 #[inline]
86 fn try_map_in_place<F>(self, mut f: F) -> Result<(), usize>
87 where
88 Self::Write: Default,
89 F: FnMut(Self::Item) -> Option<Self::Write>,
90 {
91 #[inline(always)]
92 fn chunk<S, F>(values: &mut S, base: usize, count: usize, f: &mut F) -> Option<usize>
93 where
94 S: IndexedSink,
95 S::Write: Default,
96 F: FnMut(S::Item) -> Option<S::Write>,
97 {
98 let mut fail_bits: u64 = 0;
99 for bit_idx in 0..count {
100 let idx = base + bit_idx;
101 let val = unsafe { values.get_unchecked(idx) };
103 let opt = f(val);
104 fail_bits |= (opt.is_none() as u64) << bit_idx;
105 let result = opt.unwrap_or_default();
106 unsafe { values.set_unchecked(idx, result) };
108 }
109 (fail_bits != 0).then_some(base + fail_bits.trailing_zeros() as usize)
110 }
111
112 let mut values = self;
113 let len = values.len();
114 let chunks_count = len / 64;
115 let remainder = len % 64;
116
117 for chunk_idx in 0..chunks_count {
118 if let Some(failing) = chunk(&mut values, chunk_idx * 64, 64, &mut f) {
119 return Err(failing);
120 }
121 }
122 if remainder != 0
123 && let Some(failing) = chunk(&mut values, chunks_count * 64, remainder, &mut f)
124 {
125 return Err(failing);
126 }
127 Ok(())
128 }
129
130 #[inline]
157 fn try_map_masked_in_place<F>(self, mask: &BitBuffer, mut f: F) -> Result<(), usize>
158 where
159 Self::Write: Default,
160 F: FnMut(Self::Item) -> Option<Self::Write>,
161 {
162 #[inline(always)]
166 fn chunk<S, F>(
167 values: &mut S,
168 src_chunk: u64,
169 base: usize,
170 count: usize,
171 f: &mut F,
172 ) -> Option<usize>
173 where
174 S: IndexedSink,
175 S::Write: Default,
176 F: FnMut(S::Item) -> Option<S::Write>,
177 {
178 let mut fail_bits: u64 = 0;
179 for bit_idx in 0..count {
180 let idx = base + bit_idx;
181 let val = unsafe { values.get_unchecked(idx) };
183 let opt = f(val);
184 fail_bits |= (opt.is_none() as u64) << bit_idx;
185 let result = opt.unwrap_or_default();
186 unsafe { values.set_unchecked(idx, result) };
187 }
188 let valid_failures = fail_bits & src_chunk;
189 (valid_failures != 0).then_some(base + valid_failures.trailing_zeros() as usize)
190 }
191
192 let mut values = self;
193 let len = values.len();
194 assert_eq!(len, mask.len(), "values and mask must have the same length");
195
196 let chunks = mask.chunks();
197 let chunks_count = len / 64;
198 let remainder = len % 64;
199
200 for (chunk_idx, src_chunk) in chunks.iter().enumerate() {
201 if let Some(failing) = chunk(&mut values, src_chunk, chunk_idx * 64, 64, &mut f) {
202 return Err(failing);
203 }
204 }
205 if remainder != 0
206 && let Some(failing) = chunk(
207 &mut values,
208 chunks.remainder_bits(),
209 chunks_count * 64,
210 remainder,
211 &mut f,
212 )
213 {
214 return Err(failing);
215 }
216 Ok(())
217 }
218}
219
220impl<S: IndexedSink> IndexedSinkExt for S {}
221
222#[cfg(test)]
223#[allow(clippy::cast_possible_truncation)]
224mod tests {
225 use vortex_buffer::BitBuffer;
226 use vortex_buffer::BitBufferMut;
227
228 use super::*;
229 use crate::lane_kernels::sink::ReinterpretSink;
230
231 #[test]
232 fn try_map_masked_in_place_all_ok() {
233 let mut values: Vec<u32> = (0..200).collect();
234 let mask = BitBuffer::new_set(200);
235 let res = values
236 .as_mut_slice()
237 .try_map_masked_in_place(&mask, |v| v.checked_mul(2));
238 assert!(res.is_ok());
239 let expected: Vec<u32> = (0..200u32).map(|v| v * 2).collect();
240 assert_eq!(values, expected);
241 }
242
243 #[test]
244 fn try_map_masked_in_place_first_failing_chunk_wins() {
245 let mut values: Vec<u32> = (0..200).collect();
246 values[83] = u32::MAX;
247 values[150] = u32::MAX;
248 let mask = BitBuffer::new_set(200);
249 let res = values
250 .as_mut_slice()
251 .try_map_masked_in_place(&mask, |v| v.checked_mul(2));
252 assert_eq!(res, Err(83));
253 }
254
255 #[test]
256 fn try_map_masked_in_place_within_chunk_reports_lowest() {
257 let mut values: Vec<u32> = (0..200).collect();
258 values[80] = u32::MAX;
259 values[100] = u32::MAX;
260 let mask = BitBuffer::new_set(200);
261 let res = values
262 .as_mut_slice()
263 .try_map_masked_in_place(&mask, |v| v.checked_mul(2));
264 assert_eq!(res, Err(80));
265 }
266
267 #[test]
268 fn try_map_masked_in_place_single_failure_lane_exact() {
269 let mut values: Vec<u32> = (0..200).collect();
270 values[42] = u32::MAX;
271 let mask = BitBuffer::new_set(200);
272 let res = values
273 .as_mut_slice()
274 .try_map_masked_in_place(&mask, |v| v.checked_mul(2));
275 assert_eq!(res, Err(42));
276 }
277
278 #[test]
279 fn try_map_masked_in_place_null_bypass() {
280 let mut values: Vec<u32> = (0..200).collect();
281 values[5] = u32::MAX;
282 let mask = {
283 let mut m = BitBufferMut::with_capacity(200);
284 for i in 0..200 {
285 m.append(i != 5);
286 }
287 m.freeze()
288 };
289 let res = values
290 .as_mut_slice()
291 .try_map_masked_in_place(&mask, |v| v.checked_mul(2));
292 assert!(res.is_ok(), "null-lane overflow should be filtered");
293 assert_eq!(values[5], 0);
294 assert_eq!(values[6], 12);
295 }
296
297 #[test]
298 fn try_map_masked_in_place_remainder_overflow() {
299 let mut values: Vec<u32> = (0..130).collect();
300 values[129] = u32::MAX;
301 let mask = BitBuffer::new_set(130);
302 let res = values
303 .as_mut_slice()
304 .try_map_masked_in_place(&mask, |v| v.checked_mul(2));
305 assert_eq!(res, Err(129));
306 }
307
308 #[test]
309 fn try_map_masked_in_place_sliced_mask() {
310 let big = BitBuffer::new_set(256);
311 let mask = big.slice(13..143);
312 assert_eq!(mask.len(), 130);
313
314 let mut values: Vec<u32> = (0..130).collect();
315 values[77] = u32::MAX;
316 let res = values
317 .as_mut_slice()
318 .try_map_masked_in_place(&mask, |v| v.checked_mul(2));
319 assert_eq!(res, Err(77));
320 }
321
322 #[test]
323 fn reinterpret_sink_same_width_f32_u32() {
324 let mut buf: Vec<f32> = (0..130).map(|i| i as f32).collect();
325 let mask = BitBuffer::new_set(130);
326 ReinterpretSink::<f32, u32>::new(buf.as_mut_slice())
327 .try_map_masked_in_place(&mask, |f| Some(f.to_bits().wrapping_add(1)))
328 .unwrap();
329 let as_u32: &[u32] =
332 unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u32, buf.len()) };
333 for (i, &got) in as_u32.iter().enumerate() {
334 assert_eq!(got, (i as f32).to_bits().wrapping_add(1), "lane {i}");
335 }
336 }
337
338 #[test]
339 fn reinterpret_sink_failure_reports_lane() {
340 let mut buf: Vec<f32> = (0..200).map(|i| i as f32).collect();
341 let mask = BitBuffer::new_set(200);
342 let res = ReinterpretSink::<f32, u32>::new(buf.as_mut_slice()).try_map_masked_in_place(
343 &mask,
344 |f| {
345 if f as u32 == 137 {
346 None
347 } else {
348 Some(f as u32)
349 }
350 },
351 );
352 assert_eq!(res, Err(137));
353 }
354
355 #[test]
356 fn try_map_masked_in_place_partial_chunk_success() {
357 let mut values: Vec<u32> = (0..130).collect();
358 let mask = BitBuffer::new_set(130);
359 let res = values
360 .as_mut_slice()
361 .try_map_masked_in_place(&mask, |v| Some(v + 1));
362 assert!(res.is_ok());
363 assert_eq!(values[0], 1);
364 assert_eq!(values[63], 64);
365 assert_eq!(values[64], 65);
366 assert_eq!(values[129], 130);
367 }
368}