Add way to intern structs from references

This commit is contained in:
puuuuh 2024-10-04 07:41:20 +03:00 committed by puuuuh
parent af2ec49d80
commit 1e18334672
No known key found for this signature in database
GPG key ID: 171E3E1356CEE151
7 changed files with 148 additions and 11 deletions

View file

@ -10,7 +10,7 @@ description = "A generic framework for on-demand, incrementalized computation (e
[dependencies]
arc-swap = "1"
crossbeam = "0.8"
dashmap = "6"
dashmap = { version = "6", features = ["raw-api"] }
hashlink = "0.9"
indexmap = "2"
append-only-vec = "0.1.5"

View file

@ -32,6 +32,9 @@ macro_rules! setup_interned_struct {
// Indices for each field from 0..N -- must be unsuffixed (e.g., `0`, `1`).
field_indices: [$($field_index:tt),*],
// Indexed types for each field (T0, T1, ...)
field_indexed_tys: [$($indexed_ty:ident),*],
// Number of fields
num_fields: $N:literal,
@ -62,10 +65,30 @@ macro_rules! setup_interned_struct {
type $Configuration = $Struct<'static>;
type StructData<$db_lt> = ($($field_ty,)*);
struct StructKey<$db_lt, $($indexed_ty: $zalsa::interned::Lookup<$field_ty>),*>($($indexed_ty,)* std::marker::PhantomData<&$db_lt ()>,);
impl<$db_lt, $($indexed_ty: $zalsa::interned::Lookup<$field_ty>),*> $zalsa::interned::Lookup<StructData<$db_lt>>
for StructKey<$db_lt, $($indexed_ty),*> {
fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
$($zalsa::interned::Lookup::hash(&self.$field_index, &mut *h);)*
}
fn eq(&self, data: &StructData<$db_lt>) -> bool {
($($zalsa::interned::Lookup::eq(&self.$field_index, &data.$field_index) && )* true)
}
fn into_owned(self) -> StructData<$db_lt> {
($($zalsa::interned::Lookup::into_owned(self.$field_index),)*)
}
}
impl $zalsa_struct::Configuration for $Configuration {
const DEBUG_NAME: &'static str = stringify!($Struct);
type Data<$db_lt> = ($($field_ty,)*);
type Struct<$db_lt> = $Struct<$db_lt>;
type Data<'a> = StructData<'a>;
type Struct<'a> = $Struct<'a>;
fn struct_from_id<'db>(id: salsa::Id) -> Self::Struct<'db> {
$Struct(id, std::marker::PhantomData)
}
@ -126,13 +149,14 @@ macro_rules! setup_interned_struct {
}
impl<$db_lt> $Struct<$db_lt> {
pub fn $new_fn<$Db>(db: &$db_lt $Db, $($field_id: $field_ty),*) -> Self
pub fn $new_fn<$Db>(db: &$db_lt $Db, $($field_id: impl $zalsa::interned::Lookup<$field_ty>),*) -> Self
where
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + salsa::Database,
{
let current_revision = $zalsa::current_revision(db);
$Configuration::ingredient(db).intern(db.as_dyn_database(), ($($field_id,)*))
$Configuration::ingredient(db).intern(db.as_dyn_database(),
StructKey::<$db_lt>($($field_id,)* std::marker::PhantomData::default()))
}
$(

View file

@ -90,6 +90,7 @@ impl Macro {
let field_getter_ids = salsa_struct.field_getter_ids();
let field_options = salsa_struct.field_options();
let field_tys = salsa_struct.field_tys();
let field_indexed_tys = salsa_struct.field_indexed_tys();
let generate_debug_impl = salsa_struct.generate_debug_impl();
let zalsa = self.hygiene.ident("zalsa");
@ -112,6 +113,7 @@ impl Macro {
field_getters: [#(#field_vis #field_getter_ids),*],
field_tys: [#(#field_tys),*],
field_indices: [#(#field_indices),*],
field_indexed_tys: [#(#field_indexed_tys),*],
num_fields: #num_fields,
generate_debug_impl: #generate_debug_impl,
unused_names: [

View file

@ -242,6 +242,14 @@ where
self.fields.iter().map(|f| &f.field.ty).collect()
}
pub(crate) fn field_indexed_tys(&self) -> Vec<syn::Ident> {
self.fields
.iter()
.enumerate()
.map(|(i, _)| quote::format_ident!("T{i}"))
.collect()
}
pub(crate) fn field_options(&self) -> Vec<TokenStream> {
self.fields
.iter()

View file

@ -1,5 +1,5 @@
use std::fmt;
use std::hash::Hash;
use std::hash::{BuildHasher, Hash, Hasher};
use std::marker::PhantomData;
use crate::durability::Durability;
@ -114,19 +114,19 @@ where
unsafe { std::mem::transmute(data) }
}
pub fn intern_id<'db>(
pub fn intern_id<'db, T: Lookup<C::Data<'db>>>(
&'db self,
db: &'db dyn crate::Database,
data: C::Data<'db>,
data: T,
) -> crate::Id {
C::deref_struct(self.intern(db, data)).as_id()
}
/// Intern data to a unique reference.
pub fn intern<'db>(
pub fn intern<'db, T: Lookup<C::Data<'db>>>(
&'db self,
db: &'db dyn crate::Database,
data: C::Data<'db>,
data: T,
) -> C::Struct<'db> {
let zalsa_local = db.zalsa_local();
zalsa_local.report_tracked_read(
@ -137,7 +137,25 @@ where
// Optimisation to only get read lock on the map if the data has already
// been interned.
let mut hasher = self.key_map.hasher().build_hasher();
data.hash(&mut hasher);
let data_hash = hasher.finish();
let shard = self.key_map.determine_shard(data_hash as _);
{
let lock = self.key_map.shards()[shard].read();
if let Some(bucket) = lock.find(data_hash, |(a, _)| {
// lifetime shrink
let a: &C::Data<'db> = unsafe { std::mem::transmute(a) };
Lookup::eq(&data, a)
}) {
return C::struct_from_id(unsafe { *bucket.as_ref().1.get() });
}
};
let data = data.into_owned();
let internal_data = unsafe { self.to_internal_data(data) };
if let Some(guard) = self.key_map.get(&internal_data) {
let id = *guard;
drop(guard);
@ -288,3 +306,58 @@ where
&self.syncs
}
}
pub trait Lookup<O>
{
fn hash<H: Hasher>(&self, h: &mut H);
fn eq(&self, data: &O) -> bool;
fn into_owned(self) -> O;
}
impl<T> Lookup<T> for T
where
T: Hash + Eq
{
fn hash<H: Hasher>(&self, h: &mut H) {
Hash::hash(self, &mut *h);
}
fn eq(&self, data: &T) -> bool {
self == data
}
fn into_owned(self) -> T {
self
}
}
impl<T> Lookup<T> for &T
where
T: Clone + Eq + Hash,
{
fn hash<H: Hasher>(&self, h: &mut H) {
Hash::hash(self, &mut *h);
}
fn eq(&self, data: &T) -> bool {
*self == data
}
fn into_owned(self) -> T {
Clone::clone(self)
}
}
impl Lookup<String> for &str {
fn hash<H: Hasher>(&self, h: &mut H) {
Hash::hash(self, &mut *h)
}
fn eq(&self, data: &String) -> bool {
self == data
}
fn into_owned(self) -> String {
self.to_owned()
}
}

View file

@ -133,6 +133,7 @@ pub mod plumbing {
pub use crate::interned::IngredientImpl;
pub use crate::interned::JarImpl;
pub use crate::interned::Value;
pub use crate::interned::Lookup;
}
pub mod function {

View file

@ -9,16 +9,24 @@ struct InternedString<'db> {
data: String,
}
#[salsa::interned]
struct InternedPair<'db> {
data: (InternedString<'db>, InternedString<'db>),
}
#[salsa::interned]
struct InternedTwoFields<'db> {
data1: String,
data2: String,
}
#[salsa::tracked]
fn intern_stuff(db: &dyn salsa::Database) -> String {
let s1 = InternedString::new(db, "Hello, ".to_string());
let s2 = InternedString::new(db, "World, ".to_string());
let s2 = InternedString::new(db, "World, ");
let s3 = InternedPair::new(db, (s1, s2));
format!("{s3:?}")
}
@ -29,3 +37,24 @@ fn execute() {
"InternedPair { data: (InternedString { data: \"Hello, \" }, InternedString { data: \"World, \" }) }"
"#]].assert_debug_eq(&intern_stuff(&db));
}
#[test]
fn interning_returns_equal_keys_for_equal_data() {
let db = salsa::DatabaseImpl::new();
let s1 = InternedString::new(&db, "Hello, ".to_string());
let s2 = InternedString::new(&db, "World, ".to_string());
let s1_2 = InternedString::new(&db, "Hello, ");
let s2_2 = InternedString::new(&db, "World, ");
assert_eq!(s1, s1_2);
assert_eq!(s2, s2_2);
}
#[test]
fn interning_returns_equal_keys_for_equal_data_multi_field() {
let db = salsa::DatabaseImpl::new();
let s1 = InternedTwoFields::new(&db, "Hello, ".to_string(), "World");
let s2 = InternedTwoFields::new(&db, "World, ", "Hello".to_string());
let s1_2 = InternedTwoFields::new(&db, "Hello, ", "World");
let s2_2 = InternedTwoFields::new(&db, "World, ", "Hello");
assert_eq!(s1, s1_2);
assert_eq!(s2, s2_2);
}