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
22 changes: 20 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
needs:
- build
- test-with-sanitizer
- test-big-endian
steps:
- name: mark the job as a success or failure
run: exit 0
Expand Down Expand Up @@ -104,10 +105,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install nightly Rust
run: rustup override set nightly
- name: Test with Address Sanitizer
run: |
rustup override set nightly
rustup component add rust-src --toolchain nightly
- name: Test with Address Sanitizer
run: |
cargo clean
export RUSTFLAGS=-Zsanitizer=address
export RUSTDOCFLAGS=-Zsanitizer=address
Expand All @@ -117,3 +119,19 @@ jobs:
export RUSTDOCFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins'
cargo test -Zbuild-std --target x86_64-unknown-linux-gnu --verbose --features=${{ matrix.features }}

test-big-endian:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Install nightly Rust
run: |
rustup toolchain install nightly --allow-downgrade -c miri --profile minimal
rustup default nightly
- name: Run Big Endian Test via Miri
# We only run a subset of tests because Miri is too slow for running everything
run: |
cargo miri test --target mips64-unknown-linux-gnuabi64 -- \
--skip raw_table \
--skip init_in_place \
--skip quickchecks
68 changes: 35 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ mod swisstable_group_query;
mod unhash;

use error::Error;
use memory_layout::Header;
use std::borrow::{Borrow, BorrowMut};
use swisstable_group_query::REFERENCE_GROUP_SIZE;

Expand Down Expand Up @@ -217,12 +218,14 @@ impl<C: Config> HashTableOwned<C> {
let encoded_key = C::encode_key(key);
let encoded_value = C::encode_value(value);

if let Some(old_value) = self.as_raw_mut().insert(encoded_key, encoded_value) {
Some(C::decode_value(&old_value))
} else {
self.allocation.header_mut().set_item_count(item_count + 1);
None
}
with_raw_mut(&mut self.allocation, |header, mut raw_table| {
if let Some(old_value) = raw_table.insert(encoded_key, encoded_value) {
Some(C::decode_value(&old_value))
} else {
header.set_item_count(item_count + 1);
None
}
})
}

#[inline]
Expand Down Expand Up @@ -308,12 +311,6 @@ impl<C: Config> HashTableOwned<C> {
RawTable::new(entry_metadata, entry_data)
}

#[inline]
fn as_raw_mut(&mut self) -> RawTableMut<'_, C::EncodedKey, C::EncodedValue, C::H> {
let (entry_metadata, entry_data) = self.allocation.data_slices_mut();
RawTableMut::new(entry_metadata, entry_data)
}

