use std::arch::x86_64::__m128i;
use std::arch::x86_64::__m256;
use std::arch::x86_64::__m256i;
use std::arch::x86_64::_mm_loadu_si128;
use std::arch::x86_64::_mm256_add_epi32;
use std::arch::x86_64::_mm256_and_si256;
use std::arch::x86_64::_mm256_castsi256_ps;
use std::arch::x86_64::_mm256_cmpeq_epi32;
use std::arch::x86_64::_mm256_cvtepu16_epi32;
use std::arch::x86_64::_mm256_i32gather_epi32;
use std::arch::x86_64::_mm256_loadu_si256;
use std::arch::x86_64::_mm256_mask_i32gather_epi32;
use std::arch::x86_64::_mm256_max_epu32;
use std::arch::x86_64::_mm256_min_epu32;
use std::arch::x86_64::_mm256_movemask_ps;
use std::arch::x86_64::_mm256_set1_epi32;
use std::arch::x86_64::_mm256_setr_epi32;
use std::arch::x86_64::_mm256_setzero_si256;
use std::arch::x86_64::_mm256_slli_epi32;
use std::arch::x86_64::_mm256_srli_epi32;
use std::arch::x86_64::_mm256_srlv_epi32;
use std::slice::from_raw_parts;
use fearless_simd::Level;
use fearless_simd::Simd;
use num_traits::AsPrimitive;
use crate::BitBuffer;
use crate::BitBufferView;
use crate::BufferMut;
use crate::bit::get_bit;
use crate::bit::get_bit_unchecked;
pub(super) fn take<I: AsPrimitive<usize>>(
bits: BitBufferView<'_>,
indices: &[I],
validity: Option<BitBufferView<'_>>,
) -> Option<BitBuffer> {
let avx2 = Level::new().as_avx2()?;
if bits.len() >= (1 << 31) - 8 {
return None;
}
let last_dword = (bits.offset() + bits.len() - 1) / 32;
if (last_dword + 1) * 4 > bits.inner().len() {
return None;
}
let size = size_of::<I>();
if size == size_of::<u16>() {
let indices = unsafe { from_raw_parts(indices.as_ptr().cast::<u16>(), indices.len()) };
let indices_ptr: *const u16 = indices.as_ptr();
unsafe {
let group = |group_idx| -> __m256i {
let group_ptr: *const __m128i = indices_ptr.add(group_idx * 8).cast();
let vector: __m128i = _mm_loadu_si128(group_ptr);
_mm256_cvtepu16_epi32(vector)
};
return Some(avx2.vectorize(|| take_group(bits, indices, validity, group)));
}
}
if size == size_of::<u32>() {
let indices = unsafe { from_raw_parts(indices.as_ptr().cast::<u32>(), indices.len()) };
let indices_ptr: *const u32 = indices.as_ptr();
unsafe {
let group = |group_idx| -> __m256i {
let group_ptr: *const __m256i = indices_ptr.add(group_idx * 8).cast();
_mm256_loadu_si256(group_ptr)
};
return Some(avx2.vectorize(|| take_group(bits, indices, validity, group)));
}
}
None
}
#[target_feature(enable = "avx2")]
unsafe fn take_group<I: AsPrimitive<usize>>(
bits: BitBufferView<'_>,
indices: &[I],
validity: Option<BitBufferView<'_>>,
load_group: impl Fn(usize) -> __m256i,
) -> BitBuffer {
let inner = bits.inner();
let base = inner.as_ptr();
let zero: __m256i = _mm256_setzero_si256();
let Some(validity) = validity else {
let gather = |_group_idx: usize, indices: __m256i| -> (__m256i, __m256i) {
let gathered = unsafe { _mm256_i32gather_epi32::<4>(base.cast(), indices) };
(gathered, zero)
};
return unsafe { take_lanes(bits, indices, load_group, gather, usize::MAX, None) };
};
let valid_bytes = validity.inner();
let validity_offset = validity.offset();
let validity_shift = validity_offset % 8;
let validity_first_byte = validity_offset / 8;
let has_non_byte_shift: usize = (validity_shift != 0).as_();
let lane_bits: __m256i = _mm256_setr_epi32(1, 2, 4, 8, 16, 32, 64, 128);
let gather = |group_idx: usize, indices: __m256i| -> (__m256i, __m256i) {
let byte = validity_first_byte + group_idx;
let next_byte = byte + has_non_byte_shift;
let lo = unsafe { *valid_bytes.get_unchecked(byte) as u16 };
let hi = unsafe { *valid_bytes.get_unchecked(next_byte) as u16 };
let window = lo | (hi << 8);
let valid = i32::from(window >> validity_shift);
let mask = _mm256_set1_epi32(valid);
let selected = _mm256_and_si256(mask, lane_bits);
let lanes = _mm256_cmpeq_epi32(selected, lane_bits);
let gathered =
unsafe { _mm256_mask_i32gather_epi32::<4>(zero, base.cast(), indices, lanes) };
(gathered, lanes)
};
unsafe {
take_lanes(
bits,
indices,
load_group,
gather,
bits.len() - 1,
Some((valid_bytes, validity_offset)),
)
}
}
#[target_feature(enable = "avx2")]
unsafe fn take_lanes<I>(
bits: BitBufferView<'_>,
indices: &[I],
load_group: impl Fn(usize) -> __m256i,
gather: impl Fn(usize, __m256i) -> (__m256i, __m256i),
max_index: usize,
validity: Option<(&[u8], usize)>,
) -> BitBuffer
where
I: AsPrimitive<usize>,
{
let total = indices.len();
let groups = total / 8;
let bit_offset: __m256i = _mm256_set1_epi32(bits.offset().as_());
let low_bits: __m256i = _mm256_set1_epi32(31);
let max_index: __m256i = _mm256_set1_epi32(max_index.as_());
let mut max_taken_index: __m256i = _mm256_setzero_si256();
let mut out = BufferMut::<u8>::with_capacity(total.div_ceil(8));
for group in 0..groups {
let original_indices: __m256i = load_group(group);
let clamped = _mm256_min_epu32(original_indices, max_index);
let clamped_shifted = _mm256_add_epi32(clamped, bit_offset);
let gather_indices = _mm256_srli_epi32::<5>(clamped_shifted);
let (gathered, lanes) = gather(group, gather_indices);
let taken_indices = _mm256_and_si256(original_indices, lanes);
max_taken_index = _mm256_max_epu32(max_taken_index, taken_indices);
let shift = _mm256_and_si256(clamped_shifted, low_bits);
let shifted = _mm256_srlv_epi32(gathered, shift);
let top: __m256i = _mm256_slli_epi32::<31>(shifted);
let top: __m256 = _mm256_castsi256_ps(top);
let msb: u8 = _mm256_movemask_ps(top).as_();
unsafe { out.push_unchecked(msb) };
}
let max_clamped = _mm256_min_epu32(max_taken_index, max_index);
let in_bounds: __m256i = _mm256_cmpeq_epi32(max_clamped, max_taken_index);
let in_bounds: __m256 = _mm256_castsi256_ps(in_bounds);
let msb = _mm256_movemask_ps(in_bounds);
assert_eq!(msb, 0xFF, "index out of bounds");
let tail = total % 8;
if tail == 0 {
return BitBuffer::new(out.freeze(), total);
}
let start = groups * 8;
let inner = bits.inner();
let mut byte = 0u8;
for bit in 0..tail {
let pos = start + bit;
let keep = validity.map_or(usize::MAX, |(valid_bytes, validity_offset)| {
(unsafe { get_bit_unchecked(valid_bytes.as_ptr(), validity_offset + pos) } as usize)
.wrapping_neg()
});
let idx = unsafe { indices.get_unchecked(pos) }.as_() & keep;
let value = get_bit(inner, bits.offset() + idx);
byte |= (value as u8) << bit;
}
unsafe { out.push_unchecked(byte) }
BitBuffer::new(out.freeze(), total)
}