Skip to content

Commit 1d49de5

Browse files
authored
Rollup merge of #153376 - Zoxc:cycle-waiters-iter, r=nnethercote
Replace `visit_waiters` with `abstracted_waiters_of` This replaces `visit_waiters` which uses closures to visit waiters with `abstracted_waiters_of` which returns a list of waiters. This makes the control flow of their users a bit clearer. Additionally `abstracted_waiters_of` includes non-query waiters unlike `visit_waiters`. This means that `connected_to_root` now considers resumable waiters from the compiler root as being connected to root, while previously only non-resumable waiters counted. This can result in some additional entry points being found. Similarly in the loop detecting entry points we now consider queries in the cycle with direct resumable waiters from the compiler root as being entry points. When looking for entry points we now look at waiters until we found a query to populate the `EntryPoint.waiter` field instead of stopping when we determined it to be an entry point. cc @petrochenkov @nnethercote
2 parents 76b769c + 1f44a11 commit 1d49de5

2 files changed

Lines changed: 101 additions & 74 deletions

File tree

  • compiler

compiler/rustc_middle/src/query/job.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl<'tcx> QueryJob<'tcx> {
6565

6666
#[derive(Debug)]
6767
pub struct QueryWaiter<'tcx> {
68-
pub query: Option<QueryJobId>,
68+
pub parent: Option<QueryJobId>,
6969
pub condvar: Condvar,
7070
pub span: Span,
7171
pub cycle: Mutex<Option<CycleError<'tcx>>>,
@@ -94,8 +94,12 @@ impl<'tcx> QueryLatch<'tcx> {
9494
return Ok(()); // already complete
9595
};
9696

97-
let waiter =
98-
Arc::new(QueryWaiter { query, span, cycle: Mutex::new(None), condvar: Condvar::new() });
97+
let waiter = Arc::new(QueryWaiter {
98+
parent: query,
99+
span,
100+
cycle: Mutex::new(None),
101+
condvar: Condvar::new(),
102+
});
99103

100104
// We push the waiter on to the `waiters` list. It can be accessed inside
101105
// the `wait` call below, by 1) the `set` method or 2) by deadlock detection.

compiler/rustc_query_impl/src/job.rs

Lines changed: 94 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -111,38 +111,44 @@ pub(crate) fn find_dep_kind_root<'tcx>(
111111
last_layout
112112
}
113113

114-
/// A resumable waiter of a query. The usize is the index into waiters in the query's latch
115-
type Waiter = (QueryJobId, usize);
116-
117-
/// Visits all the non-resumable and resumable waiters of a query.
118-
/// Only waiters in a query are visited.
119-
/// `visit` is called for every waiter and is passed a query waiting on `query`
120-
/// and a span indicating the reason the query waited on `query`.
121-
/// If `visit` returns `Break`, this function also returns `Break`,
122-
/// and if all `visit` calls returns `Continue` it also returns `Continue`.
123-
/// For visits of non-resumable waiters it returns the return value of `visit`.
124-
/// For visits of resumable waiters it returns information required to resume that waiter.
125-
fn visit_waiters<'tcx>(
126-
job_map: &QueryJobMap<'tcx>,
127-
query: QueryJobId,
128-
mut visit: impl FnMut(Span, QueryJobId) -> ControlFlow<Option<Waiter>>,
129-
) -> ControlFlow<Option<Waiter>> {
130-
// Visit the parent query which is a non-resumable waiter since it's on the same stack
131-
if let Some(parent) = job_map.parent_of(query) {
132-
visit(job_map.span_of(query), parent)?;
133-
}
114+
/// The locaton of a resumable waiter. The usize is the index into waiters in the query's latch.
115+
/// We'll use this to remove the waiter using `QueryLatch::extract_waiter` if we're waking it up.
116+
type ResumableWaiterLocation = (QueryJobId, usize);
117+
118+
/// This abstracts over non-resumable waiters which are found in `QueryJob`'s `parent` field
119+
/// and resumable waiters are in `latch` field.
120+
struct AbstractedWaiter {
121+
/// The span corresponding to the reason for why we're waiting on this query.
122+
span: Span,
123+
/// The query which we are waiting from, if none the waiter is from a compiler root.
124+
parent: Option<QueryJobId>,
125+
resumable: Option<ResumableWaiterLocation>,
126+
}
127+
128+
/// Returns all the non-resumable and resumable waiters of a query.
129+
/// This is used so we can uniformly loop over both non-resumable and resumable waiters.
130+
fn abstracted_waiters_of(job_map: &QueryJobMap<'_>, query: QueryJobId) -> Vec<AbstractedWaiter> {
131+
let mut result = Vec::new();
134132

135-
// Visit the explicit waiters which use condvars and are resumable
133+
// Add the parent which is a non-resumable waiter since it's on the same stack
134+
result.push(AbstractedWaiter {
135+
span: job_map.span_of(query),
136+
parent: job_map.parent_of(query),
137+
resumable: None,
138+
});
139+
140+
// Add the explicit waiters which use condvars and are resumable
136141
if let Some(latch) = job_map.latch_of(query) {
137142
for (i, waiter) in latch.waiters.lock().as_ref().unwrap().iter().enumerate() {
138-
if let Some(waiter_query) = waiter.query {
139-
// Return a value which indicates that this waiter can be resumed
140-
visit(waiter.span, waiter_query).map_break(|_| Some((query, i)))?;
141-
}
143+
result.push(AbstractedWaiter {
144+
span: waiter.span,
145+
parent: waiter.parent,
146+
resumable: Some((query, i)),
147+
});
142148
}
143149
}
144150

145-
ControlFlow::Continue(())
151+
result
146152
}
147153

148154
/// Look for query cycles by doing a depth first search starting at `query`.
@@ -155,13 +161,13 @@ fn cycle_check<'tcx>(
155161
span: Span,
156162
stack: &mut Vec<(Span, QueryJobId)>,
157163
visited: &mut FxHashSet<QueryJobId>,
158-
) -> ControlFlow<Option<Waiter>> {
164+
) -> ControlFlow<Option<ResumableWaiterLocation>> {
159165
if !visited.insert(query) {
160-
return if let Some(p) = stack.iter().position(|q| q.1 == query) {
166+
return if let Some(pos) = stack.iter().position(|q| q.1 == query) {
161167
// We detected a query cycle, fix up the initial span and return Some
162168

163169
// Remove previous stack entries
164-
stack.drain(0..p);
170+
stack.drain(0..pos);
165171
// Replace the span for the first query with the cycle cause
166172
stack[0].0 = span;
167173
ControlFlow::Break(None)
@@ -174,16 +180,23 @@ fn cycle_check<'tcx>(
174180
stack.push((span, query));
175181

176182
// Visit all the waiters
177-
let r = visit_waiters(job_map, query, |span, successor| {
178-
cycle_check(job_map, successor, span, stack, visited)
179-
});
180-
181-
// Remove the entry in our stack if we didn't find a cycle
182-
if r.is_continue() {
183-
stack.pop();
183+
for abstracted_waiter in abstracted_waiters_of(job_map, query) {
184+
let Some(parent) = abstracted_waiter.parent else {
185+
// Skip waiters which are not queries
186+
continue;
187+
};
188+
if let ControlFlow::Break(maybe_resumable) =
189+
cycle_check(job_map, parent, abstracted_waiter.span, stack, visited)
190+
{
191+
// Return the resumable waiter in `waiter.resumable` if present
192+
return ControlFlow::Break(abstracted_waiter.resumable.or(maybe_resumable));
193+
}
184194
}
185195

186-
r
196+
// Remove the entry in our stack since we didn't find a cycle
197+
stack.pop();
198+
199+
ControlFlow::Continue(())
187200
}
188201

189202
/// 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>(
193206
job_map: &QueryJobMap<'tcx>,
194207
query: QueryJobId,
195208
visited: &mut FxHashSet<QueryJobId>,
196-
) -> ControlFlow<Option<Waiter>> {
209+
) -> bool {
197210
// We already visited this or we're deliberately ignoring it
198211
if !visited.insert(query) {
199-
return ControlFlow::Continue(());
212+
return false;
200213
}
201214

202-
// This query is connected to the root (it has no query parent), return true
203-
if job_map.parent_of(query).is_none() {
204-
return ControlFlow::Break(None);
215+
// Visit all the waiters
216+
for abstracted_waiter in abstracted_waiters_of(job_map, query) {
217+
match abstracted_waiter.parent {
218+
// This query is connected to the root
219+
None => return true,
220+
Some(parent) => {
221+
if connected_to_root(job_map, parent, visited) {
222+
return true;
223+
}
224+
}
225+
}
205226
}
206227

207-
visit_waiters(job_map, query, |_, successor| connected_to_root(job_map, successor, visited))
228+
false
208229
}
209230

210231
/// Looks for query cycles starting from the last query in `jobs`.
@@ -220,7 +241,7 @@ fn remove_cycle<'tcx>(
220241
let mut visited = FxHashSet::default();
221242
let mut stack = Vec::new();
222243
// Look for a cycle starting with the last query in `jobs`
223-
if let ControlFlow::Break(waiter) =
244+
if let ControlFlow::Break(resumable) =
224245
cycle_check(job_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited)
225246
{
226247
// The stack is a vector of pairs of spans and queries; reverse it so that
@@ -242,44 +263,44 @@ fn remove_cycle<'tcx>(
242263

243264
struct EntryPoint {
244265
query_in_cycle: QueryJobId,
245-
waiter: Option<(Span, QueryJobId)>,
266+
query_waiting_on_cycle: Option<(Span, QueryJobId)>,
246267
}
247268

248269
// Find the queries in the cycle which are
249270
// connected to queries outside the cycle
250271
let entry_points = stack
251272
.iter()
252273
.filter_map(|&(_, query_in_cycle)| {
253-
if job_map.parent_of(query_in_cycle).is_none() {
254-
// This query is connected to the root (it has no query parent)
255-
Some(EntryPoint { query_in_cycle, waiter: None })
256-
} else {
257-
let mut waiter_on_cycle = None;
258-
// Find a direct waiter who leads to the root
259-
let _ = visit_waiters(job_map, query_in_cycle, |span, waiter| {
260-
// Mark all the other queries in the cycle as already visited
261-
let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1));
262-
263-
if connected_to_root(job_map, waiter, &mut visited).is_break() {
264-
waiter_on_cycle = Some((span, waiter));
265-
ControlFlow::Break(None)
266-
} else {
267-
ControlFlow::Continue(())
268-
}
269-
});
270-
271-
waiter_on_cycle.map(|waiter_on_cycle| EntryPoint {
272-
query_in_cycle,
273-
waiter: Some(waiter_on_cycle),
274-
})
274+
let mut entrypoint = false;
275+
let mut query_waiting_on_cycle = None;
276+
277+
// Find a direct waiter who leads to the root
278+
for abstracted_waiter in abstracted_waiters_of(job_map, query_in_cycle) {
279+
let Some(parent) = abstracted_waiter.parent else {
280+
// The query in the cycle is directly connected to root.
281+
entrypoint = true;
282+
continue;
283+
};
284+
285+
// Mark all the other queries in the cycle as already visited,
286+
// so paths to the root through the cycle itself won't count.
287+
let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1));
288+
289+
if connected_to_root(job_map, parent, &mut visited) {
290+
query_waiting_on_cycle = Some((abstracted_waiter.span, parent));
291+
entrypoint = true;
292+
break;
293+
}
275294
}
295+
296+
entrypoint.then_some(EntryPoint { query_in_cycle, query_waiting_on_cycle })
276297
})
277298
.collect::<Vec<EntryPoint>>();
278299

279300
// Pick an entry point, preferring ones with waiters
280301
let entry_point = entry_points
281302
.iter()
282-
.find(|entry_point| entry_point.waiter.is_some())
303+
.find(|entry_point| entry_point.query_waiting_on_cycle.is_some())
283304
.unwrap_or(&entry_points[0]);
284305

285306
// Shift the stack so that our entry point is first
@@ -289,7 +310,9 @@ fn remove_cycle<'tcx>(
289310
stack.rotate_left(pos);
290311
}
291312

292-
let usage = entry_point.waiter.map(|(span, job)| (span, job_map.frame_of(job).clone()));
313+
let usage = entry_point
314+
.query_waiting_on_cycle
315+
.map(|(span, job)| (span, job_map.frame_of(job).clone()));
293316

294317
// Create the cycle error
295318
let error = CycleError {
@@ -300,9 +323,9 @@ fn remove_cycle<'tcx>(
300323
.collect(),
301324
};
302325

303-
// We unwrap `waiter` here since there must always be one
326+
// We unwrap `resumable` here since there must always be one
304327
// edge which is resumable / waited using a query latch
305-
let (waitee_query, waiter_idx) = waiter.unwrap();
328+
let (waitee_query, waiter_idx) = resumable.unwrap();
306329

307330
// Extract the waiter we want to resume
308331
let waiter = job_map.latch_of(waitee_query).unwrap().extract_waiter(waiter_idx);

0 commit comments

Comments
 (0)