|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// SPDX-FileCopyrightText: Copyright the Vortex contributors |
| 3 | + |
| 4 | +use num_traits::AsPrimitive; |
| 5 | +use vortex_buffer::BitBufferMut; |
| 6 | +use vortex_buffer::BufferMut; |
| 7 | +use vortex_error::VortexResult; |
| 8 | +use vortex_error::vortex_ensure; |
| 9 | +use vortex_mask::Mask; |
| 10 | + |
| 11 | +use crate::ArrayRef; |
| 12 | +use crate::Canonical; |
| 13 | +use crate::ExecutionCtx; |
| 14 | +use crate::IntoArray; |
| 15 | +use crate::arrays::ConstantArray; |
| 16 | +use crate::arrays::FixedSizeListArray; |
| 17 | +use crate::arrays::List; |
| 18 | +use crate::arrays::ListArray; |
| 19 | +use crate::arrays::ListViewArray; |
| 20 | +use crate::arrays::PrimitiveArray; |
| 21 | +use crate::arrays::fixed_size_list::FixedSizeListArrayExt; |
| 22 | +use crate::arrays::fixed_size_list::FixedSizeListArraySlotsExt; |
| 23 | +use crate::arrays::list::ListArrayExt; |
| 24 | +use crate::arrays::list::ListArraySlotsExt; |
| 25 | +use crate::arrays::listview::ListViewArrayExt; |
| 26 | +use crate::arrays::listview::ListViewArraySlotsExt; |
| 27 | +use crate::dtype::DType; |
| 28 | +use crate::dtype::NativePType; |
| 29 | +use crate::match_each_integer_ptype; |
| 30 | +use crate::validity::Validity; |
| 31 | + |
| 32 | +/// The elements a per-element lambda runs over, list after list, and how to rebuild the lists |
| 33 | +/// around one result per element. |
| 34 | +/// |
| 35 | +/// Every element of a non-null list is included. A null list's elements are dropped when they must |
| 36 | +/// not reach the lambda, for a body that can fail; otherwise they stay, and their results stay |
| 37 | +/// hidden behind the list's validity. Nothing is copied unless the included ranges are not already |
| 38 | +/// laid out back to back. |
| 39 | +pub(super) struct ListElements { |
| 40 | + elements: ArrayRef, |
| 41 | + /// Where each list's elements start, then where the last one's end: `n + 1` offsets from 0. |
| 42 | + offsets: ArrayRef, |
| 43 | + validity: Validity, |
| 44 | +} |
| 45 | + |
| 46 | +impl ListElements { |
| 47 | + pub(super) fn new( |
| 48 | + list: ArrayRef, |
| 49 | + keep_null_lists: bool, |
| 50 | + ctx: &mut ExecutionCtx, |
| 51 | + ) -> VortexResult<Self> { |
| 52 | + // A list array is used as it is, unless a null list holds elements that must not reach |
| 53 | + // the lambda. Writers usually leave null lists empty. |
| 54 | + if let Some(list) = list.as_opt::<List>() |
| 55 | + && list.offset_at(0)? == 0 |
| 56 | + { |
| 57 | + let validity = list.list_validity(); |
| 58 | + if keep_null_lists || !null_lists_hold_elements(list.offsets(), &validity, ctx)? { |
| 59 | + return Ok(Self { |
| 60 | + elements: list.sliced_elements()?, |
| 61 | + offsets: list.offsets().clone(), |
| 62 | + validity, |
| 63 | + }); |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + let (elements, starts, mut sizes, validity) = list_parts(list, ctx)?; |
| 68 | + if !keep_null_lists && !matches!(validity, Validity::NonNullable | Validity::AllValid) { |
| 69 | + let valid = validity.execute_mask(sizes.len(), ctx)?; |
| 70 | + for (list, size) in sizes.iter_mut().enumerate() { |
| 71 | + if !valid.value(list) { |
| 72 | + *size = 0; |
| 73 | + } |
| 74 | + } |
| 75 | + } |
| 76 | + let elements = gather(elements, &starts, &sizes)?; |
| 77 | + let mut offsets = BufferMut::<u64>::with_capacity(sizes.len() + 1); |
| 78 | + let mut end = 0; |
| 79 | + offsets.push(end); |
| 80 | + for size in sizes { |
| 81 | + end += size; |
| 82 | + offsets.push(end); |
| 83 | + } |
| 84 | + Ok(Self { |
| 85 | + elements, |
| 86 | + offsets: offsets.into_array(), |
| 87 | + validity, |
| 88 | + }) |
| 89 | + } |
| 90 | + |
| 91 | + /// The number of elements each list contributes, in list order. |
| 92 | + fn sizes(&self, ctx: &mut ExecutionCtx) -> VortexResult<Vec<u64>> { |
| 93 | + let offsets = to_u64s(&self.offsets, ctx)?; |
| 94 | + Ok(offsets |
| 95 | + .windows(2) |
| 96 | + .map(|bound| bound[1] - bound[0]) |
| 97 | + .collect()) |
| 98 | + } |
| 99 | + |
| 100 | + /// The number of elements. |
| 101 | + pub(super) fn len(&self) -> usize { |
| 102 | + self.elements.len() |
| 103 | + } |
| 104 | + |
| 105 | + /// The elements, list after list. |
| 106 | + pub(super) fn elements(&self) -> &ArrayRef { |
| 107 | + &self.elements |
| 108 | + } |
| 109 | + |
| 110 | + /// Each element's position within its list: sizes `[2, 0, 3]` give `[0, 1, 0, 1, 2]`. |
| 111 | + pub(super) fn indices(&self, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> { |
| 112 | + // A list's positions are written in whole blocks, `0..BLOCK` then `BLOCK..2 * BLOCK` and so |
| 113 | + // on, and the last block may spill past the list's end: the next list overwrites the spill, |
| 114 | + // and the buffer has room for the final one. A list no longer than a block then costs one |
| 115 | + // store and no branch on its length, which a position-by-position loop mispredicts at the |
| 116 | + // end of most lists. |
| 117 | + const BLOCK: usize = 16; |
| 118 | + let len = self.len(); |
| 119 | + let mut indices = BufferMut::<u64>::with_capacity(len + BLOCK); |
| 120 | + let out = indices.as_mut_ptr(); |
| 121 | + let first: [u64; BLOCK] = std::array::from_fn(|i| i as u64); |
| 122 | + let mut start = 0; |
| 123 | + for size in self.sizes(ctx)? { |
| 124 | + let end = start + usize::try_from(size)?; |
| 125 | + assert!(end <= len, "list sizes add up to more than {len} elements"); |
| 126 | + let (mut at, mut block) = (start, first); |
| 127 | + // An empty list writes a block too, which the next list overwrites. |
| 128 | + loop { |
| 129 | + // SAFETY: `at` is `start` or below `end`, so at most `len`, and the buffer has |
| 130 | + // capacity for `len + BLOCK`. |
| 131 | + unsafe { out.add(at).cast::<[u64; BLOCK]>().write_unaligned(block) }; |
| 132 | + at += BLOCK; |
| 133 | + if at >= end { |
| 134 | + break; |
| 135 | + } |
| 136 | + block = block.map(|index| index + BLOCK as u64); |
| 137 | + } |
| 138 | + start = end; |
| 139 | + } |
| 140 | + // SAFETY: every position below `start` was written by the list that holds it. |
| 141 | + unsafe { indices.set_len(start) }; |
| 142 | + Ok(indices.freeze().into_array()) |
| 143 | + } |
| 144 | + |
| 145 | + /// Each capture's one value per list, repeated for each of that list's elements: sizes |
| 146 | + /// `[2, 0, 3]` turn `[a, b, c]` into `[a, a, c, c, c]`. |
| 147 | + pub(super) fn repeat( |
| 148 | + &self, |
| 149 | + captures: Vec<ArrayRef>, |
| 150 | + ctx: &mut ExecutionCtx, |
| 151 | + ) -> VortexResult<Vec<ArrayRef>> { |
| 152 | + // Which list each element is in, built once for all the captures that are not constant. |
| 153 | + let lists = if captures.iter().all(|values| values.as_constant().is_some()) { |
| 154 | + None |
| 155 | + } else { |
| 156 | + Some(self.lists(ctx)?) |
| 157 | + }; |
| 158 | + let mut repeated = Vec::with_capacity(captures.len()); |
| 159 | + for values in captures { |
| 160 | + repeated.push(match (values.as_constant(), &lists) { |
| 161 | + (Some(value), _) => ConstantArray::new(value, self.len()).into_array(), |
| 162 | + // TODO(mk): repeat each value as one run of a run-end array instead of copying it |
| 163 | + // once per element, once run-end lives in vortex-array. |
| 164 | + // The body may read the repeated values once for every slice it takes, so compute |
| 165 | + // them once. |
| 166 | + (None, Some(lists)) => values |
| 167 | + .take(lists.clone())? |
| 168 | + .execute::<Canonical>(ctx)? |
| 169 | + .into_array(), |
| 170 | + (None, None) => { |
| 171 | + unreachable!("the lists are numbered when a capture is not constant") |
| 172 | + } |
| 173 | + }); |
| 174 | + } |
| 175 | + Ok(repeated) |
| 176 | + } |
| 177 | + |
| 178 | + /// Which list each element is in: sizes `[2, 0, 3]` give `[0, 0, 2, 2, 2]`, in the narrowest |
| 179 | + /// unsigned type that numbers every list. |
| 180 | + fn lists(&self, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> { |
| 181 | + let sizes = self.sizes(ctx)?; |
| 182 | + let last = sizes.len().saturating_sub(1); |
| 183 | + if u8::try_from(last).is_ok() { |
| 184 | + list_numbers::<u8>(&sizes, self.len()) |
| 185 | + } else if u16::try_from(last).is_ok() { |
| 186 | + list_numbers::<u16>(&sizes, self.len()) |
| 187 | + } else if u32::try_from(last).is_ok() { |
| 188 | + list_numbers::<u32>(&sizes, self.len()) |
| 189 | + } else { |
| 190 | + list_numbers::<u64>(&sizes, self.len()) |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + /// The lists again, with one value per element as their new elements. |
| 195 | + pub(super) fn rebuild(self, values: ArrayRef) -> VortexResult<ArrayRef> { |
| 196 | + vortex_ensure!( |
| 197 | + values.len() == self.len(), |
| 198 | + "expected one value per element: {} values for {} elements", |
| 199 | + values.len(), |
| 200 | + self.len() |
| 201 | + ); |
| 202 | + // SAFETY: the offsets start at 0, never decrease, and end at the number of elements, |
| 203 | + // which is `values.len()`; the validity is the input lists'. |
| 204 | + Ok(unsafe { ListArray::new_unchecked(values, self.offsets, self.validity) }.into_array()) |
| 205 | + } |
| 206 | +} |
| 207 | + |
| 208 | +/// Whether a null list holds elements, which a lambda that can fail must not see. |
| 209 | +fn null_lists_hold_elements( |
| 210 | + offsets: &ArrayRef, |
| 211 | + validity: &Validity, |
| 212 | + ctx: &mut ExecutionCtx, |
| 213 | +) -> VortexResult<bool> { |
| 214 | + if matches!(validity, Validity::NonNullable | Validity::AllValid) { |
| 215 | + return Ok(false); |
| 216 | + } |
| 217 | + let offsets = offsets.clone().execute::<PrimitiveArray>(ctx)?; |
| 218 | + let valid = validity.execute_mask(offsets.len() - 1, ctx)?; |
| 219 | + Ok(match_each_integer_ptype!(offsets.ptype(), |T| { |
| 220 | + offsets |
| 221 | + .as_slice::<T>() |
| 222 | + .windows(2) |
| 223 | + .enumerate() |
| 224 | + .any(|(list, bound)| bound[0] != bound[1] && !valid.value(list)) |
| 225 | + })) |
| 226 | +} |
| 227 | + |
| 228 | +/// Each list's number, once for each of its elements, for lists of the given sizes. |
| 229 | +fn list_numbers<T: NativePType>(sizes: &[u64], len: usize) -> VortexResult<ArrayRef> |
| 230 | +where |
| 231 | + usize: AsPrimitive<T>, |
| 232 | +{ |
| 233 | + let mut numbers = BufferMut::<T>::with_capacity(len); |
| 234 | + for (list, &size) in sizes.iter().enumerate() { |
| 235 | + numbers.push_n(list.as_(), usize::try_from(size)?); |
| 236 | + } |
| 237 | + Ok(numbers.freeze().into_array()) |
| 238 | +} |
| 239 | + |
| 240 | +/// The elements, the start and size of each list's range of them, and the lists' validity. |
| 241 | +type ListParts = (ArrayRef, Vec<u64>, Vec<u64>, Validity); |
| 242 | + |
| 243 | +fn list_parts(list: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<ListParts> { |
| 244 | + if let Some(list) = list.as_opt::<List>() { |
| 245 | + let bounds = to_u64s(list.offsets(), ctx)?; |
| 246 | + let starts = bounds[..list.len()].to_vec(); |
| 247 | + let sizes = bounds.windows(2).map(|bound| bound[1] - bound[0]).collect(); |
| 248 | + return Ok((list.elements().clone(), starts, sizes, list.list_validity())); |
| 249 | + } |
| 250 | + if let DType::FixedSizeList(_, size, _) = list.dtype() { |
| 251 | + let size = u64::from(*size); |
| 252 | + let list = list.execute::<FixedSizeListArray>(ctx)?; |
| 253 | + let starts = (0..list.len() as u64).map(|i| i * size).collect(); |
| 254 | + let sizes = vec![size; list.len()]; |
| 255 | + let validity = list.fixed_size_list_validity(); |
| 256 | + return Ok((list.elements().clone(), starts, sizes, validity)); |
| 257 | + } |
| 258 | + let list = list.execute::<ListViewArray>(ctx)?; |
| 259 | + let starts = to_u64s(list.offsets(), ctx)?; |
| 260 | + let sizes = to_u64s(list.sizes(), ctx)?; |
| 261 | + let validity = list.listview_validity(); |
| 262 | + Ok((list.elements().clone(), starts, sizes, validity)) |
| 263 | +} |
| 264 | + |
| 265 | +/// The elements at `starts[i]..starts[i] + sizes[i]` of each list, in list order. |
| 266 | +fn gather(elements: ArrayRef, starts: &[u64], sizes: &[u64]) -> VortexResult<ArrayRef> { |
| 267 | + let mut range: Option<(u64, u64)> = None; |
| 268 | + let (mut back_to_back, mut in_order) = (true, true); |
| 269 | + for (&start, &size) in starts.iter().zip(sizes).filter(|(_, size)| **size > 0) { |
| 270 | + if let Some((_, end)) = range { |
| 271 | + back_to_back &= start == end; |
| 272 | + in_order &= start >= end; |
| 273 | + } |
| 274 | + range = Some((range.map_or(start, |(first, _)| first), start + size)); |
| 275 | + } |
| 276 | + |
| 277 | + let Some((first, end)) = range else { |
| 278 | + return elements.slice(0..0); |
| 279 | + }; |
| 280 | + if back_to_back { |
| 281 | + return elements.slice(usize::try_from(first)?..usize::try_from(end)?); |
| 282 | + } |
| 283 | + if in_order { |
| 284 | + // Copy the ranges, keeping their order: a filter by a mask of them. |
| 285 | + let mut included = BitBufferMut::new_unset(elements.len()); |
| 286 | + for (&start, &size) in starts.iter().zip(sizes) { |
| 287 | + included.fill_range( |
| 288 | + usize::try_from(start)?, |
| 289 | + usize::try_from(start + size)?, |
| 290 | + true, |
| 291 | + ); |
| 292 | + } |
| 293 | + return elements.filter(Mask::from(included.freeze())); |
| 294 | + } |
| 295 | + // Ranges that overlap or run out of order, as in a list view. |
| 296 | + let total = usize::try_from(sizes.iter().sum::<u64>())?; |
| 297 | + let mut indices = BufferMut::<u64>::with_capacity(total); |
| 298 | + for (&start, &size) in starts.iter().zip(sizes) { |
| 299 | + indices.extend(start..start + size); |
| 300 | + } |
| 301 | + elements.take(indices.freeze().into_array()) |
| 302 | +} |
| 303 | + |
| 304 | +fn to_u64s(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Vec<u64>> { |
| 305 | + let array = array.clone().execute::<PrimitiveArray>(ctx)?; |
| 306 | + Ok(match_each_integer_ptype!(array.ptype(), |T| { |
| 307 | + array |
| 308 | + .as_slice::<T>() |
| 309 | + .iter() |
| 310 | + .map(|&value| value.as_()) |
| 311 | + .collect() |
| 312 | + })) |
| 313 | +} |
0 commit comments