-
Notifications
You must be signed in to change notification settings - Fork 158
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
Coarse-grained tracked structs #657
base: master
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for salsa-rs canceled.
|
CodSpeed Performance ReportMerging #657 will not alter performanceComparing Summary
|
2937a33
to
c331b84
Compare
c331b84
to
3e2d7f3
Compare
pub name: FunctionId<'db>, | ||
|
||
#[tracked] | ||
name_span: Span<'db>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried not adding this #[tracked]
attribute because it seems unnecessary, but I actually got a test failure that I don't really understand:
---- type_check::fix_bad_variable_in_function stdout ----
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: parse_statements(Id(0)) } }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: type_check_program(Id(1400)) } }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: type_check_function(Id(1800)) } }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: type_check_function(Id(1801)) } }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: find_function(Id(1c00)) } }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: find_function(Id(1c01)) } }
Event: Event { thread_id: ThreadId(11), kind: DidSetCancellationFlag }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillExecute { database_key: parse_statements(Id(0)) } }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
Event: Event { thread_id: ThreadId(11), kind: WillCheckCancellation }
thread 'type_check::fix_bad_variable_in_function' panicked at /home/ibraheem/dev/sync/salsa/src/table.rs:281:9:
assertion `left == right` failed: page has hidden type `"salsa::table::Page<salsa::tracked_struct::Value<calc::ir::Function>>"` but `"salsa::table::Page<salsa::tracked_struct::Value<calc::ir::Program>>"` was expected
left: TypeId(0xbeb3ca6f8846f92e6ae4fc7569b4bf5f)
right: TypeId(0xbbdce2561688eb1bfc9fa5f5006a8342)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some debug statements to function::maybe_chagned_after
to log the dependencies and compared the logs between a run that succeeds and a run that fails.
The most interesting difference is
Successful
[src/tracked_struct/tracked_field.rs:58:9] self = salsa::tracked_struct::tracked_field::FieldIngredientImpl<calc::ir::Program> {
ingredient_index: IngredientIndex(
13,
),
field_index: 0,
}
Event: Event { thread_id: ThreadId(2), kind: WillCheckCancellation }
[src/function/maybe_changed_after.rs:154:9] &database_key_index = type_check_function(Id(1800))
[src/function/maybe_changed_after.rs:156:15] &old_memo.revisions.origin = Derived(
QueryEdges {
input_outputs: [
Input(
Function.args(Id(1000)),
),
Input(
Program(Id(1000)),
),
Input(
Span.start(Id(406)),
),
Input(
Span.end(Id(406)),
),
],
},
)
With Panic
[src/tracked_struct/tracked_field.rs:58:9] self = salsa::tracked_struct::tracked_field::FieldIngredientImpl<calc::ir::Program> {
ingredient_index: IngredientIndex(
12,
),
field_index: 0,
}
Event: Event { thread_id: ThreadId(2), kind: WillCheckCancellation }
[src/function/maybe_changed_after.rs:154:9] &database_key_index = type_check_function(Id(1800))
[src/function/maybe_changed_after.rs:156:15] &old_memo.revisions.origin = Derived(
QueryEdges {
input_outputs: [
Input(
Program(Id(1000)),
),
Input(
Program.statements(Id(1000)),
),
Input(
Span.start(Id(406)),
),
Input(
Span.end(Id(406)),
),
],
},
)
The main difference is that the ingredient on which we run maybe_changed_after
is different, AND so are the inputs. Looking at type_check_function
then the inputs of the panic case make no sense. The query never calls Program.statements
but it does call Function.args
. I don't understand how it is happening but I suspect that we're incorrectly reusing a memo and turn a Function
memo into a Program
which would align with the panic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I think I know what the issue is...
You changed the tracked struct's Configuration::create_ingredients
to only create ingredients for tracked fields. I think this change is correct. However, you call Ingredient::Index::successor
in tracked_field
with the field_index
which is the absolute offset including all fields -- including untracked fields. What this means is that we now add incorrect dependencies as soon as we have a tracked struct where a tracked
field follows an untracked field.
I hope that helps. I let you take a stab at correctly mapping the field indices. It would be great to have a unit test demonstrating the behavior and possibly an assertion somewhere that would help us catch this sooner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that is a tricky. I tried a couple things here:
- Creating the ingredients with the absolute indices directly. Unfortunately this causes a panic, as some of the ingredient code relies on indices being create sequentially.
- Passing the correct relative index to
successor
intracked_field
. Unfortunately this also did not seem to work correctly and didn't result in the correct fields being tracked, but I'm not exactly sure why. The logging code also relies on the indices being absolute in order to print field names, so this was a little awkward.
Eventually I think the easiest solution is just creating ingredients for all fields. I added a failing test that is now passing with this fix (and also fails with the second idea I tried).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that's unfortunate but seems reasonable. Would it be possible to avoid creating any field ingredients if the structure only has tracked fields? Doing this optimization seems worthwhile, considering that all-fields-untracked is the new default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that keeping a mapping table from field_index
to relative_ingredient_index
(a [Option<usize>]
where the value is Some
for all tracked fields) is annoying to write the macro code for? If not, maybe that's worth a try. I otherwise suggest we keep it as is for now (but optimize for the no-tracked fields case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me. I think the idea of tracked
and untracked
fields is easier to understand than id
(I'm fairly certain we used id
incorrectly in Red Knot).
I think I have an understanding why the panic occurs and I added an inline comment with an explanation. I let you take a stab at fixing it.
pub name: FunctionId<'db>, | ||
|
||
#[tracked] | ||
name_span: Span<'db>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some debug statements to function::maybe_chagned_after
to log the dependencies and compared the logs between a run that succeeds and a run that fails.
The most interesting difference is
Successful
[src/tracked_struct/tracked_field.rs:58:9] self = salsa::tracked_struct::tracked_field::FieldIngredientImpl<calc::ir::Program> {
ingredient_index: IngredientIndex(
13,
),
field_index: 0,
}
Event: Event { thread_id: ThreadId(2), kind: WillCheckCancellation }
[src/function/maybe_changed_after.rs:154:9] &database_key_index = type_check_function(Id(1800))
[src/function/maybe_changed_after.rs:156:15] &old_memo.revisions.origin = Derived(
QueryEdges {
input_outputs: [
Input(
Function.args(Id(1000)),
),
Input(
Program(Id(1000)),
),
Input(
Span.start(Id(406)),
),
Input(
Span.end(Id(406)),
),
],
},
)
With Panic
[src/tracked_struct/tracked_field.rs:58:9] self = salsa::tracked_struct::tracked_field::FieldIngredientImpl<calc::ir::Program> {
ingredient_index: IngredientIndex(
12,
),
field_index: 0,
}
Event: Event { thread_id: ThreadId(2), kind: WillCheckCancellation }
[src/function/maybe_changed_after.rs:154:9] &database_key_index = type_check_function(Id(1800))
[src/function/maybe_changed_after.rs:156:15] &old_memo.revisions.origin = Derived(
QueryEdges {
input_outputs: [
Input(
Program(Id(1000)),
),
Input(
Program.statements(Id(1000)),
),
Input(
Span.start(Id(406)),
),
Input(
Span.end(Id(406)),
),
],
},
)
The main difference is that the ingredient on which we run maybe_changed_after
is different, AND so are the inputs. Looking at type_check_function
then the inputs of the panic case make no sense. The query never calls Program.statements
but it does call Function.args
. I don't understand how it is happening but I suspect that we're incorrectly reusing a memo and turn a Function
memo into a Program
which would align with the panic
pub name: FunctionId<'db>, | ||
|
||
#[tracked] | ||
name_span: Span<'db>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I think I know what the issue is...
You changed the tracked struct's Configuration::create_ingredients
to only create ingredients for tracked fields. I think this change is correct. However, you call Ingredient::Index::successor
in tracked_field
with the field_index
which is the absolute offset including all fields -- including untracked fields. What this means is that we now add incorrect dependencies as soon as we have a tracked struct where a tracked
field follows an untracked field.
I hope that helps. I let you take a stab at correctly mapping the field indices. It would be great to have a unit test demonstrating the behavior and possibly an assertion somewhere that would help us catch this sooner.
let id = C::deref_struct(s); | ||
let data = Self::data(zalsa.table(), id); | ||
|
||
data.read_lock(zalsa.current_revision()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the read_lock
necessary here for an untracked field? It seems not because leak_fields
does not call it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a 100% but the comment on Value::updated_at
and the inline comment inside update
does suggest to me that the lock is required before accessing any field (handing out references to field) and it not being present in leak_field
seems like a bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks. This is great. I think we should be able to optimize the implementation to avoid creating any field-ingredients if all fields are tracked.
This PR makes tracked structs coarse-grained by default. To declare a field independently tracked, you now use the
#[tracked]
attribute. This is the opposite of the previous behavior, where#[id]
effectively declared a field untracked. The#[id]
attribute now has no effect. Importantly, reads of untracked fields do not require a read dependency to be declared.There is some duplicated code in the macros as we need to generate separate accessors for tracked vs. untracked fields and I would like these to be static (as opposed to branching at runtime).
I believe the guide documentation also needs to be updated.
Resolves #598.