style/scoped_tls.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Stack-scoped thread-local storage for rayon thread pools.
6
7#![allow(unsafe_code)]
8#![deny(missing_docs)]
9
10use crate::global_style_data::STYLO_MAX_THREADS;
11use std::cell::{Ref, RefCell, RefMut};
12use std::ops::DerefMut;
13
14/// A scoped TLS set, that is alive during the `'scope` lifetime.
15///
16/// We use this on Servo to construct thread-local contexts, but clear them once
17/// we're done with restyling.
18///
19/// Note that the cleanup is done on the thread that owns the scoped TLS, thus
20/// the Send bound.
21pub struct ScopedTLS<'scope, T: Send> {
22 pool: Option<&'scope rayon::ThreadPool>,
23 slots: [RefCell<Option<T>>; STYLO_MAX_THREADS],
24}
25
26/// The scoped TLS is `Sync` because no more than one worker thread can access a
27/// given slot.
28unsafe impl<'scope, T: Send> Sync for ScopedTLS<'scope, T> {}
29
30impl<'scope, T: Send> ScopedTLS<'scope, T> {
31 /// Create a new scoped TLS that will last as long as this rayon threadpool
32 /// reference.
33 pub fn new(pool: Option<&'scope rayon::ThreadPool>) -> Self {
34 debug_assert!(pool.map_or(true, |p| p.current_num_threads() <= STYLO_MAX_THREADS));
35 ScopedTLS {
36 pool,
37 slots: Default::default(),
38 }
39 }
40
41 /// Returns the index corresponding to the calling thread in the thread pool.
42 #[inline]
43 pub fn current_thread_index(&self) -> usize {
44 self.pool.map_or(0, |p| p.current_thread_index().unwrap())
45 }
46
47 /// Return an immutable reference to the `Option<T>` that this thread owns.
48 pub fn borrow(&self) -> Ref<'_, Option<T>> {
49 let idx = self.current_thread_index();
50 self.slots[idx].borrow()
51 }
52
53 /// Return a mutable reference to the `Option<T>` that this thread owns.
54 pub fn borrow_mut(&self) -> RefMut<'_, Option<T>> {
55 let idx = self.current_thread_index();
56 self.slots[idx].borrow_mut()
57 }
58
59 /// Ensure that the current data this thread owns is initialized, or
60 /// initialize it using `f`. We want ensure() to be fast and inline, and we
61 /// want to inline the memmove that initializes the Option<T>. But we don't
62 /// want to inline space for the entire large T struct in our stack frame.
63 /// That's why we hand `f` a mutable borrow to write to instead of just
64 /// having it return a T.
65 #[inline(always)]
66 pub fn ensure<F: FnOnce(&mut Option<T>)>(&self, f: F) -> RefMut<'_, T> {
67 let mut opt = self.borrow_mut();
68 if opt.is_none() {
69 f(opt.deref_mut());
70 }
71
72 RefMut::map(opt, |x| x.as_mut().unwrap())
73 }
74
75 /// Returns the slots. Safe because if we have a mut reference the tls can't be referenced by
76 /// any other thread.
77 pub fn slots(&mut self) -> &mut [RefCell<Option<T>>] {
78 &mut self.slots
79 }
80}