| use std::fmt::Debug; |
| use std::hash::Hash; |
| use std::num::NonZero; |
| use std::sync::Arc; |
| |
| use parking_lot::{Condvar, Mutex}; |
| use rustc_errors::Diag; |
| use rustc_hir::def::DefKind; |
| use rustc_session::Session; |
| use rustc_span::Span; |
| |
| use super::{QueryStackDeferred, QueryStackFrameExtra}; |
| use crate::error::CycleStack; |
| use crate::query::plumbing::CycleError; |
| use crate::query::{QueryContext, QueryStackFrame}; |
| |
| /// Represents a span and a query key. |
| #[derive(Clone, Debug)] |
| pub struct QueryInfo<I> { |
| /// The span corresponding to the reason for which this query was required. |
| pub span: Span, |
| pub frame: QueryStackFrame<I>, |
| } |
| |
| impl<'tcx> QueryInfo<QueryStackDeferred<'tcx>> { |
| pub(crate) fn lift(&self) -> QueryInfo<QueryStackFrameExtra> { |
| QueryInfo { span: self.span, frame: self.frame.lift() } |
| } |
| } |
| |
| /// A value uniquely identifying an active query job. |
| #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] |
| pub struct QueryJobId(pub NonZero<u64>); |
| |
| /// Represents an active query job. |
| #[derive(Clone, Debug)] |
| pub struct QueryJob<'tcx> { |
| pub id: QueryJobId, |
| |
| /// The span corresponding to the reason for which this query was required. |
| pub span: Span, |
| |
| /// The parent query job which created this job and is implicitly waiting on it. |
| pub parent: Option<QueryJobId>, |
| |
| /// The latch that is used to wait on this job. |
| pub latch: Option<QueryLatch<'tcx>>, |
| } |
| |
| impl<'tcx> QueryJob<'tcx> { |
| /// Creates a new query job. |
| #[inline] |
| pub fn new(id: QueryJobId, span: Span, parent: Option<QueryJobId>) -> Self { |
| QueryJob { id, span, parent, latch: None } |
| } |
| |
| pub fn latch(&mut self) -> QueryLatch<'tcx> { |
| if self.latch.is_none() { |
| self.latch = Some(QueryLatch::new()); |
| } |
| self.latch.as_ref().unwrap().clone() |
| } |
| |
| /// Signals to waiters that the query is complete. |
| /// |
| /// This does nothing for single threaded rustc, |
| /// as there are no concurrent jobs which could be waiting on us |
| #[inline] |
| pub fn signal_complete(self) { |
| if let Some(latch) = self.latch { |
| latch.set(); |
| } |
| } |
| } |
| |
| #[derive(Debug)] |
| pub struct QueryWaiter<'tcx> { |
| pub query: Option<QueryJobId>, |
| pub condvar: Condvar, |
| pub span: Span, |
| pub cycle: Mutex<Option<CycleError<QueryStackDeferred<'tcx>>>>, |
| } |
| |
| #[derive(Debug)] |
| pub struct QueryLatchInfo<'tcx> { |
| pub complete: bool, |
| pub waiters: Vec<Arc<QueryWaiter<'tcx>>>, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct QueryLatch<'tcx> { |
| pub info: Arc<Mutex<QueryLatchInfo<'tcx>>>, |
| } |
| |
| impl<'tcx> QueryLatch<'tcx> { |
| fn new() -> Self { |
| QueryLatch { |
| info: Arc::new(Mutex::new(QueryLatchInfo { complete: false, waiters: Vec::new() })), |
| } |
| } |
| |
| /// Awaits for the query job to complete. |
| pub fn wait_on( |
| &self, |
| qcx: impl QueryContext<'tcx>, |
| query: Option<QueryJobId>, |
| span: Span, |
| ) -> Result<(), CycleError<QueryStackDeferred<'tcx>>> { |
| let waiter = |
| Arc::new(QueryWaiter { query, span, cycle: Mutex::new(None), condvar: Condvar::new() }); |
| self.wait_on_inner(qcx, &waiter); |
| // FIXME: Get rid of this lock. We have ownership of the QueryWaiter |
| // although another thread may still have a Arc reference so we cannot |
| // use Arc::get_mut |
| let mut cycle = waiter.cycle.lock(); |
| match cycle.take() { |
| None => Ok(()), |
| Some(cycle) => Err(cycle), |
| } |
| } |
| |
| /// Awaits the caller on this latch by blocking the current thread. |
| fn wait_on_inner(&self, qcx: impl QueryContext<'tcx>, waiter: &Arc<QueryWaiter<'tcx>>) { |
| let mut info = self.info.lock(); |
| if !info.complete { |
| // We push the waiter on to the `waiters` list. It can be accessed inside |
| // the `wait` call below, by 1) the `set` method or 2) by deadlock detection. |
| // Both of these will remove it from the `waiters` list before resuming |
| // this thread. |
| info.waiters.push(Arc::clone(waiter)); |
| |
| // If this detects a deadlock and the deadlock handler wants to resume this thread |
| // we have to be in the `wait` call. This is ensured by the deadlock handler |
| // getting the self.info lock. |
| rustc_thread_pool::mark_blocked(); |
| let proxy = qcx.jobserver_proxy(); |
| proxy.release_thread(); |
| waiter.condvar.wait(&mut info); |
| // Release the lock before we potentially block in `acquire_thread` |
| drop(info); |
| proxy.acquire_thread(); |
| } |
| } |
| |
| /// Sets the latch and resumes all waiters on it |
| fn set(&self) { |
| let mut info = self.info.lock(); |
| debug_assert!(!info.complete); |
| info.complete = true; |
| let registry = rustc_thread_pool::Registry::current(); |
| for waiter in info.waiters.drain(..) { |
| rustc_thread_pool::mark_unblocked(®istry); |
| waiter.condvar.notify_one(); |
| } |
| } |
| |
| /// Removes a single waiter from the list of waiters. |
| /// This is used to break query cycles. |
| pub fn extract_waiter(&self, waiter: usize) -> Arc<QueryWaiter<'tcx>> { |
| let mut info = self.info.lock(); |
| debug_assert!(!info.complete); |
| // Remove the waiter from the list of waiters |
| info.waiters.remove(waiter) |
| } |
| } |
| |
| #[inline(never)] |
| #[cold] |
| pub fn report_cycle<'a>( |
| sess: &'a Session, |
| CycleError { usage, cycle: stack }: &CycleError, |
| ) -> Diag<'a> { |
| assert!(!stack.is_empty()); |
| |
| let span = stack[0].frame.info.default_span(stack[1 % stack.len()].span); |
| |
| let mut cycle_stack = Vec::new(); |
| |
| use crate::error::StackCount; |
| let stack_count = if stack.len() == 1 { StackCount::Single } else { StackCount::Multiple }; |
| |
| for i in 1..stack.len() { |
| let frame = &stack[i].frame; |
| let span = frame.info.default_span(stack[(i + 1) % stack.len()].span); |
| cycle_stack.push(CycleStack { span, desc: frame.info.description.to_owned() }); |
| } |
| |
| let mut cycle_usage = None; |
| if let Some((span, ref query)) = *usage { |
| cycle_usage = Some(crate::error::CycleUsage { |
| span: query.info.default_span(span), |
| usage: query.info.description.to_string(), |
| }); |
| } |
| |
| let alias = |
| if stack.iter().all(|entry| matches!(entry.frame.info.def_kind, Some(DefKind::TyAlias))) { |
| Some(crate::error::Alias::Ty) |
| } else if stack.iter().all(|entry| entry.frame.info.def_kind == Some(DefKind::TraitAlias)) { |
| Some(crate::error::Alias::Trait) |
| } else { |
| None |
| }; |
| |
| let cycle_diag = crate::error::Cycle { |
| span, |
| cycle_stack, |
| stack_bottom: stack[0].frame.info.description.to_owned(), |
| alias, |
| cycle_usage, |
| stack_count, |
| note_span: (), |
| }; |
| |
| sess.dcx().create_err(cycle_diag) |
| } |