tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

intern.rs (15277B)


      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 interning module provides a generic data structure
      6 //! interning container. It is similar in concept to a
      7 //! traditional string interning container, but it is
      8 //! specialized to the WR thread model.
      9 //!
     10 //! There is an Interner structure, that lives in the
     11 //! scene builder thread, and a DataStore structure
     12 //! that lives in the frame builder thread.
     13 //!
     14 //! Hashing, interning and handle creation is done by
     15 //! the interner structure during scene building.
     16 //!
     17 //! Delta changes for the interner are pushed during
     18 //! a transaction to the frame builder. The frame builder
     19 //! is then able to access the content of the interned
     20 //! handles quickly, via array indexing.
     21 //!
     22 //! Epoch tracking ensures that the garbage collection
     23 //! step which the interner uses to remove items is
     24 //! only invoked on items that the frame builder thread
     25 //! is no longer referencing.
     26 //!
     27 //! Items in the data store are stored in a traditional
     28 //! free-list structure, for content access and memory
     29 //! usage efficiency.
     30 //!
     31 //! The epoch is incremented each time a scene is
     32 //! built. The most recently used scene epoch is
     33 //! stored inside each handle. This is then used for
     34 //! cache invalidation.
     35 
     36 use crate::internal_types::FastHashMap;
     37 use malloc_size_of::MallocSizeOf;
     38 use std::fmt::Debug;
     39 use std::hash::Hash;
     40 use std::marker::PhantomData;
     41 use std::{ops, u64};
     42 use crate::util::VecHelper;
     43 use crate::profiler::TransactionProfile;
     44 use peek_poke::PeekPoke;
     45 
     46 #[cfg_attr(feature = "capture", derive(Serialize))]
     47 #[cfg_attr(feature = "replay", derive(Deserialize))]
     48 #[derive(Debug, Copy, Clone, Hash, MallocSizeOf, PartialEq, Eq)]
     49 struct Epoch(u32);
     50 
     51 /// A list of updates to be applied to the data store,
     52 /// provided by the interning structure.
     53 #[cfg_attr(feature = "capture", derive(Serialize))]
     54 #[cfg_attr(feature = "replay", derive(Deserialize))]
     55 #[derive(MallocSizeOf)]
     56 pub struct UpdateList<S> {
     57    /// Items to insert.
     58    pub insertions: Vec<Insertion<S>>,
     59 
     60    /// Items to remove.
     61    pub removals: Vec<Removal>,
     62 }
     63 
     64 #[cfg_attr(feature = "capture", derive(Serialize))]
     65 #[cfg_attr(feature = "replay", derive(Deserialize))]
     66 #[derive(MallocSizeOf)]
     67 pub struct Insertion<S> {
     68    pub index: usize,
     69    pub uid: ItemUid,
     70    pub value: S,
     71 }
     72 
     73 #[cfg_attr(feature = "capture", derive(Serialize))]
     74 #[cfg_attr(feature = "replay", derive(Deserialize))]
     75 #[derive(MallocSizeOf)]
     76 pub struct Removal {
     77    pub index: usize,
     78    pub uid: ItemUid,
     79 }
     80 
     81 impl<S> UpdateList<S> {
     82    fn new() -> UpdateList<S> {
     83        UpdateList {
     84            insertions: Vec::new(),
     85            removals: Vec::new(),
     86        }
     87    }
     88 
     89    fn take_and_preallocate(&mut self) -> UpdateList<S> {
     90        UpdateList {
     91            insertions: self.insertions.take_and_preallocate(),
     92            removals: self.removals.take_and_preallocate(),
     93        }
     94    }
     95 }
     96 
     97 /// A globally, unique identifier
     98 #[cfg_attr(feature = "capture", derive(Serialize))]
     99 #[cfg_attr(feature = "replay", derive(Deserialize))]
    100 #[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PeekPoke, Default)]
    101 pub struct ItemUid {
    102    uid: u64,
    103 }
    104 
    105 impl ItemUid {
    106    // Intended for debug usage only
    107    pub fn get_uid(&self) -> u64 {
    108        self.uid
    109    }
    110 }
    111 
    112 #[cfg_attr(feature = "capture", derive(Serialize))]
    113 #[cfg_attr(feature = "replay", derive(Deserialize))]
    114 #[derive(Debug, Hash, MallocSizeOf, PartialEq, Eq)]
    115 pub struct Handle<I> {
    116    index: u32,
    117    epoch: Epoch,
    118    _marker: PhantomData<I>,
    119 }
    120 
    121 impl<I> Clone for Handle<I> {
    122    fn clone(&self) -> Self {
    123        Handle {
    124            index: self.index,
    125            epoch: self.epoch,
    126            _marker: self._marker,
    127        }
    128    }
    129 }
    130 
    131 impl<I> Copy for Handle<I> {}
    132 
    133 impl<I> Handle<I> {
    134    pub fn uid(&self) -> ItemUid {
    135        ItemUid {
    136            // The index in the freelist + the epoch it was interned generates a stable
    137            // unique id for an interned element.
    138            uid: ((self.index as u64) << 32) | self.epoch.0 as u64
    139        }
    140    }
    141 
    142    pub const INVALID: Self = Handle { index: !0, epoch: Epoch(!0), _marker: PhantomData };
    143 }
    144 
    145 pub trait InternDebug {
    146    fn on_interned(&self, _uid: ItemUid) {}
    147 }
    148 
    149 /// The data store lives in the frame builder thread. It
    150 /// contains a free-list of items for fast access.
    151 #[cfg_attr(feature = "capture", derive(Serialize))]
    152 #[cfg_attr(feature = "replay", derive(Deserialize))]
    153 #[derive(MallocSizeOf)]
    154 pub struct DataStore<I: Internable> {
    155    items: Vec<Option<I::StoreData>>,
    156 }
    157 
    158 impl<I: Internable> Default for DataStore<I> {
    159    fn default() -> Self {
    160        DataStore {
    161            items: Vec::new(),
    162        }
    163    }
    164 }
    165 
    166 impl<I: Internable> DataStore<I> {
    167    /// Apply any updates from the scene builder thread to
    168    /// this data store.
    169    pub fn apply_updates(
    170        &mut self,
    171        update_list: UpdateList<I::Key>,
    172        profile: &mut TransactionProfile,
    173    ) {
    174        for insertion in update_list.insertions {
    175            self.items
    176                .entry(insertion.index)
    177                .set(Some(insertion.value.into()));
    178        }
    179 
    180        for removal in update_list.removals {
    181            self.items[removal.index] = None;
    182        }
    183 
    184        profile.set(I::PROFILE_COUNTER, self.items.len());
    185    }
    186 }
    187 
    188 /// Retrieve an item from the store via handle
    189 impl<I: Internable> ops::Index<Handle<I>> for DataStore<I> {
    190    type Output = I::StoreData;
    191    fn index(&self, handle: Handle<I>) -> &I::StoreData {
    192        self.items[handle.index as usize].as_ref().expect("Bad datastore lookup")
    193    }
    194 }
    195 
    196 /// Retrieve a mutable item from the store via handle
    197 /// Retrieve an item from the store via handle
    198 impl<I: Internable> ops::IndexMut<Handle<I>> for DataStore<I> {
    199    fn index_mut(&mut self, handle: Handle<I>) -> &mut I::StoreData {
    200        self.items[handle.index as usize].as_mut().expect("Bad datastore lookup")
    201    }
    202 }
    203 
    204 #[cfg_attr(feature = "capture", derive(Serialize))]
    205 #[cfg_attr(feature = "replay", derive(Deserialize))]
    206 #[derive(MallocSizeOf)]
    207 struct ItemDetails<I> {
    208    /// Frame that this element was first interned
    209    interned_epoch: Epoch,
    210    /// Last frame this element was referenced (used to GC intern items)
    211    last_used_epoch: Epoch,
    212    /// Index into the freelist this item is located
    213    index: usize,
    214    /// Type marker for create_handle method
    215    _marker: PhantomData<I>,
    216 }
    217 
    218 impl<I> ItemDetails<I> {
    219    /// Construct a stable handle value from the item details
    220    fn create_handle(&self) -> Handle<I> {
    221        Handle {
    222            index: self.index as u32,
    223            epoch: self.interned_epoch,
    224            _marker: PhantomData,
    225        }
    226    }
    227 }
    228 
    229 /// The main interning data structure. This lives in the
    230 /// scene builder thread, and handles hashing and interning
    231 /// unique data structures. It also manages a free-list for
    232 /// the items in the data store, which is synchronized via
    233 /// an update list of additions / removals.
    234 #[cfg_attr(feature = "capture", derive(Serialize))]
    235 #[cfg_attr(feature = "replay", derive(Deserialize))]
    236 #[derive(MallocSizeOf)]
    237 pub struct Interner<I: Internable> {
    238    /// Uniquely map an interning key to a handle
    239    map: FastHashMap<I::Key, ItemDetails<I>>,
    240    /// List of free slots in the data store for re-use.
    241    free_list: Vec<usize>,
    242    /// Pending list of updates that need to be applied.
    243    update_list: UpdateList<I::Key>,
    244    /// The current epoch for the interner.
    245    current_epoch: Epoch,
    246    /// The information associated with each interned
    247    /// item that can be accessed by the interner.
    248    local_data: Vec<I::InternData>,
    249 }
    250 
    251 impl<I: Internable> Default for Interner<I> {
    252    fn default() -> Self {
    253        Interner {
    254            map: FastHashMap::default(),
    255            free_list: Vec::new(),
    256            update_list: UpdateList::new(),
    257            current_epoch: Epoch(1),
    258            local_data: Vec::new(),
    259        }
    260    }
    261 }
    262 
    263 impl<I: Internable> Interner<I> {
    264    /// Intern a data structure, and return a handle to
    265    /// that data. The handle can then be stored in the
    266    /// frame builder, and safely accessed via the data
    267    /// store that lives in the frame builder thread.
    268    /// The provided closure is invoked to build the
    269    /// local data about an interned structure if the
    270    /// key isn't already interned.
    271    pub fn intern<F>(
    272        &mut self,
    273        data: &I::Key,
    274        fun: F,
    275    ) -> Handle<I> where F: FnOnce() -> I::InternData {
    276        // Use get_mut rather than entry here to avoid
    277        // cloning the (sometimes large) key in the common
    278        // case, where the data already exists in the interner.
    279        if let Some(details) = self.map.get_mut(data) {
    280            // Update the last referenced frame for this element
    281            details.last_used_epoch = self.current_epoch;
    282            // Return a stable handle value for dependency checking
    283            return details.create_handle();
    284        }
    285 
    286        // We need to intern a new data item. First, find out
    287        // if there is a spare slot in the free-list that we
    288        // can use. Otherwise, append to the end of the list.
    289        let index = match self.free_list.pop() {
    290            Some(index) => index,
    291            None => self.local_data.len(),
    292        };
    293 
    294        // Generate a handle for access via the data store.
    295        let handle = Handle {
    296            index: index as u32,
    297            epoch: self.current_epoch,
    298            _marker: PhantomData,
    299        };
    300 
    301        let uid = handle.uid();
    302 
    303        // Add a pending update to insert the new data.
    304        self.update_list.insertions.push(Insertion {
    305            index,
    306            uid,
    307            value: data.clone(),
    308        });
    309 
    310        #[cfg(debug_assertions)]
    311        data.on_interned(uid);
    312 
    313        // Store this handle so the next time it is
    314        // interned, it gets re-used.
    315        self.map.insert(data.clone(), ItemDetails {
    316            interned_epoch: self.current_epoch,
    317            last_used_epoch: self.current_epoch,
    318            index,
    319            _marker: PhantomData,
    320        });
    321 
    322        // Create the local data for this item that is
    323        // being interned.
    324        self.local_data.entry(index).set(fun());
    325 
    326        handle
    327    }
    328 
    329    /// Retrieve the pending list of updates for an interner
    330    /// that need to be applied to the data store. Also run
    331    /// a GC step that removes old entries.
    332    pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList<I::Key> {
    333        let mut update_list = self.update_list.take_and_preallocate();
    334 
    335        let free_list = &mut self.free_list;
    336        let current_epoch = self.current_epoch.0;
    337 
    338        // First, run a GC step. Walk through the handles, and
    339        // if we find any that haven't been used for some time,
    340        // remove them. If this ever shows up in profiles, we
    341        // can make the GC step partial (scan only part of the
    342        // map each frame). It also might make sense in the
    343        // future to adjust how long items remain in the cache
    344        // based on the current size of the list.
    345        self.map.retain(|_, details| {
    346            if details.last_used_epoch.0 + 10 < current_epoch {
    347                // To expire an item:
    348                //  - Add index to the free-list for re-use.
    349                //  - Add an update to the data store to invalidate this slot.
    350                //  - Remove from the hash map.
    351                free_list.push(details.index);
    352                update_list.removals.push(Removal {
    353                    index: details.index,
    354                    uid: details.create_handle().uid(),
    355                });
    356                return false;
    357            }
    358 
    359            true
    360        });
    361 
    362        // Begin the next epoch
    363        self.current_epoch = Epoch(self.current_epoch.0 + 1);
    364 
    365        update_list
    366    }
    367 }
    368 
    369 /// Retrieve the local data for an item from the interner via handle
    370 impl<I: Internable> ops::Index<Handle<I>> for Interner<I> {
    371    type Output = I::InternData;
    372    fn index(&self, handle: Handle<I>) -> &I::InternData {
    373        &self.local_data[handle.index as usize]
    374    }
    375 }
    376 
    377 /// Meta-macro to enumerate the various interner identifiers and types.
    378 ///
    379 /// IMPORTANT: Keep this synchronized with the list in mozilla-central located at
    380 /// gfx/webrender_bindings/webrender_ffi.h
    381 ///
    382 /// Note that this could be a lot less verbose if concat_idents! were stable. :-(
    383 #[macro_export]
    384 macro_rules! enumerate_interners {
    385    ($macro_name: ident) => {
    386        $macro_name! {
    387            clip: ClipIntern,
    388            prim: PrimitiveKeyKind,
    389            normal_border: NormalBorderPrim,
    390            image_border: ImageBorder,
    391            image: Image,
    392            yuv_image: YuvImage,
    393            line_decoration: LineDecoration,
    394            linear_grad: LinearGradient,
    395            radial_grad: RadialGradient,
    396            conic_grad: ConicGradient,
    397            picture: Picture,
    398            text_run: TextRun,
    399            filter_data: FilterDataIntern,
    400            backdrop_capture: BackdropCapture,
    401            backdrop_render: BackdropRender,
    402            polygon: PolygonIntern,
    403            box_shadow: BoxShadow,
    404        }
    405    }
    406 }
    407 
    408 macro_rules! declare_interning_memory_report {
    409    ( $( $name:ident: $ty:ident, )+ ) => {
    410        ///
    411        #[repr(C)]
    412        #[derive(AddAssign, Clone, Debug, Default)]
    413        pub struct InternerSubReport {
    414            $(
    415                ///
    416                pub $name: usize,
    417            )+
    418        }
    419    }
    420 }
    421 
    422 enumerate_interners!(declare_interning_memory_report);
    423 
    424 /// Memory report for interning-related data structures.
    425 /// cbindgen:derive-eq=false
    426 /// cbindgen:derive-ostream=false
    427 #[repr(C)]
    428 #[derive(Clone, Debug, Default)]
    429 pub struct InterningMemoryReport {
    430    ///
    431    pub interners: InternerSubReport,
    432    ///
    433    pub data_stores: InternerSubReport,
    434 }
    435 
    436 impl ::std::ops::AddAssign for InterningMemoryReport {
    437    fn add_assign(&mut self, other: InterningMemoryReport) {
    438        self.interners += other.interners;
    439        self.data_stores += other.data_stores;
    440    }
    441 }
    442 
    443 // The trick to make trait bounds configurable by features.
    444 mod dummy {
    445    #[cfg(not(feature = "capture"))]
    446    pub trait Serialize {}
    447    #[cfg(not(feature = "capture"))]
    448    impl<T> Serialize for T {}
    449    #[cfg(not(feature = "replay"))]
    450    pub trait Deserialize<'a> {}
    451    #[cfg(not(feature = "replay"))]
    452    impl<'a, T> Deserialize<'a> for T {}
    453 }
    454 #[cfg(feature = "capture")]
    455 use serde::Serialize as InternSerialize;
    456 #[cfg(not(feature = "capture"))]
    457 use self::dummy::Serialize as InternSerialize;
    458 #[cfg(feature = "replay")]
    459 use serde::Deserialize as InternDeserialize;
    460 #[cfg(not(feature = "replay"))]
    461 use self::dummy::Deserialize as InternDeserialize;
    462 
    463 /// Implement `Internable` for a type that wants to participate in interning.
    464 pub trait Internable: MallocSizeOf {
    465    type Key: Eq + Hash + Clone + Debug + MallocSizeOf + InternDebug + InternSerialize + for<'a> InternDeserialize<'a>;
    466    type StoreData: From<Self::Key> + MallocSizeOf + InternSerialize + for<'a> InternDeserialize<'a>;
    467    type InternData: MallocSizeOf + InternSerialize + for<'a> InternDeserialize<'a>;
    468 
    469    // Profile counter indices, see the list in profiler.rs
    470    const PROFILE_COUNTER: usize;
    471 }