Skip to content

Commit f7b0856

Browse files
authored
Use generational identifiers for tracked structs (#864)
* use generational identifiers for tracked structs * increase ID generations to 32-bits * remove `created_at` field from tracked structs * clean up tracked struct IDs to handle overflow * log tracing message for leaked tracked structs
1 parent a12bf31 commit f7b0856

File tree

8 files changed

+294
-168
lines changed

8 files changed

+294
-168
lines changed

src/id.rs

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
use std::fmt::Debug;
22
use std::hash::Hash;
3-
use std::num::NonZeroU32;
3+
use std::num::NonZeroU64;
44

55
use crate::zalsa::Zalsa;
66

77
/// The `Id` of a salsa struct in the database [`Table`](`crate::table::Table`).
88
///
9-
/// The higher-order bits of an `Id` identify a [`Page`](`crate::table::Page`)
10-
/// and the low-order bits identify a slot within the page.
9+
/// The high-order bits of an `Id` store a 32-bit generation counter, while
10+
/// the low-order bits pack a [`PageIndex`](`crate::table::PageIndex`) and
11+
/// [`SlotIndex`](`crate::table::SlotIndex`) within the page.
1112
///
12-
/// An Id is a newtype'd u32 ranging from `0..Id::MAX_U32`.
13-
/// The maximum range is smaller than a standard u32 to leave
13+
/// The low-order bits of `Id` are a `u32` ranging from `0..Id::MAX_U32`.
14+
/// The maximum range is smaller than a standard `u32` to leave
1415
/// room for niches; currently there is only one niche, so that
1516
/// `Option<Id>` is the same size as an `Id`.
1617
///
1718
/// As an end-user of `Salsa` you will generally not use `Id` directly,
1819
/// it is wrapped in new types.
1920
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
2021
pub struct Id {
21-
value: NonZeroU32,
22+
value: NonZeroU64,
2223
}
2324

2425
impl Id {
2526
pub const MAX_U32: u32 = u32::MAX - 0xFF;
2627
pub const MAX_USIZE: usize = Self::MAX_U32 as usize;
2728

28-
/// Create a `salsa::Id` from a u32 value. This value should
29-
/// be less than [`Self::MAX_U32`].
29+
/// Create a `salsa::Id` from a u32 value, without a generation. This
30+
/// value should be less than [`Self::MAX_U32`].
3031
///
3132
/// In general, you should not need to create salsa ids yourself,
3233
/// but it can be useful if you are using the type as a general
@@ -38,23 +39,90 @@ impl Id {
3839
#[doc(hidden)]
3940
#[track_caller]
4041
#[inline]
41-
pub const unsafe fn from_u32(v: u32) -> Self {
42+
pub const unsafe fn from_index(v: u32) -> Self {
4243
debug_assert!(v < Self::MAX_U32);
44+
45+
Id {
46+
// SAFETY: Caller obligation.
47+
value: unsafe { NonZeroU64::new_unchecked((v + 1) as u64) },
48+
}
49+
}
50+
51+
/// Create a `salsa::Id` from a u64 value.
52+
///
53+
/// This should only be used to recreate an `Id` together with `Id::as_u64`.
54+
///
55+
/// # Safety
56+
///
57+
/// The data bits of the supplied value must represent a valid `Id` returned
58+
/// by `Id::as_u64`.
59+
#[doc(hidden)]
60+
#[track_caller]
61+
#[inline]
62+
pub const unsafe fn from_bits(v: u64) -> Self {
4363
Id {
44-
// SAFETY: Caller obligation
45-
value: unsafe { NonZeroU32::new_unchecked(v + 1) },
64+
// SAFETY: Caller obligation.
65+
value: unsafe { NonZeroU64::new_unchecked(v) },
4666
}
4767
}
4868

69+
/// Returns a new `Id` with same index, but the generation incremented by one.
70+
///
71+
/// Returns `None` if the generation would overflow, i.e. the current generation
72+
/// is `u32::MAX`.
4973
#[inline]
50-
pub const fn as_u32(self) -> u32 {
51-
self.value.get() - 1
74+
pub fn next_generation(self) -> Option<Id> {
75+
self.generation()
76+
.checked_add(1)
77+
.map(|generation| self.with_generation(generation))
78+
}
79+
80+
/// Mark the `Id` with a generation.
81+
///
82+
/// This `Id` will refer to the same page and slot in the database,
83+
/// but will differ from other identifiers of the slot based on the
84+
/// provided generation.
85+
#[inline]
86+
pub const fn with_generation(self, generation: u32) -> Id {
87+
let mut value = self.value.get();
88+
89+
value &= 0xFFFFFFFF;
90+
value |= (generation as u64) << 32;
91+
92+
Id {
93+
// SAFETY: The niche of `value` is in the lower bits, which we did not touch.
94+
value: unsafe { NonZeroU64::new_unchecked(value) },
95+
}
96+
}
97+
98+
/// Return the index portion of this `Id`.
99+
#[inline]
100+
pub const fn index(self) -> u32 {
101+
// Truncate the high-order bits.
102+
(self.value.get() as u32) - 1
103+
}
104+
105+
/// Return the generation of this `Id`.
106+
#[inline]
107+
pub const fn generation(self) -> u32 {
108+
// Shift away the low-order bits.
109+
(self.value.get() >> 32) as u32
110+
}
111+
112+
/// Return the internal `u64` representation of this `Id`.
113+
#[inline]
114+
pub const fn as_bits(self) -> u64 {
115+
self.value.get()
52116
}
53117
}
54118

55119
impl Debug for Id {
56120
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57-
write!(f, "Id({:x})", self.as_u32())
121+
if self.generation() == 0 {
122+
write!(f, "Id({:x})", self.index())
123+
} else {
124+
write!(f, "Id({:x}g{:x})", self.index(), self.generation())
125+
}
58126
}
59127
}
60128

src/input/singleton.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::loom::sync::atomic::{AtomicU32, Ordering};
1+
use crate::loom::sync::atomic::{AtomicU64, Ordering};
22
use crate::Id;
33

44
mod sealed {
@@ -11,7 +11,7 @@ pub trait SingletonChoice: sealed::Sealed + Default {
1111
}
1212

1313
pub struct Singleton {
14-
index: AtomicU32,
14+
index: AtomicU64,
1515
}
1616
impl sealed::Sealed for Singleton {}
1717
impl SingletonChoice for Singleton {
@@ -22,7 +22,7 @@ impl SingletonChoice for Singleton {
2222
let id = cb();
2323
if self
2424
.index
25-
.compare_exchange(0, id.as_u32() + 1, Ordering::AcqRel, Ordering::Acquire)
25+
.compare_exchange(0, id.as_bits(), Ordering::AcqRel, Ordering::Acquire)
2626
.is_err()
2727
{
2828
panic!("singleton struct may not be duplicated");
@@ -33,16 +33,17 @@ impl SingletonChoice for Singleton {
3333
fn index(&self) -> Option<Id> {
3434
match self.index.load(Ordering::Acquire) {
3535
0 => None,
36-
// SAFETY: Our u32 is derived from an ID and thus safe to convert back.
37-
id => Some(unsafe { Id::from_u32(id - 1) }),
36+
37+
// SAFETY: Our u64 is derived from an ID and thus safe to convert back.
38+
id => Some(unsafe { Id::from_bits(id) }),
3839
}
3940
}
4041
}
4142

4243
impl Default for Singleton {
4344
fn default() -> Self {
4445
Self {
45-
index: AtomicU32::new(0),
46+
index: AtomicU64::new(0),
4647
}
4748
}
4849
}

src/key.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,29 @@ use crate::{Database, Id};
1212
/// only for inserting into maps and the like.
1313
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
1414
pub struct DatabaseKeyIndex {
15+
key_index: u32,
16+
key_generation: u32,
1517
ingredient_index: IngredientIndex,
16-
key_index: Id,
1718
}
1819
// ANCHOR_END: DatabaseKeyIndex
1920

2021
impl DatabaseKeyIndex {
2122
#[inline]
2223
pub(crate) fn new(ingredient_index: IngredientIndex, key_index: Id) -> Self {
2324
Self {
24-
key_index,
25+
key_index: key_index.index(),
26+
key_generation: key_index.generation(),
2527
ingredient_index,
2628
}
2729
}
2830

29-
pub fn ingredient_index(self) -> IngredientIndex {
31+
pub const fn ingredient_index(self) -> IngredientIndex {
3032
self.ingredient_index
3133
}
3234

33-
pub fn key_index(self) -> Id {
34-
self.key_index
35+
pub const fn key_index(self) -> Id {
36+
// SAFETY: `self.key_index` was returned by `Id::data`.
37+
unsafe { Id::from_index(self.key_index) }.with_generation(self.key_generation)
3538
}
3639

3740
pub(crate) fn maybe_changed_after(
@@ -44,15 +47,15 @@ impl DatabaseKeyIndex {
4447
// SAFETY: The `db` belongs to the ingredient
4548
unsafe {
4649
zalsa
47-
.lookup_ingredient(self.ingredient_index)
48-
.maybe_changed_after(db, self.key_index, last_verified_at, cycle_heads)
50+
.lookup_ingredient(self.ingredient_index())
51+
.maybe_changed_after(db, self.key_index(), last_verified_at, cycle_heads)
4952
}
5053
}
5154

5255
pub(crate) fn remove_stale_output(&self, zalsa: &Zalsa, executor: DatabaseKeyIndex) {
5356
zalsa
54-
.lookup_ingredient(self.ingredient_index)
55-
.remove_stale_output(zalsa, executor, self.key_index)
57+
.lookup_ingredient(self.ingredient_index())
58+
.remove_stale_output(zalsa, executor, self.key_index())
5659
}
5760

5861
pub(crate) fn mark_validated_output(
@@ -61,21 +64,21 @@ impl DatabaseKeyIndex {
6164
database_key_index: DatabaseKeyIndex,
6265
) {
6366
zalsa
64-
.lookup_ingredient(self.ingredient_index)
65-
.mark_validated_output(zalsa, database_key_index, self.key_index)
67+
.lookup_ingredient(self.ingredient_index())
68+
.mark_validated_output(zalsa, database_key_index, self.key_index())
6669
}
6770
}
6871

6972
impl fmt::Debug for DatabaseKeyIndex {
7073
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7174
crate::attach::with_attached_database(|db| {
72-
let ingredient = db.zalsa().lookup_ingredient(self.ingredient_index);
73-
ingredient.fmt_index(self.key_index, f)
75+
let ingredient = db.zalsa().lookup_ingredient(self.ingredient_index());
76+
ingredient.fmt_index(self.key_index(), f)
7477
})
7578
.unwrap_or_else(|| {
7679
f.debug_tuple("DatabaseKeyIndex")
77-
.field(&self.ingredient_index)
78-
.field(&self.key_index)
80+
.field(&self.ingredient_index())
81+
.field(&self.key_index())
7982
.finish()
8083
})
8184
}

src/table.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -400,13 +400,13 @@ fn make_id(page: PageIndex, slot: SlotIndex) -> Id {
400400
let page = page.0 as u32;
401401
let slot = slot.0 as u32;
402402
// SAFETY: `slot` is guaranteed to be small enough that the resulting Id won't be bigger than `Id::MAX_U32`
403-
unsafe { Id::from_u32((page << PAGE_LEN_BITS) | slot) }
403+
unsafe { Id::from_index((page << PAGE_LEN_BITS) | slot) }
404404
}
405405

406406
#[inline]
407407
fn split_id(id: Id) -> (PageIndex, SlotIndex) {
408-
let id = id.as_u32() as usize;
409-
let slot = id & PAGE_LEN_MASK;
410-
let page = id >> PAGE_LEN_BITS;
408+
let index = id.index() as usize;
409+
let slot = index & PAGE_LEN_MASK;
410+
let page = index >> PAGE_LEN_BITS;
411411
(PageIndex::new(page), SlotIndex::new(slot))
412412
}

0 commit comments

Comments
 (0)