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