Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement placement-in protocol for HashMap #40390

Merged
merged 1 commit into from
Mar 12, 2017
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
160 changes: 155 additions & 5 deletions src/libstd/collections/hash/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ use fmt::{self, Debug};
use hash::{Hash, Hasher, BuildHasher, SipHasher13};
use iter::{FromIterator, FusedIterator};
use mem::{self, replace};
use ops::{Deref, Index};
use ops::{Deref, Index, InPlace, Place, Placer};
use rand::{self, Rng};
use ptr;

use super::table::{self, Bucket, EmptyBucket, FullBucket, FullBucketMut, RawTable, SafeHash};
use super::table::BucketState::{Empty, Full};
Expand Down Expand Up @@ -483,7 +484,7 @@ fn robin_hood<'a, K: 'a, V: 'a>(bucket: FullBucketMut<'a, K, V>,
mut hash: SafeHash,
mut key: K,
mut val: V)
-> &'a mut V {
-> FullBucketMut<'a, K, V> {
let start_index = bucket.index();
let size = bucket.table().size();
// Save the *starting point*.
Expand Down Expand Up @@ -515,7 +516,7 @@ fn robin_hood<'a, K: 'a, V: 'a>(bucket: FullBucketMut<'a, K, V>,
// bucket, which is a FullBucket on top of a
// FullBucketMut, into just one FullBucketMut. The "table"
// refers to the inner FullBucketMut in this context.
return bucket.into_table().into_mut_refs().1;
return bucket.into_table();
}
Full(bucket) => bucket,
};
Expand Down Expand Up @@ -1818,6 +1819,80 @@ impl<'a, K, V> fmt::Debug for Drain<'a, K, V>
}
}

/// A place for insertion to a `Entry`.
///
/// See [`HashMap::entry`](struct.HashMap.html#method.entry) for details.
#[must_use = "places do nothing unless written to with `<-` syntax"]
#[unstable(feature = "collection_placement",
reason = "struct name and placement protocol is subject to change",
issue = "30172")]
pub struct EntryPlace<'a, K: 'a, V: 'a> {
bucket: FullBucketMut<'a, K, V>,
}

#[unstable(feature = "collection_placement",
reason = "struct name and placement protocol is subject to change",
issue = "30172")]
impl<'a, K: 'a + Debug, V: 'a + Debug> Debug for EntryPlace<'a, K, V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("EntryPlace")
.field("key", self.bucket.read().0)
.field("value", self.bucket.read().1)
.finish()
}
}

#[unstable(feature = "collection_placement",
reason = "struct name and placement protocol is subject to change",
issue = "30172")]
impl<'a, K, V> Drop for EntryPlace<'a, K, V> {
fn drop(&mut self) {
// Inplacement insertion failed. Only key need to drop.
// The value is failed to insert into map.
unsafe { self.bucket.remove_key() };
}
}

#[unstable(feature = "collection_placement",
reason = "placement protocol is subject to change",
issue = "30172")]
impl<'a, K, V> Placer<V> for Entry<'a, K, V> {
type Place = EntryPlace<'a, K, V>;

fn make_place(self) -> EntryPlace<'a, K, V> {
let b = match self {
Occupied(mut o) => {
unsafe { ptr::drop_in_place(o.elem.read_mut().1); }
o.elem
}
Vacant(v) => {
unsafe { v.insert_key() }
}
};
EntryPlace { bucket: b }
}
}

#[unstable(feature = "collection_placement",
reason = "placement protocol is subject to change",
issue = "30172")]
impl<'a, K, V> Place<V> for EntryPlace<'a, K, V> {
fn pointer(&mut self) -> *mut V {
self.bucket.read_mut().1
}
}

#[unstable(feature = "collection_placement",
reason = "placement protocol is subject to change",
issue = "30172")]
impl<'a, K, V> InPlace<V> for EntryPlace<'a, K, V> {
type Owner = ();

unsafe fn finalize(self) {
Copy link
Contributor

@arthurprs arthurprs Mar 10, 2017

Choose a reason for hiding this comment

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

Thinking a bit more about this you can forget(self) here, avoiding the flag altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good suggestion. Fixed.

mem::forget(self);
}
}

