Skip to content

Commit 7222241

Browse files
committed
Auto 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
2 parents 56714ac + 4f7d0fd commit 7222241

File tree

6 files changed

+558
-183
lines changed

6 files changed

+558
-183
lines changed

src/librustc/ty/context.rs

+179-47
Original file line numberDiff line numberDiff line change
@@ -1244,7 +1244,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
12441244
Lrc::new(StableVec::new(v)));
12451245
}
12461246

1247-
tls::enter_global(GlobalCtxt {
1247+
let gcx = &GlobalCtxt {
12481248
sess: s,
12491249
cstore,
12501250
global_arenas: &arenas.global,
@@ -1285,7 +1285,9 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
12851285
all_traits: RefCell::new(None),
12861286
tx_to_llvm_workers: tx,
12871287
output_filenames: Arc::new(output_filenames.clone()),
1288-
}, f)
1288+
};
1289+
1290+
tls::enter_global(gcx, f)
12891291
}
12901292

12911293
pub fn consider_optimizing<T: Fn() -> String>(&self, msg: T) -> bool {
@@ -1509,11 +1511,28 @@ impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> {
15091511

15101512
impl<'gcx: 'tcx, 'tcx> GlobalCtxt<'gcx> {
15111513
/// Call the closure with a local `TyCtxt` using the given arena.
1512-
pub fn enter_local<F, R>(&self, arena: &'tcx DroplessArena, f: F) -> R
1513-
where F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R
1514+
pub fn enter_local<F, R>(
1515+
&self,
1516+
arena: &'tcx DroplessArena,
1517+
f: F
1518+
) -> R
1519+
where
1520+
F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R
15141521
{
15151522
let interners = CtxtInterners::new(arena);
1516-
tls::enter(self, &interners, f)
1523+
let tcx = TyCtxt {
1524+
gcx: self,
1525+
interners: &interners,
1526+
};
1527+
ty::tls::with_related_context(tcx.global_tcx(), |icx| {
1528+
let new_icx = ty::tls::ImplicitCtxt {
1529+
tcx,
1530+
query: icx.query.clone(),
1531+
};
1532+
ty::tls::enter_context(&new_icx, |new_icx| {
1533+
f(new_icx.tcx)
1534+
})
1535+
})
15171536
}
15181537
}
15191538

@@ -1678,83 +1697,196 @@ impl<'a, 'tcx> Lift<'tcx> for &'a Slice<CanonicalVarInfo> {
16781697
}
16791698

16801699
pub mod tls {
1681-
use super::{CtxtInterners, GlobalCtxt, TyCtxt};
1700+
use super::{GlobalCtxt, TyCtxt};
16821701

16831702
use std::cell::Cell;
16841703
use std::fmt;
1704+
use std::mem;
16851705
use syntax_pos;
1706+
use ty::maps;
1707+
use errors::{Diagnostic, TRACK_DIAGNOSTICS};
1708+
use rustc_data_structures::OnDrop;
1709+
use rustc_data_structures::sync::Lrc;
16861710

1687-
/// Marker types used for the scoped TLS slot.
1688-
/// The type context cannot be used directly because the scoped TLS
1689-
/// in libstd doesn't allow types generic over lifetimes.
1690-
enum ThreadLocalGlobalCtxt {}
1691-
enum ThreadLocalInterners {}
1711+
/// This is the implicit state of rustc. It contains the current
1712+
/// TyCtxt and query. It is updated when creating a local interner or
1713+
/// executing a new query. Whenever there's a TyCtxt value available
1714+
/// you should also have access to an ImplicitCtxt through the functions
1715+
/// in this module.
1716+
#[derive(Clone)]
1717+
pub struct ImplicitCtxt<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
1718+
/// The current TyCtxt. Initially created by `enter_global` and updated
1719+
/// by `enter_local` with a new local interner
1720+
pub tcx: TyCtxt<'a, 'gcx, 'tcx>,
16921721

1693-
thread_local! {
1694-
static TLS_TCX: Cell<Option<(*const ThreadLocalGlobalCtxt,
1695-
*const ThreadLocalInterners)>> = Cell::new(None)
1722+
/// The current query job, if any. This is updated by start_job in
1723+
/// ty::maps::plumbing when executing a query
1724+
pub query: Option<Lrc<maps::QueryJob<'gcx>>>,
16961725
}
16971726

1727+
// A thread local value which stores a pointer to the current ImplicitCtxt
1728+
thread_local!(static TLV: Cell<usize> = Cell::new(0));
1729+
1730+
fn set_tlv<F: FnOnce() -> R, R>(value: usize, f: F) -> R {
1731+
let old = get_tlv();
1732+
let _reset = OnDrop(move || TLV.with(|tlv| tlv.set(old)));
1733+
TLV.with(|tlv| tlv.set(value));
1734+
f()
1735+
}
1736+
1737+
fn get_tlv() -> usize {
1738+
TLV.with(|tlv| tlv.get())
1739+
}
1740+
1741+
/// This is a callback from libsyntax as it cannot access the implicit state
1742+
/// in librustc otherwise
16981743
fn span_debug(span: syntax_pos::Span, f: &mut fmt::Formatter) -> fmt::Result {
16991744
with(|tcx| {
17001745
write!(f, "{}", tcx.sess.codemap().span_to_string(span))
17011746
})
17021747
}
17031748

1704-
pub fn enter_global<'gcx, F, R>(gcx: GlobalCtxt<'gcx>, f: F) -> R
1705-
where F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'gcx>) -> R
1749+
/// This is a callback from libsyntax as it cannot access the implicit state
1750+
/// in librustc otherwise. It is used to when diagnostic messages are
1751+
/// emitted and stores them in the current query, if there is one.
1752+
fn track_diagnostic(diagnostic: &Diagnostic) {
1753+
with_context(|context| {
1754+
if let Some(ref query) = context.query {
1755+
query.diagnostics.lock().push(diagnostic.clone());
1756+
}
1757+
})
1758+
}
1759+
1760+
/// Sets up the callbacks from libsyntax on the current thread
1761+
pub fn with_thread_locals<F, R>(f: F) -> R
1762+
where F: FnOnce() -> R
17061763
{
17071764
syntax_pos::SPAN_DEBUG.with(|span_dbg| {
17081765
let original_span_debug = span_dbg.get();
17091766
span_dbg.set(span_debug);
1710-
let result = enter(&gcx, &gcx.global_interners, f);
1711-
span_dbg.set(original_span_debug);
1712-
result
1767+
1768+
let _on_drop = OnDrop(move || {
1769+
span_dbg.set(original_span_debug);
1770+
});
1771+
1772+
TRACK_DIAGNOSTICS.with(|current| {
1773+
let original = current.get();
1774+
current.set(track_diagnostic);
1775+
1776+
let _on_drop = OnDrop(move || {
1777+
current.set(original);
1778+
});
1779+
1780+
f()
1781+
})
17131782
})
17141783
}
17151784

1716-
pub fn enter<'a, 'gcx: 'tcx, 'tcx, F, R>(gcx: &'a GlobalCtxt<'gcx>,
1717-
interners: &'a CtxtInterners<'tcx>,
1718-
f: F) -> R
1719-
where F: FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R
1785+
/// Sets `context` as the new current ImplicitCtxt for the duration of the function `f`
1786+
pub fn enter_context<'a, 'gcx: 'tcx, 'tcx, F, R>(context: &ImplicitCtxt<'a, 'gcx, 'tcx>,
1787+
f: F) -> R
1788+
where F: FnOnce(&ImplicitCtxt<'a, 'gcx, 'tcx>) -> R
17201789
{
1721-
let gcx_ptr = gcx as *const _ as *const ThreadLocalGlobalCtxt;
1722-
let interners_ptr = interners as *const _ as *const ThreadLocalInterners;
1723-
TLS_TCX.with(|tls| {
1724-
let prev = tls.get();
1725-
tls.set(Some((gcx_ptr, interners_ptr)));
1726-
let ret = f(TyCtxt {
1727-
gcx,
1728-
interners,
1729-
});
1730-
tls.set(prev);
1731-
ret
1790+
set_tlv(context as *const _ as usize, || {
1791+
f(&context)
17321792
})
17331793
}
17341794

1735-
pub fn with<F, R>(f: F) -> R
1736-
where F: for<'a, 'gcx, 'tcx> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R
1795+
/// Enters GlobalCtxt by setting up libsyntax callbacks and
1796+
/// creating a initial TyCtxt and ImplicitCtxt.
1797+
/// This happens once per rustc session and TyCtxts only exists
1798+
/// inside the `f` function.
1799+
pub fn enter_global<'gcx, F, R>(gcx: &GlobalCtxt<'gcx>, f: F) -> R
1800+
where F: for<'a> FnOnce(TyCtxt<'a, 'gcx, 'gcx>) -> R
17371801
{
1738-
TLS_TCX.with(|tcx| {
1739-
let (gcx, interners) = tcx.get().unwrap();
1740-
let gcx = unsafe { &*(gcx as *const GlobalCtxt) };
1741-
let interners = unsafe { &*(interners as *const CtxtInterners) };
1742-
f(TyCtxt {
1802+
with_thread_locals(|| {
1803+
let tcx = TyCtxt {
17431804
gcx,
1744-
interners,
1805+
interners: &gcx.global_interners,
1806+
};
1807+
let icx = ImplicitCtxt {
1808+
tcx,
1809+
query: None,
1810+
};
1811+
enter_context(&icx, |_| {
1812+
f(tcx)
17451813
})
17461814
})
17471815
}
17481816

1749-
pub fn with_opt<F, R>(f: F) -> R
1750-
where F: for<'a, 'gcx, 'tcx> FnOnce(Option<TyCtxt<'a, 'gcx, 'tcx>>) -> R
1817+
/// Allows access to the current ImplicitCtxt in a closure if one is available
1818+
pub fn with_context_opt<F, R>(f: F) -> R
1819+
where F: for<'a, 'gcx, 'tcx> FnOnce(Option<&ImplicitCtxt<'a, 'gcx, 'tcx>>) -> R
17511820
{
1752-
if TLS_TCX.with(|tcx| tcx.get().is_some()) {
1753-
with(|v| f(Some(v)))
1754-
} else {
1821+
let context = get_tlv();
1822+
if context == 0 {
17551823
f(None)
1824+
} else {
1825+
unsafe { f(Some(&*(context as *const ImplicitCtxt))) }
17561826
}
17571827
}
1828+
1829+
/// Allows access to the current ImplicitCtxt.
1830+
/// Panics if there is no ImplicitCtxt available
1831+
pub fn with_context<F, R>(f: F) -> R
1832+
where F: for<'a, 'gcx, 'tcx> FnOnce(&ImplicitCtxt<'a, 'gcx, 'tcx>) -> R
1833+
{
1834+
with_context_opt(|opt_context| f(opt_context.expect("no ImplicitCtxt stored in tls")))
1835+
}
1836+
1837+
/// Allows access to the current ImplicitCtxt whose tcx field has the same global
1838+
/// interner as the tcx argument passed in. This means the closure is given an ImplicitCtxt
1839+
/// with the same 'gcx lifetime as the TyCtxt passed in.
1840+
/// This will panic if you pass it a TyCtxt which has a different global interner from
1841+
/// the current ImplicitCtxt's tcx field.
1842+
pub fn with_related_context<'a, 'gcx, 'tcx1, F, R>(tcx: TyCtxt<'a, 'gcx, 'tcx1>, f: F) -> R
1843+
where F: for<'b, 'tcx2> FnOnce(&ImplicitCtxt<'b, 'gcx, 'tcx2>) -> R
1844+
{
1845+
with_context(|context| {
1846+
unsafe {
1847+
let gcx = tcx.gcx as *const _ as usize;
1848+
assert!(context.tcx.gcx as *const _ as usize == gcx);
1849+
let context: &ImplicitCtxt = mem::transmute(context);
1850+
f(context)
1851+
}
1852+
})
1853+
}
1854+
1855+
/// Allows access to the current ImplicitCtxt whose tcx field has the same global
1856+
/// interner and local interner as the tcx argument passed in. This means the closure
1857+
/// is given an ImplicitCtxt with the same 'tcx and 'gcx lifetimes as the TyCtxt passed in.
1858+
/// This will panic if you pass it a TyCtxt which has a different global interner or
1859+
/// a different local interner from the current ImplicitCtxt's tcx field.
1860+
pub fn with_fully_related_context<'a, 'gcx, 'tcx, F, R>(tcx: TyCtxt<'a, 'gcx, 'tcx>, f: F) -> R
1861+
where F: for<'b> FnOnce(&ImplicitCtxt<'b, 'gcx, 'tcx>) -> R
1862+
{
1863+
with_context(|context| {
1864+
unsafe {
1865+
let gcx = tcx.gcx as *const _ as usize;
1866+
let interners = tcx.interners as *const _ as usize;
1867+
assert!(context.tcx.gcx as *const _ as usize == gcx);
1868+
assert!(context.tcx.interners as *const _ as usize == interners);
1869+
let context: &ImplicitCtxt = mem::transmute(context);
1870+
f(context)
1871+
}
1872+
})
1873+
}
1874+
1875+
/// Allows access to the TyCtxt in the current ImplicitCtxt.
1876+
/// Panics if there is no ImplicitCtxt available
1877+
pub fn with<F, R>(f: F) -> R
1878+
where F: for<'a, 'gcx, 'tcx> FnOnce(TyCtxt<'a, 'gcx, 'tcx>) -> R
1879+
{
1880+
with_context(|context| f(context.tcx))
1881+
}
1882+
1883+
/// Allows access to the TyCtxt in the current ImplicitCtxt.
1884+
/// The closure is passed None if there is no ImplicitCtxt available
1885+
pub fn with_opt<F, R>(f: F) -> R
1886+
where F: for<'a, 'gcx, 'tcx> FnOnce(Option<TyCtxt<'a, 'gcx, 'tcx>>) -> R
1887+
{
1888+
with_context_opt(|opt_context| f(opt_context.map(|context| context.tcx)))
1889+
}
17581890
}
17591891

17601892
macro_rules! sty_debug_print {

src/librustc/ty/maps/job.rs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use rustc_data_structures::sync::{Lock, Lrc};
12+
use syntax_pos::Span;
13+
use ty::tls;
14+
use ty::maps::Query;
15+
use ty::maps::plumbing::CycleError;
16+
use ty::context::TyCtxt;
17+
use errors::Diagnostic;
18+
19+
/// Indicates the state of a query for a given key in a query map
20+
pub(super) enum QueryResult<'tcx, T> {
21+
/// An already executing query. The query job can be used to await for its completion
22+
Started(Lrc<QueryJob<'tcx>>),
23+
24+
/// The query is complete and produced `T`
25+
Complete(T),
26+
27+
/// The query panicked. Queries trying to wait on this will raise a fatal error / silently panic
28+
Poisoned,
29+
}
30+
31+
/// A span and a query key
32+
#[derive(Clone, Debug)]
33+
pub struct QueryInfo<'tcx> {
34+
pub span: Span,
35+
pub query: Query<'tcx>,
36+
}
37+
38+
/// A object representing an active query job.
39+
pub struct QueryJob<'tcx> {
40+
pub info: QueryInfo<'tcx>,
41+
42+
/// The parent query job which created this job and is implicitly waiting on it.
43+
pub parent: Option<Lrc<QueryJob<'tcx>>>,
44+
45+
/// Diagnostic messages which are emitted while the query executes
46+
pub diagnostics: Lock<Vec<Diagnostic>>,
47+
}
48+
49+
impl<'tcx> QueryJob<'tcx> {
50+
/// Creates a new query job
51+
pub fn new(info: QueryInfo<'tcx>, parent: Option<Lrc<QueryJob<'tcx>>>) -> Self {
52+
QueryJob {
53+
diagnostics: Lock::new(Vec::new()),
54+
info,
55+
parent,
56+
}
57+
}
58+
59+
/// Awaits for the query job to complete.
60+
///
61+
/// For single threaded rustc there's no concurrent jobs running, so if we are waiting for any
62+
/// query that means that there is a query cycle, thus this always running a cycle error.
63+
pub(super) fn await<'lcx>(
64+
&self,
65+
tcx: TyCtxt<'_, 'tcx, 'lcx>,
66+
span: Span,
67+
) -> Result<(), CycleError<'tcx>> {
68+
// Get the current executing query (waiter) and find the waitee amongst its parents
69+
let mut current_job = tls::with_related_context(tcx, |icx| icx.query.clone());
70+
let mut cycle = Vec::new();
71+
72+
while let Some(job) = current_job {
73+
cycle.insert(0, job.info.clone());
74+
75+
if &*job as *const _ == self as *const _ {
76+
break;
77+
}
78+
79+
current_job = job.parent.clone();
80+
}
81+
82+
Err(CycleError { span, cycle })
83+
}
84+
85+
/// Signals to waiters that the query is complete.
86+
///
87+
/// This does nothing for single threaded rustc,
88+
/// as there are no concurrent jobs which could be waiting on us
89+
pub fn signal_complete(&self) {}
90+
}

0 commit comments

Comments
 (0)