-
-
Notifications
You must be signed in to change notification settings - Fork 14.7k
Replace visit_waiters with abstracted_waiters_of
#153376
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,38 +111,44 @@ pub(crate) fn find_dep_kind_root<'tcx>( | |
| last_layout | ||
| } | ||
|
|
||
| /// A resumable waiter of a query. The usize is the index into waiters in the query's latch | ||
| type Waiter = (QueryJobId, usize); | ||
|
|
||
| /// Visits all the non-resumable and resumable waiters of a query. | ||
| /// Only waiters in a query are visited. | ||
| /// `visit` is called for every waiter and is passed a query waiting on `query` | ||
| /// and a span indicating the reason the query waited on `query`. | ||
| /// If `visit` returns `Break`, this function also returns `Break`, | ||
| /// and if all `visit` calls returns `Continue` it also returns `Continue`. | ||
| /// For visits of non-resumable waiters it returns the return value of `visit`. | ||
| /// For visits of resumable waiters it returns information required to resume that waiter. | ||
| fn visit_waiters<'tcx>( | ||
| job_map: &QueryJobMap<'tcx>, | ||
| query: QueryJobId, | ||
| mut visit: impl FnMut(Span, QueryJobId) -> ControlFlow<Option<Waiter>>, | ||
| ) -> ControlFlow<Option<Waiter>> { | ||
| // Visit the parent query which is a non-resumable waiter since it's on the same stack | ||
| if let Some(parent) = job_map.parent_of(query) { | ||
| visit(job_map.span_of(query), parent)?; | ||
| } | ||
| /// The locaton of a resumable waiter. The usize is the index into waiters in the query's latch. | ||
| /// We'll use this to remove the waiter using `QueryLatch::extract_waiter` if we're waking it up. | ||
| type ResumableWaiterLocation = (QueryJobId, usize); | ||
|
|
||
| /// This abstracts over non-resumable waiters which are found in `QueryJob`'s `parent` field | ||
| /// and resumable waiters are in `latch` field. | ||
| struct AbstractedWaiter { | ||
| /// The span corresponding to the reason for why we're waiting on this query. | ||
| span: Span, | ||
| /// The query which we are waiting from, if none the waiter is from a compiler root. | ||
| parent: Option<QueryJobId>, | ||
| resumable: Option<ResumableWaiterLocation>, | ||
| } | ||
|
|
||
| /// Returns all the non-resumable and resumable waiters of a query. | ||
| /// This is used so we can uniformly loop over both non-resumable and resumable waiters. | ||
| fn abstracted_waiters_of(job_map: &QueryJobMap<'_>, query: QueryJobId) -> Vec<AbstractedWaiter> { | ||
| let mut result = Vec::new(); | ||
|
|
||
| // Visit the explicit waiters which use condvars and are resumable | ||
| // Add the parent which is a non-resumable waiter since it's on the same stack | ||
| result.push(AbstractedWaiter { | ||
| span: job_map.span_of(query), | ||
| parent: job_map.parent_of(query), | ||
| resumable: None, | ||
| }); | ||
|
|
||
| // Add the explicit waiters which use condvars and are resumable | ||
| if let Some(latch) = job_map.latch_of(query) { | ||
| for (i, waiter) in latch.waiters.lock().as_ref().unwrap().iter().enumerate() { | ||
| if let Some(waiter_query) = waiter.query { | ||
| // Return a value which indicates that this waiter can be resumed | ||
| visit(waiter.span, waiter_query).map_break(|_| Some((query, i)))?; | ||
| } | ||
| result.push(AbstractedWaiter { | ||
| span: waiter.span, | ||
| parent: waiter.parent, | ||
| resumable: Some((query, i)), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're pushing the same
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I'm not sure if that works. It's still odd to see |
||
| }); | ||
| } | ||
| } | ||
|
|
||
| ControlFlow::Continue(()) | ||
| result | ||
| } | ||
|
|
||
| /// Look for query cycles by doing a depth first search starting at `query`. | ||
|
|
@@ -155,13 +161,13 @@ fn cycle_check<'tcx>( | |
| span: Span, | ||
| stack: &mut Vec<(Span, QueryJobId)>, | ||
| visited: &mut FxHashSet<QueryJobId>, | ||
| ) -> ControlFlow<Option<Waiter>> { | ||
| ) -> ControlFlow<Option<ResumableWaiterLocation>> { | ||
| if !visited.insert(query) { | ||
| return if let Some(p) = stack.iter().position(|q| q.1 == query) { | ||
| return if let Some(pos) = stack.iter().position(|q| q.1 == query) { | ||
| // We detected a query cycle, fix up the initial span and return Some | ||
|
|
||
| // Remove previous stack entries | ||
| stack.drain(0..p); | ||
| stack.drain(0..pos); | ||
| // Replace the span for the first query with the cycle cause | ||
| stack[0].0 = span; | ||
| ControlFlow::Break(None) | ||
|
|
@@ -174,16 +180,23 @@ fn cycle_check<'tcx>( | |
| stack.push((span, query)); | ||
|
|
||
| // Visit all the waiters | ||
| let r = visit_waiters(job_map, query, |span, successor| { | ||
| cycle_check(job_map, successor, span, stack, visited) | ||
| }); | ||
|
|
||
| // Remove the entry in our stack if we didn't find a cycle | ||
| if r.is_continue() { | ||
| stack.pop(); | ||
| for abstracted_waiter in abstracted_waiters_of(job_map, query) { | ||
| let Some(parent) = abstracted_waiter.parent else { | ||
| // Skip waiters which are not queries | ||
| continue; | ||
| }; | ||
| if let ControlFlow::Break(maybe_resumable) = | ||
| cycle_check(job_map, parent, abstracted_waiter.span, stack, visited) | ||
| { | ||
| // Return the resumable waiter in `waiter.resumable` if present | ||
| return ControlFlow::Break(abstracted_waiter.resumable.or(maybe_resumable)); | ||
| } | ||
| } | ||
|
|
||
| r | ||
| // Remove the entry in our stack since we didn't find a cycle | ||
| stack.pop(); | ||
|
|
||
| ControlFlow::Continue(()) | ||
| } | ||
|
|
||
| /// Finds out if there's a path to the compiler root (aka. code which isn't in a query) | ||
|
|
@@ -193,18 +206,26 @@ fn connected_to_root<'tcx>( | |
| job_map: &QueryJobMap<'tcx>, | ||
| query: QueryJobId, | ||
| visited: &mut FxHashSet<QueryJobId>, | ||
| ) -> ControlFlow<Option<Waiter>> { | ||
| ) -> bool { | ||
| // We already visited this or we're deliberately ignoring it | ||
| if !visited.insert(query) { | ||
| return ControlFlow::Continue(()); | ||
| return false; | ||
| } | ||
|
|
||
| // This query is connected to the root (it has no query parent), return true | ||
| if job_map.parent_of(query).is_none() { | ||
| return ControlFlow::Break(None); | ||
| // Visit all the waiters | ||
| for abstracted_waiter in abstracted_waiters_of(job_map, query) { | ||
| match abstracted_waiter.parent { | ||
| // This query is connected to the root | ||
| None => return true, | ||
| Some(parent) => { | ||
| if connected_to_root(job_map, parent, visited) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| visit_waiters(job_map, query, |_, successor| connected_to_root(job_map, successor, visited)) | ||
| false | ||
| } | ||
|
|
||
| /// Looks for query cycles starting from the last query in `jobs`. | ||
|
|
@@ -220,7 +241,7 @@ fn remove_cycle<'tcx>( | |
| let mut visited = FxHashSet::default(); | ||
| let mut stack = Vec::new(); | ||
| // Look for a cycle starting with the last query in `jobs` | ||
| if let ControlFlow::Break(waiter) = | ||
| if let ControlFlow::Break(resumable) = | ||
| cycle_check(job_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited) | ||
| { | ||
| // The stack is a vector of pairs of spans and queries; reverse it so that | ||
|
|
@@ -242,44 +263,44 @@ fn remove_cycle<'tcx>( | |
|
|
||
| struct EntryPoint { | ||
| query_in_cycle: QueryJobId, | ||
| waiter: Option<(Span, QueryJobId)>, | ||
| query_waiting_on_cycle: Option<(Span, QueryJobId)>, | ||
| } | ||
|
|
||
| // Find the queries in the cycle which are | ||
| // connected to queries outside the cycle | ||
| let entry_points = stack | ||
| .iter() | ||
| .filter_map(|&(_, query_in_cycle)| { | ||
| if job_map.parent_of(query_in_cycle).is_none() { | ||
| // This query is connected to the root (it has no query parent) | ||
| Some(EntryPoint { query_in_cycle, waiter: None }) | ||
| } else { | ||
| let mut waiter_on_cycle = None; | ||
| // Find a direct waiter who leads to the root | ||
| let _ = visit_waiters(job_map, query_in_cycle, |span, waiter| { | ||
| // Mark all the other queries in the cycle as already visited | ||
| let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1)); | ||
|
|
||
| if connected_to_root(job_map, waiter, &mut visited).is_break() { | ||
| waiter_on_cycle = Some((span, waiter)); | ||
| ControlFlow::Break(None) | ||
| } else { | ||
| ControlFlow::Continue(()) | ||
| } | ||
| }); | ||
|
|
||
| waiter_on_cycle.map(|waiter_on_cycle| EntryPoint { | ||
| query_in_cycle, | ||
| waiter: Some(waiter_on_cycle), | ||
| }) | ||
| let mut entrypoint = false; | ||
| let mut query_waiting_on_cycle = None; | ||
|
|
||
| // Find a direct waiter who leads to the root | ||
| for abstracted_waiter in abstracted_waiters_of(job_map, query_in_cycle) { | ||
| let Some(parent) = abstracted_waiter.parent else { | ||
| // The query in the cycle is directly connected to root. | ||
| entrypoint = true; | ||
| continue; | ||
| }; | ||
|
|
||
| // Mark all the other queries in the cycle as already visited, | ||
| // so paths to the root through the cycle itself won't count. | ||
| let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1)); | ||
|
|
||
| if connected_to_root(job_map, parent, &mut visited) { | ||
| query_waiting_on_cycle = Some((abstracted_waiter.span, parent)); | ||
| entrypoint = true; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| entrypoint.then_some(EntryPoint { query_in_cycle, query_waiting_on_cycle }) | ||
| }) | ||
| .collect::<Vec<EntryPoint>>(); | ||
|
|
||
| // Pick an entry point, preferring ones with waiters | ||
| let entry_point = entry_points | ||
| .iter() | ||
| .find(|entry_point| entry_point.waiter.is_some()) | ||
| .find(|entry_point| entry_point.query_waiting_on_cycle.is_some()) | ||
| .unwrap_or(&entry_points[0]); | ||
|
|
||
| // Shift the stack so that our entry point is first | ||
|
|
@@ -289,7 +310,9 @@ fn remove_cycle<'tcx>( | |
| stack.rotate_left(pos); | ||
| } | ||
|
|
||
| let usage = entry_point.waiter.map(|(span, job)| (span, job_map.frame_of(job).clone())); | ||
| let usage = entry_point | ||
| .query_waiting_on_cycle | ||
| .map(|(span, job)| (span, job_map.frame_of(job).clone())); | ||
|
|
||
| // Create the cycle error | ||
| let error = CycleError { | ||
|
|
@@ -300,9 +323,9 @@ fn remove_cycle<'tcx>( | |
| .collect(), | ||
| }; | ||
|
|
||
| // We unwrap `waiter` here since there must always be one | ||
| // We unwrap `resumable` here since there must always be one | ||
| // edge which is resumable / waited using a query latch | ||
| let (waitee_query, waiter_idx) = waiter.unwrap(); | ||
| let (waitee_query, waiter_idx) = resumable.unwrap(); | ||
|
|
||
| // Extract the waiter we want to resume | ||
| let waiter = job_map.latch_of(waitee_query).unwrap().extract_waiter(waiter_idx); | ||
|
|
||
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 now have
QueryWaiterandWaiter. And alsoQueryJobInfoandQueryInfo. These structs with similar names and contents are hard to remember.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.
Also, the variable name
waiternow sometimes means aQueryWaiter, sometimes aWaiter, and sometimes aQueryJobId. This gets quite confusing. It would be good to have separate names. E.g. sometimeswaiter_queryis used for aQueryJobIdthat comes fromwaiter.query, which is better.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.
Waiterrepresents graph edges, so we could use graph terminology for contrast here.