diff --git a/Cargo.lock b/Cargo.lock index 84a668b5188ce..e886f17884d04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4033,6 +4033,7 @@ dependencies = [ "rustc_span", "rustc_target", "rustc_type_ir", + "scoped-tls", "smallvec", "thin-vec", "tracing", diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 5c9b39cdbe305..53ae131fca6a8 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -185,7 +185,7 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, R: Send>( f: F, ) -> R { use rustc_data_structures::{jobserver, sync::FromDyn}; - use rustc_middle::ty::tls; + use rustc_middle::ty::tls::{self, GcxPtr}; use rustc_query_impl::QueryCtxt; use rustc_query_system::query::{deadlock, QueryContext}; @@ -208,11 +208,25 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, R: Send>( .deadlock_handler(|| { // On deadlock, creates a new thread and forwards information in thread // locals to it. The new thread runs the deadlock handler. - let query_map = FromDyn::from(tls::with(|tcx| { - QueryCtxt::new(tcx) - .try_collect_active_jobs() - .expect("active jobs shouldn't be locked in deadlock handler") - })); + let query_map = FromDyn::from({ + // Get a GlobalCtxt reference from GCX_PTR as we cannot rely on having a TyCtxt TLS + // reference here. + // SAFETY: No thread will end the lifetime of `GlobalCtxt` as they're deadlocked + // and won't resume until the `deadlock` call. + unsafe { + tls::GCX_PTR.with(|gcx_ptr| { + gcx_ptr.access(|gcx| { + tls::enter_context(&tls::ImplicitCtxt::new(gcx), || { + tls::with(|tcx| { + QueryCtxt::new(tcx).try_collect_active_jobs().expect( + "active jobs shouldn't be locked in deadlock handler", + ) + }) + }) + }) + }) + } + }); let registry = rayon_core::Registry::current(); thread::spawn(move || deadlock(query_map.into_inner(), ®istry)); }); @@ -220,6 +234,8 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, R: Send>( builder = builder.stack_size(size); } + let gcx_ptr = GcxPtr::new(); + // We create the session globals on the main thread, then create the thread // pool. Upon creation, each worker thread created gets a copy of the // session globals in TLS. This is possible because `SessionGlobals` impls @@ -235,7 +251,7 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, R: Send>( registry.register(); rustc_span::set_session_globals_then(session_globals.into_inner(), || { - thread.run() + tls::GCX_PTR.set(&gcx_ptr, || thread.run()) }) }, // Run `f` on the first thread in the thread pool. diff --git a/compiler/rustc_middle/Cargo.toml b/compiler/rustc_middle/Cargo.toml index bb8e774cea3d1..d7fc6cc61ff3b 100644 --- a/compiler/rustc_middle/Cargo.toml +++ b/compiler/rustc_middle/Cargo.toml @@ -35,6 +35,7 @@ rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } rustc_type_ir = { path = "../rustc_type_ir" } +scoped-tls = "1.0" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } thin-vec = "0.2.12" tracing = "0.1" diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index e2f9e299566ae..7eda94c2c687c 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -590,7 +590,7 @@ impl<'tcx> GlobalCtxt<'tcx> { F: FnOnce(TyCtxt<'tcx>) -> R, { let icx = tls::ImplicitCtxt::new(self); - tls::enter_context(&icx, || f(icx.tcx)) + tls::enter_global_context(&icx, || f(icx.tcx)) } } diff --git a/compiler/rustc_middle/src/ty/context/tls.rs b/compiler/rustc_middle/src/ty/context/tls.rs index 9de77b9fda11a..2abc2b4dbafe0 100644 --- a/compiler/rustc_middle/src/ty/context/tls.rs +++ b/compiler/rustc_middle/src/ty/context/tls.rs @@ -2,6 +2,7 @@ use super::{GlobalCtxt, TyCtxt}; use crate::dep_graph::TaskDepsRef; use crate::query::plumbing::QueryJobId; +use rustc_data_structures::defer; use rustc_data_structures::sync::{self, Lock}; use rustc_errors::Diagnostic; #[cfg(not(parallel_compiler))] @@ -153,3 +154,51 @@ where { with_context_opt(|opt_context| f(opt_context.map(|context| context.tcx))) } + +/// Enters `GlobalCtxt` by setting up the `GCX_PTR` pointer. +pub fn enter_global_context<'tcx, F, R>(context: &ImplicitCtxt<'_, 'tcx>, f: F) -> R +where + F: FnOnce() -> R, +{ + if cfg!(parallel_compiler) && GCX_PTR.is_set() { + // Update `GCX_PTR` to indicate there's a `GlobalCtxt` available. + GCX_PTR.with(|gcx_ptr| { + let mut lock = gcx_ptr.value.lock(); + assert!(lock.is_none()); + *lock = Some(context.tcx.gcx as *const _ as *const ()); + }); + // Set `GCX_PTR` back to 0 when we exit. + let _on_drop = defer(move || { + GCX_PTR.with(|gcx_ptr| *gcx_ptr.value.lock() = None); + }); + enter_context(context, f) + } else { + enter_context(context, f) + } +} + +pub struct GcxPtr { + /// Stores a pointer to the `GlobalCtxt` if one is available. + /// This is used to access the `GlobalCtxt` in the deadlock handler given to Rayon. + value: Lock>, +} + +impl GcxPtr { + pub fn new() -> Self { + GcxPtr { value: Lock::new(None) } + } + + /// This accesses the GlobalCtxt. + /// + /// Safety: The caller must ensure that the GlobalCtxt is live during `f`. + pub unsafe fn access(&self, f: impl for<'tcx> FnOnce(&'tcx GlobalCtxt<'tcx>) -> R) -> R { + let gcx_ptr: *const GlobalCtxt<'_> = self.value.lock().unwrap() as *const _; + f(unsafe { &*gcx_ptr }) + } +} + +unsafe impl Sync for GcxPtr {} + +scoped_tls::scoped_thread_local! { + pub static GCX_PTR: GcxPtr +}