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