Skip to content
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: 2 additions & 0 deletions components/salsa-macro-rules/src/setup_input_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ macro_rules! setup_input_struct {
}

impl $zalsa::SalsaStructInDb for $Struct {
type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex;

fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices {
aux.add_or_lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into()
}
Expand Down
2 changes: 2 additions & 0 deletions components/salsa-macro-rules/src/setup_interned_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ macro_rules! setup_interned_struct {
}

impl< $($db_lt_arg)? > $zalsa::SalsaStructInDb for $Struct< $($db_lt_arg)? > {
type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex;

fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices {
aux.add_or_lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into()
}
Expand Down
12 changes: 7 additions & 5 deletions components/salsa-macro-rules/src/setup_tracked_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ macro_rules! setup_tracked_fn {
$zalsa::IngredientCache::new();

impl $zalsa::SalsaStructInDb for $InternedData<'_> {
type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex;

fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices {
$zalsa::IngredientIndices::uninitialized()
$zalsa::IngredientIndices::empty()
}

#[inline]
Expand Down Expand Up @@ -212,7 +214,7 @@ macro_rules! setup_tracked_fn {
{
$zalsa::macro_if! {
if $needs_interner {
$zalsa::IngredientIndices::uninitialized()
$zalsa::IngredientIndices::empty()
} else {
<$InternedData as $zalsa::SalsaStructInDb>::lookup_or_create_ingredient_index(zalsa)
}
Expand All @@ -224,18 +226,18 @@ macro_rules! setup_tracked_fn {
first_index: $zalsa::IngredientIndex,
struct_index: $zalsa::IngredientIndices,
) -> Vec<Box<dyn $zalsa::Ingredient>> {
let struct_index = $zalsa::macro_if! {
let struct_index: $zalsa::IngredientIndices = $zalsa::macro_if! {
if $needs_interner {
first_index.successor(0).into()
} else {
struct_index
}
};
let memo_ingredient_indices = From::from((zalsa, struct_index, first_index));

let fn_ingredient = <$zalsa::function::IngredientImpl<$Configuration>>::new(
struct_index,
first_index,
zalsa,
memo_ingredient_indices,
);
fn_ingredient.set_capacity($lru);
$zalsa::macro_if! {
Expand Down
2 changes: 2 additions & 0 deletions components/salsa-macro-rules/src/setup_tracked_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ macro_rules! setup_tracked_struct {
}

impl $zalsa::SalsaStructInDb for $Struct<'_> {
type MemoIngredientMap = $zalsa::MemoIngredientSingletonIndex;

fn lookup_or_create_ingredient_index(aux: &$zalsa::Zalsa) -> $zalsa::IngredientIndices {
aux.add_or_lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>().into()
}
Expand Down
10 changes: 3 additions & 7 deletions components/salsa-macros/src/supertype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,11 @@ fn enum_impl(enum_item: syn::ItemEnum) -> syn::Result<TokenStream> {
let salsa_struct_in_db = quote! {
impl #impl_generics zalsa::SalsaStructInDb for #enum_name #type_generics
#where_clause {
type MemoIngredientMap = zalsa::MemoIngredientIndices;

#[inline]
fn lookup_or_create_ingredient_index(__zalsa: &zalsa::Zalsa) -> zalsa::IngredientIndices {
let mut __result = zalsa::IngredientIndices::uninitialized();
#(
__result.merge(
&<#variant_types as zalsa::SalsaStructInDb>::lookup_or_create_ingredient_index(__zalsa)
);
)*
__result
zalsa::IngredientIndices::merge([ #( <#variant_types as zalsa::SalsaStructInDb>::lookup_or_create_ingredient_index(__zalsa) ),* ])
}

#[inline]
Expand Down
16 changes: 9 additions & 7 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::{
cycle::CycleRecoveryStrategy,
ingredient::{fmt_index, MaybeChangedAfter},
key::DatabaseKeyIndex,
memo_ingredient_indices::{IngredientIndices, MemoIngredientIndices},
salsa_struct::SalsaStructInDb,
zalsa::{IngredientIndex, MemoIngredientIndex, Zalsa},
zalsa_local::QueryOrigin,
Expand Down Expand Up @@ -95,7 +94,10 @@ pub struct IngredientImpl<C: Configuration> {
index: IngredientIndex,

/// The index for the memo/sync tables
memo_ingredient_indices: MemoIngredientIndices,
///
/// This may be a `MemoIngredientSingletonIndex` or a `MemoIngredientIndex`, depending on
/// whether the tracked function's struct is a plain salsa struct or an enum `#[derive(Supertype)]`.
memo_ingredient_indices: <C::SalsaStruct<'static> as SalsaStructInDb>::MemoIngredientMap,

/// Used to find memos to throw out when we have too many memoized values.
lru: lru::Lru,
Expand Down Expand Up @@ -126,9 +128,10 @@ impl<C> IngredientImpl<C>
where
C: Configuration,
{
pub fn new(struct_indices: IngredientIndices, index: IngredientIndex, zalsa: &Zalsa) -> Self {
let memo_ingredient_indices = struct_indices
.memo_indices(|struct_index| zalsa.next_memo_ingredient_index(struct_index, index));
pub fn new(
index: IngredientIndex,
memo_ingredient_indices: <C::SalsaStruct<'static> as SalsaStructInDb>::MemoIngredientMap,
) -> Self {
Self {
index,
memo_ingredient_indices,
Expand Down Expand Up @@ -187,8 +190,7 @@ where

#[inline]
fn memo_ingredient_index(&self, zalsa: &Zalsa, id: Id) -> MemoIngredientIndex {
self.memo_ingredient_indices
.find(zalsa.ingredient_index(id))
self.memo_ingredient_indices[zalsa.ingredient_index(id)]
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/ingredient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub trait Jar: Any {
where
Self: Sized,
{
IngredientIndices::uninitialized()
IngredientIndices::empty()
}

/// Create the ingredients given the index of the first one.
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ pub use salsa_macros::accumulator;
pub use salsa_macros::db;
pub use salsa_macros::input;
pub use salsa_macros::interned;
pub use salsa_macros::tracked;
pub use salsa_macros::supertype;
pub use salsa_macros::tracked;
pub use salsa_macros::Update;

pub mod prelude {
Expand Down Expand Up @@ -87,7 +87,9 @@ pub mod plumbing {
pub use crate::ingredient::Ingredient;
pub use crate::ingredient::Jar;
pub use crate::key::DatabaseKeyIndex;
pub use crate::memo_ingredient_indices::IngredientIndices;
pub use crate::memo_ingredient_indices::{
IngredientIndices, MemoIngredientIndices, MemoIngredientSingletonIndex,
};
pub use crate::revision::Revision;
pub use crate::runtime::stamp;
pub use crate::runtime::Runtime;
Expand Down
152 changes: 80 additions & 72 deletions src/memo_ingredient_indices.rs
Original file line number Diff line number Diff line change
@@ -1,114 +1,122 @@
use std::fmt;
use std::ops;

use crate::zalsa::MemoIngredientIndex;
use crate::zalsa::{MemoIngredientIndex, Zalsa};
use crate::IngredientIndex;

/// The maximum number of memo ingredient indices we can hold. This affects the
/// maximum number of variants possible in `#[derive(salsa::Enum)]`. We use a const
/// so that we don't allocate and to perhaps allow the compiler to vectorize the search.
pub const MAX_MEMO_INGREDIENT_INDICES: usize = 20;

/// An ingredient has an [ingredient index][IngredientIndex]. However, Salsa also supports
/// enums of salsa structs, and those don't have a constant ingredient index, because they
/// are not ingredients by themselves but rather composed of them. However, an enum can be
/// viewed as a *set* of [`IngredientIndex`], where each instance of the enum can belong
/// enums of salsa structs (and other salsa enums), and those don't have a constant ingredient index,
/// because they are not ingredients by themselves but rather composed of them. However, an enum can
/// be viewed as a *set* of [`IngredientIndex`], where each instance of the enum can belong
/// to one, potentially different, index. This is what this type represents: a set of
/// `IngredientIndex`.
///
/// This type is represented as an array, for efficiency, and supports up to 20 indices.
/// That means that Salsa enums can have at most 20 variants. Alternatively, they can also
/// contain Salsa enums as variants, but then the total number of variants is counter - because
/// what matters is the number of unique `IngredientIndex`s.
#[derive(Clone)]
pub struct IngredientIndices {
indices: [IngredientIndex; MAX_MEMO_INGREDIENT_INDICES],
len: u8,
indices: Box<[IngredientIndex]>,
}

impl From<IngredientIndex> for IngredientIndices {
#[inline]
fn from(value: IngredientIndex) -> Self {
let mut result = Self::uninitialized();
result.indices[0] = value;
result.len = 1;
result
}
}

impl fmt::Debug for IngredientIndices {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(&self.indices[..self.len.into()])
.finish()
Self {
indices: Box::new([value]),
}
}
}

impl IngredientIndices {
#[inline]
pub(crate) fn memo_indices(
&self,
mut memo_index: impl FnMut(IngredientIndex) -> MemoIngredientIndex,
) -> MemoIngredientIndices {
let mut memo_ingredient_indices = [(
IngredientIndex::from((u32::MAX - 1) as usize),
MemoIngredientIndex::from_usize((u32::MAX - 1) as usize),
); MAX_MEMO_INGREDIENT_INDICES];
for i in 0..usize::from(self.len) {
let memo_ingredient_index = memo_index(self.indices[i]);
memo_ingredient_indices[i] = (self.indices[i], memo_ingredient_index);
}
MemoIngredientIndices {
indices: memo_ingredient_indices,
len: self.len,
pub fn empty() -> Self {
Self {
indices: Box::default(),
}
}

#[inline]
pub fn uninitialized() -> Self {
pub fn merge(iter: impl IntoIterator<Item = Self>) -> Self {
let mut indices = Vec::new();
for index in iter {
indices.extend(index.indices);
}
indices.sort_unstable();
indices.dedup();
Self {
indices: [IngredientIndex::from((u32::MAX - 1) as usize); MAX_MEMO_INGREDIENT_INDICES],
len: 0,
indices: indices.into_boxed_slice(),
}
}
}

#[track_caller]
impl From<(&Zalsa, IngredientIndices, IngredientIndex)> for MemoIngredientIndices {
#[inline]
pub fn merge(&mut self, other: &Self) {
if usize::from(self.len) + usize::from(other.len) > MAX_MEMO_INGREDIENT_INDICES {
panic!("too many variants in the salsa enum");
fn from(
(zalsa, struct_indices, ingredient): (&Zalsa, IngredientIndices, IngredientIndex),
) -> Self {
let Some(&last) = struct_indices.indices.last() else {
unreachable!("Attempting to construct struct memo mapping for non tracked function?")
};
let mut indices = Vec::new();
indices.resize(
last.as_usize() + 1,
MemoIngredientIndex::from_usize((u32::MAX - 1) as usize),
);
for &struct_ingredient in &struct_indices.indices {
indices[struct_ingredient.as_usize()] =
zalsa.next_memo_ingredient_index(struct_ingredient, ingredient);
}
MemoIngredientIndices {
indices: indices.into_boxed_slice(),
}
self.indices[usize::from(self.len)..][..usize::from(other.len)]
.copy_from_slice(&other.indices[..usize::from(other.len)]);
self.len += other.len;
}
}

/// This type is to [`MemoIngredientIndex`] what [`IngredientIndices`] is to [`IngredientIndex`]:
/// since enums can contain different ingredient indices, they can also have different memo indices,
/// so we need to keep track of them.
#[derive(Clone)]
///
/// This acts a map from [`IngredientIndex`] to [`MemoIngredientIndex`] but implemented
/// via a slice for fast lookups, trading memory for speed. With these changes, lookups are `O(1)`
/// instead of `O(n)`.
///
/// A database tends to have few ingredients (i), less function ingredients and even less
/// function ingredients targeting `#[derive(Supertype)]` enums (e).
/// While this is bounded as `O(i * e)` memory usage, the average case is significantly smaller: a
/// function ingredient targeting enums only stores a slice whose length corresponds to the largest
/// ingredient index's _value_. For example, if we have the ingredient indices `[2, 6, 17]`, then we
/// will allocate a slice whose length is `17 + 1`.
///
/// Assuming a heavy example scenario of 1000 ingredients (500 of which are function ingredients, 100
/// of which are enum targeting functions) this would come out to a maximum possibly memory usage of
/// 4bytes * 1000 * 100 ~= 0.38MB which is negligible.
pub struct MemoIngredientIndices {
indices: [(IngredientIndex, MemoIngredientIndex); MAX_MEMO_INGREDIENT_INDICES],
len: u8,
indices: Box<[MemoIngredientIndex]>,
}

impl fmt::Debug for MemoIngredientIndices {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(&self.indices[..self.len.into()])
.finish()
impl ops::Index<IngredientIndex> for MemoIngredientIndices {
type Output = MemoIngredientIndex;

#[inline]
fn index(&self, index: IngredientIndex) -> &Self::Output {
&self.indices[index.as_usize()]
}
}

impl MemoIngredientIndices {
#[derive(Debug)]
pub struct MemoIngredientSingletonIndex(MemoIngredientIndex);

impl ops::Index<IngredientIndex> for MemoIngredientSingletonIndex {
type Output = MemoIngredientIndex;

#[inline]
pub(crate) fn find(&self, ingredient_index: IngredientIndex) -> MemoIngredientIndex {
for &(ingredient, memo_ingredient_index) in &self.indices[..(self.len - 1).into()] {
if ingredient == ingredient_index {
return memo_ingredient_index;
}
}
// It must be the last.
self.indices[usize::from(self.len - 1)].1
fn index(&self, _: IngredientIndex) -> &Self::Output {
&self.0
}
}

impl From<(&Zalsa, IngredientIndices, IngredientIndex)> for MemoIngredientSingletonIndex {
#[inline]
fn from((zalsa, indices, ingredient): (&Zalsa, IngredientIndices, IngredientIndex)) -> Self {
let &[struct_ingredient] = &*indices.indices else {
unreachable!("Attempting to construct struct memo mapping from enum?")
};

Self(zalsa.next_memo_ingredient_index(struct_ingredient, ingredient))
}
}
4 changes: 4 additions & 0 deletions src/salsa_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use crate::zalsa::Zalsa;
use crate::Id;

pub trait SalsaStructInDb: Sized {
type MemoIngredientMap: std::ops::Index<crate::IngredientIndex, Output = crate::zalsa::MemoIngredientIndex>
+ Send
+ Sync;

/// This method is used to create ingredient indices. Note, it does *not* create the ingredients
/// themselves, that is the job of [`Zalsa::add_or_lookup_jar_by_type()`]. This method only creates
/// or lookup the indices. Naturally, implementors may call `add_or_lookup_jar_by_type()` to
Expand Down
2 changes: 2 additions & 0 deletions tests/interned-structs_self_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ const _: () = {
}
}
impl zalsa_::SalsaStructInDb for InternedString<'_> {
type MemoIngredientMap = zalsa_::MemoIngredientSingletonIndex;

fn lookup_or_create_ingredient_index(aux: &Zalsa) -> salsa::plumbing::IngredientIndices {
aux.add_or_lookup_jar_by_type::<zalsa_struct_::JarImpl<Configuration_>>()
.into()
Expand Down