/* * * Copyright (c) 2020-2022 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use std::{ mem::MaybeUninit, ops::{Deref, DerefMut}, sync::Mutex, }; // TODO: why is max bitmap size 64 a correct max size? Could we match // boxslabs instead or store used/not used inside the box slabs themselves? const MAX_BITMAP_SIZE: usize = 64; pub struct Bitmap { inner: bitmaps::Bitmap, max_size: usize, } impl Bitmap { pub fn new(max_size: usize) -> Self { assert!(max_size <= MAX_BITMAP_SIZE); Bitmap { inner: bitmaps::Bitmap::new(), max_size, } } pub fn set(&mut self, index: usize) -> bool { assert!(index < self.max_size); self.inner.set(index, true) } pub fn reset(&mut self, index: usize) -> bool { assert!(index < self.max_size); self.inner.set(index, false) } pub fn first_false_index(&self) -> Option { match self.inner.first_false_index() { Some(idx) if idx < self.max_size => Some(idx), _ => None, } } pub fn is_empty(&self) -> bool { self.inner.is_empty() } pub fn is_full(&self) -> bool { self.first_false_index().is_none() } } #[macro_export] macro_rules! box_slab { ($name:ident,$t:ty,$v:expr) => { use std::mem::MaybeUninit; use std::sync::Once; use $crate::{BoxSlab, Slab, SlabPool}; pub struct $name; impl SlabPool for $name { type SlabType = $t; fn get_slab() -> &'static Slab { const MAYBE_INIT: MaybeUninit<$t> = MaybeUninit::uninit(); static mut SLAB_POOL: [MaybeUninit<$t>; $v] = [MAYBE_INIT; $v]; static mut SLAB_SPACE: Option> = None; static mut INIT: Once = Once::new(); unsafe { INIT.call_once(|| { SLAB_SPACE = Some(Slab::<$name>::init(&mut SLAB_POOL, $v)); }); SLAB_SPACE.as_ref().unwrap() } } } }; } pub trait SlabPool { type SlabType: 'static; fn get_slab() -> &'static Slab where Self: Sized; } pub struct Inner { pool: &'static mut [MaybeUninit], map: Bitmap, } // TODO: Instead of a mutex, we should replace this with a CAS loop pub struct Slab(Mutex>); impl Slab { pub fn init(pool: &'static mut [MaybeUninit], size: usize) -> Self { Self(Mutex::new(Inner { pool, map: Bitmap::new(size), })) } pub fn new(new_object: T::SlabType) -> Option> { let slab = T::get_slab(); let mut inner = slab.0.lock().unwrap(); if let Some(index) = inner.map.first_false_index() { inner.map.set(index); inner.pool[index].write(new_object); let cell_ptr = unsafe { &mut *inner.pool[index].as_mut_ptr() }; Some(BoxSlab { data: cell_ptr, index, }) } else { None } } pub fn free(&self, index: usize) { let mut inner = self.0.lock().unwrap(); inner.map.reset(index); let old_value = std::mem::replace(&mut inner.pool[index], MaybeUninit::uninit()); let _old_value = unsafe { old_value.assume_init() }; // This will drop the old_value } } pub struct BoxSlab { // Because the data is a reference within the MaybeUninit, we don't have a mechanism // to go out to the MaybeUninit from this reference. Hence this index index: usize, // TODO: We should figure out a way to get rid of the index too data: &'static mut T::SlabType, } impl Drop for BoxSlab { fn drop(&mut self) { T::get_slab().free(self.index); } } impl Deref for BoxSlab { type Target = T::SlabType; fn deref(&self) -> &Self::Target { self.data } } impl DerefMut for BoxSlab { fn deref_mut(&mut self) -> &mut Self::Target { self.data } } #[cfg(test)] mod tests { use std::{ops::Deref, sync::Arc}; pub struct Test { val: Arc, } box_slab!(TestSlab, Test, 3); #[test] fn simple_alloc_free() { { let a = Slab::::new(Test { val: Arc::new(10) }).unwrap(); assert_eq!(*a.val.deref(), 10); let inner = TestSlab::get_slab().0.lock().unwrap(); assert_eq!(inner.map.is_empty(), false); } // Validates that the 'Drop' got executed let inner = TestSlab::get_slab().0.lock().unwrap(); assert_eq!(inner.map.is_empty(), true); println!("Box Size {}", std::mem::size_of::>()); println!("BoxSlab Size {}", std::mem::size_of::>()); } #[test] fn alloc_full_block() { { let a = Slab::::new(Test { val: Arc::new(10) }).unwrap(); let b = Slab::::new(Test { val: Arc::new(11) }).unwrap(); let c = Slab::::new(Test { val: Arc::new(12) }).unwrap(); // Test that at overflow, we return None assert_eq!( Slab::::new(Test { val: Arc::new(13) }).is_none(), true ); assert_eq!(*b.val.deref(), 11); { let inner = TestSlab::get_slab().0.lock().unwrap(); // Test that the bitmap is marked as full assert_eq!(inner.map.is_full(), true); } // Purposefully drop, to test that new allocation is possible std::mem::drop(b); let d = Slab::::new(Test { val: Arc::new(21) }).unwrap(); assert_eq!(*d.val.deref(), 21); // Ensure older allocations are still valid assert_eq!(*a.val.deref(), 10); assert_eq!(*c.val.deref(), 12); } // Validates that the 'Drop' got executed - test that the bitmap is empty let inner = TestSlab::get_slab().0.lock().unwrap(); assert_eq!(inner.map.is_empty(), true); } #[test] fn test_drop_logic() { let root = Arc::new(10); { let _a = Slab::::new(Test { val: root.clone() }).unwrap(); let _b = Slab::::new(Test { val: root.clone() }).unwrap(); let _c = Slab::::new(Test { val: root.clone() }).unwrap(); assert_eq!(Arc::strong_count(&root), 4); } // Test that Drop was correctly called on all the members of the pool assert_eq!(Arc::strong_count(&root), 1); } }