Skip to content

Commit 99be5d9

Browse files
authored
Merge pull request #737 from davidbarsky/davidbarsky/remove-arc-swap
internal: Replace `arc-swap` with manual `AtomicPtr`
2 parents 26aeeec + da9a21c commit 99be5d9

9 files changed

+166
-149
lines changed

Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ rust-version = "1.80"
1212
salsa-macro-rules = { version = "0.1.0", path = "components/salsa-macro-rules" }
1313
salsa-macros = { version = "0.18.0", path = "components/salsa-macros" }
1414

15-
arc-swap = "1"
1615
boxcar = "0.2.9"
1716
crossbeam-queue = "0.3.11"
1817
dashmap = { version = "6", features = ["raw-api"] }

src/function.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{any::Any, fmt, mem::ManuallyDrop, sync::Arc};
1+
use std::{any::Any, fmt, ptr::NonNull};
22

33
use crate::{
44
accumulator::accumulated_map::{AccumulatedMap, InputAccumulatedValues},
@@ -176,19 +176,25 @@ where
176176
memo: memo::Memo<C::Output<'db>>,
177177
memo_ingredient_index: MemoIngredientIndex,
178178
) -> &'db memo::Memo<C::Output<'db>> {
179-
let memo = Arc::new(memo);
179+
// We convert to a `NonNull` here as soon as possible because we are going to alias
180+
// into the `Box`, which is a `noalias` type.
181+
let memo = unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(memo))) };
182+
180183
// Unsafety conditions: memo must be in the map (it's not yet, but it will be by the time this
181184
// value is returned) and anything removed from map is added to deleted entries (ensured elsewhere).
182-
let db_memo = unsafe { self.extend_memo_lifetime(&memo) };
185+
let db_memo = unsafe { self.extend_memo_lifetime(memo.as_ref()) };
186+
183187
// Safety: We delay the drop of `old_value` until a new revision starts which ensures no
184188
// references will exist for the memo contents.
185189
if let Some(old_value) =
186190
unsafe { self.insert_memo_into_table_for(zalsa, id, memo, memo_ingredient_index) }
187191
{
188192
// In case there is a reference to the old memo out there, we have to store it
189193
// in the deleted entries. This will get cleared when a new revision starts.
190-
self.deleted_entries
191-
.push(ManuallyDrop::into_inner(old_value));
194+
//
195+
// SAFETY: Once the revision starts, there will be no oustanding borrows to the
196+
// memo contents, and so it will be safe to free.
197+
unsafe { self.deleted_entries.push(old_value) };
192198
}
193199
db_memo
194200
}
@@ -254,7 +260,6 @@ where
254260
let ingredient_index = table.ingredient_index(evict);
255261
Self::evict_value_from_memo_for(
256262
table.memos_mut(evict),
257-
&self.deleted_entries,
258263
self.memo_ingredient_indices.get(ingredient_index),
259264
)
260265
});

src/function/delete.rs

+32-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
use std::ptr::NonNull;
2+
13
use crossbeam_queue::SegQueue;
24

3-
use super::{memo::ArcMemo, Configuration};
5+
use super::memo::Memo;
6+
use super::Configuration;
47

58
/// Stores the list of memos that have been deleted so they can be freed
69
/// once the next revision starts. See the comment on the field
710
/// `deleted_entries` of [`FunctionIngredient`][] for more details.
811
pub(super) struct DeletedEntries<C: Configuration> {
9-
seg_queue: SegQueue<ArcMemo<'static, C>>,
12+
seg_queue: SegQueue<SharedBox<Memo<C::Output<'static>>>>,
1013
}
1114

