vortex_compute/lane_kernels/
map_into.rs1use 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]
182 fn try_map_into<R, F>(self, out: &mut [MaybeUninit<R>], mut f: F) -> Result<(), usize>
183 where
184 R: Copy + Default,
185 F: FnMut(Self::Item) -> Option<R>,
186 {
187 #[inline(always)]
191 fn chunk<S, R, F>(
192 values: &S,
193 out: &mut [MaybeUninit<R>],
194 f: &mut F,
195 base: usize,
196 count: usize,
197 ) -> bool
198 where
199 S: IndexedSource,
200 R: Copy + Default,
201 F: FnMut(S::Item) -> Option<R>,
202 {
203 let mut fail_acc: u64 = 0;
204 for bit_idx in 0..count {
205 let idx = base + bit_idx;
206 let val = unsafe { values.get_unchecked(idx) };
208 let opt = f(val);
209 fail_acc |= opt.is_none() as u64;
210 let result = opt.unwrap_or_default();
211 unsafe { out.get_unchecked_mut(idx).write(result) };
212 }
213 fail_acc != 0
214 }
215
216 let values = self;
217 let len = values.len();
218 assert_eq!(out.len(), len, "out must have the same length as values");
219
220 let chunks_count = len / CHUNK_LEN;
221 let remainder = len % CHUNK_LEN;
222
223 for chunk_idx in 0..chunks_count {
224 let base = chunk_idx * CHUNK_LEN;
225 if chunk(&values, out, &mut f, base, CHUNK_LEN) {
226 return Err(attribute_failure_no_mask(&values, base, CHUNK_LEN, &mut f));
227 }
228 }
229 if remainder != 0 {
230 let base = chunks_count * CHUNK_LEN;
231 if chunk(&values, out, &mut f, base, remainder) {
232 return Err(attribute_failure_no_mask(&values, base, remainder, &mut f));
233 }
234 }
235 Ok(())
236 }
237}
238
239impl<S: IndexedSource> IndexedSourceExt for S {}
240
241#[cold]
247#[inline(never)]
248fn cold_scan<S>(
249 values: &S,
250 base: usize,
251 chunk_len: usize,
252 mut lane_fails: impl FnMut(usize , S::Item) -> bool,
253) -> usize
254where
255 S: IndexedSource,
256{
257 for bit_idx in 0..chunk_len {
258 let idx = base + bit_idx;
259 let val = unsafe { values.get_unchecked(idx) };
261 if lane_fails(bit_idx, val) {
262 return idx;
263 }
264 }
265 unreachable!("cold_scan called without a failing lane")
266}
267
268#[inline]
271fn attribute_failure_no_mask<S, R, F>(values: &S, base: usize, chunk_len: usize, f: &mut F) -> usize
272where
273 S: IndexedSource,
274 F: FnMut(S::Item) -> Option<R>,
275{
276 cold_scan(values, base, chunk_len, |_bit_idx, val| f(val).is_none())
277}
278
279#[cfg(test)]
280#[allow(clippy::cast_possible_truncation)]
281mod tests {
282 use vortex_buffer::BitBuffer;
283 use vortex_buffer::BitBufferMut;
284
285 use super::*;
286
287 fn write_t<T: Copy>(out: Vec<MaybeUninit<T>>) -> Vec<T> {
288 unsafe { std::mem::transmute(out) }
290 }
291
292 #[test]
293 fn try_map_masked_into_all_ok() {
294 let values: Vec<u64> = (0..200).collect();
295 let mask = BitBuffer::new_set(200);
296 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
297 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
298 (v <= u32::MAX as u64).then_some(v as u32)
299 });
300 assert!(res.is_ok());
301 let got = write_t(out);
302 assert_eq!(got, (0..200u32).collect::<Vec<_>>());
303 }
304
305 #[test]
306 fn try_map_masked_into_overflow_fails() {
307 let mut values: Vec<u64> = (0..200).collect();
308 values[137] = (u32::MAX as u64) + 1;
309 let mask = BitBuffer::new_set(200);
310 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
311 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
312 (v <= u32::MAX as u64).then_some(v as u32)
313 });
314 assert_eq!(res, Err(137));
315 }
316
317 #[test]
318 fn try_map_masked_into_overflow_reports_first_failing_lane() {
319 let mut values: Vec<u64> = (0..200).collect();
320 values[50] = u64::MAX;
321 values[51] = u64::MAX;
322 values[137] = u64::MAX;
323 let mask = BitBuffer::new_set(200);
324 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
325 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
326 (v <= u32::MAX as u64).then_some(v as u32)
327 });
328 assert_eq!(res, Err(50));
329 }
330
331 #[test]
332 fn try_map_masked_into_value_only_closure_filters_null_overflow() {
333 let mut values: Vec<u64> = (0..200).collect();
334 values[5] = u64::MAX;
335 values[42] = u64::MAX;
336 let mask = {
337 let mut m = BitBufferMut::with_capacity(200);
338 for i in 0..200 {
339 m.append(i != 5 && i != 42);
340 }
341 m.freeze()
342 };
343 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
344 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
345 (v <= u32::MAX as u64).then_some(v as u32)
346 });
347 assert!(
348 res.is_ok(),
349 "null-lane overflow should be filtered by the cold path"
350 );
351 }
352
353 #[test]
354 fn try_map_masked_into_value_only_closure_reports_first_valid_failure() {
355 let mut values: Vec<u64> = (0..200).collect();
356 values[5] = u64::MAX;
357 values[42] = u64::MAX;
358 values[77] = u64::MAX;
359 values[100] = u64::MAX;
360 let mask = {
361 let mut m = BitBufferMut::with_capacity(200);
362 for i in 0..200 {
363 m.append(i != 5 && i != 42);
364 }
365 m.freeze()
366 };
367 let mut out = vec![MaybeUninit::<u32>::uninit(); 200];
368 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
369 (v <= u32::MAX as u64).then_some(v as u32)
370 });
371 assert_eq!(res, Err(77));
372 }
373
374 #[test]
375 fn try_map_masked_into_null_lane_bypasses_check() {
376 let mut values: Vec<u64> = (0..200).collect();
377 values[5] = u64::MAX;
378 let mask = {
379 let mut m = BitBufferMut::with_capacity(200);
380 for i in 0..200 {
381 m.append(i != 5);
382 }
383 m.freeze()
384 };
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!(res.is_ok());
390 let got = write_t(out);
391 assert_eq!(got[5], 0);
392 assert_eq!(got[6], 6);
393 }
394
395 #[test]
396 fn try_map_masked_into_branchful_matches_branchless() {
397 let mut values: Vec<u64> = (0..130).map(|i| i as u64 * 7).collect();
398 values[2] = u64::MAX;
399 values[65] = u32::MAX as u64;
400 let mask = {
401 let mut m = BitBufferMut::with_capacity(130);
402 for i in 0..130 {
403 m.append(!matches!(i, 2 | 17 | 99));
404 }
405 m.freeze()
406 };
407
408 let mut branchless = vec![MaybeUninit::<u32>::uninit(); 130];
409 let mut branchful = vec![MaybeUninit::<u32>::uninit(); 130];
410 values
411 .as_slice()
412 .try_map_masked_into(&mask, &mut branchless, |v| {
413 (v <= u32::MAX as u64).then_some(v as u32)
414 })
415 .unwrap();
416 values
417 .as_slice()
418 .try_map_masked_into(&mask, &mut branchful, |v| u32::try_from(v).ok())
419 .unwrap();
420
421 assert_eq!(write_t(branchful), write_t(branchless));
422 }
423
424 #[test]
425 fn try_map_masked_into_partial_chunk() {
426 let values: Vec<u64> = (0..130).collect();
427 let mask = BitBuffer::new_set(130);
428 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
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!(res.is_ok());
433 let got = write_t(out);
434 assert_eq!(got.len(), 130);
435 assert_eq!(got[129], 129);
436 }
437
438 #[test]
439 fn try_map_masked_into_sliced_mask_unaligned_offset() {
440 let big = BitBuffer::new_set(256);
441 let mask = big.slice(13..143);
442 assert_eq!(mask.len(), 130);
443
444 let values: Vec<u64> = (0..130).collect();
445 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
446 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
447 (v <= u32::MAX as u64).then_some(v as u32)
448 });
449 assert!(res.is_ok());
450 let got = write_t(out);
451 assert_eq!(got, (0..130u32).collect::<Vec<_>>());
452 }
453
454 #[test]
455 fn try_map_masked_into_sliced_mask_with_overflow() {
456 let big = BitBuffer::new_set(256);
457 let mask = big.slice(13..143);
458 assert_eq!(mask.len(), 130);
459
460 let mut values: Vec<u64> = (0..130).collect();
461 values[77] = u64::MAX;
462 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
463 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
464 (v <= u32::MAX as u64).then_some(v as u32)
465 });
466 assert_eq!(res, Err(77));
467 }
468
469 #[test]
470 fn try_map_masked_into_sliced_mask_null_lanes() {
471 let mut m = BitBufferMut::with_capacity(256);
472 for i in 0..256 {
473 m.append(i % 3 != 0);
474 }
475 let big = m.freeze();
476 let mask = big.slice(13..143);
477 assert_eq!(mask.len(), 130);
478
479 let mut values: Vec<u64> = (0..130).collect();
480 values[2] = u64::MAX;
481 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
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!(res.is_ok(), "null lane should bypass the range check");
486 }
487
488 #[test]
489 fn try_map_masked_into_overflow_in_remainder() {
490 let mut values: Vec<u64> = (0..130).collect();
491 values[129] = (u32::MAX as u64) + 1;
492 let mask = BitBuffer::new_set(130);
493 let mut out = vec![MaybeUninit::<u32>::uninit(); 130];
494 let res = values.as_slice().try_map_masked_into(&mask, &mut out, |v| {
495 (v <= u32::MAX as u64).then_some(v as u32)
496 });
497 assert_eq!(res, Err(129));
498 }
499}