Skip to main content

vortex_array/arrays/fixed_width/take/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
5mod avx2;
6mod records;
7mod scalar;
8mod slices;
9mod small_table;
10#[cfg(test)]
11mod tests;
12
13#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
14use std::sync::LazyLock;
15
16use vortex_buffer::Buffer;
17use vortex_buffer::BufferAllocatorRef;
18use vortex_error::VortexResult;
19use vortex_error::vortex_bail;
20use vortex_mask::Mask;
21
22use self::records::take_byte_records;
23use self::scalar::take_values_scalar;
24use self::slices::take_slices;
25use self::slices::take_slices_constant_length;
26use super::FixedWidthArray;
27use super::with_values;
28use crate::ArrayRef;
29use crate::Columnar;
30use crate::ExecutionCtx;
31use crate::IntoArray;
32use crate::array::ArrayView;
33use crate::arrays::ConstantArray;
34use crate::arrays::PiecewiseSequence;
35use crate::arrays::PrimitiveArray;
36use crate::arrays::dict::TakeExecute;
37use crate::arrays::piecewise_sequence::constant_unsigned_usize;
38use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
39use crate::builtins::ArrayBuiltins;
40use crate::dtype::DType;
41use crate::dtype::UnsignedPType;
42use crate::dtype::half::f16;
43use crate::match_each_unsigned_integer_ptype;
44use crate::scalar::Scalar;
45
46#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
47static HAS_AVX2: LazyLock<bool> = LazyLock::new(|| is_x86_feature_detected!("avx2"));
48
49impl<V: FixedWidthArray> TakeExecute for V {
50    fn take(
51        array: ArrayView<'_, Self>,
52        indices: &ArrayRef,
53        ctx: &mut ExecutionCtx,
54    ) -> VortexResult<Option<ArrayRef>> {
55        take(array, indices, ctx)
56    }
57}
58
59/// A fixed-width value whose initialized bytes may be moved through integer SIMD lanes.
60///
61/// # Safety
62///
63/// Implementors must have no uninitialized bytes. The shared AVX2 gather reads the complete
64/// representation through a same-width integer lane before writing those bytes back unchanged.
65pub(crate) unsafe trait FixedWidthTakeValue: Copy {
66    /// Takes values using the kernel appropriate for this value type.
67    fn take<I: UnsignedPType>(
68        values: &[Self],
69        indices: &[I],
70        allocator: &BufferAllocatorRef,
71    ) -> Buffer<Self> {
72        take_values_fallback(values, indices, allocator)
73    }
74}
75
76macro_rules! impl_fixed_width_take_value {
77    ($($ty:ty),+ $(,)?) => {
78        $(
79            // SAFETY: These scalar representations contain no padding or uninitialized bytes.
80            unsafe impl FixedWidthTakeValue for $ty {}
81        )+
82    };
83}
84
85impl_fixed_width_take_value!(u16, u32, u64, i16, i32, i64, f16, f32, f64,);
86
87pub(crate) fn take_values<T: FixedWidthTakeValue, I: UnsignedPType>(
88    values: &[T],
89    indices: &[I],
90    allocator: &BufferAllocatorRef,
91) -> Buffer<T> {
92    T::take(values, indices, allocator)
93}
94
95fn take_values_fallback<T: FixedWidthTakeValue, I: UnsignedPType>(
96    values: &[T],
97    indices: &[I],
98    allocator: &BufferAllocatorRef,
99) -> Buffer<T> {
100    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
101    if *HAS_AVX2 {
102        // SAFETY: AVX2 was detected above and `FixedWidthTakeValue` guarantees an initialized byte
103        // representation. The AVX2 dispatcher retains Primitive's existing scalar fallbacks and
104        // out-of-bounds behavior for every value width.
105        return unsafe { avx2::take_avx2(values, indices, allocator) };
106    }
107
108    take_values_scalar(values, indices, allocator)
109}
110
111pub(crate) fn take<V: FixedWidthArray>(
112    array: ArrayView<'_, V>,
113    indices: &ArrayRef,
114    ctx: &mut ExecutionCtx,
115) -> VortexResult<Option<ArrayRef>> {
116    if let Some(piecewise_indices) = indices.as_opt::<PiecewiseSequence>()
117        && let Some(taken) = take_contiguous_ranges(array, piecewise_indices, indices, ctx)?
118    {
119        return Ok(Some(taken));
120    }
121
122    let DType::Primitive(ptype, nullability) = indices.dtype() else {
123        vortex_bail!("Invalid indices dtype: {}", indices.dtype())
124    };
125    if !ptype.is_int() {
126        vortex_bail!("Invalid indices dtype: {}", indices.dtype())
127    }
128
129    let indices_validity = indices.validity()?;
130    let indices_nulls_zeroed = match indices_validity.execute_mask(indices.len(), ctx)? {
131        Mask::AllTrue(_) => indices.clone(),
132        Mask::AllFalse(_) => {
133            return Ok(Some(
134                ConstantArray::new(Scalar::null(array.dtype().as_nullable()), indices.len())
135                    .into_array(),
136            ));
137        }
138        Mask::Values(_) => indices
139            .clone()
140            .fill_null(Scalar::from(0).cast(indices.dtype())?)?,
141    };
142
143    let indices = if ptype.is_unsigned_int() {
144        indices_nulls_zeroed.execute::<PrimitiveArray>(ctx)?
145    } else {
146        indices_nulls_zeroed
147            .cast(DType::Primitive(ptype.to_unsigned(), *nullability))?
148            .execute::<PrimitiveArray>(ctx)?
149    };
150    let validity = array
151        .validity()?
152        .take(&indices.clone().into_array())?
153        .and(indices_validity)?;
154
155    let source = V::values(array);
156    let values = match_each_unsigned_integer_ptype!(indices.ptype(), |I| {
157        take_byte_records(
158            &source,
159            V::byte_width(array),
160            array.len(),
161            indices.as_slice::<I>(),
162            ctx.allocator(),
163        )
164    })?;
165    Ok(Some(
166        with_values(array, values, indices.len(), validity)?.into_array(),
167    ))
168}
169
170fn take_contiguous_ranges<V: FixedWidthArray>(
171    array: ArrayView<'_, V>,
172    indices: ArrayView<'_, PiecewiseSequence>,
173    indices_ref: &ArrayRef,
174    ctx: &mut ExecutionCtx,
175) -> VortexResult<Option<ArrayRef>> {
176    let Some((starts, lengths)) = maybe_contiguous_slices(indices, ctx)? else {
177        return Ok(None);
178    };
179
180    let values = V::values(array);
181    let byte_width = V::byte_width(array);
182    let output_len = indices_ref.len();
183    let taken = match lengths {
184        Columnar::Constant(lengths) => {
185            let length = constant_unsigned_usize(&lengths);
186            match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
187                take_slices_constant_length(
188                    &values,
189                    byte_width,
190                    array.len(),
191                    starts.as_slice::<S>(),
192                    length,
193                    output_len,
194                    ctx.allocator(),
195                )
196            })
197        }
198        Columnar::Canonical(lengths) => {
199            let lengths = lengths.into_primitive();
200            match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
201                match_each_unsigned_integer_ptype!(lengths.ptype(), |L| {
202                    take_slices(
203                        &values,
204                        byte_width,
205                        array.len(),
206                        starts.as_slice::<S>(),
207                        lengths.as_slice::<L>(),
208                        output_len,
209                        ctx.allocator(),
210                    )
211                })
212            })
213        }
214    }?;
215    let validity = array.validity()?.take(indices_ref)?;
216    Ok(Some(
217        with_values(array, taken, output_len, validity)?.into_array(),
218    ))
219}