Преглед на файлове

Allocator is ready, provide linking symbols, complete bookkeeping, proper merges, safe multithreading, OOM handling (+ custom handlers), system primitives, add extensive testing.

TODO: Fix the fail cases.
ticki преди 9 години
родител
ревизия
a022e08f1f
променени са 16 файла, в които са добавени 476 реда и са изтрити 93 реда
  1. 94 0
      src/allocator.rs
  2. 86 2
      src/block.rs
  3. 18 23
      src/bookkeeper.rs
  4. 48 0
      src/fail.rs
  5. 7 8
      src/lib.rs
  6. 15 60
      src/sys.rs
  7. 23 0
      tests/box.rs
  8. 32 0
      tests/brk.rs
  9. 19 0
      tests/brk_multithreaded.rs
  10. 18 0
      tests/btreemap.rs
  11. 31 0
      tests/mpsc.rs
  12. 24 0
      tests/multithreading.rs
  13. 15 0
      tests/realloc.rs
  14. 13 0
      tests/send.rs
  15. 7 0
      tests/string.rs
  16. 26 0
      tests/vec.rs

+ 94 - 0
src/allocator.rs

@@ -0,0 +1,94 @@
+//! The global allocator.
+//!
+//! This contains primitives for the cross-thread allocator. Furthermore, it provides symbols for
+//! allocation, deallocation, and reallocation for Rust.
+
+use core::intrinsics;
+use core::ptr::Unique;
+use core::sync::atomic;
+
+use bookkeeper::Bookkeeper;
+use block::Block;
+
+/// The bookkeeper lock.
+///
+/// This atomic boolean is false whenever the lock is free.
+static mut BOOKKEEPER_LOCK: atomic::AtomicBool = atomic::AtomicBool::new(false);
+/// The bookkeeper.
+///
+/// This is the associated bookkeeper of this allocator.
+static mut BOOKKEEPER: Option<Bookkeeper> = None;
+
+/// Unlock the associated mutex.
+///
+/// This is unsafe, since it will make future use of the acquired bookkeeper reference invalid,
+/// until it is reacquired through [the `get_bookkeeper` method](./fn.get_bookkeeper.html).
+unsafe fn unlock_bookkeeper() {
+    BOOKKEEPER_LOCK.store(false, atomic::Ordering::SeqCst);
+}
+
+/// Lock and possibly initialize the bookkeeper.
+///
+/// Note that the mutex should be unlocked manually, through the [`unlock_bookkeeper`
+/// method](./fn.unlock_bookkeeper.html).
+// TODO use condvar.
+fn get_bookkeeper() -> &'static mut Bookkeeper {
+    unsafe {
+        // Lock the mutex.
+        while BOOKKEEPER_LOCK.load(atomic::Ordering::SeqCst) {}
+        BOOKKEEPER_LOCK.store(true, atomic::Ordering::SeqCst);
+
+        if let Some(ref mut x) = BOOKKEEPER {
+            x
+        } else {
+            BOOKKEEPER = Some(Bookkeeper::new());
+
+            BOOKKEEPER.as_mut().unwrap_or_else(|| intrinsics::unreachable())
+        }
+    }
+}
+
+/// Allocate memory.
+#[no_mangle]
+pub extern fn __rust_allocate(size: usize, align: usize) -> *mut u8 {
+    let res = *get_bookkeeper().alloc(size, align);
+    unsafe { unlock_bookkeeper() }
+
+    res
+}
+
+/// Deallocate memory.
+#[no_mangle]
+pub extern fn __rust_deallocate(ptr: *mut u8, size: usize, _align: usize) {
+    let res = get_bookkeeper().free(Block {
+        size: size,
+        ptr: unsafe { Unique::new(ptr) },
+    });
+    unsafe { unlock_bookkeeper() }
+
+    res
+}
+
+/// Reallocate memory.
+#[no_mangle]
+pub extern fn __rust_reallocate(ptr: *mut u8, old_size: usize, size: usize, align: usize) -> *mut u8 {
+    let res = *get_bookkeeper().realloc(Block {
+        size: old_size,
+        ptr: unsafe { Unique::new(ptr) },
+    }, size, align);
+    unsafe { unlock_bookkeeper() }
+
+    res
+}
+
+/// Return the maximal amount of inplace reallocation that can be done.
+#[no_mangle]
+pub extern fn __rust_reallocate_inplace(_ptr: *mut u8, old_size: usize, _size: usize, _align: usize) -> usize {
+    old_size // TODO
+}
+
+/// Get the usable size of the some number of bytes of allocated memory.
+#[no_mangle]
+pub extern fn __rust_usable_size(size: usize, _align: usize) -> usize {
+    size
+}

