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