From 4f7d0fde1c5f577c1f956d5d4edfbb202a5bc3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Sat, 24 Mar 2018 06:19:20 +0100 Subject: [PATCH] Some cleanups and added comments --- src/librustc/ty/context.rs | 92 +++++++++++++++++++++++--------- src/librustc/ty/maps/job.rs | 69 ++++++++++++------------ src/librustc/ty/maps/mod.rs | 2 +- src/librustc/ty/maps/plumbing.rs | 80 ++++++++++++++++++--------- 4 files changed, 157 insertions(+), 86 deletions(-) diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs index adb0ecd39846a..44b9d61cf0202 100644 --- a/src/librustc/ty/context.rs +++ b/src/librustc/ty/context.rs @@ -1489,10 +1489,13 @@ impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> { impl<'gcx: 'tcx, 'tcx> GlobalCtxt<'gcx> { /// Call the closure with a local `TyCtxt` using the given arena. - pub fn enter_local(&self, - arena: &'tcx DroplessArena, - f: F) -> R - where F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R + pub fn enter_local( + &self, + arena: &'tcx DroplessArena, + f: F + ) -> R + where + F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R { let interners = CtxtInterners::new(arena); let tcx = TyCtxt { @@ -1665,12 +1668,23 @@ pub mod tls { use rustc_data_structures::OnDrop; use rustc_data_structures::sync::Lrc; + /// This is the implicit state of rustc. It contains the current + /// TyCtxt and query. It is updated when creating a local interner or + /// executing a new query. Whenever there's a TyCtxt value available + /// you should also have access to an ImplicitCtxt through the functions + /// 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>, + + /// The current query job, if any. This is updated by start_job in + /// ty::maps::plumbing when executing a query pub query: Option>>, } + // A thread local value which stores a pointer to the current ImplicitCtxt thread_local!(static TLV: Cell = Cell::new(0)); fn set_tlv R, R>(value: usize, f: F) -> R { @@ -1684,12 +1698,17 @@ pub mod tls { 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 { with(|tcx| { write!(f, "{}", tcx.sess.codemap().span_to_string(span)) }) } + /// This is a callback from libsyntax as it cannot access the implicit state + /// 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 { @@ -1698,6 +1717,7 @@ pub mod tls { }) } + /// Sets up the callbacks from libsyntax on the current thread pub fn with_thread_locals(f: F) -> R where F: FnOnce() -> R { @@ -1722,6 +1742,20 @@ pub mod tls { }) } + /// 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 { @@ -1740,15 +1774,7 @@ pub mod tls { }) } - 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) - }) - } - + /// Allows access to the current ImplicitCtxt in a closure if one is available pub fn with_context_opt(f: F) -> R where F: for<'a, 'gcx, 'tcx> FnOnce(Option<&ImplicitCtxt<'a, 'gcx, 'tcx>>) -> R { @@ -1760,46 +1786,62 @@ pub mod tls { } } - 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 + /// Allows access to the current ImplicitCtxt. + /// Panics if there is no ImplicitCtxt available + pub fn with_context(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; - 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) } }) } - 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 + /// 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) } }) } - pub fn with_context(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 TyCtxt in the current ImplicitCtxt. + /// Panics if there is no ImplicitCtxt available pub fn with(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: F) -> R where F: for<'a, 'gcx, 'tcx> FnOnce(Option>) -> R { diff --git a/src/librustc/ty/maps/job.rs b/src/librustc/ty/maps/job.rs index 2ed993bd975e5..7d756fb16a453 100644 --- a/src/librustc/ty/maps/job.rs +++ b/src/librustc/ty/maps/job.rs @@ -8,65 +8,69 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![allow(warnings)] - -use std::mem; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use rustc_data_structures::sync::{Lock, LockGuard, Lrc}; +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; -use std::process; -use std::fmt; -use std::sync::{Arc, Mutex}; -use std::collections::HashSet; -pub struct PoisonedJob; +/// 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>), + + /// 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 StackEntry<'tcx> { +pub struct QueryInfo<'tcx> { pub span: Span, pub query: Query<'tcx>, } +/// A object representing an active query job. pub struct QueryJob<'tcx> { - pub entry: StackEntry<'tcx>, + pub info: QueryInfo<'tcx>, + + /// The parent query job which created this job and is implicitly waiting on it. pub parent: Option>>, - pub track_diagnostics: bool, + + /// Diagnostic messages which are emitted while the query executes pub diagnostics: Lock>, } impl<'tcx> QueryJob<'tcx> { - pub fn new( - entry: StackEntry<'tcx>, - track_diagnostics: bool, - parent: Option>>, - ) -> Self { + /// Creates a new query job + pub fn new(info: QueryInfo<'tcx>, parent: Option>>) -> Self { QueryJob { - track_diagnostics, diagnostics: Lock::new(Vec::new()), - entry, + 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>> { - // The query is already executing, so this must be a cycle for single threaded rustc, - // so we find the cycle and return it - + // 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.entry.clone()); + cycle.insert(0, job.info.clone()); if &*job as *const _ == self as *const _ { break; @@ -78,14 +82,9 @@ impl<'tcx> QueryJob<'tcx> { Err(CycleError { span, cycle }) } - pub fn signal_complete(&self) { - // Signals to waiters that the query is complete. - // This is a no-op for single threaded rustc - } -} - -pub(super) enum QueryResult<'tcx, T> { - Started(Lrc>), - Complete(T), - Poisoned, + /// 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) {} } diff --git a/src/librustc/ty/maps/mod.rs b/src/librustc/ty/maps/mod.rs index 6dd31baa5931b..f4977be78776b 100644 --- a/src/librustc/ty/maps/mod.rs +++ b/src/librustc/ty/maps/mod.rs @@ -67,7 +67,7 @@ use self::plumbing::*; pub use self::plumbing::force_from_dep_node; mod job; -pub use self::job::{QueryJob, StackEntry, PoisonedJob}; +pub use self::job::{QueryJob, QueryInfo}; use self::job::QueryResult; mod keys; diff --git a/src/librustc/ty/maps/plumbing.rs b/src/librustc/ty/maps/plumbing.rs index 895bf3d797336..c21b53cd427c0 100644 --- a/src/librustc/ty/maps/plumbing.rs +++ b/src/librustc/ty/maps/plumbing.rs @@ -16,7 +16,7 @@ use dep_graph::{DepNodeIndex, DepNode, DepKind, DepNodeColor}; use errors::DiagnosticBuilder; use ty::{TyCtxt}; use ty::maps::config::QueryDescription; -use ty::maps::job::{QueryResult, StackEntry}; +use ty::maps::job::{QueryResult, QueryInfo}; use ty::item_path; use rustc_data_structures::fx::{FxHashMap}; @@ -62,7 +62,18 @@ pub(super) trait GetCacheInternal<'tcx>: QueryDescription<'tcx> + Sized { #[derive(Clone)] pub(super) struct CycleError<'tcx> { pub(super) span: Span, - pub(super) cycle: Vec>, + pub(super) cycle: Vec>, +} + +/// 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> { @@ -85,7 +96,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { err.span_note(self.sess.codemap().def_span(stack[0].span), &format!("the cycle begins when {}...", stack[0].query.describe(self))); - for &StackEntry { span, ref query, .. } in &stack[1..] { + for &QueryInfo { span, ref query, .. } in &stack[1..] { err.span_note(self.sess.codemap().def_span(span), &format!("...which then requires {}...", query.describe(self))); } @@ -252,11 +263,14 @@ macro_rules! define_maps { DepNode::new(tcx, $node(*key)) } - fn try_get_lock(tcx: TyCtxt<'a, $tcx, 'lcx>, - mut span: Span, - key: &$K) - -> Result>, - Result<($V, DepNodeIndex), CycleError<$tcx>>> + /// 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(); @@ -265,7 +279,8 @@ macro_rules! define_maps { QueryResult::Started(ref job) => Some(job.clone()), QueryResult::Complete(ref value) => { profq_msg!(tcx, ProfileQueriesMsg::CacheHit); - return Err(Ok(((&value.value).clone(), value.index))); + let result = Ok(((&value.value).clone(), value.index)); + return TryGetLock::JobCompleted(result); }, QueryResult::Poisoned => FatalError.raise(), } @@ -275,16 +290,19 @@ macro_rules! define_maps { let job = if let Some(job) = job { job } else { - return Ok(lock); + 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 Err(Err(cycle)); + return TryGetLock::JobCompleted(Err(cycle)); } } } @@ -306,21 +324,23 @@ macro_rules! define_maps { ) ); - macro_rules! get_lock { + /// Get the lock used to start the query or + /// return the result of the completed query + macro_rules! get_lock_or_return { () => {{ match Self::try_get_lock(tcx, span, &key) { - Ok(lock) => lock, - Err(result) => { + TryGetLock::NotYetStarted(lock) => lock, + TryGetLock::JobCompleted(result) => { return result.map(|(v, index)| { tcx.dep_graph.read_index(index); v - }); - }, + }) + } } }} } - let mut lock = get_lock!(); + let mut lock = get_lock_or_return!(); // FIXME(eddyb) Get more valid Span's on queries. // def_span guard is necessary to prevent a recursive loop, @@ -331,7 +351,7 @@ macro_rules! define_maps { // So we drop the lock here and reacquire it mem::drop(lock); span = key.default_span(tcx); - lock = get_lock!(); + lock = get_lock_or_return!(); } // Fast path for when incr. comp. is off. `to_dep_node` is @@ -385,7 +405,7 @@ macro_rules! define_maps { dep_node_index, &dep_node) } - lock = get_lock!(); + lock = get_lock_or_return!(); } match Self::force_with_lock(tcx, key, span, lock, dep_node) { @@ -421,6 +441,9 @@ 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(tcx: TyCtxt<'_, $tcx, 'lcx>, span: Span, key: $K, @@ -431,21 +454,25 @@ macro_rules! define_maps { { let query = Query::$name(Clone::clone(&key)); - let entry = StackEntry { + 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, true, icx.query.clone())); + 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 panics + // Poison the query so jobs waiting on it panic tcx.maps .$name .borrow_mut() @@ -456,11 +483,13 @@ macro_rules! define_maps { 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) }); @@ -473,6 +502,7 @@ macro_rules! define_maps { (r, job) }); + // Extract the diagnostic from the job let diagnostics: Vec<_> = mem::replace(&mut *job.diagnostics.lock(), Vec::new()); Ok(((r, diagnostics), job)) @@ -590,8 +620,8 @@ macro_rules! define_maps { // 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) { - Ok(lock) => lock, - Err(result) => return result, + TryGetLock::NotYetStarted(lock) => lock, + TryGetLock::JobCompleted(result) => return result, }; Self::force_with_lock(tcx, key,