+ 86 - 2
src/block.rs

@@ -1,7 +1,7 @@
 //! Memory primitives.
 
-use std::{ops, cmp};
-use std::ptr::Unique;
+use core::{ops, cmp};
+use core::ptr::Unique;
 
 /// A contigious memory block.
 pub struct Block {
@@ -91,3 +91,87 @@ impl From<Block> for BlockEntry {
         }
     }
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use core::ptr::Unique;
+
+    #[test]
+    fn test_end() {
+        let a = Block {
+            size: 10,
+            ptr: unsafe { Unique::new(10 as *mut _) },
+        };
+        let b = Block {
+            size: 15,
+            ptr: unsafe { Unique::new(25 as *mut _) },
+        };
+        let c = Block {
+            size: 75,
+            ptr: unsafe { Unique::new(40 as *mut _) },
+        };
+
+        assert_eq!(*a.end(), *b.ptr);
+        assert_eq!(*b.end(), *c.ptr);
+    }
+
+    #[test]
+    fn test_from() {
+        let ent: BlockEntry = Block {
+            size: 10,
+            ptr: unsafe { Unique::new(10 as *mut _) },
+        }.into();
+        assert!(ent.free)
+    }
+
+    #[test]
+    fn test_left_to() {
+        let a = Block {
+            size: 10,
+            ptr: unsafe { Unique::new(10 as *mut _) },
+        };
+        let b = Block {
+            size: 15,
+            ptr: unsafe { Unique::new(25 as *mut _) },
+        };
+        let c = Block {
+            size: 75,
+            ptr: unsafe { Unique::new(40 as *mut _) },
+        };
+
+        assert!(a.left_to(&b));
+        assert!(b.left_to(&c));
+        assert!(!c.left_to(&a));
+        assert!(!a.left_to(&c));
+        assert!(!b.left_to(&b));
+        assert!(!b.left_to(&a));
+    }
+
+    #[test]
+    fn test_cmp() {
+        let a = Block {
+            size: 10,
+            ptr: unsafe { Unique::new(10 as *mut _) },
+        };
+        let b = Block {
+            size: 15,
+            ptr: unsafe { Unique::new(25 as *mut _) },
+        };
+        let c = Block {
+            size: 75,
+            ptr: unsafe { Unique::new(40 as *mut _) },
+        };
+
+        assert!(a < b);
+        assert!(b < c);
+        assert!(c > a);
+        assert!(a == a);
+        assert!(b == b);
+        assert!(c == c);
+        assert!(c >= c);
+        assert!(c <= c);
+        assert!(a <= c);
+        assert!(b >= a);
+    }
+}

+ 18 - 23
src/bookkeeper.rs

@@ -8,13 +8,12 @@
 use block::{BlockEntry, Block};
 use sys;
 
-use std::mem::align_of;
-use std::{ops, ptr, slice, cmp};
-use std::ptr::Unique;
+use core::mem::align_of;
+use core::{ops, ptr, slice, cmp, intrinsics};
+use core::ptr::Unique;
 
-use alloc::heap;
-
-use extra::option::OptionalExt;
+/// An address representing an "empty" or non-allocated value on the heap.
+const EMPTY_HEAP: *mut u8 = 0x1 as *mut _;
 
 /// The memory bookkeeper.
 ///
