Skip to content

Commit f706aa2

Browse files
authored
Merge pull request #502 from nikomatsakis/update-sets-and-maps
implement Update trait for sets/maps
2 parents 38a44ee + 7c36154 commit f706aa2

File tree

3 files changed

+115
-11
lines changed

3 files changed

+115
-11
lines changed

src/id.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub struct Id {
1616
}
1717

1818
impl Id {
19-
pub const MAX_U32: u32 = std::u32::MAX - 0xFF;
19+
pub const MAX_U32: u32 = u32::MAX - 0xFF;
2020
pub const MAX_USIZE: usize = Self::MAX_U32 as usize;
2121

2222
/// Create a `salsa::Id` from a u32 value. This value should

src/routes.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct IngredientIndex(u32);
1111
impl IngredientIndex {
1212
/// Create an ingredient index from a usize.
1313
pub(crate) fn from(v: usize) -> Self {
14-
assert!(v < (std::u32::MAX as usize));
14+
assert!(v < (u32::MAX as usize));
1515
Self(v as u32)
1616
}
1717

src/update.rs

+113-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use std::path::PathBuf;
1+
use std::{
2+
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
3+
hash::{BuildHasher, Hash},
4+
path::PathBuf,
5+
};
26

37
use crate::Revision;
48

@@ -51,6 +55,8 @@ pub mod helper {
5155
///
5256
/// Impl will fulfill the postconditions of `maybe_update`
5357
pub unsafe trait Fallback<T> {
58+
/// # Safety
59+
///
5460
/// Same safety conditions as `Update::maybe_update`
5561
unsafe fn maybe_update(old_pointer: *mut T, new_value: T) -> bool;
5662
}
@@ -106,15 +112,21 @@ pub fn always_update<T>(
106112

107113
/// # Safety
108114
///
109-
/// The `unsafe` on the trait is to assert that `maybe_update` ensures
110-
/// the properties it is intended to ensure.
115+
/// Implementing this trait requires the implementor to verify:
116+
///
117+
/// * `maybe_update` ensures the properties it is intended to ensure.
118+
/// * If the value implements `Eq`, it is safe to compare an instance
119+
/// of the value from an older revision with one from the newer
120+
/// revision. If the value compares as equal, no update is needed to
121+
/// bring it into the newer revision.
111122
///
112-
/// `Update` must NEVER be implemented for any `&'db T` value
113-
/// (i.e., any Rust reference tied to the database).
114-
/// This is because update methods are invoked across revisions.
115-
/// The `'db` lifetimes are only valid within a single revision.
116-
/// Therefore any use of that reference in a new revision will violate
117-
/// stacked borrows.
123+
/// NB: The second point implies that `Update` cannot be implemented for any
124+
/// `&'db T` -- (i.e., any Rust reference tied to the database).
125+
/// Such a value could refer to memory that was freed in some
126+
/// earlier revision. Even if the memory is still valid, it could also
127+
/// have been part of a tracked struct whose values were mutated,
128+
/// thus invalidating the `'db` lifetime (from a stacked borrows perspective).
129+
/// Either way, the `Eq` implementation would be invalid.
118130
pub unsafe trait Update {
119131
/// # Returns
120132
///
@@ -167,6 +179,98 @@ where
167179
}
168180
}
169181

182+
macro_rules! maybe_update_set {
183+
($old_pointer: expr, $new_set: expr) => {{
184+
let old_pointer = $old_pointer;
185+
let new_set = $new_set;
186+
187+
let old_set: &mut Self = unsafe { &mut *old_pointer };
188+
189+
if *old_set == new_set {
190+
false
191+
} else {
192+
old_set.clear();
193+
old_set.extend(new_set);
194+
return true;
195+
}
196+
}};
197+
}
198+
199+
unsafe impl<K, S> Update for HashSet<K, S>
200+
where
201+
K: Update + Eq + Hash,
202+
S: BuildHasher,
203+
{
204+
unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool {
205+
maybe_update_set!(old_pointer, new_set)
206+
}
207+
}
208+
209+
unsafe impl<K> Update for BTreeSet<K>
210+
where
211+
K: Update + Eq + Ord,
212+
{
213+
unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool {
214+
maybe_update_set!(old_pointer, new_set)
215+
}
216+
}
217+
218+
// Duck typing FTW, it was too annoying to make a proper function out of this.
219+
macro_rules! maybe_update_map {
220+
($old_pointer: expr, $new_map: expr) => {
221+
'function: {
222+
let old_pointer = $old_pointer;
223+
let new_map = $new_map;
224+
let old_map: &mut Self = unsafe { &mut *old_pointer };
225+
226+
// To be considered "equal", the set of keys
227+
// must be the same between the two maps.
228+
let same_keys =
229+
old_map.len() == new_map.len() && old_map.keys().all(|k| new_map.contains_key(k));
230+
231+
// If the set of keys has changed, then just pull in the new values
232+
// from new_map and discard the old ones.
233+
if !same_keys {
234+
old_map.clear();
235+
old_map.extend(new_map);
236+
break 'function true;
237+
}
238+
239+
// Otherwise, recursively descend to the values.
240+
// We do not invoke `K::update` because we assume
241+
// that if the values are `Eq` they must not need
242+
// updating (see the trait criteria).
243+
let mut changed = false;
244+
for (key, new_value) in new_map.into_iter() {
245+
let old_value = old_map.get_mut(&key).unwrap();
246+
changed |= V::maybe_update(old_value, new_value);
247+
}
248+
changed
249+
}
250+
};
251+
}
252+
253+
unsafe impl<K, V, S> Update for HashMap<K, V, S>
254+
where
255+
K: Update + Eq + Hash,
256+
V: Update,
257+
S: BuildHasher,
258+
{
259+
unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool {
260+
maybe_update_map!(old_pointer, new_map)
261+
}
262+
}
263+
264+
unsafe impl<K, V> Update for BTreeMap<K, V>
265+
where
266+
K: Update + Eq + Ord,
267+
V: Update,
268+
{
269+
unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool {
270+
maybe_update_map!(old_pointer, new_map)
271+
}
272+
}
273+
170274
unsafe impl<T> Update for Box<T>
171275
where
172276
T: Update,

0 commit comments

Comments
 (0)