15+
unsafe impl<T: Send> Send for SharedBox<T> {}
16+
unsafe impl<T: Sync> Sync for SharedBox<T> {}
17+
1218
impl<C: Configuration> Default for DeletedEntries<C> {
1319
fn default() -> Self {
1420
Self {
@@ -18,8 +24,29 @@ impl<C: Configuration> Default for DeletedEntries<C> {
1824
}
1925

2026
impl<C: Configuration> DeletedEntries<C> {
21-
pub(super) fn push(&self, memo: ArcMemo<'_, C>) {
22-
let memo = unsafe { std::mem::transmute::<ArcMemo<'_, C>, ArcMemo<'static, C>>(memo) };
23-
self.seg_queue.push(memo);
27+
/// # Safety
28+
///
29+
/// The memo must be valid and safe to free when the `DeletedEntries` list is dropped.
30+
pub(super) unsafe fn push(&self, memo: NonNull<Memo<C::Output<'_>>>) {
31+
let memo = unsafe {
32+
std::mem::transmute::<NonNull<Memo<C::Output<'_>>>, NonNull<Memo<C::Output<'static>>>>(
33+
memo,
34+
)
35+
};
36+
37+
self.seg_queue.push(SharedBox(memo));
38+
}
39+
}
40+
41+
/// A wrapper around `NonNull` that frees the allocation when it is dropped.
42+
///
43+
/// `crossbeam::SegQueue` does not expose mutable accessors so we have to create
44+
/// a wrapper to run code during `Drop`.
45+
struct SharedBox<T>(NonNull<T>);
46+
47+
impl<T> Drop for SharedBox<T> {
48+
fn drop(&mut self) {
49+
// SAFETY: Guaranteed by the caller of `DeletedEntries::push`.
50+
unsafe { drop(Box::from_raw(self.0.as_ptr())) };
2451
}
2552
}

src/function/execute.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::sync::Arc;
2-
31
use crate::{
42
zalsa::ZalsaDatabase, zalsa_local::ActiveQueryGuard, Cycle, Database, Event, EventKind,
53
};
@@ -23,7 +21,7 @@ where
2321
&'db self,
2422
db: &'db C::DbView,
2523
active_query: ActiveQueryGuard<'_>,
26-
opt_old_memo: Option<Arc<Memo<C::Output<'_>>>>,
24+
opt_old_memo: Option<&Memo<C::Output<'_>>>,
2725
) -> &'db Memo<C::Output<'db>> {
2826
let zalsa = db.zalsa();
2927
let revision_now = zalsa.current_revision();

src/function/maybe_changed_after.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ where
3838
MaybeChangedAfter::No(memo.revisions.accumulated_inputs.load())
3939
};
4040
}
41-
drop(memo_guard); // release the arc-swap guard before cold path
4241
if let Some(mcs) =
4342
self.maybe_changed_after_cold(zalsa, db, id, revision, memo_ingredient_index)
4443
{
@@ -86,7 +85,7 @@ where
8685
);
8786

8887
// Check if the inputs are still valid. We can just compare `changed_at`.
89-
if self.deep_verify_memo(db, zalsa, &old_memo, &active_query) {
88+
if self.deep_verify_memo(db, zalsa, old_memo, &active_query) {
9089
return Some(if old_memo.revisions.changed_at > revision {
9190
MaybeChangedAfter::Yes
9291
} else {

src/function/memo.rs

+30-49
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use std::any::Any;
22
use std::fmt::Debug;
33
use std::fmt::Formatter;
4-
use std::mem::ManuallyDrop;
5-
use std::sync::Arc;
4+
use std::ptr::NonNull;
65

76
use crate::accumulator::accumulated_map::InputAccumulatedValues;
8-
use crate::function::DeletedEntries;
97
use crate::revision::AtomicRevision;
108
use crate::table::memo::MemoTable;
119
use crate::zalsa::MemoIngredientIndex;
@@ -17,21 +15,33 @@ use crate::{
1715

1816
use super::{Configuration, IngredientImpl};
1917

20-
#[allow(type_alias_bounds)]
21-
pub(super) type ArcMemo<'lt, C: Configuration> = Arc<Memo<<C as Configuration>::Output<'lt>>>;
22-
2318
impl<C: Configuration> IngredientImpl<C> {
2419
/// Memos have to be stored internally using `'static` as the database lifetime.
2520
/// This (unsafe) function call converts from something tied to self to static.
2621
/// Values transmuted this way have to be transmuted back to being tied to self
2722
/// when they are returned to the user.
28-
unsafe fn to_static<'db>(&'db self, memo: ArcMemo<'db, C>) -> ArcMemo<'static, C> {
29-
unsafe { std::mem::transmute(memo) }
23+
unsafe fn to_static<'db>(
24+
&'db self,
25+
memo: NonNull<Memo<C::Output<'db>>>,
26+
) -> NonNull<Memo<C::Output<'static>>> {
27+
memo.cast()
3028
}
3129

3230
/// Convert from an internal memo (which uses `'static`) to one tied to self
3331
/// so it can be publicly released.
34-
unsafe fn to_self<'db>(&'db self, memo: ArcMemo<'static, C>) -> ArcMemo<'db, C> {
32+
unsafe fn to_self<'db>(
33+
&'db self,
34+
memo: NonNull<Memo<C::Output<'static>>>,
35+
) -> NonNull<Memo<C::Output<'db>>> {
36+
memo.cast()
37+
}
38+
39+
/// Convert from an internal memo (which uses `'static`) to one tied to self
40+
/// so it can be publicly released.
41+
unsafe fn to_self_ref<'db>(
42+
&'db self,
43+
memo: &'db Memo<C::Output<'static>>,
44+
) -> &'db Memo<C::Output<'db>> {
3545
unsafe { std::mem::transmute(memo) }
3646
}
3747

@@ -45,17 +55,16 @@ impl<C: Configuration> IngredientImpl<C> {
4555
&'db self,
4656
zalsa: &'db Zalsa,
4757
id: Id,
48-
memo: ArcMemo<'db, C>,
58+
memo: NonNull<Memo<C::Output<'db>>>,
4959
memo_ingredient_index: MemoIngredientIndex,
50-
) -> Option<ManuallyDrop<ArcMemo<'db, C>>> {
60+
) -> Option<NonNull<Memo<C::Output<'db>>>> {
5161
let static_memo = unsafe { self.to_static(memo) };
5262
let old_static_memo = unsafe {
5363
zalsa
5464
.memo_table_for(id)
5565
.insert(memo_ingredient_index, static_memo)
5666
}?;
57-
let old_static_memo = ManuallyDrop::into_inner(old_static_memo);
58-
Some(ManuallyDrop::new(unsafe { self.to_self(old_static_memo) }))
67+
Some(unsafe { self.to_self(old_static_memo) })
5968
}
6069

6170
/// Loads the current memo for `key_index`. This does not hold any sort of
@@ -66,20 +75,20 @@ impl<C: Configuration> IngredientImpl<C> {
6675
zalsa: &'db Zalsa,
6776
id: Id,
6877
memo_ingredient_index: MemoIngredientIndex,
69-
) -> Option<ArcMemo<'db, C>> {
78+
) -> Option<&'db Memo<C::Output<'db>>> {
7079
let static_memo = zalsa.memo_table_for(id).get(memo_ingredient_index)?;
71-
unsafe { Some(self.to_self(static_memo)) }
80+
81+
unsafe { Some(self.to_self_ref(static_memo)) }
7282
}
7383

7484
/// Evicts the existing memo for the given key, replacing it
7585
/// with an equivalent memo that has no value. If the memo is untracked, BaseInput,
7686
/// or has values assigned as output of another query, this has no effect.
7787
pub(super) fn evict_value_from_memo_for(
7888
table: &mut MemoTable,
79-
deleted_entries: &DeletedEntries<C>,
8089
memo_ingredient_index: MemoIngredientIndex,
8190
) {
82-
let map = |memo: ArcMemo<'static, C>| -> ArcMemo<'static, C> {
91+
let map = |memo: &mut Memo<C::Output<'static>>| {
8392
match &memo.revisions.origin {
8493
QueryOrigin::Assigned(_)
8594
| QueryOrigin::DerivedUntracked(_)
@@ -88,43 +97,15 @@ impl<C: Configuration> IngredientImpl<C> {
8897
// assigned as output of another query
8998
// or those with untracked inputs
9099
// as their values cannot be reconstructed.
91-
memo
92100
}
93101
QueryOrigin::Derived(_) => {
94-
// Note that we cannot use `Arc::get_mut` here as the use of `ArcSwap` makes it
95-
// impossible to get unique access to the interior Arc
96-
// QueryRevisions: !Clone to discourage cloning, we need it here though
97-
let &QueryRevisions {
98-
changed_at,
99-
durability,
100-
ref origin,
101-
ref tracked_struct_ids,
102-
ref accumulated,
103-
ref accumulated_inputs,
104-
} = &memo.revisions;
105-
// Re-assemble the memo but with the value set to `None`
106-
Arc::new(Memo::new(
107-
None,
108-
memo.verified_at.load(),
109-
QueryRevisions {
110-
changed_at,
111-
durability,
112-
origin: origin.clone(),
113-
tracked_struct_ids: tracked_struct_ids.clone(),
114-
accumulated: accumulated.clone(),
115-
accumulated_inputs: accumulated_inputs.clone(),
116-
},
117-
))
102+
// Set the memo value to `None`.
103+
memo.value = None;
118104
}
119105
}
120106
};
121-
// SAFETY: We queue the old value for deletion, delaying its drop until the next revision bump.
122-
let old = unsafe { table.map_memo(memo_ingredient_index, map) };
123-
if let Some(old) = old {
124-
// In case there is a reference to the old memo out there, we have to store it
125-
// in the deleted entries. This will get cleared when a new revision starts.
126-
deleted_entries.push(ManuallyDrop::into_inner(old));
127-
}
107+
108+
table.map_memo(memo_ingredient_index, map)
128109
}
129110
}
130111

src/function/specify.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ where
7575

7676
let memo_ingredient_index = self.memo_ingredient_index(zalsa, key);
7777
if let Some(old_memo) = self.get_memo_from_table_for(zalsa, key, memo_ingredient_index) {
78-
self.backdate_if_appropriate(&old_memo, &mut revisions, &value);
79-
self.diff_outputs(zalsa, db, database_key_index, &old_memo, &mut revisions);
78+
self.backdate_if_appropriate(old_memo, &mut revisions, &value);
79+
self.diff_outputs(zalsa, db, database_key_index, old_memo, &mut revisions);
8080
}
8181

8282
let memo = Memo {

0 commit comments

Comments
 (0)