Skip to content

Commit 57ec532

Browse files
committed
use generational identifiers for tracked structs
1 parent 247a9df commit 57ec532

File tree

8 files changed

+192
-76
lines changed

8 files changed

+192
-76
lines changed

src/id.rs

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
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 16-bit generation counter
10+
/// as well as an optional 16-bit ingredient index.
1111
///
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
12+
/// The low-order bits pack a [`PageIndex`](`crate::table::PageIndex`) and
13+
/// [`SlotIndex`](`crate::table::SlotIndex`) within the page.
14+
///
15+
/// The low-order bits of `Id` are a `u32` ranging from `0..Id::MAX_U32`.
16+
/// The maximum range is smaller than a standard `u32` to leave
1417
/// room for niches; currently there is only one niche, so that
1518
/// `Option<Id>` is the same size as an `Id`.
1619
///
1720
/// As an end-user of `Salsa` you will generally not use `Id` directly,
1821
/// it is wrapped in new types.
1922
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
2023
pub struct Id {
21-
value: NonZeroU32,
24+
value: NonZeroU64,
2225
}
2326

2427
impl Id {
2528
pub const MAX_U32: u32 = u32::MAX - 0xFF;
2629
pub const MAX_USIZE: usize = Self::MAX_U32 as usize;
2730

28-
/// Create a `salsa::Id` from a u32 value. This value should
29-
/// be less than [`Self::MAX_U32`].
31+
const INGREDIENT_MASK: u64 = 0x0000FFFFFFFFFFFF;
32+
const GENERATION_MASK: u64 = 0xFFFF0000FFFFFFFF;
33+
34+
/// Create a `salsa::Id` from a u32 value, without a generation. This
35+
/// value should be less than [`Self::MAX_U32`].
3036
///
3137
/// In general, you should not need to create salsa ids yourself,
3238
/// but it can be useful if you are using the type as a general
@@ -38,23 +44,98 @@ impl Id {
3844
#[doc(hidden)]
3945
#[track_caller]
4046
#[inline]
41-
pub const unsafe fn from_u32(v: u32) -> Self {
47+
pub const unsafe fn from_data(v: u32) -> Self {
4248
debug_assert!(v < Self::MAX_U32);
49+
50+
Id {
51+
// SAFETY: Caller obligation.
52+
value: unsafe { NonZeroU64::new_unchecked((v + 1) as u64) },
53+
}
54+
}
55+
56+
/// Create a `salsa::Id` from a u64 value.
57+
///
58+
/// This should only be used to recreate an `Id` together with `Id::as_u64`.
59+
///
60+
/// # Safety
61+
///
62+
/// The data bits of the supplied value must represent a valid `Id` returned
63+
/// by `Id::as_u64`.
64+
#[doc(hidden)]
65+
#[track_caller]
66+
#[inline]
67+
pub const unsafe fn from_bits(v: u64) -> Self {
4368
Id {
44-
// SAFETY: Caller obligation
45-
value: unsafe { NonZeroU32::new_unchecked(v + 1) },
69+
// SAFETY: Caller obligation.
70+
value: unsafe { NonZeroU64::new_unchecked(v) },
4671
}
4772
}
4873

74+
/// Mark the `Id` with a generation.
75+
///
76+
/// This `Id` will refer to the same page and slot in the database,
77+
/// but will differ from other identifiers of the slot based on the
78+
/// provided generation.
4979
#[inline]
50-
pub const fn as_u32(self) -> u32 {
51-
self.value.get() - 1
80+
pub fn with_generation(self, generation: u16) -> Id {
81+
let mut value = self.value.get();
82+
83+
value &= Id::GENERATION_MASK;
84+
value |= (generation as u64) << 32;
85+
86+
Id {
87+
// SAFETY: The niche of `value` is in the lower bits, which we did not touch.
88+
value: unsafe { NonZeroU64::new_unchecked(value) },
89+
}
90+
}
91+
92+
/// Mark the `Id` with an ingredient index.
93+
#[inline]
94+
pub fn with_ingredient_index(self, ingredient: u16) -> Id {
95+
let mut value = self.value.get();
96+
97+
value &= Id::INGREDIENT_MASK;
98+
value |= (ingredient as u64) << 48;
99+
100+
Id {
101+
// SAFETY: The niche of `value` is in the lower bits, which we did not touch.
102+
value: unsafe { NonZeroU64::new_unchecked(value) },
103+
}
104+
}
105+
106+
/// Return the internal `u64` representation of this `Id`.
107+
#[inline]
108+
pub const fn as_bits(self) -> u64 {
109+
self.value.get()
110+
}
111+
112+
/// Return the data portion of this `Id`.
113+
#[inline]
114+
pub const fn data(self) -> u32 {
115+
// Truncate the high-order bits.
116+
(self.value.get() as u32) - 1
117+
}
118+
119+
/// Return the generation of this `Id`.
120+
#[inline]
121+
pub const fn generation(self) -> u16 {
122+
((self.value.get() & !Id::GENERATION_MASK) >> 32) as u16
123+
}
124+
125+
/// Return the ingredient index of this `Id`.
126+
#[inline]
127+
pub const fn ingredient_index(self) -> u16 {
128+
(self.value.get() >> 48) as u16
52129
}
53130
}
54131

55132
impl Debug for Id {
56133
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57-
write!(f, "Id({:x})", self.as_u32())
134+
if self.generation() == 0 {
135+
write!(f, "Id({:x})", self.data())
136+
} else {
137+
write!(f, "Id({:x}g{:x})", self.data(), self.generation())
138+
}
58139
}
59140
}
60141

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: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@ 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-
ingredient_index: IngredientIndex,
16-
key_index: Id,
15+
packed_key_index: Id,
1716
}
1817
// ANCHOR_END: DatabaseKeyIndex
1918

2019
impl DatabaseKeyIndex {
2120
#[inline]
2221
pub(crate) fn new(ingredient_index: IngredientIndex, key_index: Id) -> Self {
2322
Self {
24-
key_index,
25-
ingredient_index,
23+
// Pack the ingredient index with the key.
24+
packed_key_index: key_index.with_ingredient_index(ingredient_index.as_u16()),
2625
}
2726
}
2827

2928
pub fn ingredient_index(self) -> IngredientIndex {
30-
self.ingredient_index
29+
IngredientIndex::from(self.packed_key_index.ingredient_index() as usize)
3130
}
3231

3332
pub fn key_index(self) -> Id {
34-
self.key_index
33+
// Clear the ingredient index from the key as it not part of the dependency.
34+
self.packed_key_index.with_ingredient_index(0)
3535
}
3636

3737
pub(crate) fn maybe_changed_after(
@@ -44,15 +44,15 @@ impl DatabaseKeyIndex {
4444
// SAFETY: The `db` belongs to the ingredient
4545
unsafe {
4646
zalsa
47-
.lookup_ingredient(self.ingredient_index)
48-
.maybe_changed_after(db, self.key_index, last_verified_at, cycle_heads)
47+
.lookup_ingredient(self.ingredient_index())
48+
.maybe_changed_after(db, self.key_index(), last_verified_at, cycle_heads)
4949
}
5050
}
5151

5252
pub(crate) fn remove_stale_output(&self, zalsa: &Zalsa, executor: DatabaseKeyIndex) {
5353
zalsa
54-
.lookup_ingredient(self.ingredient_index)
55-
.remove_stale_output(zalsa, executor, self.key_index)
54+
.lookup_ingredient(self.ingredient_index())
55+
.remove_stale_output(zalsa, executor, self.key_index())
5656
}
5757

5858
pub(crate) fn mark_validated_output(
@@ -61,21 +61,21 @@ impl DatabaseKeyIndex {
6161
database_key_index: DatabaseKeyIndex,
6262
) {
6363
zalsa
64-
.lookup_ingredient(self.ingredient_index)
65-
.mark_validated_output(zalsa, database_key_index, self.key_index)
64+
.lookup_ingredient(self.ingredient_index())
65+
.mark_validated_output(zalsa, database_key_index, self.key_index())
6666
}
6767
}
6868

6969
impl fmt::Debug for DatabaseKeyIndex {
7070
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7171
crate::attach::with_attached_database(|db| {
72-
let ingredient = db.zalsa().lookup_ingredient(self.ingredient_index);
73-
ingredient.fmt_index(self.key_index, f)
72+
let ingredient = db.zalsa().lookup_ingredient(self.ingredient_index());
73+
ingredient.fmt_index(self.key_index(), f)
7474
})
7575
.unwrap_or_else(|| {
7676
f.debug_tuple("DatabaseKeyIndex")
77-
.field(&self.ingredient_index)
78-
.field(&self.key_index)
77+
.field(&self.ingredient_index())
78+
.field(&self.key_index())
7979
.finish()
8080
})
8181
}

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_data((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 data = id.data() as usize;
409+
let slot = data & PAGE_LEN_MASK;
410+
let page = data >> PAGE_LEN_BITS;
411411
(PageIndex::new(page), SlotIndex::new(slot))
412412
}

0 commit comments

Comments
 (0)