From 06f1379337b5152d43ac48186bd747c1b06ca621 Mon Sep 17 00:00:00 2001 From: Jacob Trueb Date: Sun, 29 Oct 2023 22:04:31 -0500 Subject: [PATCH 01/16] Add choice of Two-Level Segregated Fit and Linked List First Fit --- Cargo.toml | 12 +- README.md | 2 +- examples/global_alloc.rs | 5 +- src/lib.rs | 322 +++++++++++++++++++++++++++------------ 4 files changed, 238 insertions(+), 103 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eeae610..0a02726 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,14 +24,20 @@ name = "embedded-alloc" version = "0.5.0" [features] +default = ["llff"] allocator_api = [] +# Use the Two-Level Segregated Fit allocator +tlsf = ["rlsf", "const-default"] + # Use the LinkedList first-fit allocator +llff = ["linked_list_allocator"] + [dependencies] critical-section = "1.0" +linked_list_allocator = { version = "0.10.5", default-features = false, optional = true } +rlsf = { version = "0.2.1", default-features = false, optional = true } +const-default = { version = "1.0.0", default-features = false, optional = true } -[dependencies.linked_list_allocator] -default-features = false -version = "0.10.5" [dev-dependencies] cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } diff --git a/README.md b/README.md index 5fd93d7..a34cb15 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Starting with Rust 1.68, this crate can be used as a global allocator on stable extern crate alloc; use cortex_m_rt::entry; -use embedded_alloc::Heap; +use embedded_alloc::LlffHeap as Heap; #[global_allocator] static HEAP: Heap = Heap::empty(); diff --git a/examples/global_alloc.rs b/examples/global_alloc.rs index 812c9f1..d41debd 100644 --- a/examples/global_alloc.rs +++ b/examples/global_alloc.rs @@ -6,7 +6,10 @@ extern crate alloc; use alloc::vec::Vec; use core::panic::PanicInfo; use cortex_m_rt::entry; -use embedded_alloc::Heap; +// Linked-List First Fit Heap allocator (feature = "llff") +use embedded_alloc::LlffHeap as Heap; +// Two-Level Segregated Fit Heap allocator (feature = "tlsf") +// use embedded_alloc::TlsfHeap as Heap; #[global_allocator] static HEAP: Heap = Heap::empty(); diff --git a/src/lib.rs b/src/lib.rs index c92a123..93ac4ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,121 +7,247 @@ use core::cell::RefCell; use core::ptr::{self, NonNull}; use critical_section::Mutex; -use linked_list_allocator::Heap as LLHeap; -pub struct Heap { - heap: Mutex>, -} +#[cfg(feature = "llff")] +pub use llff::Heap as LlffHeap; +#[cfg(feature = "tlsf")] +pub use tlsf::Heap as TlsfHeap; -impl Heap { - /// Crate a new UNINITIALIZED heap allocator - /// - /// You must initialize this heap using the - /// [`init`](Self::init) method before using the allocator. - pub const fn empty() -> Heap { - Heap { - heap: Mutex::new(RefCell::new(LLHeap::empty())), - } +#[cfg(feature = "llff")] +mod llff { + use super::*; + use linked_list_allocator::Heap as LLHeap; + + pub struct Heap { + heap: Mutex>, } - /// Initializes the heap - /// - /// This function must be called BEFORE you run any code that makes use of the - /// allocator. - /// - /// `start_addr` is the address where the heap will be located. - /// - /// `size` is the size of the heap in bytes. - /// - /// Note that: - /// - /// - The heap grows "upwards", towards larger addresses. Thus `start_addr` will - /// be the smallest address used. - /// - /// - The largest address used is `start_addr + size - 1`, so if `start_addr` is - /// `0x1000` and `size` is `0x30000` then the allocator won't use memory at - /// addresses `0x31000` and larger. - /// - /// # Safety - /// - /// Obey these or Bad Stuff will happen. - /// - /// - This function must be called exactly ONCE. - /// - `size > 0` - pub unsafe fn init(&self, start_addr: usize, size: usize) { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .init(start_addr as *mut u8, size); - }); + impl Heap { + /// Crate a new UNINITIALIZED heap allocator + /// + /// You must initialize this heap using the + /// [`init`](Self::init) method before using the allocator. + pub const fn empty() -> Heap { + Heap { + heap: Mutex::new(RefCell::new(LLHeap::empty())), + } + } + + /// Initializes the heap + /// + /// This function must be called BEFORE you run any code that makes use of the + /// allocator. + /// + /// `start_addr` is the address where the heap will be located. + /// + /// `size` is the size of the heap in bytes. + /// + /// Note that: + /// + /// - The heap grows "upwards", towards larger addresses. Thus `start_addr` will + /// be the smallest address used. + /// + /// - The largest address used is `start_addr + size - 1`, so if `start_addr` is + /// `0x1000` and `size` is `0x30000` then the allocator won't use memory at + /// addresses `0x31000` and larger. + /// + /// # Safety + /// + /// Obey these or Bad Stuff will happen. + /// + /// - This function must be called exactly ONCE. + /// - `size > 0` + pub unsafe fn init(&self, start_addr: usize, size: usize) { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .init(start_addr as *mut u8, size); + }); + } + + /// Returns an estimate of the amount of bytes in use. + pub fn used(&self) -> usize { + critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().used()) + } + + /// Returns an estimate of the amount of bytes available. + pub fn free(&self) -> usize { + critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free()) + } } - /// Returns an estimate of the amount of bytes in use. - pub fn used(&self) -> usize { - critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().used()) + unsafe impl GlobalAlloc for Heap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .allocate_first_fit(layout) + .ok() + .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) + }) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .deallocate(NonNull::new_unchecked(ptr), layout) + }); + } } - /// Returns an estimate of the amount of bytes available. - pub fn free(&self) -> usize { - critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free()) + #[cfg(feature = "allocator_api")] + mod allocator_api { + use super::*; + use core::{ + alloc::{AllocError, Allocator, Layout}, + ptr::NonNull, + }; + + unsafe impl Allocator for Heap { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + match layout.size() { + 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), + size => critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .allocate_first_fit(layout) + .map(|allocation| NonNull::slice_from_raw_parts(allocation, size)) + .map_err(|_| AllocError) + }), + } + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + if layout.size() != 0 { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .deallocate(NonNull::new_unchecked(ptr.as_ptr()), layout) + }); + } + } + } } } -unsafe impl GlobalAlloc for Heap { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .allocate_first_fit(layout) - .ok() - .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) - }) - } +#[cfg(feature = "tlsf")] +mod tlsf { + use super::*; + use const_default::ConstDefault; + use rlsf::Tlsf; + + type TlsfHeap = Tlsf<'static, usize, usize, { usize::BITS as usize }, { usize::BITS as usize }>; - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .deallocate(NonNull::new_unchecked(ptr), layout) - }); + pub struct Heap { + heap: Mutex>, } -} -#[cfg(feature = "allocator_api")] -mod allocator_api { - use core::{ - alloc::{AllocError, Allocator, Layout}, - ptr::NonNull, - }; - - use crate::Heap; - - unsafe impl Allocator for Heap { - fn allocate(&self, layout: Layout) -> Result, AllocError> { - match layout.size() { - 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), - size => critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .allocate_first_fit(layout) - .map(|allocation| NonNull::slice_from_raw_parts(allocation, size)) - .map_err(|_| AllocError) - }), + impl Heap { + /// Crate a new UNINITIALIZED heap allocator + /// + /// You must initialize this heap using the + /// [`init`](Self::init) method before using the allocator. + pub const fn empty() -> Heap { + Heap { + heap: Mutex::new(RefCell::new(ConstDefault::DEFAULT)), } } - unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { - if layout.size() != 0 { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .deallocate(NonNull::new_unchecked(ptr.as_ptr()), layout) - }); + /// Initializes the heap + /// + /// This function must be called BEFORE you run any code that makes use of the + /// allocator. + /// + /// `start_addr` is the address where the heap will be located. + /// + /// `size` is the size of the heap in bytes. + /// + /// Note that: + /// + /// - The heap grows "upwards", towards larger addresses. Thus `start_addr` will + /// be the smallest address used. + /// + /// - The largest address used is `start_addr + size - 1`, so if `start_addr` is + /// `0x1000` and `size` is `0x30000` then the allocator won't use memory at + /// addresses `0x31000` and larger. + /// + /// # Safety + /// + /// Obey these or Bad Stuff will happen. + /// + /// - This function must be called exactly ONCE. + /// - `size > 0` + pub unsafe fn init(&self, start_addr: usize, size: usize) { + critical_section::with(|cs| { + let block: &[u8] = core::slice::from_raw_parts(start_addr as *const u8, size); + self.heap + .borrow(cs) + .borrow_mut() + .insert_free_block_ptr(block.into()); + }); + } + } + + unsafe impl GlobalAlloc for Heap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .allocate(layout) + .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) + }) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .deallocate(NonNull::new_unchecked(ptr), layout.align()) + }); + } + } + + #[cfg(feature = "allocator_api")] + mod allocator_api { + use super::*; + use core::{ + alloc::{AllocError, Allocator, Layout}, + ptr::NonNull, + }; + + unsafe impl Allocator for Heap { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + match layout.size() { + 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), + size => critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .allocate(layout) + .map_or(Err(AllocError), |allocation| { + Ok(NonNull::slice_from_raw_parts(allocation, size)) + }) + }), + } + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + if layout.size() != 0 { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .deallocate(NonNull::new_unchecked(ptr.as_ptr()), layout.align()) + }); + } } } } From 26f60411f7f37f34c1c72c290fded71fcc1c6724 Mon Sep 17 00:00:00 2001 From: Jacob Trueb Date: Tue, 31 Oct 2023 10:11:01 -0500 Subject: [PATCH 02/16] Update integration tests for choice of allocator --- .github/workflows/ci.yml | 3 +- examples/allocator_api.rs | 2 +- ...ation_test.rs => llff_integration_test.rs} | 4 +- examples/tlsf_integration_test.rs | 105 ++++++++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) rename examples/{integration_test.rs => llff_integration_test.rs} (94%) create mode 100644 examples/tlsf_integration_test.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7a0f5e..49f0e38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,8 @@ jobs: sudo apt update sudo apt install qemu-system-arm - run: qemu-system-arm --version - - run: cargo run --target thumbv7m-none-eabi --example integration_test --all-features + - run: cargo run --target thumbv7m-none-eabi --example llff_integration_test --all-features + - run: cargo run --target thumbv7m-none-eabi --example tlsf_integration_test --all-features clippy: name: Clippy diff --git a/examples/allocator_api.rs b/examples/allocator_api.rs index 95ab702..39d54cf 100644 --- a/examples/allocator_api.rs +++ b/examples/allocator_api.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; use core::mem::MaybeUninit; use core::panic::PanicInfo; use cortex_m_rt::entry; -use embedded_alloc::Heap; +use embedded_alloc::LlffHeap as Heap; // This is not used, but as of 2023-10-29 allocator_api cannot be used without // a global heap diff --git a/examples/integration_test.rs b/examples/llff_integration_test.rs similarity index 94% rename from examples/integration_test.rs rename to examples/llff_integration_test.rs index a45ba87..af006be 100644 --- a/examples/integration_test.rs +++ b/examples/llff_integration_test.rs @@ -7,7 +7,7 @@ //! After toolchain installation this test can be run with: //! //! ```bash -//! cargo +nightly run --target thumbv7m-none-eabi --example integration_test --all-features +//! cargo +nightly run --target thumbv7m-none-eabi --example llff_integration_test --all-features //! ``` //! //! [Embedded Rust Book]: https://docs.rust-embedded.org/book/intro/index.html @@ -23,7 +23,7 @@ use alloc::vec::Vec; use core::mem::{size_of, MaybeUninit}; use cortex_m_rt::entry; use cortex_m_semihosting::{debug, hprintln}; -use embedded_alloc::Heap; +use embedded_alloc::LlffHeap as Heap; #[global_allocator] static HEAP: Heap = Heap::empty(); diff --git a/examples/tlsf_integration_test.rs b/examples/tlsf_integration_test.rs new file mode 100644 index 0000000..574497b --- /dev/null +++ b/examples/tlsf_integration_test.rs @@ -0,0 +1,105 @@ +//! This is a very basic smoke test that runs in QEMU +//! Reference the QEMU section of the [Embedded Rust Book] for more information +//! +//! This only tests integration of the allocator on an embedded target. +//! Comprehensive allocator tests are located in the allocator dependency. +//! +//! After toolchain installation this test can be run with: +//! +//! ```bash +//! cargo +nightly run --target thumbv7m-none-eabi --example tlsf_integration_test --all-features +//! ``` +//! +//! [Embedded Rust Book]: https://docs.rust-embedded.org/book/intro/index.html + +#![feature(allocator_api)] +#![no_main] +#![no_std] + +extern crate alloc; +extern crate panic_semihosting; + +use alloc::collections::LinkedList; +use core::mem::MaybeUninit; +use cortex_m_rt::entry; +use cortex_m_semihosting::{debug, hprintln}; +use embedded_alloc::TlsfHeap as Heap; + +#[global_allocator] +static HEAP: Heap = Heap::empty(); +const HEAP_SIZE: usize = 30 * 1024; + +fn test_global_heap() { + const ELEMS: usize = 250; + + let mut allocated = LinkedList::new(); + for _ in 0..ELEMS { + allocated.push_back(0); + } + for i in 0..ELEMS { + allocated.push_back(i as i32); + } + + assert_eq!(allocated.len(), 2 * ELEMS); + + for _ in 0..ELEMS { + allocated.pop_front(); + } + + for i in 0..ELEMS { + assert_eq!(allocated.pop_front().unwrap(), i as i32); + } +} + +fn test_allocator_api() { + // small local heap + const HEAP_SIZE: usize = 256; + let heap_mem: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + let local_heap: Heap = Heap::empty(); + unsafe { local_heap.init(heap_mem.as_ptr() as usize, HEAP_SIZE) } + + const ELEMS: usize = 2; + + let mut allocated = LinkedList::new_in(local_heap); + for _ in 0..ELEMS { + allocated.push_back(0); + } + for i in 0..ELEMS { + allocated.push_back(i as i32); + } + + assert_eq!(allocated.len(), 2 * ELEMS); + + for _ in 0..ELEMS { + allocated.pop_front(); + } + + for i in 0..ELEMS { + assert_eq!(allocated.pop_front().unwrap(), i as i32); + } +} + +#[entry] +fn main() -> ! { + { + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + } + + #[allow(clippy::type_complexity)] + let tests: &[(fn() -> (), &'static str)] = &[ + (test_global_heap, "test_global_heap"), + (test_allocator_api, "test_allocator_api"), + ]; + + for (test_fn, test_name) in tests { + hprintln!("{}: start", test_name); + test_fn(); + hprintln!("{}: pass", test_name); + } + + // exit QEMU with a success status + debug::exit(debug::EXIT_SUCCESS); + #[allow(clippy::empty_loop)] + loop {} +} From dff7df232bc5422f98a98d6497e5888e4500ab63 Mon Sep 17 00:00:00 2001 From: Jacob Trueb Date: Tue, 31 Oct 2023 10:16:31 -0500 Subject: [PATCH 03/16] Remove duplicate code from allocator_api --- src/lib.rs | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d3dc59c..d17e4ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,7 @@ mod llff { critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free()) } - unsafe fn alloc(&self, layout: Layout) -> Option> { + fn alloc(&self, layout: Layout) -> Option> { critical_section::with(|cs| { self.heap .borrow(cs) @@ -119,25 +119,15 @@ mod llff { fn allocate(&self, layout: Layout) -> Result, AllocError> { match layout.size() { 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), - size => critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .allocate_first_fit(layout) - .map(|allocation| NonNull::slice_from_raw_parts(allocation, size)) - .map_err(|_| AllocError) + size => self.alloc(layout).map_or(Err(AllocError), |allocation| { + Ok(NonNull::slice_from_raw_parts(allocation, size)) }), } } unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { if layout.size() != 0 { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .deallocate(NonNull::new_unchecked(ptr.as_ptr()), layout) - }); + self.dealloc(ptr.as_ptr(), layout); } } } @@ -201,7 +191,7 @@ mod tlsf { }); } - unsafe fn alloc(&self, layout: Layout) -> Option> { + fn alloc(&self, layout: Layout) -> Option> { critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().allocate(layout)) } @@ -238,26 +228,15 @@ mod tlsf { fn allocate(&self, layout: Layout) -> Result, AllocError> { match layout.size() { 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), - size => critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .allocate(layout) - .map_or(Err(AllocError), |allocation| { - Ok(NonNull::slice_from_raw_parts(allocation, size)) - }) + size => self.alloc(layout).map_or(Err(AllocError), |allocation| { + Ok(NonNull::slice_from_raw_parts(allocation, size)) }), } } unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { if layout.size() != 0 { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .deallocate(NonNull::new_unchecked(ptr.as_ptr()), layout.align()) - }); + self.dealloc(ptr.as_ptr(), layout); } } } From d2e6fa41a7b9ef4353b270236d0b6558da51c66a Mon Sep 17 00:00:00 2001 From: Alex Martens Date: Sat, 4 Nov 2023 11:15:08 -0700 Subject: [PATCH 04/16] merge cron.yml with ci.yml --- .github/workflows/ci.yml | 15 +++++++++++++++ .github/workflows/cron.yml | 26 -------------------------- 2 files changed, 15 insertions(+), 26 deletions(-) delete mode 100644 .github/workflows/cron.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7a0f5e..0085f6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,9 @@ on: pull_request: # Run CI for PRs on any branch merge_group: # Run CI for the GitHub merge queue workflow_dispatch: # Run CI when manually requested + schedule: + # Run every week at 8am UTC Saturday + - cron: '0 8 * * SAT' name: Continuous integration @@ -27,6 +30,18 @@ jobs: - run: cargo check --target=${{ matrix.target }} --example global_alloc - if: ${{ matrix.toolchain == 'nightly' }} run: cargo check --target=${{ matrix.target }} --examples --all-features + - uses: imjohnbo/issue-bot@v3 + if: | + failure() + && github.event_name == 'schedule' + with: + title: CI Failure + labels: ci + body: | + Scheduled CI run failed. Details: + https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} test: runs-on: ubuntu-latest diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml deleted file mode 100644 index ec46eb6..0000000 --- a/.github/workflows/cron.yml +++ /dev/null @@ -1,26 +0,0 @@ -on: - schedule: - # Run every week at 8am UTC Saturday. - - cron: '0 8 * * SAT' - -name: Cron CI - -jobs: - ci-cron: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - targets: thumbv6m-none-eabi - - run: cargo check --examples --target thumbv6m-none-eabi - - uses: imjohnbo/issue-bot@v3 - if: failure() - with: - title: CI Failure - labels: ci - body: | - Scheduled CI run failed. Details: - https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From ba635851aa148f7bb4eb6680103323941c011a92 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Mon, 4 Dec 2023 13:49:22 -0600 Subject: [PATCH 05/16] chore(typo): "Crate" -> "Create" --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index cc9fe1e..999308c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ pub struct Heap { } impl Heap { - /// Crate a new UNINITIALIZED heap allocator + /// Create a new UNINITIALIZED heap allocator /// /// You must initialize this heap using the /// [`init`](Self::init) method before using the allocator. From 69187bf2da35e07d8138746f9b3a8ee45776372c Mon Sep 17 00:00:00 2001 From: Alex Martens Date: Thu, 14 Dec 2023 12:18:00 -0800 Subject: [PATCH 06/16] Update documentation for TLSF and LLFF heaps --- CHANGELOG.md | 10 +++ Cargo.toml | 1 - README.md | 10 ++- src/lib.rs | 240 ++------------------------------------------------- src/llff.rs | 122 ++++++++++++++++++++++++++ src/tlsf.rs | 110 +++++++++++++++++++++++ 6 files changed, 256 insertions(+), 237 deletions(-) create mode 100644 src/llff.rs create mode 100644 src/tlsf.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c8bb34..6c1daaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Added + +- Added a Two-Level Segregated Fit heap with the `tlsf` feature. + +### Changed + +- The `Heap` struct has been renamed to `LlffHeap` and requires the `llff` feature. + ## [v0.5.1] - 2023-11-04 ### Added diff --git a/Cargo.toml b/Cargo.toml index 247517c..086fcf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ linked_list_allocator = { version = "0.10.5", default-features = false, optional rlsf = { version = "0.2.1", default-features = false, optional = true } const-default = { version = "1.0.0", default-features = false, optional = true } - [dev-dependencies] cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" diff --git a/README.md b/README.md index a34cb15..bbe9eb9 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,17 @@ For a full usage example, see [`examples/global_alloc.rs`](https://github.com/ru For this to work, an implementation of [`critical-section`](https://github.com/rust-embedded/critical-section) must be provided. -For simple use cases you may enable the `critical-section-single-core` feature in the [cortex-m](https://github.com/rust-embedded/cortex-m) crate. +For simple use cases with Cortex-M CPUs you may enable the `critical-section-single-core` feature in the [cortex-m](https://github.com/rust-embedded/cortex-m) crate. Please refer to the documentation of [`critical-section`](https://docs.rs/critical-section) for further guidance. +## Features + +There are two heaps available to use: + +* `llff`: Provides `LlffHeap`, a Linked List First Fit heap. +* `tlsf`: Provides `TlsfHeap`, a Two-Level Segregated Fit heap. + +The best heap to use will depend on your application, see [#78](https://github.com/rust-embedded/embedded-alloc/pull/78) for more discussion. ## License diff --git a/src/lib.rs b/src/lib.rs index 33ac654..4308790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,244 +1,14 @@ #![doc = include_str!("../README.md")] #![no_std] #![cfg_attr(feature = "allocator_api", feature(allocator_api, alloc_layout_extra))] - -use core::alloc::{GlobalAlloc, Layout}; -use core::cell::RefCell; -use core::ptr::{self, NonNull}; - -use critical_section::Mutex; +#![warn(missing_docs)] #[cfg(feature = "llff")] -pub use llff::Heap as LlffHeap; +mod llff; #[cfg(feature = "tlsf")] -pub use tlsf::Heap as TlsfHeap; +mod tlsf; #[cfg(feature = "llff")] -mod llff { - use super::*; - use linked_list_allocator::Heap as LLHeap; - - pub struct Heap { - heap: Mutex>, - } - - impl Heap { - /// Create a new UNINITIALIZED heap allocator - /// - /// You must initialize this heap using the - /// [`init`](Self::init) method before using the allocator. - pub const fn empty() -> Heap { - Heap { - heap: Mutex::new(RefCell::new(LLHeap::empty())), - } - } - - /// Initializes the heap - /// - /// This function must be called BEFORE you run any code that makes use of the - /// allocator. - /// - /// `start_addr` is the address where the heap will be located. - /// - /// `size` is the size of the heap in bytes. - /// - /// Note that: - /// - /// - The heap grows "upwards", towards larger addresses. Thus `start_addr` will - /// be the smallest address used. - /// - /// - The largest address used is `start_addr + size - 1`, so if `start_addr` is - /// `0x1000` and `size` is `0x30000` then the allocator won't use memory at - /// addresses `0x31000` and larger. - /// - /// # Safety - /// - /// Obey these or Bad Stuff will happen. - /// - /// - This function must be called exactly ONCE. - /// - `size > 0` - pub unsafe fn init(&self, start_addr: usize, size: usize) { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .init(start_addr as *mut u8, size); - }); - } - - /// Returns an estimate of the amount of bytes in use. - pub fn used(&self) -> usize { - critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().used()) - } - - /// Returns an estimate of the amount of bytes available. - pub fn free(&self) -> usize { - critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free()) - } - - fn alloc(&self, layout: Layout) -> Option> { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .allocate_first_fit(layout) - .ok() - }) - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .deallocate(NonNull::new_unchecked(ptr), layout) - }); - } - } - - unsafe impl GlobalAlloc for Heap { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - self.alloc(layout) - .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - self.dealloc(ptr, layout); - } - } - - #[cfg(feature = "allocator_api")] - mod allocator_api { - use super::*; - use core::{ - alloc::{AllocError, Allocator, Layout}, - ptr::NonNull, - }; - - unsafe impl Allocator for Heap { - fn allocate(&self, layout: Layout) -> Result, AllocError> { - match layout.size() { - 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), - size => self.alloc(layout).map_or(Err(AllocError), |allocation| { - Ok(NonNull::slice_from_raw_parts(allocation, size)) - }), - } - } - - unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { - if layout.size() != 0 { - self.dealloc(ptr.as_ptr(), layout); - } - } - } - } -} - +pub use llff::Heap as LlffHeap; #[cfg(feature = "tlsf")] -mod tlsf { - use super::*; - use const_default::ConstDefault; - use rlsf::Tlsf; - - type TlsfHeap = Tlsf<'static, usize, usize, { usize::BITS as usize }, { usize::BITS as usize }>; - - pub struct Heap { - heap: Mutex>, - } - - impl Heap { - /// Create a new UNINITIALIZED heap allocator - /// - /// You must initialize this heap using the - /// [`init`](Self::init) method before using the allocator. - pub const fn empty() -> Heap { - Heap { - heap: Mutex::new(RefCell::new(ConstDefault::DEFAULT)), - } - } - - /// Initializes the heap - /// - /// This function must be called BEFORE you run any code that makes use of the - /// allocator. - /// - /// `start_addr` is the address where the heap will be located. - /// - /// `size` is the size of the heap in bytes. - /// - /// Note that: - /// - /// - The heap grows "upwards", towards larger addresses. Thus `start_addr` will - /// be the smallest address used. - /// - /// - The largest address used is `start_addr + size - 1`, so if `start_addr` is - /// `0x1000` and `size` is `0x30000` then the allocator won't use memory at - /// addresses `0x31000` and larger. - /// - /// # Safety - /// - /// Obey these or Bad Stuff will happen. - /// - /// - This function must be called exactly ONCE. - /// - `size > 0` - pub unsafe fn init(&self, start_addr: usize, size: usize) { - critical_section::with(|cs| { - let block: &[u8] = core::slice::from_raw_parts(start_addr as *const u8, size); - self.heap - .borrow(cs) - .borrow_mut() - .insert_free_block_ptr(block.into()); - }); - } - - fn alloc(&self, layout: Layout) -> Option> { - critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().allocate(layout)) - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .deallocate(NonNull::new_unchecked(ptr), layout.align()) - }) - } - } - - unsafe impl GlobalAlloc for Heap { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - self.alloc(layout) - .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - self.dealloc(ptr, layout) - } - } - - #[cfg(feature = "allocator_api")] - mod allocator_api { - use super::*; - use core::{ - alloc::{AllocError, Allocator, Layout}, - ptr::NonNull, - }; - - unsafe impl Allocator for Heap { - fn allocate(&self, layout: Layout) -> Result, AllocError> { - match layout.size() { - 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), - size => self.alloc(layout).map_or(Err(AllocError), |allocation| { - Ok(NonNull::slice_from_raw_parts(allocation, size)) - }), - } - } - - unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { - if layout.size() != 0 { - self.dealloc(ptr.as_ptr(), layout); - } - } - } - } -} +pub use tlsf::Heap as TlsfHeap; diff --git a/src/llff.rs b/src/llff.rs new file mode 100644 index 0000000..d618a6a --- /dev/null +++ b/src/llff.rs @@ -0,0 +1,122 @@ +use core::alloc::{GlobalAlloc, Layout}; +use core::cell::RefCell; +use core::ptr::{self, NonNull}; + +use critical_section::Mutex; +use linked_list_allocator::Heap as LLHeap; + +/// A linked list first fit heap. +pub struct Heap { + heap: Mutex>, +} + +impl Heap { + /// Create a new UNINITIALIZED heap allocator + /// + /// You must initialize this heap using the + /// [`init`](Self::init) method before using the allocator. + pub const fn empty() -> Heap { + Heap { + heap: Mutex::new(RefCell::new(LLHeap::empty())), + } + } + + /// Initializes the heap + /// + /// This function must be called BEFORE you run any code that makes use of the + /// allocator. + /// + /// `start_addr` is the address where the heap will be located. + /// + /// `size` is the size of the heap in bytes. + /// + /// Note that: + /// + /// - The heap grows "upwards", towards larger addresses. Thus `start_addr` will + /// be the smallest address used. + /// + /// - The largest address used is `start_addr + size - 1`, so if `start_addr` is + /// `0x1000` and `size` is `0x30000` then the allocator won't use memory at + /// addresses `0x31000` and larger. + /// + /// # Safety + /// + /// Obey these or Bad Stuff will happen. + /// + /// - This function must be called exactly ONCE. + /// - `size > 0` + pub unsafe fn init(&self, start_addr: usize, size: usize) { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .init(start_addr as *mut u8, size); + }); + } + + /// Returns an estimate of the amount of bytes in use. + pub fn used(&self) -> usize { + critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().used()) + } + + /// Returns an estimate of the amount of bytes available. + pub fn free(&self) -> usize { + critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free()) + } + + fn alloc(&self, layout: Layout) -> Option> { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .allocate_first_fit(layout) + .ok() + }) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .deallocate(NonNull::new_unchecked(ptr), layout) + }); + } +} + +unsafe impl GlobalAlloc for Heap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.alloc(layout) + .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.dealloc(ptr, layout); + } +} + +#[cfg(feature = "allocator_api")] +mod allocator_api { + use super::*; + use core::{ + alloc::{AllocError, Allocator, Layout}, + ptr::NonNull, + }; + + unsafe impl Allocator for Heap { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + match layout.size() { + 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), + size => self.alloc(layout).map_or(Err(AllocError), |allocation| { + Ok(NonNull::slice_from_raw_parts(allocation, size)) + }), + } + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + if layout.size() != 0 { + self.dealloc(ptr.as_ptr(), layout); + } + } + } +} diff --git a/src/tlsf.rs b/src/tlsf.rs new file mode 100644 index 0000000..3a31cfd --- /dev/null +++ b/src/tlsf.rs @@ -0,0 +1,110 @@ +use core::alloc::{GlobalAlloc, Layout}; +use core::cell::RefCell; +use core::ptr::{self, NonNull}; + +use const_default::ConstDefault; +use critical_section::Mutex; +use rlsf::Tlsf; + +type TlsfHeap = Tlsf<'static, usize, usize, { usize::BITS as usize }, { usize::BITS as usize }>; + +/// A two-Level segregated fit heap. +pub struct Heap { + heap: Mutex>, +} + +impl Heap { + /// Create a new UNINITIALIZED heap allocator + /// + /// You must initialize this heap using the + /// [`init`](Self::init) method before using the allocator. + pub const fn empty() -> Heap { + Heap { + heap: Mutex::new(RefCell::new(ConstDefault::DEFAULT)), + } + } + + /// Initializes the heap + /// + /// This function must be called BEFORE you run any code that makes use of the + /// allocator. + /// + /// `start_addr` is the address where the heap will be located. + /// + /// `size` is the size of the heap in bytes. + /// + /// Note that: + /// + /// - The heap grows "upwards", towards larger addresses. Thus `start_addr` will + /// be the smallest address used. + /// + /// - The largest address used is `start_addr + size - 1`, so if `start_addr` is + /// `0x1000` and `size` is `0x30000` then the allocator won't use memory at + /// addresses `0x31000` and larger. + /// + /// # Safety + /// + /// Obey these or Bad Stuff will happen. + /// + /// - This function must be called exactly ONCE. + /// - `size > 0` + pub unsafe fn init(&self, start_addr: usize, size: usize) { + critical_section::with(|cs| { + let block: &[u8] = core::slice::from_raw_parts(start_addr as *const u8, size); + self.heap + .borrow(cs) + .borrow_mut() + .insert_free_block_ptr(block.into()); + }); + } + + fn alloc(&self, layout: Layout) -> Option> { + critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().allocate(layout)) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + critical_section::with(|cs| { + self.heap + .borrow(cs) + .borrow_mut() + .deallocate(NonNull::new_unchecked(ptr), layout.align()) + }) + } +} + +unsafe impl GlobalAlloc for Heap { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.alloc(layout) + .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.dealloc(ptr, layout) + } +} + +#[cfg(feature = "allocator_api")] +mod allocator_api { + use super::*; + use core::{ + alloc::{AllocError, Allocator, Layout}, + ptr::NonNull, + }; + + unsafe impl Allocator for Heap { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + match layout.size() { + 0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)), + size => self.alloc(layout).map_or(Err(AllocError), |allocation| { + Ok(NonNull::slice_from_raw_parts(allocation, size)) + }), + } + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + if layout.size() != 0 { + self.dealloc(ptr.as_ptr(), layout); + } + } + } +} From 4d6ff20957bed0c1a671b0737c60b8d849c34399 Mon Sep 17 00:00:00 2001 From: Alex Martens Date: Thu, 14 Dec 2023 15:34:07 -0800 Subject: [PATCH 07/16] edition: 2018 -> 2021 --- CHANGELOG.md | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c1daaf..e26db31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - The `Heap` struct has been renamed to `LlffHeap` and requires the `llff` feature. +- Updated the rust edition from 2018 to 2021. ## [v0.5.1] - 2023-11-04 diff --git a/Cargo.toml b/Cargo.toml index 086fcf6..881bafe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ description = "A heap allocator for embedded systems" repository = "https://github.com/rust-embedded/embedded-alloc" documentation = "https://docs.rs/embedded-alloc" readme = "README.md" -edition = "2018" +edition = "2021" keywords = [ "allocator", From 5ca340f0d3245b7b27512342e287a592b539dad8 Mon Sep 17 00:00:00 2001 From: Alex Martens Date: Sat, 24 Feb 2024 09:16:45 -0800 Subject: [PATCH 08/16] Fix new clippy lints --- src/llff.rs | 5 +---- src/tlsf.rs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/llff.rs b/src/llff.rs index d618a6a..a07e73c 100644 --- a/src/llff.rs +++ b/src/llff.rs @@ -98,10 +98,7 @@ unsafe impl GlobalAlloc for Heap { #[cfg(feature = "allocator_api")] mod allocator_api { use super::*; - use core::{ - alloc::{AllocError, Allocator, Layout}, - ptr::NonNull, - }; + use core::alloc::{AllocError, Allocator}; unsafe impl Allocator for Heap { fn allocate(&self, layout: Layout) -> Result, AllocError> { diff --git a/src/tlsf.rs b/src/tlsf.rs index 3a31cfd..75f5255 100644 --- a/src/tlsf.rs +++ b/src/tlsf.rs @@ -86,10 +86,7 @@ unsafe impl GlobalAlloc for Heap { #[cfg(feature = "allocator_api")] mod allocator_api { use super::*; - use core::{ - alloc::{AllocError, Allocator, Layout}, - ptr::NonNull, - }; + use core::alloc::{AllocError, Allocator}; unsafe impl Allocator for Heap { fn allocate(&self, layout: Layout) -> Result, AllocError> { From 80e76f57d9b94fb349479167f31fd3803fdb9d5c Mon Sep 17 00:00:00 2001 From: Alex Martens Date: Sun, 1 Sep 2024 09:26:09 -0700 Subject: [PATCH 09/16] version: 0.5.1 -> 0.6.0 --- CHANGELOG.md | 5 +++-- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e26db31..9f8c5a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [v0.6.0] - 2024-09-01 ### Added @@ -131,7 +131,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Initial version of the allocator -[Unreleased]: https://github.com/rust-embedded/embedded-alloc/compare/v0.5.1...HEAD +[Unreleased]: https://github.com/rust-embedded/embedded-alloc/compare/v0.6.0...HEAD +[v0.6.0]: https://github.com/rust-embedded/embedded-alloc/compare/v0.5.1...v0.6.0 [v0.5.1]: https://github.com/rust-embedded/embedded-alloc/compare/v0.5.0...v0.5.1 [v0.5.0]: https://github.com/rust-embedded/embedded-alloc/compare/v0.4.3...v0.5.0 [v0.4.3]: https://github.com/rust-embedded/embedded-alloc/compare/v0.4.2...v0.4.3 diff --git a/Cargo.toml b/Cargo.toml index 881bafe..f84d36a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ keywords = [ ] license = "MIT OR Apache-2.0" name = "embedded-alloc" -version = "0.5.1" +version = "0.6.0" [features] default = ["llff", "tlsf"] From 0e9babaf1f413c9be18b1c27c307319e715116af Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Sat, 21 Sep 2024 08:56:25 +0000 Subject: [PATCH 10/16] Declare required features for examples --- Cargo.toml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f84d36a..bcf197a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,3 +43,19 @@ cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" cortex-m-semihosting = "0.5" panic-semihosting = { version = "0.6", features = ["exit"] } + +[[example]] +name = "allocator_api" +required-features = ["allocator_api", "llff"] + +[[example]] +name = "llff_integration_test" +required-features = ["allocator_api", "llff"] + +[[example]] +name = "tlsf_integration_test" +required-features = ["allocator_api", "tlsf"] + +[[example]] +name = "global_alloc" +required-features = ["llff"] From 93b323b4723e6afc480ecb1e15e599735a059e3c Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Sat, 21 Sep 2024 09:10:10 +0000 Subject: [PATCH 11/16] Use addr_of_mut! instead of as_ptr() to avoid shared reference to mutable static --- README.md | 3 ++- examples/allocator_api.rs | 3 ++- examples/global_alloc.rs | 3 ++- examples/llff_integration_test.rs | 3 ++- examples/tlsf_integration_test.rs | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bbe9eb9..5eb6cde 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Starting with Rust 1.68, this crate can be used as a global allocator on stable extern crate alloc; +use core::ptr::addr_of_mut; use cortex_m_rt::entry; use embedded_alloc::LlffHeap as Heap; @@ -35,7 +36,7 @@ fn main() -> ! { use core::mem::MaybeUninit; const HEAP_SIZE: usize = 1024; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } } // now the allocator is ready types like Box, Vec can be used. diff --git a/examples/allocator_api.rs b/examples/allocator_api.rs index 39d54cf..ab08818 100644 --- a/examples/allocator_api.rs +++ b/examples/allocator_api.rs @@ -7,6 +7,7 @@ extern crate alloc; use alloc::vec::Vec; use core::mem::MaybeUninit; use core::panic::PanicInfo; +use core::ptr::addr_of_mut; use cortex_m_rt::entry; use embedded_alloc::LlffHeap as Heap; @@ -20,7 +21,7 @@ fn main() -> ! { const HEAP_SIZE: usize = 16; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; let heap: Heap = Heap::empty(); - unsafe { heap.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + unsafe { heap.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } let mut xs = Vec::new_in(heap); xs.push(1); diff --git a/examples/global_alloc.rs b/examples/global_alloc.rs index d41debd..21c95f5 100644 --- a/examples/global_alloc.rs +++ b/examples/global_alloc.rs @@ -5,6 +5,7 @@ extern crate alloc; use alloc::vec::Vec; use core::panic::PanicInfo; +use core::ptr::addr_of_mut; use cortex_m_rt::entry; // Linked-List First Fit Heap allocator (feature = "llff") use embedded_alloc::LlffHeap as Heap; @@ -21,7 +22,7 @@ fn main() -> ! { use core::mem::MaybeUninit; const HEAP_SIZE: usize = 1024; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } } let mut xs = Vec::new(); diff --git a/examples/llff_integration_test.rs b/examples/llff_integration_test.rs index af006be..c0823e5 100644 --- a/examples/llff_integration_test.rs +++ b/examples/llff_integration_test.rs @@ -21,6 +21,7 @@ extern crate panic_semihosting; use alloc::vec::Vec; use core::mem::{size_of, MaybeUninit}; +use core::ptr::addr_of_mut; use cortex_m_rt::entry; use cortex_m_semihosting::{debug, hprintln}; use embedded_alloc::LlffHeap as Heap; @@ -66,7 +67,7 @@ fn main() -> ! { { const HEAP_SIZE: usize = 1024; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } } #[allow(clippy::type_complexity)] diff --git a/examples/tlsf_integration_test.rs b/examples/tlsf_integration_test.rs index 574497b..13e95b0 100644 --- a/examples/tlsf_integration_test.rs +++ b/examples/tlsf_integration_test.rs @@ -21,6 +21,7 @@ extern crate panic_semihosting; use alloc::collections::LinkedList; use core::mem::MaybeUninit; +use core::ptr::addr_of_mut; use cortex_m_rt::entry; use cortex_m_semihosting::{debug, hprintln}; use embedded_alloc::TlsfHeap as Heap; @@ -83,7 +84,7 @@ fn test_allocator_api() { fn main() -> ! { { static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } + unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } } #[allow(clippy::type_complexity)] From 8cf2418c743407b9eee865e1ba6d870a848d1aa3 Mon Sep 17 00:00:00 2001 From: riceman2000 Date: Mon, 27 Jan 2025 21:30:27 -0500 Subject: [PATCH 12/16] Change to &raw --- README.md | 3 +-- examples/allocator_api.rs | 3 +-- examples/global_alloc.rs | 3 +-- examples/llff_integration_test.rs | 7 +++---- examples/tlsf_integration_test.rs | 7 +++---- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5eb6cde..a1022f4 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ Starting with Rust 1.68, this crate can be used as a global allocator on stable extern crate alloc; -use core::ptr::addr_of_mut; use cortex_m_rt::entry; use embedded_alloc::LlffHeap as Heap; @@ -36,7 +35,7 @@ fn main() -> ! { use core::mem::MaybeUninit; const HEAP_SIZE: usize = 1024; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } + unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } } // now the allocator is ready types like Box, Vec can be used. diff --git a/examples/allocator_api.rs b/examples/allocator_api.rs index ab08818..10f5261 100644 --- a/examples/allocator_api.rs +++ b/examples/allocator_api.rs @@ -7,7 +7,6 @@ extern crate alloc; use alloc::vec::Vec; use core::mem::MaybeUninit; use core::panic::PanicInfo; -use core::ptr::addr_of_mut; use cortex_m_rt::entry; use embedded_alloc::LlffHeap as Heap; @@ -21,7 +20,7 @@ fn main() -> ! { const HEAP_SIZE: usize = 16; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; let heap: Heap = Heap::empty(); - unsafe { heap.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } + unsafe { heap.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } let mut xs = Vec::new_in(heap); xs.push(1); diff --git a/examples/global_alloc.rs b/examples/global_alloc.rs index 21c95f5..81705fc 100644 --- a/examples/global_alloc.rs +++ b/examples/global_alloc.rs @@ -5,7 +5,6 @@ extern crate alloc; use alloc::vec::Vec; use core::panic::PanicInfo; -use core::ptr::addr_of_mut; use cortex_m_rt::entry; // Linked-List First Fit Heap allocator (feature = "llff") use embedded_alloc::LlffHeap as Heap; @@ -22,7 +21,7 @@ fn main() -> ! { use core::mem::MaybeUninit; const HEAP_SIZE: usize = 1024; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } + unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } } let mut xs = Vec::new(); diff --git a/examples/llff_integration_test.rs b/examples/llff_integration_test.rs index c0823e5..ce9b553 100644 --- a/examples/llff_integration_test.rs +++ b/examples/llff_integration_test.rs @@ -21,7 +21,6 @@ extern crate panic_semihosting; use alloc::vec::Vec; use core::mem::{size_of, MaybeUninit}; -use core::ptr::addr_of_mut; use cortex_m_rt::entry; use cortex_m_semihosting::{debug, hprintln}; use embedded_alloc::LlffHeap as Heap; @@ -46,9 +45,9 @@ fn test_global_heap() { fn test_allocator_api() { // small local heap const HEAP_SIZE: usize = 16; - let heap_mem: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + let mut heap_mem: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; let local_heap: Heap = Heap::empty(); - unsafe { local_heap.init(heap_mem.as_ptr() as usize, HEAP_SIZE) } + unsafe { local_heap.init(&raw mut heap_mem as usize, HEAP_SIZE) } assert_eq!(local_heap.used(), 0); @@ -67,7 +66,7 @@ fn main() -> ! { { const HEAP_SIZE: usize = 1024; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } + unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } } #[allow(clippy::type_complexity)] diff --git a/examples/tlsf_integration_test.rs b/examples/tlsf_integration_test.rs index 13e95b0..26d36f4 100644 --- a/examples/tlsf_integration_test.rs +++ b/examples/tlsf_integration_test.rs @@ -21,7 +21,6 @@ extern crate panic_semihosting; use alloc::collections::LinkedList; use core::mem::MaybeUninit; -use core::ptr::addr_of_mut; use cortex_m_rt::entry; use cortex_m_semihosting::{debug, hprintln}; use embedded_alloc::TlsfHeap as Heap; @@ -55,9 +54,9 @@ fn test_global_heap() { fn test_allocator_api() { // small local heap const HEAP_SIZE: usize = 256; - let heap_mem: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + let mut heap_mem: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; let local_heap: Heap = Heap::empty(); - unsafe { local_heap.init(heap_mem.as_ptr() as usize, HEAP_SIZE) } + unsafe { local_heap.init(&raw mut heap_mem as usize, HEAP_SIZE) } const ELEMS: usize = 2; @@ -84,7 +83,7 @@ fn test_allocator_api() { fn main() -> ! { { static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(addr_of_mut!(HEAP_MEM) as usize, HEAP_SIZE) } + unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } } #[allow(clippy::type_complexity)] From 20d9db5e6aba2028f03c605f30efdfc3599cfdea Mon Sep 17 00:00:00 2001 From: riceman2000 Date: Mon, 3 Feb 2025 01:50:03 -0500 Subject: [PATCH 13/16] Review changes --- examples/tlsf_integration_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tlsf_integration_test.rs b/examples/tlsf_integration_test.rs index 26d36f4..26a4353 100644 --- a/examples/tlsf_integration_test.rs +++ b/examples/tlsf_integration_test.rs @@ -56,7 +56,7 @@ fn test_allocator_api() { const HEAP_SIZE: usize = 256; let mut heap_mem: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; let local_heap: Heap = Heap::empty(); - unsafe { local_heap.init(&raw mut heap_mem as usize, HEAP_SIZE) } + unsafe { local_heap.init(heap_mem.as_mut_ptr() as usize, HEAP_SIZE) } const ELEMS: usize = 2; From eb87f1a58450e76702676575273cedce8c8c915b Mon Sep 17 00:00:00 2001 From: Jalon Wong Date: Thu, 14 Aug 2025 14:30:50 -0500 Subject: [PATCH 14/16] Add a macro to make initialization easier --- CHANGELOG.md | 6 +++++ Cargo.toml | 2 +- README.md | 4 +++ examples/global_alloc.rs | 7 ++--- examples/llff_integration_test.rs | 6 ++--- examples/tlsf_integration_test.rs | 5 ++-- src/lib.rs | 45 +++++++++++++++++++++++++++++++ 7 files changed, 62 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f8c5a6..e23d086 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Added + +- Added a `init` macro to make initialization easier. + ## [v0.6.0] - 2024-09-01 ### Added diff --git a/Cargo.toml b/Cargo.toml index bcf197a..d23dfe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ allocator_api = [] # Use the Two-Level Segregated Fit allocator tlsf = ["rlsf", "const-default"] - # Use the LinkedList first-fit allocator +# Use the LinkedList first-fit allocator llff = ["linked_list_allocator"] [dependencies] diff --git a/README.md b/README.md index a1022f4..868ae51 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ static HEAP: Heap = Heap::empty(); #[entry] fn main() -> ! { // Initialize the allocator BEFORE you use it + unsafe { + embedded_alloc::init!(HEAP, 1024); + } + // Alternatively, you can write the code directly to meet specific requirements. { use core::mem::MaybeUninit; const HEAP_SIZE: usize = 1024; diff --git a/examples/global_alloc.rs b/examples/global_alloc.rs index 81705fc..b47f0ef 100644 --- a/examples/global_alloc.rs +++ b/examples/global_alloc.rs @@ -17,11 +17,8 @@ static HEAP: Heap = Heap::empty(); #[entry] fn main() -> ! { // Initialize the allocator BEFORE you use it - { - use core::mem::MaybeUninit; - const HEAP_SIZE: usize = 1024; - static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } + unsafe { + embedded_alloc::init!(HEAP, 1024); } let mut xs = Vec::new(); diff --git a/examples/llff_integration_test.rs b/examples/llff_integration_test.rs index ce9b553..5b7f4b2 100644 --- a/examples/llff_integration_test.rs +++ b/examples/llff_integration_test.rs @@ -63,10 +63,8 @@ fn test_allocator_api() { #[entry] fn main() -> ! { - { - const HEAP_SIZE: usize = 1024; - static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } + unsafe { + embedded_alloc::init!(HEAP, 1024); } #[allow(clippy::type_complexity)] diff --git a/examples/tlsf_integration_test.rs b/examples/tlsf_integration_test.rs index 26a4353..591b7d3 100644 --- a/examples/tlsf_integration_test.rs +++ b/examples/tlsf_integration_test.rs @@ -81,9 +81,8 @@ fn test_allocator_api() { #[entry] fn main() -> ! { - { - static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } + unsafe { + embedded_alloc::init!(HEAP, HEAP_SIZE); } #[allow(clippy::type_complexity)] diff --git a/src/lib.rs b/src/lib.rs index 4308790..860c82b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,3 +12,48 @@ mod tlsf; pub use llff::Heap as LlffHeap; #[cfg(feature = "tlsf")] pub use tlsf::Heap as TlsfHeap; + +/// Initialize the global heap. +/// +/// This macro creates a static, uninitialized memory buffer of the specified size and +/// initializes the heap instance with that buffer. +/// +/// # Parameters +/// +/// - `$heap:ident`: The identifier of the global heap instance to initialize. +/// - `$size:expr`: An expression evaluating to a `usize` that specifies the size of the +/// static memory buffer in bytes. It must be **greater than zero**. +/// +/// # Safety +/// +/// This macro must be called first, before any operations on the heap, and **only once**. +/// It internally calls `Heap::init(...)` on the heap, +/// so `Heap::init(...)` should not be called directly if this macro is used. +/// +/// # Example +/// +/// ```rust +/// use cortex_m_rt::entry; +/// use embedded_alloc::LlffHeap as Heap; +/// +/// #[global_allocator] +/// static HEAP: Heap = Heap::empty(); +/// +/// #[entry] +/// fn main() -> ! { +/// // Initialize the allocator BEFORE you use it +/// unsafe { +/// embedded_alloc::init!(HEAP, 1024); +/// } +/// let mut xs = Vec::new(); +/// // ... +/// } +/// ``` +#[macro_export] +macro_rules! init { + ($heap:ident, $size:expr) => { + static mut HEAP_MEM: [::core::mem::MaybeUninit; $size] = + [::core::mem::MaybeUninit::uninit(); $size]; + $heap.init(&raw mut HEAP_MEM as usize, $size) + }; +} From 7d9604211e7bba7ca2bbb9c5c633b725cdfb925b Mon Sep 17 00:00:00 2001 From: Jalon Wong Date: Mon, 25 Aug 2025 13:00:13 -0500 Subject: [PATCH 15/16] Add a flag to prevent duplicate initialization --- CHANGELOG.md | 4 ++++ src/lib.rs | 7 +++++++ src/llff.rs | 38 +++++++++++++++++++++++--------------- src/tlsf.rs | 32 ++++++++++++++++++++------------ 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e23d086..a5ac15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added a `init` macro to make initialization easier. +### Changed + +- The `init` function will panic if it's called more than once or with `size == 0`. + ## [v0.6.0] - 2024-09-01 ### Added diff --git a/src/lib.rs b/src/lib.rs index 860c82b..0b6bd29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,13 @@ pub use tlsf::Heap as TlsfHeap; /// It internally calls `Heap::init(...)` on the heap, /// so `Heap::init(...)` should not be called directly if this macro is used. /// +/// # Panics +/// +/// This macro will panic if either of the following are true: +/// +/// - this function is called more than ONCE. +/// - `size == 0`. +/// /// # Example /// /// ```rust diff --git a/src/llff.rs b/src/llff.rs index a07e73c..aae2485 100644 --- a/src/llff.rs +++ b/src/llff.rs @@ -7,7 +7,7 @@ use linked_list_allocator::Heap as LLHeap; /// A linked list first fit heap. pub struct Heap { - heap: Mutex>, + heap: Mutex>, } impl Heap { @@ -17,7 +17,7 @@ impl Heap { /// [`init`](Self::init) method before using the allocator. pub const fn empty() -> Heap { Heap { - heap: Mutex::new(RefCell::new(LLHeap::empty())), + heap: Mutex::new(RefCell::new((LLHeap::empty(), false))), } } @@ -41,34 +41,42 @@ impl Heap { /// /// # Safety /// - /// Obey these or Bad Stuff will happen. + /// This function is safe if the following invariants hold: /// - /// - This function must be called exactly ONCE. - /// - `size > 0` + /// - `start_addr` points to valid memory. + /// - `size` is correct. + /// + /// # Panics + /// + /// This function will panic if either of the following are true: + /// + /// - this function is called more than ONCE. + /// - `size == 0`. pub unsafe fn init(&self, start_addr: usize, size: usize) { + assert!(size > 0); critical_section::with(|cs| { - self.heap - .borrow(cs) - .borrow_mut() - .init(start_addr as *mut u8, size); + let mut heap = self.heap.borrow_ref_mut(cs); + assert!(!heap.1); + heap.1 = true; + heap.0.init(start_addr as *mut u8, size); }); } /// Returns an estimate of the amount of bytes in use. pub fn used(&self) -> usize { - critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().used()) + critical_section::with(|cs| self.heap.borrow_ref_mut(cs).0.used()) } /// Returns an estimate of the amount of bytes available. pub fn free(&self) -> usize { - critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().free()) + critical_section::with(|cs| self.heap.borrow_ref_mut(cs).0.free()) } fn alloc(&self, layout: Layout) -> Option> { critical_section::with(|cs| { self.heap - .borrow(cs) - .borrow_mut() + .borrow_ref_mut(cs) + .0 .allocate_first_fit(layout) .ok() }) @@ -77,8 +85,8 @@ impl Heap { unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { critical_section::with(|cs| { self.heap - .borrow(cs) - .borrow_mut() + .borrow_ref_mut(cs) + .0 .deallocate(NonNull::new_unchecked(ptr), layout) }); } diff --git a/src/tlsf.rs b/src/tlsf.rs index 75f5255..37935d1 100644 --- a/src/tlsf.rs +++ b/src/tlsf.rs @@ -10,7 +10,7 @@ type TlsfHeap = Tlsf<'static, usize, usize, { usize::BITS as usize }, { usize::B /// A two-Level segregated fit heap. pub struct Heap { - heap: Mutex>, + heap: Mutex>, } impl Heap { @@ -20,7 +20,7 @@ impl Heap { /// [`init`](Self::init) method before using the allocator. pub const fn empty() -> Heap { Heap { - heap: Mutex::new(RefCell::new(ConstDefault::DEFAULT)), + heap: Mutex::new(RefCell::new((ConstDefault::DEFAULT, false))), } } @@ -44,29 +44,37 @@ impl Heap { /// /// # Safety /// - /// Obey these or Bad Stuff will happen. + /// This function is safe if the following invariants hold: /// - /// - This function must be called exactly ONCE. - /// - `size > 0` + /// - `start_addr` points to valid memory. + /// - `size` is correct. + /// + /// # Panics + /// + /// This function will panic if either of the following are true: + /// + /// - this function is called more than ONCE. + /// - `size == 0`. pub unsafe fn init(&self, start_addr: usize, size: usize) { + assert!(size > 0); critical_section::with(|cs| { + let mut heap = self.heap.borrow_ref_mut(cs); + assert!(!heap.1); + heap.1 = true; let block: &[u8] = core::slice::from_raw_parts(start_addr as *const u8, size); - self.heap - .borrow(cs) - .borrow_mut() - .insert_free_block_ptr(block.into()); + heap.0.insert_free_block_ptr(block.into()); }); } fn alloc(&self, layout: Layout) -> Option> { - critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().allocate(layout)) + critical_section::with(|cs| self.heap.borrow_ref_mut(cs).0.allocate(layout)) } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { critical_section::with(|cs| { self.heap - .borrow(cs) - .borrow_mut() + .borrow_ref_mut(cs) + .0 .deallocate(NonNull::new_unchecked(ptr), layout.align()) }) } From 61a268565e8878e7807b8eb4fa963d715df51528 Mon Sep 17 00:00:00 2001 From: Jalon Wong Date: Sun, 31 Aug 2025 11:50:21 -0500 Subject: [PATCH 16/16] Update CHANGELOG.md Co-authored-by: Zeeshan Ali Khan --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5ac15b..0921f25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- The `init` function will panic if it's called more than once or with `size == 0`. +- The `Heap::init` methods now panic if they're called more than once or with `size == 0`. ## [v0.6.0] - 2024-09-01