driver.rs (6516B)
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 https://mozilla.org/MPL/2.0/. */ 4 5 //! Implements traversal over the DOM tree. The traversal starts in sequential 6 //! mode, and optionally parallelizes as it discovers work. 7 8 #![deny(missing_docs)] 9 10 use crate::context::{PerThreadTraversalStatistics, StyleContext}; 11 use crate::context::{ThreadLocalStyleContext, TraversalStatistics}; 12 use crate::dom::{SendNode, TElement, TNode}; 13 use crate::parallel; 14 use crate::scoped_tls::ScopedTLS; 15 use crate::traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken}; 16 use std::collections::VecDeque; 17 use std::time::Instant; 18 19 #[cfg(feature = "servo")] 20 fn should_report_statistics() -> bool { 21 false 22 } 23 24 #[cfg(feature = "gecko")] 25 fn should_report_statistics() -> bool { 26 unsafe { crate::gecko_bindings::structs::ServoTraversalStatistics_sActive } 27 } 28 29 #[cfg(feature = "servo")] 30 fn report_statistics(_stats: &PerThreadTraversalStatistics) { 31 unreachable!("Servo never report stats"); 32 } 33 34 #[cfg(feature = "gecko")] 35 fn report_statistics(stats: &PerThreadTraversalStatistics) { 36 // This should only be called in the main thread, or it may be racy 37 // to update the statistics in a global variable. 38 unsafe { 39 debug_assert!(crate::gecko_bindings::bindings::Gecko_IsMainThread()); 40 let gecko_stats = std::ptr::addr_of_mut!( 41 crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton 42 ); 43 (*gecko_stats).mElementsTraversed += stats.elements_traversed; 44 (*gecko_stats).mElementsStyled += stats.elements_styled; 45 (*gecko_stats).mElementsMatched += stats.elements_matched; 46 (*gecko_stats).mStylesShared += stats.styles_shared; 47 (*gecko_stats).mStylesReused += stats.styles_reused; 48 } 49 } 50 51 fn with_pool_in_place_scope<'scope>( 52 work_unit_max: usize, 53 pool: Option<&rayon::ThreadPool>, 54 closure: impl FnOnce(Option<&rayon::ScopeFifo<'scope>>) + Send + 'scope, 55 ) { 56 if work_unit_max == 0 || pool.is_none() { 57 closure(None); 58 } else { 59 let pool = pool.unwrap(); 60 pool.in_place_scope_fifo(|scope| { 61 #[cfg(feature = "gecko")] 62 debug_assert_eq!( 63 pool.current_thread_index(), 64 Some(0), 65 "Main thread should be the first thread" 66 ); 67 if cfg!(feature = "gecko") || pool.current_thread_index().is_some() { 68 closure(Some(scope)); 69 } else { 70 scope.spawn_fifo(|scope| closure(Some(scope))); 71 } 72 }); 73 } 74 } 75 76 /// See documentation of the pref for performance characteristics. 77 fn work_unit_max() -> usize { 78 static_prefs::pref!("layout.css.stylo-work-unit-size") as usize 79 } 80 81 /// Do a DOM traversal for top-down and (optionally) bottom-up processing, generic over `D`. 82 /// 83 /// We use an adaptive traversal strategy. We start out with simple sequential processing, until we 84 /// arrive at a wide enough level in the DOM that the parallel traversal would parallelize it. 85 /// If a thread pool is provided, we then transfer control over to the parallel traversal. 86 /// 87 /// Returns true if the traversal was parallel, and also returns the statistics object containing 88 /// information on nodes traversed (on nightly only). Not all of its fields will be initialized 89 /// since we don't call finish(). 90 pub fn traverse_dom<E, D>( 91 traversal: &D, 92 token: PreTraverseToken<E>, 93 pool: Option<&rayon::ThreadPool>, 94 ) -> E 95 where 96 E: TElement, 97 D: DomTraversal<E>, 98 { 99 let root = token 100 .traversal_root() 101 .expect("Should've ensured we needed to traverse"); 102 103 let report_stats = should_report_statistics(); 104 let dump_stats = traversal.shared_context().options.dump_style_statistics; 105 let start_time = if dump_stats { 106 Some(Instant::now()) 107 } else { 108 None 109 }; 110 111 // Declare the main-thread context, as well as the worker-thread contexts, 112 // which we may or may not instantiate. It's important to declare the worker- 113 // thread contexts first, so that they get dropped second. This matters because: 114 // * ThreadLocalContexts borrow AtomicRefCells in TLS. 115 // * Dropping a ThreadLocalContext can run SequentialTasks. 116 // * Sequential tasks may call into functions like 117 // Servo_StyleSet_GetBaseComputedValuesForElement, which instantiate a 118 // ThreadLocalStyleContext on the main thread. If the main thread 119 // ThreadLocalStyleContext has not released its TLS borrow by that point, 120 // we'll panic on double-borrow. 121 let mut scoped_tls = ScopedTLS::<ThreadLocalStyleContext<E>>::new(pool); 122 // Process the nodes breadth-first. This helps keep similar traversal characteristics for the 123 // style sharing cache. 124 let work_unit_max = work_unit_max(); 125 126 let send_root = unsafe { SendNode::new(root.as_node()) }; 127 with_pool_in_place_scope(work_unit_max, pool, |maybe_scope| { 128 let mut tlc = scoped_tls.ensure(parallel::create_thread_local_context); 129 let mut context = StyleContext { 130 shared: traversal.shared_context(), 131 thread_local: &mut tlc, 132 }; 133 134 let mut discovered = VecDeque::with_capacity(work_unit_max * 2); 135 let current_dom_depth = send_root.depth(); 136 let opaque_root = send_root.opaque(); 137 discovered.push_back(send_root); 138 parallel::style_trees( 139 &mut context, 140 discovered, 141 opaque_root, 142 work_unit_max, 143 PerLevelTraversalData { current_dom_depth }, 144 maybe_scope, 145 traversal, 146 &scoped_tls, 147 ); 148 }); 149 150 // Collect statistics from thread-locals if requested. 151 if dump_stats || report_stats { 152 let mut aggregate = PerThreadTraversalStatistics::default(); 153 for slot in scoped_tls.slots() { 154 if let Some(cx) = slot.get_mut() { 155 aggregate += cx.statistics.clone(); 156 } 157 } 158 159 if report_stats { 160 report_statistics(&aggregate); 161 } 162 // dump statistics to stdout if requested 163 if dump_stats { 164 let parallel = pool.is_some(); 165 let stats = 166 TraversalStatistics::new(aggregate, traversal, parallel, start_time.unwrap()); 167 if stats.is_large { 168 println!("{}", stats); 169 } 170 } 171 } 172 173 root 174 }