Skip to content

Commit 2049f02

Browse files
committed
Replace visit_waiters with waiters_of
1 parent d3877ec commit 2049f02

1 file changed

Lines changed: 81 additions & 63 deletions

File tree

  • compiler/rustc_query_impl/src

compiler/rustc_query_impl/src/job.rs

Lines changed: 81 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -113,38 +113,41 @@ 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+
struct Waiter {
121+
span: Span,
122+
/// The query which we are waiting from, if none the waiter is from a compiler root.
123+
query: Option<QueryJobId>,
124+
resumable: Option<ResumableWaiterLocation>,
125+
}
136126

137-
// Visit the explicit waiters which use condvars and are resumable
127+
/// Returns all the non-resumable and resumable waiters of a query.
128+
/// This is used so we can uniformly loop over both non-resumable and resumable waiters.
129+
fn waiters_of(job_map: &QueryJobMap<'_>, query: QueryJobId) -> Vec<Waiter> {
130+
let mut result = Vec::new();
131+
132+
// Add the parent which is a non-resumable waiter since it's on the same stack
133+
result.push(Waiter {
134+
span: job_map.span_of(query),
135+
query: job_map.parent_of(query),
136+
resumable: None,
137+
});
138+
139+
// Add the explicit waiters which use condvars and are resumable
138140
if let Some(latch) = job_map.latch_of(query) {
139141
for (i, waiter) in latch.info.lock().waiters.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-
}
142+
result.push(Waiter {
143+
span: waiter.span,
144+
query: waiter.query,
145+
resumable: Some((query, i)),
146+
});
144147
}
145148
}
146149

147-
ControlFlow::Continue(())
150+
result
148151
}
149152

150153
/// Look for query cycles by doing a depth first search starting at `query`.
@@ -157,7 +160,7 @@ fn cycle_check<'tcx>(
157160
span: Span,
158161
stack: &mut Vec<(Span, QueryJobId)>,
159162
visited: &mut FxHashSet<QueryJobId>,
160-
) -> ControlFlow<Option<Waiter>> {
163+
) -> ControlFlow<Option<ResumableWaiterLocation>> {
161164
if !visited.insert(query) {
162165
return if let Some(p) = stack.iter().position(|q| q.1 == query) {
163166
// We detected a query cycle, fix up the initial span and return Some
@@ -176,16 +179,23 @@ fn cycle_check<'tcx>(
176179
stack.push((span, query));
177180

178181
// 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();
182+
for waiter in waiters_of(job_map, query) {
183+
let Some(waiter_query) = waiter.query else {
184+
// Skip waiters which are not queries
185+
continue;
186+
};
187+
if let ControlFlow::Break(maybe_waiter) =
188+
cycle_check(job_map, waiter_query, waiter.span, stack, visited)
189+
{
190+
// Return the resumable waiter in `waiter.resumable` if present
191+
return ControlFlow::Break(waiter.resumable.or(maybe_waiter));
192+
}
186193
}
187194

188-
r
195+
// Remove the entry in our stack since we didn't find a cycle
196+
stack.pop();
197+
198+
ControlFlow::Continue(())
189199
}
190200

191201
/// Finds out if there's a path to the compiler root (aka. code which isn't in a query)
@@ -195,18 +205,26 @@ fn connected_to_root<'tcx>(
195205
job_map: &QueryJobMap<'tcx>,
196206
query: QueryJobId,
197207
visited: &mut FxHashSet<QueryJobId>,
198-
) -> ControlFlow<Option<Waiter>> {
208+
) -> bool {
199209
// We already visited this or we're deliberately ignoring it
200210
if !visited.insert(query) {
201-
return ControlFlow::Continue(());
211+
return false;
202212
}
203213

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);
214+
// Visit all the waiters
215+
for waiter in waiters_of(job_map, query) {
216+
match waiter.query {
217+
// This query is connected to the root
218+
None => return true,
219+
Some(waiter) => {
220+
if connected_to_root(job_map, waiter, visited) {
221+
return true;
222+
}
223+
}
224+
}
207225
}
208226

209-
visit_waiters(job_map, query, |_, successor| connected_to_root(job_map, successor, visited))
227+
false
210228
}
211229

212230
/// Looks for query cycles starting from the last query in `jobs`.
@@ -252,29 +270,29 @@ fn remove_cycle<'tcx>(
252270
let entry_points = stack
253271
.iter()
254272
.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-
})
273+
let mut entrypoint = false;
274+
let mut found_waiter = None;
275+
276+
// Find a direct waiter who leads to the root
277+
for waiter in waiters_of(job_map, query_in_cycle) {
278+
let Some(waiter_query) = waiter.query else {
279+
// The query in the cycle is directly connected to root.
280+
entrypoint = true;
281+
continue;
282+
};
283+
284+
// Mark all the other queries in the cycle as already visited,
285+
// so paths to the root through the cycle itself won't count.
286+
let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1));
287+
288+
if connected_to_root(job_map, waiter_query, &mut visited) {
289+
found_waiter = Some((waiter.span, waiter_query));
290+
entrypoint = true;
291+
break;
292+
}
277293
}
294+
295+
entrypoint.then_some(EntryPoint { query_in_cycle, waiter: found_waiter })
278296
})
279297
.collect::<Vec<EntryPoint>>();
280298

0 commit comments

Comments
 (0)