Rollup merge of #49045 - Zoxc:tls, r=michaelwoerister

Make queries thread safe

This makes queries thread safe by removing the query stack and making queries point to their parents. Queries write to the query map when starting and cycles are detected by checking if there's already an entry in the query map. This makes cycle detection O(1) instead of O(n), where `n` is the size of the query stack.

This is mostly corresponds to the method I described [here](https://internals.rust-lang.org/t/parallelizing-rustc-using-rayon/6606).

cc @rust-lang/compiler

r? @michaelwoerister
This commit is contained in:
Alex Crichton 2018-04-05 10:49:13 -05:00 committed by GitHub
commit b0bd9a771e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 564 additions and 189 deletions

View file

@ -1244,7 +1244,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
Lrc::new(StableVec::new(v))); Lrc::new(StableVec::new(v)));
} }
tls::enter_global(GlobalCtxt { let gcx = &GlobalCtxt {
sess: s, sess: s,
cstore, cstore,
global_arenas: &arenas.global, global_arenas: &arenas.global,
@ -1285,7 +1285,9 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
all_traits: RefCell::new(None), all_traits: RefCell::new(None),
tx_to_llvm_workers: tx, tx_to_llvm_workers: tx,
output_filenames: Arc::new(output_filenames.clone()), output_filenames: Arc::new(output_filenames.clone()),
}, f) };
tls::enter_global(gcx, f)
} }
pub fn consider_optimizing<T: Fn() -> String>(&self, msg: T) -> bool { pub fn consider_optimizing<T: Fn() -> String>(&self, msg: T) -> bool {
@ -1509,11 +1511,28 @@ impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> {
impl<'gcx: 'tcx, 'tcx> GlobalCtxt<'gcx> { impl<'gcx: 'tcx, 'tcx> GlobalCtxt<'gcx> {
/// Call the closure with a local `TyCtxt` using the given arena. /// Call the closure with a local `TyCtxt` using the given arena.
pub fn enter_local<F, R>(&self, arena: &'tcx DroplessArena, f: F) -> R pub fn enter_local<F, R>(
where F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R &self,
arena: &'tcx DroplessArena,
f: F
) -> R
where
F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R
{ {
let interners = CtxtInterners::new(arena); let interners = CtxtInterners::new(arena);
tls::enter(self, &interners, f) let tcx = TyCtxt {
gcx: self,
interners: &interners,
};
ty::tls::with_related_context(tcx.global_tcx(), |icx| {
let new_icx = ty::tls::ImplicitCtxt {
tcx,
query: icx.query.clone(),
};
ty::tls::enter_context(&new_icx, |new_icx| {
f(new_icx.tcx)
})
})
} }
} }
@ -1678,82 +1697,195 @@ impl<'a, 'tcx> Lift<'tcx> for &'a Slice<CanonicalVarInfo> {
} }
pub mod tls { pub mod tls {
use super::{CtxtInterners, GlobalCtxt, TyCtxt}; use super::{GlobalCtxt, TyCtxt};
use std::cell::Cell; use std::cell::Cell;
use std::fmt; use std::fmt;
use std::mem;
use syntax_pos; use syntax_pos;
use ty::maps;
use errors::{Diagnostic, TRACK_DIAGNOSTICS};
use rustc_data_structures::OnDrop;
use rustc_data_structures::sync::Lrc;
/// Marker types used for the scoped TLS slot. /// This is the implicit state of rustc. It contains the current
/// The type context cannot be used directly because the scoped TLS /// TyCtxt and query. It is updated when creating a local interner or
/// in libstd doesn't allow types generic over lifetimes. /// executing a new query. Whenever there's a TyCtxt value available
enum ThreadLocalGlobalCtxt {} /// you should also have access to an ImplicitCtxt through the functions
enum ThreadLocalInterners {} /// in this module.
#[derive(Clone)]
pub struct ImplicitCtxt<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
/// The current TyCtxt. Initially created by `enter_global` and updated
/// by `enter_local` with a new local interner
pub tcx: TyCtxt<'a, 'gcx, 'tcx>,
thread_local! { /// The current query job, if any. This is updated by start_job in
static TLS_TCX: Cell<Option<(*const ThreadLocalGlobalCtxt, /// ty::maps::plumbing when executing a query
*const ThreadLocalInterners)>> = Cell::new(None) pub query: Option<Lrc<maps::QueryJob<'gcx>>>,
} }
// A thread local value which stores a pointer to the current ImplicitCtxt
thread_local!(static TLV: Cell<usize> = Cell::new(0));
fn set_tlv<F: FnOnce() -> R, R>(value: usize, f: F) -> R {
let old = get_tlv();
let _reset = OnDrop(move || TLV.with(|tlv| tlv.set(old)));
TLV.with(|tlv| tlv.set(value));
f()
}
fn get_tlv() -> usize {
TLV.with(|tlv| tlv.get())
}
/// This is a callback from libsyntax as it cannot access the implicit state
/// in librustc otherwise
fn span_debug(span: syntax_pos::Span, f: &mut fmt::Formatter) -> fmt::Result { fn span_debug(span: syntax_pos::Span, f: &mut fmt::Formatter) -> fmt::Result {
with(|tcx| { with(|tcx| {
write!(f, "{}", tcx.sess.codemap().span_to_string(span)) write!(f, "{}", tcx.sess.codemap().span_to_string(span))
}) })
} }
pub fn enter_global<'gcx, F, R>(gcx: GlobalCtxt<'gcx>, f: F) -> R /// This is a callback from libsyntax as it cannot access the implicit state
where F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'gcx>) -> R /// in librustc otherwise. It is used to when diagnostic messages are
/// emitted and stores them in the current query, if there is one.
fn track_diagnostic(diagnostic: &Diagnostic) {
with_context(|context| {
if let Some(ref query) = context.query {
query.diagnostics.lock().push(diagnostic.clone());
}
})
}
/// Sets up the callbacks from libsyntax on the current thread
pub fn with_thread_locals<F, R>(f: F) -> R
where F: FnOnce() -> R
{ {
syntax_pos::SPAN_DEBUG.with(|span_dbg| { syntax_pos::SPAN_DEBUG.with(|span_dbg| {
let original_span_debug = span_dbg.get(); let original_span_debug = span_dbg.get();
span_dbg.set(span_debug); span_dbg.set(span_debug);
let result = enter(&gcx, &gcx.global_interners, f);
span_dbg.set(original_span_debug);
result
})
}
pub fn enter<'a, 'gcx: 'tcx, 'tcx, F, R>(gcx: &'a GlobalCtxt<'gcx>, let _on_drop = OnDrop(move || {
interners: &'a CtxtInterners<'tcx>, span_dbg.set(original_span_debug);
f: F) -> R
where F: FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R
{
let gcx_ptr = gcx as *const _ as *const ThreadLocalGlobalCtxt;
let interners_ptr = interners as *const _ as *const ThreadLocalInterners;
TLS_TCX.with(|tls| {
let prev = tls.get();
tls.set(Some((gcx_ptr, interners_ptr)));
let ret = f(TyCtxt {
gcx,
interners,
}); });
tls.set(prev);
ret
})
}
pub fn with<F, R>(f: F) -> R TRACK_DIAGNOSTICS.with(|current| {
where F: for<'a, 'gcx, 'tcx> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R let original = current.get();
{ current.set(track_diagnostic);
TLS_TCX.with(|tcx| {
let (gcx, interners) = tcx.get().unwrap(); let _on_drop = OnDrop(move || {
let gcx = unsafe { &*(gcx as *const GlobalCtxt) }; current.set(original);
let interners = unsafe { &*(interners as *const CtxtInterners) }; });
f(TyCtxt {
gcx, f()
interners,
}) })
}) })
} }
/// Sets `context` as the new current ImplicitCtxt for the duration of the function `f`
pub fn enter_context<'a, 'gcx: 'tcx, 'tcx, F, R>(context: &ImplicitCtxt<'a, 'gcx, 'tcx>,
f: F) -> R
where F: FnOnce(&ImplicitCtxt<'a, 'gcx, 'tcx>) -> R
{
set_tlv(context as *const _ as usize, || {
f(&context)
})
}
/// Enters GlobalCtxt by setting up libsyntax callbacks and
/// creating a initial TyCtxt and ImplicitCtxt.
/// This happens once per rustc session and TyCtxts only exists
/// inside the `f` function.
pub fn enter_global<'gcx, F, R>(gcx: &GlobalCtxt<'gcx>, f: F) -> R
where F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'gcx>) -> R
{
with_thread_locals(|| {
let tcx = TyCtxt {
gcx,
interners: &gcx.global_interners,
};
let icx = ImplicitCtxt {
tcx,
query: None,
};
enter_context(&icx, |_| {
f(tcx)
})
})
}
/// Allows access to the current ImplicitCtxt in a closure if one is available
pub fn with_context_opt<F, R>(f: F) -> R
where F: for<'a, 'gcx, 'tcx> FnOnce(Option<&ImplicitCtxt<'a, 'gcx, 'tcx>>) -> R
{
let context = get_tlv();
if context == 0 {
f(None)
} else {
unsafe { f(Some(&*(context as *const ImplicitCtxt))) }
}
}
/// Allows access to the current ImplicitCtxt.
/// Panics if there is no ImplicitCtxt available
pub fn with_context<F, R>(f: F) -> R
where F: for<'a, 'gcx, 'tcx> FnOnce(&ImplicitCtxt<'a, 'gcx, 'tcx>) -> R
{
with_context_opt(|opt_context| f(opt_context.expect("no ImplicitCtxt stored in tls")))
}
/// Allows access to the current ImplicitCtxt whose tcx field has the same global
/// interner as the tcx argument passed in. This means the closure is given an ImplicitCtxt
/// with the same 'gcx lifetime as the TyCtxt passed in.
/// This will panic if you pass it a TyCtxt which has a different global interner from
/// the current ImplicitCtxt's tcx field.
pub fn with_related_context<'a, 'gcx, 'tcx1, F, R>(tcx: TyCtxt<'a, 'gcx, 'tcx1>, f: F) -> R
where F: for<'b, 'tcx2> FnOnce(&ImplicitCtxt<'b, 'gcx, 'tcx2>) -> R
{
with_context(|context| {
unsafe {
let gcx = tcx.gcx as *const _ as usize;
assert!(context.tcx.gcx as *const _ as usize == gcx);
let context: &ImplicitCtxt = mem::transmute(context);
f(context)
}
})
}
/// Allows access to the current ImplicitCtxt whose tcx field has the same global
/// interner and local interner as the tcx argument passed in. This means the closure
/// is given an ImplicitCtxt with the same 'tcx and 'gcx lifetimes as the TyCtxt passed in.
/// This will panic if you pass it a TyCtxt which has a different global interner or
/// a different local interner from the current ImplicitCtxt's tcx field.
pub fn with_fully_related_context<'a, 'gcx, 'tcx, F, R>(tcx: TyCtxt<'a, 'gcx, 'tcx>, f: F) -> R
where F: for<'b> FnOnce(&ImplicitCtxt<'b, 'gcx, 'tcx>) -> R
{
with_context(|context| {
unsafe {
let gcx = tcx.gcx as *const _ as usize;
let interners = tcx.interners as *const _ as usize;
assert!(context.tcx.gcx as *const _ as usize == gcx);
assert!(context.tcx.interners as *const _ as usize == interners);
let context: &ImplicitCtxt = mem::transmute(context);
f(context)
}
})
}
/// Allows access to the TyCtxt in the current ImplicitCtxt.
/// Panics if there is no ImplicitCtxt available
pub fn with<F, R>(f: F) -> R
where F: for<'a, 'gcx, 'tcx> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R
{
with_context(|context| f(context.tcx))
}
/// Allows access to the TyCtxt in the current ImplicitCtxt.
/// The closure is passed None if there is no ImplicitCtxt available
pub fn with_opt<F, R>(f: F) -> R pub fn with_opt<F, R>(f: F) -> R
where F: for<'a, 'gcx, 'tcx> FnOnce(Option<TyCtxt<'a, 'gcx, 'tcx>>) -> R where F: for<'a, 'gcx, 'tcx> FnOnce(Option<TyCtxt<'a, 'gcx, 'tcx>>) -> R
{ {
if TLS_TCX.with(|tcx| tcx.get().is_some()) { with_context_opt(|opt_context| f(opt_context.map(|context| context.tcx)))
with(|v| f(Some(v)))
} else {
f(None)
}
} }
} }

