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
8 changes: 5 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install taplo
run: curl -fsSL https://github.com/tamasfe/taplo/releases/latest/download/taplo-linux-x86_64.gz | gzip -d - | install -m 755 /dev/stdin /usr/local/bin/taplo
- env:
TARGET: i586-unknown-linux-gnu
run: sh ci/tools.sh
Expand Down Expand Up @@ -54,7 +56,7 @@ jobs:
# Disabled due to issues with cross-rs
#x86_64-pc-windows-gnu,
]
channel: [1.80.0, nightly]
channel: [1.85.1, nightly]
include:
- os: macos-latest
target: aarch64-apple-darwin
Expand All @@ -64,10 +66,10 @@ jobs:
channel: nightly
- os: macos-latest
target: aarch64-apple-darwin
channel: 1.80.0
channel: 1.85.1
- os: windows-latest
target: x86_64-pc-windows-msvc
channel: 1.80.0
channel: 1.85.1
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
channel: beta
Expand Down
43 changes: 33 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,30 @@ readme = "README.md"
keywords = ["hash", "no_std", "hashmap", "swisstable"]
categories = ["data-structures", "no-std"]
exclude = [".github", "/ci/*"]
edition = "2021"
rust-version = "1.65.0"
edition = "2024"
rust-version = "1.85.1"

[lints.rust]
missing_docs = "warn"
unreachable_pub = "warn"
unsafe_op_in_unsafe_fn = "warn"

# rust_2018_idioms
bare_trait_objects = "warn"
elided_lifetimes_in_paths = "warn"
ellipsis_inclusive_range_patterns = "warn"
explicit_outlives_requirements = "warn"
unused_extern_crates = "warn"

[lints.clippy]
doc_markdown = "allow"
manual_map = "allow"
missing_errors_doc = "allow"
missing_safety_doc = "allow"
module_name_repetitions = "allow"
must_use_candidate = "allow"
option_if_let_else = "allow"
redundant_else = "allow"

