mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Auto merge of #17281 - bholley:shrink_rule_again, r=emilio
Pack bloom filter hashes better and save a word on Rule https://bugzilla.mozilla.org/show_bug.cgi?id=1371949 <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17281) <!-- Reviewable:end -->
This commit is contained in:
commit
75d6796cbd
16 changed files with 251 additions and 96 deletions
|
@ -97,6 +97,7 @@ use std::convert::TryFrom;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use style::applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
||||||
use style::context::{QuirksMode, ReflowGoal};
|
use style::context::{QuirksMode, ReflowGoal};
|
||||||
use style::element_state::*;
|
use style::element_state::*;
|
||||||
|
@ -109,7 +110,6 @@ use style::selector_parser::extended_filtering;
|
||||||
use style::shared_lock::{SharedRwLock, Locked};
|
use style::shared_lock::{SharedRwLock, Locked};
|
||||||
use style::sink::Push;
|
use style::sink::Push;
|
||||||
use style::stylearc::Arc;
|
use style::stylearc::Arc;
|
||||||
use style::stylist::ApplicableDeclarationBlock;
|
|
||||||
use style::thread_state;
|
use style::thread_state;
|
||||||
use style::values::{CSSFloat, Either};
|
use style::values::{CSSFloat, Either};
|
||||||
use style::values::specified;
|
use style::values::specified;
|
||||||
|
|
|
@ -61,6 +61,7 @@ use std::marker::PhantomData;
|
||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use style;
|
use style;
|
||||||
|
use style::applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use style::attr::AttrValue;
|
use style::attr::AttrValue;
|
||||||
use style::computed_values::display;
|
use style::computed_values::display;
|
||||||
use style::context::{QuirksMode, SharedStyleContext};
|
use style::context::{QuirksMode, SharedStyleContext};
|
||||||
|
@ -76,7 +77,6 @@ use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocke
|
||||||
use style::sink::Push;
|
use style::sink::Push;
|
||||||
use style::str::is_whitespace;
|
use style::str::is_whitespace;
|
||||||
use style::stylearc::Arc;
|
use style::stylearc::Arc;
|
||||||
use style::stylist::ApplicableDeclarationBlock;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct ServoLayoutNode<'a> {
|
pub struct ServoLayoutNode<'a> {
|
||||||
|
|
|
@ -7,10 +7,13 @@
|
||||||
use fnv::FnvHasher;
|
use fnv::FnvHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
// The top 12 bits of the 32-bit hash value are not used by the bloom filter.
|
||||||
|
// Consumers may rely on this to pack hashes more efficiently.
|
||||||
|
pub const BLOOM_HASH_MASK: u32 = 0x00ffffff;
|
||||||
const KEY_SIZE: usize = 12;
|
const KEY_SIZE: usize = 12;
|
||||||
|
|
||||||
const ARRAY_SIZE: usize = 1 << KEY_SIZE;
|
const ARRAY_SIZE: usize = 1 << KEY_SIZE;
|
||||||
const KEY_MASK: u32 = (1 << KEY_SIZE) - 1;
|
const KEY_MASK: u32 = (1 << KEY_SIZE) - 1;
|
||||||
const KEY_SHIFT: usize = 16;
|
|
||||||
|
|
||||||
/// A counting Bloom filter with 8-bit counters. For now we assume
|
/// A counting Bloom filter with 8-bit counters. For now we assume
|
||||||
/// that having two hash functions is enough, but we may revisit that
|
/// that having two hash functions is enough, but we may revisit that
|
||||||
|
@ -183,7 +186,7 @@ fn hash1(hash: u32) -> u32 {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn hash2(hash: u32) -> u32 {
|
fn hash2(hash: u32) -> u32 {
|
||||||
(hash >> KEY_SHIFT) & KEY_MASK
|
(hash >> KEY_SIZE) & KEY_MASK
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
|
use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
|
||||||
use bloom::BloomFilter;
|
use bloom::{BLOOM_HASH_MASK, BloomFilter};
|
||||||
use parser::{AncestorHashes, Combinator, Component, LocalName};
|
use parser::{AncestorHashes, Combinator, Component, LocalName};
|
||||||
use parser::{Selector, SelectorImpl, SelectorIter, SelectorList};
|
use parser::{Selector, SelectorImpl, SelectorIter, SelectorList};
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
@ -279,19 +279,30 @@ fn may_match<E>(hashes: &AncestorHashes,
|
||||||
-> bool
|
-> bool
|
||||||
where E: Element,
|
where E: Element,
|
||||||
{
|
{
|
||||||
// Check against the list of precomputed hashes.
|
// Check the first three hashes. Note that we can check for zero before
|
||||||
for hash in hashes.0.iter() {
|
// masking off the high bits, since if any of the first three hashes is
|
||||||
// If we hit the 0 sentinel hash, that means the rest are zero as well.
|
// zero the fourth will be as well. We also take care to avoid the
|
||||||
if *hash == 0 {
|
// special-case complexity of the fourth hash until we actually reach it,
|
||||||
break;
|
// because we usually don't.
|
||||||
|
//
|
||||||
|
// To be clear: this is all extremely hot.
|
||||||
|
for i in 0..3 {
|
||||||
|
let packed = hashes.packed_hashes[i];
|
||||||
|
if packed == 0 {
|
||||||
|
// No more hashes left - unable to fast-reject.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bf.might_contain_hash(*hash) {
|
if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) {
|
||||||
|
// Hooray! We fast-rejected on this hash.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
// Now do the slighty-more-complex work of synthesizing the fourth hash,
|
||||||
|
// and check it against the filter if it exists.
|
||||||
|
let fourth = hashes.fourth_hash();
|
||||||
|
fourth == 0 || bf.might_contain_hash(fourth)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tracks whether we are currently looking for relevant links for a given
|
/// Tracks whether we are currently looking for relevant links for a given
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use attr::{AttrSelectorWithNamespace, ParsedAttrSelectorOperation, AttrSelectorOperator};
|
use attr::{AttrSelectorWithNamespace, ParsedAttrSelectorOperation, AttrSelectorOperator};
|
||||||
use attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE, NamespaceConstraint};
|
use attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE, NamespaceConstraint};
|
||||||
|
use bloom::BLOOM_HASH_MASK;
|
||||||
use cssparser::{ParseError, BasicParseError};
|
use cssparser::{ParseError, BasicParseError};
|
||||||
use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
|
use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
|
||||||
use precomputed_hash::PrecomputedHash;
|
use precomputed_hash::PrecomputedHash;
|
||||||
|
@ -203,16 +204,25 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copied from Gecko, who copied it from WebKit. Note that increasing the
|
|
||||||
/// number of hashes here will adversely affect the cache hit when fast-
|
|
||||||
/// rejecting long lists of Rules with inline hashes.
|
|
||||||
const NUM_ANCESTOR_HASHES: usize = 4;
|
|
||||||
|
|
||||||
/// Ancestor hashes for the bloom filter. We precompute these and store them
|
/// Ancestor hashes for the bloom filter. We precompute these and store them
|
||||||
/// inline with selectors to optimize cache performance during matching.
|
/// inline with selectors to optimize cache performance during matching.
|
||||||
/// This matters a lot.
|
/// This matters a lot.
|
||||||
|
///
|
||||||
|
/// We use 4 hashes, which is copied from Gecko, who copied it from WebKit.
|
||||||
|
/// Note that increasing the number of hashes here will adversely affect the
|
||||||
|
/// cache hit when fast-rejecting long lists of Rules with inline hashes.
|
||||||
|
///
|
||||||
|
/// Because the bloom filter only uses the bottom 24 bits of the hash, we pack
|
||||||
|
/// the fourth hash into the upper bits of the first three hashes in order to
|
||||||
|
/// shrink Rule (whose size matters a lot). This scheme minimizes the runtime
|
||||||
|
/// overhead of the packing for the first three hashes (we just need to mask
|
||||||
|
/// off the upper bits) at the expense of making the fourth somewhat more
|
||||||
|
/// complicated to assemble, because we often bail out before checking all the
|
||||||
|
/// hashes.
|
||||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||||
pub struct AncestorHashes(pub [u32; NUM_ANCESTOR_HASHES]);
|
pub struct AncestorHashes {
|
||||||
|
pub packed_hashes: [u32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
impl AncestorHashes {
|
impl AncestorHashes {
|
||||||
pub fn new<Impl: SelectorImpl>(s: &Selector<Impl>) -> Self {
|
pub fn new<Impl: SelectorImpl>(s: &Selector<Impl>) -> Self {
|
||||||
|
@ -220,20 +230,38 @@ impl AncestorHashes {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_iter<Impl: SelectorImpl>(iter: SelectorIter<Impl>) -> Self {
|
pub fn from_iter<Impl: SelectorImpl>(iter: SelectorIter<Impl>) -> Self {
|
||||||
let mut hashes = [0; NUM_ANCESTOR_HASHES];
|
|
||||||
// Compute ancestor hashes for the bloom filter.
|
// Compute ancestor hashes for the bloom filter.
|
||||||
|
let mut hashes = [0u32; 4];
|
||||||
let mut hash_iter = AncestorIter::new(iter)
|
let mut hash_iter = AncestorIter::new(iter)
|
||||||
.map(|x| x.ancestor_hash())
|
.map(|x| x.ancestor_hash())
|
||||||
.filter(|x| x.is_some())
|
.filter(|x| x.is_some())
|
||||||
.map(|x| x.unwrap());
|
.map(|x| x.unwrap());
|
||||||
for i in 0..NUM_ANCESTOR_HASHES {
|
for i in 0..4 {
|
||||||
hashes[i] = match hash_iter.next() {
|
hashes[i] = match hash_iter.next() {
|
||||||
Some(x) => x,
|
Some(x) => x & BLOOM_HASH_MASK,
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AncestorHashes(hashes)
|
// Now, pack the fourth hash (if it exists) into the upper byte of each of
|
||||||
|
// the other three hashes.
|
||||||
|
let fourth = hashes[3];
|
||||||
|
if fourth != 0 {
|
||||||
|
hashes[0] |= (fourth & 0x000000ff) << 24;
|
||||||
|
hashes[1] |= (fourth & 0x0000ff00) << 16;
|
||||||
|
hashes[2] |= (fourth & 0x00ff0000) << 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
AncestorHashes {
|
||||||
|
packed_hashes: [hashes[0], hashes[1], hashes[2]],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the fourth hash, reassembled from parts.
|
||||||
|
pub fn fourth_hash(&self) -> u32 {
|
||||||
|
((self.packed_hashes[0] & 0xff000000) >> 24) |
|
||||||
|
((self.packed_hashes[1] & 0xff000000) >> 16) |
|
||||||
|
((self.packed_hashes[2] & 0xff000000) >> 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
137
components/style/applicable_declarations.rs
Normal file
137
components/style/applicable_declarations.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
//! Applicable declarations management.
|
||||||
|
|
||||||
|
use properties::PropertyDeclarationBlock;
|
||||||
|
use rule_tree::{CascadeLevel, StyleSource};
|
||||||
|
use shared_lock::Locked;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::fmt::{Debug, self};
|
||||||
|
use std::mem;
|
||||||
|
use stylearc::Arc;
|
||||||
|
|
||||||
|
/// List of applicable declarations. This is a transient structure that shuttles
|
||||||
|
/// declarations between selector matching and inserting into the rule tree, and
|
||||||
|
/// therefore we want to avoid heap-allocation where possible.
|
||||||
|
///
|
||||||
|
/// In measurements on wikipedia, we pretty much never have more than 8 applicable
|
||||||
|
/// declarations, so we could consider making this 8 entries instead of 16.
|
||||||
|
/// However, it may depend a lot on workload, and stack space is cheap.
|
||||||
|
pub type ApplicableDeclarationList = SmallVec<[ApplicableDeclarationBlock; 16]>;
|
||||||
|
|
||||||
|
/// Blink uses 18 bits to store source order, and does not check overflow [1].
|
||||||
|
/// That's a limit that could be reached in realistic webpages, so we use
|
||||||
|
/// 24 bits and enforce defined behavior in the overflow case.
|
||||||
|
///
|
||||||
|
/// Note that the value of 24 is also hard-coded into the level() accessor,
|
||||||
|
/// which does a byte-aligned load of the 4th byte. If you change this value
|
||||||
|
/// you'll need to change that as well.
|
||||||
|
///
|
||||||
|
/// [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/css/
|
||||||
|
/// RuleSet.h?l=128&rcl=90140ab80b84d0f889abc253410f44ed54ae04f3
|
||||||
|
const SOURCE_ORDER_BITS: usize = 24;
|
||||||
|
const SOURCE_ORDER_MASK: u32 = (1 << SOURCE_ORDER_BITS) - 1;
|
||||||
|
const SOURCE_ORDER_MAX: u32 = SOURCE_ORDER_MASK;
|
||||||
|
|
||||||
|
/// Stores the source order of a block and the cascade level it belongs to.
|
||||||
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
struct SourceOrderAndCascadeLevel(u32);
|
||||||
|
|
||||||
|
impl SourceOrderAndCascadeLevel {
|
||||||
|
fn new(source_order: u32, cascade_level: CascadeLevel) -> SourceOrderAndCascadeLevel {
|
||||||
|
let mut bits = ::std::cmp::min(source_order, SOURCE_ORDER_MAX);
|
||||||
|
bits |= (cascade_level as u8 as u32) << SOURCE_ORDER_BITS;
|
||||||
|
SourceOrderAndCascadeLevel(bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn order(&self) -> u32 {
|
||||||
|
self.0 & SOURCE_ORDER_MASK
|
||||||
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> CascadeLevel {
|
||||||
|
unsafe {
|
||||||
|
// Transmute rather than shifting so that we're sure the compiler
|
||||||
|
// emits a simple byte-aligned load.
|
||||||
|
let as_bytes: [u8; 4] = mem::transmute(self.0);
|
||||||
|
CascadeLevel::from_byte(as_bytes[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for SourceOrderAndCascadeLevel {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("SourceOrderAndCascadeLevel")
|
||||||
|
.field("order", &self.order())
|
||||||
|
.field("level", &self.level())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A property declaration together with its precedence among rules of equal
|
||||||
|
/// specificity so that we can sort them.
|
||||||
|
///
|
||||||
|
/// This represents the declarations in a given declaration block for a given
|
||||||
|
/// importance.
|
||||||
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct ApplicableDeclarationBlock {
|
||||||
|
/// The style source, either a style rule, or a property declaration block.
|
||||||
|
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
|
||||||
|
pub source: StyleSource,
|
||||||
|
/// The source order of the block, and the cascade level it belongs to.
|
||||||
|
order_and_level: SourceOrderAndCascadeLevel,
|
||||||
|
/// The specificity of the selector this block is represented by.
|
||||||
|
pub specificity: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicableDeclarationBlock {
|
||||||
|
/// Constructs an applicable declaration block from a given property
|
||||||
|
/// declaration block and importance.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_declarations(declarations: Arc<Locked<PropertyDeclarationBlock>>,
|
||||||
|
level: CascadeLevel)
|
||||||
|
-> Self {
|
||||||
|
ApplicableDeclarationBlock {
|
||||||
|
source: StyleSource::Declarations(declarations),
|
||||||
|
order_and_level: SourceOrderAndCascadeLevel::new(0, level),
|
||||||
|
specificity: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs an applicable declaration block from the given components
|
||||||
|
#[inline]
|
||||||
|
pub fn new(source: StyleSource,
|
||||||
|
order: u32,
|
||||||
|
level: CascadeLevel,
|
||||||
|
specificity: u32) -> Self {
|
||||||
|
ApplicableDeclarationBlock {
|
||||||
|
source: source,
|
||||||
|
order_and_level: SourceOrderAndCascadeLevel::new(order, level),
|
||||||
|
specificity: specificity,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the source order of the block.
|
||||||
|
#[inline]
|
||||||
|
pub fn source_order(&self) -> u32 {
|
||||||
|
self.order_and_level.order()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the cascade level of the block.
|
||||||
|
#[inline]
|
||||||
|
pub fn level(&self) -> CascadeLevel {
|
||||||
|
self.order_and_level.level()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience method to consume self and return the source alongside the
|
||||||
|
/// level.
|
||||||
|
#[inline]
|
||||||
|
pub fn order_and_level(self) -> (StyleSource, CascadeLevel) {
|
||||||
|
let level = self.level();
|
||||||
|
(self.source, level)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use {Atom, Namespace, LocalName};
|
use {Atom, Namespace, LocalName};
|
||||||
|
use applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||||
#[cfg(feature = "gecko")] use context::UpdateAnimationsTasks;
|
#[cfg(feature = "gecko")] use context::UpdateAnimationsTasks;
|
||||||
use data::ElementData;
|
use data::ElementData;
|
||||||
|
@ -29,7 +30,6 @@ use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use stylearc::Arc;
|
use stylearc::Arc;
|
||||||
use stylist::ApplicableDeclarationBlock;
|
|
||||||
use thread_state;
|
use thread_state;
|
||||||
|
|
||||||
pub use style_traits::UnsafeNode;
|
pub use style_traits::UnsafeNode;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
//! the separation between the style system implementation and everything else.
|
//! the separation between the style system implementation and everything else.
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
|
use applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use atomic_refcell::AtomicRefCell;
|
use atomic_refcell::AtomicRefCell;
|
||||||
use context::{QuirksMode, SharedStyleContext, UpdateAnimationsTasks};
|
use context::{QuirksMode, SharedStyleContext, UpdateAnimationsTasks};
|
||||||
use data::ElementData;
|
use data::ElementData;
|
||||||
|
@ -87,7 +88,6 @@ use std::ptr;
|
||||||
use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
|
use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
|
||||||
use stylearc::Arc;
|
use stylearc::Arc;
|
||||||
use stylesheets::UrlExtraData;
|
use stylesheets::UrlExtraData;
|
||||||
use stylist::ApplicableDeclarationBlock;
|
|
||||||
|
|
||||||
/// A simple wrapper over a non-null Gecko node (`nsINode`) pointer.
|
/// A simple wrapper over a non-null Gecko node (`nsINode`) pointer.
|
||||||
///
|
///
|
||||||
|
|
|
@ -91,6 +91,7 @@ extern crate unicode_segmentation;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
|
pub mod applicable_declarations;
|
||||||
#[allow(missing_docs)] // TODO.
|
#[allow(missing_docs)] // TODO.
|
||||||
#[cfg(feature = "servo")] pub mod attr;
|
#[cfg(feature = "servo")] pub mod attr;
|
||||||
pub mod bezier;
|
pub mod bezier;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use applicable_declarations::ApplicableDeclarationList;
|
||||||
use cascade_info::CascadeInfo;
|
use cascade_info::CascadeInfo;
|
||||||
use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
|
use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
|
||||||
use data::{ComputedStyle, ElementData, RestyleData};
|
use data::{ComputedStyle, ElementData, RestyleData};
|
||||||
|
@ -25,7 +26,7 @@ use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, S
|
||||||
use selectors::matching::{VisitedHandlingMode, AFFECTED_BY_PSEUDO_ELEMENTS};
|
use selectors::matching::{VisitedHandlingMode, AFFECTED_BY_PSEUDO_ELEMENTS};
|
||||||
use sharing::StyleSharingBehavior;
|
use sharing::StyleSharingBehavior;
|
||||||
use stylearc::Arc;
|
use stylearc::Arc;
|
||||||
use stylist::{ApplicableDeclarationList, RuleInclusion};
|
use stylist::RuleInclusion;
|
||||||
|
|
||||||
/// The way a style should be inherited.
|
/// The way a style should be inherited.
|
||||||
enum InheritMode {
|
enum InheritMode {
|
||||||
|
|
|
@ -6,17 +6,18 @@
|
||||||
|
|
||||||
//! The rule tree.
|
//! The rule tree.
|
||||||
|
|
||||||
|
use applicable_declarations::ApplicableDeclarationList;
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
use heapsize::HeapSizeOf;
|
use heapsize::HeapSizeOf;
|
||||||
use properties::{AnimationRules, Importance, LonghandIdSet, PropertyDeclarationBlock};
|
use properties::{AnimationRules, Importance, LonghandIdSet, PropertyDeclarationBlock};
|
||||||
use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
|
use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
use std::mem;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
||||||
use stylearc::{Arc, NonZeroPtrMut};
|
use stylearc::{Arc, NonZeroPtrMut};
|
||||||
use stylesheets::StyleRule;
|
use stylesheets::StyleRule;
|
||||||
use stylist::ApplicableDeclarationList;
|
|
||||||
use thread_state;
|
use thread_state;
|
||||||
|
|
||||||
/// The rule tree, the structure servo uses to preserve the results of selector
|
/// The rule tree, the structure servo uses to preserve the results of selector
|
||||||
|
@ -229,7 +230,7 @@ impl RuleTree {
|
||||||
guards: &StylesheetGuards)
|
guards: &StylesheetGuards)
|
||||||
-> StrongRuleNode
|
-> StrongRuleNode
|
||||||
{
|
{
|
||||||
let rules = applicable_declarations.drain().map(|d| (d.source, d.level));
|
let rules = applicable_declarations.drain().map(|d| d.order_and_level());
|
||||||
let rule_node = self.insert_ordered_rules_with_important(rules, guards);
|
let rule_node = self.insert_ordered_rules_with_important(rules, guards);
|
||||||
rule_node
|
rule_node
|
||||||
}
|
}
|
||||||
|
@ -425,10 +426,18 @@ pub enum CascadeLevel {
|
||||||
/// User-agent important rules.
|
/// User-agent important rules.
|
||||||
UAImportant,
|
UAImportant,
|
||||||
/// Transitions
|
/// Transitions
|
||||||
|
///
|
||||||
|
/// NB: If this changes from being last, change from_byte below.
|
||||||
Transitions,
|
Transitions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CascadeLevel {
|
impl CascadeLevel {
|
||||||
|
/// Converts a raw byte to a CascadeLevel.
|
||||||
|
pub unsafe fn from_byte(byte: u8) -> Self {
|
||||||
|
debug_assert!(byte <= CascadeLevel::Transitions as u8);
|
||||||
|
mem::transmute(byte)
|
||||||
|
}
|
||||||
|
|
||||||
/// Select a lock guard for this level
|
/// Select a lock guard for this level
|
||||||
pub fn guard<'a>(&self, guards: &'a StylesheetGuards<'a>) -> &'a SharedRwLockReadGuard<'a> {
|
pub fn guard<'a>(&self, guards: &'a StylesheetGuards<'a>) -> &'a SharedRwLockReadGuard<'a> {
|
||||||
match *self {
|
match *self {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//! name, ids and hash.
|
//! name, ids and hash.
|
||||||
|
|
||||||
use {Atom, LocalName};
|
use {Atom, LocalName};
|
||||||
|
use applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use dom::TElement;
|
use dom::TElement;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use pdqsort::sort_by;
|
use pdqsort::sort_by;
|
||||||
|
@ -18,7 +19,7 @@ use smallvec::VecLike;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use stylist::{ApplicableDeclarationBlock, Rule};
|
use stylist::Rule;
|
||||||
|
|
||||||
/// A trait to abstract over a given selector map entry.
|
/// A trait to abstract over a given selector map entry.
|
||||||
pub trait SelectorMapEntry : Sized + Clone {
|
pub trait SelectorMapEntry : Sized + Clone {
|
||||||
|
@ -163,7 +164,7 @@ impl SelectorMap<Rule> {
|
||||||
|
|
||||||
// Sort only the rules we just added.
|
// Sort only the rules we just added.
|
||||||
sort_by_key(&mut matching_rules_list[init_len..],
|
sort_by_key(&mut matching_rules_list[init_len..],
|
||||||
|block| (block.specificity, block.source_order));
|
|block| (block.specificity, block.source_order()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether we have rules for the given id
|
/// Check whether we have rules for the given id
|
||||||
|
@ -190,7 +191,7 @@ impl SelectorMap<Rule> {
|
||||||
}
|
}
|
||||||
|
|
||||||
sort_by_key(&mut rules_list,
|
sort_by_key(&mut rules_list,
|
||||||
|block| (block.specificity, block.source_order));
|
|block| (block.specificity, block.source_order()));
|
||||||
|
|
||||||
rules_list
|
rules_list
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
//! elements makes sense.
|
//! elements makes sense.
|
||||||
|
|
||||||
use Atom;
|
use Atom;
|
||||||
|
use applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use bit_vec::BitVec;
|
use bit_vec::BitVec;
|
||||||
use bloom::StyleBloom;
|
use bloom::StyleBloom;
|
||||||
use cache::{LRUCache, LRUCacheMutIterator};
|
use cache::{LRUCache, LRUCacheMutIterator};
|
||||||
|
@ -78,7 +79,7 @@ use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode, StyleRelati
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use stylist::{ApplicableDeclarationBlock, Stylist};
|
use stylist::Stylist;
|
||||||
|
|
||||||
mod checks;
|
mod checks;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
//! Selector matching.
|
//! Selector matching.
|
||||||
|
|
||||||
use {Atom, LocalName, Namespace};
|
use {Atom, LocalName, Namespace};
|
||||||
|
use applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
|
||||||
use bit_vec::BitVec;
|
use bit_vec::BitVec;
|
||||||
use context::{QuirksMode, SharedStyleContext};
|
use context::{QuirksMode, SharedStyleContext};
|
||||||
use data::ComputedStyle;
|
use data::ComputedStyle;
|
||||||
|
@ -33,7 +34,8 @@ use selectors::parser::{SelectorIter, SelectorMethods};
|
||||||
use selectors::visitor::SelectorVisitor;
|
use selectors::visitor::SelectorVisitor;
|
||||||
use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
|
use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
|
||||||
use sink::Push;
|
use sink::Push;
|
||||||
use smallvec::{SmallVec, VecLike};
|
use smallvec::VecLike;
|
||||||
|
use std::fmt::Debug;
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use style_traits::viewport::ViewportConstraints;
|
use style_traits::viewport::ViewportConstraints;
|
||||||
|
@ -48,15 +50,6 @@ use thread_state;
|
||||||
|
|
||||||
pub use ::fnv::FnvHashMap;
|
pub use ::fnv::FnvHashMap;
|
||||||
|
|
||||||
/// List of applicable declaration. This is a transient structure that shuttles
|
|
||||||
/// declarations between selector matching and inserting into the rule tree, and
|
|
||||||
/// therefore we want to avoid heap-allocation where possible.
|
|
||||||
///
|
|
||||||
/// In measurements on wikipedia, we pretty much never have more than 8 applicable
|
|
||||||
/// declarations, so we could consider making this 8 entries instead of 16.
|
|
||||||
/// However, it may depend a lot on workload, and stack space is cheap.
|
|
||||||
pub type ApplicableDeclarationList = SmallVec<[ApplicableDeclarationBlock; 16]>;
|
|
||||||
|
|
||||||
/// This structure holds all the selectors and device characteristics
|
/// This structure holds all the selectors and device characteristics
|
||||||
/// for a given document. The selectors are converted into `Rule`s
|
/// for a given document. The selectors are converted into `Rule`s
|
||||||
/// (defined in rust-selectors), and introduced in a `SelectorMap`
|
/// (defined in rust-selectors), and introduced in a `SelectorMap`
|
||||||
|
@ -121,7 +114,7 @@ pub struct Stylist {
|
||||||
|
|
||||||
/// A monotonically increasing counter to represent the order on which a
|
/// A monotonically increasing counter to represent the order on which a
|
||||||
/// style rule appears in a stylesheet, needed to sort them by source order.
|
/// style rule appears in a stylesheet, needed to sort them by source order.
|
||||||
rules_source_order: usize,
|
rules_source_order: u32,
|
||||||
|
|
||||||
/// Selector dependencies used to compute restyle hints.
|
/// Selector dependencies used to compute restyle hints.
|
||||||
dependencies: DependencySet,
|
dependencies: DependencySet,
|
||||||
|
@ -584,7 +577,7 @@ impl Stylist {
|
||||||
// FIXME(emilio): When we've taken rid of the cascade we can just
|
// FIXME(emilio): When we've taken rid of the cascade we can just
|
||||||
// use into_iter.
|
// use into_iter.
|
||||||
self.rule_tree.insert_ordered_rules_with_important(
|
self.rule_tree.insert_ordered_rules_with_important(
|
||||||
declarations.into_iter().map(|a| (a.source.clone(), a.level)),
|
declarations.into_iter().map(|a| (a.source.clone(), a.level())),
|
||||||
guards)
|
guards)
|
||||||
}
|
}
|
||||||
None => self.rule_tree.root(),
|
None => self.rule_tree.root(),
|
||||||
|
@ -770,7 +763,7 @@ impl Stylist {
|
||||||
|
|
||||||
let rule_node =
|
let rule_node =
|
||||||
self.rule_tree.insert_ordered_rules_with_important(
|
self.rule_tree.insert_ordered_rules_with_important(
|
||||||
declarations.into_iter().map(|a| (a.source, a.level)),
|
declarations.into_iter().map(|a| a.order_and_level()),
|
||||||
guards);
|
guards);
|
||||||
if rule_node == self.rule_tree.root() {
|
if rule_node == self.rule_tree.root() {
|
||||||
None
|
None
|
||||||
|
@ -975,7 +968,7 @@ impl Stylist {
|
||||||
context: &mut MatchingContext,
|
context: &mut MatchingContext,
|
||||||
flags_setter: &mut F)
|
flags_setter: &mut F)
|
||||||
where E: TElement,
|
where E: TElement,
|
||||||
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> + ::std::fmt::Debug,
|
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> + Debug,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
F: FnMut(&E, ElementSelectorFlags),
|
||||||
{
|
{
|
||||||
debug_assert!(!self.is_device_dirty);
|
debug_assert!(!self.is_device_dirty);
|
||||||
|
@ -1027,7 +1020,7 @@ impl Stylist {
|
||||||
if applicable_declarations.len() != length_before_preshints {
|
if applicable_declarations.len() != length_before_preshints {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
for declaration in &applicable_declarations[length_before_preshints..] {
|
for declaration in &applicable_declarations[length_before_preshints..] {
|
||||||
assert_eq!(declaration.level, CascadeLevel::PresHints);
|
assert_eq!(declaration.level(), CascadeLevel::PresHints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Note the existence of presentational attributes so that the
|
// Note the existence of presentational attributes so that the
|
||||||
|
@ -1222,7 +1215,7 @@ impl Stylist {
|
||||||
CascadeLevel::StyleAttributeNormal)
|
CascadeLevel::StyleAttributeNormal)
|
||||||
];
|
];
|
||||||
let rule_node =
|
let rule_node =
|
||||||
self.rule_tree.insert_ordered_rules(v.into_iter().map(|a| (a.source, a.level)));
|
self.rule_tree.insert_ordered_rules(v.into_iter().map(|a| a.order_and_level()));
|
||||||
|
|
||||||
// This currently ignores visited styles. It appears to be used for
|
// This currently ignores visited styles. It appears to be used for
|
||||||
// font styles in <canvas> via Servo_StyleSet_ResolveForDeclarations.
|
// font styles in <canvas> via Servo_StyleSet_ResolveForDeclarations.
|
||||||
|
@ -1513,11 +1506,13 @@ pub struct Rule {
|
||||||
/// The ancestor hashes associated with the selector.
|
/// The ancestor hashes associated with the selector.
|
||||||
#[cfg_attr(feature = "servo", ignore_heap_size_of = "No heap data")]
|
#[cfg_attr(feature = "servo", ignore_heap_size_of = "No heap data")]
|
||||||
pub hashes: AncestorHashes,
|
pub hashes: AncestorHashes,
|
||||||
|
/// The source order this style rule appears in. Note that we only use
|
||||||
|
/// three bytes to store this value in ApplicableDeclarationsBlock, so
|
||||||
|
/// we could repurpose that storage here if we needed to.
|
||||||
|
pub source_order: u32,
|
||||||
/// The actual style rule.
|
/// The actual style rule.
|
||||||
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
|
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
|
||||||
pub style_rule: Arc<Locked<StyleRule>>,
|
pub style_rule: Arc<Locked<StyleRule>>,
|
||||||
/// The source order this style rule appears in.
|
|
||||||
pub source_order: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectorMapEntry for Rule {
|
impl SelectorMapEntry for Rule {
|
||||||
|
@ -1541,19 +1536,18 @@ impl Rule {
|
||||||
pub fn to_applicable_declaration_block(&self,
|
pub fn to_applicable_declaration_block(&self,
|
||||||
level: CascadeLevel)
|
level: CascadeLevel)
|
||||||
-> ApplicableDeclarationBlock {
|
-> ApplicableDeclarationBlock {
|
||||||
ApplicableDeclarationBlock {
|
let source = StyleSource::Style(self.style_rule.clone());
|
||||||
source: StyleSource::Style(self.style_rule.clone()),
|
ApplicableDeclarationBlock::new(source,
|
||||||
source_order: self.source_order,
|
self.source_order,
|
||||||
specificity: self.specificity(),
|
level,
|
||||||
level: level,
|
self.specificity())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new Rule.
|
/// Creates a new Rule.
|
||||||
pub fn new(selector: Selector<SelectorImpl>,
|
pub fn new(selector: Selector<SelectorImpl>,
|
||||||
hashes: AncestorHashes,
|
hashes: AncestorHashes,
|
||||||
style_rule: Arc<Locked<StyleRule>>,
|
style_rule: Arc<Locked<StyleRule>>,
|
||||||
source_order: usize)
|
source_order: u32)
|
||||||
-> Self
|
-> Self
|
||||||
{
|
{
|
||||||
Rule {
|
Rule {
|
||||||
|
@ -1564,38 +1558,3 @@ impl Rule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A property declaration together with its precedence among rules of equal
|
|
||||||
/// specificity so that we can sort them.
|
|
||||||
///
|
|
||||||
/// This represents the declarations in a given declaration block for a given
|
|
||||||
/// importance.
|
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct ApplicableDeclarationBlock {
|
|
||||||
/// The style source, either a style rule, or a property declaration block.
|
|
||||||
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
|
|
||||||
pub source: StyleSource,
|
|
||||||
/// The source order of this block.
|
|
||||||
pub source_order: usize,
|
|
||||||
/// The specificity of the selector this block is represented by.
|
|
||||||
pub specificity: u32,
|
|
||||||
/// The cascade level this applicable declaration block is in.
|
|
||||||
pub level: CascadeLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicableDeclarationBlock {
|
|
||||||
/// Constructs an applicable declaration block from a given property
|
|
||||||
/// declaration block and importance.
|
|
||||||
#[inline]
|
|
||||||
pub fn from_declarations(declarations: Arc<Locked<PropertyDeclarationBlock>>,
|
|
||||||
level: CascadeLevel)
|
|
||||||
-> Self {
|
|
||||||
ApplicableDeclarationBlock {
|
|
||||||
source: StyleSource::Declarations(declarations),
|
|
||||||
source_order: 0,
|
|
||||||
specificity: 0,
|
|
||||||
level: level,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec<Vec<Rule>>, SharedRwLock) {
|
||||||
let guard = shared_lock.read();
|
let guard = shared_lock.read();
|
||||||
let rule = locked.read_with(&guard);
|
let rule = locked.read_with(&guard);
|
||||||
rule.selectors.0.iter().map(|s| {
|
rule.selectors.0.iter().map(|s| {
|
||||||
Rule::new(s.selector.clone(), s.hashes.clone(), locked.clone(), i)
|
Rule::new(s.selector.clone(), s.hashes.clone(), locked.clone(), i as u32)
|
||||||
}).collect()
|
}).collect()
|
||||||
}).collect(), shared_lock)
|
}).collect(), shared_lock)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@ use selectors::gecko_like_types as dummies;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use std::mem::{size_of, align_of};
|
use std::mem::{size_of, align_of};
|
||||||
use style;
|
use style;
|
||||||
|
use style::applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use style::data::{ComputedStyle, ElementData, ElementStyles};
|
use style::data::{ComputedStyle, ElementData, ElementStyles};
|
||||||
use style::gecko::selector_parser as real;
|
use style::gecko::selector_parser as real;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::rule_tree::StrongRuleNode;
|
use style::rule_tree::{RuleNode, StrongRuleNode};
|
||||||
use style::stylist::ApplicableDeclarationBlock;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn size_of_selectors_dummy_types() {
|
fn size_of_selectors_dummy_types() {
|
||||||
|
@ -27,7 +27,7 @@ fn size_of_selectors_dummy_types() {
|
||||||
// The size of this is critical to performance on the bloom-basic microbenchmark.
|
// The size of this is critical to performance on the bloom-basic microbenchmark.
|
||||||
// When iterating over a large Rule array, we want to be able to fast-reject
|
// When iterating over a large Rule array, we want to be able to fast-reject
|
||||||
// selectors (with the inline hashes) with as few cache misses as possible.
|
// selectors (with the inline hashes) with as few cache misses as possible.
|
||||||
size_of_test!(test_size_of_rule, style::stylist::Rule, 40);
|
size_of_test!(test_size_of_rule, style::stylist::Rule, 32);
|
||||||
|
|
||||||
size_of_test!(test_size_of_option_arc_cv, Option<Arc<ComputedValues>>, 8);
|
size_of_test!(test_size_of_option_arc_cv, Option<Arc<ComputedValues>>, 8);
|
||||||
size_of_test!(test_size_of_option_rule_node, Option<StrongRuleNode>, 8);
|
size_of_test!(test_size_of_option_rule_node, Option<StrongRuleNode>, 8);
|
||||||
|
@ -37,7 +37,11 @@ size_of_test!(test_size_of_element_data, ElementData, 56);
|
||||||
|
|
||||||
size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32);
|
size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32);
|
||||||
|
|
||||||
size_of_test!(test_size_of_application_declaration_block, ApplicableDeclarationBlock, 32);
|
size_of_test!(test_size_of_application_declaration_block, ApplicableDeclarationBlock, 24);
|
||||||
|
|
||||||
|
// FIXME(bholley): This can shrink with a little bit of work.
|
||||||
|
// See https://github.com/servo/servo/issues/17280
|
||||||
|
size_of_test!(test_size_of_rule_node, RuleNode, 96);
|
||||||
|
|
||||||
// This is huge, but we allocate it on the stack and then never move it,
|
// This is huge, but we allocate it on the stack and then never move it,
|
||||||
// we only pass `&mut SourcePropertyDeclaration` references around.
|
// we only pass `&mut SourcePropertyDeclaration` references around.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue