diff --git a/agb/.cargo/config.toml b/agb/.cargo/config.toml index f56bbdda..54493f4b 100644 --- a/agb/.cargo/config.toml +++ b/agb/.cargo/config.toml @@ -1,5 +1,5 @@ [unstable] -build-std = ["core"] +build-std = ["core", "alloc"] build-std-features = ["compiler-builtins-mem"] [build] diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 8b651160..12b7bf1f 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -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" diff --git a/agb/examples/allocation.rs b/agb/examples/allocation.rs new file mode 100644 index 00000000..327ed07e --- /dev/null +++ b/agb/examples/allocation.rs @@ -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 _); + } +} diff --git a/agb/src/agb_alloc/block_allocator.rs b/agb/src/agb_alloc/block_allocator.rs new file mode 100644 index 00000000..8347b007 --- /dev/null +++ b/agb/src/agb_alloc/block_allocator.rs @@ -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>, +} + +impl Block { + pub unsafe fn from_data_ptr(data_ptr: *mut u8, layout: Layout) -> *mut Block { + let block_layout = Layout::new::(); + let (_, offset) = block_layout.extend(layout).expect("Overflow on allocation"); + + data_ptr.sub(offset).cast() + } +} + +struct BlockAllocatorState { + first_block: Option>, + last_block: Option>, +} + +pub(crate) struct BlockAllocator { + inner_allocator: BumpAllocator, + state: Mutex, +} + +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::(); + 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::().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::().as_mut().next = Some(block_ptr); + } + state.last_block = Some(block_ptr); + + block_ptr.as_ptr().cast::().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::(); + 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::().as_ptr().offset_from(curr.as_ptr().cast()); + if size >= full_layout.size() as isize { + curr_block.used = true; + return curr.as_ptr().cast::().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; + } +} diff --git a/agb/src/agb_alloc/bump_allocator.rs b/agb/src/agb_alloc/bump_allocator.rs new file mode 100644 index 00000000..733e6667 --- /dev/null +++ b/agb/src/agb_alloc/bump_allocator.rs @@ -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>>, +} + +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) {} +} diff --git a/agb/src/agb_alloc/mod.rs b/agb/src/agb_alloc/mod.rs new file mode 100644 index 00000000..edc8efc1 --- /dev/null +++ b/agb/src/agb_alloc/mod.rs @@ -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); + } +} diff --git a/agb/src/interrupt.rs b/agb/src/interrupt.rs index 21e65016..05f58752 100644 --- a/agb/src/interrupt.rs +++ b/agb/src/interrupt.rs @@ -347,7 +347,7 @@ impl Mutex { } } - 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 => {} } } } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index cb2a98fd..8864b605 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -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. diff --git a/template/.cargo/config.toml b/template/.cargo/config.toml index d352818b..76de9890 100644 --- a/template/.cargo/config.toml +++ b/template/.cargo/config.toml @@ -1,5 +1,5 @@ [unstable] -build-std = ["core"] +build-std = ["core", "alloc"] build-std-features = ["compiler-builtins-mem"] [build]