Skip to content

Commit aa3446d

Browse files
committed
Simplify unsafe code a bit
1 parent 08b0f27 commit aa3446d

File tree

1 file changed

+66
-64
lines changed

1 file changed

+66
-64
lines changed

src/tracked_struct.rs

+66-64
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ where
160160
ingredient_index: IngredientIndex,
161161

162162
/// Phantom data: we fetch `Value<C>` out from `Table`
163-
phantom: PhantomData<fn() -> Value<C>>,
163+
phantom: PhantomData<fn() -> ValueWithLock<C>>,
164164

165165
/// Store freed ids
166166
free_list: SegQueue<Id>,
@@ -255,26 +255,10 @@ impl IdentityMap {
255255
}
256256

257257
// ANCHOR: ValueStruct
258-
#[derive(Debug)]
259-
pub struct Value<C>
258+
pub struct ValueWithLock<C>
260259
where
261260
C: Configuration,
262261
{
263-
/// The durability minimum durability of all inputs consumed
264-
/// by the creator query prior to creating this tracked struct.
265-
/// If any of those inputs changes, then the creator query may
266-
/// create this struct with different values.
267-
durability: Durability,
268-
269-
/// The revision in which the tracked struct was first created.
270-
///
271-
/// Unlike `updated_at`, which gets bumped on every read,
272-
/// `created_at` is updated whenever an untracked field is updated.
273-
/// This is necessary to detect reused tracked struct ids _after_
274-
/// they've been freed in a prior revision or tracked structs that have been updated
275-
/// in-place because of a bad `Hash` implementation.
276-
created_at: Revision,
277-
278262
/// The revision when this tracked struct was last updated.
279263
/// This field also acts as a kind of "lock". Once it is equal
280264
/// to `Some(current_revision)`, the fields are locked and
@@ -295,6 +279,27 @@ where
295279
/// This `None` value should never be observable by users unless they have
296280
/// leaked a reference across threads somehow.
297281
updated_at: OptionalAtomicRevision,
282+
value: Value<C>,
283+
}
284+
285+
pub struct Value<C>
286+
where
287+
C: Configuration,
288+
{
289+
/// The durability minimum durability of all inputs consumed
290+
/// by the creator query prior to creating this tracked struct.
291+
/// If any of those inputs changes, then the creator query may
292+
/// create this struct with different values.
293+
durability: Durability,
294+
295+
/// The revision in which the tracked struct was first created.
296+
///
297+
/// Unlike `updated_at`, which gets bumped on every read,
298+
/// `created_at` is updated whenever an untracked field is updated.
299+
/// This is necessary to detect reused tracked struct ids _after_
300+
/// they've been freed in a prior revision or tracked structs that have been updated
301+
/// in-place because of a bad `Hash` implementation.
302+
created_at: Revision,
298303

299304
/// Fields of this tracked struct. They can change across revisions,
300305
/// but they do not change within a particular revision.
@@ -427,14 +432,16 @@ where
427432
fields: C::Fields<'db>,
428433
) -> Id {
429434
let current_revision = zalsa.current_revision();
430-
let value = |_| Value {
431-
created_at: current_revision,
435+
let value = |_| ValueWithLock {
432436
updated_at: OptionalAtomicRevision::new(Some(current_revision)),
433-
durability: current_deps.durability,
434-
fields: unsafe { self.to_static(fields) },
435-
revisions: C::new_revisions(current_deps.changed_at),
436-
memos: Default::default(),
437-
syncs: Default::default(),
437+
value: Value {
438+
created_at: current_revision,
439+
durability: current_deps.durability,
440+
fields: unsafe { self.to_static(fields) },
441+
revisions: C::new_revisions(current_deps.changed_at),
442+
memos: Default::default(),
443+
syncs: Default::default(),
444+
},
438445
};
439446

440447
if let Some(id) = self.free_list.pop() {
@@ -450,7 +457,7 @@ where
450457

451458
id
452459
} else {
453-
zalsa_local.allocate::<Value<C>>(zalsa.table(), self.ingredient_index, value)
460+
zalsa_local.allocate::<ValueWithLock<C>>(zalsa.table(), self.ingredient_index, value)
454461
}
455462
}
456463

@@ -484,8 +491,9 @@ where
484491
// In that case we should not modify or touch it because there may be
485492
// `&`-references to its contents floating around.
486493
//
487-
// Observing `Some(current_revision)` can happen in two scenarios: leaks (tsk tsk)
488-
// but also the scenario embodied by the test test `test_run_5_then_20` in `specify_tracked_fn_in_rev_1_but_not_2.rs`:
494+
// Observing `Some(current_revision)` can happen in two scenarios:
495+
// - leaks, see tests\preverify-struct-with-leaked-data.rs and tests\preverify-struct-with-leaked-data-2.rs
496+
// - or the following scenario (FIXME verify this? There is no test that covers this behavior):
489497
//
490498
// * Revision 1:
491499
// * Tracked function F creates tracked struct S
@@ -505,9 +513,6 @@ where
505513
// that is still live.
506514

507515
let current_revision = zalsa.current_revision();
508-
// UNSAFE: Marking as mut requires exclusive access for the duration of
509-
// the `mut`. We have now *claimed* this data by swapping in `None`,
510-
// any attempt to read concurrently will panic.
511516
let last_updated_at = unsafe { (*data_raw).updated_at.load() };
512517
assert!(
513518
last_updated_at.is_some(),
@@ -528,39 +533,36 @@ where
528533
"failed to acquire write lock, id `{id:?}` must have been leaked across threads"
529534
);
530535
}
531-
532536
// SAFETY: Marking as mut requires exclusive access for the duration of
533537
// the `mut`. We have now *claimed* this data by swapping in `None`,
534-
// any attempt to read concurrently will panic. Note that we cannot create
535-
// a `&mut` reference to the full `Value` though because
536-
// another thread may access `updated_at` concurrently.
538+
// any attempt to read concurrently will panic.
539+
let data = unsafe { &mut (*data_raw).value };
537540

