Skip to content

fix: change detection for fixpoint queries #836

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

Merged
merged 16 commits into from
May 6, 2025

Conversation

MichaReiser
Copy link
Contributor

@MichaReiser MichaReiser commented Apr 30, 2025

This PR fixes two bugs in maybe_changed_after for cyclic queries

Fix number 1

Salsa doesn't invalidate a cycle of query_a -> query_b if query_b only depends on the input in the first iteration.

Fixing this requires that we carry over inputs to later iterations. This is a very similar fix to what I did for tracked structs.

This fixes #834.

Fix number 2

We saw many access to interned struct that wasn't interned in this revision panics. This was caused by maybe_changed_after re-running a query in the attempt to backdate it even when the query participates in a cycle (in which case salsa returned Fixpoint initial on which the query that Salsa tries to backdate now depends on). This is problematic the assumption before calling execute is that all its inputs are valid but this isn't the case when returning fixpoint initial, Salsa then intentionally defers validating all the input/outputs.

The fix here is to only perform the backdating if not in a cycle. If incide a cycle, then the backdating must happen for the cycle head (which then calls maybe_changed_after of inner queries again)

Performance

The performance results on ty (former Red Knot) are neutral. The performance should mainly regress if cycles have many different inputs/outputs between iterations because they're now carried over

Copy link

netlify bot commented Apr 30, 2025

Deploy Preview for salsa-rs canceled.

Name Link
🔨 Latest commit f702cd5
🔍 Latest deploy log https://app.netlify.com/sites/salsa-rs/deploys/6819c1fd459a8000083befa6

Copy link

codspeed-hq bot commented Apr 30, 2025

CodSpeed Performance Report

Merging #836 will degrade performances by 10.44%

Comparing MichaReiser:fix-missing-iteration-inputs (f702cd5) with master (2c04176)

Summary

❌ 1 (👁 1) regressions
✅ 11 untouched benchmarks

Benchmarks breakdown

Benchmark BASE HEAD Change
👁 converge_diverge 129.8 µs 144.9 µs -10.44%

@MichaReiser MichaReiser force-pushed the fix-missing-iteration-inputs branch 2 times, most recently from 5a435a7 to 953dc34 Compare April 30, 2025 15:06
@MichaReiser
Copy link
Contributor Author

It's not entirely clear what behavior we want for accumulated values. E.g. a depends on b but only in its first iteration and b emits an accumulated value. Is that value now returned when retrieving the accumulated values of a?

If no, then we'll need a way to distinguish between dependencies from the current and previous iterations.

@MichaReiser MichaReiser force-pushed the fix-missing-iteration-inputs branch from 9036ecf to 22a383b Compare May 4, 2025 17:15
@MichaReiser MichaReiser changed the title fix: Cycles with conditional input-dependencies fix: change detection for fixpoint queries May 5, 2025
"salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(403)) })",
"salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(400)) })",
"salsa_event(DidReinternValue { key: query_d::interned_arguments(Id(800)), revision: R2 })",
"salsa_event(WillExecute { database_key: query_b(Id(0)) })",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test captures fix 2. Salsa tries to see if query_b changed (cycle head). query_b depends on query_a and query_a depends on query_b and query_d.

Salsa returns a provisional value for query_b.

Before

maybe_changed_after:

  • query_b: Unchanged
    • query_a:
    • Calls maybe_changed_after for query_b: Returns Unchanged (but fixpoint initial)
    • Calls maybe_changed_after for query_d: Detects that one input changed. Tries backdating by executing the query (which isn't safe because it may depend on a struct interned by query_b which was never fully verified

Now

query_d now returns Changed without trying to backdate, same for query_a because both see that they're part of a cycle. Salsa then executes query_b, which may get backdated.

Copy link
Contributor

Choose a reason for hiding this comment

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

And I guess since you reverted the earlier (not really right) change I'd made to avoid having output-validation fail in this case, this test will also now fail without the other fix in this PR (and not only due to the log assertions.)

@MichaReiser MichaReiser marked this pull request as ready for review May 5, 2025 08:20
@MichaReiser MichaReiser added the bug Something isn't working label May 5, 2025
Comment on lines 416 to 419
QueryOrigin::FixpointInitial if old_memo.may_be_provisional() => {
VerifyResult::changed()
}
QueryOrigin::FixpointInitial if old_memo.may_be_provisional() => VerifyResult::Changed,
QueryOrigin::FixpointInitial => VerifyResult::unchanged(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@carljm I'm not 100% sure about the two fixpoint arms here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not either :) I don't have a memory of why this is this way -- maybe it was needed in some much-earlier version of fixpoint iteration?

Locally it seems that replacing both arms with this doesn't fail any tests, and it feels more clearly correct (but I'd want to also check it for perf regression on ty):

QueryOrigin:FixpointInitial => VerifyResult::changed(),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes me wonder if we even need this extra variant or if we could just use Derived everywhere because we also have revisions.maybe_changed_after to identify provisional values.

Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

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

So good!!

This all makes sense to me, fantastic work.

"salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(403)) })",
"salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(400)) })",
"salsa_event(DidReinternValue { key: query_d::interned_arguments(Id(800)), revision: R2 })",
"salsa_event(WillExecute { database_key: query_b(Id(0)) })",
Copy link
Contributor

Choose a reason for hiding this comment

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

And I guess since you reverted the earlier (not really right) change I'd made to avoid having output-validation fail in this case, this test will also now fail without the other fix in this PR (and not only due to the log assertions.)

Comment on lines +38 to +42
fn query_a_initial(_db: &dyn Database, _input: Input) -> u32 {
0
}

fn query_a_recover(
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe these shouldn't be named query_a_* since they are used by both query_a and query_d? Looks a bit weird above.

Comment on lines 416 to 419
QueryOrigin::FixpointInitial if old_memo.may_be_provisional() => {
VerifyResult::changed()
}
QueryOrigin::FixpointInitial if old_memo.may_be_provisional() => VerifyResult::Changed,
QueryOrigin::FixpointInitial => VerifyResult::unchanged(),
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not either :) I don't have a memory of why this is this way -- maybe it was needed in some much-earlier version of fixpoint iteration?

Locally it seems that replacing both arms with this doesn't fail any tests, and it feels more clearly correct (but I'd want to also check it for perf regression on ty):

QueryOrigin:FixpointInitial => VerifyResult::changed(),

@MichaReiser MichaReiser enabled auto-merge May 6, 2025 08:02
@MichaReiser MichaReiser added this pull request to the merge queue May 6, 2025
Merged via the queue into salsa-rs:master with commit b2b82bc May 6, 2025
11 checks passed
@MichaReiser MichaReiser deleted the fix-missing-iteration-inputs branch May 6, 2025 08:22
@github-actions github-actions bot mentioned this pull request May 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

assertion failed: old_memo.revisions.changed_at <= revisions.changed_at
2 participants