use crate::dep_graph::DepContext;
use crate::query::plumbing::CycleError;
use crate::query::{QueryContext, QueryStackFrame};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{struct_span_err, Diagnostic, DiagnosticBuilder, Handler, Level};
use rustc_session::Session;
use rustc_span::Span;
use std::convert::TryFrom;
use std::hash::Hash;
use std::marker::PhantomData;
use std::num::NonZeroU32;
#[cfg(parallel_compiler)]
use {
crate::dep_graph::DepKind,
parking_lot::{Condvar, Mutex},
rustc_data_structures::fx::FxHashSet,
rustc_data_structures::sync::Lock,
rustc_data_structures::sync::Lrc,
rustc_data_structures::{jobserver, OnDrop},
rustc_rayon_core as rayon_core,
rustc_span::DUMMY_SP,
std::iter::FromIterator,
std::{mem, process},
};
#[derive(Clone, Debug)]
pub struct QueryInfo {
pub span: Span,
pub query: QueryStackFrame,
}
pub type QueryMap<D> = FxHashMap<QueryJobId<D>, QueryJobInfo<D>>;
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct QueryShardJobId(pub NonZeroU32);
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct QueryJobId<D> {
pub job: QueryShardJobId,
pub shard: u16,
pub kind: D,
}
impl<D> QueryJobId<D>
where
D: Copy + Clone + Eq + Hash,
{
pub fn new(job: QueryShardJobId, shard: usize, kind: D) -> Self {
QueryJobId { job, shard: u16::try_from(shard).unwrap(), kind }
}
fn query(self, map: &QueryMap<D>) -> QueryStackFrame {
map.get(&self).unwrap().info.query.clone()
}
#[cfg(parallel_compiler)]
fn span(self, map: &QueryMap<D>) -> Span {
map.get(&self).unwrap().job.span
}
#[cfg(parallel_compiler)]
fn parent(self, map: &QueryMap<D>) -> Option<QueryJobId<D>> {
map.get(&self).unwrap().job.parent
}
#[cfg(parallel_compiler)]
fn latch<'a>(self, map: &'a QueryMap<D>) -> Option<&'a QueryLatch<D>> {
map.get(&self).unwrap().job.latch.as_ref()
}
}
pub struct QueryJobInfo<D> {
pub info: QueryInfo,
pub job: QueryJob<D>,
}
#[derive(Clone)]
pub struct QueryJob<D> {
pub id: QueryShardJobId,
pub span: Span,
pub parent: Option<QueryJobId<D>>,
#[cfg(parallel_compiler)]
latch: Option<QueryLatch<D>>,
dummy: PhantomData<QueryLatch<D>>,
}
impl<D> QueryJob<D>
where
D: Copy + Clone + Eq + Hash,
{
pub fn new(id: QueryShardJobId, span: Span, parent: Option<QueryJobId<D>>) -> Self {
QueryJob {
id,
span,
parent,
#[cfg(parallel_compiler)]
latch: None,
dummy: PhantomData,
}
}
#[cfg(parallel_compiler)]
pub(super) fn latch(&mut self, _id: QueryJobId<D>) -> QueryLatch<D> {
if self.latch.is_none() {
self.latch = Some(QueryLatch::new());
}
self.latch.as_ref().unwrap().clone()
}
#[cfg(not(parallel_compiler))]
pub(super) fn latch(&mut self, id: QueryJobId<D>) -> QueryLatch<D> {
QueryLatch { id }
}
pub fn signal_complete(self) {
#[cfg(parallel_compiler)]
{
if let Some(latch) = self.latch {
latch.set();
}
}
}
}
#[cfg(not(parallel_compiler))]
#[derive(Clone)]
pub(super) struct QueryLatch<D> {
id: QueryJobId<D>,
}
#[cfg(not(parallel_compiler))]
impl<D> QueryLatch<D>
where
D: Copy + Clone + Eq + Hash,
{
pub(super) fn find_cycle_in_stack(
&self,
query_map: QueryMap<D>,
current_job: &Option<QueryJobId<D>>,
span: Span,
) -> CycleError {
let mut cycle = Vec::new();
let mut current_job = Option::clone(current_job);
while let Some(job) = current_job {
let info = query_map.get(&job).unwrap();
cycle.push(info.info.clone());
if job == self.id {
cycle.reverse();
cycle[0].span = span;
let usage = info
.job
.parent
.as_ref()
.map(|parent| (info.info.span, parent.query(&query_map)));
return CycleError { usage, cycle };
}
current_job = info.job.parent;
}
panic!("did not find a cycle")
}
}
#[cfg(parallel_compiler)]
struct QueryWaiter<D> {
query: Option<QueryJobId<D>>,
condvar: Condvar,
span: Span,
cycle: Lock<Option<CycleError>>,
}
#[cfg(parallel_compiler)]
impl<D> QueryWaiter<D> {
fn notify(&self, registry: &rayon_core::Registry) {
rayon_core::mark_unblocked(registry);
self.condvar.notify_one();
}
}
#[cfg(parallel_compiler)]
struct QueryLatchInfo<D> {
complete: bool,
waiters: Vec<Lrc<QueryWaiter<D>>>,
}
#[cfg(parallel_compiler)]
#[derive(Clone)]
pub(super) struct QueryLatch<D> {
info: Lrc<Mutex<QueryLatchInfo<D>>>,
}
#[cfg(parallel_compiler)]
impl<D: Eq + Hash> QueryLatch<D> {
fn new() -> Self {
QueryLatch {
info: Lrc::new(Mutex::new(QueryLatchInfo { complete: false, waiters: Vec::new() })),
}
}
}
#[cfg(parallel_compiler)]
impl<D> QueryLatch<D> {
pub(super) fn wait_on(
&self,
query: Option<QueryJobId<D>>,
span: Span,
) -> Result<(), CycleError> {
let waiter =
Lrc::new(QueryWaiter { query, span, cycle: Lock::new(None), condvar: Condvar::new() });
self.wait_on_inner(&waiter);
let mut cycle = waiter.cycle.lock();
match cycle.take() {
None => Ok(()),
Some(cycle) => Err(cycle),
}
}
fn wait_on_inner(&self, waiter: &Lrc<QueryWaiter<D>>) {
let mut info = self.info.lock();
if !info.complete {
info.waiters.push(waiter.clone());
rayon_core::mark_blocked();
jobserver::release_thread();
waiter.condvar.wait(&mut info);
mem::drop(info);
jobserver::acquire_thread();
}
}
fn set(&self) {
let mut info = self.info.lock();
debug_assert!(!info.complete);
info.complete = true;
let registry = rayon_core::Registry::current();
for waiter in info.waiters.drain(..) {
waiter.notify(®istry);
}
}
fn extract_waiter(&self, waiter: usize) -> Lrc<QueryWaiter<D>> {
let mut info = self.info.lock();
debug_assert!(!info.complete);
info.waiters.remove(waiter)
}
}
#[cfg(parallel_compiler)]
type Waiter<D> = (QueryJobId<D>, usize);
#[cfg(parallel_compiler)]
fn visit_waiters<D, F>(
query_map: &QueryMap<D>,
query: QueryJobId<D>,
mut visit: F,
) -> Option<Option<Waiter<D>>>
where
D: Copy + Clone + Eq + Hash,
F: FnMut(Span, QueryJobId<D>) -> Option<Option<Waiter<D>>>,
{
if let Some(parent) = query.parent(query_map) {
if let Some(cycle) = visit(query.span(query_map), parent) {
return Some(cycle);
}
}
if let Some(latch) = query.latch(query_map) {
for (i, waiter) in latch.info.lock().waiters.iter().enumerate() {
if let Some(waiter_query) = waiter.query {
if visit(waiter.span, waiter_query).is_some() {
return Some(Some((query, i)));
}
}
}
}
None
}
#[cfg(parallel_compiler)]
fn cycle_check<D>(
query_map: &QueryMap<D>,
query: QueryJobId<D>,
span: Span,
stack: &mut Vec<(Span, QueryJobId<D>)>,
visited: &mut FxHashSet<QueryJobId<D>>,
) -> Option<Option<Waiter<D>>>
where
D: Copy + Clone + Eq + Hash,
{
if !visited.insert(query) {
return if let Some(p) = stack.iter().position(|q| q.1 == query) {
stack.drain(0..p);
stack[0].0 = span;
Some(None)
} else {
None
};
}
stack.push((span, query));
let r = visit_waiters(query_map, query, |span, successor| {
cycle_check(query_map, successor, span, stack, visited)
});
if r.is_none() {
stack.pop();
}
r
}
#[cfg(parallel_compiler)]
fn connected_to_root<D>(
query_map: &QueryMap<D>,
query: QueryJobId<D>,
visited: &mut FxHashSet<QueryJobId<D>>,
) -> bool
where
D: Copy + Clone + Eq + Hash,
{
if !visited.insert(query) {
return false;
}
if query.parent(query_map).is_none() {
return true;
}
visit_waiters(query_map, query, |_, successor| {
connected_to_root(query_map, successor, visited).then_some(None)
})
.is_some()
}
#[cfg(parallel_compiler)]
fn pick_query<'a, D, T, F>(query_map: &QueryMap<D>, queries: &'a [T], f: F) -> &'a T
where
D: Copy + Clone + Eq + Hash,
F: Fn(&T) -> (Span, QueryJobId<D>),
{
queries
.iter()
.min_by_key(|v| {
let (span, query) = f(v);
let hash = query.query(query_map).hash;
let span_cmp = if span == DUMMY_SP { 1 } else { 0 };
(span_cmp, hash)
})
.unwrap()
}
#[cfg(parallel_compiler)]
fn remove_cycle<D: DepKind>(
query_map: &QueryMap<D>,
jobs: &mut Vec<QueryJobId<D>>,
wakelist: &mut Vec<Lrc<QueryWaiter<D>>>,
) -> bool {
let mut visited = FxHashSet::default();
let mut stack = Vec::new();
if let Some(waiter) =
cycle_check(query_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited)
{
let (mut spans, queries): (Vec<_>, Vec<_>) = stack.into_iter().rev().unzip();
spans.rotate_right(1);
let mut stack: Vec<_> = spans.into_iter().zip(queries).collect();
for r in &stack {
if let Some(pos) = jobs.iter().position(|j| j == &r.1) {
jobs.remove(pos);
}
}
let entry_points = stack
.iter()
.filter_map(|&(span, query)| {
if query.parent(query_map).is_none() {
Some((span, query, None))
} else {
let mut waiters = Vec::new();
visit_waiters(query_map, query, |span, waiter| {
let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1));
if connected_to_root(query_map, waiter, &mut visited) {
waiters.push((span, waiter));
}
None
});
if waiters.is_empty() {
None
} else {
let waiter = *pick_query(query_map, &waiters, |s| *s);
Some((span, query, Some(waiter)))
}
}
})
.collect::<Vec<(Span, QueryJobId<D>, Option<(Span, QueryJobId<D>)>)>>();
let (_, entry_point, usage) = pick_query(query_map, &entry_points, |e| (e.0, e.1));
let entry_point_pos = stack.iter().position(|(_, query)| query == entry_point);
if let Some(pos) = entry_point_pos {
stack.rotate_left(pos);
}
let usage = usage.as_ref().map(|(span, query)| (*span, query.query(query_map)));
let error = CycleError {
usage,
cycle: stack
.iter()
.map(|&(s, ref q)| QueryInfo { span: s, query: q.query(query_map) })
.collect(),
};
let (waitee_query, waiter_idx) = waiter.unwrap();
let waiter = waitee_query.latch(query_map).unwrap().extract_waiter(waiter_idx);
*waiter.cycle.lock() = Some(error);
wakelist.push(waiter);
true
} else {
false
}
}
#[cfg(parallel_compiler)]
pub fn deadlock<CTX: QueryContext>(tcx: CTX, registry: &rayon_core::Registry) {
let on_panic = OnDrop(|| {
eprintln!("deadlock handler panicked, aborting process");
process::abort();
});
let mut wakelist = Vec::new();
let query_map = tcx.try_collect_active_jobs().unwrap();
let mut jobs: Vec<QueryJobId<CTX::DepKind>> = query_map.keys().cloned().collect();
let mut found_cycle = false;
while jobs.len() > 0 {
if remove_cycle(&query_map, &mut jobs, &mut wakelist) {
found_cycle = true;
}
}
assert!(found_cycle);
for waiter in wakelist.into_iter() {
waiter.notify(registry);
}
on_panic.disable();
}
#[inline(never)]
#[cold]
pub(crate) fn report_cycle<'a>(
sess: &'a Session,
CycleError { usage, cycle: stack }: CycleError,
) -> DiagnosticBuilder<'a> {
assert!(!stack.is_empty());
let fix_span = |span: Span, query: &QueryStackFrame| {
sess.source_map().guess_head_span(query.default_span(span))
};
let span = fix_span(stack[1 % stack.len()].span, &stack[0].query);
let mut err =
struct_span_err!(sess, span, E0391, "cycle detected when {}", stack[0].query.description);
for i in 1..stack.len() {
let query = &stack[i].query;
let span = fix_span(stack[(i + 1) % stack.len()].span, query);
err.span_note(span, &format!("...which requires {}...", query.description));
}
err.note(&format!(
"...which again requires {}, completing the cycle",
stack[0].query.description
));
if let Some((span, query)) = usage {
err.span_note(fix_span(span, &query), &format!("cycle used when {}", query.description));
}
err
}
pub fn print_query_stack<CTX: QueryContext>(
tcx: CTX,
mut current_query: Option<QueryJobId<CTX::DepKind>>,
handler: &Handler,
num_frames: Option<usize>,
) -> usize {
let mut i = 0;
let query_map = tcx.try_collect_active_jobs();
while let Some(query) = current_query {
if Some(i) == num_frames {
break;
}
let query_info = if let Some(info) = query_map.as_ref().and_then(|map| map.get(&query)) {
info
} else {
break;
};
let mut diag = Diagnostic::new(
Level::FailureNote,
&format!(
"#{} [{}] {}",
i, query_info.info.query.name, query_info.info.query.description
),
);
diag.span =
tcx.dep_context().sess().source_map().guess_head_span(query_info.info.span).into();
handler.force_print_diagnostic(diag);
current_query = query_info.job.parent;
i += 1;
}
i
}