summaryrefslogtreecommitdiffstats
path: root/servo/components/style/rule_tree/core.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/rule_tree/core.rs')
-rw-r--r--servo/components/style/rule_tree/core.rs772
1 files changed, 772 insertions, 0 deletions
diff --git a/servo/components/style/rule_tree/core.rs b/servo/components/style/rule_tree/core.rs
new file mode 100644
index 0000000000..85584a0e22
--- /dev/null
+++ b/servo/components/style/rule_tree/core.rs
@@ -0,0 +1,772 @@
+/* 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/. */
+
+#![allow(unsafe_code)]
+
+use crate::applicable_declarations::CascadePriority;
+use crate::shared_lock::StylesheetGuards;
+use crate::stylesheets::layer_rule::LayerOrder;
+use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
+use parking_lot::RwLock;
+use smallvec::SmallVec;
+use std::fmt;
+use std::hash;
+use std::io::Write;
+use std::mem;
+use std::ptr;
+use std::sync::atomic::{self, AtomicPtr, AtomicUsize, Ordering};
+
+use super::map::{Entry, Map};
+use super::unsafe_box::UnsafeBox;
+use super::{CascadeLevel, StyleSource};
+
+/// The rule tree, the structure servo uses to preserve the results of selector
+/// matching.
+///
+/// This is organized as a tree of rules. When a node matches a set of rules,
+/// they're inserted in order in the tree, starting with the less specific one.
+///
+/// When a rule is inserted in the tree, other elements may share the path up to
+/// a given rule. If that's the case, we don't duplicate child nodes, but share
+/// them.
+///
+/// When the rule node refcount drops to zero, it doesn't get freed. It gets
+/// instead put into a free list, and it is potentially GC'd after a while.
+///
+/// That way, a rule node that represents a likely-to-match-again rule (like a
+/// :hover rule) can be reused if we haven't GC'd it yet.
+#[derive(Debug)]
+pub struct RuleTree {
+ root: StrongRuleNode,
+}
+
+impl Drop for RuleTree {
+ fn drop(&mut self) {
+ unsafe { self.swap_free_list_and_gc(ptr::null_mut()) }
+ }
+}
+
+impl MallocSizeOf for RuleTree {
+ fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+ let mut n = 0;
+ let mut stack = SmallVec::<[_; 32]>::new();
+ stack.push(self.root.clone());
+
+ while let Some(node) = stack.pop() {
+ n += unsafe { ops.malloc_size_of(&*node.p) };
+ let children = node.p.children.read();
+ children.shallow_size_of(ops);
+ for c in &*children {
+ stack.push(unsafe { c.upgrade() });
+ }
+ }
+
+ n
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+struct ChildKey(CascadePriority, ptr::NonNull<()>);
+unsafe impl Send for ChildKey {}
+unsafe impl Sync for ChildKey {}
+
+impl RuleTree {
+ /// Construct a new rule tree.
+ pub fn new() -> Self {
+ RuleTree {
+ root: StrongRuleNode::new(Box::new(RuleNode::root())),
+ }
+ }
+
+ /// Get the root rule node.
+ pub fn root(&self) -> &StrongRuleNode {
+ &self.root
+ }
+
+ /// This can only be called when no other threads is accessing this tree.
+ pub fn gc(&self) {
+ unsafe { self.swap_free_list_and_gc(RuleNode::DANGLING_PTR) }
+ }
+
+ /// This can only be called when no other threads is accessing this tree.
+ pub fn maybe_gc(&self) {
+ #[cfg(debug_assertions)]
+ self.maybe_dump_stats();
+
+ if self.root.p.approximate_free_count.load(Ordering::Relaxed) > RULE_TREE_GC_INTERVAL {
+ self.gc();
+ }
+ }
+
+ #[cfg(debug_assertions)]
+ fn maybe_dump_stats(&self) {
+ use itertools::Itertools;
+ use std::cell::Cell;
+ use std::time::{Duration, Instant};
+
+ if !log_enabled!(log::Level::Trace) {
+ return;
+ }
+
+ const RULE_TREE_STATS_INTERVAL: Duration = Duration::from_secs(2);
+
+ thread_local! {
+ pub static LAST_STATS: Cell<Instant> = Cell::new(Instant::now());
+ };
+
+ let should_dump = LAST_STATS.with(|s| {
+ let now = Instant::now();
+ if now.duration_since(s.get()) < RULE_TREE_STATS_INTERVAL {
+ return false;
+ }
+ s.set(now);
+ true
+ });
+
+ if !should_dump {
+ return;
+ }
+
+ let mut children_count = fxhash::FxHashMap::default();
+
+ let mut stack = SmallVec::<[_; 32]>::new();
+ stack.push(self.root.clone());
+ while let Some(node) = stack.pop() {
+ let children = node.p.children.read();
+ *children_count.entry(children.len()).or_insert(0) += 1;
+ for c in &*children {
+ stack.push(unsafe { c.upgrade() });
+ }
+ }
+
+ trace!("Rule tree stats:");
+ let counts = children_count.keys().sorted();
+ for count in counts {
+ trace!(" {} - {}", count, children_count[count]);
+ }
+ }
+
+ /// Steals the free list and drops its contents.
+ unsafe fn swap_free_list_and_gc(&self, ptr: *mut RuleNode) {
+ let root = &self.root.p;
+
+ debug_assert!(!root.next_free.load(Ordering::Relaxed).is_null());
+
+ // Reset the approximate free count to zero, as we are going to steal
+ // the free list.
+ root.approximate_free_count.store(0, Ordering::Relaxed);
+
+ // Steal the free list head. Memory loads on nodes while iterating it
+ // must observe any prior changes that occured so this requires
+ // acquire ordering, but there are no writes that need to be kept
+ // before this swap so there is no need for release.
+ let mut head = root.next_free.swap(ptr, Ordering::Acquire);
+
+ while head != RuleNode::DANGLING_PTR {
+ debug_assert!(!head.is_null());
+
+ let mut node = UnsafeBox::from_raw(head);
+
+ // The root node cannot go on the free list.
+ debug_assert!(node.root.is_some());
+
+ // The refcount of nodes on the free list never goes below 1.
+ debug_assert!(node.refcount.load(Ordering::Relaxed) > 0);
+
+ // No one else is currently writing to that field. Get the address
+ // of the next node in the free list and replace it with null,
+ // other threads will now consider that this node is not on the
+ // free list.
+ head = node.next_free.swap(ptr::null_mut(), Ordering::Relaxed);
+
+ // This release write synchronises with the acquire fence in
+ // `WeakRuleNode::upgrade`, making sure that if `upgrade` observes
+ // decrements the refcount to 0, it will also observe the
+ // `node.next_free` swap to null above.
+ if node.refcount.fetch_sub(1, Ordering::Release) == 1 {
+ // And given it observed the null swap above, it will need
+ // `pretend_to_be_on_free_list` to finish its job, writing
+ // `RuleNode::DANGLING_PTR` in `node.next_free`.
+ RuleNode::pretend_to_be_on_free_list(&node);
+
+ // Drop this node now that we just observed its refcount going
+ // down to zero.
+ RuleNode::drop_without_free_list(&mut node);
+ }
+ }
+ }
+}
+
+/// The number of RuleNodes added to the free list before we will consider
+/// doing a GC when calling maybe_gc(). (The value is copied from Gecko,
+/// where it likely did not result from a rigorous performance analysis.)
+const RULE_TREE_GC_INTERVAL: usize = 300;
+
+/// A node in the rule tree.
+struct RuleNode {
+ /// The root node. Only the root has no root pointer, for obvious reasons.
+ root: Option<WeakRuleNode>,
+
+ /// The parent rule node. Only the root has no parent.
+ parent: Option<StrongRuleNode>,
+
+ /// The actual style source, either coming from a selector in a StyleRule,
+ /// or a raw property declaration block (like the style attribute).
+ ///
+ /// None for the root node.
+ source: Option<StyleSource>,
+
+ /// The cascade level + layer order this rule is positioned at.
+ cascade_priority: CascadePriority,
+
+ /// The refcount of this node.
+ ///
+ /// Starts at one. Incremented in `StrongRuleNode::clone` and
+ /// `WeakRuleNode::upgrade`. Decremented in `StrongRuleNode::drop`
+ /// and `RuleTree::swap_free_list_and_gc`.
+ ///
+ /// If a non-root node's refcount reaches zero, it is incremented back to at
+ /// least one in `RuleNode::pretend_to_be_on_free_list` until the caller who
+ /// observed it dropping to zero had a chance to try to remove it from its
+ /// parent's children list.
+ ///
+ /// The refcount should never be decremented to zero if the value in
+ /// `next_free` is not null.
+ refcount: AtomicUsize,
+
+ /// Only used for the root, stores the number of free rule nodes that are
+ /// around.
+ approximate_free_count: AtomicUsize,
+
+ /// The children of a given rule node. Children remove themselves from here
+ /// when they go away.
+ children: RwLock<Map<ChildKey, WeakRuleNode>>,
+
+ /// This field has two different meanings depending on whether this is the
+ /// root node or not.
+ ///
+ /// If it is the root, it represents the head of the free list. It may be
+ /// null, which means the free list is gone because the tree was dropped,
+ /// and it may be `RuleNode::DANGLING_PTR`, which means the free list is
+ /// empty.
+ ///
+ /// If it is not the root node, this field is either null if the node is
+ /// not on the free list, `RuleNode::DANGLING_PTR` if it is the last item
+ /// on the free list or the node is pretending to be on the free list, or
+ /// any valid non-null pointer representing the next item on the free list
+ /// after this one.
+ ///
+ /// See `RuleNode::push_on_free_list`, `swap_free_list_and_gc`, and
+ /// `WeakRuleNode::upgrade`.
+ ///
+ /// Two threads should never attempt to put the same node on the free list
+ /// both at the same time.
+ next_free: AtomicPtr<RuleNode>,
+}
+
+// On Gecko builds, hook into the leak checking machinery.
+#[cfg(feature = "gecko_refcount_logging")]
+mod gecko_leak_checking {
+ use super::RuleNode;
+ use std::mem::size_of;
+ use std::os::raw::{c_char, c_void};
+
+ extern "C" {
+ fn NS_LogCtor(aPtr: *mut c_void, aTypeName: *const c_char, aSize: u32);
+ fn NS_LogDtor(aPtr: *mut c_void, aTypeName: *const c_char, aSize: u32);
+ }
+ static NAME: &'static [u8] = b"RuleNode\0";
+
+ /// Logs the creation of a heap-allocated object to Gecko's leak-checking machinery.
+ pub(super) fn log_ctor(ptr: *const RuleNode) {
+ let s = NAME as *const [u8] as *const u8 as *const c_char;
+ unsafe {
+ NS_LogCtor(ptr as *mut c_void, s, size_of::<RuleNode>() as u32);
+ }
+ }
+
+ /// Logs the destruction of a heap-allocated object to Gecko's leak-checking machinery.
+ pub(super) fn log_dtor(ptr: *const RuleNode) {
+ let s = NAME as *const [u8] as *const u8 as *const c_char;
+ unsafe {
+ NS_LogDtor(ptr as *mut c_void, s, size_of::<RuleNode>() as u32);
+ }
+ }
+}
+
+#[inline(always)]
+fn log_new(_ptr: *const RuleNode) {
+ #[cfg(feature = "gecko_refcount_logging")]
+ gecko_leak_checking::log_ctor(_ptr);
+}
+
+#[inline(always)]
+fn log_drop(_ptr: *const RuleNode) {
+ #[cfg(feature = "gecko_refcount_logging")]
+ gecko_leak_checking::log_dtor(_ptr);
+}
+
+impl RuleNode {
+ const DANGLING_PTR: *mut Self = ptr::NonNull::dangling().as_ptr();
+
+ unsafe fn new(
+ root: WeakRuleNode,
+ parent: StrongRuleNode,
+ source: StyleSource,
+ cascade_priority: CascadePriority,
+ ) -> Self {
+ debug_assert!(root.p.parent.is_none());
+ RuleNode {
+ root: Some(root),
+ parent: Some(parent),
+ source: Some(source),
+ cascade_priority,
+ refcount: AtomicUsize::new(1),
+ children: Default::default(),
+ approximate_free_count: AtomicUsize::new(0),
+ next_free: AtomicPtr::new(ptr::null_mut()),
+ }
+ }
+
+ fn root() -> Self {
+ RuleNode {
+ root: None,
+ parent: None,
+ source: None,
+ cascade_priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()),
+ refcount: AtomicUsize::new(1),
+ approximate_free_count: AtomicUsize::new(0),
+ children: Default::default(),
+ next_free: AtomicPtr::new(RuleNode::DANGLING_PTR),
+ }
+ }
+
+ fn key(&self) -> ChildKey {
+ ChildKey(
+ self.cascade_priority,
+ self.source
+ .as_ref()
+ .expect("Called key() on the root node")
+ .key(),
+ )
+ }
+
+ /// Drops a node without ever putting it on the free list.
+ ///
+ /// Note that the node may not be dropped if we observe that its refcount
+ /// isn't zero anymore when we write-lock its parent's children map to
+ /// remove it.
+ ///
+ /// This loops over parents of dropped nodes if their own refcount reaches
+ /// zero to avoid recursion when dropping deep hierarchies of nodes.
+ ///
+ /// For non-root nodes, this should always be preceded by a call of
+ /// `RuleNode::pretend_to_be_on_free_list`.
+ unsafe fn drop_without_free_list(this: &mut UnsafeBox<Self>) {
+ // We clone the box and shadow the original one to be able to loop
+ // over its ancestors if they also need to be dropped.
+ let mut this = UnsafeBox::clone(this);
+ loop {
+ // If the node has a parent, we need to remove it from its parent's
+ // children list.
+ if let Some(parent) = this.parent.as_ref() {
+ debug_assert!(!this.next_free.load(Ordering::Relaxed).is_null());
+
+ // We lock the parent's children list, which means no other
+ // thread will have any more opportunity to resurrect the node
+ // anymore.
+ let mut children = parent.p.children.write();
+
+ this.next_free.store(ptr::null_mut(), Ordering::Relaxed);
+
+ // We decrement the counter to remove the "pretend to be
+ // on the free list" reference.
+ let old_refcount = this.refcount.fetch_sub(1, Ordering::Release);
+ debug_assert!(old_refcount != 0);
+ if old_refcount != 1 {
+ // Other threads resurrected this node and those references
+ // are still alive, we have nothing to do anymore.
+ return;
+ }
+
+ // We finally remove the node from its parent's children list,
+ // there are now no other references to it and it cannot
+ // be resurrected anymore even after we unlock the list.
+ debug!(
+ "Remove from child list: {:?}, parent: {:?}",
+ this.as_mut_ptr(),
+ this.parent.as_ref().map(|p| p.p.as_mut_ptr())
+ );
+ let weak = children.remove(&this.key(), |node| node.p.key()).unwrap();
+ assert_eq!(weak.p.as_mut_ptr(), this.as_mut_ptr());
+ } else {
+ debug_assert_eq!(this.next_free.load(Ordering::Relaxed), ptr::null_mut());
+ debug_assert_eq!(this.refcount.load(Ordering::Relaxed), 0);
+ }
+
+ // We are going to drop this node for good this time, as per the
+ // usual refcounting protocol we need an acquire fence here before
+ // we run the destructor.
+ //
+ // See https://github.com/rust-lang/rust/pull/41714#issuecomment-298996916
+ // for why it doesn't matter whether this is a load or a fence.
+ atomic::fence(Ordering::Acquire);
+
+ // Remove the parent reference from the child to avoid
+ // recursively dropping it and putting it on the free list.
+ let parent = UnsafeBox::deref_mut(&mut this).parent.take();
+
+ // We now drop the actual box and its contents, no one should
+ // access the current value in `this` anymore.
+ log_drop(&*this);
+ UnsafeBox::drop(&mut this);
+
+ if let Some(parent) = parent {
+ // We will attempt to drop the node's parent without the free
+ // list, so we clone the inner unsafe box and forget the
+ // original parent to avoid running its `StrongRuleNode`
+ // destructor which would attempt to use the free list if it
+ // still exists.
+ this = UnsafeBox::clone(&parent.p);
+ mem::forget(parent);
+ if this.refcount.fetch_sub(1, Ordering::Release) == 1 {
+ debug_assert_eq!(this.next_free.load(Ordering::Relaxed), ptr::null_mut());
+ if this.root.is_some() {
+ RuleNode::pretend_to_be_on_free_list(&this);
+ }
+ // Parent also reached refcount zero, we loop to drop it.
+ continue;
+ }
+ }
+
+ return;
+ }
+ }
+
+ /// Pushes this node on the tree's free list. Returns false if the free list
+ /// is gone. Should only be called after we decremented a node's refcount
+ /// to zero and pretended to be on the free list.
+ unsafe fn push_on_free_list(this: &UnsafeBox<Self>) -> bool {
+ let root = &this.root.as_ref().unwrap().p;
+
+ debug_assert!(this.refcount.load(Ordering::Relaxed) > 0);
+ debug_assert_eq!(this.next_free.load(Ordering::Relaxed), Self::DANGLING_PTR);
+
+ // Increment the approximate free count by one.
+ root.approximate_free_count.fetch_add(1, Ordering::Relaxed);
+
+ // If the compare-exchange operation fails in the loop, we will retry
+ // with the new head value, so this can be a relaxed load.
+ let mut head = root.next_free.load(Ordering::Relaxed);
+
+ while !head.is_null() {
+ // Two threads can never attempt to push the same node on the free
+ // list both at the same time, so whoever else pushed a node on the
+ // free list cannot have done so with this node.
+ debug_assert_ne!(head, this.as_mut_ptr());
+
+ // Store the current head of the free list in this node.
+ this.next_free.store(head, Ordering::Relaxed);
+
+ // Any thread acquiring the free list must observe the previous
+ // next_free changes that occured, hence the release ordering
+ // on success.
+ match root.next_free.compare_exchange_weak(
+ head,
+ this.as_mut_ptr(),
+ Ordering::Release,
+ Ordering::Relaxed,
+ ) {
+ Ok(_) => {
+ // This node is now on the free list, caller should not use
+ // the node anymore.
+ return true;
+ },
+ Err(new_head) => head = new_head,
+ }
+ }
+
+ // Tree was dropped and free list has been destroyed. We did not push
+ // this node on the free list but we still pretend to be on the free
+ // list to be ready to call `drop_without_free_list`.
+ false
+ }
+
+ /// Makes the node pretend to be on the free list. This will increment the
+ /// refcount by 1 and store `Self::DANGLING_PTR` in `next_free`. This
+ /// method should only be called after caller decremented the refcount to
+ /// zero, with the null pointer stored in `next_free`.
+ unsafe fn pretend_to_be_on_free_list(this: &UnsafeBox<Self>) {
+ debug_assert_eq!(this.next_free.load(Ordering::Relaxed), ptr::null_mut());
+ this.refcount.fetch_add(1, Ordering::Relaxed);
+ this.next_free.store(Self::DANGLING_PTR, Ordering::Release);
+ }
+
+ fn as_mut_ptr(&self) -> *mut RuleNode {
+ self as *const RuleNode as *mut RuleNode
+ }
+}
+
+pub(crate) struct WeakRuleNode {
+ p: UnsafeBox<RuleNode>,
+}
+
+/// A strong reference to a rule node.
+pub struct StrongRuleNode {
+ p: UnsafeBox<RuleNode>,
+}
+
+#[cfg(feature = "servo")]
+malloc_size_of_is_0!(StrongRuleNode);
+
+impl StrongRuleNode {
+ fn new(n: Box<RuleNode>) -> Self {
+ debug_assert_eq!(n.parent.is_none(), !n.source.is_some());
+
+ log_new(&*n);
+
+ debug!("Creating rule node: {:p}", &*n);
+
+ Self {
+ p: UnsafeBox::from_box(n),
+ }
+ }
+
+ unsafe fn from_unsafe_box(p: UnsafeBox<RuleNode>) -> Self {
+ Self { p }
+ }
+
+ unsafe fn downgrade(&self) -> WeakRuleNode {
+ WeakRuleNode {
+ p: UnsafeBox::clone(&self.p),
+ }
+ }
+
+ /// Get the parent rule node of this rule node.
+ pub fn parent(&self) -> Option<&StrongRuleNode> {
+ self.p.parent.as_ref()
+ }
+
+ pub(super) fn ensure_child(
+ &self,
+ root: &StrongRuleNode,
+ source: StyleSource,
+ cascade_priority: CascadePriority,
+ ) -> StrongRuleNode {
+ use parking_lot::RwLockUpgradableReadGuard;
+
+ debug_assert!(
+ self.p.cascade_priority <= cascade_priority,
+ "Should be ordered (instead {:?} > {:?}), from {:?} and {:?}",
+ self.p.cascade_priority,
+ cascade_priority,
+ self.p.source,
+ source,
+ );
+
+ let key = ChildKey(cascade_priority, source.key());
+ let children = self.p.children.upgradable_read();
+ if let Some(child) = children.get(&key, |node| node.p.key()) {
+ // Sound to call because we read-locked the parent's children.
+ return unsafe { child.upgrade() };
+ }
+ let mut children = RwLockUpgradableReadGuard::upgrade(children);
+ match children.entry(key, |node| node.p.key()) {
+ Entry::Occupied(child) => {
+ // Sound to call because we write-locked the parent's children.
+ unsafe { child.upgrade() }
+ },
+ Entry::Vacant(entry) => unsafe {
+ let node = StrongRuleNode::new(Box::new(RuleNode::new(
+ root.downgrade(),
+ self.clone(),
+ source,
+ cascade_priority,
+ )));
+ // Sound to call because we still own a strong reference to
+ // this node, through the `node` variable itself that we are
+ // going to return to the caller.
+ entry.insert(node.downgrade());
+ node
+ },
+ }
+ }
+
+ /// Get the style source corresponding to this rule node. May return `None`
+ /// if it's the root node, which means that the node hasn't matched any
+ /// rules.
+ pub fn style_source(&self) -> Option<&StyleSource> {
+ self.p.source.as_ref()
+ }
+
+ /// The cascade priority.
+ #[inline]
+ pub fn cascade_priority(&self) -> CascadePriority {
+ self.p.cascade_priority
+ }
+
+ /// The cascade level.
+ #[inline]
+ pub fn cascade_level(&self) -> CascadeLevel {
+ self.cascade_priority().cascade_level()
+ }
+
+ /// The importance.
+ #[inline]
+ pub fn importance(&self) -> crate::properties::Importance {
+ self.cascade_level().importance()
+ }
+
+ /// Returns whether this node has any child, only intended for testing
+ /// purposes.
+ pub unsafe fn has_children_for_testing(&self) -> bool {
+ !self.p.children.read().is_empty()
+ }
+
+ pub(super) fn dump<W: Write>(&self, guards: &StylesheetGuards, writer: &mut W, indent: usize) {
+ const INDENT_INCREMENT: usize = 4;
+
+ for _ in 0..indent {
+ let _ = write!(writer, " ");
+ }
+
+ let _ = writeln!(
+ writer,
+ " - {:p} (ref: {:?}, parent: {:?})",
+ &*self.p,
+ self.p.refcount.load(Ordering::Relaxed),
+ self.parent().map(|p| &*p.p as *const RuleNode)
+ );
+
+ for _ in 0..indent {
+ let _ = write!(writer, " ");
+ }
+
+ if let Some(source) = self.style_source() {
+ source.dump(self.cascade_level().guard(guards), writer);
+ } else {
+ if indent != 0 {
+ warn!("How has this happened?");
+ }
+ let _ = write!(writer, "(root)");
+ }
+
+ let _ = write!(writer, "\n");
+ for child in &*self.p.children.read() {
+ unsafe {
+ child
+ .upgrade()
+ .dump(guards, writer, indent + INDENT_INCREMENT);
+ }
+ }
+ }
+}
+
+impl Clone for StrongRuleNode {
+ fn clone(&self) -> Self {
+ debug!(
+ "{:p}: {:?}+",
+ &*self.p,
+ self.p.refcount.load(Ordering::Relaxed)
+ );
+ debug_assert!(self.p.refcount.load(Ordering::Relaxed) > 0);
+ self.p.refcount.fetch_add(1, Ordering::Relaxed);
+ unsafe { StrongRuleNode::from_unsafe_box(UnsafeBox::clone(&self.p)) }
+ }
+}
+
+impl Drop for StrongRuleNode {
+ #[cfg_attr(feature = "servo", allow(unused_mut))]
+ fn drop(&mut self) {
+ let node = &*self.p;
+ debug!("{:p}: {:?}-", node, node.refcount.load(Ordering::Relaxed));
+ debug!(
+ "Dropping node: {:p}, root: {:?}, parent: {:?}",
+ node,
+ node.root.as_ref().map(|r| &*r.p as *const RuleNode),
+ node.parent.as_ref().map(|p| &*p.p as *const RuleNode)
+ );
+
+ let should_drop = {
+ debug_assert!(node.refcount.load(Ordering::Relaxed) > 0);
+ node.refcount.fetch_sub(1, Ordering::Release) == 1
+ };
+
+ if !should_drop {
+ // The refcount didn't even drop zero yet, there is nothing for us
+ // to do anymore.
+ return;
+ }
+
+ unsafe {
+ if node.root.is_some() {
+ // This is a non-root node and we just observed the refcount
+ // dropping to zero, we need to pretend to be on the free list
+ // to unstuck any thread who tried to resurrect this node first
+ // through `WeakRuleNode::upgrade`.
+ RuleNode::pretend_to_be_on_free_list(&self.p);
+
+ // Attempt to push the node on the free list. This may fail
+ // if the free list is gone.
+ if RuleNode::push_on_free_list(&self.p) {
+ return;
+ }
+ }
+
+ // Either this was the last reference of the root node, or the
+ // tree rule is gone and there is no free list anymore. Drop the
+ // node.
+ RuleNode::drop_without_free_list(&mut self.p);
+ }
+ }
+}
+
+impl WeakRuleNode {
+ /// Upgrades this weak node reference, returning a strong one.
+ ///
+ /// Must be called with items stored in a node's children list. The children
+ /// list must at least be read-locked when this is called.
+ unsafe fn upgrade(&self) -> StrongRuleNode {
+ debug!("Upgrading weak node: {:p}", &*self.p);
+
+ if self.p.refcount.fetch_add(1, Ordering::Relaxed) == 0 {
+ // We observed a refcount of 0, we need to wait for this node to
+ // be put on the free list. Resetting the `next_free` pointer to
+ // null is only done in `RuleNode::drop_without_free_list`, just
+ // before a release refcount decrement, so this acquire fence here
+ // makes sure that we observed the write to null before we loop
+ // until there is a non-null value.
+ atomic::fence(Ordering::Acquire);
+ while self.p.next_free.load(Ordering::Relaxed).is_null() {}
+ }
+ StrongRuleNode::from_unsafe_box(UnsafeBox::clone(&self.p))
+ }
+}
+
+impl fmt::Debug for StrongRuleNode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ (&*self.p as *const RuleNode).fmt(f)
+ }
+}
+
+impl Eq for StrongRuleNode {}
+impl PartialEq for StrongRuleNode {
+ fn eq(&self, other: &Self) -> bool {
+ &*self.p as *const RuleNode == &*other.p
+ }
+}
+
+impl hash::Hash for StrongRuleNode {
+ fn hash<H>(&self, state: &mut H)
+ where
+ H: hash::Hasher,
+ {
+ (&*self.p as *const RuleNode).hash(state)
+ }
+}
+
+// Large pages generate thousands of RuleNode objects.
+size_of_test!(RuleNode, 80);
+// StrongRuleNode should be pointer-sized even inside an option.
+size_of_test!(Option<StrongRuleNode>, 8);