Skip to content

Commit ed07376

Browse files
committed
Replace visit_waiters with abstracted_waiters_of
1 parent 4efe3dc commit ed07376

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
@@ -71,7 +71,7 @@ impl<'tcx> QueryJob<'tcx> {
7171

7272
#[derive(Debug)]
7373
pub struct QueryWaiter<'tcx> {
74-
pub query: Option<QueryJobId>,
74+
pub parent: Option<QueryJobId>,
7575
pub condvar: Condvar,
7676
pub span: Span,
7777
pub cycle: Mutex<Option<CycleError<QueryStackDeferred<'tcx>>>>,
@@ -100,8 +100,12 @@ impl<'tcx> QueryLatch<'tcx> {
100100
return Ok(()); // already complete
101101
};
102102

103-
let waiter =
104-
Arc::new(QueryWaiter { query, span, cycle: Mutex::new(None), condvar: Condvar::new() });
103+
let waiter = Arc::new(QueryWaiter {
104+
parent: query,
105+
span,
106+
cycle: Mutex::new(None),
107+
condvar: Condvar::new(),
108+
});
105109

106110
// We push the waiter on to the `waiters` list. It can be accessed inside
107111
// 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
@@ -113,38 +113,44 @@ pub(crate) fn find_dep_kind_root<'tcx>(
113113
last_layout
114114
}
115115

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

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

147-
ControlFlow::Continue(())
153+
result
148154
}
149155

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

165171
// Remove previous stack entries
166-
stack.drain(0..p);
172+
stack.drain(0..pos);
167173
// Replace the span for the first query with the cycle cause
168174
stack[0].0 = span;
169175
ControlFlow::Break(None)
@@ -176,16 +182,23 @@ fn cycle_check<'tcx>(
176182
stack.push((span, query));
177183

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

188-
r
198+
// Remove the entry in our stack since we didn't find a cycle
199+
stack.pop();
200+
201+
ControlFlow::Continue(())
189202
}
190203

191204
/// Finds out if there's a path to the compiler root (aka. code which isn't in a query)
@@ -195,18 +208,26 @@ fn connected_to_root<'tcx>(
195208
job_map: &QueryJobMap<'tcx>,
196209
query: QueryJobId,
197210
visited: &mut FxHashSet<QueryJobId>,
198-
) -> ControlFlow<Option<Waiter>> {
211+
) -> bool {
199212
// We already visited this or we're deliberately ignoring it
200213
if !visited.insert(query) {
201-
return ControlFlow::Continue(());
214+
return false;
202215
}
203216

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

209-
visit_waiters(job_map, query, |_, successor| connected_to_root(job_map, successor, visited))
230+
false
210231
}
211232

212233
/// Looks for query cycles starting from the last query in `jobs`.
@@ -222,7 +243,7 @@ fn remove_cycle<'tcx>(
222243
let mut visited = FxHashSet::default();
223244
let mut stack = Vec::new();
224245
// Look for a cycle starting with the last query in `jobs`
225-
if let ControlFlow::Break(waiter) =
246+
if let ControlFlow::Break(resumable) =
226247
cycle_check(job_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited)
227248
{
228249
// The stack is a vector of pairs of spans and queries; reverse it so that
@@ -244,44 +265,44 @@ fn remove_cycle<'tcx>(
244265

245266
struct EntryPoint {
246267
query_in_cycle: QueryJobId,
247-
waiter: Option<(Span, QueryJobId)>,
268+
query_waiting_on_cycle: Option<(Span, QueryJobId)>,
248269
}
249270

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

281302
// Pick an entry point, preferring ones with waiters
282303
let entry_point = entry_points
283304
.iter()
284-
.find(|entry_point| entry_point.waiter.is_some())
305+
.find(|entry_point| entry_point.query_waiting_on_cycle.is_some())
285306
.unwrap_or(&entry_points[0]);
286307

287308
// Shift the stack so that our entry point is first
@@ -291,7 +312,9 @@ fn remove_cycle<'tcx>(
291312
stack.rotate_left(pos);
292313
}
293314

294-
let usage = entry_point.waiter.map(|(span, job)| (span, job_map.frame_of(job).clone()));
315+
let usage = entry_point
316+
.query_waiting_on_cycle
317+
.map(|(span, job)| (span, job_map.frame_of(job).clone()));
295318

296319
// Create the cycle error
297320
let error = CycleError {
@@ -302,9 +325,9 @@ fn remove_cycle<'tcx>(
302325
.collect(),
303326
};
304327

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

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

0 commit comments

Comments
 (0)