1#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
5mod avx2;
6
7use std::ptr;
8use std::sync::LazyLock;
9
10use itertools::Itertools as _;
11use vortex_buffer::Buffer;
12use vortex_buffer::BufferMut;
13use vortex_error::VortexResult;
14use vortex_error::vortex_bail;
15use vortex_error::vortex_ensure;
16use vortex_error::vortex_err;
17use vortex_mask::Mask;
18
19use crate::ArrayRef;
20use crate::Columnar;
21use crate::IntoArray;
22use crate::array::ArrayView;
23use crate::arrays::ConstantArray;
24use crate::arrays::PiecewiseSequence;
25use crate::arrays::Primitive;
26use crate::arrays::PrimitiveArray;
27use crate::arrays::dict::TakeExecute;
28use crate::arrays::piecewise_sequence::constant_unsigned_usize;
29use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
30use crate::builtins::ArrayBuiltins;
31use crate::dtype::DType;
32use crate::dtype::IntegerPType;
33use crate::dtype::NativePType;
34use crate::dtype::UnsignedPType;
35use crate::executor::ExecutionCtx;
36use crate::match_each_integer_ptype;
37use crate::match_each_native_ptype;
38use crate::match_each_unsigned_integer_ptype;
39use crate::scalar::Scalar;
40use crate::validity::Validity;
41
42static PRIMITIVE_TAKE_KERNEL: LazyLock<&'static dyn TakeImpl> = LazyLock::new(|| {
45 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
46 {
47 if is_x86_feature_detected!("avx2") {
48 &avx2::TakeKernelAVX2
49 } else {
50 &TakeKernelScalar
51 }
52 }
53
54 #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
55 {
56 &TakeKernelScalar
57 }
58});
59
60trait TakeImpl: Send + Sync {
61 fn take(
62 &self,
63 array: ArrayView<'_, Primitive>,
64 indices: ArrayView<'_, Primitive>,
65 validity: Validity,
66 ) -> VortexResult<ArrayRef>;
67}
68
69struct TakeKernelScalar;
70
71impl TakeImpl for TakeKernelScalar {
72 fn take(
73 &self,
74 array: ArrayView<'_, Primitive>,
75 indices: ArrayView<'_, Primitive>,
76 validity: Validity,
77 ) -> VortexResult<ArrayRef> {
78 match_each_native_ptype!(array.ptype(), |T| {
79 match_each_integer_ptype!(indices.ptype(), |I| {
80 let values = take_primitive_scalar(array.as_slice::<T>(), indices.as_slice::<I>());
81 Ok(PrimitiveArray::new(values, validity).into_array())
82 })
83 })
84 }
85}
86
87impl TakeExecute for Primitive {
88 fn take(
89 array: ArrayView<'_, Primitive>,
90 indices: &ArrayRef,
91 ctx: &mut ExecutionCtx,
92 ) -> VortexResult<Option<ArrayRef>> {
93 if let Some(piecewise_indices) = indices.as_opt::<PiecewiseSequence>()
94 && let Some(taken) = take_contiguous_ranges(array, piecewise_indices, indices, ctx)?
95 {
96 return Ok(Some(taken));
97 }
98
99 let DType::Primitive(ptype, null) = indices.dtype() else {
100 vortex_bail!("Invalid indices dtype: {}", indices.dtype())
101 };
102
103 let indices_validity = indices.validity()?;
104 let indices_nulls_zeroed = match indices_validity.execute_mask(indices.len(), ctx)? {
108 Mask::AllTrue(_) => indices.clone(),
109 Mask::AllFalse(_) => {
110 return Ok(Some(
111 ConstantArray::new(Scalar::null(array.dtype().as_nullable()), indices.len())
112 .into_array(),
113 ));
114 }
115 Mask::Values(_) => indices
116 .clone()
117 .fill_null(Scalar::from(0).cast(indices.dtype())?)?,
118 };
119
120 let unsigned_indices = if ptype.is_unsigned_int() {
121 indices_nulls_zeroed.execute::<PrimitiveArray>(ctx)?
122 } else {
123 indices_nulls_zeroed
125 .cast(DType::Primitive(ptype.to_unsigned(), *null))?
126 .execute::<PrimitiveArray>(ctx)?
127 };
128
129 let validity = array
130 .validity()?
131 .take(&unsigned_indices.clone().into_array())?
132 .and(indices_validity)?;
133 {
135 let unsigned_indices = unsigned_indices.as_view();
136 PRIMITIVE_TAKE_KERNEL
137 .take(array, unsigned_indices, validity)
138 .map(Some)
139 }
140 }
141}
142
143fn take_contiguous_ranges(
144 array: ArrayView<'_, Primitive>,
145 indices: ArrayView<'_, PiecewiseSequence>,
146 indices_ref: &ArrayRef,
147 ctx: &mut ExecutionCtx,
148) -> VortexResult<Option<ArrayRef>> {
149 let Some((starts, lengths)) = maybe_contiguous_slices(indices, ctx)? else {
150 return Ok(None);
151 };
152 let validity = array.validity()?.take(indices_ref)?;
153 let output_len = indices_ref.len();
154 let taken = match lengths {
155 Columnar::Constant(lengths) => {
156 let length = constant_unsigned_usize(&lengths);
157 take_slices_constant_length(array, &starts, length, validity, output_len)?
158 }
159 Columnar::Canonical(lengths) => {
160 let lengths = lengths.into_primitive();
161 take_slices(array, &starts, &lengths, validity, output_len)?
162 }
163 };
164 Ok(Some(taken))
165}
166
167#[inline(always)]
169fn take_primitive_scalar<T: Copy, I: IntegerPType>(buffer: &[T], indices: &[I]) -> Buffer<T> {
170 let mut result = BufferMut::with_capacity(indices.len());
174 let ptr = result.spare_capacity_mut().as_mut_ptr().cast::<T>();
175
176 for (i, idx) in indices.iter().enumerate() {
179 unsafe { ptr.add(i).write(buffer[idx.as_()]) };
181 }
182
183 unsafe { result.set_len(indices.len()) };
185 result.freeze()
186}
187
188fn take_slices(
189 array: ArrayView<'_, Primitive>,
190 starts: &PrimitiveArray,
191 lengths: &PrimitiveArray,
192 validity: Validity,
193 output_len: usize,
194) -> VortexResult<ArrayRef> {
195 match_each_native_ptype!(array.ptype(), |T| {
196 take_slices_typed::<T>(array, starts, lengths, validity, output_len)
197 })
198}
199
200fn take_slices_typed<T>(
201 array: ArrayView<'_, Primitive>,
202 starts: &PrimitiveArray,
203 lengths: &PrimitiveArray,
204 validity: Validity,
205 output_len: usize,
206) -> VortexResult<ArrayRef>
207where
208 T: NativePType,
209{
210 match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
211 take_slices_start_typed::<T, S>(array, starts, lengths, validity, output_len)
212 })
213}
214
215fn take_slices_start_typed<T, S>(
216 array: ArrayView<'_, Primitive>,
217 starts: &PrimitiveArray,
218 lengths: &PrimitiveArray,
219 validity: Validity,
220 output_len: usize,
221) -> VortexResult<ArrayRef>
222where
223 T: NativePType,
224 S: UnsignedPType,
225{
226 match_each_unsigned_integer_ptype!(lengths.ptype(), |L| {
227 let values = take_slices_to_buffer::<T, S, L>(
228 array.as_slice::<T>(),
229 starts.as_slice::<S>(),
230 lengths.as_slice::<L>(),
231 output_len,
232 )?;
233 Ok(PrimitiveArray::new(values, validity).into_array())
234 })
235}
236
237fn take_slices_to_buffer<T, S, L>(
238 source: &[T],
239 starts: &[S],
240 lengths: &[L],
241 output_len: usize,
242) -> VortexResult<Buffer<T>>
243where
244 T: Copy,
245 S: UnsignedPType,
246 L: UnsignedPType,
247{
248 let mut values = BufferMut::<T>::with_capacity(output_len);
249 let spare = &mut values.spare_capacity_mut()[..output_len];
250 let mut cursor = 0usize;
251 for (&start, &length) in starts.iter().zip_eq(lengths) {
252 let start = start.as_();
253 let length = length.as_();
254 let src = &source[start..][..length];
255 unsafe {
257 ptr::copy_nonoverlapping(
258 src.as_ptr(),
259 spare[cursor..][..src.len()].as_mut_ptr().cast::<T>(),
260 src.len(),
261 );
262 }
263 cursor += src.len();
264 }
265 unsafe { values.set_len(cursor) };
267 vortex_ensure!(
268 values.len() == output_len,
269 "PiecewiseSequenceArray expanded length {} does not match declared length {output_len}",
270 values.len()
271 );
272 Ok(values.freeze())
273}
274
275fn take_slices_constant_length(
276 array: ArrayView<'_, Primitive>,
277 starts: &PrimitiveArray,
278 length: usize,
279 validity: Validity,
280 output_len: usize,
281) -> VortexResult<ArrayRef> {
282 match_each_native_ptype!(array.ptype(), |T| {
283 take_slices_constant_length_typed::<T>(array, starts, length, validity, output_len)
284 })
285}
286
287fn take_slices_constant_length_typed<T>(
288 array: ArrayView<'_, Primitive>,
289 starts: &PrimitiveArray,
290 length: usize,
291 validity: Validity,
292 output_len: usize,
293) -> VortexResult<ArrayRef>
294where
295 T: NativePType,
296{
297 match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
298 let values = take_slices_constant_length_to_buffer::<T, S>(
299 array.as_slice::<T>(),
300 starts.as_slice::<S>(),
301 length,
302 output_len,
303 )?;
304 Ok(PrimitiveArray::new(values, validity).into_array())
305 })
306}
307
308fn take_slices_constant_length_to_buffer<T, S>(
309 source: &[T],
310 starts: &[S],
311 length: usize,
312 output_len: usize,
313) -> VortexResult<Buffer<T>>
314where
315 T: Copy,
316 S: UnsignedPType,
317{
318 let computed_len = starts
319 .len()
320 .checked_mul(length)
321 .ok_or_else(|| vortex_err!("PiecewiseSequenceArray output length overflows usize"))?;
322 vortex_ensure!(
323 computed_len == output_len,
324 "PiecewiseSequenceArray expanded length {computed_len} does not match declared length {output_len}"
325 );
326
327 let mut values = BufferMut::<T>::with_capacity(output_len);
328 let spare = &mut values.spare_capacity_mut()[..output_len];
329 let mut cursor = 0usize;
330 for &start in starts {
331 let start = start.as_();
332 let src = &source[start..][..length];
333 unsafe {
335 ptr::copy_nonoverlapping(
336 src.as_ptr(),
337 spare[cursor..][..src.len()].as_mut_ptr().cast::<T>(),
338 src.len(),
339 );
340 }
341 cursor += src.len();
342 }
343 unsafe { values.set_len(cursor) };
345 vortex_ensure!(
346 values.len() == output_len,
347 "PiecewiseSequenceArray expanded length {} does not match declared length {output_len}",
348 values.len()
349 );
350 Ok(values.freeze())
351}
352
353#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
354#[cfg(test)]
355mod test {
356 use rstest::rstest;
357 use vortex_buffer::buffer;
358 use vortex_error::VortexExpect;
359
360 use crate::IntoArray;
361 use crate::VortexSessionExecute;
362 use crate::array_session;
363 use crate::arrays::BoolArray;
364 use crate::arrays::PrimitiveArray;
365 use crate::arrays::primitive::compute::take::take_primitive_scalar;
366 use crate::compute::conformance::take::test_take_conformance;
367 use crate::scalar::Scalar;
368 use crate::validity::Validity;
369
370 #[test]
371 fn test_take() {
372 let a = vec![1i32, 2, 3, 4, 5];
373 let result = take_primitive_scalar(&a, &[0, 0, 4, 2]);
374 assert_eq!(result.as_slice(), &[1i32, 1, 5, 3]);
375 }
376
377 #[test]
378 fn test_take_with_null_indices() {
379 let mut ctx = array_session().create_execution_ctx();
380 let values = PrimitiveArray::new(
381 buffer![1i32, 2, 3, 4, 5],
382 Validity::Array(BoolArray::from_iter([true, true, false, false, true]).into_array()),
383 );
384 let indices = PrimitiveArray::new(
385 buffer![0, 3, 4],
386 Validity::Array(BoolArray::from_iter([true, true, false]).into_array()),
387 );
388 let actual = values.take(indices.into_array()).unwrap();
389 assert_eq!(
390 actual.execute_scalar(0, &mut ctx).vortex_expect("no fail"),
391 Scalar::from(Some(1))
392 );
393 assert_eq!(
395 actual.execute_scalar(1, &mut ctx).vortex_expect("no fail"),
396 Scalar::null_native::<i32>()
397 );
398 assert_eq!(
400 actual.execute_scalar(2, &mut ctx).vortex_expect("no fail"),
401 Scalar::null_native::<i32>()
402 );
403 }
404
405 #[rstest]
406 #[case(PrimitiveArray::new(buffer![42i32], Validity::NonNullable))]
407 #[case(PrimitiveArray::new(buffer![0, 1], Validity::NonNullable))]
408 #[case(PrimitiveArray::new(buffer![0, 1, 2, 3, 4], Validity::NonNullable))]
409 #[case(PrimitiveArray::new(buffer![0, 1, 2, 3, 4, 5, 6, 7], Validity::NonNullable))]
410 #[case(PrimitiveArray::new(buffer![0, 1, 2, 3, 4], Validity::AllValid))]
411 #[case(PrimitiveArray::new(
412 buffer![0, 1, 2, 3, 4, 5],
413 Validity::Array(BoolArray::from_iter([true, false, true, false, true, true]).into_array()),
414 ))]
415 #[case(PrimitiveArray::from_option_iter([Some(1), None, Some(3), Some(4), None]))]
416 fn test_take_primitive_conformance(#[case] array: PrimitiveArray) {
417 test_take_conformance(
418 &array.into_array(),
419 &mut array_session().create_execution_ctx(),
420 );
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use vortex_buffer::buffer;
427
428 use crate::IntoArray;
429 use crate::VortexSessionExecute;
430 use crate::array_session;
431 use crate::arrays::BoolArray;
432 use crate::arrays::PrimitiveArray;
433 use crate::assert_arrays_eq;
434 use crate::validity::Validity;
435
436 #[test]
437 fn take_null_index_skips_out_of_bounds_value() {
438 let mut ctx = array_session().create_execution_ctx();
439 let values = PrimitiveArray::from_iter([10i32, 20, 30]);
440 let indices = PrimitiveArray::new(
441 buffer![1u64, 3],
442 Validity::Array(BoolArray::from_iter([true, false]).into_array()),
443 );
444
445 let taken = values.take(indices.into_array()).unwrap();
446
447 assert_arrays_eq!(
448 taken,
449 PrimitiveArray::from_option_iter([Some(20i32), None]).into_array(),
450 &mut ctx
451 );
452 }
453}