Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ jobs:
- name: Build benchmark
run: cargo codspeed build --features codspeed
env:
RUSTFLAGS: '-C target-cpu=native'
# Cap the ISA at the AVX2 baseline. `target-cpu=native` makes LLVM
# emit AVX-512-class instructions unconditionally based on the
# assigned runner's CPU, which CodSpeed's Valgrind simulation cannot
# execute (SIGILL / exit 132). x86-64-v3 stays within what Valgrind
# supports while still exercising the AVX2 kernel.
RUSTFLAGS: '-C target-cpu=x86-64-v3'

- name: Run benchmark
uses: CodSpeedHQ/action@v4
Expand Down
42 changes: 25 additions & 17 deletions src/simd/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,9 @@ macro_rules! impl_bits {

#[inline]
fn as_little_endian(&self) -> Self {
#[cfg(target_endian = "little")]
{
self.clone()
}
#[cfg(target_endian = "big")]
{
self.swap_bytes()
}
// The software bitmask already uses a canonical layout
// (lane `i` -> bit `i`), so there is no byte order to swap.
self.clone()
}

#[inline]
Expand Down Expand Up @@ -55,9 +50,27 @@ pub struct NeonBits(u64);

#[cfg(target_arch = "aarch64")]
impl NeonBits {
/// Wraps the raw `u64` produced by the NEON `vshrn` bitmask extraction,
/// normalizing it to the canonical `lane i -> nibble i` layout that
/// `first_offset`, `clear_high_bits` and `all_zero` assume.
///
/// On little-endian the extraction is already canonical. On big-endian the
/// `vreinterpretq_u16_u8` + `vshrn_n_u16` step packs each lane pair with its
/// two nibbles (and the byte pairs) reversed relative to lane order, so the
/// whole nibble sequence ends up reversed. Reverse the 16 nibbles to restore
/// `lane i -> nibble i` (a plain `swap_bytes` only fixes the byte order, not
/// the nibble order within each byte).
#[inline]
pub fn new(u: u64) -> Self {
Self(u)
#[cfg(target_endian = "little")]
{
Self(u)
}
#[cfg(target_endian = "big")]
{
let b = u.swap_bytes();
Self(((b & 0x0f0f_0f0f_0f0f_0f0f) << 4) | ((b & 0xf0f0_f0f0_f0f0_f0f0) >> 4))
}
}
}

Expand All @@ -72,14 +85,9 @@ impl BitMask for NeonBits {

#[inline]
fn as_little_endian(&self) -> Self {
#[cfg(target_endian = "little")]
{
Self::new(self.0)
}
#[cfg(target_endian = "big")]
{
Self::new(self.0.swap_bytes())
}
// `new` already normalized the bits to the canonical `lane i -> nibble i`
// layout on every target, so there is no byte order left to swap.
Self(self.0)
}

#[inline]
Expand Down
5 changes: 5 additions & 0 deletions src/simd/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub trait Mask: Sized + BitOr<Self> + BitOrAssign + BitAnd<Self> {
/// Trait for the bitmask of a vector Mask.
pub trait BitMask {
/// Total bits in the bitmask.
// Used only by the wide-SIMD kernels (sse2/avx2/avx512/neon); dead code on
// scalar-only targets like s390x that compile just `v128`.
#[allow(dead_code)]
const LEN: usize;

/// get the offset of the first `1` bit.
Expand All @@ -41,8 +44,10 @@ pub trait BitMask {
fn as_little_endian(&self) -> Self;

/// whether all bits are zero.
#[allow(dead_code)]
fn all_zero(&self) -> bool;

/// clear high n bits.
#[allow(dead_code)]
fn clear_high_bits(&self, n: usize) -> Self;
}
21 changes: 7 additions & 14 deletions src/simd/v128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,13 @@ impl Mask for Mask128 {
type Element = u8;

fn bitmask(self) -> Self::BitMask {
#[cfg(target_endian = "little")]
{
self.0
.iter()
.enumerate()
.fold(0, |acc, (i, &b)| acc | ((b as u16) << i))
}
#[cfg(target_endian = "big")]
{
self.0
.iter()
.enumerate()
.fold(0, |acc, (i, &b)| acc | ((b as u16) << (15 - i)))
}
// Built in software, so the bitmask has no inherent byte order: lane
// `i` maps to bit `i` on every target, matching `first_offset`'s plain
// `trailing_zeros`.
self.0
.iter()
.enumerate()
.fold(0, |acc, (i, &b)| acc | ((b as u16) << i))
}
}

Expand Down
Loading