mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Refactor rule tree children
We move the data structure to its own module for better encapsulation of unsafe code.
This commit is contained in:
parent
3ca86eeba5
commit
13db0c1584
2 changed files with 201 additions and 172 deletions
168
components/style/rule_tree/map.rs
Normal file
168
components/style/rule_tree/map.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps};
|
||||
use std::hash::Hash;
|
||||
use std::mem;
|
||||
|
||||
pub(super) struct Map<K, V> {
|
||||
inner: MapInner<K, V>,
|
||||
}
|
||||
|
||||
enum MapInner<K, V> {
|
||||
Empty,
|
||||
One(V),
|
||||
Map(Box<FxHashMap<K, V>>),
|
||||
}
|
||||
|
||||
pub(super) struct MapIter<'a, K, V> {
|
||||
inner: MapIterInner<'a, K, V>,
|
||||
}
|
||||
|
||||
enum MapIterInner<'a, K, V> {
|
||||
One(std::option::IntoIter<&'a V>),
|
||||
Map(std::collections::hash_map::Values<'a, K, V>),
|
||||
}
|
||||
|
||||
impl<K, V> Default for Map<K, V> {
|
||||
fn default() -> Self {
|
||||
Map {
|
||||
inner: MapInner::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> IntoIterator for &'a Map<K, V> {
|
||||
type Item = &'a V;
|
||||
type IntoIter = MapIter<'a, K, V>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
MapIter {
|
||||
inner: match &self.inner {
|
||||
MapInner::Empty => MapIterInner::One(None.into_iter()),
|
||||
MapInner::One(one) => MapIterInner::One(Some(one).into_iter()),
|
||||
MapInner::Map(map) => MapIterInner::Map(map.values()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> Iterator for MapIter<'a, K, V> {
|
||||
type Item = &'a V;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match &mut self.inner {
|
||||
MapIterInner::One(one_iter) => one_iter.next(),
|
||||
MapIterInner::Map(map_iter) => map_iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Map<K, V>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
match &self.inner {
|
||||
MapInner::Empty => true,
|
||||
MapInner::One(_) => false,
|
||||
MapInner::Map(map) => map.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(super) fn len(&self) -> usize {
|
||||
match &self.inner {
|
||||
MapInner::Empty => 0,
|
||||
MapInner::One(_) => 1,
|
||||
MapInner::Map(map) => map.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get(&self, key: &K, key_from_value: impl FnOnce(&V) -> K) -> Option<&V> {
|
||||
match &self.inner {
|
||||
MapInner::One(one) if *key == key_from_value(one) => Some(one),
|
||||
MapInner::Map(map) => map.get(key),
|
||||
MapInner::Empty | MapInner::One(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_or_insert_with(
|
||||
&mut self,
|
||||
key: K,
|
||||
key_from_value: impl FnOnce(&V) -> K,
|
||||
new_value: impl FnOnce() -> V,
|
||||
) -> &mut V {
|
||||
match self.inner {
|
||||
MapInner::Empty => {
|
||||
self.inner = MapInner::One(new_value());
|
||||
match &mut self.inner {
|
||||
MapInner::One(one) => one,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
MapInner::One(_) => {
|
||||
let one = match mem::replace(&mut self.inner, MapInner::Empty) {
|
||||
MapInner::One(one) => one,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// If this panics, the child `one` will be lost.
|
||||
let one_key = key_from_value(&one);
|
||||
// Same for the equality test.
|
||||
if key == one_key {
|
||||
self.inner = MapInner::One(one);
|
||||
match &mut self.inner {
|
||||
MapInner::One(one) => return one,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
self.inner = MapInner::Map(Box::new(FxHashMap::with_capacity_and_hasher(
|
||||
2,
|
||||
Default::default(),
|
||||
)));
|
||||
let map = match &mut self.inner {
|
||||
MapInner::Map(map) => map,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
map.insert(one_key, one);
|
||||
// But it doesn't matter if f panics, by this point
|
||||
// the map is as before but represented as a map instead
|
||||
// of a single value.
|
||||
map.entry(key).or_insert_with(new_value)
|
||||
},
|
||||
MapInner::Map(ref mut map) => map.entry(key).or_insert_with(new_value),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn remove(&mut self, key: &K, key_from_value: impl FnOnce(&V) -> K) -> Option<V> {
|
||||
match &mut self.inner {
|
||||
MapInner::One(one) if *key == key_from_value(one) => {
|
||||
match mem::replace(&mut self.inner, MapInner::Empty) {
|
||||
MapInner::One(one) => Some(one),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
MapInner::Map(map) => map.remove(key),
|
||||
MapInner::Empty | MapInner::One(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> MallocShallowSizeOf for Map<K, V>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
|
||||
match &self.inner {
|
||||
MapInner::Map(m) => {
|
||||
// We want to account for both the box and the hashmap.
|
||||
m.shallow_size_of(ops) + (**m).shallow_size_of(ops)
|
||||
},
|
||||
MapInner::One(_) | MapInner::Empty => 0,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
//! The rule tree.
|
||||
|
||||
use crate::applicable_declarations::ApplicableDeclarationList;
|
||||
use crate::hash::{self, FxHashMap};
|
||||
use crate::hash::FxHashMap;
|
||||
use crate::properties::{Importance, LonghandIdSet, PropertyDeclarationBlock};
|
||||
use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
|
||||
use crate::stylesheets::{Origin, StyleRule};
|
||||
|
@ -21,6 +21,9 @@ use std::mem;
|
|||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
||||
|
||||
mod map;
|
||||
use self::map::Map;
|
||||
|
||||
/// The rule tree, the structure servo uses to preserve the results of selector
|
||||
/// matching.
|
||||
///
|
||||
|
@ -80,7 +83,9 @@ impl MallocSizeOf for RuleTree {
|
|||
n += unsafe { ops.malloc_size_of(node.ptr()) };
|
||||
let children = unsafe { (*node.ptr()).children.read() };
|
||||
children.shallow_size_of(ops);
|
||||
children.each(|c| stack.push(c.clone()));
|
||||
for c in &*children {
|
||||
stack.push(c.clone());
|
||||
}
|
||||
}
|
||||
|
||||
n
|
||||
|
@ -450,7 +455,9 @@ impl RuleTree {
|
|||
while let Some(node) = stack.pop() {
|
||||
let children = node.get().children.read();
|
||||
*children_count.entry(children.len()).or_insert(0) += 1;
|
||||
children.each(|c| stack.push(c.upgrade()));
|
||||
for c in &*children {
|
||||
stack.push(c.upgrade());
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Rule tree stats:");
|
||||
|
@ -841,161 +848,6 @@ impl CascadeLevel {
|
|||
}
|
||||
}
|
||||
|
||||
/// The children of a single rule node.
|
||||
///
|
||||
/// We optimize the case of no kids and a single child, since they're by far the
|
||||
/// most common case and it'd cause a bunch of bloat for no reason.
|
||||
///
|
||||
/// The children remove themselves when they go away, which means that it's ok
|
||||
/// for us to store weak pointers to them.
|
||||
enum RuleNodeChildren {
|
||||
/// There are no kids.
|
||||
Empty,
|
||||
/// There's just one kid. This is an extremely common case, so we don't
|
||||
/// bother allocating a map for it.
|
||||
One(WeakRuleNode),
|
||||
/// At least at one point in time there was more than one kid (that is to
|
||||
/// say, we don't bother re-allocating if children are removed dynamically).
|
||||
Map(Box<FxHashMap<ChildKey, WeakRuleNode>>),
|
||||
}
|
||||
|
||||
impl MallocShallowSizeOf for RuleNodeChildren {
|
||||
fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
|
||||
match *self {
|
||||
RuleNodeChildren::One(..) | RuleNodeChildren::Empty => 0,
|
||||
RuleNodeChildren::Map(ref m) => {
|
||||
// Want to account for both the box and the hashmap.
|
||||
m.shallow_size_of(ops) + (**m).shallow_size_of(ops)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RuleNodeChildren {
|
||||
fn default() -> Self {
|
||||
RuleNodeChildren::Empty
|
||||
}
|
||||
}
|
||||
|
||||
impl RuleNodeChildren {
|
||||
/// Executes a given function for each of the children.
|
||||
fn each(&self, mut f: impl FnMut(&WeakRuleNode)) {
|
||||
match *self {
|
||||
RuleNodeChildren::Empty => {},
|
||||
RuleNodeChildren::One(ref child) => f(child),
|
||||
RuleNodeChildren::Map(ref map) => {
|
||||
for (_key, kid) in map.iter() {
|
||||
f(kid)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
match *self {
|
||||
RuleNodeChildren::Empty => 0,
|
||||
RuleNodeChildren::One(..) => 1,
|
||||
RuleNodeChildren::Map(ref map) => map.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
fn get(&self, key: &ChildKey) -> Option<&WeakRuleNode> {
|
||||
match *self {
|
||||
RuleNodeChildren::Empty => return None,
|
||||
RuleNodeChildren::One(ref kid) => {
|
||||
// We're read-locked, so no need to do refcount stuff, since the
|
||||
// child is only removed from the main thread, _and_ it'd need
|
||||
// to write-lock us anyway.
|
||||
if unsafe { (*kid.ptr()).key() } == *key {
|
||||
Some(kid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
RuleNodeChildren::Map(ref map) => map.get(&key),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_insert_with(
|
||||
&mut self,
|
||||
key: ChildKey,
|
||||
get_new_child: impl FnOnce() -> StrongRuleNode,
|
||||
) -> StrongRuleNode {
|
||||
let existing_child_key = match *self {
|
||||
RuleNodeChildren::Empty => {
|
||||
let new = get_new_child();
|
||||
debug_assert_eq!(new.get().key(), key);
|
||||
*self = RuleNodeChildren::One(new.downgrade());
|
||||
return new;
|
||||
},
|
||||
RuleNodeChildren::One(ref weak) => unsafe {
|
||||
// We're locked necessarily, so it's fine to look at our
|
||||
// weak-child without refcount-traffic.
|
||||
let existing_child_key = (*weak.ptr()).key();
|
||||
if existing_child_key == key {
|
||||
return weak.upgrade();
|
||||
}
|
||||
existing_child_key
|
||||
},
|
||||
RuleNodeChildren::Map(ref mut map) => {
|
||||
return match map.entry(key) {
|
||||
hash::map::Entry::Occupied(ref occupied) => occupied.get().upgrade(),
|
||||
hash::map::Entry::Vacant(vacant) => {
|
||||
let new = get_new_child();
|
||||
|
||||
debug_assert_eq!(new.get().key(), key);
|
||||
vacant.insert(new.downgrade());
|
||||
|
||||
new
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
let existing_child = match mem::replace(self, RuleNodeChildren::Empty) {
|
||||
RuleNodeChildren::One(o) => o,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Two rule-nodes are still a not-totally-uncommon thing, so
|
||||
// avoid over-allocating entries.
|
||||
//
|
||||
// TODO(emilio): Maybe just inline two kids too?
|
||||
let mut children = Box::new(FxHashMap::with_capacity_and_hasher(2, Default::default()));
|
||||
children.insert(existing_child_key, existing_child);
|
||||
|
||||
let new = get_new_child();
|
||||
debug_assert_eq!(new.get().key(), key);
|
||||
children.insert(key, new.downgrade());
|
||||
|
||||
*self = RuleNodeChildren::Map(children);
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
fn remove(&mut self, key: &ChildKey) -> Option<WeakRuleNode> {
|
||||
match *self {
|
||||
RuleNodeChildren::Empty => return None,
|
||||
RuleNodeChildren::One(ref one) => {
|
||||
if unsafe { (*one.ptr()).key() } != *key {
|
||||
return None;
|
||||
}
|
||||
},
|
||||
RuleNodeChildren::Map(ref mut multiple) => {
|
||||
return multiple.remove(key);
|
||||
},
|
||||
}
|
||||
|
||||
match mem::replace(self, RuleNodeChildren::Empty) {
|
||||
RuleNodeChildren::One(o) => Some(o),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the rule tree.
|
||||
pub struct RuleNode {
|
||||
/// The root node. Only the root has no root pointer, for obvious reasons.
|
||||
|
@ -1021,7 +873,7 @@ pub struct RuleNode {
|
|||
|
||||
/// The children of a given rule node. Children remove themselves from here
|
||||
/// when they go away.
|
||||
children: RwLock<RuleNodeChildren>,
|
||||
children: RwLock<Map<ChildKey, WeakRuleNode>>,
|
||||
|
||||
/// The next item in the rule tree free list, that starts on the root node.
|
||||
///
|
||||
|
@ -1142,7 +994,11 @@ impl RuleNode {
|
|||
);
|
||||
|
||||
if let Some(parent) = self.parent.as_ref() {
|
||||
let weak = parent.get().children.write().remove(&self.key());
|
||||
let weak = parent
|
||||
.get()
|
||||
.children
|
||||
.write()
|
||||
.remove(&self.key(), |node| (*node.ptr()).key());
|
||||
assert_eq!(weak.unwrap().ptr() as *const _, self as *const _);
|
||||
}
|
||||
}
|
||||
|
@ -1179,12 +1035,12 @@ impl RuleNode {
|
|||
}
|
||||
|
||||
let _ = write!(writer, "\n");
|
||||
self.children.read().each(|child| {
|
||||
for child in &*self.children.read() {
|
||||
child
|
||||
.upgrade()
|
||||
.get()
|
||||
.dump(guards, writer, indent + INDENT_INCREMENT);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1250,19 +1106,24 @@ impl StrongRuleNode {
|
|||
|
||||
let key = ChildKey(level, source.key());
|
||||
|
||||
let read_guard = self.get().children.upgradable_read();
|
||||
if let Some(child) = read_guard.get(&key) {
|
||||
let children = self.get().children.upgradable_read();
|
||||
if let Some(child) = children.get(&key, |node| unsafe { (*node.ptr()).key() }) {
|
||||
return child.upgrade();
|
||||
}
|
||||
let mut children = RwLockUpgradableReadGuard::upgrade(children);
|
||||
let weak = children.get_or_insert_with(
|
||||
key,
|
||||
|node| unsafe { (*node.ptr()).key() },
|
||||
move || {
|
||||
let strong =
|
||||
StrongRuleNode::new(Box::new(RuleNode::new(root, self.clone(), source, level)));
|
||||
let weak = strong.downgrade();
|
||||
mem::forget(strong);
|
||||
weak
|
||||
},
|
||||
);
|
||||
|
||||
RwLockUpgradableReadGuard::upgrade(read_guard).get_or_insert_with(key, move || {
|
||||
StrongRuleNode::new(Box::new(RuleNode::new(
|
||||
root,
|
||||
self.clone(),
|
||||
source.clone(),
|
||||
level,
|
||||
)))
|
||||
})
|
||||
StrongRuleNode::from_ptr(weak.p)
|
||||
}
|
||||
|
||||
/// Raw pointer to the RuleNode
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue