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

@ -19,7 +19,6 @@ use net_traits::image_cache_thread::{ImageOrMetadataAvailable, UsePlaceholder};
use parking_lot::RwLock; use parking_lot::RwLock;
use servo_config::opts; use servo_config::opts;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use std::borrow::Borrow;
use std::cell::{RefCell, RefMut}; use std::cell::{RefCell, RefMut};
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
@ -27,59 +26,64 @@ use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use style::context::{SharedStyleContext, ThreadLocalStyleContext}; use style::context::{SharedStyleContext, ThreadLocalStyleContext};
pub struct ThreadLocalLayoutContext { /// TLS data scoped to the traversal.
pub struct ScopedThreadLocalLayoutContext {
pub style_context: ThreadLocalStyleContext, pub style_context: ThreadLocalStyleContext,
}
impl ScopedThreadLocalLayoutContext {
pub fn new(shared: &SharedLayoutContext) -> Self {
ScopedThreadLocalLayoutContext {
style_context: ThreadLocalStyleContext::new(&shared.style_context),
}
}
}
/// TLS data that persists across traversals.
pub struct PersistentThreadLocalLayoutContext {
// FontContext uses Rc all over the place and so isn't Send, which means we
// can't use ScopedTLS for it. There's also no reason to scope it to the
// traversal, and performance is probably better if we don't.
pub font_context: RefCell<FontContext>, pub font_context: RefCell<FontContext>,
} }
impl ThreadLocalLayoutContext { impl PersistentThreadLocalLayoutContext {
pub fn new(shared: &SharedLayoutContext) -> Rc<Self> { pub fn new(shared: &SharedLayoutContext) -> Rc<Self> {
let font_cache_thread = shared.font_cache_thread.lock().unwrap().clone(); let font_cache_thread = shared.font_cache_thread.lock().unwrap().clone();
let local_style_data = shared.style_context.local_context_creation_data.lock().unwrap(); Rc::new(PersistentThreadLocalLayoutContext {
Rc::new(ThreadLocalLayoutContext {
style_context: ThreadLocalStyleContext::new(&local_style_data),
font_context: RefCell::new(FontContext::new(font_cache_thread)), font_context: RefCell::new(FontContext::new(font_cache_thread)),
}) })
} }
} }
impl Borrow<ThreadLocalStyleContext> for ThreadLocalLayoutContext { impl HeapSizeOf for PersistentThreadLocalLayoutContext {
fn borrow(&self) -> &ThreadLocalStyleContext {
&self.style_context
}
}
impl HeapSizeOf for ThreadLocalLayoutContext {
// FIXME(njn): measure other fields eventually.
fn heap_size_of_children(&self) -> usize { fn heap_size_of_children(&self) -> usize {
self.font_context.heap_size_of_children() self.font_context.heap_size_of_children()
} }
} }
thread_local!(static LOCAL_CONTEXT_KEY: RefCell<Option<Rc<ThreadLocalLayoutContext>>> = RefCell::new(None)); thread_local!(static LOCAL_CONTEXT_KEY: RefCell<Option<Rc<PersistentThreadLocalLayoutContext>>> = RefCell::new(None));
pub fn heap_size_of_local_context() -> usize { fn create_or_get_persistent_context(shared: &SharedLayoutContext)
LOCAL_CONTEXT_KEY.with(|r| { -> Rc<PersistentThreadLocalLayoutContext> {
r.borrow().clone().map_or(0, |context| context.heap_size_of_children())
})
}
// Keep this implementation in sync with the one in ports/geckolib/traversal.rs.
pub fn create_or_get_local_context(shared: &SharedLayoutContext)
-> Rc<ThreadLocalLayoutContext> {
LOCAL_CONTEXT_KEY.with(|r| { LOCAL_CONTEXT_KEY.with(|r| {
let mut r = r.borrow_mut(); let mut r = r.borrow_mut();
if let Some(context) = r.clone() { if let Some(context) = r.clone() {
context context
} else { } else {
let context = ThreadLocalLayoutContext::new(shared); let context = PersistentThreadLocalLayoutContext::new(shared);
*r = Some(context.clone()); *r = Some(context.clone());
context context
} }
}) })
} }
pub fn heap_size_of_persistent_local_context() -> usize {
LOCAL_CONTEXT_KEY.with(|r| {
r.borrow().clone().map_or(0, |context| context.heap_size_of_children())
})
}
/// Layout information shared among all workers. This must be thread-safe. /// Layout information shared among all workers. This must be thread-safe.
pub struct SharedLayoutContext { pub struct SharedLayoutContext {
/// Bits shared by the layout and style system. /// Bits shared by the layout and style system.
@ -100,24 +104,17 @@ pub struct SharedLayoutContext {
BuildHasherDefault<FnvHasher>>>>, BuildHasherDefault<FnvHasher>>>>,
} }
impl Borrow<SharedStyleContext> for SharedLayoutContext {
fn borrow(&self) -> &SharedStyleContext {
&self.style_context
}
}
pub struct LayoutContext<'a> { pub struct LayoutContext<'a> {
pub shared: &'a SharedLayoutContext, pub shared: &'a SharedLayoutContext,
pub thread_local: &'a ThreadLocalLayoutContext, pub persistent: Rc<PersistentThreadLocalLayoutContext>,
} }
impl<'a> LayoutContext<'a> { impl<'a> LayoutContext<'a> {
pub fn new(shared: &'a SharedLayoutContext, pub fn new(shared: &'a SharedLayoutContext) -> Self
thread_local: &'a ThreadLocalLayoutContext) -> Self
{ {
LayoutContext { LayoutContext {
shared: shared, shared: shared,
thread_local: thread_local, persistent: create_or_get_persistent_context(shared),
} }
} }
} }
@ -138,7 +135,7 @@ impl<'a> LayoutContext<'a> {
#[inline(always)] #[inline(always)]
pub fn font_context(&self) -> RefMut<FontContext> { pub fn font_context(&self) -> RefMut<FontContext> {
self.thread_local.font_context.borrow_mut() self.persistent.font_context.borrow_mut()
} }
} }

View file

@ -8,8 +8,7 @@
#![allow(unsafe_code)] #![allow(unsafe_code)]
use context::{LayoutContext, SharedLayoutContext, ThreadLocalLayoutContext}; use context::{LayoutContext, SharedLayoutContext};
use context::create_or_get_local_context;
use flow::{self, Flow, MutableFlowUtils, PostorderFlowTraversal, PreorderFlowTraversal}; use flow::{self, Flow, MutableFlowUtils, PostorderFlowTraversal, PreorderFlowTraversal};
use flow_ref::FlowRef; use flow_ref::FlowRef;
use profile_traits::time::{self, TimerMetadata, profile}; use profile_traits::time::{self, TimerMetadata, profile};
@ -51,7 +50,7 @@ pub fn borrowed_flow_to_unsafe_flow(flow: &Flow) -> UnsafeFlow {
} }
pub type ChunkedFlowTraversalFunction<'scope> = pub type ChunkedFlowTraversalFunction<'scope> =
extern "Rust" fn(Box<[UnsafeFlow]>, &'scope SharedLayoutContext, &rayon::Scope<'scope>); extern "Rust" fn(Box<[UnsafeFlow]>, &rayon::Scope<'scope>, &'scope SharedLayoutContext);
pub type FlowTraversalFunction = extern "Rust" fn(UnsafeFlow, &LayoutContext); pub type FlowTraversalFunction = extern "Rust" fn(UnsafeFlow, &LayoutContext);
@ -133,23 +132,22 @@ trait ParallelPostorderFlowTraversal : PostorderFlowTraversal {
trait ParallelPreorderFlowTraversal : PreorderFlowTraversal { trait ParallelPreorderFlowTraversal : PreorderFlowTraversal {
fn run_parallel<'scope>(&self, fn run_parallel<'scope>(&self,
unsafe_flows: &[UnsafeFlow], unsafe_flows: &[UnsafeFlow],
layout_context: &'scope SharedLayoutContext, scope: &rayon::Scope<'scope>,
scope: &rayon::Scope<'scope>); shared: &'scope SharedLayoutContext);
fn should_record_thread_ids(&self) -> bool; fn should_record_thread_ids(&self) -> bool;
#[inline(always)] #[inline(always)]
fn run_parallel_helper<'scope>(&self, fn run_parallel_helper<'scope>(&self,
unsafe_flows: &[UnsafeFlow], unsafe_flows: &[UnsafeFlow],
shared: &'scope SharedLayoutContext,
scope: &rayon::Scope<'scope>, scope: &rayon::Scope<'scope>,
shared: &'scope SharedLayoutContext,
top_down_func: ChunkedFlowTraversalFunction<'scope>, top_down_func: ChunkedFlowTraversalFunction<'scope>,
bottom_up_func: FlowTraversalFunction) bottom_up_func: FlowTraversalFunction)
{ {
let tlc = create_or_get_local_context(shared);
let context = LayoutContext::new(&shared, &*tlc);
let mut discovered_child_flows = vec![]; let mut discovered_child_flows = vec![];
let context = LayoutContext::new(&shared);
for unsafe_flow in unsafe_flows { for unsafe_flow in unsafe_flows {
let mut had_children = false; let mut had_children = false;
unsafe { unsafe {
@ -187,7 +185,7 @@ trait ParallelPreorderFlowTraversal : PreorderFlowTraversal {
let nodes = chunk.iter().cloned().collect::<Vec<_>>().into_boxed_slice(); let nodes = chunk.iter().cloned().collect::<Vec<_>>().into_boxed_slice();
scope.spawn(move |scope| { scope.spawn(move |scope| {
top_down_func(nodes, shared, scope); top_down_func(nodes, scope, shared);
}); });
} }
} }
@ -196,12 +194,12 @@ trait ParallelPreorderFlowTraversal : PreorderFlowTraversal {
impl<'a> ParallelPreorderFlowTraversal for AssignISizes<'a> { impl<'a> ParallelPreorderFlowTraversal for AssignISizes<'a> {
fn run_parallel<'scope>(&self, fn run_parallel<'scope>(&self,
unsafe_flows: &[UnsafeFlow], unsafe_flows: &[UnsafeFlow],
layout_context: &'scope SharedLayoutContext, scope: &rayon::Scope<'scope>,
scope: &rayon::Scope<'scope>) shared: &'scope SharedLayoutContext)
{ {
self.run_parallel_helper(unsafe_flows, self.run_parallel_helper(unsafe_flows,
layout_context,
scope, scope,
shared,
assign_inline_sizes, assign_inline_sizes,
assign_block_sizes_and_store_overflow) assign_block_sizes_and_store_overflow)
} }
@ -214,12 +212,12 @@ impl<'a> ParallelPreorderFlowTraversal for AssignISizes<'a> {
impl<'a> ParallelPostorderFlowTraversal for AssignBSizes<'a> {} impl<'a> ParallelPostorderFlowTraversal for AssignBSizes<'a> {}
fn assign_inline_sizes<'scope>(unsafe_flows: Box<[UnsafeFlow]>, fn assign_inline_sizes<'scope>(unsafe_flows: Box<[UnsafeFlow]>,
shared_layout_context: &'scope SharedLayoutContext, scope: &rayon::Scope<'scope>,
scope: &rayon::Scope<'scope>) { shared: &'scope SharedLayoutContext) {
let assign_inline_sizes_traversal = AssignISizes { let assign_inline_sizes_traversal = AssignISizes {
shared_context: &shared_layout_context.style_context, shared_context: &shared.style_context,
}; };
assign_inline_sizes_traversal.run_parallel(&unsafe_flows, shared_layout_context, scope) assign_inline_sizes_traversal.run_parallel(&unsafe_flows, scope, shared)
} }
fn assign_block_sizes_and_store_overflow( fn assign_block_sizes_and_store_overflow(
@ -238,8 +236,7 @@ pub fn traverse_flow_tree_preorder(
shared: &SharedLayoutContext, shared: &SharedLayoutContext,
queue: &rayon::ThreadPool) { queue: &rayon::ThreadPool) {
if opts::get().bubble_inline_sizes_separately { if opts::get().bubble_inline_sizes_separately {
let tlc = ThreadLocalLayoutContext::new(shared); let context = LayoutContext::new(shared);
let context = LayoutContext::new(shared, &*tlc);
let bubble_inline_sizes = BubbleISizes { layout_context: &context }; let bubble_inline_sizes = BubbleISizes { layout_context: &context };
root.traverse_postorder(&bubble_inline_sizes); root.traverse_postorder(&bubble_inline_sizes);
} }
@ -250,7 +247,7 @@ pub fn traverse_flow_tree_preorder(
rayon::scope(move |scope| { rayon::scope(move |scope| {
profile(time::ProfilerCategory::LayoutParallelWarmup, profile(time::ProfilerCategory::LayoutParallelWarmup,
profiler_metadata, time_profiler_chan, move || { profiler_metadata, time_profiler_chan, move || {
assign_inline_sizes(nodes, &shared, scope); assign_inline_sizes(nodes, scope, &shared);
}); });
}); });
}); });

View file

@ -6,8 +6,7 @@
use app_units::Au; use app_units::Au;
use construct::ConstructionResult; use construct::ConstructionResult;
use context::SharedLayoutContext; use context::{ScopedThreadLocalLayoutContext, SharedLayoutContext};
use context::create_or_get_local_context;
use euclid::point::Point2D; use euclid::point::Point2D;
use euclid::rect::Rect; use euclid::rect::Rect;
use euclid::size::Size2D; use euclid::size::Size2D;
@ -646,10 +645,10 @@ pub fn process_resolved_style_request<'a, N>(shared: &SharedLayoutContext,
// we'd need a mechanism to prevent detect when it's stale (since we don't // we'd need a mechanism to prevent detect when it's stale (since we don't
// traverse display:none subtrees during restyle). // traverse display:none subtrees during restyle).
let display_none_root = if element.get_data().is_none() { let display_none_root = if element.get_data().is_none() {
let tlc = create_or_get_local_context(shared); let mut tlc = ScopedThreadLocalLayoutContext::new(shared);
let context = StyleContext { let context = StyleContext {
shared: &shared.style_context, shared: &shared.style_context,
thread_local: &tlc.style_context, thread_local: &mut tlc.style_context,
}; };
Some(style_element_in_display_none_subtree(&context, element, Some(style_element_in_display_none_subtree(&context, element,

View file

@ -5,7 +5,7 @@
//! Implements sequential traversals over the DOM and flow trees. //! Implements sequential traversals over the DOM and flow trees.
use app_units::Au; use app_units::Au;
use context::{LayoutContext, SharedLayoutContext, ThreadLocalLayoutContext}; use context::{LayoutContext, SharedLayoutContext};
use display_list_builder::DisplayListBuildState; use display_list_builder::DisplayListBuildState;
use euclid::point::Point2D; use euclid::point::Point2D;
use floats::SpeculatedFloatPlacement; use floats::SpeculatedFloatPlacement;
@ -34,8 +34,7 @@ pub fn resolve_generated_content(root: &mut Flow, shared: &SharedLayoutContext)
} }
} }
let tlc = ThreadLocalLayoutContext::new(shared); let layout_context = LayoutContext::new(shared);
let layout_context = LayoutContext::new(shared, &*tlc);
let mut traversal = ResolveGeneratedContent::new(&layout_context); let mut traversal = ResolveGeneratedContent::new(&layout_context);
doit(root, 0, &mut traversal) doit(root, 0, &mut traversal)
} }
@ -58,8 +57,7 @@ pub fn traverse_flow_tree_preorder(root: &mut Flow,
} }
} }
let tlc = ThreadLocalLayoutContext::new(shared); let layout_context = LayoutContext::new(shared);
let layout_context = LayoutContext::new(shared, &*tlc);
if opts::get().bubble_inline_sizes_separately { if opts::get().bubble_inline_sizes_separately {
let bubble_inline_sizes = BubbleISizes { layout_context: &layout_context }; let bubble_inline_sizes = BubbleISizes { layout_context: &layout_context };

View file

@ -5,15 +5,13 @@
//! Traversals over the DOM and flow trees, running the layout computations. //! Traversals over the DOM and flow trees, running the layout computations.
use construct::FlowConstructor; use construct::FlowConstructor;
use context::{LayoutContext, SharedLayoutContext, ThreadLocalLayoutContext}; use context::{LayoutContext, ScopedThreadLocalLayoutContext, SharedLayoutContext};
use context::create_or_get_local_context;
use display_list_builder::DisplayListBuildState; use display_list_builder::DisplayListBuildState;
use flow::{self, PreorderFlowTraversal}; use flow::{self, PreorderFlowTraversal};
use flow::{CAN_BE_FRAGMENTED, Flow, ImmutableFlowUtils, PostorderFlowTraversal}; use flow::{CAN_BE_FRAGMENTED, Flow, ImmutableFlowUtils, PostorderFlowTraversal};
use gfx::display_list::OpaqueNode; use gfx::display_list::OpaqueNode;
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_config::opts; use servo_config::opts;
use std::rc::Rc;
use style::atomic_refcell::AtomicRefCell; use style::atomic_refcell::AtomicRefCell;
use style::context::{SharedStyleContext, StyleContext}; use style::context::{SharedStyleContext, StyleContext};
use style::data::ElementData; use style::data::ElementData;
@ -56,9 +54,10 @@ impl<N> DomTraversal<N> for RecalcStyleAndConstructFlows
where N: LayoutNode + TNode, where N: LayoutNode + TNode,
N::ConcreteElement: TElement N::ConcreteElement: TElement
{ {
type ThreadLocalContext = ThreadLocalLayoutContext; type ThreadLocalContext = ScopedThreadLocalLayoutContext;
fn process_preorder(&self, node: N, traversal_data: &mut PerLevelTraversalData) { fn process_preorder(&self, traversal_data: &mut PerLevelTraversalData,
thread_local: &mut Self::ThreadLocalContext, node: N) {
// FIXME(pcwalton): Stop allocating here. Ideally this should just be // FIXME(pcwalton): Stop allocating here. Ideally this should just be
// done by the HTML parser. // done by the HTML parser.
node.initialize_data(); node.initialize_data();
@ -66,19 +65,17 @@ impl<N> DomTraversal<N> for RecalcStyleAndConstructFlows
if !node.is_text_node() { if !node.is_text_node() {
let el = node.as_element().unwrap(); let el = node.as_element().unwrap();
let mut data = el.mutate_data().unwrap(); let mut data = el.mutate_data().unwrap();
let tlc = create_or_get_local_context(&self.shared); let mut context = StyleContext {
let context = StyleContext {
shared: &self.shared.style_context, shared: &self.shared.style_context,
thread_local: &tlc.style_context, thread_local: &mut thread_local.style_context,
}; };
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, node: N) { fn process_postorder(&self, thread_local: &mut Self::ThreadLocalContext, node: N) {
let tlc = create_or_get_local_context(&self.shared); let context = LayoutContext::new(&self.shared);
let context = LayoutContext::new(&self.shared, &*tlc); construct_flows_at(&context, thread_local, self.root, node);
construct_flows_at(&context, self.root, node);
} }
fn text_node_needs_traversal(node: N) -> bool { fn text_node_needs_traversal(node: N) -> bool {
@ -103,8 +100,8 @@ impl<N> DomTraversal<N> for RecalcStyleAndConstructFlows
&self.shared.style_context &self.shared.style_context
} }
fn create_or_get_thread_local_context(&self) -> Rc<ThreadLocalLayoutContext> { fn create_thread_local_context(&self) -> Self::ThreadLocalContext {
create_or_get_local_context(&self.shared) ScopedThreadLocalLayoutContext::new(&self.shared)
} }
} }
@ -117,7 +114,9 @@ pub trait PostorderNodeMutTraversal<ConcreteThreadSafeLayoutNode: ThreadSafeLayo
/// The flow construction traversal, which builds flows for styled nodes. /// The flow construction traversal, which builds flows for styled nodes.
#[inline] #[inline]
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn construct_flows_at<'a, N>(context: &LayoutContext<'a>, root: OpaqueNode, node: N) fn construct_flows_at<'a, N>(context: &LayoutContext<'a>,
_thread_local: &ScopedThreadLocalLayoutContext,
root: OpaqueNode, node: N)
where N: LayoutNode, where N: LayoutNode,
{ {
debug!("construct_flows_at: {:?}", node); debug!("construct_flows_at: {:?}", node);

View file

@ -63,7 +63,8 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER; use ipc_channel::router::ROUTER;
use layout::animation; use layout::animation;
use layout::construct::ConstructionResult; use layout::construct::ConstructionResult;
use layout::context::{LayoutContext, SharedLayoutContext, ThreadLocalLayoutContext, heap_size_of_local_context}; use layout::context::{LayoutContext, SharedLayoutContext};
use layout::context::heap_size_of_persistent_local_context;
use layout::display_list_builder::ToGfxColor; use layout::display_list_builder::ToGfxColor;
use layout::flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils}; use layout::flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
use layout::flow_ref::FlowRef; use layout::flow_ref::FlowRef;
@ -723,11 +724,11 @@ impl LayoutThread {
size: stylist.heap_size_of_children(), size: stylist.heap_size_of_children(),
}); });
// The LayoutThread has a context in TLS... // The LayoutThread has data in Persistent TLS...
reports.push(Report { reports.push(Report {
path: path![formatted_url, "layout-thread", "local-context"], path: path![formatted_url, "layout-thread", "local-context"],
kind: ReportKind::ExplicitJemallocHeapSize, kind: ReportKind::ExplicitJemallocHeapSize,
size: heap_size_of_local_context(), size: heap_size_of_persistent_local_context(),
}); });
reports_chan.send(reports); reports_chan.send(reports);
@ -1447,8 +1448,7 @@ impl LayoutThread {
self.profiler_metadata(), self.profiler_metadata(),
self.time_profiler_chan.clone(), self.time_profiler_chan.clone(),
|| { || {
let tlc = ThreadLocalLayoutContext::new(&shared); let context = LayoutContext::new(&shared);
let context = LayoutContext::new(&shared, &*tlc);
sequential::store_overflow(&context, sequential::store_overflow(&context,
FlowRef::deref_mut(&mut root_flow) as &mut Flow); FlowRef::deref_mut(&mut root_flow) as &mut Flow);
}); });

View file

@ -84,10 +84,10 @@ pub struct ThreadLocalStyleContext {
} }
impl ThreadLocalStyleContext { impl ThreadLocalStyleContext {
pub fn new(local_context_creation_data: &ThreadLocalStyleContextCreationInfo) -> Self { pub fn new(shared: &SharedStyleContext) -> Self {
ThreadLocalStyleContext { ThreadLocalStyleContext {
style_sharing_candidate_cache: RefCell::new(StyleSharingCandidateCache::new()), 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub mod context;
pub mod data; pub mod data;
pub mod restyle_damage; pub mod restyle_damage;
pub mod snapshot; pub mod snapshot;

View file

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

View file

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

View file

@ -8,8 +8,8 @@
use dom::{OpaqueNode, TElement, TNode, UnsafeNode}; use dom::{OpaqueNode, TElement, TNode, UnsafeNode};
use rayon; use rayon;
use scoped_tls::ScopedTLS;
use servo_config::opts; use servo_config::opts;
use std::borrow::Borrow;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken}; use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
use traversal::{STYLE_SHARING_CACHE_HITS, STYLE_SHARING_CACHE_MISSES}; 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) (vec![root.as_node().to_unsafe()], known_root_dom_depth)
}; };
let data = PerLevelTraversalData { let traversal_data = PerLevelTraversalData {
current_dom_depth: depth, current_dom_depth: depth,
}; };
let tls = ScopedTLS::<D::ThreadLocalContext>::new(queue);
let root = root.as_node().opaque(); let root = root.as_node().opaque();
queue.install(|| { queue.install(|| {
rayon::scope(|scope| { 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)] #[allow(unsafe_code)]
fn top_down_dom<'a, 'scope, N, D>(unsafe_nodes: &'a [UnsafeNode], fn top_down_dom<'a, 'scope, N, D>(unsafe_nodes: &'a [UnsafeNode],
root: OpaqueNode, root: OpaqueNode,
mut data: PerLevelTraversalData, mut traversal_data: PerLevelTraversalData,
scope: &'a rayon::Scope<'scope>, scope: &'a rayon::Scope<'scope>,
traversal: &'scope D) traversal: &'scope D,
tls: &'scope ScopedTLS<'scope, D::ThreadLocalContext>)
where N: TNode, where N: TNode,
D: DomTraversal<N>, D: DomTraversal<N>,
{ {
let mut discovered_child_nodes = vec![]; let mut discovered_child_nodes = vec![];
for unsafe_node in unsafe_nodes { {
// Get a real layout node. // Scope the borrow of the TLS so that the borrow is dropped before
let node = unsafe { N::from_unsafe(&unsafe_node) }; // potentially traversing a child on this thread.
let mut tlc = tls.ensure(|| traversal.create_thread_local_context());
// Perform the appropriate traversal. for unsafe_node in unsafe_nodes {
let mut children_to_process = 0isize; // Get a real layout node.
traversal.process_preorder(node, &mut data); let node = unsafe { N::from_unsafe(&unsafe_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 // Perform the appropriate traversal.
// after the top up. let mut children_to_process = 0isize;
if D::needs_postorder_traversal() { traversal.process_preorder(&mut traversal_data, &mut *tlc, node);
if children_to_process == 0 { if let Some(el) = node.as_element() {
// If there were no more children, start walking back up. D::traverse_children(el, |kid| {
bottom_up_dom(root, *unsafe_node, traversal) children_to_process += 1;
} else { discovered_child_nodes.push(kid.to_unsafe())
// Otherwise record the number of children to process when the });
// time comes. }
node.as_element().unwrap().store_children_to_process(children_to_process);
// 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 if let Some(ref mut depth) = traversal_data.current_dom_depth {
// 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 {
*depth += 1; *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, fn traverse_nodes<'a, 'scope, N, D>(nodes: Vec<UnsafeNode>, root: OpaqueNode,
data: PerLevelTraversalData, traversal_data: PerLevelTraversalData,
scope: &'a rayon::Scope<'scope>, scope: &'a rayon::Scope<'scope>,
traversal: &'scope D) traversal: &'scope D,
tls: &'scope ScopedTLS<'scope, D::ThreadLocalContext>)
where N: TNode, where N: TNode,
D: DomTraversal<N>, 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. // we're only pushing one work unit.
if nodes.len() <= CHUNK_SIZE { if nodes.len() <= CHUNK_SIZE {
let nodes = nodes.into_boxed_slice(); 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; return;
} }
// General case. // General case.
for chunk in nodes.chunks(CHUNK_SIZE) { for chunk in nodes.chunks(CHUNK_SIZE) {
let nodes = chunk.iter().cloned().collect::<Vec<_>>().into_boxed_slice(); let nodes = chunk.iter().cloned().collect::<Vec<_>>().into_boxed_slice();
let data = data.clone(); let traversal_data = traversal_data.clone();
scope.spawn(move |scope| { scope.spawn(move |scope| {
let nodes = nodes; 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 /// The only communication between siblings is that they both
/// fetch-and-subtract the parent's children count. /// fetch-and-subtract the parent's children count.
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn bottom_up_dom<N, D>(root: OpaqueNode, fn bottom_up_dom<N, D>(traversal: &D,
unsafe_node: UnsafeNode, thread_local: &mut D::ThreadLocalContext,
traversal: &D) root: OpaqueNode,
unsafe_node: UnsafeNode)
where N: TNode, where N: TNode,
D: DomTraversal<N> D: DomTraversal<N>
{ {
@ -170,7 +175,7 @@ fn bottom_up_dom<N, D>(root: OpaqueNode,
let mut node = unsafe { N::from_unsafe(&unsafe_node) }; let mut node = unsafe { N::from_unsafe(&unsafe_node) };
loop { loop {
// Perform the appropriate operation. // Perform the appropriate operation.
traversal.process_postorder(node); traversal.process_postorder(thread_local, node);
if node.opaque() == root { if node.opaque() == root {
break; 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. //! Implements sequential traversal over the DOM tree.
use dom::{TElement, TNode}; use dom::{TElement, TNode};
use std::borrow::Borrow;
use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken}; use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
pub fn traverse_dom<N, D>(traversal: &D, 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()); 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, where N: TNode,
D: DomTraversal<N> 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(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; *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; *depth -= 1;
} }
} }
if D::needs_postorder_traversal() { 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, current_dom_depth: None,
}; };
let mut tlc = traversal.create_thread_local_context();
if token.traverse_unstyled_children_only() { if token.traverse_unstyled_children_only() {
for kid in root.as_node().children() { for kid in root.as_node().children() {
if kid.as_element().map_or(false, |el| el.get_data().is_none()) { 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 { } 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 atomic_refcell::{AtomicRefCell, AtomicRefMut};
use bloom::StyleBloom; use bloom::StyleBloom;
use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext}; use context::{SharedStyleContext, StyleContext};
use data::{ElementData, StoredRestyleHint}; use data::{ElementData, StoredRestyleHint};
use dom::{OpaqueNode, TElement, TNode}; use dom::{OpaqueNode, TElement, TNode};
use matching::{MatchMethods, StyleSharingResult}; use matching::{MatchMethods, StyleSharingResult};
@ -15,10 +15,8 @@ use selector_parser::RestyleDamage;
use selectors::Element; use selectors::Element;
use selectors::matching::StyleRelations; use selectors::matching::StyleRelations;
use servo_config::opts; use servo_config::opts;
use std::borrow::Borrow;
use std::cell::RefCell; use std::cell::RefCell;
use std::mem; use std::mem;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use stylist::Stylist; use stylist::Stylist;
@ -128,15 +126,16 @@ impl LogBehavior {
} }
pub trait DomTraversal<N: TNode> : Sync { 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. /// 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. /// Process `node` on the way up, after its children have been processed.
/// ///
/// This is only executed if `needs_postorder_traversal` returns true. /// 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 /// Boolean that specifies whether a bottom up traversal should be
/// performed. /// performed.
@ -321,7 +320,7 @@ pub trait DomTraversal<N: TNode> : Sync {
fn shared_context(&self) -> &SharedStyleContext; 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. /// Determines the amount of relations where we're going to share style.

View file

@ -17,11 +17,11 @@ use std::ptr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use style::arc_ptr_eq; use style::arc_ptr_eq;
use style::atomic_refcell::AtomicRefMut; use style::atomic_refcell::AtomicRefMut;
use style::context::{ThreadLocalStyleContextCreationInfo, QuirksMode, ReflowGoal, SharedStyleContext, StyleContext}; use style::context::{QuirksMode, ReflowGoal, SharedStyleContext, StyleContext};
use style::context::{ThreadLocalStyleContext, ThreadLocalStyleContextCreationInfo};
use style::data::{ElementData, RestyleData}; use style::data::{ElementData, RestyleData};
use style::dom::{ShowSubtreeData, TElement, TNode}; use style::dom::{ShowSubtreeData, TElement, TNode};
use style::error_reporting::StdoutErrorReporter; use style::error_reporting::StdoutErrorReporter;
use style::gecko::context::clear_local_context;
use style::gecko::data::{NUM_THREADS, PerDocumentStyleData, PerDocumentStyleDataImpl}; use style::gecko::data::{NUM_THREADS, PerDocumentStyleData, PerDocumentStyleDataImpl};
use style::gecko::restyle_damage::GeckoRestyleDamage; use style::gecko::restyle_damage::GeckoRestyleDamage;
use style::gecko::selector_parser::{SelectorImpl, PseudoElement}; use style::gecko::selector_parser::{SelectorImpl, PseudoElement};
@ -88,12 +88,6 @@ pub extern "C" fn Servo_Initialize() -> () {
pub extern "C" fn Servo_Shutdown() -> () { pub extern "C" fn Servo_Shutdown() -> () {
// Destroy our default computed values. // Destroy our default computed values.
unsafe { ComputedValues::shutdown(); } unsafe { ComputedValues::shutdown(); }
// In general, ThreadLocalStyleContexts will get destroyed when the worker thread
// is joined and the TLS is dropped. However, under some configurations we
// may do sequential style computation on the main thread, so we need to be
// sure to clear the main thread TLS entry as well.
clear_local_context();
} }
fn create_shared_context(mut per_doc_data: &mut AtomicRefMut<PerDocumentStyleDataImpl>) -> SharedStyleContext { fn create_shared_context(mut per_doc_data: &mut AtomicRefMut<PerDocumentStyleDataImpl>) -> SharedStyleContext {
@ -875,22 +869,19 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
let mut per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut(); let mut per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
let shared_style_context = create_shared_context(&mut per_doc_data); let shared_style_context = create_shared_context(&mut per_doc_data);
let traversal = RecalcStyleOnly::new(shared_style_context); let traversal = RecalcStyleOnly::new(shared_style_context);
let tlc = traversal.create_or_get_thread_local_context();
let mut traversal_data = PerLevelTraversalData { let mut traversal_data = PerLevelTraversalData {
current_dom_depth: None, current_dom_depth: None,
}; };
let mut tlc = ThreadLocalStyleContext::new(traversal.shared_context());
let context = StyleContext { let context = StyleContext {
shared: traversal.shared_context(), shared: traversal.shared_context(),
thread_local: &*tlc, thread_local: &mut tlc,
}; };
recalc_style_at(&traversal, &mut traversal_data, &context, element, &mut data); recalc_style_at(&traversal, &mut traversal_data, &context, element, &mut data);
// We don't want to keep any cached style around after this one-off style resolution.
tlc.style_sharing_candidate_cache.borrow_mut().clear();
// The element was either unstyled or needed restyle. If it was unstyled, it may have // The element was either unstyled or needed restyle. If it was unstyled, it may have
// additional unstyled children that subsequent traversals won't find now that the style // additional unstyled children that subsequent traversals won't find now that the style
// on this element is up-to-date. Mark dirty descendants in that case. // on this element is up-to-date. Mark dirty descendants in that case.