Concurrent rule tree, v1

This patch introduces infrastructure for the rule tree, and constructs it.

We don't use it yet, nor have good heuristics for GC'ing it, but this should not
happen anymore once we store the rule node reference in the node.

I haven't messed up with memory orders because I want to do a try run with it,
then mess with them.

Take down the ApplicableDeclarationsCache, use the rule tree for doing the cascade.
This commit is contained in:
Emilio Cobos Álvarez 2016-09-06 11:13:50 +08:00 committed by Simon Sapin
parent f7875dad1a
commit de4fe6e2b6
22 changed files with 1067 additions and 552 deletions

View file

@ -8,26 +8,24 @@
use {Atom, LocalName};
use animation;
use arc_ptr_eq;
use atomic_refcell::AtomicRefMut;
use cache::{LRUCache, SimpleHashCache};
use cache::LRUCache;
use cascade_info::CascadeInfo;
use context::{SharedStyleContext, StyleContext};
use data::{ElementData, ElementStyles, PseudoStyles};
use dom::{TElement, TNode, TRestyleDamage, UnsafeNode};
use properties::{CascadeFlags, ComputedValues, SHAREABLE, cascade};
use properties::longhands::display::computed_value as display;
use selector_impl::{PseudoElement, TheSelectorImpl};
use rule_tree::StrongRuleNode;
use selector_impl::{TheSelectorImpl, PseudoElement};
use selector_matching::{ApplicableDeclarationBlock, Stylist};
use selectors::MatchAttr;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, MatchingReason, StyleRelations};
use sink::ForgetfulSink;
use smallvec::SmallVec;
use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::hash::BuildHasherDefault;
use std::mem;
use std::ops::Deref;
use std::slice::IterMut;
use std::sync::Arc;
use util::opts;
@ -53,7 +51,7 @@ fn create_common_style_affecting_attributes_from_element<E: TElement>(element: &
}
pub struct ApplicableDeclarations {
pub normal: SmallVec<[ApplicableDeclarationBlock; 16]>,
pub normal: Vec<ApplicableDeclarationBlock>,
pub per_pseudo: HashMap<PseudoElement,
Vec<ApplicableDeclarationBlock>,
BuildHasherDefault<::fnv::FnvHasher>>,
@ -65,7 +63,7 @@ pub struct ApplicableDeclarations {
impl ApplicableDeclarations {
pub fn new() -> Self {
let mut applicable_declarations = ApplicableDeclarations {
normal: SmallVec::new(),
normal: Vec::with_capacity(16),
per_pseudo: HashMap::with_hasher(Default::default()),
normal_shareable: false,
};
@ -78,105 +76,6 @@ impl ApplicableDeclarations {
}
}
#[derive(Clone)]
pub struct ApplicableDeclarationsCacheEntry {
pub declarations: Vec<ApplicableDeclarationBlock>,
}
impl ApplicableDeclarationsCacheEntry {
fn new(declarations: Vec<ApplicableDeclarationBlock>) -> ApplicableDeclarationsCacheEntry {
ApplicableDeclarationsCacheEntry {
declarations: declarations,
}
}
}
impl PartialEq for ApplicableDeclarationsCacheEntry {
fn eq(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
let this_as_query = ApplicableDeclarationsCacheQuery::new(&*self.declarations);
this_as_query.eq(other)
}
}
impl Eq for ApplicableDeclarationsCacheEntry {}
impl Hash for ApplicableDeclarationsCacheEntry {
fn hash<H: Hasher>(&self, state: &mut H) {
let tmp = ApplicableDeclarationsCacheQuery::new(&*self.declarations);
tmp.hash(state);
}
}
struct ApplicableDeclarationsCacheQuery<'a> {
declarations: &'a [ApplicableDeclarationBlock],
}
impl<'a> ApplicableDeclarationsCacheQuery<'a> {
fn new(declarations: &'a [ApplicableDeclarationBlock]) -> ApplicableDeclarationsCacheQuery<'a> {
ApplicableDeclarationsCacheQuery {
declarations: declarations,
}
}
}
impl<'a> PartialEq for ApplicableDeclarationsCacheQuery<'a> {
fn eq(&self, other: &ApplicableDeclarationsCacheQuery<'a>) -> bool {
self.declarations.len() == other.declarations.len() &&
self.declarations.iter().zip(other.declarations).all(|(this, other)| {
arc_ptr_eq(&this.mixed_declarations, &other.mixed_declarations) &&
this.importance == other.importance
})
}
}
impl<'a> Eq for ApplicableDeclarationsCacheQuery<'a> {}
impl<'a> PartialEq<ApplicableDeclarationsCacheEntry> for ApplicableDeclarationsCacheQuery<'a> {
fn eq(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
let other_as_query = ApplicableDeclarationsCacheQuery::new(&other.declarations);
self.eq(&other_as_query)
}
}
impl<'a> Hash for ApplicableDeclarationsCacheQuery<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
for declaration in self.declarations {
// Each declaration contians an Arc, which is a stable
// pointer; we use that for hashing and equality.
let ptr: *const _ = Arc::deref(&declaration.mixed_declarations);
ptr.hash(state);
declaration.importance.hash(state);
}
}
}
static APPLICABLE_DECLARATIONS_CACHE_SIZE: usize = 32;
pub struct ApplicableDeclarationsCache {
cache: SimpleHashCache<ApplicableDeclarationsCacheEntry, Arc<ComputedValues>>,
}
impl ApplicableDeclarationsCache {
pub fn new() -> Self {
ApplicableDeclarationsCache {
cache: SimpleHashCache::new(APPLICABLE_DECLARATIONS_CACHE_SIZE),
}
}
pub fn find(&self, declarations: &[ApplicableDeclarationBlock]) -> Option<Arc<ComputedValues>> {
match self.cache.find(&ApplicableDeclarationsCacheQuery::new(declarations)) {
None => None,
Some(ref values) => Some((*values).clone()),
}
}
pub fn insert(&mut self, declarations: Vec<ApplicableDeclarationBlock>, style: Arc<ComputedValues>) {
self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style)
}
pub fn evict_all(&mut self) {
self.cache.evict_all();
}
}
/// Information regarding a candidate.
///
/// TODO: We can stick a lot more info here.
@ -184,8 +83,6 @@ impl ApplicableDeclarationsCache {
struct StyleSharingCandidate {
/// The node, guaranteed to be an element.
node: UnsafeNode,
/// The cached computed style, here for convenience.
style: Arc<ComputedValues>,
/// The cached common style affecting attribute info.
common_style_affecting_attributes: Option<CommonStyleAffectingAttributes>,
/// the cached class names.
@ -195,7 +92,6 @@ struct StyleSharingCandidate {
impl PartialEq<StyleSharingCandidate> for StyleSharingCandidate {
fn eq(&self, other: &Self) -> bool {
self.node == other.node &&
arc_ptr_eq(&self.style, &other.style) &&
self.common_style_affecting_attributes == other.common_style_affecting_attributes
}
}
@ -234,7 +130,7 @@ fn element_matches_candidate<E: TElement>(element: &E,
candidate: &mut StyleSharingCandidate,
candidate_element: &E,
shared_context: &SharedStyleContext)
-> Result<Arc<ComputedValues>, CacheMiss> {
-> Result<(Arc<ComputedValues>, StrongRuleNode), CacheMiss> {
macro_rules! miss {
($miss: ident) => {
return Err(CacheMiss::$miss);
@ -295,7 +191,11 @@ fn element_matches_candidate<E: TElement>(element: &E,
miss!(NonCommonAttrRules)
}
Ok(candidate.style.clone())
let data = candidate_element.borrow_data().unwrap();
let current_styles = data.get_current_styles().unwrap();
Ok((current_styles.primary.clone(),
current_styles.rule_node.clone()))
}
fn have_same_common_style_affecting_attributes<E: TElement>(element: &E,
@ -458,7 +358,6 @@ impl StyleSharingCandidateCache {
self.cache.insert(StyleSharingCandidate {
node: element.as_node().to_unsafe(),
style: style.clone(),
common_style_affecting_attributes: None,
class_attributes: None,
}, ());
@ -490,7 +389,6 @@ pub enum StyleSharingResult<ConcreteRestyleDamage: TRestyleDamage> {
// FIXME(pcwalton): Unify with `CascadeFlags`, perhaps?
struct CascadeBooleans {
shareable: bool,
cacheable: bool,
animate: bool,
}
@ -503,23 +401,16 @@ trait PrivateMatchMethods: TElement {
context: &Ctx,
parent_style: Option<&Arc<ComputedValues>>,
old_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[ApplicableDeclarationBlock],
applicable_declarations_cache:
&mut ApplicableDeclarationsCache,
applicable_declarations: &mut Vec<ApplicableDeclarationBlock>,
booleans: CascadeBooleans)
-> Arc<ComputedValues>
-> (Arc<ComputedValues>, StrongRuleNode)
where Ctx: StyleContext<'a>
{
let mut cacheable = booleans.cacheable;
let shared_context = context.shared_context();
// Dont cache applicable declarations for elements with a style attribute.
// Since the style attribute contributes to that set, no other element would have the same set
// and the cache would not be effective anyway.
// This also works around the test failures at
// https://github.com/servo/servo/pull/13459#issuecomment-250717584
let has_style_attribute = self.style_attribute().is_some();
cacheable = cacheable && !has_style_attribute;
let rule_node =
shared_context.stylist.rule_tree
.insert_ordered_rules(
applicable_declarations.drain(..).map(|d| (d.source, d.importance)));
let mut cascade_info = CascadeInfo::new();
let mut cascade_flags = CascadeFlags::empty();
@ -527,26 +418,18 @@ trait PrivateMatchMethods: TElement {
cascade_flags.insert(SHAREABLE)
}
let (this_style, is_cacheable) = match parent_style {
let this_style = match parent_style {
Some(ref parent_style) => {
let cache_entry = applicable_declarations_cache.find(applicable_declarations);
let cached_computed_values = match cache_entry {
Some(ref style) => Some(&**style),
None => None,
};
cascade(shared_context.viewport_size,
applicable_declarations,
&rule_node,
Some(&***parent_style),
cached_computed_values,
Some(&mut cascade_info),
shared_context.error_reporter.clone(),
cascade_flags)
}
None => {
cascade(shared_context.viewport_size,
applicable_declarations,
None,
&rule_node,
None,
Some(&mut cascade_info),
shared_context.error_reporter.clone(),
@ -555,43 +438,30 @@ trait PrivateMatchMethods: TElement {
};
cascade_info.finish(&self.as_node());
cacheable = cacheable && is_cacheable;
let mut this_style = Arc::new(this_style);
if booleans.animate {
let new_animations_sender = &context.local_context().new_animations_sender;
let this_opaque = self.as_node().opaque();
// Trigger any present animations if necessary.
let mut animations_started = animation::maybe_start_animations(
&shared_context,
new_animations_sender,
this_opaque,
&this_style);
animation::maybe_start_animations(&shared_context,
new_animations_sender,
this_opaque, &this_style);
// Trigger transitions if necessary. This will reset `this_style` back
// to its old value if it did trigger a transition.
if let Some(ref style) = old_style {
animations_started |=
animation::start_transitions_if_applicable(
new_animations_sender,
this_opaque,
self.as_node().to_unsafe(),
&**style,
&mut this_style,
&shared_context.timer);
animation::start_transitions_if_applicable(
new_animations_sender,
this_opaque,
self.as_node().to_unsafe(),
&**style,
&mut this_style,
&shared_context.timer);
}
cacheable = cacheable && !animations_started
}
// Cache the resolved style if it was cacheable.
if cacheable {
applicable_declarations_cache.insert(applicable_declarations.to_vec(),
this_style.clone());
}
this_style
(this_style, rule_node)
}
fn update_animations_for_cascade(&self,
@ -636,7 +506,7 @@ trait PrivateMatchMethods: TElement {
fn share_style_with_candidate_if_possible(&self,
shared_context: &SharedStyleContext,
candidate: &mut StyleSharingCandidate)
-> Result<Arc<ComputedValues>, CacheMiss> {
-> Result<(Arc<ComputedValues>, StrongRuleNode), CacheMiss> {
let candidate_element = unsafe {
Self::ConcreteNode::from_unsafe(&candidate.node).as_element().unwrap()
};
@ -652,9 +522,10 @@ pub trait MatchMethods : TElement {
fn match_element(&self,
stylist: &Stylist,
parent_bf: Option<&BloomFilter>,
applicable_declarations: &mut ApplicableDeclarations)
mut applicable_declarations: &mut ApplicableDeclarations)
-> StyleRelations {
use traversal::relations_are_shareable;
let style_attribute = self.style_attribute();
let mut relations =
@ -711,7 +582,7 @@ pub trait MatchMethods : TElement {
for (i, &mut (ref mut candidate, ())) in style_sharing_candidate_cache.iter_mut().enumerate() {
let sharing_result = self.share_style_with_candidate_if_possible(shared_context, candidate);
match sharing_result {
Ok(shared_style) => {
Ok((shared_style, rule_node)) => {
// Yay, cache hit. Share the style.
// TODO: add the display: none optimisation here too! Even
@ -728,7 +599,7 @@ pub trait MatchMethods : TElement {
}
};
data.finish_styling(ElementStyles::new(shared_style));
data.finish_styling(ElementStyles::new(shared_style, rule_node));
return StyleSharingResult::StyleWasShared(i, damage)
}
@ -848,7 +719,7 @@ pub trait MatchMethods : TElement {
context: &Ctx,
mut data: AtomicRefMut<ElementData>,
parent: Option<Self>,
applicable_declarations: &ApplicableDeclarations)
mut applicable_declarations: ApplicableDeclarations)
where Ctx: StyleContext<'a>
{
// Get our parent's style.
@ -857,40 +728,39 @@ pub trait MatchMethods : TElement {
let mut new_styles;
let mut applicable_declarations_cache =
context.local_context().applicable_declarations_cache.borrow_mut();
let damage = {
// Update animations before the cascade. This may modify the value of the old primary
// style.
let cacheable = data.previous_styles_mut().map_or(true,
|x| !self.update_animations_for_cascade(context.shared_context(), &mut x.primary));
let shareable = applicable_declarations.normal_shareable;
let (old_primary, old_pseudos) = match data.previous_styles_mut() {
None => (None, None),
Some(x) => (Some(&x.primary), Some(&mut x.pseudos)),
Some(previous) => {
// Update animations before the cascade. This may modify the
// value of the old primary style.
self.update_animations_for_cascade(context.shared_context(),
&mut previous.primary);
(Some(&previous.primary), Some(&mut previous.pseudos))
}
};
new_styles = ElementStyles::new(
let (new_style, rule_node) =
self.cascade_node_pseudo_element(context,
parent_style.clone(),
parent_style,
old_primary,
&applicable_declarations.normal,
&mut applicable_declarations_cache,
&mut applicable_declarations.normal,
CascadeBooleans {
shareable: shareable,
cacheable: cacheable,
animate: true,
}));
});
new_styles = ElementStyles::new(new_style, rule_node);
let damage =
self.compute_damage_and_cascade_pseudos(old_primary,
old_pseudos,
&new_styles.primary,
&mut new_styles.pseudos,
context, applicable_declarations,
&mut applicable_declarations_cache);
context,
&mut applicable_declarations);
self.as_node().set_can_be_fragmented(parent.map_or(false, |p| {
p.as_node().can_be_fragmented() ||
@ -912,8 +782,7 @@ pub trait MatchMethods : TElement {
new_primary: &Arc<ComputedValues>,
new_pseudos: &mut PseudoStyles,
context: &Ctx,
applicable_declarations: &ApplicableDeclarations,
mut applicable_declarations_cache: &mut ApplicableDeclarationsCache)
applicable_declarations: &mut ApplicableDeclarations)
-> Self::ConcreteRestyleDamage
where Ctx: StyleContext<'a>
{
@ -953,58 +822,55 @@ pub trait MatchMethods : TElement {
let rebuild_and_reflow =
Self::ConcreteRestyleDamage::rebuild_and_reflow();
let no_damage = Self::ConcreteRestyleDamage::empty();
debug_assert!(new_pseudos.is_empty());
<Self as MatchAttr>::Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
let applicable_declarations_for_this_pseudo =
applicable_declarations.per_pseudo.get(&pseudo).unwrap();
let mut applicable_declarations_for_this_pseudo =
applicable_declarations.per_pseudo.get_mut(&pseudo).unwrap();
let has_declarations =
!applicable_declarations_for_this_pseudo.is_empty();
// Grab the old pseudo style for analysis.
let mut old_pseudo_style = old_pseudos.as_mut().and_then(|x| x.remove(&pseudo));
let mut maybe_old_pseudo_style_and_rule_node =
old_pseudos.as_mut().and_then(|x| x.remove(&pseudo));
if has_declarations {
// We have declarations, so we need to cascade. Compute parameters.
let animate = <Self as MatchAttr>::Impl::pseudo_is_before_or_after(&pseudo);
let cacheable = if animate && old_pseudo_style.is_some() {
// Update animations before the cascade. This may modify
// the value of old_pseudo_style.
!self.update_animations_for_cascade(context.shared_context(),
old_pseudo_style.as_mut().unwrap())
} else {
true
};
if animate {
if let Some((ref mut old_pseudo_style, _)) = maybe_old_pseudo_style_and_rule_node {
// Update animations before the cascade. This may modify
// the value of old_pseudo_style.
self.update_animations_for_cascade(context.shared_context(),
old_pseudo_style);
}
}
let new_pseudo_style =
let (new_pseudo_style, new_rule_node) =
self.cascade_node_pseudo_element(context, Some(new_primary),
old_pseudo_style.as_ref(),
&*applicable_declarations_for_this_pseudo,
&mut applicable_declarations_cache,
maybe_old_pseudo_style_and_rule_node.as_ref().map(|s| &s.0),
&mut applicable_declarations_for_this_pseudo,
CascadeBooleans {
shareable: false,
cacheable: cacheable,
animate: animate,
});
// Compute restyle damage unless we've already maxed it out.
if damage != rebuild_and_reflow {
damage = damage | match old_pseudo_style {
damage = damage | match maybe_old_pseudo_style_and_rule_node {
None => rebuild_and_reflow,
Some(ref old) => self.compute_restyle_damage(Some(old), &new_pseudo_style,
Some(&pseudo)),
Some((ref old, _)) => self.compute_restyle_damage(Some(old), &new_pseudo_style,
Some(&pseudo)),
};
}
// Insert the new entry into the map.
let existing = new_pseudos.insert(pseudo, new_pseudo_style);
let existing = new_pseudos.insert(pseudo, (new_pseudo_style, new_rule_node));
debug_assert!(existing.is_none());
} else {
damage = damage | match old_pseudo_style {
Some(_) => rebuild_and_reflow,
None => no_damage,
if maybe_old_pseudo_style_and_rule_node.is_some() {
damage = rebuild_and_reflow;
}
}
});