@@ -28,6 +27,15 @@ use extra::option::OptionalExt;
 /// to be rendered with private item exposed).
 pub struct Bookkeeper {
     /// The internal block list.
+    ///
+    /// Guarantees
+    /// ==========
+    ///
+    /// Certain guarantees are made:
+    ///
+    /// 1. The list is always sorted with respect to the block's pointers.
+    /// 2. No two free blocks overlap.
+    /// 3. No two free blocks are adjacent.
     block_list: BlockList,
 }
 
@@ -72,6 +80,8 @@ impl Bookkeeper {
     ///
     /// After this have been called, no guarantees are made about the passed pointer. If it want
     /// to, it could begin shooting laser beams.
+    ///
+    /// Freeing an invalid block will drop all future guarantees about this bookkeeper.
     pub fn free(&mut self, block: Block) {
         self.block_list.free(block)
     }
@@ -103,21 +113,6 @@ fn canonicalize_brk(size: usize) -> usize {
 /// A block list.
 ///
 /// This primitive is used for keeping track of the free blocks.
-///
-/// Guarantees
-/// ==========
-///
-/// Certain guarantees are made:
-///
-/// 1. The list is always sorted with respect to the block's pointers.
-/// 2. No two free blocks overlap.
-/// 3. No two free blocks are adjacent.
-///
-/// Merging
-/// =======
-///
-/// Merging is the way the block lists keep these guarantees. Merging works by adding two adjacent
-/// free blocks to one, and then marking the secondary block as occupied.
 struct BlockList {
     /// The capacity of the block list.
     cap: usize,
@@ -135,7 +130,7 @@ impl BlockList {
         BlockList {
             cap: 0,
             len: 0,
-            ptr: unsafe { Unique::new(heap::EMPTY as *mut _) },
+            ptr: unsafe { Unique::new(EMPTY_HEAP as *mut _) },
         }
     }
 
@@ -229,7 +224,7 @@ impl BlockList {
         self.reserve(len + 1);
 
         unsafe {
-            ptr::write((&mut *self.last_mut().unchecked_unwrap() as *mut _).offset(1), block);
+            ptr::write((&mut *self.last_mut().unwrap_or_else(|| intrinsics::unreachable()) as *mut _).offset(1), block);
         }
 
         // Check consistency.

+ 48 - 0
src/fail.rs

@@ -0,0 +1,48 @@
+//! Primitives for allocator failures.
+
+use core::sync::atomic::{self, AtomicPtr};
+use core::{mem, intrinsics};
+
+/// The OOM handler.
+static OOM_HANDLER: AtomicPtr<()> = AtomicPtr::new(default_oom_handler as *mut ());
+
+/// The default OOM handler.
+///
+/// This will simply abort the process with exit code, 1.
+fn default_oom_handler() -> ! {
+    unsafe {
+        intrinsics::abort();
+    }
+}
+
+/// Call the OOM handler.
+#[cold]
+#[inline(never)]
+pub fn oom() -> ! {
+    let value = OOM_HANDLER.load(atomic::Ordering::SeqCst);
+    let handler: fn() -> ! = unsafe { mem::transmute(value) };
+    handler();
+}
+
+/// Set the OOM handler.
+///
+/// This allows for overwriting the default OOM handler with a custom one.
+pub fn set_oom_handler(handler: fn() -> !) {
+    OOM_HANDLER.store(handler as *mut (), atomic::Ordering::SeqCst);
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    #[should_panic]
+    fn test_handler() {
+        fn panic() -> ! {
+            panic!("blame canada for the OOM.");
+        }
+
+        set_oom_handler(panic);
+        oom();
+    }
+}

+ 7 - 8
src/lib.rs

@@ -3,11 +3,12 @@
 //! This crates define the user space allocator for Redox, which emphasizes performance and memory
 //! efficiency.
 
-#![cfg_attr(not(test), feature(oom))]
-#![cfg_attr(test, feature(const_fn))]
+#![allocator]
+#![no_std]
 
-#![feature(alloc)]
-#![feature(heap_api)]
+#![feature(allocator)]
+#![feature(const_fn)]
+#![feature(core_intrinsics)]
 #![feature(stmt_expr_attributes)]
 #![feature(unique)]
 
@@ -19,10 +20,8 @@ extern crate system;
 #[macro_use]
 extern crate syscall;
 
-#[macro_use]
-extern crate extra;
-extern crate alloc;
-
+pub mod allocator;
 pub mod block;
 pub mod bookkeeper;
+pub mod fail;
 pub mod sys;

+ 15 - 60
src/sys.rs

@@ -1,6 +1,6 @@
 //! System primitives.
 
-use std::ptr::Unique;
+use core::ptr::Unique;
 
 /// Out of memory.
 ///
@@ -11,7 +11,10 @@ pub fn oom() -> ! {
     panic!("Out of memory.");
 
     #[cfg(not(test))]
-    ::alloc::oom();
+    {
+        use fail;
+        fail::oom();
+    }
 }
 
 /// A system call error.
@@ -56,7 +59,7 @@ pub fn inc_brk(n: usize) -> Result<Unique<u8>, Error> {
         }
     }
 
-    let expected_end = maybe!(orig_seg_end.checked_add(n) => return Err(Error::ArithOverflow));
+    let expected_end = try!(orig_seg_end.checked_add(n).ok_or(Error::ArithOverflow));
     let new_seg_end = try!(unsafe { sys_brk(expected_end) });
 
     if new_seg_end != expected_end {
@@ -96,46 +99,15 @@ unsafe fn sys_brk(n: usize) -> Result<usize, Error> {
 #[cfg(test)]
 mod test {
     use super::*;
-    use std::ptr;
 
     #[test]
     fn test_oom() {
-        assert_eq!(inc_brk(9999999999999), Err(Error::OutOfMemory));
-    }
-
-    #[test]
-    fn test_write() {
-        let alloc_before = Box::new("hello from the outside.");
-        let ptr = unsafe { (segment_end().unwrap() as *const u8).offset(-1) };
-        let byte_end = unsafe { ptr::read(ptr) };
-
-        let abc = "abc";
-        let mem = inc_brk(8).unwrap() as *mut u64;
-        unsafe {
-            *mem = 90823;
-            *mem = 2897309273;
-            *mem = 293872;
-            *mem = 0xDEADBEAFDEADBEAF;
-            *mem = 99999;
-
-            assert_eq!(*mem, 99999);
-        }
-
-        // Do some heap allocations.
-        println!("test");
-        let bx = Box::new("yo mamma is so nice.");
-        println!("{}", bx);
-
-        assert_eq!(*bx, "yo mamma is so nice.");
-        assert_eq!(*alloc_before, "hello from the outside.");
-        // Check that the stack frame is unaltered.
-        assert_eq!(abc, "abc");
-        assert_eq!(byte_end, unsafe { ptr::read(ptr) });
+        assert_eq!(inc_brk(9999999999999).err(), Some(Error::OutOfMemory));
     }
 
     #[test]
     fn test_read() {
-        let mem = inc_brk(8).unwrap() as *mut u64;
+        let mem = *inc_brk(8).unwrap() as *mut u64;
         unsafe {
             assert_eq!(*mem, 0);
         }
@@ -143,44 +115,27 @@ mod test {
 
     #[test]
     fn test_overflow() {
-        assert_eq!(inc_brk(!0), Err(Error::ArithOverflow));
-        assert_eq!(inc_brk(!0 - 2000), Err(Error::ArithOverflow));
+        assert_eq!(inc_brk(!0).err(), Some(Error::ArithOverflow));
+        assert_eq!(inc_brk(!0 - 2000).err(), Some(Error::ArithOverflow));
     }
 
     #[test]
     fn test_empty() {
-        assert_eq!(inc_brk(0), segment_end())
+        assert_eq!(*inc_brk(0).unwrap(), segment_end().unwrap())
     }
 
     #[test]
     fn test_seq() {
-        let a = inc_brk(4).unwrap() as usize;
-        let b = inc_brk(5).unwrap() as usize;
-        let c = inc_brk(6).unwrap() as usize;
-        let d = inc_brk(7).unwrap() as usize;
+        let a = *inc_brk(4).unwrap() as usize;
+        let b = *inc_brk(5).unwrap() as usize;
+        let c = *inc_brk(6).unwrap() as usize;
+        let d = *inc_brk(7).unwrap() as usize;
 
         assert_eq!(a + 4, b);
         assert_eq!(b + 5, c);
         assert_eq!(c + 6, d);
     }
 
-    #[test]
-    fn test_thread() {
-        use std::thread;
-
-        let mut threads = Vec::new();
-
-        for _ in 0..1000 {
-            threads.push(thread::spawn(|| {
-                inc_brk(9999).unwrap();
-            }));
-        }
-
-        for i in threads {
-            i.join().unwrap();
-        }
-    }
-
     #[test]
     fn test_segment_end() {
         assert_eq!(segment_end().unwrap(), segment_end().unwrap());

+ 23 - 0
tests/box.rs

@@ -0,0 +1,23 @@
+extern crate ralloc;
+
+fn alloc_box() -> Box<u32> {
+    Box::new(0xDEADBEAF)
+}
+
+fn main() {
+    let mut a = Box::new(1);
+    let mut b = Box::new(2);
+    let mut c = Box::new(3);
+
+    assert_eq!(*a, 1);
+    assert_eq!(*b, 2);
+    assert_eq!(*c, 3);
+    assert_eq!(*alloc_box(), 0xDEADBEAF);
+
+    *a = 0;
+    *b = 0;
+    *c = 0;
+    assert_eq!(*a, 0);
+    assert_eq!(*b, 0);
+    assert_eq!(*c, 0);
+}

+ 32 - 0
tests/brk.rs

@@ -0,0 +1,32 @@
+extern crate ralloc;
+
+use ralloc::sys::{inc_brk, segment_end};
+
+use std::ptr;
+
+fn main() {
+    let alloc_before = Box::new("hello from the outside.");
+    let ptr = unsafe { (segment_end().unwrap() as *const u8).offset(-1) };
+    let byte_end = unsafe { ptr::read(ptr) };
+
+    let abc = "abc";
+    let mem = *inc_brk(8).unwrap() as *mut u64;
+    unsafe {
+        *mem = 90823;
+        *mem = 2897309273;
+        *mem = 293872;
+        *mem = 0xDEADBEAFDEADBEAF;
+        *mem = 99999;
+
+        assert_eq!(*mem, 99999);
+    }
+
+    // Do some heap allocations.
+    let bx = Box::new("yo mamma is so nice.");
+
+    assert_eq!(*bx, "yo mamma is so nice.");
+    assert_eq!(*alloc_before, "hello from the outside.");
+    // Check that the stack frame is unaltered.
+    assert_eq!(abc, "abc");
+    assert_eq!(byte_end, unsafe { ptr::read(ptr) });
+}

+ 19 - 0
tests/brk_multithreaded.rs

@@ -0,0 +1,19 @@
+extern crate ralloc;
+
+use ralloc::sys::inc_brk;
+
+use std::thread;
+
+fn main() {
+    let mut threads = Vec::new();
+
+    for _ in 0..1000 {
+        threads.push(thread::spawn(|| {
+            inc_brk(9999).unwrap();
+        }));
+    }
+
+    for i in threads {
+        i.join().unwrap();
+    }
+}

+ 18 - 0
tests/btreemap.rs

@@ -0,0 +1,18 @@
+extern crate ralloc;
+
+use std::collections::BTreeMap;
+
+fn main() {
+    let mut map = BTreeMap::new();
+
+    map.insert("Nicolas", "Cage");
+    map.insert("is", "God");
+    map.insert("according", "to");
+    map.insert("ca1ek", ".");
+
+    assert_eq!(map.get("Nicolas"), Some(&"Cage"));
+    assert_eq!(map.get("is"), Some(&"God"));
+    assert_eq!(map.get("according"), Some(&"to"));
+    assert_eq!(map.get("ca1ek"), Some(&"."));
+    assert_eq!(map.get("This doesn't exist."), None);
+}

+ 31 - 0
tests/mpsc.rs

@@ -0,0 +1,31 @@
+extern crate ralloc;
+
+use std::thread;
+use std::sync::mpsc;
+
+fn main() {
+    {
+        let (tx, rx) = mpsc::channel::<Box<u64>>();
+        thread::spawn(move || {
+            tx.send(Box::new(0xBABAFBABAF)).unwrap();
+            tx.send(Box::new(0xDEADBEAF)).unwrap();
+            tx.send(Box::new(0xDECEA5E)).unwrap();
+            tx.send(Box::new(0xDEC1A551F1E5)).unwrap();
+        });
+        assert_eq!(*rx.recv().unwrap(), 0xBABAFBABAF);
+        assert_eq!(*rx.recv().unwrap(), 0xDEADBEAF);
+        assert_eq!(*rx.recv().unwrap(), 0xDECEA5E);
+        assert_eq!(*rx.recv().unwrap(), 0xDEC1A551F1E5);
+    }
+
+    let (tx, rx) = mpsc::channel();
+    for _ in 0..0xFFFF {
+        let tx = tx.clone();
+        thread::spawn(move || {
+            tx.send(Box::new(0xFA11BAD)).unwrap();
+        });
+    }
+    for _ in 0..0xFFFF {
+        assert_eq!(*rx.recv().unwrap(), 0xFA11BAD);
+    }
+}

+ 24 - 0
tests/multithreading.rs

@@ -0,0 +1,24 @@
+extern crate ralloc;
+
+use std::thread;
+
+fn make_thread() {
+    thread::spawn(|| {
+        let mut vec = Vec::new();
+
+        for i in 0..0xFFFF {
+            vec.push(0);
+            vec[i] = i;
+        }
+
+        for i in 0..0xFFFF {
+            assert_eq!(vec[i], i);
+        }
+    });
+}
+
+fn main() {
+    for _ in 0..5 {
+        make_thread();
+    }
+}

+ 15 - 0
tests/realloc.rs

@@ -0,0 +1,15 @@
+extern crate ralloc;
+
+fn main() {
+    let mut vec = Vec::new();
+    vec.reserve(1);
+    vec.reserve(2);
+    vec.reserve(3);
+    vec.reserve(100);
+    vec.reserve(600);
+    vec.reserve(1000);
+    vec.reserve(2000);
+
+    vec.push(1);
+    vec.push(2);
+}

+ 13 - 0
tests/send.rs

@@ -0,0 +1,13 @@
+extern crate ralloc;
+
+use std::thread;
+
+fn main() {
+    for _ in 0..0xFFFF {
+        let bx: Box<u64> = Box::new(0x11FE15C001);
+
+        thread::spawn(move || {
+            assert_eq!(*bx, 0x11FE15C001);
+        });
+    }
+}

+ 7 - 0
tests/string.rs

@@ -0,0 +1,7 @@
+extern crate ralloc;
+
+fn main() {
+    assert_eq!(&String::from("you only live twice"), "you only live twice");
+    assert_eq!(&String::from("wtf have you smoked"), "wtf have you smoked");
+    assert_eq!(&String::from("get rekt m8"), "get rekt m8");
+}

+ 26 - 0
tests/vec.rs

@@ -0,0 +1,26 @@
+extern crate ralloc;
+
+fn main() {
+    let mut vec = Vec::new();
+
+    for i in 0..0xFFFF {
+        // We're going to annoy the allocator by allocating a small chunk, after which we push.
+        let _bx = Box::new(4);
+        vec.push(i);
+    }
+
+    assert_eq!(vec[0xDEAD], 0xDEAD);
+    assert_eq!(vec[0xBEAF], 0xBEAF);
+    assert_eq!(vec[0xABCD], 0xABCD);
+    assert_eq!(vec[0xFFFAB], 0xFFFAB);
+    assert_eq!(vec[0xAAAAAAA], 0xAAAAAAA);
+
+    for i in 0xFFFF..0 {
+        assert_eq!(vec.pop(), Some(i));
+    }
+
+    for i in 0..0xFFFF {
+        vec[i] = 0;
+        assert_eq!(vec[i], 0);
+    }
+}