orc/
lib.rs

1//! Threadsafe garbage collector (the `Orc<T>` type).
2//!
3//! The Orc<T> type provides shared ownership over an immutable value that is
4//! in stored in a preallocated memory area.
5//! As soon as the last reference to a stored value is gone the value is dropped.
6//! In addition to that cycles are reclaimed if the space is needed for
7//! new allocations.
8//!
9//! While there may be some useful applications in pure rust programms for this
10//! of memory managment scheme, the intended use case is garbage collection for
11//! unityped (speak: dynamic) languages written in and tightly integrated with
12//! rust.
13//!
14
15use std::mem::{transmute, size_of, transmute_copy, forget};
16use std::ops::Deref;
17use std::sync::atomic::{AtomicUsize, Ordering};
18	use std::marker::PhantomData;
19use std::marker::Sync;
20use std::cell::Cell;
21
22// constants
23
24// change to const PTR_SIZE: usize = size_of::<usize>() as soon it's a const fn
25#[cfg(target_pointer_width = "32")]
26const PTR_SIZE: usize = 4;
27#[cfg(target_pointer_width = "64")]
28const PTR_SIZE: usize = 8;
29
30const MAX_WEIGHT_EXP: u8 = PTR_SIZE as u8 * 8 - 1;
31const MAX_WEIGHT: usize = 1usize << MAX_WEIGHT_EXP; // 2^MAX_WEIGHT_EXP
32
33/// A pointer into an OrcHeap. Can be shared across threads.
34pub struct Orc<'a, T: 'a> {
35    pointer_data: [u8; PTR_SIZE - 1], // the ptr is in little endian byteorder
36    weight_exp: Cell<u8>,
37    lifetime_and_type: PhantomData<&'a T>,
38}
39
40unsafe impl<'a, T> Sync for Orc<'a, T> {}
41
42impl<'a, T> Drop for Orc<'a, T> {
43    fn drop(&mut self) {
44        let slot = construct_pointer::<T>(self.pointer_data, 0);
45        let weight = two_two_the(self.weight_exp.get());
46        slot.weight.fetch_sub(weight, Ordering::Release);
47    }
48}
49
50impl<'a, T> Clone for Orc<'a, T> {
51    fn clone(&self) -> Orc<'a, T> {
52        if self.weight_exp.get() > 1 {
53            self.weight_exp.set(self.weight_exp.get() - 1);
54            return Orc {
55                weight_exp: Cell::new(self.weight_exp.get()),
56                pointer_data: self.pointer_data,
57                lifetime_and_type: PhantomData,
58            };
59        }
60        panic!("not implemented yet");
61    }
62}
63
64impl<'a, T> Deref for Orc<'a, T> {
65    type Target = T;
66
67    #[inline(always)]
68    fn deref(&self) -> &T {
69        let slot = construct_pointer::<T>(self.pointer_data, 0);
70        match slot.data {
71            Some(ref d) => d,
72            None => unreachable!(), // since a reference is in existence
73        }
74    }
75}
76
77// wrapper around the type T, that is saved in the heap
78//
79struct OrcInner<T> {
80    weight: AtomicUsize,
81    data: Option<T>,
82}
83
84// The heap that holds all allocated values
85pub struct OrcHeap<T> {
86    heap: Vec<OrcInner<T>>,
87}
88
89unsafe impl<'a, T> Sync for OrcHeap<T> {}
90
91impl<'a, T> OrcHeap<T> {
92    /// Creates a new Heap of sensible size (for certain definitions of sensible)
93    /// # Example:
94    /// ```
95    /// use orc::OrcHeap;
96    /// let heap = OrcHeap::<usize>::new();
97    /// ```
98    pub fn new() -> OrcHeap<T> {
99        const DEFAULT_HEAP_SIZE: usize = 16;
100        OrcHeap::<T>::with_capacity(DEFAULT_HEAP_SIZE)
101    }
102
103    /// Creates a new Heap of a user defined size
104    /// # Example:
105    /// ```
106    /// use orc::OrcHeap;
107    /// let heap = OrcHeap::<usize>::with_capacity(42);
108    /// ```
109    pub fn with_capacity(capacity: usize) -> OrcHeap<T> {
110        let mut heap = Vec::with_capacity(capacity);
111        // it is important that no other push operations on any of theses vectors are performed
112        for _ in 0..capacity {
113            heap.push(OrcInner {
114                weight: AtomicUsize::new(0),
115                data: None,
116            });
117        }
118        // make sure that all pointers have enough headroom to store the weight
119        let (_, weight) = deconstruct_pointer(heap.iter().nth(capacity - 1).unwrap());
120        assert_eq!(weight, 0);
121
122        OrcHeap::<T> { heap: heap }
123    }
124
125
126    /// Allocates a Value in the heap.
127    pub fn alloc(&'a self, value: T) -> Result<Orc<T>, &'static str> {
128        // find an empty slot
129
130        let mut position = 0;
131        loop {
132            unsafe {
133                let slot = self.heap.get_unchecked(position);
134                if slot.weight.compare_and_swap(0, MAX_WEIGHT, Ordering::Relaxed) == 0 {
135                    // a little dance to make the gods of borrow checking happy
136                    let ref data: Option<T> = slot.data;
137                    let mut_data: *mut Option<T> = hack_transmute(data);
138                    // overwrite the data
139                    *mut_data = Some(value);
140                    // give out the pointer
141                    let (pointer_data, _) = deconstruct_pointer(slot);
142                    return Ok(Orc::<'a, T> {
143                        pointer_data: pointer_data,
144                        weight_exp: Cell::new(MAX_WEIGHT_EXP),
145                        lifetime_and_type: PhantomData,
146                    });
147                }
148            }
149
150            position += 1;
151            if position == self.heap.capacity() {
152                position = 0;
153                // Just for now
154                break;
155            }
156        }
157        Err("Out of memory")
158    }
159
160
161    pub fn collect(&'a self) {
162        for position in 0..self.heap.capacity() {
163            unsafe {
164                let slot = self.heap.get_unchecked(position);
165                if slot.weight.compare_and_swap(0, MAX_WEIGHT, Ordering::Relaxed) == 0 {
166                    let ref data: Option<T> = slot.data;
167                    let mut_data: *mut Option<T> = hack_transmute(data);
168                    // overwrite the data
169                    *mut_data = None;
170                }
171            }
172        }
173    }
174}
175
176
177// helper functions
178//
179#[inline(always)]
180fn deconstruct_pointer<T>(p: &OrcInner<T>) -> ([u8; PTR_SIZE - 1], u8) {
181    unsafe {
182        let p: usize = transmute(p);
183        transmute(usize::from_le(p)) // NOOP on little endian machines
184    }
185}
186
187#[inline(always)]
188fn construct_pointer<'a, T>(pointer: [u8; PTR_SIZE - 1], weight: u8) -> &'a OrcInner<T> {
189    unsafe {
190        let p: usize = transmute((pointer, weight));
191        transmute(usize::from_le(p)) // NOOP on little endian machines
192    }
193}
194
195#[inline(always)]
196fn two_two_the(exp: u8) -> usize {
197    1usize << exp
198}
199
200// use this instead of transmute to work around [E0139]
201#[inline(always)]
202unsafe fn hack_transmute<T, U>(x: T) -> U {
203    debug_assert_eq!(size_of::<T>(), size_of::<U>());
204    let y = transmute_copy(&x);
205    forget(x);
206    y
207}
208
209// unit tests
210//
211#[test]
212fn test_two_two_the() {
213    assert_eq!(two_two_the(0), 1);
214    assert_eq!(two_two_the(1), 2);
215    assert_eq!(two_two_the(8), 256);
216}
217
218
219// functional test
220//
221#[cfg(test)]
222mod test_drop {
223    use OrcHeap;
224    use std::cell::Cell;
225
226    struct DropTest<'a>(&'a Cell<usize>);
227
228    impl<'a> Drop for DropTest<'a> {
229        fn drop(&mut self) {
230            let v = self.0.get();
231            self.0.set(v - 1);
232        }
233    }
234
235    #[test]
236    #[allow(unused_variables)]
237    fn test_drop() {
238        let test_size = 1000;
239        let values_in_existence = Cell::new(test_size);
240
241        let heap = OrcHeap::with_capacity(test_size);
242
243        for _ in 0..test_size {
244            let o = heap.alloc(DropTest(&values_in_existence)).unwrap();
245        }
246        heap.collect();
247        assert_eq!(values_in_existence.get(), 0);
248    }
249
250    #[test]
251    #[allow(unused_variables)]
252    fn test_heap_freed() {
253        let test_size = 2;
254        let values_in_existence = Cell::new(5);
255
256        let heap = OrcHeap::with_capacity(test_size);
257
258        {
259            let a = heap.alloc(DropTest(&values_in_existence)).unwrap();
260            let b = heap.alloc(DropTest(&values_in_existence)).unwrap();
261        }
262        // now the heap should be freed and the allocations should be possible
263        let c = heap.alloc(DropTest(&values_in_existence)).unwrap();
264        let d = heap.alloc(DropTest(&values_in_existence)).unwrap();
265        assert_eq!(values_in_existence.get(), 3); // a and b are dropped
266
267        // and this must fail
268        assert!(heap.alloc(DropTest(&values_in_existence)).is_err())
269    }
270}
271
272#[cfg(test)]
273mod test_concurrency {
274    // this test may not fail, even if something is wrong with the concurrent
275    // allocation behaviour. But with a high enough test_size, it will most
276    // likely blow up.
277    extern crate crossbeam;
278    use OrcHeap;
279
280    #[test]
281    fn test_concurrency() {
282        extern crate crossbeam;
283        let test_size = 1000;
284
285        let heap = OrcHeap::with_capacity(test_size * 10);
286
287        crossbeam::scope(|scope| {
288            for _ in 0..test_size {
289                scope.spawn(|| {
290                    for j in 0..test_size {
291                        if let Ok(v) = heap.alloc(j) {
292                            assert_eq!(*v, j);
293                        }
294                    }
295                });
296            }
297        });
298    }
299}