Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assign memo ingredients per salsa-struct-ingredient #614

Merged
merged 7 commits into from
Dec 18, 2024

Conversation

ShoyuVanilla
Copy link
Contributor

Closes #600

Copy link

netlify bot commented Nov 13, 2024

Deploy Preview for salsa-rs canceled.

Name Link
🔨 Latest commit 53c7eba
🔍 Latest deploy log https://app.netlify.com/sites/salsa-rs/deploys/6762f08f5314490008b8f2fd

Copy link

codspeed-hq bot commented Nov 13, 2024

CodSpeed Performance Report

Merging #614 will not alter performance

Comparing ShoyuVanilla:issue-600 (53c7eba) with master (3c7f169)

Summary

✅ 9 untouched benchmarks

src/zalsa.rs Outdated
@@ -130,6 +132,9 @@ pub struct Zalsa {
/// adding new kinds of ingredients.
jar_map: Mutex<FxHashMap<TypeId, IngredientIndex>>,

/// Map from the type-id of a salsa struct to the index of its first ingredient.
salsa_struct_map: Mutex<FxHashMap<TypeId, IngredientIndex>>,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this new table for SalsaStruct -> IngredientIndex but I couldn't find any other way to pass the salsa-struct-ingredient to its tracked function when creating the function's ingredient, as salsa-struct-ingredient is accessible only with its Configuration, that is private inside the macro-expanded code of that salsa struct.
Would there be some good alternatives?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be inclined to add a method ingredient_index to SalsaStructInDb but best to hear what @nikomatsakis thinks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to get the IngredientIndex for salsa struct here;

impl $zalsa::Jar for $Configuration {
fn create_ingredients(
&self,
aux: &dyn $zalsa::JarAux,
first_index: $zalsa::IngredientIndex,
) -> Vec<Box<dyn $zalsa::Ingredient>> {
let fn_ingredient = <$zalsa::function::IngredientImpl<$Configuration>>::new(
first_index,
aux,
);

and we can't get the actual instance of salsa struct here. All we have is JarAux - actually, this is Zalsa -, so that function should be a static method, I guess. (I'm afraid that this makes SalsaStructInDb dyn-incompatible but we are not using it as a trait object anywhere in salsa)

Thus, the function would be implemented inside our setup_* macros like;

fn ingredient_index(aux: &dyn JarAux) -> IngredientIndex {
    aux.lookup_jar_by_type(JarImpl::<$Configuration>::default())
}

and we need to expose some method like lookup_jar_by_type in JarAux, I think.

Copy link
Contributor

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we currently have a test for a tracked fn that has more than one argument (other than db) and thus uses the interner. Could you double check if that's indeed the case and, if so, add a test for it?

src/zalsa.rs Outdated
Comment on lines 123 to 125
/// [ingredient-indices](`IngredientIndex`)for tracked functions that have this salsa struct
/// as input.
memo_ingredients: RwLock<FxHashMap<IngredientIndex, Vec<IngredientIndex>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
/// [ingredient-indices](`IngredientIndex`)for tracked functions that have this salsa struct
/// as input.
memo_ingredients: RwLock<FxHashMap<IngredientIndex, Vec<IngredientIndex>>>,
/// [ingredient-indices](`IngredientIndex`) for tracked functions that have this salsa struct
/// as input.
memo_ingredient_indices: RwLock<FxHashMap<IngredientIndex, Vec<IngredientIndex>>>,

I also have a slight preference to the name @nikomatsakis suggest in the mentoring instructions (memo_ingredient_indices)

src/zalsa.rs Outdated
Comment on lines 123 to 125
/// [ingredient-indices](`IngredientIndex`)for tracked functions that have this salsa struct
/// as input.
memo_ingredients: RwLock<FxHashMap<IngredientIndex, Vec<IngredientIndex>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider using a Vec<Vec<IngredientIndex>> here and directly index into the Vec using the IngredientIndex as an offset. This would result in wasting some memory because not every IngredientIndex is an index for a tracked struct but it would avoid one additional hashing step

@ShoyuVanilla
Copy link
Contributor Author

I don't think we currently have a test for a tracked fn that has more than one argument (other than db) and thus uses the internet. Could you double check if that's indeed the case and, if so, add a test for it?

Sure! I'll add tests and edit things you commented when I get home. Thanks for the review

impl JarAux for Zalsa {
fn next_memo_ingredient_index(&self, ingredient_index: IngredientIndex) -> MemoIngredientIndex {
let mut memo_ingredients = self.memo_ingredients.lock();
struct JarAuxImpl<'a>(&'a Zalsa, &'a FxHashMap<TypeId, IngredientIndex>);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed salsa struct ingredient lookup as I said here but I had to change this to avoid deadlocks 😢

});
if should_create {
let aux = JarAuxImpl(self, &jar_map);
let ingredients = jar.create_ingredients(&aux, index);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a comment on jar_map that suggests that it's important to only access ingredients_vec while the lock on jar_map is held

https://github.com/MichaReiser/salsa/blob/38ad6555ebe4374bbc69022e005f4136a1839ad9/src/zalsa.rs#L127-L133

So moving it out to avoid the deadlock might not be entirely safe

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example. What could happen now is that two calls end up with the same index because the new ingredient isn't pushed on the ingredients_vec until line 206, so that self.ingredients_vec.len() returns the same length.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll fix this

@nikomatsakis
Copy link
Member

@ShoyuVanilla hey, cool! My apologies for radio silence. I've been overwhelmed. I'm going to carve out more time for salsa though so let me take a look...

Copy link
Member

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good! Took me a bit to get my head in the right space. I left comments, can you check that they are accurate and merge them in? After that, I'd say we can land this.

.or_insert_with(|| {
let index = IngredientIndex::from(self.ingredients_vec.len());
let ingredients = jar.create_ingredients(self, index);
let mut should_create = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite follow this -- why move this logic out from the or_insert_with call?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it is so that ingredient construction can call lookup_jar_by_type?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to have a comment here explaining that interaction

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember for sure - as it was weeks before 😅 -, but I think that it was because it had some issues due to creation order like using ingredients index before they are created.
I'll look into such order related things along with the one you commented here

src/ingredient.rs Show resolved Hide resolved
}

#[salsa::tracked]
fn tracked_fn<'db>(db: &'db dyn salsa::Database, input: MyInput, interned: MyInterned<'db>) -> u32 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this particular test? Tracked functions with multiple arguments today compile to a tracked function on a hidden interned value -- is that what you were intending to test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Though this PR is not directly related to that, but as it changes some codes in such hidden interned value, I was intending to add a regression test for it. Other case are pre-existing

@@ -199,7 +202,19 @@ macro_rules! setup_tracked_fn {
aux: &dyn $zalsa::JarAux,
first_index: $zalsa::IngredientIndex,
) -> Vec<Box<dyn $zalsa::Ingredient>> {
let struct_index = $zalsa::macro_if! {
if $needs_interner {
first_index.successor(0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I...guess this is fine. It's a bit surprising to use the index of something that hasn't been made yet. An alternative would be to flip the order so that we create the interned struct first and then pass its index to the function. There'd probably be some minor adjustments made elsewhere in the file as a result but shouldn't be too bad.

if $needs_interner {
first_index.successor(0)
} else {
<$InternedData as $zalsa::SalsaStructInDb>::lookup_ingredient_index(aux)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see now the role of lookup_ingredient_index.

first_index.successor(0)
} else {
<$InternedData as $zalsa::SalsaStructInDb>::lookup_ingredient_index(aux)
.expect(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 1000% percent sure that the struct ingredient will always have been created first. If this code doesn't run until the tracked functon is first called, I suppose that's true, but if it could run at some other time maybe not? Still, this seems like a good assertion for now to simplify our lives. If it's not true we can probably add some kind of dependency mechanism to force it to be true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought - and experimented with some cases - that the structs are created first to be passed to a tracked function but my understanding on salsa is very limited and I might be wrong. I'll investigate this more

src/ingredient.rs Show resolved Hide resolved
src/ingredient.rs Show resolved Hide resolved
src/zalsa.rs Show resolved Hide resolved
@@ -186,21 +188,22 @@ impl Zalsa {
{
let jar_type_id = jar.type_id();
let mut jar_map = self.jar_map.lock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let mut jar_map = self.jar_map.lock();
// Important: we hold the lock on `jar_map` during ingredient construction to
// ensure all ingredients are created atomically.
let mut jar_map = self.jar_map.lock();

@ShoyuVanilla
Copy link
Contributor Author

This looks good! Took me a bit to get my head in the right space. I left comments, can you check that they are accurate and merge them in? After that, I'd say we can land this.

Thanks for the review! I'll fix those things by this weekend

@ShoyuVanilla
Copy link
Contributor Author

Well, I was going to continue working on this but it seems that I'm too late, sorry. I had family affairs last week 😢
I'll issue a new PR with fixes for quirks on this PR

@Veykril
Copy link
Member

Veykril commented Dec 18, 2024

No need to apologize you have no obligations so do not feel pressured to work on things asap

@nikomatsakis
Copy link
Member

@ShoyuVanilla yeah, no worries, I'm grateful you did the original PR! Very big impact.

The main thing this PR needs is cargo fmt

@davidbarsky
Copy link
Contributor

@ShoyuVanilla if it's okay, i'll merge this PR: it'll make managing the stack of changes that Lukas and I have for master...davidbarsky:salsa:david-and-lukas/required-salsa-changes-for-ra a lot easier. Can you do the followups on a separate PR?

@ShoyuVanilla
Copy link
Contributor Author

@ShoyuVanilla if it's okay, i'll merge this PR: it'll make managing the stack of changes that Lukas and I have for master...davidbarsky:salsa:david-and-lukas/required-salsa-changes-for-ra a lot easier. Can you do the followups on a separate PR?

Yes, I'll issue a new follow-up PR (along with #599, maybe?)

@davidbarsky
Copy link
Contributor

Yes, I'll issue a new follow-up PR (along with #599, maybe?)

Up to you!

@davidbarsky davidbarsky added this pull request to the merge queue Dec 18, 2024
Merged via the queue into salsa-rs:master with commit 0ac5c1c Dec 18, 2024
10 checks passed
@ShoyuVanilla ShoyuVanilla deleted the issue-600 branch December 18, 2024 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assign memo ingredients per salsa-struct-ingredient, not globally
5 participants