mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-24 08:41:34 +11:00
Merge pull request #108 from gwilymk/simple-alloc-implementation
Simple alloc implementation
This commit is contained in:
commit
e5f331587f
|
@ -1,5 +1,5 @@
|
|||
[unstable]
|
||||
build-std = ["core"]
|
||||
build-std = ["core", "alloc"]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
|
||||
[build]
|
||||
|
|
|
@ -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"
|
||||
|
|
15
agb/examples/allocation.rs
Normal file
15
agb/examples/allocation.rs
Normal 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 _);
|
||||
}
|
||||
}
|
106
agb/src/agb_alloc/block_allocator.rs
Normal file
106
agb/src/agb_alloc/block_allocator.rs
Normal 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;
|
||||
}
|
||||
}
|
62
agb/src/agb_alloc/bump_allocator.rs
Normal file
62
agb/src/agb_alloc/bump_allocator.rs
Normal 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
91
agb/src/agb_alloc/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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,14 +362,14 @@ 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 prev_state = *state;
|
||||
*state = MutexState::Unlocked;
|
||||
match prev_state {
|
||||
MutexState::Locked(b) => INTERRUPTS_ENABLED.set(b as u16),
|
||||
MutexState::Unlocked => {}
|
||||
}
|
||||
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 => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[unstable]
|
||||
build-std = ["core"]
|
||||
build-std = ["core", "alloc"]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
|
||||
[build]
|
||||
|
|
Loading…
Reference in a new issue