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]
|
[unstable]
|
||||||
build-std = ["core"]
|
build-std = ["core", "alloc"]
|
||||||
build-std-features = ["compiler-builtins-mem"]
|
build-std-features = ["compiler-builtins-mem"]
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
|
|
@ -14,8 +14,9 @@ debug = true
|
||||||
lto = true
|
lto = true
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
[features]
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
default = ["alloc"]
|
||||||
|
alloc = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.2"
|
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 {
|
Mutex {
|
||||||
internal: UnsafeCell::new(val),
|
internal: UnsafeCell::new(val),
|
||||||
state: UnsafeCell::new(MutexState::Unlocked),
|
state: UnsafeCell::new(MutexState::Unlocked),
|
||||||
|
@ -362,14 +362,14 @@ pub struct MutexRef<'a, T> {
|
||||||
|
|
||||||
impl<'a, T> Drop for MutexRef<'a, T> {
|
impl<'a, T> Drop for MutexRef<'a, T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
let state = unsafe { &mut *self.state.get() };
|
||||||
let state = &mut *self.state.get();
|
|
||||||
let prev_state = *state;
|
let prev_state = *state;
|
||||||
*state = MutexState::Unlocked;
|
*state = MutexState::Unlocked;
|
||||||
match prev_state {
|
|
||||||
MutexState::Locked(b) => INTERRUPTS_ENABLED.set(b as u16),
|
match prev_state {
|
||||||
MutexState::Unlocked => {}
|
MutexState::Locked(b) => INTERRUPTS_ENABLED.set(b as u16),
|
||||||
}
|
MutexState::Unlocked => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,20 @@
|
||||||
#![feature(asm)]
|
#![feature(asm)]
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
#![feature(custom_test_frameworks)]
|
#![feature(custom_test_frameworks)]
|
||||||
|
#![feature(alloc_error_handler)]
|
||||||
#![test_runner(crate::test_runner)]
|
#![test_runner(crate::test_runner)]
|
||||||
#![reexport_test_harness_main = "test_main"]
|
#![reexport_test_harness_main = "test_main"]
|
||||||
|
|
||||||
//! # agb
|
//! # agb
|
||||||
//! `agb` is a library for making games on the Game Boy Advance using the Rust
|
//! `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
|
//! programming language. It attempts to be a high level abstraction over the
|
||||||
//! internal workings of the Game Boy Advance whilst still being high
|
//! internal workings of the Game Boy Advance whilst still being high
|
||||||
//! performance and memory efficient.
|
//! 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.
|
/// Implements everything relating to things that are displayed on screen.
|
||||||
pub mod display;
|
pub mod display;
|
||||||
/// Button inputs to the system.
|
/// Button inputs to the system.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[unstable]
|
[unstable]
|
||||||
build-std = ["core"]
|
build-std = ["core", "alloc"]
|
||||||
build-std-features = ["compiler-builtins-mem"]
|
build-std-features = ["compiler-builtins-mem"]
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
|
Loading…
Reference in a new issue