538541
// SAFETY: We assert that the pointer to `data.revisions`
539542
// is a pointer into the database referencing a value
540543
// from a previous revision. As such, it continues to meet
541544
// its validity invariant and any owned content also continues
542545
// to meet its safety invariant.
543-
unsafe {
544-
if C::update_fields(
546+
let updated = unsafe {
547+
C::update_fields(
545548
current_revision,
546-
&mut (*data_raw).revisions,
547-
self.to_self_ptr(std::ptr::addr_of_mut!((*data_raw).fields)),
549+
&mut data.revisions,
550+
self.to_self_ptr(std::ptr::addr_of_mut!(data.fields)),
548551
fields,
549-
) {
550-
// Consider this a new tracked-struct (even though it still uses the same id)
551-
// when any non-tracked field got updated.
552-
// This should be rare and only ever happen if there's a hash collision
553-
// which makes Salsa consider two tracked structs to still be the same
554-
// even though the fields are different.
555-
// See `tracked-struct-id-field-bad-hash` for more details.
556-
(*data_raw).revisions = C::new_revisions(current_revision);
557-
(*data_raw).created_at = current_revision;
558-
} else if current_deps.durability < (*data_raw).durability {
559-
(*data_raw).revisions = C::new_revisions(current_revision);
560-
(*data_raw).created_at = current_revision;
561-
}
562-
(*data_raw).durability = current_deps.durability;
552+
)
553+
};
554+
if updated || current_deps.durability < data.durability {
555+
// If `updated`, consider this a new tracked-struct (even though it still uses the same id)
556+
// when any non-tracked field got updated.
557+
// This should be rare and only ever happen if there's a hash collision
558+
// which makes Salsa consider two tracked structs to still be the same
559+
// even though the fields are different.
560+
// See `tracked-struct-id-field-bad-hash` for more details.
561+
data.revisions = C::new_revisions(current_revision);
562+
data.created_at = current_revision;
563563
}
564+
data.durability = current_deps.durability;
565+
// release the lock
564566
let swapped_out = unsafe { (*data_raw).updated_at.swap_mut(Some(current_revision)) };
565567
assert!(
566568
swapped_out.is_none(),
@@ -574,10 +576,10 @@ where
574576
let val = Self::data_raw(table, id);
575577
acquire_read_lock(unsafe { &(*val).updated_at }, current_revision);
576578
// We have acquired the read lock, so it is safe to return a reference to the data.
577-
unsafe { &*val }
579+
unsafe { &(*val).value }
578580
}
579581

580-
fn data_raw(table: &Table, id: Id) -> *mut Value<C> {
582+
fn data_raw(table: &Table, id: Id) -> *mut ValueWithLock<C> {
581583
table.get_raw(id)
582584
}
583585

@@ -615,7 +617,7 @@ where
615617

616618
// Take the memo table. This is safe because we have modified `data_ref.updated_at` to `None`
617619
// signalling that we have acquired the write lock
618-
let memo_table = std::mem::take(unsafe { &mut (*data).memos });
620+
let memo_table = std::mem::take(unsafe { &mut (*data).value.memos });
619621

620622
// SAFETY: We have verified that no more references to these memos exist and so we are good
621623
// to drop them.
@@ -712,12 +714,12 @@ where
712714
pub fn entries<'db>(
713715
&'db self,
714716
db: &'db dyn crate::Database,
715-
) -> impl Iterator<Item = &'db Value<C>> {
717+
) -> impl Iterator<Item = &'db ValueWithLock<C>> {
716718
db.zalsa()
717719
.table()
718720
.pages
719721
.iter()
720-
.filter_map(|(_, page)| page.cast_type::<crate::table::Page<Value<C>>>())
722+
.filter_map(|(_, page)| page.cast_type::<crate::table::Page<ValueWithLock<C>>>())
721723
.flat_map(|page| page.slots())
722724
}
723725
}
@@ -792,7 +794,7 @@ where
792794
}
793795
}
794796

795-
impl<C> Value<C>
797+
impl<C> ValueWithLock<C>
796798
where
797799
C: Configuration,
798800
{
@@ -802,7 +804,7 @@ where
802804
/// a particular revision.
803805
#[cfg(feature = "salsa_unstable")]
804806
pub fn fields(&self) -> &C::Fields<'static> {
805-
&self.fields
807+
&self.value.fields
806808
}
807809
}
808810

@@ -826,7 +828,7 @@ fn acquire_read_lock(updated_at: &OptionalAtomicRevision, current_revision: Revi
826828
}
827829
}
828830

829-
impl<C> Slot for Value<C>
831+
impl<C> Slot for ValueWithLock<C>
830832
where
831833
C: Configuration,
832834
{
@@ -836,11 +838,11 @@ where
836838
// ensures that there is no danger of a race
837839
// when deleting a tracked struct.
838840
acquire_read_lock(&self.updated_at, current_revision);
839-
&self.memos
841+
&self.value.memos
840842
}
841843

842844
fn memos_mut(&mut self) -> &mut crate::table::memo::MemoTable {
843-
&mut self.memos
845+
&mut self.value.memos
844846
}
845847

846848
// FIXME: `&self` may alias here?
@@ -849,7 +851,7 @@ where
849851
// ensures that there is no danger of a race
850852
// when deleting a tracked struct.
851853
acquire_read_lock(&self.updated_at, current_revision);
852-
&self.syncs
854+
&self.value.syncs
853855
}
854856
}
855857

0 commit comments

Comments
 (0)