#[inline]
pub fn pack_validity(valid: &[u8], out: &mut [u8]) {
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
{
unsafe { pack_validity_avx2(valid, out) }
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
{
pack_validity_scalar(valid, out)
}
}
#[inline]
pub fn pack_validity_scalar(valid: &[u8], out: &mut [u8]) {
out.iter_mut().for_each(|b| *b = 0);
for (i, &v) in valid.iter().enumerate() {
if v != 0 {
out[i / 8] |= 1 << (i % 8);
}
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
#[inline]
pub unsafe fn pack_validity_avx2(valid: &[u8], out: &mut [u8]) {
use std::arch::x86_64::*;
let ptr = valid.as_ptr();
let out_ptr = out.as_mut_ptr();
let n = valid.len();
let mut i = 0;
let mut out_idx = 0;
while i + 32 <= n {
let v = _mm256_loadu_si256(ptr.add(i) as *const __m256i);
let is_zero = _mm256_cmpeq_epi8(v, _mm256_setzero_si256());
let mask = _mm256_movemask_epi8(is_zero) as u32;
let valid_mask = !mask;
(out_ptr.add(out_idx) as *mut u32).write_unaligned(valid_mask);
out_idx += 4;
i += 32;
}
for j in i..n {
if *ptr.add(j) != 0 {
*out_ptr.add(j / 8) |= 1 << (j % 8);
}
}
}
#[inline]
pub fn is_ascii(data: &[u8]) -> bool {
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
{
unsafe { is_ascii_avx2(data) }
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
{
is_ascii_scalar(data)
}
}
#[inline]
pub fn is_ascii_scalar(data: &[u8]) -> bool {
data.iter().all(|&b| b < 128)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
#[inline]
pub unsafe fn is_ascii_avx2(data: &[u8]) -> bool {
use std::arch::x86_64::*;
let ptr = data.as_ptr();
let len = data.len();
let mut i = 0;
while i + 32 <= len {
let chunk = _mm256_loadu_si256(ptr.add(i) as *const __m256i);
if _mm256_movemask_epi8(chunk) != 0 {
return false; }
i += 32;
}
data[i..].iter().all(|&b| b < 128)
}
#[cfg(target_arch = "x86_64")]
#[inline]
pub fn is_ascii_runtime(data: &[u8]) -> bool {
if is_x86_feature_detected!("avx2") {
unsafe { is_ascii_avx2(data) }
} else {
is_ascii_scalar(data)
}
}
#[cfg(not(target_arch = "x86_64"))]
#[inline]
pub fn is_ascii_runtime(data: &[u8]) -> bool {
is_ascii_scalar(data)
}
#[inline]
pub fn i32_to_f64(src: &[i32], dst: &mut [f64]) {
assert!(dst.len() >= src.len(), "destination too small");
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
{
unsafe { i32_to_f64_avx2(src, dst) }
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
{
i32_to_f64_scalar(src, dst)
}
}
#[inline]
pub fn i32_to_f64_scalar(src: &[i32], dst: &mut [f64]) {
for (d, &s) in dst.iter_mut().zip(src.iter()) {
*d = s as f64;
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
#[inline]
pub unsafe fn i32_to_f64_avx2(src: &[i32], dst: &mut [f64]) {
use std::arch::x86_64::*;
let src_ptr = src.as_ptr();
let dst_ptr = dst.as_mut_ptr();
let len = src.len();
let mut i = 0;
while i + 4 <= len {
let ints = _mm_loadu_si128(src_ptr.add(i) as *const __m128i);
let floats = _mm256_cvtepi32_pd(ints);
_mm256_storeu_pd(dst_ptr.add(i), floats);
i += 4;
}
for j in i..len {
*dst_ptr.add(j) = *src_ptr.add(j) as f64;
}
}
#[cfg(target_arch = "x86_64")]
#[inline]
pub fn i32_to_f64_runtime(src: &[i32], dst: &mut [f64]) {
assert!(dst.len() >= src.len(), "destination too small");
if is_x86_feature_detected!("avx2") {
unsafe { i32_to_f64_avx2(src, dst) }
} else {
i32_to_f64_scalar(src, dst)
}
}
#[cfg(not(target_arch = "x86_64"))]
#[inline]
pub fn i32_to_f64_runtime(src: &[i32], dst: &mut [f64]) {
i32_to_f64_scalar(src, dst)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pack_validity_scalar() {
let valid = [1u8, 0, 1, 1, 0, 0, 1, 0];
let mut out = [0u8; 1];
pack_validity_scalar(&valid, &mut out);
assert_eq!(out[0], 0b01001101);
}
#[test]
fn test_pack_validity_16() {
let valid = [1u8, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1];
let mut out = [0u8; 2];
pack_validity_scalar(&valid, &mut out);
assert_eq!(out[0], 0b11110011); assert_eq!(out[1], 0b11110000); }
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
#[test]
fn test_pack_validity_avx2() {
let mut valid = [0u8; 32];
valid[0] = 1;
valid[7] = 1;
valid[8] = 1;
valid[31] = 1;
let mut out_scalar = [0u8; 4];
let mut out_avx2 = [0u8; 4];
pack_validity_scalar(&valid, &mut out_scalar);
unsafe { pack_validity_avx2(&valid, &mut out_avx2) };
assert_eq!(out_scalar, out_avx2);
}
#[test]
fn test_is_ascii_scalar() {
assert!(is_ascii_scalar(b"hello world"));
assert!(is_ascii_scalar(b"IBM US Equity"));
assert!(is_ascii_scalar(b"PX_LAST"));
assert!(!is_ascii_scalar("café".as_bytes()));
assert!(!is_ascii_scalar(&[0x80]));
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
#[test]
fn test_is_ascii_avx2() {
let ascii_long = b"This is a long ASCII string that exceeds 32 bytes easily!";
let non_ascii = "This has a café in it somewhere in the middle here".as_bytes();
unsafe {
assert!(is_ascii_avx2(ascii_long));
assert!(!is_ascii_avx2(non_ascii));
}
}
#[test]
fn test_i32_to_f64_scalar() {
let src = [1i32, -2, 3, 1000000];
let mut dst = [0.0f64; 4];
i32_to_f64_scalar(&src, &mut dst);
assert_eq!(dst, [1.0, -2.0, 3.0, 1000000.0]);
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
#[test]
fn test_i32_to_f64_avx2() {
let src: Vec<i32> = (0..100).collect();
let mut dst_scalar = vec![0.0f64; 100];
let mut dst_avx2 = vec![0.0f64; 100];
i32_to_f64_scalar(&src, &mut dst_scalar);
unsafe { i32_to_f64_avx2(&src, &mut dst_avx2) };
assert_eq!(dst_scalar, dst_avx2);
}
}