Skip to content

Commit

Permalink
Implement placement-in protocol for HashMap
Browse files Browse the repository at this point in the history
  • Loading branch information
Charlie Fan authored and F001 committed Mar 11, 2017
1 parent 1727b23 commit 584c798
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 5 deletions.
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) {
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

0 comments on commit 584c798

Please sign in to comment.