Skip to content

Commit 1cb6925

Browse files
authored
Merge pull request RustPython#3696 from youknowone/dict
Remove DictInner::next_new_entry_idx and IndexEntry as a shallow wrapper of i64
2 parents aa10084 + 25a6629 commit 1cb6925

File tree

1 file changed

+87
-74
lines changed

1 file changed

+87
-74
lines changed

vm/src/dictdatatype.rs

Lines changed: 87 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -37,34 +37,26 @@ impl<T> fmt::Debug for Dict<T> {
3737
}
3838
}
3939

40-
#[derive(Debug, Copy, Clone)]
41-
enum IndexEntry {
42-
Dummy,
43-
Free,
44-
Index(usize),
45-
}
40+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
41+
#[repr(transparent)]
42+
struct IndexEntry(i64);
4643

4744
impl IndexEntry {
48-
const FREE: i64 = -1;
49-
const DUMMY: i64 = -2;
50-
}
45+
const FREE: Self = Self(-1);
46+
const DUMMY: Self = Self(-2);
5147

52-
impl From<i64> for IndexEntry {
53-
fn from(idx: i64) -> Self {
54-
match idx {
55-
IndexEntry::FREE => IndexEntry::Free,
56-
IndexEntry::DUMMY => IndexEntry::Dummy,
57-
x => IndexEntry::Index(x as usize),
58-
}
48+
/// # Safety
49+
/// idx must not be one of FREE or DUMMY
50+
unsafe fn from_index_unchecked(idx: usize) -> Self {
51+
debug_assert!((idx as isize) >= 0);
52+
Self(idx as i64)
5953
}
60-
}
6154

62-
impl From<IndexEntry> for i64 {
63-
fn from(idx: IndexEntry) -> Self {
64-
match idx {
65-
IndexEntry::Free => IndexEntry::FREE,
66-
IndexEntry::Dummy => IndexEntry::DUMMY,
67-
IndexEntry::Index(i) => i as i64,
55+
fn index(self) -> Option<usize> {
56+
if self.0 >= 0 {
57+
Some(self.0 as usize)
58+
} else {
59+
None
6860
}
6961
}
7062
}
@@ -73,10 +65,8 @@ impl From<IndexEntry> for i64 {
7365
struct DictInner<T> {
7466
used: usize,
7567
filled: usize,
76-
indices: Vec<i64>,
68+
indices: Vec<IndexEntry>,
7769
entries: Vec<Option<DictEntry<T>>>,
78-
// index to new inserted element should be in entries
79-
next_new_entry_idx: usize,
8070
}
8171

8272
impl<T: Clone> Clone for Dict<T> {
@@ -95,7 +85,6 @@ impl<T> Default for Dict<T> {
9585
filled: 0,
9686
indices: vec![IndexEntry::FREE; 8],
9787
entries: Vec::new(),
98-
next_new_entry_idx: 0,
9988
}),
10089
}
10190
}
@@ -108,6 +97,7 @@ struct DictEntry<T> {
10897
index: IndexIndex,
10998
value: T,
11099
}
100+
static_assertions::assert_eq_size!(DictEntry<PyObjectRef>, Option<DictEntry<PyObjectRef>>);
111101

112102
impl<T: Clone> DictEntry<T> {
113103
pub(crate) fn as_tuple(&self) -> (PyObjectRef, T) {
@@ -175,19 +165,23 @@ impl<T> DictInner<T> {
175165
let mut idxs = GenIndexes::new(entry.hash, mask);
176166
loop {
177167
let index_index = idxs.next();
178-
let idx = &mut self.indices[index_index];
179-
if *idx == IndexEntry::FREE {
180-
*idx = entry_idx as i64;
181-
entry.index = index_index;
182-
break;
168+
unsafe {
169+
// Safety: index is always valid here
170+
// index_index is generated by idxs
171+
// entry_idx is saved one
172+
let idx = self.indices.get_unchecked_mut(index_index);
173+
if *idx == IndexEntry::FREE {
174+
*idx = IndexEntry::from_index_unchecked(entry_idx);
175+
entry.index = index_index;
176+
break;
177+
}
183178
}
184179
}
185180
} else {
186181
//removed entry
187182
}
188183
}
189184
self.filled = self.used;
190-
self.next_new_entry_idx = self.entries.len();
191185
}
192186

193187
fn unchecked_push(
@@ -204,16 +198,15 @@ impl<T> DictInner<T> {
204198
value,
205199
index,
206200
};
207-
let entry_index = self.next_new_entry_idx;
208-
if self.entries.len() == entry_index {
209-
self.entries.push(Some(entry));
210-
} else {
211-
self.entries[entry_index] = Some(entry);
212-
}
213-
self.indices[index] = entry_index as i64;
201+
let entry_index = self.entries.len();
202+
self.entries.push(Some(entry));
203+
self.indices[index] = unsafe {
204+
// SAFETY: entry_index is self.entries.len(). it never can
205+
// grow to `usize-2` because hash tables cannot full its index
206+
IndexEntry::from_index_unchecked(entry_index)
207+
};
214208
self.used += 1;
215-
self.next_new_entry_idx += 1;
216-
if let IndexEntry::Free = index_entry {
209+
if let IndexEntry::FREE = index_entry {
217210
self.filled += 1;
218211
if let Some(new_size) = self.should_resize() {
219212
self.resize(new_size)
@@ -260,7 +253,7 @@ impl<T: Clone> Dict<T> {
260253
let _removed = loop {
261254
let (entry_index, index_index) = self.lookup(vm, key, hash, None)?;
262255
let mut inner = self.write();
263-
if let IndexEntry::Index(index) = entry_index {
256+
if let Some(index) = entry_index.index() {
264257
// Update existing key
265258
if let Some(entry) = inner.entries.get_mut(index) {
266259
let entry = entry
@@ -287,7 +280,7 @@ impl<T: Clone> Dict<T> {
287280

288281
pub fn contains<K: DictKey + ?Sized>(&self, vm: &VirtualMachine, key: &K) -> PyResult<bool> {
289282
let (entry, _) = self.lookup(vm, key, key.key_hash(vm)?, None)?;
290-
Ok(matches!(entry, IndexEntry::Index(_)))
283+
Ok(entry.index().is_some())
291284
}
292285

293286
/// Retrieve a key
@@ -305,7 +298,7 @@ impl<T: Clone> Dict<T> {
305298
) -> PyResult<Option<T>> {
306299
let ret = loop {
307300
let (entry, index_index) = self.lookup(vm, key, hash, None)?;
308-
if let IndexEntry::Index(index) = entry {
301+
if let Some(index) = entry.index() {
309302
let inner = self.read();
310303
if let Some(entry) = inner.entries.get(index) {
311304
let entry = extract_dict_entry(entry);
@@ -346,7 +339,6 @@ impl<T: Clone> Dict<T> {
346339
inner.indices.resize(8, IndexEntry::FREE);
347340
inner.used = 0;
348341
inner.filled = 0;
349-
inner.next_new_entry_idx = 0;
350342
// defer dec rc
351343
std::mem::take(&mut inner.entries)
352344
};
@@ -394,7 +386,7 @@ impl<T: Clone> Dict<T> {
394386
let _removed = loop {
395387
let lookup = self.lookup(vm, key, hash, None)?;
396388
let (entry, index_index) = lookup;
397-
if let IndexEntry::Index(_) = entry {
389+
if entry.index().is_some() {
398390
match self.pop_inner(lookup) {
399391
ControlFlow::Break(Some(entry)) => break Some(entry),
400392
_ => continue,
@@ -416,8 +408,8 @@ impl<T: Clone> Dict<T> {
416408
let hash = key.key_hash(vm)?;
417409
let res = loop {
418410
let lookup = self.lookup(vm, key, hash, None)?;
419-
let (entry, index_index) = lookup;
420-
if let IndexEntry::Index(index) = entry {
411+
let (index_entry, index_index) = lookup;
412+
if let Some(index) = index_entry.index() {
421413
let inner = self.read();
422414
if let Some(entry) = inner.entries.get(index) {
423415
let entry = extract_dict_entry(entry);
@@ -433,7 +425,13 @@ impl<T: Clone> Dict<T> {
433425
} else {
434426
let value = default();
435427
let mut inner = self.write();
436-
inner.unchecked_push(index_index, hash, key.to_pyobject(vm), value.clone(), entry);
428+
inner.unchecked_push(
429+
index_index,
430+
hash,
431+
key.to_pyobject(vm),
432+
value.clone(),
433+
index_entry,
434+
);
437435
break value;
438436
}
439437
};
@@ -454,8 +452,8 @@ impl<T: Clone> Dict<T> {
454452
let hash = key.key_hash(vm)?;
455453
let res = loop {
456454
let lookup = self.lookup(vm, key, hash, None)?;
457-
let (entry, index_index) = lookup;
458-
if let IndexEntry::Index(index) = entry {
455+
let (index_entry, index_index) = lookup;
456+
if let Some(index) = index_entry.index() {
459457
let inner = self.read();
460458
if let Some(entry) = inner.entries.get(index) {
461459
let entry = extract_dict_entry(entry);
@@ -473,7 +471,7 @@ impl<T: Clone> Dict<T> {
473471
let key = key.to_pyobject(vm);
474472
let mut inner = self.write();
475473
let ret = (key.clone(), value.clone());
476-
inner.unchecked_push(index_index, hash, key, value, entry);
474+
inner.unchecked_push(index_index, hash, key, value, index_entry);
477475
break ret;
478476
}
479477
};
@@ -541,7 +539,7 @@ impl<T: Clone> Dict<T> {
541539
mut lock: Option<PyRwLockReadGuard<DictInner<T>>>,
542540
) -> PyResult<LookupResult> {
543541
let mut idxs = None;
544-
let mut freeslot = None;
542+
let mut free_slot = None;
545543
let ret = 'outer: loop {
546544
let (entry_key, ret) = {
547545
let inner = lock.take().unwrap_or_else(|| self.read());
@@ -550,22 +548,31 @@ impl<T: Clone> Dict<T> {
550548
});
551549
loop {
552550
let index_index = idxs.next();
553-
match IndexEntry::from(inner.indices[index_index]) {
554-
IndexEntry::Dummy => {
555-
if freeslot.is_none() {
556-
freeslot = Some(index_index);
551+
let index_entry = *unsafe {
552+
// Safety: index_index is generated
553+
inner.indices.get_unchecked(index_index)
554+
};
555+
match index_entry {
556+
IndexEntry::DUMMY => {
557+
if free_slot.is_none() {
558+
free_slot = Some(index_index);
557559
}
558560
}
559-
IndexEntry::Free => {
560-
let idxs = match freeslot {
561-
Some(free) => (IndexEntry::Dummy, free),
562-
None => (IndexEntry::Free, index_index),
561+
IndexEntry::FREE => {
562+
let idxs = match free_slot {
563+
Some(free) => (IndexEntry::DUMMY, free),
564+
None => (IndexEntry::FREE, index_index),
563565
};
564566
return Ok(idxs);
565567
}
566-
IndexEntry::Index(i) => {
567-
let entry = &inner.entries[i].as_ref().unwrap();
568-
let ret = (IndexEntry::Index(i), index_index);
568+
idx => {
569+
let entry = unsafe {
570+
// Safety: DUMMY and FREE are already handled above.
571+
// i is always valid and entry always exists.
572+
let i = idx.index().unwrap_unchecked();
573+
inner.entries.get_unchecked(i).as_ref().unwrap_unchecked()
574+
};
575+
let ret = (idx, index_index);
569576
if key.key_is(&entry.key) {
570577
break 'outer ret;
571578
} else if entry.hash == hash_value {
@@ -602,7 +609,7 @@ impl<T: Clone> Dict<T> {
602609
pred: impl Fn(&T) -> Result<bool, E>,
603610
) -> Result<PopInnerResult<T>, E> {
604611
let (entry_index, index_index) = lookup;
605-
let entry_index = if let IndexEntry::Index(entry_index) = entry_index {
612+
let entry_index = if let Some(entry_index) = entry_index.index() {
606613
entry_index
607614
} else {
608615
return Ok(ControlFlow::Break(None));
@@ -623,7 +630,10 @@ impl<T: Clone> Dict<T> {
623630
// The dict was changed since we did lookup. Let's try again.
624631
_ => return Ok(ControlFlow::Continue(())),
625632
}
626-
inner.indices[index_index] = IndexEntry::DUMMY;
633+
*unsafe {
634+
// index_index is result of lookup
635+
inner.indices.get_unchecked_mut(index_index)
636+
} = IndexEntry::DUMMY;
627637
inner.used -= 1;
628638
let removed = slot.take();
629639
Ok(ControlFlow::Break(removed))
@@ -644,14 +654,17 @@ impl<T: Clone> Dict<T> {
644654

645655
pub fn pop_back(&self) -> Option<(PyObjectRef, T)> {
646656
let mut inner = &mut *self.write();
647-
let (entry_idx, entry) = inner.entries[..inner.next_new_entry_idx]
648-
.iter_mut()
649-
.enumerate()
650-
.rev()
651-
.find_map(|(i, entry)| entry.take().map(|e| (i, e)))?;
657+
let entry = loop {
658+
let entry = inner.entries.pop()?;
659+
if let Some(entry) = entry {
660+
break entry;
661+
}
662+
};
652663
inner.used -= 1;
653-
inner.indices[entry.index] = IndexEntry::DUMMY;
654-
inner.next_new_entry_idx = entry_idx;
664+
*unsafe {
665+
// entry.index always refers valid index
666+
inner.indices.get_unchecked_mut(entry.index)
667+
} = IndexEntry::DUMMY;
655668
Some((entry.key, entry.value))
656669
}
657670

0 commit comments

Comments
 (0)