Introduce and use Scoped TLS.

It turns out that it's problematic to embed ThreadLocalStyleContext within
LayoutContext, because parameterizing the former on TElement (which we do
in the next patch) infects all the traversal stuff with the trait parameters,
which we don't really want.

In general, it probably makes sense to use separate scoped TLS types for
the separate DOM and Flow tree passes, so we can add a different ScopedTLS
type for the Flow pass if we ever need it.

We also reorder the |scope| and |shared| parameters in parallel.rs, because
it aligns more with the order in style/parallel.rs. I did this when I was
adding a TLS parameter to all these functions, which I realized we don't need
for now.
This commit is contained in:
Bobby Holley 2016-12-16 11:50:11 -08:00
parent 8f7f62f810
commit c5f01fe3b8
16 changed files with 212 additions and 205 deletions

View file

@ -84,10 +84,10 @@ pub struct ThreadLocalStyleContext {
}
impl ThreadLocalStyleContext {
pub fn new(local_context_creation_data: &ThreadLocalStyleContextCreationInfo) -> Self {
pub fn new(shared: &SharedStyleContext) -> Self {
ThreadLocalStyleContext {
style_sharing_candidate_cache: RefCell::new(StyleSharingCandidateCache::new()),
new_animations_sender: local_context_creation_data.new_animations_sender.clone(),
new_animations_sender: shared.local_context_creation_data.lock().unwrap().new_animations_sender.clone(),
}
}
}

View file

@ -1,27 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use context::{SharedStyleContext, ThreadLocalStyleContext};
use std::cell::RefCell;
use std::rc::Rc;
thread_local!(static LOCAL_CONTEXT_KEY: RefCell<Option<Rc<ThreadLocalStyleContext>>> = RefCell::new(None));
// Keep this implementation in sync with the one in components/layout/context.rs.
pub fn create_or_get_local_context(shared: &SharedStyleContext) -> Rc<ThreadLocalStyleContext> {
LOCAL_CONTEXT_KEY.with(|r| {
let mut r = r.borrow_mut();
if let Some(context) = r.clone() {
context
} else {
let context = Rc::new(ThreadLocalStyleContext::new(&shared.local_context_creation_data.lock().unwrap()));
*r = Some(context.clone());
context
}
})
}
pub fn clear_local_context() {
LOCAL_CONTEXT_KEY.with(|r| *r.borrow_mut() = None);
}

View file

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub mod context;
pub mod data;
pub mod restyle_damage;
pub mod snapshot;

View file

@ -6,9 +6,8 @@ use atomic_refcell::AtomicRefCell;
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
use data::ElementData;
use dom::{NodeInfo, TNode};
use gecko::context::create_or_get_local_context;
use gecko::wrapper::{GeckoElement, GeckoNode};
use std::rc::Rc; use traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at};
use traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at};
pub struct RecalcStyleOnly {
shared: SharedStyleContext,
@ -25,20 +24,22 @@ impl RecalcStyleOnly {
impl<'ln> DomTraversal<GeckoNode<'ln>> for RecalcStyleOnly {
type ThreadLocalContext = ThreadLocalStyleContext;
fn process_preorder(&self, node: GeckoNode<'ln>, traversal_data: &mut PerLevelTraversalData) {
fn process_preorder(&self, traversal_data: &mut PerLevelTraversalData,
thread_local: &mut ThreadLocalStyleContext,
node: GeckoNode<'ln>)
{
if node.is_element() {
let el = node.as_element().unwrap();
let mut data = unsafe { el.ensure_data() }.borrow_mut();
let tlc = self.create_or_get_thread_local_context();
let context = StyleContext {
let mut context = StyleContext {
shared: &self.shared,
thread_local: &*tlc,
thread_local: thread_local,
};
recalc_style_at(self, traversal_data, &context, el, &mut data);
recalc_style_at(self, traversal_data, &mut context, el, &mut data);
}
}
fn process_postorder(&self, _: GeckoNode<'ln>) {
fn process_postorder(&self, _: &mut ThreadLocalStyleContext, _: GeckoNode<'ln>) {
unreachable!();
}
@ -57,7 +58,7 @@ impl<'ln> DomTraversal<GeckoNode<'ln>> for RecalcStyleOnly {
&self.shared
}
fn create_or_get_thread_local_context(&self) -> Rc<ThreadLocalStyleContext> {
create_or_get_local_context(&self.shared)
fn create_thread_local_context(&self) -> ThreadLocalStyleContext {
ThreadLocalStyleContext::new(&self.shared)
}
}

View file

@ -115,6 +115,7 @@ pub mod parallel;
pub mod parser;
pub mod restyle_hints;
pub mod rule_tree;
pub mod scoped_tls;
pub mod selector_parser;
pub mod stylist;
#[cfg(feature = "servo")] #[allow(unsafe_code)] pub mod servo;

View file

@ -8,8 +8,8 @@
use dom::{OpaqueNode, TElement, TNode, UnsafeNode};
use rayon;
use scoped_tls::ScopedTLS;
use servo_config::opts;
use std::borrow::Borrow;
use std::sync::atomic::Ordering;
use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
use traversal::{STYLE_SHARING_CACHE_HITS, STYLE_SHARING_CACHE_MISSES};
@ -45,14 +45,15 @@ pub fn traverse_dom<N, D>(traversal: &D,
(vec![root.as_node().to_unsafe()], known_root_dom_depth)
};
let data = PerLevelTraversalData {
let traversal_data = PerLevelTraversalData {
current_dom_depth: depth,
};
let tls = ScopedTLS::<D::ThreadLocalContext>::new(queue);
let root = root.as_node().opaque();
queue.install(|| {
rayon::scope(|scope| {
traverse_nodes(nodes, root, data, scope, traversal);
traverse_nodes(nodes, root, traversal_data, scope, traversal, &tls);
});
});
@ -71,57 +72,60 @@ pub fn traverse_dom<N, D>(traversal: &D,
#[allow(unsafe_code)]
fn top_down_dom<'a, 'scope, N, D>(unsafe_nodes: &'a [UnsafeNode],
root: OpaqueNode,
mut data: PerLevelTraversalData,
mut traversal_data: PerLevelTraversalData,
scope: &'a rayon::Scope<'scope>,
traversal: &'scope D)
traversal: &'scope D,
tls: &'scope ScopedTLS<'scope, D::ThreadLocalContext>)
where N: TNode,
D: DomTraversal<N>,
{
let mut discovered_child_nodes = vec![];
for unsafe_node in unsafe_nodes {
// Get a real layout node.
let node = unsafe { N::from_unsafe(&unsafe_node) };
{
// Scope the borrow of the TLS so that the borrow is dropped before
// potentially traversing a child on this thread.
let mut tlc = tls.ensure(|| traversal.create_thread_local_context());
// Perform the appropriate traversal.
let mut children_to_process = 0isize;
traversal.process_preorder(node, &mut data);
if let Some(el) = node.as_element() {
D::traverse_children(el, |kid| {
children_to_process += 1;
discovered_child_nodes.push(kid.to_unsafe())
});
}
for unsafe_node in unsafe_nodes {
// Get a real layout node.
let node = unsafe { N::from_unsafe(&unsafe_node) };
// Reset the count of children if we need to do a bottom-up traversal
// after the top up.
if D::needs_postorder_traversal() {
if children_to_process == 0 {
// If there were no more children, start walking back up.
bottom_up_dom(root, *unsafe_node, traversal)
} else {
// Otherwise record the number of children to process when the
// time comes.
node.as_element().unwrap().store_children_to_process(children_to_process);
// Perform the appropriate traversal.
let mut children_to_process = 0isize;
traversal.process_preorder(&mut traversal_data, &mut *tlc, node);
if let Some(el) = node.as_element() {
D::traverse_children(el, |kid| {
children_to_process += 1;
discovered_child_nodes.push(kid.to_unsafe())
});
}
// Reset the count of children if we need to do a bottom-up traversal
// after the top up.
if D::needs_postorder_traversal() {
if children_to_process == 0 {
// If there were no more children, start walking back up.
bottom_up_dom(traversal, &mut *tlc, root, *unsafe_node)
} else {
// Otherwise record the number of children to process when the
// time comes.
node.as_element().unwrap().store_children_to_process(children_to_process);
}
}
}
}
// NB: In parallel traversal mode we have to purge the LRU cache in order to
// be able to access it without races.
let tlc = traversal.create_or_get_thread_local_context();
(*tlc).borrow().style_sharing_candidate_cache.borrow_mut().clear();
if let Some(ref mut depth) = data.current_dom_depth {
if let Some(ref mut depth) = traversal_data.current_dom_depth {
*depth += 1;
}
traverse_nodes(discovered_child_nodes, root, data, scope, traversal);
traverse_nodes(discovered_child_nodes, root, traversal_data, scope, traversal, tls);
}
fn traverse_nodes<'a, 'scope, N, D>(nodes: Vec<UnsafeNode>, root: OpaqueNode,
data: PerLevelTraversalData,
traversal_data: PerLevelTraversalData,
scope: &'a rayon::Scope<'scope>,
traversal: &'scope D)
traversal: &'scope D,
tls: &'scope ScopedTLS<'scope, D::ThreadLocalContext>)
where N: TNode,
D: DomTraversal<N>,
{
@ -133,17 +137,17 @@ fn traverse_nodes<'a, 'scope, N, D>(nodes: Vec<UnsafeNode>, root: OpaqueNode,
// we're only pushing one work unit.
if nodes.len() <= CHUNK_SIZE {
let nodes = nodes.into_boxed_slice();
top_down_dom(&nodes, root, data, scope, traversal);
top_down_dom(&nodes, root, traversal_data, scope, traversal, tls);
return;
}
// General case.
for chunk in nodes.chunks(CHUNK_SIZE) {
let nodes = chunk.iter().cloned().collect::<Vec<_>>().into_boxed_slice();
let data = data.clone();
let traversal_data = traversal_data.clone();
scope.spawn(move |scope| {
let nodes = nodes;
top_down_dom(&nodes, root, data, scope, traversal)
top_down_dom(&nodes, root, traversal_data, scope, traversal, tls)
})
}
}
@ -160,9 +164,10 @@ fn traverse_nodes<'a, 'scope, N, D>(nodes: Vec<UnsafeNode>, root: OpaqueNode,
/// The only communication between siblings is that they both
/// fetch-and-subtract the parent's children count.
#[allow(unsafe_code)]
fn bottom_up_dom<N, D>(root: OpaqueNode,
unsafe_node: UnsafeNode,
traversal: &D)
fn bottom_up_dom<N, D>(traversal: &D,
thread_local: &mut D::ThreadLocalContext,
root: OpaqueNode,
unsafe_node: UnsafeNode)
where N: TNode,
D: DomTraversal<N>
{
@ -170,7 +175,7 @@ fn bottom_up_dom<N, D>(root: OpaqueNode,
let mut node = unsafe { N::from_unsafe(&unsafe_node) };
loop {
// Perform the appropriate operation.
traversal.process_postorder(node);
traversal.process_postorder(thread_local, node);
if node.opaque() == root {
break;

View file

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![allow(unsafe_code)]
use rayon;
use std::cell::{Ref, RefCell, RefMut};
/// Stack-scoped thread-local storage for rayon thread pools.
pub struct ScopedTLS<'a, T: Send> {
pool: &'a rayon::ThreadPool,
slots: Box<[RefCell<Option<T>>]>,
}
unsafe impl<'a, T: Send> Sync for ScopedTLS<'a, T> {}
impl<'a, T: Send> ScopedTLS<'a, T> {
pub fn new(p: &'a rayon::ThreadPool) -> Self {
let count = p.num_threads();
let mut v = Vec::with_capacity(count);
for _ in 0..count {
v.push(RefCell::new(None));
}
ScopedTLS {
pool: p,
slots: v.into_boxed_slice(),
}
}
pub fn borrow(&self) -> Ref<Option<T>> {
let idx = self.pool.current_thread_index().unwrap();
self.slots[idx].borrow()
}
pub fn borrow_mut(&self) -> RefMut<Option<T>> {
let idx = self.pool.current_thread_index().unwrap();
self.slots[idx].borrow_mut()
}
pub fn ensure<F: FnOnce() -> T>(&self, f: F) -> RefMut<T> {
let mut opt = self.borrow_mut();
if opt.is_none() {
*opt = Some(f());
}
RefMut::map(opt, |x| x.as_mut().unwrap())
}
}

View file

@ -5,7 +5,6 @@
//! Implements sequential traversal over the DOM tree.
use dom::{TElement, TNode};
use std::borrow::Borrow;
use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
pub fn traverse_dom<N, D>(traversal: &D,
@ -16,43 +15,41 @@ pub fn traverse_dom<N, D>(traversal: &D,
{
debug_assert!(token.should_traverse());
fn doit<N, D>(traversal: &D, node: N, data: &mut PerLevelTraversalData)
fn doit<N, D>(traversal: &D, traversal_data: &mut PerLevelTraversalData,
thread_local: &mut D::ThreadLocalContext, node: N)
where N: TNode,
D: DomTraversal<N>
{
traversal.process_preorder(node, data);
traversal.process_preorder(traversal_data, thread_local, node);
if let Some(el) = node.as_element() {
if let Some(ref mut depth) = data.current_dom_depth {
if let Some(ref mut depth) = traversal_data.current_dom_depth {
*depth += 1;
}
D::traverse_children(el, |kid| doit(traversal, kid, data));
D::traverse_children(el, |kid| doit(traversal, traversal_data, thread_local, kid));
if let Some(ref mut depth) = data.current_dom_depth {
if let Some(ref mut depth) = traversal_data.current_dom_depth {
*depth -= 1;
}
}
if D::needs_postorder_traversal() {
traversal.process_postorder(node);
traversal.process_postorder(thread_local, node);
}
}
let mut data = PerLevelTraversalData {
let mut traversal_data = PerLevelTraversalData {
current_dom_depth: None,
};
let mut tlc = traversal.create_thread_local_context();
if token.traverse_unstyled_children_only() {
for kid in root.as_node().children() {
if kid.as_element().map_or(false, |el| el.get_data().is_none()) {
doit(traversal, kid, &mut data);
doit(traversal, &mut traversal_data, &mut tlc, kid);
}
}
} else {
doit(traversal, root.as_node(), &mut data);
doit(traversal, &mut traversal_data, &mut tlc, root.as_node());
}
// Clear the local LRU cache since we store stateful elements inside.
let tlc = traversal.create_or_get_thread_local_context();
(*tlc).borrow().style_sharing_candidate_cache.borrow_mut().clear();
}

View file

@ -6,7 +6,7 @@
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use bloom::StyleBloom;
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
use context::{SharedStyleContext, StyleContext};
use data::{ElementData, StoredRestyleHint};
use dom::{OpaqueNode, TElement, TNode};
use matching::{MatchMethods, StyleSharingResult};
@ -15,10 +15,8 @@ use selector_parser::RestyleDamage;
use selectors::Element;
use selectors::matching::StyleRelations;
use servo_config::opts;
use std::borrow::Borrow;
use std::cell::RefCell;
use std::mem;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use stylist::Stylist;
@ -128,15 +126,16 @@ impl LogBehavior {
}
pub trait DomTraversal<N: TNode> : Sync {
type ThreadLocalContext: Borrow<ThreadLocalStyleContext>;
type ThreadLocalContext: Send;
/// Process `node` on the way down, before its children have been processed.
fn process_preorder(&self, node: N, data: &mut PerLevelTraversalData);
fn process_preorder(&self, data: &mut PerLevelTraversalData,
thread_local: &mut Self::ThreadLocalContext, node: N);
/// Process `node` on the way up, after its children have been processed.
///
/// This is only executed if `needs_postorder_traversal` returns true.
fn process_postorder(&self, node: N);
fn process_postorder(&self, thread_local: &mut Self::ThreadLocalContext, node: N);
/// Boolean that specifies whether a bottom up traversal should be
/// performed.
@ -321,7 +320,7 @@ pub trait DomTraversal<N: TNode> : Sync {
fn shared_context(&self) -> &SharedStyleContext;
fn create_or_get_thread_local_context(&self) -> Rc<Self::ThreadLocalContext>;
fn create_thread_local_context(&self) -> Self::ThreadLocalContext;
}
/// Determines the amount of relations where we're going to share style.