-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Implement goal caching with the new solver #108071
Conversation
30f915e
to
7dcc40e
Compare
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.
we currently move all cycle participants to the global cache but should only move the head of the cycle. The other goals still in the provisional cache should get dropped
} | ||
}, | ||
) | ||
let (result, dep_node) = tcx.dep_graph.with_anon_task(tcx, DepKind::TraitSelect, || { |
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.
we should add a comment here, which should state:
- this is for global caching to correctly track dependencies
- that we only cache the cycle root and why that should still be fairly performant
can write some of that myself or can chat about it in sync it to correctly summarize the results of https://rust-lang.zulipchat.com/#narrow/stream/364551-t-types.2Ftrait-system-refactor/topic/global.20cache/near/324056794
@rustbot author |
7dcc40e
to
45f3e05
Compare
); | ||
|
||
// FIXME: ??? | ||
if i == provisional_entry_index.index() { |
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 this the proper way of checking the head of the cycle? Just the first entry in this drain iterator?
(modulo the type error that currently exists, will fix)
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.
jup 👍 though it's probably nicer to first drain all other cycle participants. SO something like
// If the current goal is the head of a cycle, we drop all other
// cycle participants without moving them to the global cache.
let other_cycle_participants = provisional_entry_index.index() + 1;
for (i, entry) in cache.entries.drain_enumerated(other_cycle_participants..) {
let actual_index = cache.lookup_table.remove(&entry.goal);
debug_assert_eq!(Some(i), actual_index);
debug_assert!(entry.depth == depth);
}
let current_goal = cache.entries.pop().unwrap();
let actual_index = cache.lookup_table.remove(&entry.goal);
debug_assert_eq!(Some(i), provisional_entry_index.index());
debug_assert!(entry.depth == depth);
try_to_global_cache
can we somehow move the pop entries
and fix lookup_table
into a function 🤔 it's a bit scary that we may forget to do one of them somewhere
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.
can we somehow move the pop entries and fix lookup_table into a function
Hm... I can't see how that works with the drain_enumerated
, unless we'd write a wrapper around that or something?
☔ The latest upstream changes (presumably #107965) made this pull request unmergeable. Please resolve the merge conflicts. |
45f3e05
to
53dd7e7
Compare
Still needs a comment for why we can only cache the root goal 🤔 |
Some changes occurred to the core trait solver cc @rust-lang/initiative-trait-system-refactor |
// Everything that affects the `Result` should be performed within this | ||
// `with_anon_task` closure. | ||
// | ||
// We only cache the cycle root (why?) |
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.
The DepGraph
is a DAG, so we cannot give it cycles. Therefore, we need a way to represent this cycle (= a SCC in the evaluation graph) as a tree of DepNode
s. Those DepNode
s must contain as transitive dependencies all the queries that are run while evaluating this SCC.
Later, when replaying an execution, the DepGraph
marks the dependencies green/red in their order of apparition. This is important to avoid ICEs. For instance, if the original execution called def_kind
to check that a definition was not a closure before calling fn_sig
, the replay must realise that def_kind
has changed before trying to force fn_sig
.
So an SCC's DepNode
s must contain as transitive dependencies all the queries that are run while evaluating this SCC, in chronological order.
Having a single DepNode
to represent the SCC root (= first goal to have been pushed on the stack) is ok.
Consider a cycle A -> B -> A
that we cached as a single DepNode
for the cycle root A
. The SCC root DepNode
represents A
.
When replaying, we know that evaluating A
makes sense. However, we do not know that the A -> B
edge nor B
makes sense from just that information, we need to start re-playing the dependencies to know that.
Conversely, we cannot use the same SCC root DepNode
to cache B
. When re-playing, the invoking code would know B
, and the DepGraph
would start replaying the execution from A
. But the edge B -> A
may not exist any more and A
may not even make sense.
|
||
self.try_move_finished_goal_to_global_cache(tcx, canonical_goal, dep_node); | ||
|
||
result | ||
} |
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.
In terms of code organisation, it may be interesting to have a single function that does all the global cache manipulation (looking at the cached goals, calling the dep-graph and containing the code in try_move_finished_goal_to_global_cache
), and eventually generic on the closure that you pass to with_anon_task
.
fn with_new_goal(
&mut self,
tcx: TyCtxt<'tcx>,
canonical_goal: CanonicalGoal<'tcx>,
evaluate: impl FnMut(&mut Self) -> QueryResult<'tcx>,
) -> QueryResult<'tcx> {
if let Some(result) = tcx.new_solver_evaluation_cache.get(&canonical_goal, tcx) {
return result;
}
match self.try_push_stack(tcx, canonical_goal) { ... }
let (result, dep_node) = tcx.dep_graph.with_anon_task(tcx, DepKind::TraitSelect, evaluate);
/* inline try_move_finished_goal_to_global_cache */
result
}
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.
Ended up inlining try_move_finished_goal_to_global_cache
here. Also attempted to distill that comment about why we can't cache non-root goals, but not sure if I totally wrap my head around it despite a thorough explanation 😅
☔ The latest upstream changes (presumably #108707) made this pull request unmergeable. Please resolve the merge conflicts. |
a0a391d
to
2959c89
Compare
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.
r=me after extending the comment a bit
not sure if @cjgillot still wants to take another look
// the root goal as that will now always hit the same overflow limit. | ||
// | ||
// NOTE: We cannot move any non-root goals to the global cache. When replaying the root goal's | ||
// dependencies, our non-root goal may no longer appear as child of the root goal. |
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.
// dependencies, our non-root goal may no longer appear as child of the root goal. | |
// dependencies, our non-root goal may no longer appear as child of the root goal. | |
// | |
// See https://github.com/rust-lang/rust/pull/108071 for some additional | |
// context. |
2959c89
to
d21e4d8
Compare
@bors r=lcnr gonna approve this for now, happy to do any documentation or refactoring follow-ups later. |
…, r=lcnr Implement goal caching with the new solver Maybe it's wrong, idk. Opening mostly for first impressions before I go to sleep. r? `@lcnr,` cc `@cjgillot`
…iaskrgr Rollup of 9 pull requests Successful merges: - rust-lang#104363 (Make `unused_allocation` lint against `Box::new` too) - rust-lang#106633 (Stabilize `nonzero_min_max`) - rust-lang#106844 (allow negative numeric literals in `concat!`) - rust-lang#108071 (Implement goal caching with the new solver) - rust-lang#108542 (Force parentheses around `match` expression in binary expression) - rust-lang#108690 (Place size limits on query keys and values) - rust-lang#108708 (Prevent overflow through Arc::downgrade) - rust-lang#108739 (Prevent the `start_bx` basic block in codegen from having two `Builder`s at the same time) - rust-lang#108806 (Querify register_tools and post-expansion early lints) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
Maybe it's wrong, idk. Opening mostly for first impressions before I go to sleep.
r? @lcnr, cc @cjgillot