Skip to content

Commit

Permalink
Merge pull request #640 from davidbarsky/davidbarsky/add-db-debug-int…
Browse files Browse the repository at this point in the history
…rospection

feature: Add support for dumping database contents
  • Loading branch information
nikomatsakis authored Jan 31, 2025
2 parents 1e094dc + c818a1e commit 88dc597
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 25 deletions.
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ salsa-macros = { path = "components/salsa-macros" }
smallvec = "1"
rayon = "1.10.0"

[features]
# FIXME: remove this as a default feature before 1.0.
default = ["salsa_unstable"]
salsa_unstable = []

[dev-dependencies]
annotate-snippets = "0.11.5"
derive-new = "0.6.0"
Expand All @@ -33,7 +38,7 @@ eyre = "0.6.8"
notify-debouncer-mini = "0.4.1"
ordered-float = "4.2.1"
rustversion = "1.0"
test-log = { version ="0.2.11", features = ["trace"] }
test-log = { version = "0.2.11", features = ["trace"] }
trybuild = "1.0"

[[bench]]
Expand Down
2 changes: 1 addition & 1 deletion components/salsa-macro-rules/src/setup_input_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ macro_rules! setup_input_struct {
use salsa::plumbing as $zalsa;
use $zalsa::input as $zalsa_struct;

struct $Configuration;
type $Configuration = $Struct;

impl $zalsa_struct::Configuration for $Configuration {
const DEBUG_NAME: &'static str = stringify!($Struct);
Expand Down
2 changes: 1 addition & 1 deletion components/salsa-macro-rules/src/setup_interned_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ macro_rules! setup_interned_struct {

impl salsa::plumbing::interned::Configuration for $StructWithStatic {
const DEBUG_NAME: &'static str = stringify!($Struct);
type Data<'a> = $StructDataIdent<'a>;
type Fields<'a> = $StructDataIdent<'a>;
type Struct<'db> = $Struct< $($db_lt_arg)? >;
fn struct_from_id<'db>(id: salsa::Id) -> Self::Struct<'db> {
use salsa::plumbing::FromId;
Expand Down
2 changes: 1 addition & 1 deletion components/salsa-macro-rules/src/setup_tracked_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ macro_rules! setup_tracked_fn {
impl $zalsa::interned::Configuration for $Configuration {
const DEBUG_NAME: &'static str = "Configuration";

type Data<$db_lt> = ($($input_ty),*);
type Fields<$db_lt> = ($($input_ty),*);

type Struct<$db_lt> = $InternedData<$db_lt>;

Expand Down
4 changes: 4 additions & 0 deletions src/database_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ impl DatabaseImpl {
pub fn new() -> Self {
Self::default()
}

pub fn storage(&self) -> &Storage<Self> {
&self.storage
}
}

#[salsa::db]
Expand Down
20 changes: 18 additions & 2 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,10 @@ pub struct Value<C>
where
C: Configuration,
{
/// Fields of this input struct. They can change across revisions,
/// but they do not change within a particular revision.
/// Fields of this input struct.
///
/// They can change across revisions, but they do not change within
/// a particular revision.
fields: C::Fields,

/// The revision and durability information for each field: when did this field last change.
Expand All @@ -286,6 +288,20 @@ where
syncs: SyncTable,
}

impl<C> Value<C>
where
C: Configuration,
{
/// Fields of this tracked struct.
///
/// They can change across revisions, but they do not change within
/// a particular revision.
#[cfg(feature = "salsa_unstable")]
pub fn fields(&self) -> &C::Fields {
&self.fields
}
}

pub trait HasBuilder {
type Builder;
}
Expand Down
48 changes: 31 additions & 17 deletions src/interned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use super::Revision;
pub trait Configuration: Sized + 'static {
const DEBUG_NAME: &'static str;

/// The type of data being interned
type Data<'db>: InternedData;
/// The fields of the struct being interned.
type Fields<'db>: InternedData;

/// The end user struct
type Struct<'db>: Copy;
Expand Down Expand Up @@ -60,7 +60,7 @@ pub struct IngredientImpl<C: Configuration> {
/// Maps from data to the existing interned id for that data.
///
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
key_map: FxDashMap<C::Data<'static>, Id>,
key_map: FxDashMap<C::Fields<'static>, Id>,

/// Stores the revision when this interned ingredient was last cleared.
/// You can clear an interned table at any point, deleting all its entries,
Expand All @@ -74,11 +74,22 @@ pub struct Value<C>
where
C: Configuration,
{
data: C::Data<'static>,
fields: C::Fields<'static>,
memos: MemoTable,
syncs: SyncTable,
}

impl<C> Value<C>
where
C: Configuration,
{
/// Fields of this interned struct.
#[cfg(feature = "salsa_unstable")]
pub fn fields(&self) -> &C::Fields<'static> {
&self.fields
}
}

impl<C: Configuration> Default for JarImpl<C> {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -113,11 +124,11 @@ where
}
}

unsafe fn to_internal_data<'db>(&'db self, data: C::Data<'db>) -> C::Data<'static> {
unsafe fn to_internal_data<'db>(&'db self, data: C::Fields<'db>) -> C::Fields<'static> {
unsafe { std::mem::transmute(data) }
}

unsafe fn from_internal_data<'db>(data: &'db C::Data<'static>) -> &'db C::Data<'db> {
unsafe fn from_internal_data<'db>(data: &'db C::Fields<'static>) -> &'db C::Fields<'db> {
unsafe { std::mem::transmute(data) }
}

Expand All @@ -134,11 +145,11 @@ where
&'db self,
db: &'db dyn crate::Database,
key: Key,
assemble: impl FnOnce(Id, Key) -> C::Data<'db>,
assemble: impl FnOnce(Id, Key) -> C::Fields<'db>,
) -> C::Struct<'db>
where
Key: Hash,
C::Data<'db>: HashEqLike<Key>,
C::Fields<'db>: HashEqLike<Key>,
{
C::struct_from_id(self.intern_id(db, key, assemble))
}
Expand All @@ -156,15 +167,15 @@ where
&'db self,
db: &'db dyn crate::Database,
key: Key,
assemble: impl FnOnce(Id, Key) -> C::Data<'db>,
assemble: impl FnOnce(Id, Key) -> C::Fields<'db>,
) -> crate::Id
where
Key: Hash,
// We'd want the following predicate, but this currently implies `'static` due to a rustc
// bug
// for<'db> C::Data<'db>: HashEqLike<Key>,
// so instead we go with this and transmute the lifetime in the `eq` closure
C::Data<'db>: HashEqLike<Key>,
C::Fields<'db>: HashEqLike<Key>,
{
let zalsa_local = db.zalsa_local();
zalsa_local.report_tracked_read(
Expand All @@ -180,7 +191,7 @@ where
let eq = |(data, _): &_| {
// SAFETY: it's safe to go from Data<'static> to Data<'db>
// shrink lifetime here to use a single lifetime in Lookup::eq(&StructKey<'db>, &C::Data<'db>)
let data: &C::Data<'db> = unsafe { std::mem::transmute(data) };
let data: &C::Fields<'db> = unsafe { std::mem::transmute(data) };
HashEqLike::eq(data, &key)
};

Expand All @@ -203,22 +214,25 @@ where
let zalsa = db.zalsa();
let table = zalsa.table();
let id = zalsa_local.allocate(table, self.ingredient_index, |id| Value::<C> {
data: unsafe { self.to_internal_data(assemble(id, key)) },
fields: unsafe { self.to_internal_data(assemble(id, key)) },
memos: Default::default(),
syncs: Default::default(),
});
unsafe {
lock.insert_in_slot(
data_hash,
slot,
(table.get::<Value<C>>(id).data.clone(), SharedValue::new(id)),
(
table.get::<Value<C>>(id).fields.clone(),
SharedValue::new(id),
),
)
};
debug_assert_eq!(
data_hash,
self.key_map
.hasher()
.hash_one(table.get::<Value<C>>(id).data.clone())
.hash_one(table.get::<Value<C>>(id).fields.clone())
);
id
}
Expand All @@ -228,14 +242,14 @@ where
/// Lookup the data for an interned value based on its id.
/// Rarely used since end-users generally carry a struct with a pointer directly
/// to the interned item.
pub fn data<'db>(&'db self, db: &'db dyn Database, id: Id) -> &'db C::Data<'db> {
pub fn data<'db>(&'db self, db: &'db dyn Database, id: Id) -> &'db C::Fields<'db> {
let internal_data = db.zalsa().table().get::<Value<C>>(id);
unsafe { Self::from_internal_data(&internal_data.data) }
unsafe { Self::from_internal_data(&internal_data.fields) }
}

/// Lookup the fields from an interned struct.
/// Note that this is not "leaking" since no dependency edge is required.
pub fn fields<'db>(&'db self, db: &'db dyn Database, s: C::Struct<'db>) -> &'db C::Data<'db> {
pub fn fields<'db>(&'db self, db: &'db dyn Database, s: C::Struct<'db>) -> &'db C::Fields<'db> {
self.data(db, C::deref_struct(s))
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub mod plumbing {
pub use crate::input::HasBuilder;
pub use crate::input::IngredientImpl;
pub use crate::input::JarImpl;
pub use crate::input::Value;
}

pub mod interned {
Expand Down
47 changes: 47 additions & 0 deletions src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{marker::PhantomData, panic::RefUnwindSafe, sync::Arc};
use parking_lot::{Condvar, Mutex};

use crate::{
plumbing::{input, interned, tracked_struct},
zalsa::{Zalsa, ZalsaDatabase},
zalsa_local::{self, ZalsaLocal},
Database, Event, EventKind,
Expand Down Expand Up @@ -60,6 +61,52 @@ impl<Db: Database> Default for Storage<Db> {
}

impl<Db: Database> Storage<Db> {
pub fn debug_input_entries<T>(&self) -> impl Iterator<Item = &input::Value<T>>
where
T: input::Configuration,
{
let zalsa = self.zalsa_impl();
zalsa
.table()
.pages
.iter()
.filter_map(|page| page.cast_type::<crate::table::Page<input::Value<T>>>())
.flat_map(|page| page.slots())
}

pub fn debug_interned_entries<T>(&self) -> impl Iterator<Item = &interned::Value<T>>
where
T: interned::Configuration,
{
let zalsa = self.zalsa_impl();
zalsa
.table()
.pages
.iter()
.filter_map(|page| page.cast_type::<crate::table::Page<interned::Value<T>>>())
.flat_map(|page| page.slots())
}

pub fn debug_tracked_entries<T>(&self) -> impl Iterator<Item = &tracked_struct::Value<T>>
where
T: tracked_struct::Configuration,
{
let zalsa = self.zalsa_impl();
zalsa
.table()
.pages
.iter()
.filter_map(|page| page.cast_type::<crate::table::Page<tracked_struct::Value<T>>>())
.flat_map(|pages| pages.slots())
}

/// Access the `Arc<Zalsa>`. This should always be
/// possible as `zalsa_impl` only becomes
/// `None` once we are in the `Drop` impl.
fn zalsa_impl(&self) -> &Arc<Zalsa> {
&self.zalsa_impl
}

// ANCHOR: cancel_other_workers
/// Sets cancellation flag and blocks until all other workers with access
/// to this storage have completed.
Expand Down
26 changes: 25 additions & 1 deletion src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const PAGE_LEN: usize = 1 << PAGE_LEN_BITS;
const MAX_PAGES: usize = 1 << (32 - PAGE_LEN_BITS);

pub(crate) struct Table {
pages: AppendOnlyVec<Box<dyn TablePage>>,
pub(crate) pages: AppendOnlyVec<Box<dyn TablePage>>,
}

pub(crate) trait TablePage: Any + Send + Sync {
Expand Down Expand Up @@ -211,6 +211,22 @@ impl<T: Slot> Page<T> {
unsafe { (*self.data[slot.0].get()).assume_init_ref() }
}

pub(crate) fn slots(&self) -> impl Iterator<Item = &T> {
let len = self.allocated.load(std::sync::atomic::Ordering::Acquire);
let mut idx = 0;

std::iter::from_fn(move || {
let ret = if idx < len {
let value = self.get(crate::table::SlotIndex::new(idx));
Some(value)
} else {
None
};
idx += 1;
ret
})
}

/// Returns a raw pointer to the given slot.
///
/// # Panics
Expand Down Expand Up @@ -289,6 +305,14 @@ impl dyn TablePage {
// SAFETY: Assertion above
unsafe { transmute_data_ptr::<dyn TablePage, T>(self) }
}

pub(crate) fn cast_type<T: Any>(&self) -> Option<&T> {
if Any::type_id(self) == TypeId::of::<T>() {
unsafe { Some(transmute_data_ptr::<dyn TablePage, T>(self)) }
} else {
None
}
}
}

fn make_id(page: PageIndex, slot: SlotIndex) -> Id {
Expand Down
9 changes: 9 additions & 0 deletions src/tracked_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,15 @@ impl<C> Value<C>
where
C: Configuration,
{
/// Fields of this tracked struct.
///
/// They can change across revisions, but they do not change within
/// a particular revision.
#[cfg(feature = "salsa_unstable")]
pub fn fields(&self) -> &C::Fields<'static> {
&self.fields
}

fn take_memo_table(&mut self) -> MemoTable {
// This fn is only called after `updated_at` has been set to `None`;
// this ensures that there is no concurrent access
Expand Down
Loading

0 comments on commit 88dc597

Please sign in to comment.