#[inline(never)]
#[cold]
fn grow(&mut self) {
Expand All @@ -327,18 +324,15 @@ impl<C: Config> HashTableOwned<C> {
// Copy the entries over with the internal `insert_entry()` method,
// which allows us to do insertions without hashing everything again.
{
let mut new_table = new_table.as_raw_mut();
with_raw_mut(&mut new_table.allocation, |header, mut raw_table| {
for (_, entry_data) in self.as_raw().iter() {
raw_table.insert(entry_data.key, entry_data.value);
}

for (_, entry_data) in self.as_raw().iter() {
new_table.insert(entry_data.key, entry_data.value);
}
header.set_item_count(initial_item_count);
});
}

new_table
.allocation
.header_mut()
.set_item_count(initial_item_count);

*self = new_table;

assert!(
Expand Down Expand Up @@ -461,12 +455,6 @@ impl<C: Config, D: Borrow<[u8]> + BorrowMut<[u8]>> HashTable<C, D> {
Ok(HashTable { allocation })
}

#[inline]
fn as_raw_mut(&mut self) -> RawTableMut<'_, C::EncodedKey, C::EncodedValue, C::H> {
let (entry_metadata, entry_data) = self.allocation.data_slices_mut();
RawTableMut::new(entry_metadata, entry_data)
}

/// Inserts the given key-value pair into the table.
/// Unlike [HashTableOwned::insert] this method cannot grow the underlying table
/// if there is not enough space for the new item. Instead the call will panic.
Expand All @@ -483,12 +471,14 @@ impl<C: Config, D: Borrow<[u8]> + BorrowMut<[u8]>> HashTable<C, D> {
let encoded_key = C::encode_key(key);
let encoded_value = C::encode_value(value);

if let Some(old_value) = self.as_raw_mut().insert(encoded_key, encoded_value) {
Some(C::decode_value(&old_value))
} else {
self.allocation.header_mut().set_item_count(item_count + 1);
None
}
with_raw_mut(&mut self.allocation, |header, mut raw_table| {
if let Some(old_value) = raw_table.insert(encoded_key, encoded_value) {
Some(C::decode_value(&old_value))
} else {
header.set_item_count(item_count + 1);
None
}
})
}
}

Expand Down Expand Up @@ -536,6 +526,18 @@ fn max_item_count_for(slot_count: usize, max_load_factor: Factor) -> usize {
max_load_factor.apply(slot_count)
}

#[inline]
fn with_raw_mut<C, M, F, R>(allocation: &mut memory_layout::Allocation<C, M>, f: F) -> R
where
C: Config,
M: BorrowMut<[u8]>,
F: FnOnce(&mut Header, RawTableMut<'_, C::EncodedKey, C::EncodedValue, C::H>) -> R,
{
allocation.with_mut_parts(|header, entry_metadata, entry_data| {
f(header, RawTableMut::new(entry_metadata, entry_data))
})
}

/// This type is used for computing max item counts for a given load factor
/// efficiently. We use integer math here so that things are the same on
/// all platforms and with all compiler settings.
Expand Down
119 changes: 61 additions & 58 deletions src/memory_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const HEADER_TAG: [u8; 4] = *b"ODHT";
const HEADER_SIZE: usize = size_of::<Header>();

impl Header {
pub fn sanity_check<C: Config>(&self, raw_bytes: &[u8]) -> Result<(), Error> {
pub fn sanity_check<C: Config>(&self, raw_bytes_len: usize) -> Result<(), Error> {
assert!(align_of::<Header>() == 1);
assert!(HEADER_SIZE % 8 == 0);

Expand All @@ -60,12 +60,12 @@ impl Header {
check_expected_size::<C::EncodedValue>("Config::EncodedValue", self.size_of_value)?;
check_expected_size::<Header>("Header", self.size_of_header)?;

if raw_bytes.len() != bytes_needed::<C>(self.slot_count()) {
if raw_bytes_len != bytes_needed::<C>(self.slot_count()) {
return Err(Error(format!(
"Provided allocation has wrong size for slot count {}. \
The allocation's size is {} but the expected size is {}.",
self.slot_count(),
raw_bytes.len(),
raw_bytes_len,
bytes_needed::<C>(self.slot_count()),
)));
}
Expand Down Expand Up @@ -120,9 +120,14 @@ impl Header {

#[inline]
fn metadata_offset<C: Config>(&self) -> isize {
self.entry_data_offset() + self.entry_data_size_in_bytes::<C>() as isize
}

#[inline]
fn entry_data_size_in_bytes<C: Config>(&self) -> usize {
let slot_count = self.slot_count();
let size_of_entry = size_of::<Entry<C::EncodedKey, C::EncodedValue>>();
(HEADER_SIZE + slot_count * size_of_entry) as isize
slot_count * size_of_entry
}

#[inline]
Expand Down Expand Up @@ -151,7 +156,7 @@ impl Header {
padding: [0u8; 2],
};

assert_eq!(header.sanity_check::<C>(raw_bytes), Ok(()));
assert_eq!(header.sanity_check::<C>(raw_bytes.len()), Ok(()));

unsafe {
*(raw_bytes.as_mut_ptr() as *mut Header) = header;
Expand Down Expand Up @@ -183,7 +188,7 @@ where

allocation
.header()
.sanity_check::<C>(allocation.bytes.borrow())?;
.sanity_check::<C>(allocation.bytes.borrow().len())?;

// Check that the hash function provides the expected hash values.
{
Expand All @@ -210,7 +215,7 @@ where

let header: &Header = unsafe { &*(raw_bytes.as_ptr() as *const Header) };

debug_assert_eq!(header.sanity_check::<C>(raw_bytes), Ok(()));
debug_assert_eq!(header.sanity_check::<C>(raw_bytes.len()), Ok(()));

header
}
Expand All @@ -237,19 +242,20 @@ where
)
};

unsafe {
debug_assert_eq!(
raw_bytes.as_ptr().offset(raw_bytes.len() as isize),
entry_metadata
.as_ptr()
.offset(entry_metadata.len() as isize) as *const u8
);

debug_assert_eq!(
entry_data.as_ptr().offset(entry_data.len() as isize) as *const u8,
entry_metadata.as_ptr() as *const u8
);
}
debug_assert_eq!(
entry_data.as_ptr_range().start as usize,
raw_bytes.as_ptr_range().start as usize + HEADER_SIZE,
);

debug_assert_eq!(
entry_data.as_ptr_range().end as usize,
entry_metadata.as_ptr_range().start as usize,
);

debug_assert_eq!(
raw_bytes.as_ptr_range().end as usize,
entry_metadata.as_ptr_range().end as usize,
);

(entry_metadata, entry_data)
}
Expand All @@ -266,60 +272,58 @@ where
M: BorrowMut<[u8]>,
{
#[inline]
pub fn header_mut(&mut self) -> &mut Header {
pub fn with_mut_parts<R>(
&mut self,
f: impl FnOnce(
&mut Header,
&mut [EntryMetadata],
&mut [Entry<C::EncodedKey, C::EncodedValue>],
) -> R,
) -> R {
let raw_bytes = self.bytes.borrow_mut();
debug_assert!(raw_bytes.len() >= HEADER_SIZE);

let header: &mut Header = unsafe { &mut *(raw_bytes.as_mut_ptr() as *mut Header) };
// Copy the address as an integer so we can use it for the debug_assertion
// below without accessing `raw_bytes` again.
let _raw_bytes_end_addr = raw_bytes.as_ptr_range().end as usize;

debug_assert_eq!(header.sanity_check::<C>(raw_bytes), Ok(()));
let (header, rest) = raw_bytes.split_at_mut(HEADER_SIZE);
let header: &mut Header = unsafe { &mut *(header.as_mut_ptr() as *mut Header) };

header
}
let slot_count = header.slot_count();
let entry_data_size_in_bytes = header.entry_data_size_in_bytes::<C>();

#[inline]
pub fn data_slices_mut<'a>(
&'a mut self,
) -> (
&'a mut [EntryMetadata],
&'a mut [Entry<C::EncodedKey, C::EncodedValue>],
) {
let slot_count = self.header().slot_count();
let metadata_offset = self.header().metadata_offset::<C>();
let entry_data_offset = self.header().entry_data_offset();

let raw_bytes = self.bytes.borrow_mut();
let (entry_data_bytes, metadata_bytes) = rest.split_at_mut(entry_data_size_in_bytes);

let entry_metadata = unsafe {
std::slice::from_raw_parts_mut(
raw_bytes.as_mut_ptr().offset(metadata_offset) as *mut EntryMetadata,
metadata_bytes.as_mut_ptr() as *mut EntryMetadata,
slot_count + REFERENCE_GROUP_SIZE,
)
};

let entry_data = unsafe {
std::slice::from_raw_parts_mut(
raw_bytes.as_mut_ptr().offset(entry_data_offset)
as *mut Entry<C::EncodedKey, C::EncodedValue>,
entry_data_bytes.as_mut_ptr() as *mut Entry<C::EncodedKey, C::EncodedValue>,
slot_count,
)
};

unsafe {
debug_assert_eq!(
raw_bytes.as_ptr().offset(raw_bytes.len() as isize),
entry_metadata
.as_ptr()
.offset(entry_metadata.len() as isize) as *const u8
);

debug_assert_eq!(
entry_data.as_ptr().offset(entry_data.len() as isize) as *const u8,
entry_metadata.as_ptr() as *const u8
);
}
debug_assert_eq!(
entry_data.as_ptr_range().start as usize,
header as *mut Header as usize + HEADER_SIZE,
);

(entry_metadata, entry_data)
debug_assert_eq!(
entry_data.as_ptr_range().end as usize,
entry_metadata.as_ptr_range().start as usize,
);

debug_assert_eq!(
_raw_bytes_end_addr,
entry_metadata.as_ptr_range().end as usize,
);

f(header, entry_metadata, entry_data)
}
}

Expand Down Expand Up @@ -356,11 +360,10 @@ pub(crate) fn init_in_place<C: Config, M: BorrowMut<[u8]>>(
_config: PhantomData::default(),
};

{
let (metadata, data) = allocation.data_slices_mut();
allocation.with_mut_parts(|_, metadata, data| {
metadata.fill(0xFF);
data.fill(Default::default());
}
});

allocation
}