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 Update trait for sets/maps #502

Merged
merged 2 commits into from
Jun 19, 2024
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
2 changes: 1 addition & 1 deletion src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct Id {
}

impl Id {
pub const MAX_U32: u32 = std::u32::MAX - 0xFF;
pub const MAX_U32: u32 = u32::MAX - 0xFF;
pub const MAX_USIZE: usize = Self::MAX_U32 as usize;

/// Create a `salsa::Id` from a u32 value. This value should
Expand Down
2 changes: 1 addition & 1 deletion src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct IngredientIndex(u32);
impl IngredientIndex {
/// Create an ingredient index from a usize.
pub(crate) fn from(v: usize) -> Self {
assert!(v < (std::u32::MAX as usize));
assert!(v < (u32::MAX as usize));
Self(v as u32)
}

Expand Down
122 changes: 113 additions & 9 deletions src/update.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::path::PathBuf;
use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
hash::{BuildHasher, Hash},
path::PathBuf,
};

use crate::Revision;

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

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

macro_rules! maybe_update_set {
($old_pointer: expr, $new_set: expr) => {{
let old_pointer = $old_pointer;
let new_set = $new_set;

let old_set: &mut Self = unsafe { &mut *old_pointer };

if *old_set == new_set {
false
} else {
old_set.clear();
old_set.extend(new_set);
return true;
}
}};
}

unsafe impl<K, S> Update for HashSet<K, S>
where
K: Update + Eq + Hash,
S: BuildHasher,
{
unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool {
maybe_update_set!(old_pointer, new_set)
}
}

unsafe impl<K> Update for BTreeSet<K>
where
K: Update + Eq + Ord,
{
unsafe fn maybe_update(old_pointer: *mut Self, new_set: Self) -> bool {
maybe_update_set!(old_pointer, new_set)
}
}

// Duck typing FTW, it was too annoying to make a proper function out of this.
macro_rules! maybe_update_map {
($old_pointer: expr, $new_map: expr) => {
'function: {
let old_pointer = $old_pointer;
let new_map = $new_map;
let old_map: &mut Self = unsafe { &mut *old_pointer };

// To be considered "equal", the set of keys
// must be the same between the two maps.
let same_keys =
old_map.len() == new_map.len() && old_map.keys().all(|k| new_map.contains_key(k));

// If the set of keys has changed, then just pull in the new values
// from new_map and discard the old ones.
if !same_keys {
old_map.clear();
old_map.extend(new_map);
break 'function true;
}

// Otherwise, recursively descend to the values.
// We do not invoke `K::update` because we assume
// that if the values are `Eq` they must not need
// updating (see the trait criteria).
let mut changed = false;
for (key, new_value) in new_map.into_iter() {
let old_value = old_map.get_mut(&key).unwrap();
changed |= V::maybe_update(old_value, new_value);
}
changed
}
};
}

unsafe impl<K, V, S> Update for HashMap<K, V, S>
where
K: Update + Eq + Hash,
V: Update,
S: BuildHasher,
{
unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool {
maybe_update_map!(old_pointer, new_map)
}
}

unsafe impl<K, V> Update for BTreeMap<K, V>
where
K: Update + Eq + Ord,
V: Update,
{
unsafe fn maybe_update(old_pointer: *mut Self, new_map: Self) -> bool {
maybe_update_map!(old_pointer, new_map)
}
}

unsafe impl<T> Update for Box<T>
where
T: Update,
Expand Down
Loading