impl<'a, K, V> Entry<'a, K, V> {
#[stable(feature = "rust1", since = "1.0.0")]
/// Ensures a value is in the entry by inserting the default if empty, and returns
Expand Down Expand Up @@ -2108,7 +2183,7 @@ impl<'a, K: 'a, V: 'a> VacantEntry<'a, K, V> {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn insert(self, value: V) -> &'a mut V {
match self.elem {
let b = match self.elem {
NeqElem(mut bucket, disp) => {
if disp >= DISPLACEMENT_THRESHOLD {
bucket.table_mut().set_tag(true);
Expand All @@ -2119,7 +2194,28 @@ impl<'a, K: 'a, V: 'a> VacantEntry<'a, K, V> {
if disp >= DISPLACEMENT_THRESHOLD {
bucket.table_mut().set_tag(true);
}
bucket.put(self.hash, self.key, value).into_mut_refs().1
bucket.put(self.hash, self.key, value)
},
};
b.into_mut_refs().1
}

// Only used for InPlacement insert. Avoid unnecessary value copy.
// The value remains uninitialized.
unsafe fn insert_key(self) -> FullBucketMut<'a, K, V> {
match self.elem {
NeqElem(mut bucket, disp) => {
if disp >= DISPLACEMENT_THRESHOLD {
bucket.table_mut().set_tag(true);
}
let uninit = mem::uninitialized();
robin_hood(bucket, disp, self.hash, self.key, uninit)
},
NoElem(mut bucket, disp) => {
if disp >= DISPLACEMENT_THRESHOLD {
bucket.table_mut().set_tag(true);
}
bucket.put_key(self.hash, self.key)
},
}
}
Expand Down Expand Up @@ -2392,6 +2488,7 @@ mod test_map {
use super::RandomState;
use cell::RefCell;
use rand::{thread_rng, Rng};
use panic;

#[test]
fn test_zero_capacities() {
Expand Down Expand Up @@ -3265,4 +3362,57 @@ mod test_map {
}
panic!("Adaptive early resize failed");
}

#[test]
fn test_placement_in() {
let mut map = HashMap::new();
map.extend((0..10).map(|i| (i, i)));

map.entry(100) <- 100;
assert_eq!(map[&100], 100);

map.entry(0) <- 10;
assert_eq!(map[&0], 10);

assert_eq!(map.len(), 11);
}

#[test]
fn test_placement_panic() {
let mut map = HashMap::new();
map.extend((0..10).map(|i| (i, i)));

fn mkpanic() -> usize { panic!() }

// modify existing key
// when panic happens, previous key is removed.
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| { map.entry(0) <- mkpanic(); }));
assert_eq!(map.len(), 9);
assert!(!map.contains_key(&0));

// add new key
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| { map.entry(100) <- mkpanic(); }));
assert_eq!(map.len(), 9);
assert!(!map.contains_key(&100));
}

#[test]
fn test_placement_drop() {
// correctly drop
struct TestV<'a>(&'a mut bool);
impl<'a> Drop for TestV<'a> {
fn drop(&mut self) {
if !*self.0 { panic!("value double drop!"); } // no double drop
*self.0 = false;
}
}

fn makepanic<'a>() -> TestV<'a> { panic!() }

let mut can_drop = true;
let mut hm = HashMap::new();
hm.insert(0, TestV(&mut can_drop));
let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| { hm.entry(0) <- makepanic(); }));
assert_eq!(hm.len(), 0);
}
}
27 changes: 27 additions & 0 deletions src/libstd/collections/hash/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,22 @@ impl<K, V, M> EmptyBucket<K, V, M>
table: self.table,
}
}

/// Puts given key, remain value uninitialized.
/// It is only used for inplacement insertion.
pub unsafe fn put_key(mut self, hash: SafeHash, key: K) -> FullBucket<K, V, M> {
*self.raw.hash = hash.inspect();
let pair_mut = self.raw.pair as *mut (K, V);
ptr::write(&mut (*pair_mut).0, key);

self.table.borrow_table_mut().size += 1;

FullBucket {
raw: self.raw,
idx: self.idx,
table: self.table,
}
}
}

impl<K, V, M: Deref<Target = RawTable<K, V>>> FullBucket<K, V, M> {
Expand Down Expand Up @@ -581,6 +597,17 @@ impl<'t, K, V> FullBucket<K, V, &'t mut RawTable<K, V>> {
v)
}
}

/// Remove this bucket's `key` from the hashtable.
/// Only used for inplacement insertion.
/// NOTE: `Value` is uninitialized when this function is called, don't try to drop the `Value`.
pub unsafe fn remove_key(&mut self) {
self.table.size -= 1;

*self.raw.hash = EMPTY_BUCKET;
let pair_mut = self.raw.pair as *mut (K, V);
ptr::drop_in_place(&mut (*pair_mut).0); // only drop key
}
}

// This use of `Put` is misleading and restrictive, but safe and sufficient for our use cases
Expand Down
1 change: 1 addition & 0 deletions src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@
#![feature(panic_unwind)]
#![feature(peek)]
#![feature(placement_in_syntax)]
#![feature(placement_new_protocol)]
#![feature(prelude_import)]
#![feature(pub_restricted)]
#![feature(rand)]
Expand Down