/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! A generic backing store for caches. //! //! `FreeList` is a simple vector-backed data structure where each entry in the //! vector contains an Option. It maintains an index-based (rather than //! pointer-based) free list to efficiently locate the next unused entry. If all //! entries are occupied, insertion appends a new element to the vector. //! //! It also supports both strong and weak handle semantics. There is exactly one //! (non-Clonable) strong handle per occupied entry, which must be passed by //! value into `free()` to release an entry. Strong handles can produce an //! unlimited number of (Clonable) weak handles, which are used to perform //! lookups which may fail of the entry has been freed. A per-entry epoch ensures //! that weak handle lookups properly fail even if the entry has been freed and //! reused. //! //! TODO(gw): Add an occupied list head, for fast iteration of the occupied list //! to implement retain() style functionality. use std::{fmt, u32}; use std::marker::PhantomData; #[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] struct Epoch(u32); impl Epoch { /// Mints a new epoch. /// /// We start at 1 so that 0 is always an invalid epoch. fn new() -> Self { Epoch(1) } /// Returns an always-invalid epoch. fn invalid() -> Self { Epoch(0) } } #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(MallocSizeOf)] pub struct FreeListHandle { index: u32, epoch: Epoch, _marker: PhantomData, } /// More-compact textual representation for debug logging. impl fmt::Debug for FreeListHandle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("StrongHandle") .field("index", &self.index) .field("epoch", &self.epoch.0) .finish() } } impl FreeListHandle { pub fn weak(&self) -> WeakFreeListHandle { WeakFreeListHandle { index: self.index, epoch: self.epoch, _marker: PhantomData, } } pub fn invalid() -> Self { Self { index: 0, epoch: Epoch::invalid(), _marker: PhantomData, } } /// Returns true if this handle and the supplied weak handle reference /// the same underlying location in the freelist. pub fn matches(&self, weak_handle: &WeakFreeListHandle) -> bool { self.index == weak_handle.index && self.epoch == weak_handle.epoch } } impl Clone for WeakFreeListHandle { fn clone(&self) -> Self { WeakFreeListHandle { index: self.index, epoch: self.epoch, _marker: PhantomData, } } } impl PartialEq for WeakFreeListHandle { fn eq(&self, other: &Self) -> bool { self.index == other.index && self.epoch == other.epoch } } #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(MallocSizeOf)] pub struct WeakFreeListHandle { index: u32, epoch: Epoch, _marker: PhantomData, } /// More-compact textual representation for debug logging. impl fmt::Debug for WeakFreeListHandle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("WeakHandle") .field("index", &self.index) .field("epoch", &self.epoch.0) .finish() } } impl WeakFreeListHandle { /// Returns an always-invalid handle. pub fn invalid() -> Self { Self { index: 0, epoch: Epoch::invalid(), _marker: PhantomData, } } } #[derive(Debug, MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] struct Slot { next: Option, epoch: Epoch, value: Option, } #[derive(Debug, MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct FreeList { slots: Vec>, free_list_head: Option, active_count: usize, _marker: PhantomData, } impl FreeList { /// Mints a new `FreeList` with no entries. /// /// Triggers a 1-entry allocation. pub fn new() -> Self { // We guarantee that we never have zero entries by starting with one // free entry. This allows WeakFreeListHandle::invalid() to work // without adding any additional branches. let first_slot = Slot { next: None, epoch: Epoch::new(), value: None, }; FreeList { slots: vec![first_slot], free_list_head: Some(0), active_count: 0, _marker: PhantomData, } } pub fn clear(&mut self) { self.slots.truncate(1); self.slots[0] = Slot { next: None, epoch: Epoch::new(), value: None, }; self.free_list_head = Some(0); self.active_count = 0; } #[allow(dead_code)] pub fn get(&self, id: &FreeListHandle) -> &T { self.slots[id.index as usize].value.as_ref().unwrap() } #[allow(dead_code)] pub fn get_mut(&mut self, id: &FreeListHandle) -> &mut T { self.slots[id.index as usize].value.as_mut().unwrap() } pub fn get_opt(&self, id: &WeakFreeListHandle) -> Option<&T> { let slot = &self.slots[id.index as usize]; if slot.epoch == id.epoch { slot.value.as_ref() } else { None } } pub fn get_opt_mut(&mut self, id: &WeakFreeListHandle) -> Option<&mut T> { let slot = &mut self.slots[id.index as usize]; if slot.epoch == id.epoch { slot.value.as_mut() } else { None } } pub fn insert(&mut self, item: T) -> FreeListHandle { self.active_count += 1; match self.free_list_head { Some(free_index) => { let slot = &mut self.slots[free_index as usize]; // Remove from free list. self.free_list_head = slot.next; slot.next = None; slot.value = Some(item); FreeListHandle { index: free_index, epoch: slot.epoch, _marker: PhantomData, } } None => { let index = self.slots.len() as u32; let epoch = Epoch::new(); self.slots.push(Slot { next: None, epoch, value: Some(item), }); FreeListHandle { index, epoch, _marker: PhantomData, } } } } pub fn free(&mut self, id: FreeListHandle) -> T { self.active_count -= 1; let slot = &mut self.slots[id.index as usize]; slot.next = self.free_list_head; slot.epoch = Epoch(slot.epoch.0 + 1); self.free_list_head = Some(id.index); slot.value.take().unwrap() } #[allow(dead_code)] pub fn len(&self) -> usize { self.active_count } }