[dependencies]
# For the default hasher
Expand All @@ -26,7 +48,7 @@ alloc = { version = "1.0.0", optional = true, package = "rustc-std-workspace-all

# Support for allocators that use allocator-api2
allocator-api2 = { version = "0.2.9", optional = true, default-features = false, features = [
"alloc",
"alloc",
] }

# Equivalent trait which can be shared with other hash table implementations.
Expand All @@ -50,7 +72,13 @@ bumpalo = { version = "3.13.0", features = ["allocator-api2"] }
libc = "0.2.155"

[features]
default = ["default-hasher", "inline-more", "allocator-api2", "equivalent", "raw-entry"]
default = [
"default-hasher",
"inline-more",
"allocator-api2",
"equivalent",
"raw-entry",
]

# Enables use of nightly features. This is only guaranteed to work on the latest
# version of nightly Rust.
Expand All @@ -60,12 +88,7 @@ nightly = ["foldhash?/nightly", "bumpalo/allocator_api"]
rustc-internal-api = []

# Internal feature used when building as part of the standard library.
rustc-dep-of-std = [
"nightly",
"core",
"alloc",
"rustc-internal-api",
]
rustc-dep-of-std = ["nightly", "core", "alloc", "rustc-internal-api"]

# Enables serde support.
serde = ["dep:serde_core", "dep:serde"]
Expand Down
10 changes: 5 additions & 5 deletions benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// This benchmark suite contains some benchmarks along a set of dimensions:
// Hasher: std default (SipHash) and crate default (foldhash).
// Int key distribution: low bit heavy, top bit heavy, and random.
// Task: basic functionality: insert, insert_erase, lookup, lookup_fail, iter
//! This benchmark suite contains some benchmarks along a set of dimensions:
//! * Hasher: std default (SipHash) and crate default (foldhash).
//! * Int key distribution: low bit heavy, top bit heavy, and random.
//! * Task: basic functionality: insert, insert_erase, lookup, lookup_fail, iter
#![feature(test)]

extern crate test;

use test::{black_box, Bencher};
use test::{Bencher, black_box};

use hashbrown::DefaultHashBuilder;
use hashbrown::{HashMap, HashSet};
Expand Down
3 changes: 2 additions & 1 deletion benches/with_capacity.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#![expect(missing_docs)] // https://github.com/rust-lang/rust/issues/137561
#![feature(test)]

extern crate test;

use hashbrown::HashMap;
use test::{black_box, Bencher};
use test::{Bencher, black_box};

type Map<K, V> = HashMap<K, V>;

Expand Down
7 changes: 5 additions & 2 deletions ci/tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ retry() {
return $result
}


if retry rustup component add rustfmt ; then
cargo fmt --all -- --check
fi

if retry rustup component add clippy ; then
cargo clippy --all --tests --features serde,rayon -- -D clippy::all
cargo clippy --all --tests --features serde,rayon -- -D warnings
fi

if command -v taplo ; then
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to make this an "optional" check in this script so that people don't need to necessarily have taplo installed to have the script pass, although it will still be enforced on CI, where it will be installed.

taplo fmt --check --diff
fi

if command -v shellcheck ; then
Expand Down
2 changes: 1 addition & 1 deletion clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
doc-valid-idents = [ "CppCon", "SwissTable", "SipHash", "HashDoS" ]
doc-valid-idents = ["CppCon", "SwissTable", "SipHash", "HashDoS"]
14 changes: 2 additions & 12 deletions src/control/bitmask.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use super::group::{
BitMaskWord, NonZeroBitMaskWord, BITMASK_ITER_MASK, BITMASK_MASK, BITMASK_STRIDE,
};
use super::group::{BITMASK_ITER_MASK, BITMASK_STRIDE, BitMaskWord, NonZeroBitMaskWord};

/// A bit mask which contains the result of a `Match` operation on a `Group` and
/// allows iterating through them.
Expand All @@ -21,16 +19,8 @@ use super::group::{
#[derive(Copy, Clone)]
pub(crate) struct BitMask(pub(crate) BitMaskWord);

#[allow(clippy::use_self)]
#[expect(clippy::use_self)]
impl BitMask {
/// Returns a new `BitMask` with all bits inverted.
#[inline]
#[must_use]
#[allow(dead_code)]
pub(crate) fn invert(self) -> Self {
BitMask(self.0 ^ BITMASK_MASK)
}

/// Returns a new `BitMask` with the lowest bit removed.
#[inline]
#[must_use]
Expand Down
22 changes: 12 additions & 10 deletions src/control/group/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ pub(crate) type BitMaskWord = GroupWord;
pub(crate) type NonZeroBitMaskWord = NonZeroGroupWord;
pub(crate) const BITMASK_STRIDE: usize = 8;
// We only care about the highest bit of each tag for the mask.
#[allow(clippy::cast_possible_truncation, clippy::unnecessary_cast)]
pub(crate) const BITMASK_MASK: BitMaskWord = u64::from_ne_bytes([Tag::DELETED.0; 8]) as GroupWord;
#[expect(clippy::cast_possible_truncation, clippy::unnecessary_cast)]
const BITMASK_MASK: BitMaskWord = u64::from_ne_bytes([Tag::DELETED.0; 8]) as GroupWord;
pub(crate) const BITMASK_ITER_MASK: BitMaskWord = !0;

/// Helper function to replicate a tag across a `GroupWord`.
Expand All @@ -45,7 +45,7 @@ pub(crate) struct Group(GroupWord);
// little-endian just before creating a BitMask. The can potentially
// enable the compiler to eliminate unnecessary byte swaps if we are
// only checking whether a BitMask is empty.
#[allow(clippy::use_self)]
#[expect(clippy::use_self)]
impl Group {
/// Number of bytes in the group.
pub(crate) const WIDTH: usize = mem::size_of::<Self>();
Expand All @@ -70,27 +70,29 @@ impl Group {

/// Loads a group of tags starting at the given address.
#[inline]
#[allow(clippy::cast_ptr_alignment)] // unaligned load
#[expect(clippy::cast_ptr_alignment)] // unaligned load
pub(crate) unsafe fn load(ptr: *const Tag) -> Self {
Group(ptr::read_unaligned(ptr.cast()))
unsafe { Group(ptr::read_unaligned(ptr.cast())) }
}

/// Loads a group of tags starting at the given address, which must be
/// aligned to `mem::align_of::<Group>()`.
#[inline]
#[allow(clippy::cast_ptr_alignment)]
#[expect(clippy::cast_ptr_alignment)]
pub(crate) unsafe fn load_aligned(ptr: *const Tag) -> Self {
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
Group(ptr::read(ptr.cast()))
unsafe { Group(ptr::read(ptr.cast())) }
}

/// Stores the group of tags to the given address, which must be
/// aligned to `mem::align_of::<Group>()`.
#[inline]
#[allow(clippy::cast_ptr_alignment)]
#[expect(clippy::cast_ptr_alignment)]
pub(crate) unsafe fn store_aligned(self, ptr: *mut Tag) {
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
ptr::write(ptr.cast(), self.0);
unsafe {
ptr::write(ptr.cast(), self.0);
}
}

/// Returns a `BitMask` indicating all tags in the group which *may*
Expand Down Expand Up @@ -132,7 +134,7 @@ impl Group {
/// Returns a `BitMask` indicating all tags in the group which are full.
#[inline]
pub(crate) fn match_full(self) -> BitMask {
self.match_empty_or_deleted().invert()
BitMask(self.match_empty_or_deleted().0 ^ BITMASK_MASK)
}

/// Performs the following transformation on all tags in the group:
Expand Down
19 changes: 10 additions & 9 deletions src/control/group/lsx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use core::arch::loongarch64::*;
pub(crate) type BitMaskWord = u16;
pub(crate) type NonZeroBitMaskWord = NonZeroU16;
pub(crate) const BITMASK_STRIDE: usize = 1;
pub(crate) const BITMASK_MASK: BitMaskWord = 0xffff;
pub(crate) const BITMASK_ITER_MASK: BitMaskWord = !0;

/// Abstraction over a group of control tags which can be scanned in
Expand All @@ -18,7 +17,7 @@ pub(crate) const BITMASK_ITER_MASK: BitMaskWord = !0;
pub(crate) struct Group(m128i);

// FIXME: https://github.com/rust-lang/rust-clippy/issues/3859
#[allow(clippy::use_self)]
#[expect(clippy::use_self)]
impl Group {
/// Number of bytes in the group.
pub(crate) const WIDTH: usize = mem::size_of::<Self>();
Expand All @@ -28,7 +27,7 @@ impl Group {
///
/// This is guaranteed to be aligned to the group size.
#[inline]
#[allow(clippy::items_after_statements)]
#[expect(clippy::items_after_statements)]
pub(crate) const fn static_empty() -> &'static [Tag; Group::WIDTH] {
#[repr(C)]
struct AlignedTags {
Expand All @@ -44,27 +43,29 @@ impl Group {

/// Loads a group of tags starting at the given address.
#[inline]
#[allow(clippy::cast_ptr_alignment)] // unaligned load
#[expect(clippy::cast_ptr_alignment)] // unaligned load
pub(crate) unsafe fn load(ptr: *const Tag) -> Self {
Group(lsx_vld::<0>(ptr.cast()))
unsafe { Group(lsx_vld::<0>(ptr.cast())) }
}

/// Loads a group of tags starting at the given address, which must be
/// aligned to `mem::align_of::<Group>()`.
#[inline]
#[allow(clippy::cast_ptr_alignment)]
#[expect(clippy::cast_ptr_alignment)]
pub(crate) unsafe fn load_aligned(ptr: *const Tag) -> Self {
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
Group(lsx_vld::<0>(ptr.cast()))
unsafe { Group(lsx_vld::<0>(ptr.cast())) }
}

/// Stores the group of tags to the given address, which must be
/// aligned to `mem::align_of::<Group>()`.
#[inline]
#[allow(clippy::cast_ptr_alignment)]
#[expect(clippy::cast_ptr_alignment)]
pub(crate) unsafe fn store_aligned(self, ptr: *mut Tag) {
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
lsx_vst::<0>(self.0, ptr.cast());
unsafe {
lsx_vst::<0>(self.0, ptr.cast());
}
}

/// Returns a `BitMask` indicating all tags in the group which have
Expand Down
4 changes: 1 addition & 3 deletions src/control/group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,4 @@ cfg_if! {
}
}
pub(crate) use self::imp::Group;
pub(super) use self::imp::{
BitMaskWord, NonZeroBitMaskWord, BITMASK_ITER_MASK, BITMASK_MASK, BITMASK_STRIDE,
};
pub(super) use self::imp::{BITMASK_ITER_MASK, BITMASK_STRIDE, BitMaskWord, NonZeroBitMaskWord};
17 changes: 9 additions & 8 deletions src/control/group/neon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use core::num::NonZeroU64;
pub(crate) type BitMaskWord = u64;
pub(crate) type NonZeroBitMaskWord = NonZeroU64;
pub(crate) const BITMASK_STRIDE: usize = 8;
pub(crate) const BITMASK_MASK: BitMaskWord = !0;
pub(crate) const BITMASK_ITER_MASK: BitMaskWord = 0x8080_8080_8080_8080;

/// Abstraction over a group of control tags which can be scanned in
Expand All @@ -16,7 +15,7 @@ pub(crate) const BITMASK_ITER_MASK: BitMaskWord = 0x8080_8080_8080_8080;
#[derive(Copy, Clone)]
pub(crate) struct Group(neon::uint8x8_t);

#[allow(clippy::use_self)]
#[expect(clippy::use_self)]
impl Group {
/// Number of bytes in the group.
pub(crate) const WIDTH: usize = mem::size_of::<Self>();
Expand All @@ -41,27 +40,29 @@ impl Group {

/// Loads a group of tags starting at the given address.
#[inline]
#[allow(clippy::cast_ptr_alignment)] // unaligned load
#[expect(clippy::cast_ptr_alignment)] // unaligned load
pub(crate) unsafe fn load(ptr: *const Tag) -> Self {
Group(neon::vld1_u8(ptr.cast()))
unsafe { Group(neon::vld1_u8(ptr.cast())) }
}

/// Loads a group of tags starting at the given address, which must be
/// aligned to `mem::align_of::<Group>()`.
#[inline]
#[allow(clippy::cast_ptr_alignment)]
#[expect(clippy::cast_ptr_alignment)]
pub(crate) unsafe fn load_aligned(ptr: *const Tag) -> Self {
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
Group(neon::vld1_u8(ptr.cast()))
unsafe { Group(neon::vld1_u8(ptr.cast())) }
}

/// Stores the group of tags to the given address, which must be
/// aligned to `mem::align_of::<Group>()`.
#[inline]
#[allow(clippy::cast_ptr_alignment)]
#[expect(clippy::cast_ptr_alignment)]
pub(crate) unsafe fn store_aligned(self, ptr: *mut Tag) {
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
neon::vst1_u8(ptr.cast(), self.0);
unsafe {
neon::vst1_u8(ptr.cast(), self.0);
}
}

/// Returns a `BitMask` indicating all tags in the group which *may*
Expand Down
Loading