diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b259f66..1031050 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,9 +18,11 @@ jobs: strategy: matrix: rust: [1.56.0, stable, nightly] - + features: ["+avx2", "+sse2", "-avx2,-sse2"] + env: + RUSTCFLAGS: "-C target-features={{matrix.features}}" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: profile: minimal @@ -43,7 +45,7 @@ jobs: rust: [stable] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: profile: minimal @@ -64,7 +66,7 @@ jobs: rust: [stable] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: profile: minimal @@ -85,7 +87,7 @@ jobs: rust: [stable] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: profile: minimal @@ -95,4 +97,16 @@ jobs: - name: Run Clippy run: | cd benches - cargo bench --bench benches --no-run \ No newline at end of file + cargo bench --bench benches --no-run + + build-wasm: + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: build + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: wasm32-unknown-unknown + - name: Check wasm + run: cargo check --target wasm32-unknown-unknown \ No newline at end of file diff --git a/src/block/avx2.rs b/src/block/avx2.rs new file mode 100644 index 0000000..00b835a --- /dev/null +++ b/src/block/avx2.rs @@ -0,0 +1,108 @@ +#[cfg(target_arch = "x86")] +use core::arch::x86::*; +#[cfg(target_arch = "x86_64")] +use core::arch::x86_64::*; +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(__m256i); + +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 { _mm256_testz_si256(self.0, self.0) == 1 } + } + + #[inline] + pub fn andnot(self, other: Self) -> Self { + Self(unsafe { _mm256_andnot_si256(other.0, self.0) }) + } +} + +impl Not for Block { + type Output = Block; + #[inline] + fn not(self) -> Self::Output { + unsafe { Self(_mm256_xor_si256(self.0, Self::ALL.0)) } + } +} + +impl BitAnd for Block { + type Output = Block; + #[inline] + fn bitand(self, other: Self) -> Self::Output { + unsafe { Self(_mm256_and_si256(self.0, other.0)) } + } +} + +impl BitAndAssign for Block { + #[inline] + fn bitand_assign(&mut self, other: Self) { + unsafe { + self.0 = _mm256_and_si256(self.0, other.0); + } + } +} + +impl BitOr for Block { + type Output = Block; + #[inline] + fn bitor(self, other: Self) -> Self::Output { + unsafe { Self(_mm256_or_si256(self.0, other.0)) } + } +} + +impl BitOrAssign for Block { + #[inline] + fn bitor_assign(&mut self, other: Self) { + unsafe { + self.0 = _mm256_or_si256(self.0, other.0); + } + } +} + +impl BitXor for Block { + type Output = Block; + #[inline] + fn bitxor(self, other: Self) -> Self::Output { + unsafe { Self(_mm256_xor_si256(self.0, other.0)) } + } +} + +impl BitXorAssign for Block { + #[inline] + fn bitxor_assign(&mut self, other: Self) { + unsafe { self.0 = _mm256_xor_si256(self.0, other.0) } + } +} + +impl PartialEq for Block { + #[inline] + fn eq(&self, other: &Self) -> bool { + unsafe { + let eq = _mm256_cmpeq_epi8(self.0, other.0); + _mm256_movemask_epi8(eq) == !(0i32) + } + } +} diff --git a/src/block/default.rs b/src/block/default.rs new file mode 100644 index 0000000..48d52f9 --- /dev/null +++ b/src/block/default.rs @@ -0,0 +1,76 @@ +use core::iter::Iterator; +use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; + +#[derive(Copy, Clone, PartialEq, Debug)] +#[repr(transparent)] +pub struct Block(usize); + +impl Block { + pub const USIZE_COUNT: usize = 1; + pub const NONE: Self = Block(0); + pub const ALL: Self = Block(!0); + pub const BITS: usize = core::mem::size_of::() * 8; + + #[inline] + pub const fn is_empty(self) -> bool { + self.0 == Self::NONE.0 + } + + #[inline] + pub fn andnot(self, other: Self) -> Self { + Self(!other.0 & self.0) + } +} + +impl Not for Block { + type Output = Block; + #[inline] + fn not(self) -> Self::Output { + Self(self.0.not()) + } +} + +impl BitAnd for Block { + type Output = Block; + #[inline] + fn bitand(self, other: Self) -> Self::Output { + Self(self.0.bitand(other.0)) + } +} + +impl BitAndAssign for Block { + #[inline] + fn bitand_assign(&mut self, other: Self) { + self.0.bitand_assign(other.0); + } +} + +impl BitOr for Block { + type Output = Block; + #[inline] + fn bitor(self, other: Self) -> Self::Output { + Self(self.0.bitor(other.0)) + } +} + +impl BitOrAssign for Block { + #[inline] + fn bitor_assign(&mut self, other: Self) { + self.0.bitor_assign(other.0) + } +} + +impl BitXor for Block { + type Output = Block; + #[inline] + fn bitxor(self, other: Self) -> Self::Output { + Self(self.0.bitxor(other.0)) + } +} + +impl BitXorAssign for Block { + #[inline] + fn bitxor_assign(&mut self, other: Self) { + self.0.bitxor_assign(other.0) + } +} diff --git a/src/block/mod.rs b/src/block/mod.rs new file mode 100644 index 0000000..be64af9 --- /dev/null +++ b/src/block/mod.rs @@ -0,0 +1,76 @@ +use core::cmp::Ordering; +use core::hash::{Hash, Hasher}; + +#[cfg(all( + not(target_arch = "wasm32"), + not(target_feature = "sse2"), + not(target_feature = "avx2"), +))] +mod default; +#[cfg(all( + not(target_arch = "wasm32"), + not(target_feature = "sse2"), + not(target_feature = "avx2"), +))] +pub use self::default::*; + +#[cfg(all( + not(target_arch = "wasm32"), + target_feature = "sse2", + not(target_feature = "avx2"), +))] +mod sse2; +#[cfg(all( + not(target_arch = "wasm32"), + target_feature = "sse2", + not(target_feature = "avx2"), +))] +pub use self::sse2::*; + +#[cfg(all(not(target_arch = "wasm32"), target_feature = "avx2",))] +mod avx2; +#[cfg(all(not(target_arch = "wasm32"), target_feature = "avx2",))] +pub use self::avx2::*; + +#[cfg(target_arch = "wasm32")] +mod wasm32; +#[cfg(target_arch = "wasm32")] +pub use self::wasm32::*; + +impl Eq for Block {} + +impl PartialOrd for Block { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Block { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + let a = self.into_usize_array(); + let b = other.into_usize_array(); + for i in 0..Self::USIZE_COUNT { + match a[i].cmp(&b[i]) { + Ordering::Equal => continue, + cmp => return cmp, + } + } + Ordering::Equal + } +} + +impl Default for Block { + #[inline] + fn default() -> Self { + Self::NONE + } +} + +impl Hash for Block { + #[inline] + fn hash(&self, hasher: &mut H) { + self.into_usize_array().hash(hasher) + } +} diff --git a/src/block/sse2.rs b/src/block/sse2.rs new file mode 100644 index 0000000..6f61948 --- /dev/null +++ b/src/block/sse2.rs @@ -0,0 +1,119 @@ +#![allow(clippy::undocumented_unsafe_blocks)] + +#[cfg(target_arch = "x86")] +use core::arch::x86::*; +#[cfg(target_arch = "x86_64")] +use core::arch::x86_64::*; +use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; + +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub struct Block(__m128i); + +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 { + #[cfg(not(target_feature = "sse4.1"))] + { + self == Self::NONE + } + #[cfg(target_feature = "sse4.1")] + { + unsafe { _mm_test_all_zeros(self.0, Self::ALL.0) == 1 } + } + } + + #[inline] + pub fn andnot(self, other: Self) -> Self { + Self(unsafe { _mm_andnot_si128(other.0, self.0) }) + } +} + +impl Not for Block { + type Output = Block; + #[inline] + fn not(self) -> Self::Output { + unsafe { Self(_mm_xor_si128(self.0, Self::ALL.0)) } + } +} + +impl BitAnd for Block { + type Output = Block; + #[inline] + fn bitand(self, other: Self) -> Self::Output { + unsafe { Self(_mm_and_si128(self.0, other.0)) } + } +} + +impl BitAndAssign for Block { + #[inline] + fn bitand_assign(&mut self, other: Self) { + unsafe { + self.0 = _mm_and_si128(self.0, other.0); + } + } +} + +impl BitOr for Block { + type Output = Block; + #[inline] + fn bitor(self, other: Self) -> Self::Output { + unsafe { Self(_mm_or_si128(self.0, other.0)) } + } +} + +impl BitOrAssign for Block { + #[inline] + fn bitor_assign(&mut self, other: Self) { + unsafe { + self.0 = _mm_or_si128(self.0, other.0); + } + } +} + +impl BitXor for Block { + type Output = Block; + #[inline] + fn bitxor(self, other: Self) -> Self::Output { + unsafe { Self(_mm_xor_si128(self.0, other.0)) } + } +} + +impl BitXorAssign for Block { + #[inline] + fn bitxor_assign(&mut self, other: Self) { + unsafe { self.0 = _mm_xor_si128(self.0, other.0) } + } +} + +impl PartialEq for Block { + #[inline] + fn eq(&self, other: &Self) -> bool { + unsafe { + #[cfg(not(target_feature = "sse4.1"))] + { + _mm_movemask_epi8(_mm_cmpeq_epi8(self.0, other.0)) == 0xffff + } + #[cfg(target_feature = "sse4.1")] + { + let neq = _mm_xor_si128(self.0, other.0); + _mm_test_all_zeros(neq, neq) == 1 + } + } + } +} diff --git a/src/block/wasm32.rs b/src/block/wasm32.rs new file mode 100644 index 0000000..2dac999 --- /dev/null +++ b/src/block/wasm32.rs @@ -0,0 +1,98 @@ +use core::{ + arch::wasm32::*, + cmp::Ordering, + hash::{Hash, Hasher}, + iter::Iterator, + ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}, +}; + +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub struct Block(v128); + +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 { + !v128_any_true(self.0) + } + + #[inline] + pub fn andnot(self, other: Self) -> Self { + Self(unsafe { v128_andnot(self.0, other.0) }) + } +} + +impl Not for Block { + type Output = Block; + #[inline] + fn not(self) -> Self::Output { + Self(v128_xor(self.0, Self::ALL.0)) + } +} + +impl BitAnd for Block { + type Output = Block; + #[inline] + fn bitand(self, other: Self) -> Self::Output { + Self(v128_and(self.0, other.0)) + } +} + +impl BitAndAssign for Block { + #[inline] + fn bitand_assign(&mut self, other: Self) { + self.0 = v128_and(self.0, other.0); + } +} + +impl BitOr for Block { + type Output = Block; + #[inline] + fn bitor(self, other: Self) -> Self::Output { + Self(v128_or(self.0, other.0)) + } +} + +impl BitOrAssign for Block { + #[inline] + fn bitor_assign(&mut self, other: Self) { + self.0 = v128_or(self.0, other.0); + } +} + +impl BitXor for Block { + type Output = Block; + #[inline] + fn bitxor(self, other: Self) -> Self::Output { + Self(v128_xor(self.0, other.0)) + } +} + +impl BitXorAssign for Block { + #[inline] + fn bitxor_assign(&mut self, other: Self) { + self.0 = v128_xor(self.0, other.0) + } +} + +impl PartialEq for Block { + #[inline] + fn eq(&self, other: &Self) -> bool { + !v128_any_true(v128_xor(self.0, other.0)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 460e0c0..03dac9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,22 +12,16 @@ //! This version of fixedbitset requires Rust 1.39 or later. //! #![doc(html_root_url = "https://docs.rs/fixedbitset/0.4.2/")] -#![cfg_attr(not(feature = "std"), no_std)] -#![forbid(clippy::undocumented_unsafe_blocks)] +#![no_std] +#![deny(clippy::undocumented_unsafe_blocks)] -#[cfg(not(feature = "std"))] extern crate alloc; -#[cfg(not(feature = "std"))] use alloc::{ vec, vec::{IntoIter, Vec}, }; -#[cfg(feature = "std")] -use std::vec::IntoIter; - -#[cfg(not(feature = "std"))] -use core as std; +mod block; mod range; #[cfg(feature = "serde")] @@ -35,23 +29,24 @@ extern crate serde; #[cfg(feature = "serde")] mod serde_impl; -use std::fmt::Write; -use std::fmt::{Binary, Display, Error, Formatter}; +use core::fmt::{Binary, Display, Error, Formatter}; +use core::{fmt::Write, mem::ManuallyDrop}; +use core::cmp::{Ord, Ordering}; +use core::iter::{Chain, ExactSizeIterator, FromIterator, FusedIterator}; +use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Index}; pub use range::IndexRange; -use std::cmp::{Ord, Ordering}; -use std::iter::{Chain, ExactSizeIterator, FromIterator, FusedIterator}; -use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Index}; -pub(crate) const BITS: usize = std::mem::size_of::() * 8; +pub(crate) const BITS: usize = core::mem::size_of::() * 8; #[cfg(feature = "serde")] -pub(crate) const BYTES: usize = std::mem::size_of::(); +pub(crate) const BYTES: usize = core::mem::size_of::(); +use block::Block as SimdBlock; pub type Block = usize; #[inline] -fn div_rem(x: usize) -> (usize, usize) { - (x / BITS, x % BITS) +fn div_rem(x: usize, denominator: usize) -> (usize, usize) { + (x / denominator, x % denominator) } /// `FixedBitSet` is a simple fixed size set of bits that each can @@ -64,7 +59,7 @@ fn div_rem(x: usize) -> (usize, usize) { /// [0,1,0]. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct FixedBitSet { - pub(crate) data: Vec, + pub(crate) data: Vec, /// length in bits pub(crate) length: usize, } @@ -81,10 +76,10 @@ impl FixedBitSet { /// Create a new **FixedBitSet** with a specific number of bits, /// all initially clear. pub fn with_capacity(bits: usize) -> Self { - let (mut blocks, rem) = div_rem(bits); + let (mut blocks, rem) = div_rem(bits, SimdBlock::BITS); blocks += (rem > 0) as usize; FixedBitSet { - data: vec![0; blocks], + data: vec![SimdBlock::NONE; blocks], length: bits, } } @@ -103,33 +98,42 @@ impl FixedBitSet { /// assert_eq!(format!("{:b}", bs), "0010"); /// ``` pub fn with_capacity_and_blocks>(bits: usize, blocks: I) -> Self { - let (mut n_blocks, rem) = div_rem(bits); + let (mut n_blocks, rem) = div_rem(bits, SimdBlock::BITS); n_blocks += (rem > 0) as usize; - let mut data: Vec = blocks.into_iter().collect(); - // Pad data with zeros if smaller or truncate if larger - if data.len() != n_blocks { - data.resize(n_blocks, 0); - } - // Disable bits in blocks beyond capacity - let end = data.len() * BITS; - for (block, mask) in Masks::new(bits..end, end) { - // SAFETY: Masks cannot return a block index that is out of range. - let block = unsafe { data.get_unchecked_mut(block) }; - *block &= !mask; + let mut bitset = FixedBitSet { + data: vec![SimdBlock::NONE; n_blocks], + length: bits, + }; + for (subblock, value) in bitset.as_mut_slice().iter_mut().zip(blocks.into_iter()) { + *subblock = value; } - FixedBitSet { data, length: bits } + bitset } /// Grow capacity to **bits**, all new bits initialized to zero pub fn grow(&mut self, bits: usize) { - let (mut blocks, rem) = div_rem(bits); + let (mut blocks, rem) = div_rem(bits, SimdBlock::BITS); blocks += (rem > 0) as usize; if bits > self.length { self.length = bits; - self.data.resize(blocks, 0); + self.data.resize(blocks, SimdBlock::NONE); } } + unsafe fn get_unchecked(&self, subblock: usize) -> &Block { + &*self.data.as_ptr().cast::().add(subblock) + } + + unsafe fn get_unchecked_mut(&mut self, subblock: usize) -> &mut Block { + &mut *self.data.as_mut_ptr().cast::().add(subblock) + } + + fn usize_len(&self) -> usize { + let (mut blocks, rem) = div_rem(self.length, BITS); + blocks += (rem > 0) as usize; + blocks + } + /// Grows the internal size of the bitset before inserting a bit /// /// Unlike `insert`, this cannot panic, but may allocate if the bit is outside of the existing buffer's range. @@ -139,10 +143,10 @@ impl FixedBitSet { pub fn grow_and_insert(&mut self, bits: usize) { self.grow(bits + 1); - let (blocks, rem) = div_rem(bits); + let (blocks, rem) = div_rem(bits, BITS); // SAFETY: The above grow ensures that the block is inside the Vec's allocation. unsafe { - *self.data.get_unchecked_mut(blocks) |= 1 << rem; + *self.get_unchecked_mut(blocks) |= 1 << rem; } } @@ -198,7 +202,7 @@ impl FixedBitSet { /// This is equivalent to [`bitset.count_ones(..) == 0`](FixedBitSet::count_ones). #[inline] pub fn is_clear(&self) -> bool { - self.data.iter().all(|block| *block == 0) + self.data.iter().all(|block| block.is_empty()) } /// Return **true** if the bit is enabled in the **FixedBitSet**, @@ -209,11 +213,10 @@ impl FixedBitSet { /// Note: Also available with index syntax: `bitset[bit]`. #[inline] pub fn contains(&self, bit: usize) -> bool { - let (block, i) = div_rem(bit); - match self.data.get(block) { - None => false, - Some(b) => (b & (1 << i)) != 0, - } + (bit < self.length) + // SAFETY: The above check ensures that the block and bit are within bounds. + .then(|| unsafe { self.contains_unchecked(bit) }) + .unwrap_or(false) } /// Return **true** if the bit is enabled in the **FixedBitSet**, @@ -226,15 +229,15 @@ impl FixedBitSet { /// `bit` must be less than `self.len()` #[inline] pub unsafe fn contains_unchecked(&self, bit: usize) -> bool { - let (block, i) = div_rem(bit); - (self.data.get_unchecked(block) & (1 << i)) != 0 + let (block, i) = div_rem(bit, BITS); + (self.get_unchecked(block) & (1 << i)) != 0 } /// Clear all bits. #[inline] pub fn clear(&mut self) { for elt in &mut self.data { - *elt = 0 + *elt = SimdBlock::NONE } } @@ -261,10 +264,10 @@ impl FixedBitSet { /// `bit` must be less than `self.len()` #[inline] pub unsafe fn insert_unchecked(&mut self, bit: usize) { - let (block, i) = div_rem(bit); + let (block, i) = div_rem(bit, BITS); // SAFETY: The above assertion ensures that the block is inside the Vec's allocation. unsafe { - *self.data.get_unchecked_mut(block) |= 1 << i; + *self.get_unchecked_mut(block) |= 1 << i; } } @@ -291,10 +294,10 @@ impl FixedBitSet { /// `bit` must be less than `self.len()` #[inline] pub unsafe fn remove_unchecked(&mut self, bit: usize) { - let (block, i) = div_rem(bit); + let (block, i) = div_rem(bit, BITS); // SAFETY: The above assertion ensures that the block is inside the Vec's allocation. unsafe { - *self.data.get_unchecked_mut(block) &= !(1 << i); + *self.get_unchecked_mut(block) &= !(1 << i); } } @@ -319,10 +322,10 @@ impl FixedBitSet { /// `bit` must be less than `self.len()` #[inline] pub unsafe fn put_unchecked(&mut self, bit: usize) -> bool { - let (block, i) = div_rem(bit); + let (block, i) = div_rem(bit, BITS); // SAFETY: The above assertion ensures that the block is inside the Vec's allocation. unsafe { - let word = self.data.get_unchecked_mut(block); + let word = self.get_unchecked_mut(block); let prev = *word & (1 << i) != 0; *word |= 1 << i; prev @@ -352,10 +355,10 @@ impl FixedBitSet { /// `bit` must be less than `self.len()` #[inline] pub unsafe fn toggle_unchecked(&mut self, bit: usize) { - let (block, i) = div_rem(bit); + let (block, i) = div_rem(bit, BITS); // SAFETY: The above assertion ensures that the block is inside the Vec's allocation. unsafe { - *self.data.get_unchecked_mut(block) ^= 1 << i; + *self.get_unchecked_mut(block) ^= 1 << i; } } @@ -382,9 +385,9 @@ impl FixedBitSet { /// `bit` must be less than `self.len()` #[inline] pub unsafe fn set_unchecked(&mut self, bit: usize, enabled: bool) { - let (block, i) = div_rem(bit); + let (block, i) = div_rem(bit, BITS); // SAFETY: The above assertion ensures that the block is inside the Vec's allocation. - let elt = unsafe { self.data.get_unchecked_mut(block) }; + let elt = unsafe { self.get_unchecked_mut(block) }; if enabled { *elt |= 1 << i; } else { @@ -435,7 +438,7 @@ impl FixedBitSet { Masks::new(range, self.length) .map(|(block, mask)| { // SAFETY: Masks cannot return a block index that is out of range. - let value = unsafe { *self.data.get_unchecked(block) }; + let value = unsafe { *self.get_unchecked(block) }; (value & mask).count_ones() as usize }) .sum() @@ -450,7 +453,7 @@ impl FixedBitSet { pub fn set_range(&mut self, range: T, enabled: bool) { for (block, mask) in Masks::new(range, self.length) { // SAFETY: Masks cannot return a block index that is out of range. - let block = unsafe { self.data.get_unchecked_mut(block) }; + let block = unsafe { self.get_unchecked_mut(block) }; if enabled { *block |= mask; } else { @@ -478,7 +481,7 @@ impl FixedBitSet { pub fn toggle_range(&mut self, range: T) { for (block, mask) in Masks::new(range, self.length) { // SAFETY: Masks cannot return a block index that is out of range. - let block = unsafe { self.data.get_unchecked_mut(block) }; + let block = unsafe { self.get_unchecked_mut(block) }; *block ^= mask; } } @@ -486,14 +489,28 @@ impl FixedBitSet { /// View the bitset as a slice of `Block` blocks #[inline] pub fn as_slice(&self) -> &[Block] { - &self.data + // SAFETY: The bits from both usize and Block are required to be reinterprettable, and + // neither have any padding or alignment issues. The slice constructed is within bounds + // of the underlying allocation. This function is called with a read-only borrow so + // no other write can happen as long as the returned borrow lives. + unsafe { + let ptr = self.data.as_ptr().cast::(); + core::slice::from_raw_parts(ptr, self.usize_len()) + } } /// View the bitset as a mutable slice of `Block` blocks. Writing past the bitlength in the last /// will cause `contains` to return potentially incorrect results for bits past the bitlength. #[inline] pub fn as_mut_slice(&mut self) -> &mut [Block] { - &mut self.data + // SAFETY: The bits from both usize and Block are required to be reinterprettable, and + // neither have any padding or alignment issues. The slice constructed is within bounds + // of the underlying allocation. This function is called with a mutable borrow so + // no other read or write can happen as long as the returned borrow lives. + unsafe { + let ptr = self.data.as_mut_ptr().cast::(); + core::slice::from_raw_parts_mut(ptr, self.usize_len()) + } } /// Iterates over all enabled bits. @@ -526,24 +543,33 @@ impl FixedBitSet { /// /// Iterator element is the index of the `1` bit, type `usize`. /// Unlike `ones`, this function consumes the `FixedBitset`. - pub fn into_ones(mut self) -> IntoOnes { - if self.data.len() == 0 { + pub fn into_ones(self) -> IntoOnes { + // SAFETY: This is using the exact same allocation pattern, size, and capacity + // making this reconstruction of the Vec safe. + let mut data = unsafe { + let mut data = ManuallyDrop::new(self.data); + let ptr = data.as_mut_ptr().cast(); + let len = data.len() * SimdBlock::USIZE_COUNT; + let capacity = data.capacity() * SimdBlock::USIZE_COUNT; + Vec::from_raw_parts(ptr, len, capacity) + }; + if data.is_empty() { IntoOnes { bitset_front: 0, bitset_back: 0, block_idx_front: 0, block_idx_back: 0, - remaining_blocks: self.data.into_iter(), + remaining_blocks: data.into_iter(), } } else { - let first_block = self.data.remove(0); - let last_block = self.data.pop().unwrap_or(0); + let first_block = data.remove(0); + let last_block = data.pop().unwrap_or(0); IntoOnes { bitset_front: first_block, bitset_back: last_block, block_idx_front: 0, - block_idx_back: (1 + self.data.len()) * BITS, - remaining_blocks: self.data.into_iter(), + block_idx_back: (1 + data.len()) * BITS, + remaining_blocks: data.into_iter(), } } } @@ -620,9 +646,9 @@ impl FixedBitSet { for (x, y) in self.data.iter_mut().zip(other.data.iter()) { *x &= *y; } - let mn = std::cmp::min(self.data.len(), other.data.len()); + let mn = core::cmp::min(self.data.len(), other.data.len()); for wd in &mut self.data[mn..] { - *wd = 0; + *wd = SimdBlock::NONE; } } @@ -660,7 +686,7 @@ impl FixedBitSet { self.data .iter() .zip(other.data.iter()) - .all(|(x, y)| x & y == 0) + .all(|(x, y)| (*x & *y).is_empty()) } /// Returns `true` if the set is a subset of another, i.e. `other` contains @@ -669,8 +695,12 @@ impl FixedBitSet { self.data .iter() .zip(other.data.iter()) - .all(|(x, y)| x & !y == 0) - && self.data.iter().skip(other.data.len()).all(|x| *x == 0) + .all(|(x, y)| x.andnot(*y).is_empty()) + && self + .data + .iter() + .skip(other.data.len()) + .all(|x| x.is_empty()) } /// Returns `true` if the set is a superset of another, i.e. `self` contains @@ -734,12 +764,10 @@ impl<'a> Iterator for Difference<'a> { impl<'a> DoubleEndedIterator for Difference<'a> { fn next_back(&mut self) -> Option { - for nxt in self.iter.by_ref().rev() { - if !self.other.contains(nxt) { - return Some(nxt); - } - } - None + self.iter + .by_ref() + .rev() + .find(|&nxt| !self.other.contains(nxt)) } } @@ -790,12 +818,7 @@ impl<'a> Iterator for Intersection<'a> { #[inline] #[allow(clippy::manual_find)] fn next(&mut self) -> Option { - for nxt in self.iter.by_ref() { - if self.other.contains(nxt) { - return Some(nxt); - } - } - None + self.iter.by_ref().find(|&nxt| self.other.contains(nxt)) } #[inline] @@ -806,12 +829,10 @@ impl<'a> Iterator for Intersection<'a> { impl<'a> DoubleEndedIterator for Intersection<'a> { fn next_back(&mut self) -> Option { - for nxt in self.iter.by_ref().rev() { - if self.other.contains(nxt) { - return Some(nxt); - } - } - None + self.iter + .by_ref() + .rev() + .find(|&nxt| self.other.contains(nxt)) } } @@ -850,9 +871,9 @@ impl<'a> FusedIterator for Union<'a> {} struct Masks { first_block: usize, - first_mask: Block, + first_mask: usize, last_block: usize, - last_mask: Block, + last_mask: usize, } impl Masks { @@ -868,21 +889,22 @@ impl Masks { length ); - let (first_block, first_rem) = div_rem(start); - let (last_block, last_rem) = div_rem(end); + let (first_block, first_rem) = div_rem(start, BITS); + let (last_block, last_rem) = div_rem(end, BITS); Masks { first_block, - first_mask: Block::max_value() << first_rem, + first_mask: usize::max_value() << first_rem, last_block, - last_mask: (Block::max_value() >> 1) >> (BITS - last_rem - 1), + last_mask: (usize::max_value() >> 1) >> (BITS - last_rem - 1), // this is equivalent to `MAX >> (BITS - x)` with correct semantics when x == 0. } } } impl Iterator for Masks { - type Item = (usize, Block); + type Item = (usize, usize); + #[inline] fn next(&mut self) -> Option { match self.first_block.cmp(&self.last_block) { @@ -923,16 +945,16 @@ impl ExactSizeIterator for Masks {} /// /// This struct is created by the [`FixedBitSet::ones`] method. pub struct Ones<'a> { - bitset_front: Block, - bitset_back: Block, + bitset_front: usize, + bitset_back: usize, block_idx_front: usize, block_idx_back: usize, - remaining_blocks: std::slice::Iter<'a, Block>, + remaining_blocks: core::slice::Iter<'a, usize>, } impl<'a> Ones<'a> { #[inline] - pub fn last_positive_bit_and_unset(n: &mut Block) -> usize { + pub fn last_positive_bit_and_unset(n: &mut usize) -> usize { // Find the last set bit using x & -x let last_bit = *n & n.wrapping_neg(); @@ -946,12 +968,12 @@ impl<'a> Ones<'a> { } #[inline] - fn first_positive_bit_and_unset(n: &mut Block) -> usize { + fn first_positive_bit_and_unset(n: &mut usize) -> usize { /* Identify the first non zero bit */ let bit_idx = n.leading_zeros(); /* set that bit to zero */ - let mask = !((1 as Block) << (BITS as u32 - bit_idx - 1)); + let mask = !((1_usize) << (BITS as u32 - bit_idx - 1)); n.bitand_assign(mask); bit_idx as usize @@ -1036,10 +1058,10 @@ impl<'a> FusedIterator for Ones<'a> {} /// /// This struct is created by the [`FixedBitSet::ones`] method. pub struct Zeroes<'a> { - bitset: Block, + bitset: usize, block_idx: usize, len: usize, - remaining_blocks: std::slice::Iter<'a, Block>, + remaining_blocks: core::slice::Iter<'a, usize>, } impl<'a> Iterator for Zeroes<'a> { @@ -1051,7 +1073,7 @@ impl<'a> Iterator for Zeroes<'a> { self.bitset = !*self.remaining_blocks.next()?; self.block_idx += BITS; } - let t = self.bitset & (0 as Block).wrapping_sub(self.bitset); + let t = self.bitset & (0_usize).wrapping_sub(self.bitset); let r = self.bitset.trailing_zeros() as usize; self.bitset ^= t; let bit = self.block_idx + r; @@ -1128,7 +1150,7 @@ pub struct IntoOnes { bitset_back: Block, block_idx_front: usize, block_idx_back: usize, - remaining_blocks: IntoIter, + remaining_blocks: IntoIter, } impl IntoOnes { @@ -1152,7 +1174,7 @@ impl IntoOnes { let bit_idx = n.leading_zeros(); /* set that bit to zero */ - let mask = !((1 as Block) << (BITS as u32 - bit_idx - 1)); + let mask = !((1_usize) << (BITS as u32 - bit_idx - 1)); n.bitand_assign(mask); bit_idx as usize @@ -1231,7 +1253,7 @@ impl Iterator for IntoOnes { } // Ones will continue to return None once it first returns None. -impl<'a> FusedIterator for IntoOnes {} +impl FusedIterator for IntoOnes {} impl<'a> BitAnd for &'a FixedBitSet { type Output = FixedBitSet; @@ -1247,7 +1269,7 @@ impl<'a> BitAnd for &'a FixedBitSet { for (data, block) in data.iter_mut().zip(long.iter()) { *data &= *block; } - let len = std::cmp::min(self.len(), other.len()); + let len = core::cmp::min(self.len(), other.len()); FixedBitSet { data, length: len } } } @@ -1278,7 +1300,7 @@ impl<'a> BitOr for &'a FixedBitSet { for (data, block) in data.iter_mut().zip(short.iter()) { *data |= *block; } - let len = std::cmp::max(self.len(), other.len()); + let len = core::cmp::max(self.len(), other.len()); FixedBitSet { data, length: len } } } @@ -1309,7 +1331,7 @@ impl<'a> BitXor for &'a FixedBitSet { for (data, block) in data.iter_mut().zip(short.iter()) { *data ^= *block; } - let len = std::cmp::max(self.len(), other.len()); + let len = core::cmp::max(self.len(), other.len()); FixedBitSet { data, length: len } } } @@ -1608,13 +1630,13 @@ mod tests { let mut bitset = FixedBitSet::with_capacity(s); bitset.insert_range(..); let mut t = s; + extern crate std; let mut iter = bitset.ones().alternate(); loop { match iter.next() { None => break, Some(_) => { t -= 1; - //println!("{:?} < {}", iter.size_hint(), t); assert!(iter.size_hint().1.unwrap() >= t); assert!(iter.size_hint().1.unwrap() <= t + 3 * BITS); } @@ -1761,7 +1783,7 @@ mod tests { fn bitand_first_smaller() { let a_len = 113; let b_len = 137; - let len = std::cmp::min(a_len, b_len); + let len = core::cmp::min(a_len, b_len); let a_end = 97; let b_start = 89; let mut a = FixedBitSet::with_capacity(a_len); @@ -1785,7 +1807,7 @@ mod tests { fn bitand_first_larger() { let a_len = 173; let b_len = 137; - let len = std::cmp::min(a_len, b_len); + let len = core::cmp::min(a_len, b_len); let a_end = 107; let b_start = 43; let mut a = FixedBitSet::with_capacity(a_len); @@ -2017,7 +2039,7 @@ mod tests { fn bitxor_first_smaller() { let a_len = 113; let b_len = 137; - let len = std::cmp::max(a_len, b_len); + let len = core::cmp::max(a_len, b_len); let a_end = 97; let b_start = 89; let mut a = FixedBitSet::with_capacity(a_len); @@ -2041,7 +2063,7 @@ mod tests { fn bitxor_first_larger() { let a_len = 173; let b_len = 137; - let len = std::cmp::max(a_len, b_len); + let len = core::cmp::max(a_len, b_len); let a_end = 107; let b_start = 43; let mut a = FixedBitSet::with_capacity(a_len); @@ -2296,8 +2318,8 @@ mod tests { let items: Vec = vec![1, 5, 7, 10, 14, 15]; let fb = items.iter().cloned().collect::(); - assert_eq!(format!("{:b}", fb), "0100010100100011"); - assert_eq!(format!("{:#b}", fb), "0b0100010100100011"); + assert_eq!(alloc::format!("{:b}", fb), "0100010100100011"); + assert_eq!(alloc::format!("{:#b}", fb), "0b0100010100100011"); } #[cfg(feature = "std")] @@ -2309,8 +2331,8 @@ mod tests { fb.put(4); fb.put(2); - assert_eq!(format!("{}", fb), "00101000"); - assert_eq!(format!("{:#}", fb), "0b00101000"); + assert_eq!(alloc::format!("{}", fb), "00101000"); + assert_eq!(alloc::format!("{:#}", fb), "0b00101000"); } // TODO: Rewite this test to be platform agnostic. diff --git a/src/range.rs b/src/range.rs index 738b869..9b385c4 100644 --- a/src/range.rs +++ b/src/range.rs @@ -1,6 +1,4 @@ -#[cfg(not(feature = "std"))] -use core as std; -use std::ops::{Range, RangeFrom, RangeFull, RangeTo}; +use core::ops::{Range, RangeFrom, RangeFull, RangeTo}; // Taken from https://github.com/bluss/odds/blob/master/src/range.rs. diff --git a/src/serde_impl.rs b/src/serde_impl.rs index 53d97f4..9823159 100644 --- a/src/serde_impl.rs +++ b/src/serde_impl.rs @@ -1,10 +1,11 @@ #[cfg(not(feature = "std"))] use core as std; -use crate::{FixedBitSet, BYTES}; +use crate::{Block, FixedBitSet, BYTES}; +use alloc::vec::Vec; +use core::{convert::TryFrom, fmt}; use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; use serde::ser::{Serialize, SerializeStruct, Serializer}; -use std::{convert::TryFrom, fmt}; struct BitSetByteSerializer<'a>(&'a FixedBitSet); @@ -25,10 +26,10 @@ impl<'a> Serialize for BitSetByteSerializer<'a> { where S: Serializer, { - let len = self.0.data.len() * BYTES; + let len = self.0.as_slice().len() * BYTES; // PERF: Figure out a way to do this without allocating. let mut temp = Vec::with_capacity(len); - for block in &self.0.data { + for block in self.0.as_slice() { temp.extend(&block.to_le_bytes()); } serializer.serialize_bytes(&temp) @@ -45,6 +46,22 @@ impl<'de> Deserialize<'de> for FixedBitSet { Data, } + fn bytes_to_data(length: usize, input: &[u8]) -> Vec { + let block_len = length / BYTES + 1; + let mut data = Vec::with_capacity(block_len); + for chunk in input.chunks(BYTES) { + match <&[u8; BYTES]>::try_from(chunk) { + Ok(bytes) => data.push(usize::from_le_bytes(*bytes)), + Err(_) => { + let mut bytes = [0u8; BYTES]; + bytes[0..BYTES].copy_from_slice(chunk); + data.push(usize::from_le_bytes(bytes)); + } + } + } + data + } + impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where @@ -91,10 +108,11 @@ impl<'de> Deserialize<'de> for FixedBitSet { let length = seq .next_element()? .ok_or_else(|| de::Error::invalid_length(0, &self))?; - let data = seq + let data: &[u8] = seq .next_element()? .ok_or_else(|| de::Error::invalid_length(1, &self))?; - Ok(FixedBitSet { length, data }) + let data = bytes_to_data(length, data); + Ok(FixedBitSet::with_capacity_and_blocks(length, data)) } fn visit_map(self, mut map: V) -> Result @@ -120,20 +138,9 @@ impl<'de> Deserialize<'de> for FixedBitSet { } } let length = length.ok_or_else(|| de::Error::missing_field("length"))?; - let temp = temp.ok_or_else(|| de::Error::missing_field("data"))?; - let block_len = length / BYTES + 1; - let mut data = Vec::with_capacity(block_len); - for chunk in temp.chunks(BYTES) { - match <&[u8; BYTES]>::try_from(chunk) { - Ok(bytes) => data.push(usize::from_le_bytes(*bytes)), - Err(_) => { - let mut bytes = [0u8; BYTES]; - bytes[0..BYTES].copy_from_slice(chunk); - data.push(usize::from_le_bytes(bytes)); - } - } - } - Ok(FixedBitSet { length, data }) + let data = temp.ok_or_else(|| de::Error::missing_field("data"))?; + let data = bytes_to_data(length, data); + Ok(FixedBitSet::with_capacity_and_blocks(length, data)) } }