|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// SPDX-FileCopyrightText: Copyright the Vortex contributors |
| 3 | + |
| 4 | +//! NEON byte-table take for `u8` codes and at most 16 one-byte values. |
| 5 | +
|
| 6 | +use std::arch::aarch64::uint8x16_t; |
| 7 | +use std::arch::aarch64::vdupq_n_u8; |
| 8 | +use std::arch::aarch64::vld1q_u8; |
| 9 | +use std::arch::aarch64::vmaxq_u8; |
| 10 | +use std::arch::aarch64::vmaxvq_u8; |
| 11 | +use std::arch::aarch64::vqtbl1q_u8; |
| 12 | +use std::arch::aarch64::vst1q_u8; |
| 13 | + |
| 14 | +use vortex_buffer::Buffer; |
| 15 | +use vortex_buffer::BufferMut; |
| 16 | + |
| 17 | +use super::FixedWidthTakeValue; |
| 18 | + |
| 19 | +pub(crate) fn take<T: FixedWidthTakeValue>(values: &[T], indices: &[u8]) -> Option<Buffer<T>> { |
| 20 | + if values.is_empty() || values.len() > 16 || size_of::<T>() != 1 || indices.len() < 64 { |
| 21 | + return None; |
| 22 | + } |
| 23 | + |
| 24 | + let mut table = [0u8; 16]; |
| 25 | + // SAFETY: one-byte values have the same representation as bytes, and the length is <= 16. |
| 26 | + unsafe { |
| 27 | + std::ptr::copy_nonoverlapping( |
| 28 | + values.as_ptr().cast::<u8>(), |
| 29 | + table.as_mut_ptr(), |
| 30 | + values.len(), |
| 31 | + ); |
| 32 | + } |
| 33 | + |
| 34 | + let mut output = BufferMut::<T>::with_capacity(indices.len()); |
| 35 | + let output_ptr = output.spare_capacity_mut().as_mut_ptr().cast::<u8>(); |
| 36 | + // SAFETY: AArch64 always provides NEON. Both pointers advance only by complete vectors. |
| 37 | + let (offset, max_code) = unsafe { take_vectors(&table, indices, output_ptr) }; |
| 38 | + |
| 39 | + for offset in offset..indices.len() { |
| 40 | + let code = usize::from(indices[offset]); |
| 41 | + assert!( |
| 42 | + code < values.len(), |
| 43 | + "take index {code} out of bounds for length {}", |
| 44 | + values.len() |
| 45 | + ); |
| 46 | + // SAFETY: the code was checked and this reserved output position is uninitialized. |
| 47 | + unsafe { |
| 48 | + output_ptr |
| 49 | + .add(offset) |
| 50 | + .write(*values.as_ptr().add(code).cast::<u8>()) |
| 51 | + }; |
| 52 | + } |
| 53 | + assert!( |
| 54 | + usize::from(max_code) < values.len(), |
| 55 | + "take index {max_code} out of bounds for length {}", |
| 56 | + values.len() |
| 57 | + ); |
| 58 | + // SAFETY: the vector loop and scalar remainder initialized every output value. |
| 59 | + unsafe { output.set_len(indices.len()) }; |
| 60 | + Some(output.freeze()) |
| 61 | +} |
| 62 | + |
| 63 | +unsafe fn take_vectors(table: &[u8; 16], indices: &[u8], output: *mut u8) -> (usize, u8) { |
| 64 | + let table = unsafe { vld1q_u8(table.as_ptr()) }; |
| 65 | + let mut max_codes: uint8x16_t = unsafe { vdupq_n_u8(0) }; |
| 66 | + let mut offset = 0; |
| 67 | + while offset + 16 <= indices.len() { |
| 68 | + let codes = unsafe { vld1q_u8(indices.as_ptr().add(offset)) }; |
| 69 | + max_codes = unsafe { vmaxq_u8(max_codes, codes) }; |
| 70 | + unsafe { vst1q_u8(output.add(offset), vqtbl1q_u8(table, codes)) }; |
| 71 | + offset += 16; |
| 72 | + } |
| 73 | + (offset, unsafe { vmaxvq_u8(max_codes) }) |
| 74 | +} |
0 commit comments