salsa/src/table/memo.rs

170 lines
5.2 KiB
Rust
Raw Normal View History

2024-08-12 05:02:19 +00:00
use std::{
any::{Any, TypeId},
sync::Arc,
};
use arc_swap::ArcSwap;
use parking_lot::RwLock;
2024-08-15 15:14:32 +00:00
use crate::{zalsa::MemoIngredientIndex, zalsa_local::QueryOrigin};
2024-08-12 05:02:19 +00:00
#[derive(Default)]
pub(crate) struct MemoTable {
memos: RwLock<Vec<MemoEntry>>,
}
2024-08-15 15:14:32 +00:00
pub(crate) trait Memo: Any + Send + Sync {
/// Returns the `origin` of this memo
fn origin(&self) -> &QueryOrigin;
}
2024-08-12 05:02:19 +00:00
/// Wraps the data stored for a memoized entry.
/// This struct has a customized Drop that will
/// ensure that its `data` field is properly freed.
#[derive(Default)]
struct MemoEntry {
data: Option<MemoEntryData>,
}
/// Data for a memoized entry.
/// This is a type-erased `Arc<M>`, where `M` is the type of memo associated
/// with that particular ingredient index.
///
/// # Implementation note
///
/// Every entry is associated with some ingredient that has been added to the database.
/// That ingredient has a fixed type of values that it produces etc.
/// Therefore, once a given entry goes from `Empty` to `Full`,
/// the type-id associated with that entry should never change.
///
/// We take advantage of this and use an `ArcSwap` to store the actual memo.
/// This allows us to store into the memo-entry without acquiring a write-lock.
/// However, using `ArcSwap` means we cannot use a `Arc<dyn Any>` or any other wide pointer.
/// Therefore, we hide the type by transmuting to `DummyMemo`; but we must then be very careful
/// when freeing `MemoEntryData` values to transmute things back. See the `Drop` impl for
/// [`MemoEntry`][] for details.
struct MemoEntryData {
/// The `type_id` of the erased memo type `M`
type_id: TypeId,
/// A pointer to `std::mem::drop::<Arc<M>>` for the erased memo type `M`
2024-08-15 15:14:32 +00:00
to_dyn_fn: fn(Arc<DummyMemo>) -> Arc<dyn Memo>,
2024-08-12 05:02:19 +00:00
/// An [`ArcSwap`][] to a `Arc<M>` for the erased memo type `M`
arc_swap: ArcSwap<DummyMemo>,
}
/// Dummy placeholder type that we use when erasing the memo type `M` in [`MemoEntryData`][].
enum DummyMemo {}
impl MemoTable {
2024-08-15 15:14:32 +00:00
fn to_dummy<M: Memo>(memo: Arc<M>) -> Arc<DummyMemo> {
2024-08-12 05:02:19 +00:00
unsafe { std::mem::transmute::<Arc<M>, Arc<DummyMemo>>(memo) }
}
2024-08-15 15:14:32 +00:00
unsafe fn from_dummy<M: Memo>(memo: Arc<DummyMemo>) -> Arc<M> {
2024-08-12 05:02:19 +00:00
unsafe { std::mem::transmute::<Arc<DummyMemo>, Arc<M>>(memo) }
}
2024-08-15 15:14:32 +00:00
fn to_dyn_fn<M: Memo>() -> fn(Arc<DummyMemo>) -> Arc<dyn Memo> {
let f: fn(Arc<M>) -> Arc<dyn Memo> = |x| x;
unsafe {
2024-08-15 15:14:32 +00:00
std::mem::transmute::<fn(Arc<M>) -> Arc<dyn Memo>, fn(Arc<DummyMemo>) -> Arc<dyn Memo>>(
f,
)
}
2024-08-12 05:02:19 +00:00
}
2024-08-15 15:14:32 +00:00
pub(crate) fn insert<M: Memo>(&self, memo_ingredient_index: MemoIngredientIndex, memo: Arc<M>) {
2024-08-12 05:02:19 +00:00
// If the memo slot is already occupied, it must already have the
// right type info etc, and we only need the read-lock.
if let Some(MemoEntry {
data:
Some(MemoEntryData {
type_id,
2024-08-15 15:14:32 +00:00
to_dyn_fn: _,
2024-08-12 05:02:19 +00:00
arc_swap,
}),
}) = self.memos.read().get(memo_ingredient_index.as_usize())
{
assert_eq!(
*type_id,
TypeId::of::<M>(),
"inconsistent type-id for `{memo_ingredient_index:?}`"
);
arc_swap.store(Self::to_dummy(memo));
return;
}
// Otherwise we need the write lock.
self.insert_cold(memo_ingredient_index, memo)
}
2024-08-15 15:14:32 +00:00
fn insert_cold<M: Memo>(&self, memo_ingredient_index: MemoIngredientIndex, memo: Arc<M>) {
2024-08-12 05:02:19 +00:00
let mut memos = self.memos.write();
let memo_ingredient_index = memo_ingredient_index.as_usize();
memos.resize_with(memo_ingredient_index + 1, || MemoEntry::default());
memos[memo_ingredient_index] = MemoEntry {
data: Some(MemoEntryData {
type_id: TypeId::of::<M>(),
2024-08-15 15:14:32 +00:00
to_dyn_fn: Self::to_dyn_fn::<M>(),
2024-08-12 05:02:19 +00:00
arc_swap: ArcSwap::new(Self::to_dummy(memo)),
}),
};
}
2024-08-15 15:14:32 +00:00
pub(crate) fn get<M: Memo>(
2024-08-12 05:02:19 +00:00
&self,
memo_ingredient_index: MemoIngredientIndex,
) -> Option<Arc<M>> {
let memos = self.memos.read();
let Some(MemoEntry {
data:
Some(MemoEntryData {
type_id,
2024-08-15 15:14:32 +00:00
to_dyn_fn: _,
2024-08-12 05:02:19 +00:00
arc_swap,
}),
}) = memos.get(memo_ingredient_index.as_usize())
else {
return None;
};
assert_eq!(
*type_id,
TypeId::of::<M>(),
"inconsistent type-id for `{memo_ingredient_index:?}`"
);
// SAFETY: type_id check asserted above
unsafe { Some(Self::from_dummy(arc_swap.load_full())) }
}
}
impl Drop for MemoEntry {
fn drop(&mut self) {
if let Some(MemoEntryData {
type_id: _,
2024-08-15 15:14:32 +00:00
to_dyn_fn,
2024-08-12 05:02:19 +00:00
arc_swap,
}) = self.data.take()
{
let arc = arc_swap.into_inner();
2024-08-15 15:14:32 +00:00
std::mem::drop(to_dyn_fn(arc));
2024-08-12 05:02:19 +00:00
}
}
}
impl Drop for DummyMemo {
fn drop(&mut self) {
unreachable!("should never get here")
}
}
impl std::fmt::Debug for MemoTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoTable").finish()
}
}