diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 09e45a3..e1e80d8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - rust: [1.56.0, stable, nightly] + rust: [1.59.0, stable, nightly] features: ["+avx2", "+sse2"] env: RUSTFLAGS: "-C target-feature=${{matrix.features}}" @@ -34,18 +34,25 @@ jobs: cargo test --verbose --release --features "$FEATURES" build_aarch64: - runs-on: ubuntu-latest + runs-on: macos-14 strategy: matrix: - rust: [1.56.0, stable, nightly] + rust: [1.59.0, stable, nightly] + features: ["+neon"] + env: + RUSTFLAGS: "-C target-feature=${{matrix.features}}" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - target: aarch64-unknown-linux-gnu + target: aarch64-apple-darwin toolchain: ${{ matrix.rust }} - name: Tests (aarch64) - run: cargo check --target aarch64-unknown-linux-gnu + run: | + cargo test -v --no-default-features --tests --lib && + cargo build --verbose --features "$FEATURES" && + cargo test --verbose --features "$FEATURES" && + cargo test --verbose --release --features "$FEATURES" # Use clippy to lint for code smells clippy: diff --git a/Cargo.toml b/Cargo.toml index 8f782d8..06bd2d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.5.4" authors = ["bluss"] license = "MIT OR Apache-2.0" readme = "README.md" -rust-version = "1.56" +rust-version = "1.59" edition = "2021" description = "FixedBitSet is a simple bitset collection" diff --git a/src/block/mod.rs b/src/block/mod.rs index be64af9..ac7585b 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -5,12 +5,14 @@ use core::hash::{Hash, Hasher}; not(target_arch = "wasm32"), not(target_feature = "sse2"), not(target_feature = "avx2"), + not(target_feature = "neon"), ))] mod default; #[cfg(all( not(target_arch = "wasm32"), not(target_feature = "sse2"), not(target_feature = "avx2"), + not(target_feature = "neon"), ))] pub use self::default::*; @@ -32,6 +34,11 @@ mod avx2; #[cfg(all(not(target_arch = "wasm32"), target_feature = "avx2",))] pub use self::avx2::*; +#[cfg(all(not(target_arch = "wasm32"), target_feature = "neon",))] +mod neon; +#[cfg(all(not(target_arch = "wasm32"), target_feature = "neon",))] +pub use self::neon::*; + #[cfg(target_arch = "wasm32")] mod wasm32; #[cfg(target_arch = "wasm32")] diff --git a/src/block/neon.rs b/src/block/neon.rs new file mode 100644 index 0000000..538ec70 --- /dev/null +++ b/src/block/neon.rs @@ -0,0 +1,108 @@ +#[cfg(target_arch = "aarch64")] +use core::arch::aarch64::*; +use core::{ + cmp::Ordering, + hash::{Hash, Hasher}, + iter::Iterator, + ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}, +}; + +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub struct Block(pub uint8x16_t); + +impl Block { + pub const USIZE_COUNT: usize = core::mem::size_of::() / core::mem::size_of::(); + pub const NONE: Self = Self::from_usize_array([0; Self::USIZE_COUNT]); + pub const ALL: Self = Self::from_usize_array([core::usize::MAX; Self::USIZE_COUNT]); + pub const BITS: usize = core::mem::size_of::() * 8; + + #[inline] + pub fn into_usize_array(self) -> [usize; Self::USIZE_COUNT] { + unsafe { core::mem::transmute(self.0) } + } + + #[inline] + pub const fn from_usize_array(array: [usize; Self::USIZE_COUNT]) -> Self { + Self(unsafe { core::mem::transmute(array) }) + } + + #[inline] + pub fn is_empty(self) -> bool { + unsafe { vmaxvq_u8(self.0) == 0 } + } + + #[inline] + pub fn andnot(self, other: Self) -> Self { + Self(unsafe { vbicq_u8(self.0, other.0) }) + } +} + +impl Not for Block { + type Output = Block; + #[inline] + fn not(self) -> Self::Output { + Self(unsafe { vmvnq_u8(self.0) }) + } +} + +impl BitAnd for Block { + type Output = Block; + #[inline] + fn bitand(self, other: Self) -> Self::Output { + Self(unsafe { vandq_u8(self.0, other.0) }) + } +} + +impl BitAndAssign for Block { + #[inline] + fn bitand_assign(&mut self, other: Self) { + unsafe { + self.0 = vandq_u8(self.0, other.0); + } + } +} + +impl BitOr for Block { + type Output = Block; + #[inline] + fn bitor(self, other: Self) -> Self::Output { + Self(unsafe { vorrq_u8(self.0, other.0) }) + } +} + +impl BitOrAssign for Block { + #[inline] + fn bitor_assign(&mut self, other: Self) { + unsafe { + self.0 = vorrq_u8(self.0, other.0); + } + } +} + +impl BitXor for Block { + type Output = Block; + #[inline] + fn bitxor(self, other: Self) -> Self::Output { + Self(unsafe { veorq_u8(self.0, other.0) }) + } +} + +impl BitXorAssign for Block { + #[inline] + fn bitxor_assign(&mut self, other: Self) { + unsafe { + self.0 = veorq_u8(self.0, other.0); + } + } +} + +impl PartialEq for Block { + #[inline] + fn eq(&self, other: &Self) -> bool { + unsafe { + let eq = vceqq_u8(self.0, other.0); + vminvq_u8(eq) == 0xff + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 8b87ff3..bea4cb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ //! When SIMD is not available on the target, the crate will gracefully fallback to a default implementation. It is intended to add support for other SIMD architectures //! once they appear in stable Rust. //! -//! Currently only SSE2/AVX2 on x86/x86_64 and wasm32 SIMD are supported as this is what stable Rust supports. +//! Currently only SSE2/AVX2 on x86/x86_64, NEON on aarch64, and wasm32 SIMD are supported as this is what stable Rust supports. #![no_std] #![deny(clippy::undocumented_unsafe_blocks)]