1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 //! The style bloom filter is used as an optimization when matching deep
6 //! descendant selectors.
7 
8 #![deny(missing_docs)]
9 
10 use atomic_refcell::{AtomicRefMut, AtomicRefCell};
11 use dom::{SendElement, TElement};
12 use owning_ref::OwningHandle;
13 use selectors::bloom::BloomFilter;
14 use servo_arc::Arc;
15 use smallvec::SmallVec;
16 
17 /// Bloom filters are large allocations, so we store them in thread-local storage
18 /// such that they can be reused across style traversals. StyleBloom is responsible
19 /// for ensuring that the bloom filter is zeroed when it is dropped.
20 thread_local!(static BLOOM_KEY: Arc<AtomicRefCell<BloomFilter>> =
21               Arc::new(AtomicRefCell::new(BloomFilter::new())));
22 
23 /// A struct that allows us to fast-reject deep descendant selectors avoiding
24 /// selector-matching.
25 ///
26 /// This is implemented using a counting bloom filter, and it's a standard
27 /// optimization. See Gecko's `AncestorFilter`, and Blink's and WebKit's
28 /// `SelectorFilter`.
29 ///
30 /// The constraints for Servo's style system are a bit different compared to
31 /// traditional style systems given Servo does a parallel breadth-first
32 /// traversal instead of a sequential depth-first traversal.
33 ///
34 /// This implies that we need to track a bit more state than other browsers to
35 /// ensure we're doing the correct thing during the traversal, and being able to
36 /// apply this optimization effectively.
37 ///
38 /// Concretely, we have a bloom filter instance per worker thread, and we track
39 /// the current DOM depth in order to find a common ancestor when it doesn't
40 /// match the previous element we've styled.
41 ///
42 /// This is usually a pretty fast operation (we use to be one level deeper than
43 /// the previous one), but in the case of work-stealing, we may needed to push
44 /// and pop multiple elements.
45 ///
46 /// See the `insert_parents_recovering`, where most of the magic happens.
47 ///
48 /// Regarding thread-safety, this struct is safe because:
49 ///
50 ///  * We clear this after a restyle.
51 ///  * The DOM shape and attributes (and every other thing we access here) are
52 ///    immutable during a restyle.
53 ///
54 pub struct StyleBloom<E: TElement> {
55     /// A handle to the bloom filter from the thread upon which this StyleBloom
56     /// was created. We use AtomicRefCell so that this is all |Send|, which allows
57     /// StyleBloom to live in ThreadLocalStyleContext, which is dropped from the
58     /// parent thread.
59     filter: OwningHandle<Arc<AtomicRefCell<BloomFilter>>, AtomicRefMut<'static, BloomFilter>>,
60 
61     /// The stack of elements that this bloom filter contains, along with the
62     /// number of hashes pushed for each element.
63     elements: SmallVec<[PushedElement<E>; 16]>,
64 
65     /// Stack of hashes that have been pushed onto this filter.
66     pushed_hashes: SmallVec<[u32; 64]>,
67 }
68 
69 /// The very rough benchmarks in the selectors crate show clear()
70 /// costing about 25 times more than remove_hash(). We use this to implement
71 /// clear() more efficiently when only a small number of hashes have been
72 /// pushed.
73 ///
74 /// One subtly to note is that remove_hash() will not touch the value
75 /// if the filter overflowed. However, overflow can only occur if we
76 /// get 255 collisions on the same hash value, and 25 < 255.
77 const MEMSET_CLEAR_THRESHOLD: usize = 25;
78 
79 struct PushedElement<E: TElement> {
80     /// The element that was pushed.
81     element: SendElement<E>,
82 
83     /// The number of hashes pushed for the element.
84     num_hashes: usize,
85 }
86 
87 impl<E: TElement> PushedElement<E> {
new(el: E, num_hashes: usize) -> Self88     fn new(el: E, num_hashes: usize) -> Self {
89         PushedElement {
90             element: unsafe { SendElement::new(el) },
91             num_hashes,
92         }
93     }
94 }
95 
each_relevant_element_hash<E, F>(element: E, mut f: F) where E: TElement, F: FnMut(u32),96 fn each_relevant_element_hash<E, F>(element: E, mut f: F)
97 where
98     E: TElement,
99     F: FnMut(u32),
100 {
101     f(element.local_name().get_hash());
102     f(element.namespace().get_hash());
103 
104     if let Some(id) = element.id() {
105         f(id.get_hash());
106     }
107 
108     element.each_class(|class| {
109         f(class.get_hash())
110     });
111 }
112 
113 impl<E: TElement> Drop for StyleBloom<E> {
drop(&mut self)114     fn drop(&mut self) {
115         // Leave the reusable bloom filter in a zeroed state.
116         self.clear();
117     }
118 }
119 
120 impl<E: TElement> StyleBloom<E> {
121     /// Create an empty `StyleBloom`. Because StyleBloom acquires the thread-
122     /// local filter buffer, creating multiple live StyleBloom instances at
123     /// the same time on the same thread will panic.
124 
125     // Forced out of line to limit stack frame sizes after extra inlining from
126     // https://github.com/rust-lang/rust/pull/43931
127     //
128     // See https://github.com/servo/servo/pull/18420#issuecomment-328769322
129     #[inline(never)]
new() -> Self130     pub fn new() -> Self {
131         let bloom_arc = BLOOM_KEY.with(|b| b.clone());
132         let filter = OwningHandle::new_with_fn(bloom_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut());
133         debug_assert!(filter.is_zeroed(), "Forgot to zero the bloom filter last time");
134         StyleBloom {
135             filter: filter,
136             elements: Default::default(),
137             pushed_hashes: Default::default(),
138         }
139     }
140 
141     /// Return the bloom filter used properly by the `selectors` crate.
filter(&self) -> &BloomFilter142     pub fn filter(&self) -> &BloomFilter {
143         &*self.filter
144     }
145 
146     /// Push an element to the bloom filter, knowing that it's a child of the
147     /// last element parent.
push(&mut self, element: E)148     pub fn push(&mut self, element: E) {
149         if cfg!(debug_assertions) {
150             if self.elements.is_empty() {
151                 assert!(element.traversal_parent().is_none());
152             }
153         }
154         self.push_internal(element);
155     }
156 
157     /// Same as `push`, but without asserting, in order to use it from
158     /// `rebuild`.
push_internal(&mut self, element: E)159     fn push_internal(&mut self, element: E) {
160         let mut count = 0;
161         each_relevant_element_hash(element, |hash| {
162             count += 1;
163             self.filter.insert_hash(hash);
164             self.pushed_hashes.push(hash);
165         });
166         self.elements.push(PushedElement::new(element, count));
167     }
168 
169     /// Pop the last element in the bloom filter and return it.
170     #[inline]
pop(&mut self) -> Option<E>171     fn pop(&mut self) -> Option<E> {
172         let PushedElement { element, num_hashes } = self.elements.pop()?;
173         let popped_element = *element;
174 
175         // Verify that the pushed hashes match the ones we'd get from the element.
176         let mut expected_hashes = vec![];
177         if cfg!(debug_assertions) {
178             each_relevant_element_hash(popped_element, |hash| expected_hashes.push(hash));
179         }
180 
181         for _ in 0..num_hashes {
182             let hash = self.pushed_hashes.pop().unwrap();
183             debug_assert_eq!(expected_hashes.pop().unwrap(), hash);
184             self.filter.remove_hash(hash);
185         }
186 
187         Some(popped_element)
188     }
189 
190     /// Returns true if the bloom filter is empty.
is_empty(&self) -> bool191     pub fn is_empty(&self) -> bool {
192         self.elements.is_empty()
193     }
194 
195     /// Returns the DOM depth of elements that can be correctly
196     /// matched against the bloom filter (that is, the number of
197     /// elements in our list).
matching_depth(&self) -> usize198     pub fn matching_depth(&self) -> usize {
199         self.elements.len()
200     }
201 
202     /// Clears the bloom filter.
clear(&mut self)203     pub fn clear(&mut self) {
204         self.elements.clear();
205 
206         if self.pushed_hashes.len() > MEMSET_CLEAR_THRESHOLD {
207             self.filter.clear();
208             self.pushed_hashes.clear();
209         } else {
210             for hash in self.pushed_hashes.drain() {
211                 self.filter.remove_hash(hash);
212             }
213             debug_assert!(self.filter.is_zeroed());
214         }
215     }
216 
217     /// Rebuilds the bloom filter up to the parent of the given element.
rebuild(&mut self, mut element: E)218     pub fn rebuild(&mut self, mut element: E) {
219         self.clear();
220 
221         let mut parents_to_insert = SmallVec::<[E; 16]>::new();
222         while let Some(parent) = element.traversal_parent() {
223             parents_to_insert.push(parent);
224             element = parent;
225         }
226 
227         for parent in parents_to_insert.drain().rev() {
228             self.push(parent);
229         }
230     }
231 
232     /// In debug builds, asserts that all the parents of `element` are in the
233     /// bloom filter.
234     ///
235     /// Goes away in release builds.
assert_complete(&self, mut element: E)236     pub fn assert_complete(&self, mut element: E) {
237         if cfg!(debug_assertions) {
238             let mut checked = 0;
239             while let Some(parent) = element.traversal_parent() {
240                 assert_eq!(parent, *(self.elements[self.elements.len() - 1 - checked].element));
241                 element = parent;
242                 checked += 1;
243             }
244             assert_eq!(checked, self.elements.len());
245         }
246     }
247 
248     /// Get the element that represents the chain of things inserted
249     /// into the filter right now.  That chain is the given element
250     /// (if any) and its ancestors.
251     #[inline]
current_parent(&self) -> Option<E>252     pub fn current_parent(&self) -> Option<E> {
253         self.elements.last().map(|ref el| *el.element)
254     }
255 
256     /// Insert the parents of an element in the bloom filter, trying to recover
257     /// the filter if the last element inserted doesn't match.
258     ///
259     /// Gets the element depth in the dom, to make it efficient, or if not
260     /// provided always rebuilds the filter from scratch.
261     ///
262     /// Returns the new bloom filter depth, that the traversal code is
263     /// responsible to keep around if it wants to get an effective filter.
insert_parents_recovering(&mut self, element: E, element_depth: usize)264     pub fn insert_parents_recovering(&mut self,
265                                      element: E,
266                                      element_depth: usize)
267     {
268         // Easy case, we're in a different restyle, or we're empty.
269         if self.elements.is_empty() {
270             self.rebuild(element);
271             return;
272         }
273 
274         let traversal_parent = match element.traversal_parent() {
275             Some(parent) => parent,
276             None => {
277                 // Yay, another easy case.
278                 self.clear();
279                 return;
280             }
281         };
282 
283         if self.current_parent() == Some(traversal_parent) {
284             // Ta da, cache hit, we're all done.
285             return;
286         }
287 
288         if element_depth == 0 {
289             self.clear();
290             return;
291         }
292 
293         // We should've early exited above.
294         debug_assert!(element_depth != 0,
295                       "We should have already cleared the bloom filter");
296         debug_assert!(!self.elements.is_empty(),
297                       "How! We should've just rebuilt!");
298 
299         // Now the fun begins: We have the depth of the dom and the depth of the
300         // last element inserted in the filter, let's try to find a common
301         // parent.
302         //
303         // The current depth, that is, the depth of the last element inserted in
304         // the bloom filter, is the number of elements _minus one_, that is: if
305         // there's one element, it must be the root -> depth zero.
306         let mut current_depth = self.elements.len() - 1;
307 
308         // If the filter represents an element too deep in the dom, we need to
309         // pop ancestors.
310         while current_depth > element_depth - 1 {
311             self.pop().expect("Emilio is bad at math");
312             current_depth -= 1;
313         }
314 
315         // Now let's try to find a common parent in the bloom filter chain,
316         // starting with traversal_parent.
317         let mut common_parent = traversal_parent;
318         let mut common_parent_depth = element_depth - 1;
319 
320         // Let's collect the parents we are going to need to insert once we've
321         // found the common one.
322         let mut parents_to_insert = SmallVec::<[E; 16]>::new();
323 
324         // If the bloom filter still doesn't have enough elements, the common
325         // parent is up in the dom.
326         while common_parent_depth > current_depth {
327             // TODO(emilio): Seems like we could insert parents here, then
328             // reverse the slice.
329             parents_to_insert.push(common_parent);
330             common_parent =
331                 common_parent.traversal_parent().expect("We were lied to");
332             common_parent_depth -= 1;
333         }
334 
335         // Now the two depths are the same.
336         debug_assert_eq!(common_parent_depth, current_depth);
337 
338         // Happy case: The parents match, we only need to push the ancestors
339         // we've collected and we'll never enter in this loop.
340         //
341         // Not-so-happy case: Parent's don't match, so we need to keep going up
342         // until we find a common ancestor.
343         //
344         // Gecko currently models native anonymous content that conceptually
345         // hangs off the document (such as scrollbars) as a separate subtree
346         // from the document root.
347         //
348         // Thus it's possible with Gecko that we do not find any common
349         // ancestor.
350         while *(self.elements.last().unwrap().element) != common_parent {
351             parents_to_insert.push(common_parent);
352             self.pop().unwrap();
353             common_parent = match common_parent.traversal_parent() {
354                 Some(parent) => parent,
355                 None => {
356                     debug_assert!(self.elements.is_empty());
357                     if cfg!(feature = "gecko") {
358                         break;
359                     } else {
360                         panic!("should have found a common ancestor");
361                     }
362                 }
363             }
364         }
365 
366         // Now the parents match, so insert the stack of elements we have been
367         // collecting so far.
368         for parent in parents_to_insert.drain().rev() {
369             self.push(parent);
370         }
371 
372         debug_assert_eq!(self.elements.len(), element_depth);
373 
374         // We're done! Easy.
375     }
376 }
377