View file

@ -0,0 +1,90 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use rustc_data_structures::sync::{Lock, Lrc};
use syntax_pos::Span;
use ty::tls;
use ty::maps::Query;
use ty::maps::plumbing::CycleError;
use ty::context::TyCtxt;
use errors::Diagnostic;
/// Indicates the state of a query for a given key in a query map
pub(super) enum QueryResult<'tcx, T> {
/// An already executing query. The query job can be used to await for its completion
Started(Lrc<QueryJob<'tcx>>),
/// The query is complete and produced `T`
Complete(T),
/// The query panicked. Queries trying to wait on this will raise a fatal error / silently panic
Poisoned,
}
/// A span and a query key
#[derive(Clone, Debug)]
pub struct QueryInfo<'tcx> {
pub span: Span,
pub query: Query<'tcx>,
}
/// A object representing an active query job.
pub struct QueryJob<'tcx> {
pub info: QueryInfo<'tcx>,
/// The parent query job which created this job and is implicitly waiting on it.
pub parent: Option<Lrc<QueryJob<'tcx>>>,
/// Diagnostic messages which are emitted while the query executes
pub diagnostics: Lock<Vec<Diagnostic>>,
}
impl<'tcx> QueryJob<'tcx> {
/// Creates a new query job
pub fn new(info: QueryInfo<'tcx>, parent: Option<Lrc<QueryJob<'tcx>>>) -> Self {
QueryJob {
diagnostics: Lock::new(Vec::new()),
info,
parent,
}
}
/// Awaits for the query job to complete.
///
/// For single threaded rustc there's no concurrent jobs running, so if we are waiting for any
/// query that means that there is a query cycle, thus this always running a cycle error.
pub(super) fn await<'lcx>(
&self,
tcx: TyCtxt<'_, 'tcx, 'lcx>,
span: Span,
) -> Result<(), CycleError<'tcx>> {
// Get the current executing query (waiter) and find the waitee amongst its parents
let mut current_job = tls::with_related_context(tcx, |icx| icx.query.clone());
let mut cycle = Vec::new();
while let Some(job) = current_job {
cycle.insert(0, job.info.clone());
if &*job as *const _ == self as *const _ {
break;
}
current_job = job.parent.clone();
}
Err(CycleError { span, cycle })
}
/// 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
pub fn signal_complete(&self) {}
}

View file

@ -14,7 +14,7 @@ use hir::def_id::{CrateNum, DefId, DefIndex};
use hir::def::{Def, Export}; use hir::def::{Def, Export};
use hir::{self, TraitCandidate, ItemLocalId, TransFnAttrs}; use hir::{self, TraitCandidate, ItemLocalId, TransFnAttrs};
use hir::svh::Svh; use hir::svh::Svh;
use infer::canonical::{Canonical, QueryResult}; use infer::canonical::{self, Canonical};
use lint; use lint;
use middle::borrowck::BorrowCheckResult; use middle::borrowck::BorrowCheckResult;
use middle::cstore::{ExternCrate, LinkagePreference, NativeLibrary, use middle::cstore::{ExternCrate, LinkagePreference, NativeLibrary,
@ -66,6 +66,10 @@ mod plumbing;
use self::plumbing::*; use self::plumbing::*;
pub use self::plumbing::force_from_dep_node; pub use self::plumbing::force_from_dep_node;
mod job;
pub use self::job::{QueryJob, QueryInfo};
use self::job::QueryResult;
mod keys; mod keys;
pub use self::keys::Key; pub use self::keys::Key;
@ -397,7 +401,7 @@ define_maps! { <'tcx>
[] fn normalize_projection_ty: NormalizeProjectionTy( [] fn normalize_projection_ty: NormalizeProjectionTy(
CanonicalProjectionGoal<'tcx> CanonicalProjectionGoal<'tcx>
) -> Result< ) -> Result<
Lrc<Canonical<'tcx, QueryResult<'tcx, NormalizationResult<'tcx>>>>, Lrc<Canonical<'tcx, canonical::QueryResult<'tcx, NormalizationResult<'tcx>>>>,
NoSolution, NoSolution,
>, >,
@ -410,7 +414,7 @@ define_maps! { <'tcx>
[] fn dropck_outlives: DropckOutlives( [] fn dropck_outlives: DropckOutlives(
CanonicalTyGoal<'tcx> CanonicalTyGoal<'tcx>
) -> Result< ) -> Result<
Lrc<Canonical<'tcx, QueryResult<'tcx, DropckOutlivesResult<'tcx>>>>, Lrc<Canonical<'tcx, canonical::QueryResult<'tcx, DropckOutlivesResult<'tcx>>>>,
NoSolution, NoSolution,
>, >,

View file

@ -30,6 +30,7 @@ use syntax::codemap::{CodeMap, StableFilemapId};
use syntax_pos::{BytePos, Span, DUMMY_SP, FileMap}; use syntax_pos::{BytePos, Span, DUMMY_SP, FileMap};
use syntax_pos::hygiene::{Mark, SyntaxContext, ExpnInfo}; use syntax_pos::hygiene::{Mark, SyntaxContext, ExpnInfo};
use ty; use ty;
use ty::maps::job::QueryResult;
use ty::codec::{self as ty_codec, TyDecoder, TyEncoder}; use ty::codec::{self as ty_codec, TyDecoder, TyEncoder};
use ty::context::TyCtxt; use ty::context::TyCtxt;
@ -239,6 +240,10 @@ impl<'sess> OnDiskCache<'sess> {
for (key, entry) in const_eval::get_cache_internal(tcx).map.iter() { for (key, entry) in const_eval::get_cache_internal(tcx).map.iter() {
use ty::maps::config::QueryDescription; use ty::maps::config::QueryDescription;
if const_eval::cache_on_disk(key.clone()) { if const_eval::cache_on_disk(key.clone()) {
let entry = match *entry {
QueryResult::Complete(ref v) => v,
_ => panic!("incomplete query"),
};
if let Ok(ref value) = entry.value { if let Ok(ref value) = entry.value {
let dep_node = SerializedDepNodeIndex::new(entry.index.index()); let dep_node = SerializedDepNodeIndex::new(entry.index.index());
@ -1109,6 +1114,10 @@ fn encode_query_results<'enc, 'a, 'tcx, Q, E>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
{ {
for (key, entry) in Q::get_cache_internal(tcx).map.iter() { for (key, entry) in Q::get_cache_internal(tcx).map.iter() {
if Q::cache_on_disk(key.clone()) { if Q::cache_on_disk(key.clone()) {
let entry = match *entry {
QueryResult::Complete(ref v) => v,
_ => panic!("incomplete query"),
};
let dep_node = SerializedDepNodeIndex::new(entry.index.index()); let dep_node = SerializedDepNodeIndex::new(entry.index.index());
// Record position of the cache entry // Record position of the cache entry

View file

@ -15,19 +15,18 @@
use dep_graph::{DepNodeIndex, DepNode, DepKind, DepNodeColor}; use dep_graph::{DepNodeIndex, DepNode, DepKind, DepNodeColor};
use errors::DiagnosticBuilder; use errors::DiagnosticBuilder;
use ty::{TyCtxt}; use ty::{TyCtxt};
use ty::maps::Query; // NB: actually generated by the macros in this file
use ty::maps::config::QueryDescription; use ty::maps::config::QueryDescription;
use ty::maps::job::{QueryResult, QueryInfo};
use ty::item_path; use ty::item_path;
use rustc_data_structures::fx::{FxHashMap}; use rustc_data_structures::fx::{FxHashMap};
use std::cell::{Ref, RefMut}; use rustc_data_structures::sync::LockGuard;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem;
use syntax_pos::Span; use syntax_pos::Span;
pub(super) struct QueryMap<'tcx, D: QueryDescription<'tcx>> { pub(super) struct QueryMap<'tcx, D: QueryDescription<'tcx>> {
phantom: PhantomData<(D, &'tcx ())>, phantom: PhantomData<(D, &'tcx ())>,
pub(super) map: FxHashMap<D::Key, QueryValue<D::Value>>, pub(super) map: FxHashMap<D::Key, QueryResult<'tcx, QueryValue<D::Value>>>,
} }
pub(super) struct QueryValue<T> { pub(super) struct QueryValue<T> {
@ -57,23 +56,30 @@ impl<'tcx, M: QueryDescription<'tcx>> QueryMap<'tcx, M> {
pub(super) trait GetCacheInternal<'tcx>: QueryDescription<'tcx> + Sized { pub(super) trait GetCacheInternal<'tcx>: QueryDescription<'tcx> + Sized {
fn get_cache_internal<'a>(tcx: TyCtxt<'a, 'tcx, 'tcx>) fn get_cache_internal<'a>(tcx: TyCtxt<'a, 'tcx, 'tcx>)
-> Ref<'a, QueryMap<'tcx, Self>>; -> LockGuard<'a, QueryMap<'tcx, Self>>;
} }
pub(super) struct CycleError<'a, 'tcx: 'a> { #[derive(Clone)]
span: Span, pub(super) struct CycleError<'tcx> {
cycle: RefMut<'a, [(Span, Query<'tcx>)]>, pub(super) span: Span,
pub(super) cycle: Vec<QueryInfo<'tcx>>,
}
/// The result of `try_get_lock`
pub(super) enum TryGetLock<'a, 'tcx: 'a, T, D: QueryDescription<'tcx> + 'a> {
/// The query is not yet started. Contains a guard to the map eventually used to start it.
NotYetStarted(LockGuard<'a, QueryMap<'tcx, D>>),
/// The query was already completed.
/// Returns the result of the query and its dep node index
/// if it succeeded or a cycle error if it failed
JobCompleted(Result<(T, DepNodeIndex), CycleError<'tcx>>),
} }
impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
pub(super) fn report_cycle(self, CycleError { span, cycle }: CycleError) pub(super) fn report_cycle(self, CycleError { span, cycle: stack }: CycleError)
-> DiagnosticBuilder<'a> -> DiagnosticBuilder<'a>
{ {
// Subtle: release the refcell lock before invoking `describe()`
// below by dropping `cycle`.
let stack = cycle.to_vec();
mem::drop(cycle);
assert!(!stack.is_empty()); assert!(!stack.is_empty());
// Disable naming impls with types in this path, since that // Disable naming impls with types in this path, since that
@ -87,44 +93,21 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
"cyclic dependency detected"); "cyclic dependency detected");
err.span_label(span, "cyclic reference"); err.span_label(span, "cyclic reference");
err.span_note(self.sess.codemap().def_span(stack[0].0), err.span_note(self.sess.codemap().def_span(stack[0].span),
&format!("the cycle begins when {}...", stack[0].1.describe(self))); &format!("the cycle begins when {}...", stack[0].query.describe(self)));
for &(span, ref query) in &stack[1..] { for &QueryInfo { span, ref query, .. } in &stack[1..] {
err.span_note(self.sess.codemap().def_span(span), err.span_note(self.sess.codemap().def_span(span),
&format!("...which then requires {}...", query.describe(self))); &format!("...which then requires {}...", query.describe(self)));
} }
err.note(&format!("...which then again requires {}, completing the cycle.", err.note(&format!("...which then again requires {}, completing the cycle.",
stack[0].1.describe(self))); stack[0].query.describe(self)));
return err return err
}) })
} }
pub(super) fn cycle_check<F, R>(self, span: Span, query: Query<'gcx>, compute: F)
-> Result<R, CycleError<'a, 'gcx>>
where F: FnOnce() -> R
{
{
let mut stack = self.maps.query_stack.borrow_mut();
if let Some((i, _)) = stack.iter().enumerate().rev()
.find(|&(_, &(_, ref q))| *q == query) {
return Err(CycleError {
span,
cycle: RefMut::map(stack, |stack| &mut stack[i..])
});
}
stack.push((span, query));
}
let result = compute();
self.maps.query_stack.borrow_mut().pop();
Ok(result)
}
/// Try to read a node index for the node dep_node. /// Try to read a node index for the node dep_node.
/// A node will have an index, when it's already been marked green, or when we can mark it /// A node will have an index, when it's already been marked green, or when we can mark it
/// green. This function will mark the current task as a reader of the specified node, when /// green. This function will mark the current task as a reader of the specified node, when
@ -202,7 +185,11 @@ macro_rules! define_maps {
[$($modifiers:tt)*] fn $name:ident: $node:ident($K:ty) -> $V:ty,)*) => { [$($modifiers:tt)*] fn $name:ident: $node:ident($K:ty) -> $V:ty,)*) => {
use dep_graph::DepNodeIndex; use dep_graph::DepNodeIndex;
use std::cell::RefCell; use std::mem;
use errors::Diagnostic;
use errors::FatalError;
use rustc_data_structures::sync::{Lock, LockGuard};
use rustc_data_structures::OnDrop;
define_map_struct! { define_map_struct! {
tcx: $tcx, tcx: $tcx,
@ -214,8 +201,7 @@ macro_rules! define_maps {
-> Self { -> Self {
Maps { Maps {
providers, providers,
query_stack: RefCell::new(vec![]), $($name: Lock::new(QueryMap::new())),*
$($name: RefCell::new(QueryMap::new())),*
} }
} }
} }
@ -263,7 +249,7 @@ macro_rules! define_maps {
impl<$tcx> GetCacheInternal<$tcx> for queries::$name<$tcx> { impl<$tcx> GetCacheInternal<$tcx> for queries::$name<$tcx> {
fn get_cache_internal<'a>(tcx: TyCtxt<'a, $tcx, $tcx>) fn get_cache_internal<'a>(tcx: TyCtxt<'a, $tcx, $tcx>)
-> ::std::cell::Ref<'a, QueryMap<$tcx, Self>> { -> LockGuard<'a, QueryMap<$tcx, Self>> {
tcx.maps.$name.borrow() tcx.maps.$name.borrow()
} }
} }
@ -277,10 +263,54 @@ macro_rules! define_maps {
DepNode::new(tcx, $node(*key)) DepNode::new(tcx, $node(*key))
} }
/// Either get the lock of the query map, allowing us to
/// start executing the query, or it returns with the result of the query.
/// If the query already executed and panicked, this will fatal error / silently panic
fn try_get_lock(
tcx: TyCtxt<'a, $tcx, 'lcx>,
mut span: Span,
key: &$K
) -> TryGetLock<'a, $tcx, $V, Self>
{
loop {
let lock = tcx.maps.$name.borrow_mut();
let job = if let Some(value) = lock.map.get(key) {
match *value {
QueryResult::Started(ref job) => Some(job.clone()),
QueryResult::Complete(ref value) => {
profq_msg!(tcx, ProfileQueriesMsg::CacheHit);
let result = Ok(((&value.value).clone(), value.index));
return TryGetLock::JobCompleted(result);
},
QueryResult::Poisoned => FatalError.raise(),
}
} else {
None
};
let job = if let Some(job) = job {
job
} else {
return TryGetLock::NotYetStarted(lock);
};
mem::drop(lock);
// This just matches the behavior of `try_get_with` so the span when
// we await matches the span we would use when executing.
// See the FIXME there.
if span == DUMMY_SP && stringify!($name) != "def_span" {
span = key.default_span(tcx);
}
if let Err(cycle) = job.await(tcx, span) {
return TryGetLock::JobCompleted(Err(cycle));
}
}
}
fn try_get_with(tcx: TyCtxt<'a, $tcx, 'lcx>, fn try_get_with(tcx: TyCtxt<'a, $tcx, 'lcx>,
mut span: Span, mut span: Span,
key: $K) key: $K)
-> Result<$V, CycleError<'a, $tcx>> -> Result<$V, CycleError<$tcx>>
{ {
debug!("ty::queries::{}::try_get_with(key={:?}, span={:?})", debug!("ty::queries::{}::try_get_with(key={:?}, span={:?})",
stringify!($name), stringify!($name),
@ -294,24 +324,41 @@ macro_rules! define_maps {
) )
); );
if let Some(value) = tcx.maps.$name.borrow().map.get(&key) { /// Get the lock used to start the query or
profq_msg!(tcx, ProfileQueriesMsg::CacheHit); /// return the result of the completed query
tcx.dep_graph.read_index(value.index); macro_rules! get_lock_or_return {
return Ok((&value.value).clone()); () => {{
match Self::try_get_lock(tcx, span, &key) {
TryGetLock::NotYetStarted(lock) => lock,
TryGetLock::JobCompleted(result) => {
return result.map(|(v, index)| {
tcx.dep_graph.read_index(index);
v
})
}
}
}}
} }
let mut lock = get_lock_or_return!();
// FIXME(eddyb) Get more valid Span's on queries. // FIXME(eddyb) Get more valid Span's on queries.
// def_span guard is necessary to prevent a recursive loop, // def_span guard is necessary to prevent a recursive loop,
// default_span calls def_span query internally. // default_span calls def_span query internally.
if span == DUMMY_SP && stringify!($name) != "def_span" { if span == DUMMY_SP && stringify!($name) != "def_span" {
span = key.default_span(tcx) // This might deadlock if we hold the map lock since we might be
// waiting for the def_span query and switch to some other fiber
// So we drop the lock here and reacquire it
mem::drop(lock);
span = key.default_span(tcx);
lock = get_lock_or_return!();
} }
// Fast path for when incr. comp. is off. `to_dep_node` is // Fast path for when incr. comp. is off. `to_dep_node` is
// expensive for some DepKinds. // expensive for some DepKinds.
if !tcx.dep_graph.is_fully_enabled() { if !tcx.dep_graph.is_fully_enabled() {
let null_dep_node = DepNode::new_no_params(::dep_graph::DepKind::Null); let null_dep_node = DepNode::new_no_params(::dep_graph::DepKind::Null);
return Self::force(tcx, key, span, null_dep_node) return Self::force_with_lock(tcx, key, span, lock, null_dep_node)
.map(|(v, _)| v); .map(|(v, _)| v);
} }
@ -320,34 +367,36 @@ macro_rules! define_maps {
if dep_node.kind.is_anon() { if dep_node.kind.is_anon() {
profq_msg!(tcx, ProfileQueriesMsg::ProviderBegin); profq_msg!(tcx, ProfileQueriesMsg::ProviderBegin);
let res = tcx.cycle_check(span, Query::$name(key), || { let res = Self::start_job(tcx, span, key, lock, |tcx| {
tcx.sess.diagnostic().track_diagnostics(|| { tcx.dep_graph.with_anon_task(dep_node.kind, || {
tcx.dep_graph.with_anon_task(dep_node.kind, || { Self::compute_result(tcx.global_tcx(), key)
Self::compute_result(tcx.global_tcx(), key)
})
}) })
})?; })?;
profq_msg!(tcx, ProfileQueriesMsg::ProviderEnd); profq_msg!(tcx, ProfileQueriesMsg::ProviderEnd);
let ((result, dep_node_index), diagnostics) = res; let (((result, dep_node_index), diagnostics), job) = res;
tcx.dep_graph.read_index(dep_node_index); tcx.dep_graph.read_index(dep_node_index);
tcx.on_disk_query_result_cache tcx.on_disk_query_result_cache
.store_diagnostics_for_anon_node(dep_node_index, diagnostics); .store_diagnostics_for_anon_node(dep_node_index, diagnostics);
let value = QueryValue::new(result, dep_node_index); let value = QueryValue::new(Clone::clone(&result), dep_node_index);
return Ok((&tcx.maps tcx.maps
.$name .$name
.borrow_mut() .borrow_mut()
.map .map
.entry(key) .insert(key, QueryResult::Complete(value));
.or_insert(value)
.value).clone()); job.signal_complete();
return Ok(result);
} }
if !dep_node.kind.is_input() { if !dep_node.kind.is_input() {
// try_mark_green_and_read may force queries. So we must drop our lock here
mem::drop(lock);
if let Some(dep_node_index) = tcx.try_mark_green_and_read(&dep_node) { if let Some(dep_node_index) = tcx.try_mark_green_and_read(&dep_node) {
profq_msg!(tcx, ProfileQueriesMsg::CacheHit); profq_msg!(tcx, ProfileQueriesMsg::CacheHit);
return Self::load_from_disk_and_cache_in_memory(tcx, return Self::load_from_disk_and_cache_in_memory(tcx,
@ -356,9 +405,10 @@ macro_rules! define_maps {
dep_node_index, dep_node_index,
&dep_node) &dep_node)
} }
lock = get_lock_or_return!();
} }
match Self::force(tcx, key, span, dep_node) { match Self::force_with_lock(tcx, key, span, lock, dep_node) {
Ok((result, dep_node_index)) => { Ok((result, dep_node_index)) => {
tcx.dep_graph.read_index(dep_node_index); tcx.dep_graph.read_index(dep_node_index);
Ok(result) Ok(result)
@ -391,6 +441,73 @@ macro_rules! define_maps {
} }
} }
/// Creates a job for the query and updates the query map indicating that it started.
/// Then it changes ImplicitCtxt to point to the new query job while it executes.
/// If the query panics, this updates the query map to indicate so.
fn start_job<F, R>(tcx: TyCtxt<'_, $tcx, 'lcx>,
span: Span,
key: $K,
mut map: LockGuard<'_, QueryMap<$tcx, Self>>,
compute: F)
-> Result<((R, Vec<Diagnostic>), Lrc<QueryJob<$tcx>>), CycleError<$tcx>>
where F: for<'b> FnOnce(TyCtxt<'b, $tcx, 'lcx>) -> R
{
let query = Query::$name(Clone::clone(&key));
let entry = QueryInfo {
span,
query,
};
// The TyCtxt stored in TLS has the same global interner lifetime
// as `tcx`, so we use `with_related_context` to relate the 'gcx lifetimes
// when accessing the ImplicitCtxt
let (r, job) = ty::tls::with_related_context(tcx, move |icx| {
let job = Lrc::new(QueryJob::new(entry, icx.query.clone()));
// Store the job in the query map and drop the lock to allow
// others to wait it
map.map.entry(key).or_insert(QueryResult::Started(job.clone()));
mem::drop(map);
let r = {
let on_drop = OnDrop(|| {
// Poison the query so jobs waiting on it panic
tcx.maps
.$name
.borrow_mut()
.map
.insert(key, QueryResult::Poisoned);
// Also signal the completion of the job, so waiters
// will continue execution
job.signal_complete();
});
// Update the ImplicitCtxt to point to our new query job
let icx = ty::tls::ImplicitCtxt {
tcx,
query: Some(job.clone()),
};
// Use the ImplicitCtxt while we execute the query
let r = ty::tls::enter_context(&icx, |icx| {
compute(icx.tcx)
});
mem::forget(on_drop);
r
};
(r, job)
});
// Extract the diagnostic from the job
let diagnostics: Vec<_> = mem::replace(&mut *job.diagnostics.lock(), Vec::new());
Ok(((r, diagnostics), job))
}
fn compute_result(tcx: TyCtxt<'a, $tcx, 'lcx>, key: $K) -> $V { fn compute_result(tcx: TyCtxt<'a, $tcx, 'lcx>, key: $K) -> $V {
let provider = tcx.maps.providers[key.map_crate()].$name; let provider = tcx.maps.providers[key.map_crate()].$name;
provider(tcx.global_tcx(), key) provider(tcx.global_tcx(), key)
@ -401,8 +518,11 @@ macro_rules! define_maps {
span: Span, span: Span,
dep_node_index: DepNodeIndex, dep_node_index: DepNodeIndex,
dep_node: &DepNode) dep_node: &DepNode)
-> Result<$V, CycleError<'a, $tcx>> -> Result<$V, CycleError<$tcx>>
{ {
// Note this function can be called concurrently from the same query
// We must ensure that this is handled correctly
debug_assert!(tcx.dep_graph.is_green(dep_node)); debug_assert!(tcx.dep_graph.is_green(dep_node));
// First we try to load the result from the on-disk cache // First we try to load the result from the on-disk cache
@ -425,24 +545,27 @@ macro_rules! define_maps {
None None
}; };
let result = if let Some(result) = result { let (result, job) = if let Some(result) = result {
result (result, None)
} else { } else {
// We could not load a result from the on-disk cache, so // We could not load a result from the on-disk cache, so
// recompute. // recompute.
let (result, _ ) = tcx.cycle_check(span, Query::$name(key), || {
// The diagnostics for this query have already been // The diagnostics for this query have already been
// promoted to the current session during // promoted to the current session during
// try_mark_green(), so we can ignore them here. // try_mark_green(), so we can ignore them here.
tcx.sess.diagnostic().track_diagnostics(|| { let ((result, _), job) = Self::start_job(tcx,
// The dep-graph for this computation is already in span,
// place key,
tcx.dep_graph.with_ignore(|| { tcx.maps.$name.borrow_mut(),
Self::compute_result(tcx, key) |tcx| {
}) // The dep-graph for this computation is already in
// place
tcx.dep_graph.with_ignore(|| {
Self::compute_result(tcx, key)
}) })
})?; })?;
result (result, Some(job))
}; };
// If -Zincremental-verify-ich is specified, re-hash results from // If -Zincremental-verify-ich is specified, re-hash results from
@ -475,43 +598,67 @@ macro_rules! define_maps {
tcx.dep_graph.mark_loaded_from_cache(dep_node_index, true); tcx.dep_graph.mark_loaded_from_cache(dep_node_index, true);
} }
let value = QueryValue::new(result, dep_node_index); let value = QueryValue::new(Clone::clone(&result), dep_node_index);
Ok((&tcx.maps tcx.maps
.$name .$name
.borrow_mut() .borrow_mut()
.map .map
.entry(key) .insert(key, QueryResult::Complete(value));
.or_insert(value)
.value).clone()) job.map(|j| j.signal_complete());
Ok(result)
} }
#[allow(dead_code)]
fn force(tcx: TyCtxt<'a, $tcx, 'lcx>, fn force(tcx: TyCtxt<'a, $tcx, 'lcx>,
key: $K, key: $K,
span: Span, span: Span,
dep_node: DepNode) dep_node: DepNode)
-> Result<($V, DepNodeIndex), CycleError<'a, $tcx>> { -> Result<($V, DepNodeIndex), CycleError<$tcx>> {
// We may be concurrently trying both execute and force a query
// Ensure that only one of them runs the query
let lock = match Self::try_get_lock(tcx, span, &key) {
TryGetLock::NotYetStarted(lock) => lock,
TryGetLock::JobCompleted(result) => return result,
};
Self::force_with_lock(tcx,
key,
span,
lock,
dep_node)
}
fn force_with_lock(tcx: TyCtxt<'a, $tcx, 'lcx>,
key: $K,
span: Span,
map: LockGuard<'_, QueryMap<$tcx, Self>>,
dep_node: DepNode)
-> Result<($V, DepNodeIndex), CycleError<$tcx>> {
debug_assert!(!tcx.dep_graph.dep_node_exists(&dep_node)); debug_assert!(!tcx.dep_graph.dep_node_exists(&dep_node));
profq_msg!(tcx, ProfileQueriesMsg::ProviderBegin); profq_msg!(tcx, ProfileQueriesMsg::ProviderBegin);
let res = tcx.cycle_check(span, Query::$name(key), || { let res = Self::start_job(tcx,
tcx.sess.diagnostic().track_diagnostics(|| { span,
if dep_node.kind.is_eval_always() { key,
tcx.dep_graph.with_eval_always_task(dep_node, map,
tcx, |tcx| {
key, if dep_node.kind.is_eval_always() {
Self::compute_result) tcx.dep_graph.with_eval_always_task(dep_node,
} else { tcx,
tcx.dep_graph.with_task(dep_node, key,
tcx, Self::compute_result)
key, } else {
Self::compute_result) tcx.dep_graph.with_task(dep_node,
} tcx,
}) key,
Self::compute_result)
}
})?; })?;
profq_msg!(tcx, ProfileQueriesMsg::ProviderEnd); profq_msg!(tcx, ProfileQueriesMsg::ProviderEnd);
let ((result, dep_node_index), diagnostics) = res; let (((result, dep_node_index), diagnostics), job) = res;
if tcx.sess.opts.debugging_opts.query_dep_graph { if tcx.sess.opts.debugging_opts.query_dep_graph {
tcx.dep_graph.mark_loaded_from_cache(dep_node_index, false); tcx.dep_graph.mark_loaded_from_cache(dep_node_index, false);
@ -522,16 +669,19 @@ macro_rules! define_maps {
.store_diagnostics(dep_node_index, diagnostics); .store_diagnostics(dep_node_index, diagnostics);
} }
let value = QueryValue::new(result, dep_node_index); let value = QueryValue::new(Clone::clone(&result), dep_node_index);
Ok(((&tcx.maps tcx.maps
.$name .$name
.borrow_mut() .borrow_mut()
.map .map
.entry(key) .insert(key, QueryResult::Complete(value));
.or_insert(value)
.value).clone(), let job: Lrc<QueryJob> = job;
dep_node_index))
job.signal_complete();
Ok((result, dep_node_index))
} }
pub fn try_get(tcx: TyCtxt<'a, $tcx, 'lcx>, span: Span, key: $K) pub fn try_get(tcx: TyCtxt<'a, $tcx, 'lcx>, span: Span, key: $K)
@ -599,8 +749,7 @@ macro_rules! define_map_struct {
input: ($(([$($modifiers:tt)*] [$($attr:tt)*] [$name:ident]))*)) => { input: ($(([$($modifiers:tt)*] [$($attr:tt)*] [$name:ident]))*)) => {
pub struct Maps<$tcx> { pub struct Maps<$tcx> {
providers: IndexVec<CrateNum, Providers<$tcx>>, providers: IndexVec<CrateNum, Providers<$tcx>>,
query_stack: RefCell<Vec<(Span, Query<$tcx>)>>, $($(#[$attr])* $name: Lock<QueryMap<$tcx, queries::$name<$tcx>>>,)*
$($(#[$attr])* $name: RefCell<QueryMap<$tcx, queries::$name<$tcx>>>,)*
} }
}; };
} }

View file

@ -42,7 +42,6 @@ use rustc_data_structures::stable_hasher::StableHasher;
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::{RefCell, Cell}; use std::cell::{RefCell, Cell};
use std::mem;
use std::{error, fmt}; use std::{error, fmt};
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::SeqCst; use std::sync::atomic::Ordering::SeqCst;
@ -269,7 +268,6 @@ pub struct Handler {
emitter: RefCell<Box<Emitter>>, emitter: RefCell<Box<Emitter>>,
continue_after_error: Cell<bool>, continue_after_error: Cell<bool>,
delayed_span_bug: RefCell<Option<Diagnostic>>, delayed_span_bug: RefCell<Option<Diagnostic>>,
tracked_diagnostics: RefCell<Option<Vec<Diagnostic>>>,
// This set contains the `DiagnosticId` of all emitted diagnostics to avoid // This set contains the `DiagnosticId` of all emitted diagnostics to avoid
// emitting the same diagnostic with extended help (`--teach`) twice, which // emitting the same diagnostic with extended help (`--teach`) twice, which
@ -282,6 +280,11 @@ pub struct Handler {
emitted_diagnostics: RefCell<FxHashSet<u128>>, emitted_diagnostics: RefCell<FxHashSet<u128>>,
} }
fn default_track_diagnostic(_: &Diagnostic) {}
thread_local!(pub static TRACK_DIAGNOSTICS: Cell<fn(&Diagnostic)> =
Cell::new(default_track_diagnostic));
#[derive(Default)] #[derive(Default)]
pub struct HandlerFlags { pub struct HandlerFlags {
pub can_emit_warnings: bool, pub can_emit_warnings: bool,
@ -333,7 +336,6 @@ impl Handler {
emitter: RefCell::new(e), emitter: RefCell::new(e),
continue_after_error: Cell::new(true), continue_after_error: Cell::new(true),
delayed_span_bug: RefCell::new(None), delayed_span_bug: RefCell::new(None),
tracked_diagnostics: RefCell::new(None),
tracked_diagnostic_codes: RefCell::new(FxHashSet()), tracked_diagnostic_codes: RefCell::new(FxHashSet()),
emitted_diagnostics: RefCell::new(FxHashSet()), emitted_diagnostics: RefCell::new(FxHashSet()),
} }
@ -631,17 +633,6 @@ impl Handler {
} }
} }
pub fn track_diagnostics<F, R>(&self, f: F) -> (R, Vec<Diagnostic>)
where F: FnOnce() -> R
{
let prev = mem::replace(&mut *self.tracked_diagnostics.borrow_mut(),
Some(Vec::new()));
let ret = f();
let diagnostics = mem::replace(&mut *self.tracked_diagnostics.borrow_mut(), prev)
.unwrap();
(ret, diagnostics)
}
/// `true` if a diagnostic with this code has already been emitted in this handler. /// `true` if a diagnostic with this code has already been emitted in this handler.
/// ///
/// Used to suppress emitting the same error multiple times with extended explanation when /// Used to suppress emitting the same error multiple times with extended explanation when
@ -653,9 +644,9 @@ impl Handler {
fn emit_db(&self, db: &DiagnosticBuilder) { fn emit_db(&self, db: &DiagnosticBuilder) {
let diagnostic = &**db; let diagnostic = &**db;
if let Some(ref mut list) = *self.tracked_diagnostics.borrow_mut() { TRACK_DIAGNOSTICS.with(|track_diagnostics| {
list.push(diagnostic.clone()); track_diagnostics.get()(diagnostic);
} });
if let Some(ref code) = diagnostic.code { if let Some(ref code) = diagnostic.code {
self.tracked_diagnostic_codes.borrow_mut().insert(code.clone()); self.tracked_diagnostic_codes.borrow_mut().insert(code.clone());