Merge pull request #108 from gwilymk/simple-alloc-implementation

Simple alloc implementation
This commit is contained in:
Corwin 2021-08-18 00:04:40 +01:00 committed by GitHub
commit e5f331587f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 294 additions and 14 deletions

View file

@ -1,5 +1,5 @@
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]
build-std-features = ["compiler-builtins-mem"]
[build]

View file

@ -14,8 +14,9 @@ debug = true
lto = true
debug = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["alloc"]
alloc = []
[dependencies]
bitflags = "1.2"

View file

@ -0,0 +1,15 @@
#![no_std]
#![no_main]
extern crate agb;
extern crate alloc;
use alloc::boxed::Box;
#[agb::entry]
fn main() -> ! {
loop {
let b = Box::new(1);
agb::println!("dynamic allocation made to {:?}", &*b as *const _);
}
}

View file

@ -0,0 +1,106 @@
use core::alloc::{GlobalAlloc, Layout};
use core::ptr::NonNull;
use crate::interrupt::Mutex;
use super::bump_allocator::BumpAllocator;
struct Block {
used: bool,
next: Option<NonNull<Block>>,
}
impl Block {
pub unsafe fn from_data_ptr(data_ptr: *mut u8, layout: Layout) -> *mut Block {
let block_layout = Layout::new::<Block>();
let (_, offset) = block_layout.extend(layout).expect("Overflow on allocation");
data_ptr.sub(offset).cast()
}
}
struct BlockAllocatorState {
first_block: Option<NonNull<Block>>,
last_block: Option<NonNull<Block>>,
}
pub(crate) struct BlockAllocator {
inner_allocator: BumpAllocator,
state: Mutex<BlockAllocatorState>,
}
impl BlockAllocator {
pub(super) const unsafe fn new() -> Self {
Self {
inner_allocator: BumpAllocator::new(),
state: Mutex::new(BlockAllocatorState {
first_block: None,
last_block: None,
}),
}
}
unsafe fn new_block(&self, layout: Layout) -> *mut u8 {
let block_layout = Layout::new::<Block>();
let (overall_layout, offset) = block_layout.extend(layout).expect("Overflow on allocation");
let block_ptr = self.inner_allocator.alloc(overall_layout);
if block_ptr.is_null() {
return core::ptr::null_mut();
}
let block_ptr = NonNull::new_unchecked(block_ptr).cast();
let mut state = self.state.lock();
*block_ptr.cast::<Block>().as_mut() = Block {
used: true,
next: None,
};
state.first_block.get_or_insert(block_ptr);
if let Some(last_block) = state.last_block {
last_block.cast::<Block>().as_mut().next = Some(block_ptr);
}
state.last_block = Some(block_ptr);
block_ptr.as_ptr().cast::<u8>().add(offset)
}
}
unsafe impl GlobalAlloc for BlockAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// find a block that this current request fits in
let block_layout = Layout::new::<Block>();
let (full_layout, offset) = block_layout.extend(layout).unwrap();
{
let state = self.state.lock();
let mut current_block = state.first_block;
while let Some(mut curr) = current_block {
let mut curr_block = curr.as_mut();
if !curr_block.used {
if let Some(next) = curr_block.next {
let size = next.cast::<u8>().as_ptr().offset_from(curr.as_ptr().cast());
if size >= full_layout.size() as isize {
curr_block.used = true;
return curr.as_ptr().cast::<u8>().add(offset);
}
}
}
current_block = curr_block.next;
}
}
self.new_block(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let block = Block::from_data_ptr(ptr, layout);
(*block).used = false;
}
}

View file

@ -0,0 +1,62 @@
use core::alloc::{GlobalAlloc, Layout};
use core::ptr::NonNull;
use crate::interrupt::Mutex;
fn get_data_end() -> usize {
extern "C" {
static __ewram_data_end: usize;
}
// TODO: This seems completely wrong, but without the &, rust generates
// a double dereference :/. Maybe a bug in nightly?
(unsafe { &__ewram_data_end }) as *const _ as usize
}
pub(crate) struct BumpAllocator {
current_ptr: Mutex<Option<NonNull<u8>>>,
}
impl BumpAllocator {
pub const fn new() -> Self {
Self {
current_ptr: Mutex::new(None),
}
}
}
impl BumpAllocator {
fn alloc_safe(&self, layout: Layout) -> *mut u8 {
let mut current_ptr = self.current_ptr.lock();
let ptr = if let Some(c) = *current_ptr {
c.as_ptr() as usize
} else {
get_data_end()
};
let alignment_bitmask = layout.align() - 1;
let fixup = ptr & alignment_bitmask;
let amount_to_add = layout.align() - fixup;
let resulting_ptr = ptr + amount_to_add;
let new_current_ptr = resulting_ptr + layout.size();
if new_current_ptr as usize >= super::EWRAM_END {
return core::ptr::null_mut();
}
*current_ptr = NonNull::new(new_current_ptr as *mut _);
resulting_ptr as *mut _
}
}
unsafe impl GlobalAlloc for BumpAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.alloc_safe(layout)
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
}

