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 }