Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unsafe cell linked list allocator #1

Draft
wants to merge 10 commits into
base: heap_experiments
Choose a base branch
from
13 changes: 1 addition & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
FROM rust-m68k:latest
FROM quay.io/reinvantveer/rust-m68k-megadrive:1.53.0-dev
MAINTAINER [email protected]
MAINTAINER [email protected]

# Copy over all files
COPY . /rust-mega-drive

# Build the rust-mega-drive crate
WORKDIR /rust-mega-drive
ENV MEGADRIVE_HOME=/rust-mega-drive/share
ENV RUSTUP_TOOLCHAIN=m68k
ENV LLVM_CONFIG=/llvm-m68k/bin/llvm-config
RUN cargo build --release

# Install the megadrive cargo command
WORKDIR /rust-mega-drive/tools/cargo-megadrive
RUN cargo install --path=.

# Build megapong as default command
WORKDIR /rust-mega-drive/examples/megapong
RUN cargo megadrive --verbose build
Expand Down
4 changes: 1 addition & 3 deletions examples/megacoinflip/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ fn upload_graphics(vdp: &mut VDP) {
#[no_mangle]
pub fn main() -> ! {
// Initialize the allocator to provide actual heap allocations
unsafe { ALLOCATOR.init() }
unsafe { ALLOCATOR.init(); }

let mut renderer = Renderer::new();
let mut controllers = Controllers::new();
Expand All @@ -67,7 +67,6 @@ pub fn main() -> ! {

vdp.enable_interrupts(false, true, false);
vdp.enable_display(true);
let mut frame = 0u16;

let mut flipped = Vec::new();

Expand All @@ -88,7 +87,6 @@ pub fn main() -> ! {
sprite.y = y_off as u16;
renderer.draw_sprite(sprite);

frame = (frame + 1) & 0x7fff;
renderer.render(&mut vdp);
// vsync
wait_for_vblank();
Expand Down
140 changes: 67 additions & 73 deletions libs/megadrive-alloc/src/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,77 @@

use core::mem;
use core::alloc::{Layout, GlobalAlloc};
use core::cell::RefCell;

use crate::hole::{Hole, HoleList};
use megadrive_sys::heap;
use core::cell::UnsafeCell;

/// A fixed size heap backed by a linked list of free memory blocks.
pub struct Heap {
bottom: usize,
size: usize,
holes: HoleList,
pub struct Alloc {
pub(crate) bottom: usize,
pub(crate) size: usize,
pub(crate) holes: UnsafeCell<HoleList>,
}

impl Heap {
/// Creates an empty heap. All allocate calls will return `None`.
pub const fn empty() -> Heap {
Heap {
bottom: 0,
size: 0,
holes: HoleList::empty(),
}
unsafe impl GlobalAlloc for Alloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let mut holes = self.holes
.get()
.as_mut();

holes.as_mut()
.unwrap()
.allocate_first_fit(layout)
.ok()
.map_or(0 as *mut u8, |allocation| allocation)
}

unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.holes
.get()
.as_mut()
.unwrap()
.deallocate(ptr, layout)
}
}

/// SAFETY:
/// The Sync implementation block is required by the compiler in order to have a shared internally
/// mutable Heap.
///
/// This is basically a dummy implementation. The heap cannot be safely used across thread
/// boundaries as it is likely to lead to data races. So: don't use in multi-threaded apps.
unsafe impl Sync for Alloc {}

impl Alloc {
// Initializes an empty heap
//
// SAFETY:
// This function must be called at most once and must only be used on an
// empty heap.
// This function must be called at most once
pub const fn empty() -> Self {
Self {
bottom: 0,
size: 0,
holes: UnsafeCell::new(HoleList {
first: Hole {
size: 0,
next: None,
},
}),
}
}

pub unsafe fn init(&mut self) {
// Create the raw slice from the magical heap function from megadrive_sys
let heap_slice = heap();

// Take a raw pointer to the slice as the bottom
self.bottom = heap_slice.as_ptr() as usize;
let bottom = heap_slice.as_ptr() as usize;
let size = heap_slice.len();

// An then the size as the length of the slice
self.size = heap_slice.len();
self.holes = HoleList::new(self.bottom, self.size);
self.bottom = bottom;
self. size = size;
self.holes = UnsafeCell::new(
HoleList::new(bottom, size)
);
}

/// Allocates a chunk of the given size with the given alignment. Returns a pointer to the
Expand All @@ -55,7 +89,13 @@ impl Heap {
let size = align_up(size, mem::align_of::<Hole>());
let layout = Layout::from_size_align(size, layout.align()).unwrap();

self.holes.allocate_first_fit(layout)
unsafe {
self.holes
.get()
.as_mut()
.unwrap()
.allocate_first_fit(layout)
}
}

/// Frees the given allocation. `ptr` must be a pointer returned
Expand All @@ -73,7 +113,11 @@ impl Heap {
let size = align_up(size, mem::align_of::<Hole>());
let layout = Layout::from_size_align(size, layout.align()).unwrap();

self.holes.deallocate(ptr, layout);
self.holes
.get()
.as_mut()
.unwrap()
.deallocate(ptr, layout);
}

/// Returns the bottom address of the heap.
Expand All @@ -90,58 +134,8 @@ impl Heap {
pub fn top(&self) -> usize {
self.bottom + self.size
}

/// Extends the size of the heap by creating a new hole at the end
///
/// # Unsafety
///
/// The new extended area must be valid
pub unsafe fn extend(&mut self, by: usize) {
let top = self.top();
let layout = Layout::from_size_align(by, 1).unwrap();
self.holes.deallocate(top as *mut u8, layout);
self.size += by;
}
}

struct RefCellHeap {
heap: RefCell<Heap>
}

impl RefCellHeap {
const fn new() -> RefCellHeap {
RefCellHeap {
heap: RefCell::new(Heap::empty())
}
}
}

static HEAP: RefCellHeap = RefCellHeap::new();

unsafe impl GlobalAlloc for Heap {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
HEAP.heap
.borrow_mut()
.allocate_first_fit(layout)
.ok()
.map_or(0 as *mut u8, |allocation| allocation)
}

unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
HEAP.heap
.borrow_mut()
.deallocate(ptr, layout)
}
}

/// SAFETY:
/// The Sync implementation block is required by the compiler in order to have a shared internally
/// mutable Heap.
///
/// This is basically a dummy implementation. The heap cannot be safely used across thread
/// boundaries as it is likely to lead to data races. So: don't use in multi-threaded apps.
unsafe impl Sync for RefCellHeap {}

/// Align upwards. Returns the smallest x with alignment `align`
/// so that x >= addr. The alignment must be a power of 2.
pub fn align_up(addr: usize, align: usize) -> usize {
Expand Down
18 changes: 4 additions & 14 deletions libs/megadrive-alloc/src/hole.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,14 @@
use core::mem::size_of;
use core::alloc::Layout;

use crate::heap::{align_up};
use crate::heap::align_up;

/// A sorted list of holes. It uses the the holes itself to store its nodes.
pub struct HoleList {
first: Hole, // dummy
pub(crate) first: Hole, // dummy
}

impl HoleList {
/// Creates an empty `HoleList`.
pub const fn empty() -> HoleList {
HoleList {
first: Hole {
size: 0,
next: None,
},
}
}

/// Creates a `HoleList` that contains the given hole. This function is unsafe because it
/// creates a hole at the given `hole_addr`. This can cause undefined behavior if this address
/// is invalid or if memory from the `[hole_addr, hole_addr+size) range is used somewhere else.
Expand Down Expand Up @@ -84,8 +74,8 @@ impl HoleList {
/// A block containing free memory. It points to the next hole and thus forms a linked list.
#[cfg(not(test))]
pub struct Hole {
size: usize,
next: Option<&'static mut Hole>,
pub(crate) size: usize,
pub(crate) next: Option<&'static mut Hole>,
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions libs/megadrive-alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
mod heap;
mod hole;

use crate::heap::Heap;
use crate::heap::Alloc;

#[global_allocator]
pub static mut ALLOCATOR: Heap = Heap::empty();
pub static mut ALLOCATOR: Alloc = Alloc::empty();

#[alloc_error_handler]
fn alloc_error_handler(layout: core::alloc::Layout) -> ! {
Expand Down