use fearless_simd::{dispatch, f64x8, i64x8, mask64x8, prelude::*, u64x8, u8x64};
pub(crate) use fearless_simd::Level;
pub(crate) fn level() -> Level {
#[cfg(not(test))]
return Level::new();
#[cfg(test)]
return crate::test_support::eval_level();
}
pub(crate) trait LanePred {
const FLIP: u64 = 0;
fn scalar(x: i64, y: i64) -> bool;
fn vector<S: Simd>(simd: S, a: i64x8<S>, b: i64x8<S>) -> mask64x8<S>;
}
macro_rules! lane_pred {
($(#[$doc:meta])* $name:ident, |$x:ident, $y:ident| $scalar:expr, |$simd:ident, $a:ident, $b:ident| $vector:expr) => {
$(#[$doc])*
pub(crate) struct $name;
impl LanePred for $name {
#[inline(always)]
fn scalar($x: i64, $y: i64) -> bool {
$scalar
}
#[inline(always)]
fn vector<S: Simd>($simd: S, $a: i64x8<S>, $b: i64x8<S>) -> mask64x8<S> {
$vector
}
}
};
}
#[inline(always)]
fn unsigned<S: Simd>(_: S, v: i64x8<S>) -> u64x8<S> {
v.bitcast()
}
#[inline(always)]
fn float<S: Simd>(_: S, v: i64x8<S>) -> f64x8<S> {
v.bitcast()
}
#[inline(always)]
fn non_negative<S: Simd>(simd: S, v: i64x8<S>) -> mask64x8<S> {
v.simd_ge(i64x8::splat(simd, 0))
}
lane_pred!(Eq, |x, y| x == y, |_simd, a, b| a.simd_eq(b));
lane_pred!(GtSigned, |x, y| x > y, |_simd, a, b| a.simd_gt(b));
lane_pred!(GtUnsigned, |x, y| (x as u64) > (y as u64), |simd, a, b| unsigned(
simd, a
)
.simd_gt(unsigned(simd, b)));
lane_pred!(
EqUnsignedSigned,
|x, y| y >= 0 && x == y,
|simd, a, b| non_negative(simd, b) & a.simd_eq(b)
);
lane_pred!(
LtUnsignedSigned,
|x, y| y >= 0 && (x as u64) < (y as u64),
|simd, a, b| non_negative(simd, b) & unsigned(simd, a).simd_lt(unsigned(simd, b))
);
lane_pred!(
LeUnsignedSigned,
|x, y| y >= 0 && (x as u64) <= (y as u64),
|simd, a, b| non_negative(simd, b) & unsigned(simd, a).simd_le(unsigned(simd, b))
);
lane_pred!(
EqFloat,
|x, y| f64::from_bits(x as u64) == f64::from_bits(y as u64),
|simd, a, b| float(simd, a).simd_eq(float(simd, b))
);
lane_pred!(
GtFloat,
|x, y| f64::from_bits(x as u64) > f64::from_bits(y as u64),
|simd, a, b| float(simd, a).simd_gt(float(simd, b))
);
lane_pred!(
GeFloat,
|x, y| f64::from_bits(x as u64) >= f64::from_bits(y as u64),
|simd, a, b| float(simd, a).simd_ge(float(simd, b))
);
#[inline(always)]
fn total_key<S: Simd>(_: S, v: i64x8<S>) -> i64x8<S> {
let sign: u64x8<S> = (v >> 63).bitcast();
v ^ (sign >> 1).bitcast::<i64x8<S>>()
}
lane_pred!(
GtTotal,
|x, y| f64::from_bits(x as u64).total_cmp(&f64::from_bits(y as u64)).is_gt(),
|simd, a, b| total_key(simd, a).simd_gt(total_key(simd, b))
);
pub(crate) struct Not<P>(std::marker::PhantomData<P>);
impl<P: LanePred> LanePred for Not<P> {
const FLIP: u64 = !P::FLIP;
#[inline(always)]
fn scalar(x: i64, y: i64) -> bool {
!P::scalar(x, y)
}
#[inline(always)]
fn vector<S: Simd>(simd: S, a: i64x8<S>, b: i64x8<S>) -> mask64x8<S> {
P::vector(simd, a, b)
}
}
#[inline(always)]
fn lanes<S: Simd>(simd: S, word: &[i64; 64], at: usize) -> i64x8<S> {
i64x8::from_slice(simd, &word[at..at + 8])
}
#[inline(always)]
fn mask_word<S: Simd>(_: S, mut mask: impl FnMut(usize) -> mask64x8<S>) -> u64 {
let mut bits = 0u64;
macro_rules! step {
($($at:literal)*) => {
$(bits = (bits | mask($at).to_bitmask()).rotate_right(8);)*
};
}
step!(0 8 16 24 32 40 48 56);
bits
}
fn debug_assert_words(lanes: &[&[i64]], words: usize) {
debug_assert!(
lanes.iter().all(|l| l.len() == words * 64),
"a lane window is 64 lanes a word"
);
}
pub(crate) fn pred_bits<P: LanePred>(level: Level, a: &[i64], b: &[i64], out: &mut [u64]) {
debug_assert_words(&[a, b], out.len());
dispatch!(level, simd => pred_bits_at::<_, P>(simd, a, b, out));
}
#[inline(always)]
fn pred_bits_at<S: Simd, P: LanePred>(simd: S, a: &[i64], b: &[i64], out: &mut [u64]) {
for ((w, a), b) in out.iter_mut().zip(a.as_chunks::<64>().0).zip(b.as_chunks::<64>().0) {
*w = P::FLIP ^ mask_word(simd, |at| P::vector(simd, lanes(simd, a, at), lanes(simd, b, at)));
}
}
pub(crate) fn truthy_bits(level: Level, src: &[i64], out: &mut [u64]) {
debug_assert_words(&[src], out.len());
dispatch!(level, simd => truthy_bits_at(simd, src, out));
}
#[inline(always)]
fn truthy_bits_at<S: Simd>(simd: S, src: &[i64], out: &mut [u64]) {
let zero = i64x8::splat(simd, 0);
for (w, src) in out.iter_mut().zip(src.as_chunks::<64>().0) {
*w = !mask_word(simd, |at| lanes(simd, src, at).simd_eq(zero));
}
}
pub(crate) fn in_set_bits(level: Level, a: &[i64], set: &[i64], out: &mut [u64]) {
debug_assert_words(&[a], out.len());
out.fill(0);
let (fours, rest) = set.as_chunks::<4>();
dispatch!(level, simd => {
for four in fours {
or_member_bits(simd, a, four, out);
}
match *rest {
[v0] => or_member_bits(simd, a, &[v0], out),
[v0, v1] => or_member_bits(simd, a, &[v0, v1], out),
[v0, v1, v2] => or_member_bits(simd, a, &[v0, v1, v2], out),
_ => {}
}
});
}
#[inline(always)]
fn or_member_bits<S: Simd, const N: usize>(simd: S, a: &[i64], set: &[i64; N], out: &mut [u64]) {
let set = set.map(|v| i64x8::splat(simd, v));
for (w, a) in out.iter_mut().zip(a.as_chunks::<64>().0) {
*w |= mask_word(simd, |at| {
let x = lanes(simd, a, at);
set[1..].iter().fold(x.simd_eq(set[0]), |hit, &v| hit | x.simd_eq(v))
});
}
}
pub(crate) fn null_bits(level: Level, rows: &[u8], cols: u64, out: &mut [u64]) {
debug_assert_eq!(out.len(), (rows.len() / 8).div_ceil(64), "one bit per row");
let (blocks, tail) = rows.as_chunks::<{ 64 * 8 }>();
dispatch!(level, simd => null_bits_at(simd, blocks, cols, out));
if let Some(w) = out.get_mut(blocks.len()) {
let mut word = 0u64;
for (j, row) in tail.as_chunks::<8>().0.iter().enumerate() {
word |= ((u64::from_le_bytes(*row) & cols != 0) as u64) << j;
}
*w = word;
}
}
#[inline(always)]
fn null_bits_at<S: Simd>(simd: S, blocks: &[[u8; 64 * 8]], cols: u64, out: &mut [u64]) {
let (cols, zero) = (u64x8::splat(simd, cols), u64x8::splat(simd, 0));
for (w, block) in out.iter_mut().zip(blocks) {
*w = !mask_word(simd, |at| {
let rows: u64x8<S> = u8x64::from_slice(simd, &block[at * 8..at * 8 + 64]).bitcast();
(rows & cols).simd_eq(zero)
});
}
}
pub(crate) fn blend(level: Level, take_a: &[u64], a: &[i64], b: &[i64], d: &mut [i64]) {
debug_assert_words(&[a, b, d], take_a.len());
dispatch!(level, simd => blend_at(simd, take_a, a, b, d));
}
#[inline(always)]
fn blend_at<S: Simd>(simd: S, take_a: &[u64], a: &[i64], b: &[i64], d: &mut [i64]) {
let words = a.as_chunks::<64>().0.iter().zip(b.as_chunks::<64>().0);
for ((&take, (a, b)), d) in take_a.iter().zip(words).zip(d.as_chunks_mut::<64>().0) {
for at in (0..64).step_by(8) {
mask64x8::from_bitmask(simd, take >> at)
.select(lanes(simd, a, at), lanes(simd, b, at))
.store_slice(&mut d[at..at + 8]);
}
}
}
pub(crate) fn bit_lanes(level: Level, bits: &[u64], d: &mut [i64]) {
debug_assert_words(&[d], bits.len());
dispatch!(level, simd => bit_lanes_at(simd, bits, d));
}
#[inline(always)]
fn bit_lanes_at<S: Simd>(simd: S, bits: &[u64], d: &mut [i64]) {
let one = u64x8::splat(simd, 1);
let lane = u64x8::from_fn(simd, |i| i as u64);
for (&word, d) in bits.iter().zip(d.as_chunks_mut::<64>().0) {
let word = u64x8::splat(simd, word);
for at in (0..64).step_by(8) {
let v: i64x8<S> = ((word >> (lane + u64x8::splat(simd, at as u64))) & one).bitcast();
v.store_slice(&mut d[at..at + 8]);
}
}
}
#[cfg(test)]
#[path = "tests/simd.rs"]
mod tests;