From 526e9691f801b0819e6d86aed008dae74f5541e9 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 2 Aug 2017 16:42:09 +1000 Subject: [PATCH] stylo: Measure Elements and ComputedValues. The patch provides FFI access to Gecko's SeenPtrs type from Rust, in order to record what has already been measured when measuring Arcs. (The SeenPtrs must be initialized on the Gecko side because the same table is reused for measuring all Elements within a window, because Elements can share ComputedValues.) I have confirmed with DMD that this is working correctly. The patch also introduces MallocSizeOfRepeats, which is like MallocSizeOf but takes a SizeOfState, which holds a SeenPtrs table. --- components/servo_arc/lib.rs | 8 ++++ components/style/data.rs | 27 +++++++++++ components/style/gecko/generated/bindings.rs | 10 ++++ .../style/gecko/generated/structs_debug.rs | 15 ++++++ .../style/gecko/generated/structs_release.rs | 15 ++++++ components/style/properties/gecko.mako.rs | 18 +++++++ components/style/stylesheets/memory.rs | 47 +++++++++++++++++-- components/style/stylesheets/mod.rs | 2 + ports/geckolib/glue.rs | 24 +++++++++- 9 files changed, 161 insertions(+), 5 deletions(-) diff --git a/components/servo_arc/lib.rs b/components/servo_arc/lib.rs index d8a74758247..182acecf885 100644 --- a/components/servo_arc/lib.rs +++ b/components/servo_arc/lib.rs @@ -41,6 +41,7 @@ use std::hash::{Hash, Hasher}; use std::iter::{ExactSizeIterator, Iterator}; use std::mem; use std::ops::{Deref, DerefMut}; +use std::os::raw::c_void; use std::process; use std::ptr; use std::slice; @@ -200,6 +201,7 @@ impl Arc { pub fn borrow_arc<'a>(&'a self) -> ArcBorrow<'a, T> { ArcBorrow(&**self) } + /// Temporarily converts |self| into a bonafide RawOffsetArc and exposes it to the /// provided callback. The refcount is not modified. #[inline(always)] @@ -218,6 +220,12 @@ impl Arc { // Forward the result. result } + + /// Returns the address on the heap of the Arc itself -- not the T within it -- for memory + /// reporting. + pub fn heap_ptr(&self) -> *const c_void { + self.p.ptr() as *const ArcInner as *const c_void + } } impl Arc { diff --git a/components/style/data.rs b/components/style/data.rs index bce4d4502f5..8e63fb52501 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -15,6 +15,8 @@ use servo_arc::Arc; use shared_lock::StylesheetGuards; use std::fmt; use std::ops::{Deref, DerefMut}; +#[cfg(feature = "gecko")] +use stylesheets::{MallocSizeOfWithRepeats, SizeOfState}; bitflags! { flags RestyleFlags: u8 { @@ -242,6 +244,20 @@ impl fmt::Debug for ElementStyles { } } +#[cfg(feature = "gecko")] +impl MallocSizeOfWithRepeats for ElementStyles { + fn malloc_size_of_children(&self, state: &mut SizeOfState) -> usize { + let mut n = 0; + if let Some(ref primary) = self.primary { + n += primary.malloc_size_of_children(state) + }; + + // We may measure more fields in the future if DMD says it's worth it. + + n + } +} + /// Style system data associated with an Element. /// /// In Gecko, this hangs directly off the Element. Servo, this is embedded @@ -392,3 +408,14 @@ impl ElementData { self.restyle.clear_flags_and_damage(); } } + +#[cfg(feature = "gecko")] +impl MallocSizeOfWithRepeats for ElementData { + fn malloc_size_of_children(&self, state: &mut SizeOfState) -> usize { + let n = self.styles.malloc_size_of_children(state); + + // We may measure more fields in the future if DMD says it's worth it. + + n + } +} diff --git a/components/style/gecko/generated/bindings.rs b/components/style/gecko/generated/bindings.rs index 3cb2183f051..5a11e75582a 100644 --- a/components/style/gecko/generated/bindings.rs +++ b/components/style/gecko/generated/bindings.rs @@ -14,6 +14,7 @@ use gecko_bindings::structs::mozilla::css::ImageValue; use gecko_bindings::structs::mozilla::css::URLValue; use gecko_bindings::structs::mozilla::css::URLValueData; use gecko_bindings::structs::mozilla::MallocSizeOf; +use gecko_bindings::structs::mozilla::SeenPtrs; use gecko_bindings::structs::mozilla::Side; use gecko_bindings::structs::nsIContent; use gecko_bindings::structs::nsIDocument; @@ -1081,6 +1082,10 @@ extern "C" { extern "C" { pub fn Gecko_DropElementSnapshot(snapshot: ServoElementSnapshotOwned); } +extern "C" { + pub fn Gecko_HaveSeenPtr(table: *mut SeenPtrs, ptr: *const ::std::os::raw::c_void) + -> bool; +} extern "C" { pub fn Gecko_ResizeTArrayForStrings(array: *mut nsTArray, length: u32); @@ -1878,6 +1883,11 @@ extern "C" { extern "C" { pub fn Servo_Element_ClearData(node: RawGeckoElementBorrowed); } +extern "C" { + pub fn Servo_Element_SizeOfExcludingThis(malloc_size_of: MallocSizeOf, + seen_ptrs: *mut SeenPtrs, + node: RawGeckoElementBorrowed) -> usize; +} extern "C" { pub fn Servo_StyleSheet_FromUTF8Bytes(loader: *mut Loader, gecko_stylesheet: diff --git a/components/style/gecko/generated/structs_debug.rs b/components/style/gecko/generated/structs_debug.rs index ac9ad2686ce..7d6eed0e140 100644 --- a/components/style/gecko/generated/structs_debug.rs +++ b/components/style/gecko/generated/structs_debug.rs @@ -6024,6 +6024,21 @@ pub mod root { MAX = 29, } #[repr(C)] + #[derive(Debug, Copy)] + pub struct SeenPtrs { + pub _bindgen_opaque_blob: [u64; 6usize], + } + #[test] + fn bindgen_test_layout_SeenPtrs() { + assert_eq!(::std::mem::size_of::() , 48usize , concat ! + ( "Size of: " , stringify ! ( SeenPtrs ) )); + assert_eq! (::std::mem::align_of::() , 8usize , concat ! + ( "Alignment of " , stringify ! ( SeenPtrs ) )); + } + impl Clone for SeenPtrs { + fn clone(&self) -> Self { *self } + } + #[repr(C)] #[derive(Debug)] pub struct URLExtraData { pub mRefCnt: root::mozilla::ThreadSafeAutoRefCnt, diff --git a/components/style/gecko/generated/structs_release.rs b/components/style/gecko/generated/structs_release.rs index ad3b9843db6..7ab31bfc5d0 100644 --- a/components/style/gecko/generated/structs_release.rs +++ b/components/style/gecko/generated/structs_release.rs @@ -5906,6 +5906,21 @@ pub mod root { MAX = 29, } #[repr(C)] + #[derive(Debug, Copy)] + pub struct SeenPtrs { + pub _bindgen_opaque_blob: [u64; 5usize], + } + #[test] + fn bindgen_test_layout_SeenPtrs() { + assert_eq!(::std::mem::size_of::() , 40usize , concat ! + ( "Size of: " , stringify ! ( SeenPtrs ) )); + assert_eq! (::std::mem::align_of::() , 8usize , concat ! + ( "Alignment of " , stringify ! ( SeenPtrs ) )); + } + impl Clone for SeenPtrs { + fn clone(&self) -> Self { *self } + } + #[repr(C)] #[derive(Debug)] pub struct URLExtraData { pub mRefCnt: root::mozilla::ThreadSafeAutoRefCnt, diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index d35b6bf4b10..7db095ef64d 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -60,6 +60,7 @@ use selector_parser::PseudoElement; use servo_arc::{Arc, RawOffsetArc}; use std::mem::{forget, uninitialized, transmute, zeroed}; use std::{cmp, ops, ptr}; +use stylesheets::{MallocSizeOfWithRepeats, SizeOfState}; use values::{self, Auto, CustomIdent, Either, KeyframesName}; use values::computed::ToComputedValue; use values::computed::effects::{BoxShadow, Filter, SimpleShadow}; @@ -308,6 +309,11 @@ impl ComputedValuesInner { self.visited_style.as_ref().map(|x| &**x) } + /// Gets the raw visited style. Useful for memory reporting. + pub fn get_raw_visited_style(&self) -> &Option> { + &self.visited_style + } + /// Gets a reference to the visited style. Panic if no visited style exists. pub fn visited_style(&self) -> &ComputedValues { self.get_visited_style().unwrap() @@ -364,6 +370,18 @@ impl ComputedValuesInner { } } +impl MallocSizeOfWithRepeats for ComputedValues { + fn malloc_size_of_children(&self, state: &mut SizeOfState) -> usize { + let mut n = 0; + if let Some(ref raw_offset_arc) = *self.get_raw_visited_style() { + n += raw_offset_arc.with_arc(|a: &Arc| { + a.malloc_size_of_children(state) + }) + } + n + } +} + <%def name="declare_style_struct(style_struct)"> pub use ::gecko_bindings::structs::mozilla::Gecko${style_struct.gecko_name} as ${style_struct.gecko_struct_name}; impl ${style_struct.gecko_struct_name} { diff --git a/components/style/stylesheets/memory.rs b/components/style/stylesheets/memory.rs index 44a2e429d5b..39338502a25 100644 --- a/components/style/stylesheets/memory.rs +++ b/components/style/stylesheets/memory.rs @@ -4,6 +4,12 @@ //! Memory reporting for the style system when running inside of Gecko. +#[cfg(feature = "gecko")] +use gecko_bindings::bindings::Gecko_HaveSeenPtr; +#[cfg(feature = "gecko")] +use gecko_bindings::structs::SeenPtrs; +#[cfg(feature = "gecko")] +use servo_arc::Arc; use shared_lock::SharedRwLockReadGuard; use std::os::raw::c_void; @@ -14,11 +20,24 @@ use std::os::raw::c_void; /// do_malloc_size_of(), rather than directly. pub type MallocSizeOfFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; +/// Servo-side counterpart to mozilla::SizeOfState. The only difference is that +/// this struct doesn't contain the SeenPtrs table, just a pointer to it. +#[cfg(feature = "gecko")] +pub struct SizeOfState { + /// Function that measures the size of heap blocks. + pub malloc_size_of: MallocSizeOfFn, + /// Table recording heap blocks that have already been measured. + pub seen_ptrs: *mut SeenPtrs, +} + +/// Call malloc_size_of on ptr, first checking that the allocation isn't empty. +pub unsafe fn is_empty(ptr: *const T) -> bool { + return ptr as usize <= ::std::mem::align_of::(); +} + /// Call malloc_size_of on ptr, first checking that the allocation isn't empty. pub unsafe fn do_malloc_size_of(malloc_size_of: MallocSizeOfFn, ptr: *const T) -> usize { - use std::mem::align_of; - - if ptr as usize <= align_of::() { + if is_empty(ptr) { 0 } else { malloc_size_of(ptr as *const c_void) @@ -32,6 +51,15 @@ pub trait MallocSizeOf { fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize; } +/// Like MallocSizeOf, but takes a SizeOfState which allows it to measure +/// graph-like structures such as those containing Arcs. +#[cfg(feature = "gecko")] +pub trait MallocSizeOfWithRepeats { + /// Measure the size of any heap-allocated structures that hang off this + /// value, but not the space taken up by the value itself. + fn malloc_size_of_children(&self, state: &mut SizeOfState) -> usize; +} + /// Like MallocSizeOf, but operates with the global SharedRwLockReadGuard /// locked. pub trait MallocSizeOfWithGuard { @@ -58,6 +86,19 @@ impl MallocSizeOf for Vec { } } +#[cfg(feature = "gecko")] +impl MallocSizeOfWithRepeats for Arc { + fn malloc_size_of_children(&self, state: &mut SizeOfState) -> usize { + let mut n = 0; + let heap_ptr = self.heap_ptr(); + if unsafe { !is_empty(heap_ptr) && !Gecko_HaveSeenPtr(state.seen_ptrs, heap_ptr) } { + n += unsafe { (state.malloc_size_of)(heap_ptr) }; + n += (**self).malloc_size_of_children(state); + } + n + } +} + impl MallocSizeOfWithGuard for Vec { fn malloc_size_of_children( &self, diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index 480ccf65400..9a0271c2f54 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -40,6 +40,8 @@ pub use self::keyframes_rule::KeyframesRule; pub use self::loader::StylesheetLoader; pub use self::media_rule::MediaRule; pub use self::memory::{MallocSizeOf, MallocSizeOfFn, MallocSizeOfWithGuard}; +#[cfg(feature = "gecko")] +pub use self::memory::{MallocSizeOfWithRepeats, SizeOfState}; pub use self::namespace_rule::NamespaceRule; pub use self::page_rule::PageRule; pub use self::rule_parser::{State, TopLevelRuleParser}; diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 7c9eea0cd93..a63c3627f63 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -74,6 +74,7 @@ use style::gecko_bindings::structs::IterationCompositeOperation; use style::gecko_bindings::structs::MallocSizeOf; use style::gecko_bindings::structs::RawGeckoGfxMatrix4x4; use style::gecko_bindings::structs::RawGeckoPresContextOwned; +use style::gecko_bindings::structs::SeenPtrs; use style::gecko_bindings::structs::ServoElementSnapshotTable; use style::gecko_bindings::structs::ServoTraversalFlags; use style::gecko_bindings::structs::StyleRuleInclusion; @@ -107,8 +108,9 @@ use style::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard use style::string_cache::Atom; use style::style_adjuster::StyleAdjuster; use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers, DocumentRule}; -use style::stylesheets::{FontFeatureValuesRule, ImportRule, KeyframesRule, MallocSizeOfWithGuard, MediaRule}; -use style::stylesheets::{NamespaceRule, Origin, PageRule, StyleRule, SupportsRule}; +use style::stylesheets::{FontFeatureValuesRule, ImportRule, KeyframesRule, MallocSizeOfWithGuard}; +use style::stylesheets::{MallocSizeOfWithRepeats, MediaRule}; +use style::stylesheets::{NamespaceRule, Origin, PageRule, SizeOfState, StyleRule, SupportsRule}; use style::stylesheets::StylesheetContents; use style::stylesheets::StylesheetLoader as StyleStylesheetLoader; use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesStepValue}; @@ -744,6 +746,24 @@ pub extern "C" fn Servo_Element_ClearData(element: RawGeckoElementBorrowed) { unsafe { GeckoElement(element).clear_data() }; } +#[no_mangle] +pub extern "C" fn Servo_Element_SizeOfExcludingThis(malloc_size_of: MallocSizeOf, + seen_ptrs: *mut SeenPtrs, + element: RawGeckoElementBorrowed) -> usize { + let malloc_size_of = malloc_size_of.unwrap(); + let element = GeckoElement(element); + let borrow = element.borrow_data(); + if let Some(data) = borrow { + let mut state = SizeOfState { + malloc_size_of: malloc_size_of, + seen_ptrs: seen_ptrs, + }; + (*data).malloc_size_of_children(&mut state) + } else { + 0 + } +} + #[no_mangle] pub extern "C" fn Servo_StyleSheet_Empty(mode: SheetParsingMode) -> RawServoStyleSheetContentsStrong { let global_style_data = &*GLOBAL_STYLE_DATA;