style: Add a TLS-based style struct caching mechanism.

This commit is contained in:
Emilio Cobos Álvarez 2017-08-23 20:04:11 +08:00 committed by Cameron McCormack
parent 298b1363ff
commit c34c92e904
18 changed files with 278 additions and 13 deletions

View file

@ -122,6 +122,12 @@ impl<T: ?Sized + 'static> PartialEq for NonZeroPtrMut<T> {
impl<T: ?Sized + 'static> Eq for NonZeroPtrMut<T> {}
impl<T: Sized + 'static> Hash for NonZeroPtrMut<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ptr().hash(state)
}
}
pub struct Arc<T: ?Sized + 'static> {
p: NonZeroPtrMut<ArcInner<T>>,
}

View file

@ -502,7 +502,9 @@ fn compute_style_for_animation_step(context: &SharedStyleContext,
/* visited_style = */ None,
font_metrics_provider,
CascadeFlags::empty(),
context.quirks_mode());
context.quirks_mode(),
/* rule_cache = */ None,
&mut Default::default());
computed
}
}

View file

@ -20,6 +20,7 @@ use parallel::{STACK_SAFETY_MARGIN_KB, STYLE_THREAD_STACK_SIZE_KB};
#[cfg(feature = "servo")] use parking_lot::RwLock;
use properties::ComputedValues;
#[cfg(feature = "servo")] use properties::PropertyId;
use rule_cache::RuleCache;
use rule_tree::StrongRuleNode;
use selector_parser::{EAGER_PSEUDO_COUNT, SnapshotMap};
use selectors::matching::ElementSelectorFlags;
@ -685,6 +686,8 @@ impl StackLimitChecker {
pub struct ThreadLocalStyleContext<E: TElement> {
/// A cache to share style among siblings.
pub sharing_cache: StyleSharingCache<E>,
/// A cache from matched properties to elements that match those.
pub rule_cache: RuleCache,
/// The bloom filter used to fast-reject selector-matching.
pub bloom_filter: StyleBloom<E>,
/// A channel on which new animations that have been triggered by style
@ -722,6 +725,7 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
pub fn new(shared: &SharedStyleContext) -> Self {
ThreadLocalStyleContext {
sharing_cache: StyleSharingCache::new(),
rule_cache: RuleCache::new(),
bloom_filter: StyleBloom::new(),
new_animations_sender: shared.local_context_creation_data.lock().unwrap().new_animations_sender.clone(),
tasks: SequentialTaskList(Vec::new()),
@ -739,6 +743,7 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
pub fn new(shared: &SharedStyleContext) -> Self {
ThreadLocalStyleContext {
sharing_cache: StyleSharingCache::new(),
rule_cache: RuleCache::new(),
bloom_filter: StyleBloom::new(),
tasks: SequentialTaskList(Vec::new()),
selector_flags: SelectorFlagsMap::new(),

View file

@ -23,7 +23,9 @@ use media_queries::MediaType;
use parser::ParserContext;
use properties::{ComputedValues, StyleBuilder};
use properties::longhands::font_size;
use rule_cache::RuleCacheConditions;
use servo_arc::Arc;
use std::cell::RefCell;
use std::fmt::{self, Write};
use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
use str::starts_with_ignore_ascii_case;
@ -694,6 +696,7 @@ impl Expression {
// http://dev.w3.org/csswg/mediaqueries3/#units
// em units are relative to the initial font-size.
let mut conditions = RuleCacheConditions::default();
let context = computed::Context {
is_root_element: false,
builder: StyleBuilder::for_derived_style(device, default_values, None, None),
@ -703,6 +706,7 @@ impl Expression {
// TODO: pass the correct value here.
quirks_mode: quirks_mode,
for_smil_animation: false,
rule_cache_conditions: RefCell::new(&mut conditions),
};
let required_value = match self.value {

View file

@ -124,6 +124,7 @@ pub mod matching;
pub mod media_queries;
pub mod parallel;
pub mod parser;
pub mod rule_cache;
pub mod rule_tree;
pub mod scoped_tls;
pub mod selector_map;

View file

@ -59,5 +59,8 @@ bitflags! {
/// Whether the child explicitly inherits any reset property.
const INHERITS_RESET_STYLE = 1 << 8,
/// A flag to mark a style which is a visited style.
const IS_STYLE_IF_VISITED = 1 << 9,
}
}

View file

@ -52,7 +52,7 @@ use gecko::values::round_border_to_device_pixels;
use logical_geometry::WritingMode;
use media_queries::Device;
use properties::animated_properties::TransitionProperty;
use properties::computed_value_flags::ComputedValueFlags;
use properties::computed_value_flags::*;
use properties::{default_font_size_keyword, longhands, FontComputationData, Importance, LonghandId};
use properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId};
use rule_tree::StrongRuleNode;
@ -259,6 +259,11 @@ impl ops::DerefMut for ComputedValues {
}
impl ComputedValuesInner {
/// Whether we're a visited style.
pub fn is_style_if_visited(&self) -> bool {
self.flags.contains(IS_STYLE_IF_VISITED)
}
#[inline]
pub fn is_display_contents(&self) -> bool {
self.get_box().clone_display() == longhands::display::computed_value::T::contents

View file

@ -322,6 +322,10 @@
longhands::system_font::resolve_system_font(sf, context);
}
% endif
% if property.logical:
context.rule_cache_conditions.borrow_mut()
.set_writing_mode_dependency(context.builder.writing_mode);
% endif
% if property.is_vector:
// In the case of a vector property we want to pass
// down an iterator so that this can be computed
@ -363,6 +367,7 @@
CSSWideKeyword::Unset |
% endif
CSSWideKeyword::Initial => {
context.rule_cache_conditions.borrow_mut().set_uncacheable();
% if property.ident == "font_size":
longhands::font_size::cascade_initial_font_size(context);
% else:

View file

@ -16,6 +16,7 @@ use smallbitvec::SmallBitVec;
use std::borrow::Cow;
use hash::HashSet;
use std::{fmt, mem, ops};
use std::cell::RefCell;
#[cfg(feature = "gecko")] use std::ptr;
#[cfg(feature = "servo")] use cssparser::RGBA;
@ -34,6 +35,7 @@ use media_queries::Device;
use parser::ParserContext;
use properties::animated_properties::AnimatableLonghand;
#[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont;
use rule_cache::{RuleCache, RuleCacheConditions};
use selector_parser::PseudoElement;
use selectors::parser::SelectorParseError;
#[cfg(feature = "servo")] use servo_config::prefs::PREFS;
@ -46,7 +48,7 @@ use values::generics::text::LineHeight;
use values::computed;
use values::computed::NonNegativeLength;
use rule_tree::{CascadeLevel, StrongRuleNode};
use self::computed_value_flags::ComputedValueFlags;
use self::computed_value_flags::*;
use style_adjuster::StyleAdjuster;
#[cfg(feature = "servo")] use values::specified::BorderStyle;
@ -2156,6 +2158,11 @@ impl ComputedValuesInner {
/// Servo for obvious reasons.
pub fn has_moz_binding(&self) -> bool { false }
/// Whether we're a visited style.
pub fn is_style_if_visited(&self) -> bool {
self.flags.contains(IS_STYLE_IF_VISITED)
}
/// Returns whether this style's display value is equal to contents.
///
/// Since this isn't supported in Servo, this is always false for Servo.
@ -2558,7 +2565,7 @@ pub struct StyleBuilder<'a> {
/// The rule node representing the ordered list of rules matched for this
/// node.
rules: Option<StrongRuleNode>,
pub rules: Option<StrongRuleNode>,
custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
@ -2594,7 +2601,7 @@ impl<'a> StyleBuilder<'a> {
custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
writing_mode: WritingMode,
font_size_keyword: FontComputationData,
flags: ComputedValueFlags,
mut flags: ComputedValueFlags,
visited_style: Option<Arc<ComputedValues>>,
) -> Self {
debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
@ -2616,6 +2623,10 @@ impl<'a> StyleBuilder<'a> {
reset_style
};
if cascade_flags.contains(VISITED_DEPENDENT_ONLY) {
flags.insert(IS_STYLE_IF_VISITED);
}
StyleBuilder {
device,
parent_style,
@ -2639,6 +2650,11 @@ impl<'a> StyleBuilder<'a> {
}
}
/// Whether we're a visited style.
pub fn is_style_if_visited(&self) -> bool {
self.flags.contains(IS_STYLE_IF_VISITED)
}
/// Creates a StyleBuilder holding only references to the structs of `s`, in
/// order to create a derived style.
pub fn for_derived_style(
@ -2674,6 +2690,16 @@ impl<'a> StyleBuilder<'a> {
}
}
/// Copy the reset properties from `style`.
pub fn copy_reset_from(&mut self, style: &'a ComputedValues) {
% for style_struct in data.active_style_structs():
% if not style_struct.inherited:
self.${style_struct.ident} =
StyleStructRef::Borrowed(style.${style_struct.name_lower}_arc());
% endif
% endfor
}
% for property in data.longhands:
% if property.ident != "font_size":
/// Inherit `${property.ident}` from our parent style.
@ -2683,7 +2709,8 @@ impl<'a> StyleBuilder<'a> {
% if property.style_struct.inherited:
self.inherited_style.get_${property.style_struct.name_lower}();
% else:
self.inherited_style_ignoring_first_line.get_${property.style_struct.name_lower}();
self.inherited_style_ignoring_first_line
.get_${property.style_struct.name_lower}();
% endif
% if not property.style_struct.inherited:
@ -3029,7 +3056,9 @@ pub fn cascade(
visited_style: Option<Arc<ComputedValues>>,
font_metrics_provider: &FontMetricsProvider,
flags: CascadeFlags,
quirks_mode: QuirksMode
quirks_mode: QuirksMode,
rule_cache: Option<<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
) -> Arc<ComputedValues> {
debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
#[cfg(feature = "gecko")]
@ -3089,6 +3118,8 @@ pub fn cascade(
font_metrics_provider,
flags,
quirks_mode,
rule_cache,
rule_cache_conditions,
)
}
@ -3107,6 +3138,8 @@ pub fn apply_declarations<'a, F, I>(
font_metrics_provider: &FontMetricsProvider,
flags: CascadeFlags,
quirks_mode: QuirksMode,
rule_cache: Option<<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
) -> Arc<ComputedValues>
where
F: Fn() -> I,
@ -3163,11 +3196,12 @@ where
ComputedValueFlags::empty(),
visited_style,
),
font_metrics_provider: font_metrics_provider,
cached_system_font: None,
in_media_query: false,
quirks_mode: quirks_mode,
for_smil_animation: false,
font_metrics_provider,
quirks_mode,
rule_cache_conditions: RefCell::new(rule_cache_conditions),
};
let ignore_colors = !device.use_document_colors();
@ -3190,6 +3224,7 @@ where
//
// To improve i-cache behavior, we outline the individual functions and use
// virtual dispatch instead.
let mut apply_reset = true;
% for category_to_cascade_now in ["early", "other"]:
% if category_to_cascade_now == "early":
// Pull these out so that we can compute them in a specific order
@ -3222,6 +3257,10 @@ where
continue
}
if !apply_reset && !longhand_id.inherited() {
continue;
}
// When document colors are disabled, skip properties that are
// marked as ignored in that mode, if they come from a UA or
// user style sheet.
@ -3389,7 +3428,12 @@ where
(CASCADE_PROPERTY[discriminant])(&size, &mut context);
% endif
}
% endif
if let Some(style) = rule_cache.and_then(|c| c.find(&context.builder)) {
context.builder.copy_reset_from(style);
apply_reset = false;
}
% endif // category == "early"
% endfor
let mut builder = context.builder;

View file

@ -0,0 +1,143 @@
/* 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/. */
//! A cache from rule node to computed values, in order to cache reset
//! properties.
use fnv::FnvHashMap;
use logical_geometry::WritingMode;
use properties::{ComputedValues, StyleBuilder};
use rule_tree::StrongRuleNode;
use servo_arc::Arc;
use smallvec::SmallVec;
use values::computed::NonNegativeLength;
/// The conditions for caching and matching a style in the rule cache.
#[derive(Clone, Debug, Default)]
pub struct RuleCacheConditions {
uncacheable: bool,
font_size: Option<NonNegativeLength>,
writing_mode: Option<WritingMode>,
}
impl RuleCacheConditions {
/// Sets the style as depending in the font-size value.
pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) {
debug_assert!(self.font_size.map_or(true, |f| f == font_size));
self.font_size = Some(font_size);
}
/// Sets the style as uncacheable.
pub fn set_uncacheable(&mut self) {
self.uncacheable = true;
}
/// Sets the style as depending in the writing-mode value `writing_mode`.
pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) {
debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode));
self.writing_mode = Some(writing_mode);
}
/// Returns whether the current style's reset properties are cacheable.
fn cacheable(&self) -> bool {
!self.uncacheable
}
/// Returns whether `style` matches the conditions.
fn matches(&self, style: &StyleBuilder) -> bool {
if self.uncacheable {
return false;
}
if let Some(fs) = self.font_size {
if style.get_font().clone_font_size() != fs {
return false;
}
}
if let Some(wm) = self.writing_mode {
if style.writing_mode != wm {
return false;
}
}
true
}
}
/// A TLS cache from rules matched to computed values.
pub struct RuleCache {
// FIXME(emilio): Consider using LRUCache or something like that?
map: FnvHashMap<StrongRuleNode, SmallVec<[(RuleCacheConditions, Arc<ComputedValues>); 1]>>,
}
impl RuleCache {
/// Creates an empty `RuleCache`.
pub fn new() -> Self {
Self {
map: FnvHashMap::default(),
}
}
/// Finds a node in the properties matched cache.
///
/// This needs to receive a `StyleBuilder` with the `early` properties
/// already applied.
pub fn find(
&self,
builder_with_early_props: &StyleBuilder,
) -> Option<&ComputedValues> {
if builder_with_early_props.is_style_if_visited() {
// FIXME(emilio): We can probably do better, does it matter much?
return None;
}
let rules = match builder_with_early_props.rules {
Some(ref rules) => rules,
None => return None,
};
self.map.get(rules).and_then(|cached_values| {
for &(ref conditions, ref values) in cached_values.iter() {
if conditions.matches(builder_with_early_props) {
debug!("Using cached reset style with conditions {:?}", conditions);
return Some(&**values)
}
}
None
})
}
/// Inserts a node into the rules cache if possible.
///
/// Returns whether the style was inserted into the cache.
pub fn insert_if_possible(
&mut self,
style: &Arc<ComputedValues>,
conditions: &RuleCacheConditions,
) -> bool {
if !conditions.cacheable() {
return false;
}
if style.is_style_if_visited() {
// FIXME(emilio): We can probably do better, does it matter much?
return false;
}
let rules = match style.rules {
Some(ref r) => r.clone(),
None => return false,
};
debug!("Inserting cached reset style with conditions {:?}", conditions);
self.map
.entry(rules)
.or_insert_with(SmallVec::new)
.push((conditions.clone(), style.clone()));
true
}
}

View file

@ -819,7 +819,7 @@ struct WeakRuleNode {
}
/// A strong reference to a rule node.
#[derive(Debug, PartialEq)]
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct StrongRuleNode {
p: NonZeroPtrMut<RuleNode>,
}

View file

@ -13,7 +13,9 @@ use media_queries::MediaType;
use parser::ParserContext;
use properties::{ComputedValues, StyleBuilder};
use properties::longhands::font_size;
use rule_cache::RuleCacheConditions;
use selectors::parser::SelectorParseError;
use std::cell::RefCell;
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
use style_traits::{CSSPixel, DevicePixel, ToCss, ParseError};
@ -244,6 +246,7 @@ pub enum Range<T> {
impl Range<specified::Length> {
fn to_computed_range(&self, device: &Device, quirks_mode: QuirksMode) -> Range<Au> {
let default_values = device.default_computed_values();
let mut conditions = RuleCacheConditions::default();
// http://dev.w3.org/csswg/mediaqueries3/#units
// em units are relative to the initial font-size.
let context = computed::Context {
@ -257,6 +260,7 @@ impl Range<specified::Length> {
cached_system_font: None,
quirks_mode: quirks_mode,
for_smil_animation: false,
rule_cache_conditions: RefCell::new(&mut conditions),
};
match *self {

View file

@ -574,6 +574,7 @@ where
}
let implemented_pseudo = self.element.implemented_pseudo_element();
let mut conditions = Default::default();
let values =
cascade(
self.context.shared.stylist.device(),
@ -587,8 +588,15 @@ where
&self.context.thread_local.font_metrics_provider,
cascade_flags,
self.context.shared.quirks_mode(),
Some(&self.context.thread_local.rule_cache),
&mut conditions,
);
self.context
.thread_local
.rule_cache
.insert_if_possible(&values, &conditions);
values
}
}

View file

@ -17,10 +17,12 @@ use font_metrics::get_metrics_provider_for_product;
use media_queries::Device;
use parser::{ParserContext, ParserErrorContext};
use properties::StyleBuilder;
use rule_cache::RuleCacheConditions;
use selectors::parser::SelectorParseError;
use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use std::ascii::AsciiExt;
use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt;
use std::iter::Enumerate;
use std::str::Chars;
@ -707,6 +709,7 @@ impl MaybeNew for ViewportConstraints {
let default_values = device.default_computed_values();
let mut conditions = RuleCacheConditions::default();
let context = Context {
is_root_element: false,
builder: StyleBuilder::for_derived_style(device, default_values, None, None),
@ -715,6 +718,7 @@ impl MaybeNew for ViewportConstraints {
in_media_query: false,
quirks_mode: quirks_mode,
for_smil_animation: false,
rule_cache_conditions: RefCell::new(&mut conditions),
};
// DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom'

View file

@ -771,6 +771,8 @@ impl Stylist {
font_metrics,
cascade_flags,
self.quirks_mode,
/* rule_cache = */ None,
&mut Default::default(),
)
}
@ -987,6 +989,8 @@ impl Stylist {
font_metrics,
cascade_flags,
self.quirks_mode,
/* rule_cache = */ None,
&mut Default::default(),
))
} else {
None
@ -1012,6 +1016,8 @@ impl Stylist {
font_metrics,
cascade_flags,
self.quirks_mode,
/* rule_cache = */ None,
&mut Default::default(),
)
}
@ -1625,6 +1631,8 @@ impl Stylist {
&metrics,
CascadeFlags::empty(),
self.quirks_mode,
/* rule_cache = */ None,
&mut Default::default(),
)
}

View file

@ -12,9 +12,11 @@ use media_queries::Device;
#[cfg(feature = "gecko")]
use properties;
use properties::{ComputedValues, StyleBuilder};
use rule_cache::RuleCacheConditions;
#[cfg(feature = "servo")]
use servo_url::ServoUrl;
use std::{f32, fmt};
use std::cell::RefCell;
#[cfg(feature = "servo")]
use std::sync::Arc;
use style_traits::ToCss;
@ -116,6 +118,11 @@ pub struct Context<'a> {
/// This is used to allow certain properties to generate out-of-range
/// values, which SMIL allows.
pub for_smil_animation: bool,
/// The conditions to cache a rule node on the rule cache.
///
/// FIXME(emilio): Drop the refcell.
pub rule_cache_conditions: RefCell<&'a mut RuleCacheConditions>,
}
impl<'a> Context<'a> {

View file

@ -131,6 +131,12 @@ impl FontRelativeLength {
match *self {
FontRelativeLength::Em(length) => {
if !matches!(base_size, FontBaseSize::InheritedStyle) {
context.rule_cache_conditions.borrow_mut()
.set_font_size_dependency(
reference_font_size.into()
);
}
(reference_font_size, length)
},
FontRelativeLength::Ex(length) => {

View file

@ -9,6 +9,7 @@ use malloc_size_of::MallocSizeOfOps;
use selectors::Element;
use selectors::matching::{MatchingContext, MatchingMode, matches_selector};
use servo_arc::{Arc, ArcBorrow, RawOffsetArc};
use std::cell::RefCell;
use std::env;
use std::fmt::Write;
use std::iter;
@ -116,6 +117,7 @@ use style::properties::PROHIBIT_DISPLAY_CONTENTS;
use style::properties::animated_properties::{AnimatableLonghand, AnimationValue};
use style::properties::animated_properties::compare_property_priority;
use style::properties::parse_one_declaration_into;
use style::rule_cache::RuleCacheConditions;
use style::rule_tree::{CascadeLevel, StyleSource};
use style::selector_parser::PseudoElementCascadeType;
use style::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked};
@ -3186,6 +3188,7 @@ fn create_context<'a>(
parent_style: Option<&'a ComputedValues>,
pseudo: Option<&'a PseudoElement>,
for_smil_animation: bool,
rule_cache_conditions: &'a mut RuleCacheConditions,
) -> Context<'a> {
Context {
is_root_element: false,
@ -3200,6 +3203,7 @@ fn create_context<'a>(
in_media_query: false,
quirks_mode: per_doc_data.stylist.quirks_mode(),
for_smil_animation,
rule_cache_conditions: RefCell::new(rule_cache_conditions),
}
}
@ -3269,6 +3273,7 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(keyframes: RawGeckoKeyframeLis
let parent_style = parent_data.as_ref().map(|d| d.styles.primary()).map(|x| &**x);
let pseudo = style.pseudo();
let mut conditions = Default::default();
let mut context = create_context(
&data,
&metrics,
@ -3276,6 +3281,7 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(keyframes: RawGeckoKeyframeLis
parent_style,
pseudo.as_ref(),
/* for_smil_animation = */ false,
&mut conditions,
);
let global_style_data = &*GLOBAL_STYLE_DATA;
@ -3356,13 +3362,15 @@ pub extern "C" fn Servo_GetAnimationValues(declarations: RawServoDeclarationBloc
let parent_style = parent_data.as_ref().map(|d| d.styles.primary()).map(|x| &**x);
let pseudo = style.pseudo();
let mut conditions = Default::default();
let mut context = create_context(
&data,
&metrics,
&style,
parent_style,
pseudo.as_ref(),
/* for_smil_animation = */ true
/* for_smil_animation = */ true,
&mut conditions,
);
let default_values = data.default_computed_values();
@ -3392,13 +3400,15 @@ pub extern "C" fn Servo_AnimationValue_Compute(element: RawGeckoElementBorrowed,
let parent_style = parent_data.as_ref().map(|d| d.styles.primary()).map(|x| &**x);
let pseudo = style.pseudo();
let mut conditions = Default::default();
let mut context = create_context(
&data,
&metrics,
style,
parent_style,
pseudo.as_ref(),
/* for_smil_animation = */ false
/* for_smil_animation = */ false,
&mut conditions,
);
let default_values = data.default_computed_values();