@@ -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