91
agb/src/agb_alloc/mod.rs Normal file
View file

@ -0,0 +1,91 @@
use core::alloc::Layout;
mod block_allocator;
mod bump_allocator;
use block_allocator::BlockAllocator;
const EWRAM_END: usize = 0x0204_0000;
#[global_allocator]
static GLOBAL_ALLOC: BlockAllocator = unsafe { BlockAllocator::new() };
#[alloc_error_handler]
fn alloc_error(layout: Layout) -> ! {
panic!(
"Failed to allocate size {} with alignment {}",
layout.size(),
layout.align()
);
}
#[cfg(test)]
mod test {
const EWRAM_START: usize = 0x0200_0000;
use super::*;
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
#[test_case]
fn test_box(_gba: &mut crate::Gba) {
let first_box = Box::new(1);
let second_box = Box::new(2);
assert!(&*first_box as *const _ < &*second_box as *const _);
assert_eq!(*first_box, 1);
assert_eq!(*second_box, 2);
let address = &*first_box as *const _ as usize;
assert!(
address >= EWRAM_START && address < EWRAM_END,
"ewram is located between 0x0200_0000 and 0x0204_0000, address was actually found to be {:#010X}",
address
);
}
#[test_case]
fn test_vec(_gba: &mut crate::Gba) {
let mut v = Vec::with_capacity(5);
for i in 0..100 {
v.push(i);
}
for i in 0..100 {
assert_eq!(v[i], i);
}
}
#[test_case]
fn test_creating_and_removing_things(_gba: &mut crate::Gba) {
let item = Box::new(1);
for i in 0..1_000 {
let x = Box::new(i);
assert_eq!(*x, i);
let address = &*x as *const _ as usize;
assert!(
address >= EWRAM_START && address < EWRAM_END,
"ewram is located between 0x0200_0000 and 0x0204_0000, address was actually found to be {:#010X}",
address
);
}
assert_eq!(*item, 1);
}
#[test_case]
fn test_adding_to_2_different_vectors(_gba: &mut crate::Gba) {
let mut v1 = vec![1, 2, 3];
let mut v2 = vec![4, 5, 6];
for i in 0..100 {
v1.push(i + 100);
v2.push(i + 1000);
}
assert_eq!(v1[40], 137);
assert_eq!(v2[78], 1075);
}
}

View file

@ -347,7 +347,7 @@ impl<T> Mutex<T> {
}
}
pub fn new(val: T) -> Self {
pub const fn new(val: T) -> Self {
Mutex {
internal: UnsafeCell::new(val),
state: UnsafeCell::new(MutexState::Unlocked),
@ -362,17 +362,17 @@ pub struct MutexRef<'a, T> {
impl<'a, T> Drop for MutexRef<'a, T> {
fn drop(&mut self) {
unsafe {
let state = &mut *self.state.get();
let state = unsafe { &mut *self.state.get() };
let prev_state = *state;
*state = MutexState::Unlocked;
match prev_state {
MutexState::Locked(b) => INTERRUPTS_ENABLED.set(b as u16),
MutexState::Unlocked => {}
}
}
}
}
impl<'a, T> Deref for MutexRef<'a, T> {
type Target = T;

View file

@ -4,15 +4,20 @@
#![feature(asm)]
#![deny(clippy::all)]
#![feature(custom_test_frameworks)]
#![feature(alloc_error_handler)]
#![test_runner(crate::test_runner)]
#![reexport_test_harness_main = "test_main"]
//! # agb
//! `agb` is a library for making games on the Game Boy Advance using the Rust
//! programming language. It attempts to be a high level abstraction over the
//! internal workings of the Game Boy Advance whilst still being high
//! performance and memory efficient.
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
mod agb_alloc;
/// Implements everything relating to things that are displayed on screen.
pub mod display;
/// Button inputs to the system.

View file

@ -1,5 +1,5 @@
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]
build-std-features = ["compiler-builtins-mem"]
[build]