From 3b79e1300e9a7e4e39426e53393a05397d0ff782 Mon Sep 17 00:00:00 2001 From: Yu Duan Date: Mon, 27 Nov 2023 15:05:43 +0000 Subject: [PATCH] Add alloc support Removed nightly features; Simplified allocator Fixed formatting issues Move the global allocator into the allocator module --- Cargo.lock | 54 +++++++++++++++++-- Cargo.toml | 5 ++ src/allocator/bootstrap.rs | 99 ++++++++++++++++++++++++++++++++++ src/allocator/bump.rs | 49 +++++++++++++++++ src/allocator/mod.rs | 105 +++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + 6 files changed, 310 insertions(+), 4 deletions(-) create mode 100644 src/allocator/bootstrap.rs create mode 100644 src/allocator/bump.rs create mode 100644 src/allocator/mod.rs diff --git a/Cargo.lock b/Cargo.lock index bcd67a98..cc856fa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,12 +14,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1926655ba000b19e21f0402be09a1d52d318c8a8a68622870bfb7af2a71315cd" +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "anyhow" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bit_field" version = "0.10.2" @@ -61,9 +73,9 @@ checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -78,6 +90,12 @@ dependencies = [ "void", ] +[[package]] +name = "exclusive_cell" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b9e9908e50b47ebbc3d6fd66ed295b997c270e8d2312a035bcc62722a160ef" + [[package]] name = "fdt" version = "0.1.5" @@ -129,7 +147,9 @@ version = "0.4.5" dependencies = [ "align-address", "align-data", + "allocator-api2", "cc", + "exclusive_cell", "fdt", "goblin 0.8.0", "hermit-dtb", @@ -140,6 +160,7 @@ dependencies = [ "nasm-rs", "riscv", "sbi", + "spinning_top", "sptr", "uart_16550", "uefi", @@ -159,6 +180,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -304,6 +335,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29cb0870400aca7e4487e8ec1e93f9d4288da763cb1da2cedc5102e62b6522ad" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "scroll" version = "0.11.0" @@ -316,6 +353,15 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "sptr" version = "0.3.2" @@ -346,9 +392,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "powerfmt", diff --git a/Cargo.toml b/Cargo.toml index b54ba411..573f6e9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,11 @@ align-data = "0.1" hermit-dtb = { version = "0.1" } goblin = { version = "0.8", default-features = false, features = ["elf64"] } +[target.'cfg(target_os = "none")'.dependencies] +allocator-api2 = { version = "0.2", default-features = false } +exclusive_cell = "0.1" +spinning_top = "0.3" + [target.'cfg(target_os = "uefi")'.dependencies] uefi = "0.26" uefi-services = "0.23" diff --git a/src/allocator/bootstrap.rs b/src/allocator/bootstrap.rs new file mode 100644 index 00000000..b2927545 --- /dev/null +++ b/src/allocator/bootstrap.rs @@ -0,0 +1,99 @@ +//! A bootstrap allocator based on a statically allocated buffer. + +/// A pointer range that can only be compared against. +mod ptr_range { + use core::ops::Range; + use core::ptr::NonNull; + + /// A pointer range that can only be compared against. + pub struct PtrRange { + inner: Range>, + } + + // SAFETY: We never dereference, but only compare, pointers. + unsafe impl Send for PtrRange {} + unsafe impl Sync for PtrRange {} + + impl PtrRange { + /// Returns `true` if the pointer range contains `ptr`. + pub fn contains(&self, ptr: NonNull) -> bool { + self.inner.contains(&ptr) + } + } + + impl From>> for PtrRange { + fn from(value: Range>) -> Self { + Self { inner: value } + } + } +} + +use core::mem::MaybeUninit; +use core::ops::Range; +use core::ptr::NonNull; + +use allocator_api2::alloc::{AllocError, Allocator, Layout}; +use exclusive_cell::ExclusiveCell; + +use self::ptr_range::PtrRange; + +/// A bootstrap allocator. +/// +/// This allocator is generic over the internal allocator and can only be created once. +/// The bootstrap allocator provides the internal allocator with static memory. +/// +/// This allocator tracks, which static memory it was using initially. +/// It can be queried whether a pointer belongs to it. +pub struct BootstrapAllocator { + ptr_range: PtrRange, + allocator: A, +} + +impl Default for BootstrapAllocator +where + A: From<&'static mut [MaybeUninit]>, +{ + fn default() -> Self { + let mem = { + const SIZE: usize = 4 * 1024; + const BYTE: MaybeUninit = MaybeUninit::uninit(); + /// The actual memory of the boostrap allocator. + static MEM: ExclusiveCell<[MaybeUninit; SIZE]> = ExclusiveCell::new([BYTE; SIZE]); + MEM.take().unwrap() + }; + + let ptr_range = { + let Range { start, end } = mem.as_mut_ptr_range(); + let start = NonNull::new(start).unwrap().cast::(); + let end = NonNull::new(end).unwrap().cast::(); + PtrRange::from(start..end) + }; + let allocator = A::from(mem); + + Self { + ptr_range, + allocator, + } + } +} + +impl BootstrapAllocator { + /// Returns `true` if the pointer belonged to the static memory of this allocator. + pub fn manages(&self, ptr: NonNull) -> bool { + self.ptr_range.contains(ptr) + } +} + +unsafe impl Allocator for BootstrapAllocator +where + A: Allocator, +{ + fn allocate(&self, layout: Layout) -> Result, AllocError> { + self.allocator.allocate(layout) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + debug_assert!(self.manages(ptr)); + unsafe { self.allocator.deallocate(ptr, layout) } + } +} diff --git a/src/allocator/bump.rs b/src/allocator/bump.rs new file mode 100644 index 00000000..22b3e9ac --- /dev/null +++ b/src/allocator/bump.rs @@ -0,0 +1,49 @@ +//! A bump allocator. +//! +//! This is a simple allocator design which can only allocate and not deallocate. + +use core::cell::Cell; +use core::mem::MaybeUninit; +use core::ptr::NonNull; + +use allocator_api2::alloc::{AllocError, Allocator, Layout}; + +/// A simple, `!Sync` implementation of a bump allocator. +/// +/// This allocator manages the provided memory. +pub struct BumpAllocator { + mem: Cell<&'static mut [MaybeUninit]>, +} + +unsafe impl Allocator for BumpAllocator { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let ptr: *mut [MaybeUninit] = self.allocate_slice(layout)?; + Ok(NonNull::new(ptr as *mut [u8]).unwrap()) + } + + unsafe fn deallocate(&self, _ptr: NonNull, _layout: Layout) {} +} + +impl BumpAllocator { + fn allocate_slice(&self, layout: Layout) -> Result<&'static mut [MaybeUninit], AllocError> { + let mem = self.mem.take(); + let align_offset = mem.as_ptr().align_offset(layout.align()); + let mid = layout.size() + align_offset; + if mid > mem.len() { + self.mem.set(mem); + Err(AllocError) + } else { + let (alloc, remaining) = mem.split_at_mut(mid); + self.mem.set(remaining); + Ok(&mut alloc[align_offset..]) + } + } +} + +impl From<&'static mut [MaybeUninit]> for BumpAllocator { + fn from(mem: &'static mut [MaybeUninit]) -> Self { + Self { + mem: Cell::new(mem), + } + } +} diff --git a/src/allocator/mod.rs b/src/allocator/mod.rs new file mode 100644 index 00000000..43b863fe --- /dev/null +++ b/src/allocator/mod.rs @@ -0,0 +1,105 @@ +//! Implementation of the Hermit Allocator in the loader + +mod bootstrap; +mod bump; + +use core::ptr; +use core::ptr::NonNull; + +use allocator_api2::alloc::{AllocError, Allocator, GlobalAlloc, Layout}; +use spinning_top::Spinlock; + +use self::bootstrap::BootstrapAllocator; +use self::bump::BumpAllocator; + +/// The global system allocator for Hermit. +struct GlobalAllocator { + /// The bootstrap allocator, which is available immediately. + /// + /// It allows allocations before the heap has been initalized. + bootstrap_allocator: Option>, +} + +impl GlobalAllocator { + const fn empty() -> Self { + Self { + bootstrap_allocator: None, + } + } + + fn align_layout(layout: Layout) -> Layout { + let size = layout.size(); + let align = layout.align(); + Layout::from_size_align(size, align).unwrap() + } + + fn allocate(&mut self, layout: Layout) -> Result, AllocError> { + let layout = Self::align_layout(layout); + self.bootstrap_allocator + .get_or_insert_with(Default::default) + .allocate(layout) + // FIXME: Use NonNull::as_mut_ptr once `slice_ptr_get` is stabilized + // https://github.com/rust-lang/rust/issues/74265 + .map(|ptr| NonNull::new(ptr.as_ptr() as *mut u8).unwrap()) + } + + unsafe fn deallocate(&mut self, ptr: NonNull, layout: Layout) { + let layout = Self::align_layout(layout); + let bootstrap_allocator = self.bootstrap_allocator.as_ref().unwrap(); + assert!(bootstrap_allocator.manages(ptr)); + unsafe { + bootstrap_allocator.deallocate(ptr, layout); + } + } +} + +pub struct LockedAllocator(Spinlock); + +impl LockedAllocator { + /// Creates an empty allocator. All allocate calls will return `None`. + pub const fn empty() -> LockedAllocator { + LockedAllocator(Spinlock::new(GlobalAllocator::empty())) + } +} + +/// To avoid false sharing, the global memory allocator align +/// all requests to a cache line. +unsafe impl GlobalAlloc for LockedAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.0 + .lock() + .allocate(layout) + .ok() + .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { + self.0 + .lock() + .deallocate(NonNull::new_unchecked(ptr), layout) + } + } +} + +#[global_allocator] +static ALLOCATOR: LockedAllocator = LockedAllocator::empty(); + +#[cfg(all(test, not(target_os = "none")))] +mod tests { + use core::mem; + + use super::*; + + #[test] + fn empty() { + let mut allocator = GlobalAllocator::empty(); + let layout = Layout::from_size_align(1, 1).unwrap(); + // we have 4 kbyte static memory + assert!(allocator.allocate(layout.clone()).is_ok()); + + let layout = Layout::from_size_align(0x1000, mem::align_of::()); + let addr = allocator.allocate(layout.unwrap()); + assert!(addr.is_err()); + } +} diff --git a/src/main.rs b/src/main.rs index 0c5ef345..8270d8c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,8 @@ #[macro_use] mod macros; +#[cfg(target_os = "none")] +mod allocator; mod arch; mod console; mod log;