Skip to main content

allocal/
lib.rs

1use std::ptr::NonNull;
2use std::any::TypeId;
3use std::marker::PhantomData;
4
5/// A structure that allows the allocation of multiple objects of different types
6/// in a contiguous region of the memory.
7pub struct Allocal {
8    /// A pointer to the allocated data.
9    data: NonNull<u8>,
10
11    /// The number of bytes that are currently allocated.
12    cap: usize,
13
14    /// A vector that keeps tarck of all the allocated objects.
15    /// This vector is sorted by offset.
16    locations: Vec<LocationInner>,
17}
18
19/// Locates an allocated object within a `Allocal`.
20#[derive(Clone)]
21pub struct LocationInner {
22    /// The offset of the object, in bytes, from the begining of the allocated data.
23    offset: usize,
24    /// The size, in bytes, of the object.
25    size: usize,
26    /// A function that causes the object to be dropped.
27    drop_fn: Option<fn(*mut u8)>,
28}
29
30pub struct Location<T> {
31    offset: usize,
32    _m: PhantomData<*const T>,
33}
34
35impl<T> Clone for Location<T> {
36    fn clone(&self) -> Self {
37        Self {
38            offset: self.offset,
39            _m: PhantomData,
40        }
41    }
42}
43
44impl<T> Copy for Location<T> {}
45
46impl<T: 'static, U: 'static> PartialEq<Location<U>> for Location<T> {
47    #[inline(always)]
48    fn eq(&self, other: &Location<U>) -> bool {
49        TypeId::of::<T>() == TypeId::of::<U>() && self.offset == other.offset
50    }
51}
52
53impl Drop for Allocal {
54    fn drop(&mut self) {
55        use std::alloc::{dealloc, Layout};
56
57        // 1. Drop the allocated objects
58        self.clear();
59
60        // 2. Deallocate the data
61        let layout = unsafe { Layout::from_size_align_unchecked(self.cap, 1) };
62        unsafe { dealloc(self.data.as_ptr(), layout) };
63    }
64}
65
66impl Allocal {
67    /// Allocates a new `Allocal` with the given capacity.
68    /// 
69    /// # Panics
70    /// This function panics if the data is unable to be allocated.
71    pub fn with_capacity(capacity: usize) -> Self {
72        use std::alloc::{alloc, Layout};
73        
74        let layout = unsafe { Layout::from_size_align_unchecked(capacity, 1) };
75        let data = unsafe { alloc(layout) };
76
77        Self {
78            data: NonNull::new(data).unwrap(),
79            cap: capacity,
80            locations: Vec::new(),
81        }
82    }
83
84    /// Clears the `Allocal`, invalidating all locations.
85    pub fn clear(&mut self) {
86        for loc in self.locations.drain(..) {
87            if let Some(drop_fn) = loc.drop_fn {
88                // The object needs to be dropped
89                let ptr = unsafe { self.data.as_ptr().add(loc.offset) };
90                drop_fn(ptr);
91            }
92        }
93    }
94
95    /// Reserves some memory to allocate `count` more distincts items into the
96    /// `Allocal`. Note that this does not increese the capacity of the `Allocal`:
97    /// this function basically calls `Vec::reserve` on the vector that keeps
98    /// track of the locations of this `Allocal`.
99    pub fn reserve_items(&mut self, count: usize) {
100        self.locations.reserve(count);
101    }
102
103    /// Gets a `*const T` to a `T` stored in the `Allocal`.
104    #[inline]
105    pub fn get_ptr<T>(&self, location: Location<T>) -> Option<*const T> {
106        self.locations.binary_search_by_key(&location.offset, |loc| loc.offset).ok()?;
107        
108        unsafe {
109            Some(self.data.as_ptr().add(location.offset).cast())
110        }
111    }
112
113    /// Gets a `*mut T` to a `T` stored in the `Allocal`.
114    #[inline]
115    pub fn get_mut_ptr<T>(&mut self, location: Location<T>) -> Option<*mut T> {
116        self.locations.binary_search_by_key(&location.offset, |loc| loc.offset).ok()?;
117        
118        unsafe {
119            Some(self.data.as_ptr().add(location.offset).cast())
120        }
121    }
122
123    /// Gets a reference to an item stored in the `Allocal`.
124    #[inline(always)]
125    pub fn try_get<T>(&self, location: Location<T>) -> Option<&T> {
126        unsafe {
127            Some(&*self.get_ptr(location)?)
128        }
129    }
130    
131    /// Gets a mutable reference to an item stored in the `Allocal`.
132    #[inline(always)]
133    pub fn try_get_mut<T>(&mut self, location: Location<T>) -> Option<&mut T> {
134        unsafe {
135            Some(&mut *self.get_mut_ptr(location)?)
136        }
137    }
138
139    /// Gets a reference to an item stored in the `Allocal`.
140    #[inline(always)]
141    pub fn get<T>(&self, location: Location<T>) -> &T {
142        self.try_get(location).unwrap()
143    }
144    
145    /// Gets a mutable reference to an item stored in the `Allocal`.
146    #[inline(always)]
147    pub fn get_mut<T>(&mut self, location: Location<T>) -> &mut T {
148        self.try_get_mut(location).unwrap()
149    }
150
151    /// Allocates a new `T` in this `Allocal` and returns its location.
152    /// If the `Allocal` is full, `Err(())` is returned.
153    pub fn allocate<T: 'static>(&mut self, item: T) -> Result<Location<T>, ()> {
154        use std::mem::{align_of, size_of, needs_drop};
155
156        let size = size_of::<T>();
157
158        // Handle zero-sized structures.
159        if size == 0 {
160            let location = LocationInner {
161                size,
162                offset: usize::MAX,
163                drop_fn: None,
164            };
165
166            self.locations.push(location);
167
168            // Nothing needs to be written as this is a zero sized type.
169            
170            return Ok(Location {
171                offset: usize::MAX,
172                _m: PhantomData,
173            });
174        }
175
176        let align = align_of::<T>();
177
178        // (self.data + offset) is aligned !
179        let mut offset = self.data.as_ptr() as usize % align;
180
181        let loc = if self.locations.is_empty() {
182            // The `Localloc` is currently empty.
183            // We just need to check whether it is large enough to store our `item`.
184            if offset + size > self.cap {
185                // Our allocated data is too small.
186                return Err(());
187            }
188            
189            0usize
190        } else {
191            let mut loc = 0usize;
192
193            loop {
194                while offset < self.locations[loc].offset + self.locations[loc].size {
195                    offset += align;
196                }
197
198                // (self.data + offset) is still aligned
199
200                loc += 1;
201
202                if loc >= self.locations.len() {
203                    // We are at the end of the allocated data.
204                    // We just need to check whether our remaining allocated data
205                    // is large enough.
206                    if offset + size > self.cap {
207                        // Too small!
208                        return Err(());
209                    }
210
211                    break;
212                }
213
214                if offset + size < self.locations[loc].offset {
215                    // We found an aligned location between two other taken locations.
216                    break;
217                }
218            }
219
220            loc
221        };
222
223        // We can insert our location
224        let location = LocationInner {
225            offset,
226            size,
227            drop_fn: if needs_drop::<T>() {
228                Some(|ptr: *mut u8| unsafe { std::ptr::drop_in_place(ptr as *mut T) })
229            } else { None },
230        };
231
232        self.locations.insert(loc, location.clone());
233
234        // We can safely write our item because the found location is known
235        // to be uninitialized.
236        unsafe {
237            let ptr: *mut T = self.data.as_ptr().add(offset).cast();
238            ptr.write(item);
239        }
240
241        Ok(Location {
242            offset,
243            _m: PhantomData,
244        })
245    }
246
247    /// Deallocate an existing object stored at the given location.
248    /// 
249    /// # Panics
250    /// This function panics if the given location is invalid.
251    pub fn deallocate<T>(&mut self, location: Location<T>) -> T {
252        let idx = match self.locations.binary_search_by_key(&location.offset, |loc| loc.offset) {
253            Ok(val) => val,
254            Err(_) => panic!("Invalid location"),
255        };
256        
257        let location = self.locations.remove(idx);
258
259        // Dropping the object is up the caller
260        unsafe {
261            let ptr: *mut T = self.data.as_ptr().add(location.offset).cast();
262            ptr.read()
263        }
264    }
265}
266
267#[cfg(test)]
268#[test]
269fn create() {
270    let mut allocal = Allocal::with_capacity(100);
271    let mut locations = Vec::new();
272
273    for i in 0u8..100 {
274        locations.push(allocal.allocate(i).unwrap());
275    }
276
277    assert!(allocal.allocate(0u8).is_err());
278
279    for (i, loc) in locations.clone().into_iter().enumerate() {
280        assert_eq!(*allocal.get(loc), i as u8);
281    }
282
283    for (i, loc) in locations.clone().into_iter().enumerate().rev() {
284        assert_eq!(allocal.deallocate(loc), i as u8);
285    }
286
287    for loc in locations.into_iter() {
288        assert_eq!(allocal.try_get(loc), None);
289    }
290}
291
292#[cfg(test)]
293#[test]
294fn alignement() {
295    use std::mem::align_of;
296
297    for _ in 0..100 {
298        let mut allocal = Allocal::with_capacity(30);
299        let loc = allocal.allocate(0u128).unwrap();
300        
301        let ptr = allocal.get_ptr(loc).unwrap() as usize;
302        assert_eq!(ptr % align_of::<u128>(), 0);
303    }
304}
305
306#[cfg(test)]
307#[test]
308fn drop() {
309    use std::rc::Rc;
310    use std::cell::Cell;
311
312    let drop_count = Rc::new(Cell::new(0u8));
313    struct DropCheck(Rc<Cell<u8>>);
314    impl Drop for DropCheck {
315        fn drop(&mut self) {
316            self.0.set(self.0.get() + 1);
317        }
318    }
319
320    {
321        let mut allocal = Allocal::with_capacity(1024);
322
323        for _ in 0..20 {
324            allocal.allocate(DropCheck(Rc::clone(&drop_count))).unwrap();
325        }
326    }
327
328    assert_eq!(drop_count.get(), 20);
329}