From 370336b78b599b7fd4fa570519872b45a9a34c49 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 20 Nov 2025 13:15:32 +0100 Subject: [PATCH 1/7] update examples - Introduce `defmt` which is very commonly used on embedded systems. It is used to print the allocated structures. - New QEMU runner to support defmt. - Introduce new example which uses a global heap initialized on BSS memory. - Switch to more commonly used thumbv7em-none-eabihf target for the QEMU runner. thumbv6m-none-eabi is still supported as well. --- .cargo/config.toml | 25 ++++++++++++++++--- .github/workflows/ci.yml | 13 ++++++---- Cargo.toml | 14 +++++++++-- examples/allocator_api.rs | 22 ++++++++++------- examples/global_alloc.rs | 24 ++++++++++++------ examples/global_alloc_bss_heap.rs | 41 +++++++++++++++++++++++++++++++ examples/llff_integration_test.rs | 28 +++++++++++++-------- examples/tlsf_integration_test.rs | 25 +++++++++++-------- qemu-runner.sh | 38 ++++++++++++++++++++++++++++ 9 files changed, 183 insertions(+), 47 deletions(-) create mode 100644 examples/global_alloc_bss_heap.rs create mode 100755 qemu-runner.sh diff --git a/.cargo/config.toml b/.cargo/config.toml index 80fc334..71ef140 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,23 @@ -[target.thumbv7m-none-eabi] +[build] +# Common embedded build targets +target = "thumbv7em-none-eabihf" +# target = "thumbv6m-none-eabi" + +[target.thumbv7em-none-eabihf] # used to run the qemu_test.rs example with QEMU -runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" -rustflags = ["-C", "link-arg=-Tlink.x"] +runner = "./qemu-runner.sh --target thumbv7em-none-eabihf" +rustflags = [ + "-Clink-arg=-Tlink.x", + "-Clink-arg=-Tdefmt.x", +] + +[target.thumbv6m-none-eabi] +# used to run the qemu_test.rs example with QEMU +runner = "./qemu-runner.sh --target thumbv6m-none-eabi" +rustflags = [ + "-Clink-arg=-Tlink.x", + "-Clink-arg=-Tdefmt.x", +] + +[env] +DEFMT_LOG="info" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc72680..cb4cd38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: matrix: target: - thumbv6m-none-eabi - - thumbv7m-none-eabi + - thumbv7em-none-eabihf toolchain: - stable - nightly @@ -49,15 +49,15 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - targets: thumbv7m-none-eabi + targets: thumbv7em-none-eabihf toolchain: nightly - name: Install QEMU run: | sudo apt update sudo apt install qemu-system-arm - run: qemu-system-arm --version - - 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 + - run: cargo +nightly run --target thumbv7em-none-eabihf --example llff_integration_test --all-features + - run: cargo +nightly run --target thumbv7em-none-eabihf --example tlsf_integration_test --all-features clippy: name: Clippy @@ -88,5 +88,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly + with: + targets: thumbv7em-none-eabihf + toolchain: nightly - name: rustdoc - run: cargo rustdoc --all-features + run: cargo +nightly rustdoc --all-features diff --git a/Cargo.toml b/Cargo.toml index d23dfe6..6bd3613 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,8 +41,18 @@ 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" -cortex-m-semihosting = "0.5" -panic-semihosting = { version = "0.6", features = ["exit"] } +defmt = "1.0" +defmt-semihosting = "0.3.0" +semihosting = { version = "0.1.20", features = ["stdio"] } +static_cell = "2" + +# thumbv6m-none-eabi only +[target.thumbv6m-none-eabi.dev-dependencies] +portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] } + +# every other target gets the crate without the feature +[target.'cfg(not(target = "thumbv6m-none-eabi"))'.dev-dependencies] +portable-atomic = { version = "1" } [[example]] name = "allocator_api" diff --git a/examples/allocator_api.rs b/examples/allocator_api.rs index 10f5261..9887504 100644 --- a/examples/allocator_api.rs +++ b/examples/allocator_api.rs @@ -1,13 +1,15 @@ +//! This examples requires nightly for the allocator API. #![feature(allocator_api)] #![no_std] #![no_main] extern crate alloc; -use alloc::vec::Vec; -use core::mem::MaybeUninit; -use core::panic::PanicInfo; +use core::{mem::MaybeUninit, panic::PanicInfo}; +use cortex_m as _; use cortex_m_rt::entry; +use defmt::Debug2Format; +use defmt_semihosting as _; use embedded_alloc::LlffHeap as Heap; // This is not used, but as of 2023-10-29 allocator_api cannot be used without @@ -22,14 +24,16 @@ fn main() -> ! { let heap: Heap = Heap::empty(); unsafe { heap.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } - let mut xs = Vec::new_in(heap); - xs.push(1); + let mut vec = alloc::vec::Vec::new_in(heap); + vec.push(1); - #[allow(clippy::empty_loop)] - loop { /* .. */ } + defmt::info!("Allocated vector: {:?}", Debug2Format(&vec)); + + semihosting::process::exit(0); } #[panic_handler] -fn panic(_: &PanicInfo) -> ! { - loop {} +fn panic(info: &PanicInfo) -> ! { + defmt::error!("{}", info); + semihosting::process::exit(0); } diff --git a/examples/global_alloc.rs b/examples/global_alloc.rs index b47f0ef..84f29ca 100644 --- a/examples/global_alloc.rs +++ b/examples/global_alloc.rs @@ -3,9 +3,12 @@ extern crate alloc; -use alloc::vec::Vec; -use core::panic::PanicInfo; +use cortex_m as _; use cortex_m_rt::entry; +use defmt::Debug2Format; +use defmt_semihosting as _; + +use core::panic::PanicInfo; // Linked-List First Fit Heap allocator (feature = "llff") use embedded_alloc::LlffHeap as Heap; // Two-Level Segregated Fit Heap allocator (feature = "tlsf") @@ -21,14 +24,19 @@ fn main() -> ! { embedded_alloc::init!(HEAP, 1024); } - let mut xs = Vec::new(); - xs.push(1); + let vec = alloc::vec![1]; + + defmt::info!("Allocated vector: {:?}", Debug2Format(&vec)); + + let string = alloc::string::String::from("Hello, world!"); + + defmt::info!("Allocated string: {:?}", Debug2Format(&string)); - #[allow(clippy::empty_loop)] - loop { /* .. */ } + semihosting::process::exit(0); } #[panic_handler] -fn panic(_: &PanicInfo) -> ! { - loop {} +fn panic(info: &PanicInfo) -> ! { + defmt::error!("{}", info); + semihosting::process::exit(0); } diff --git a/examples/global_alloc_bss_heap.rs b/examples/global_alloc_bss_heap.rs new file mode 100644 index 0000000..38cddf9 --- /dev/null +++ b/examples/global_alloc_bss_heap.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use cortex_m as _; +use cortex_m_rt::entry; +use defmt::Debug2Format; +use defmt_semihosting as _; + +use core::panic::PanicInfo; +use embedded_alloc::TlsfHeap as Heap; + +#[global_allocator] +static HEAP: Heap = Heap::empty(); + +#[entry] +fn main() -> ! { + const HEAP_SIZE: usize = 16 * 1024; + static HEAP_MEM: static_cell::ConstStaticCell<[u8; HEAP_SIZE]> = + static_cell::ConstStaticCell::new([0; HEAP_SIZE]); + unsafe { + HEAP.init(HEAP_MEM.take().as_mut_ptr() as usize, HEAP_SIZE); + } + + let vec = alloc::vec![1]; + + defmt::info!("Allocated vector: {:?}", Debug2Format(&vec)); + + let string = alloc::string::String::from("Hello, world!"); + + defmt::info!("Allocated string: {:?}", Debug2Format(&string)); + + semihosting::process::exit(0); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + defmt::error!("{}", info); + semihosting::process::exit(0); +} diff --git a/examples/llff_integration_test.rs b/examples/llff_integration_test.rs index 5b7f4b2..edf3220 100644 --- a/examples/llff_integration_test.rs +++ b/examples/llff_integration_test.rs @@ -17,12 +17,15 @@ #![no_std] extern crate alloc; -extern crate panic_semihosting; use alloc::vec::Vec; -use core::mem::{size_of, MaybeUninit}; +use core::{ + mem::{size_of, MaybeUninit}, + panic::PanicInfo, +}; +use cortex_m as _; use cortex_m_rt::entry; -use cortex_m_semihosting::{debug, hprintln}; +use defmt_semihosting as _; use embedded_alloc::LlffHeap as Heap; #[global_allocator] @@ -61,26 +64,31 @@ fn test_allocator_api() { assert_eq!(v.as_slice(), &[0xCAFE, 0xDEAD, 0xFEED]); } +pub type TestTable<'a> = &'a [(fn() -> (), &'static str)]; + #[entry] fn main() -> ! { unsafe { embedded_alloc::init!(HEAP, 1024); } - #[allow(clippy::type_complexity)] - let tests: &[(fn() -> (), &'static str)] = &[ + let tests: TestTable = &[ (test_global_heap, "test_global_heap"), (test_allocator_api, "test_allocator_api"), ]; for (test_fn, test_name) in tests { - hprintln!("{}: start", test_name); + defmt::info!("{}: start", test_name); test_fn(); - hprintln!("{}: pass", test_name); + defmt::info!("{}: pass", test_name); } // exit QEMU with a success status - debug::exit(debug::EXIT_SUCCESS); - #[allow(clippy::empty_loop)] - loop {} + semihosting::process::exit(0); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + defmt::error!("{}", info); + semihosting::process::exit(-1); } diff --git a/examples/tlsf_integration_test.rs b/examples/tlsf_integration_test.rs index 591b7d3..5bf5601 100644 --- a/examples/tlsf_integration_test.rs +++ b/examples/tlsf_integration_test.rs @@ -17,18 +17,20 @@ #![no_std] extern crate alloc; -extern crate panic_semihosting; +use defmt_semihosting as _; use alloc::collections::LinkedList; -use core::mem::MaybeUninit; +use core::{mem::MaybeUninit, panic::PanicInfo}; +use cortex_m as _; 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; +pub type TestTable<'a> = &'a [(fn() -> (), &'static str)]; + fn test_global_heap() { const ELEMS: usize = 250; @@ -85,20 +87,23 @@ fn main() -> ! { embedded_alloc::init!(HEAP, HEAP_SIZE); } - #[allow(clippy::type_complexity)] - let tests: &[(fn() -> (), &'static str)] = &[ + let tests: TestTable = &[ (test_global_heap, "test_global_heap"), (test_allocator_api, "test_allocator_api"), ]; for (test_fn, test_name) in tests { - hprintln!("{}: start", test_name); + defmt::info!("{}: start", test_name); test_fn(); - hprintln!("{}: pass", test_name); + defmt::info!("{}: pass", test_name); } // exit QEMU with a success status - debug::exit(debug::EXIT_SUCCESS); - #[allow(clippy::empty_loop)] - loop {} + semihosting::process::exit(0); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + defmt::error!("{}", info); + semihosting::process::exit(-1); } diff --git a/qemu-runner.sh b/qemu-runner.sh new file mode 100755 index 0000000..f0ffe99 --- /dev/null +++ b/qemu-runner.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# This requires you to previously run `cargo install defmt-print` + +# See https://ferroussystems.hackmd.io/@jonathanpallant/ryA1S6QDJx for a description of all the relevant QEMU machines +TARGET="" +ELF_BINARY="" + +# very small argument parser +while [[ $# -gt 0 ]]; do + case "$1" in + --target) TARGET="$2"; shift 2 ;; + *) ELF_BINARY="$1"; shift ;; + esac +done + +# default to the target cargo is currently building for +TARGET="${TARGET:-thumbv7em-none-eabihf}" + +case "$TARGET" in + thumbv6m-none-eabi) + MACHINE="-cpu cortex-m3 -machine mps2-an385" ;; + thumbv7em-none-eabihf) + # All suitable for thumbv7em-none-eabihf + MACHINE="-cpu cortex-m4 -machine mps2-an386" ;; + # MACHINE="-cpu cortex-m7 -machine mps2-387" ;; + # MACHINE="-cpu cortex-m7 -machine mps2-500" + *) + echo "Unsupported target: $TARGET" >&2 + exit 1 ;; +esac + +LOG_FORMAT='{[{L}]%bold} {s} {({ff}:{l:1})%dimmed}' + +echo "Running on '$MACHINE'..." +echo "------------------------------------------------------------------------" +qemu-system-arm $MACHINE -semihosting-config enable=on,target=native -nographic -kernel $ELF_BINARY | defmt-print -e $ELF_BINARY --log-format="$LOG_FORMAT" +echo "------------------------------------------------------------------------" From 86fffaa5ba74e7ea678cea2e178e1abb4f9a7819 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 20 Nov 2025 15:37:59 +0100 Subject: [PATCH 2/7] update .cargo/config.toml file --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index 71ef140..6b3df67 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,6 +9,8 @@ runner = "./qemu-runner.sh --target thumbv7em-none-eabihf" rustflags = [ "-Clink-arg=-Tlink.x", "-Clink-arg=-Tdefmt.x", + # Can be useful for debugging and to inspect where the heap memory is placed/linked. + # "-Clink-args=-Map=app.map" ] [target.thumbv6m-none-eabi] From c3d2764632ca860881662903905acebf63ac1e4d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 20 Nov 2025 17:13:07 +0100 Subject: [PATCH 3/7] remove new example --- examples/global_alloc_bss_heap.rs | 41 ------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 examples/global_alloc_bss_heap.rs diff --git a/examples/global_alloc_bss_heap.rs b/examples/global_alloc_bss_heap.rs deleted file mode 100644 index 38cddf9..0000000 --- a/examples/global_alloc_bss_heap.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![no_std] -#![no_main] - -extern crate alloc; - -use cortex_m as _; -use cortex_m_rt::entry; -use defmt::Debug2Format; -use defmt_semihosting as _; - -use core::panic::PanicInfo; -use embedded_alloc::TlsfHeap as Heap; - -#[global_allocator] -static HEAP: Heap = Heap::empty(); - -#[entry] -fn main() -> ! { - const HEAP_SIZE: usize = 16 * 1024; - static HEAP_MEM: static_cell::ConstStaticCell<[u8; HEAP_SIZE]> = - static_cell::ConstStaticCell::new([0; HEAP_SIZE]); - unsafe { - HEAP.init(HEAP_MEM.take().as_mut_ptr() as usize, HEAP_SIZE); - } - - let vec = alloc::vec![1]; - - defmt::info!("Allocated vector: {:?}", Debug2Format(&vec)); - - let string = alloc::string::String::from("Hello, world!"); - - defmt::info!("Allocated string: {:?}", Debug2Format(&string)); - - semihosting::process::exit(0); -} - -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - defmt::error!("{}", info); - semihosting::process::exit(0); -} From 850edcec5b0788bfbaf3957f07ec67a7cde6cdfe Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 20 Nov 2025 17:16:12 +0100 Subject: [PATCH 4/7] Add TLSF memory usage API and update API naming - Added `Heap::free` and `Heap::used` for the TLSF heap. - Added example showcasing regular heap exhaustion and example which shows the memory usage API. --- CHANGELOG.md | 1 + Cargo.toml | 2 +- examples/allocator_api.rs | 2 +- examples/exhaustion.rs | 38 +++++++++++++++++++++++++ examples/global_alloc.rs | 2 +- examples/track_usage.rs | 54 +++++++++++++++++++++++++++++++++++ src/tlsf.rs | 59 +++++++++++++++++++++++++++++++++------ 7 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 examples/exhaustion.rs create mode 100644 examples/track_usage.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0921f25..cd95d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Added a `init` macro to make initialization easier. +- Added `Heap::free` and `Heap::used` for the TLSF heap. ### Changed diff --git a/Cargo.toml b/Cargo.toml index 6bd3613..6a5e0b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ 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 } +rlsf = { version = "0.2.1", default-features = false, features = ["unstable"], optional = true } const-default = { version = "1.0.0", default-features = false, optional = true } [dev-dependencies] diff --git a/examples/allocator_api.rs b/examples/allocator_api.rs index 9887504..7c3eab0 100644 --- a/examples/allocator_api.rs +++ b/examples/allocator_api.rs @@ -35,5 +35,5 @@ fn main() -> ! { #[panic_handler] fn panic(info: &PanicInfo) -> ! { defmt::error!("{}", info); - semihosting::process::exit(0); + semihosting::process::exit(-1); } diff --git a/examples/exhaustion.rs b/examples/exhaustion.rs new file mode 100644 index 0000000..6210c6b --- /dev/null +++ b/examples/exhaustion.rs @@ -0,0 +1,38 @@ +//! Example which shows behavior on pool exhaustion. It simply panics. +#![no_std] +#![no_main] + +extern crate alloc; + +use cortex_m as _; +use cortex_m_rt::entry; +use defmt::Debug2Format; +use defmt_semihosting as _; + +use core::panic::PanicInfo; +use embedded_alloc::TlsfHeap as Heap; + +#[global_allocator] +static HEAP: Heap = Heap::empty(); + +#[entry] +fn main() -> ! { + // Initialize the allocator BEFORE you use it + unsafe { + embedded_alloc::init!(HEAP, 16); + } + + let _vec = alloc::vec![0; 16]; + + defmt::error!("unexpected vector allocation success"); + + // Panic is expected here. + semihosting::process::exit(-1); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + defmt::warn!("received expected heap exhaustion panic"); + defmt::warn!("{}: {}", info, Debug2Format(&info.message())); + semihosting::process::exit(0); +} diff --git a/examples/global_alloc.rs b/examples/global_alloc.rs index 84f29ca..f4cd545 100644 --- a/examples/global_alloc.rs +++ b/examples/global_alloc.rs @@ -38,5 +38,5 @@ fn main() -> ! { #[panic_handler] fn panic(info: &PanicInfo) -> ! { defmt::error!("{}", info); - semihosting::process::exit(0); + semihosting::process::exit(-1); } diff --git a/examples/track_usage.rs b/examples/track_usage.rs new file mode 100644 index 0000000..272fbca --- /dev/null +++ b/examples/track_usage.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use cortex_m as _; +use cortex_m_rt::entry; +use defmt::Debug2Format; +use defmt_semihosting as _; + +use core::{mem::MaybeUninit, panic::PanicInfo}; +use embedded_alloc::TlsfHeap as Heap; +//use embedded_alloc::LlffHeap as Heap; + +#[global_allocator] +static HEAP: Heap = Heap::empty(); + +#[entry] +fn main() -> ! { + // Initialize the allocator BEFORE you use it + const HEAP_SIZE: usize = 4096; + static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } + + let mut long_vec = alloc::vec::Vec::new(); + let mut free_memory = HEAP_SIZE; + // Keep allocating until we are getting low on memory. It doesn't have to end in a panic. + while free_memory > 512 { + defmt::info!( + "{} of {} heap memory allocated so far...", + HEAP_SIZE - free_memory, + HEAP_SIZE + ); + long_vec.push([1; 16].to_vec()); + free_memory = HEAP.free(); + } + + drop(long_vec); + + defmt::warn!( + "{} of {} heap memory are allocated after drop", + HEAP_SIZE - HEAP.free(), + HEAP_SIZE + ); + + // Panic is expected here. + semihosting::process::exit(0); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + defmt::error!("{}: {}", info, Debug2Format(&info.message())); + semihosting::process::exit(-1); +} diff --git a/src/tlsf.rs b/src/tlsf.rs index 37935d1..232f00f 100644 --- a/src/tlsf.rs +++ b/src/tlsf.rs @@ -8,9 +8,20 @@ use rlsf::Tlsf; type TlsfHeap = Tlsf<'static, usize, usize, { usize::BITS as usize }, { usize::BITS as usize }>; +struct Inner { + tlsf: TlsfHeap, + initialized: bool, + raw_block: Option>, + raw_block_size: usize, +} + +// Safety: The whole inner type is wrapped by a [Mutex]. +unsafe impl Sync for Inner {} +unsafe impl Send for Inner {} + /// A two-Level segregated fit heap. pub struct Heap { - heap: Mutex>, + heap: Mutex>, } impl Heap { @@ -20,7 +31,12 @@ impl Heap { /// [`init`](Self::init) method before using the allocator. pub const fn empty() -> Heap { Heap { - heap: Mutex::new(RefCell::new((ConstDefault::DEFAULT, false))), + heap: Mutex::new(RefCell::new(Inner { + tlsf: ConstDefault::DEFAULT, + initialized: false, + raw_block: None, + raw_block_size: 0, + })), } } @@ -59,25 +75,52 @@ impl Heap { 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); - heap.0.insert_free_block_ptr(block.into()); + assert!(!heap.initialized); + heap.initialized = true; + let block: NonNull<[u8]> = + NonNull::slice_from_raw_parts(NonNull::new_unchecked(start_addr as *mut u8), size); + heap.tlsf.insert_free_block_ptr(block); + heap.raw_block = Some(block); + heap.raw_block_size = size; }); } fn alloc(&self, layout: Layout) -> Option> { - critical_section::with(|cs| self.heap.borrow_ref_mut(cs).0.allocate(layout)) + critical_section::with(|cs| self.heap.borrow_ref_mut(cs).tlsf.allocate(layout)) } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { critical_section::with(|cs| { self.heap .borrow_ref_mut(cs) - .0 + .tlsf .deallocate(NonNull::new_unchecked(ptr), layout.align()) }) } + + /// Get the amount of bytes used by the allocator. + pub fn used(&self) -> usize { + critical_section::with(|cs| { + self.heap.borrow_ref_mut(cs).raw_block_size - self.free_with_cs(cs) + }) + } + + /// Get the amount of free bytes in the allocator. + pub fn free(&self) -> usize { + critical_section::with(|cs| self.free_with_cs(cs)) + } + + fn free_with_cs(&self, cs: critical_section::CriticalSection) -> usize { + let inner_mut = self.heap.borrow_ref_mut(cs); + unsafe { + inner_mut + .tlsf + .iter_blocks(inner_mut.raw_block.unwrap()) + .filter(|block_info| !block_info.is_occupied()) + .map(|block_info| block_info.max_payload_size()) + .sum::() + } + } } unsafe impl GlobalAlloc for Heap { From 1393a02b366c7196debeb99b7305c19e3a4e37b9 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 20 Nov 2025 17:35:25 +0100 Subject: [PATCH 5/7] early return for un-initialized heap --- src/tlsf.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tlsf.rs b/src/tlsf.rs index 232f00f..a30d70e 100644 --- a/src/tlsf.rs +++ b/src/tlsf.rs @@ -112,6 +112,9 @@ impl Heap { fn free_with_cs(&self, cs: critical_section::CriticalSection) -> usize { let inner_mut = self.heap.borrow_ref_mut(cs); + if !inner_mut.initialized { + return 0; + } unsafe { inner_mut .tlsf From 82f7f5dc7bbedf533a8a7e0c91d3a1030530d04f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 20 Nov 2025 17:45:25 +0100 Subject: [PATCH 6/7] this is a bit cleaner --- examples/track_usage.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/track_usage.rs b/examples/track_usage.rs index 272fbca..c6f9a22 100644 --- a/examples/track_usage.rs +++ b/examples/track_usage.rs @@ -22,7 +22,7 @@ fn main() -> ! { static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } - let mut long_vec = alloc::vec::Vec::new(); + let mut alloc_vecs = alloc::vec::Vec::new(); let mut free_memory = HEAP_SIZE; // Keep allocating until we are getting low on memory. It doesn't have to end in a panic. while free_memory > 512 { @@ -31,11 +31,12 @@ fn main() -> ! { HEAP_SIZE - free_memory, HEAP_SIZE ); - long_vec.push([1; 16].to_vec()); + let new_vec = alloc::vec![1_u8; 64]; + alloc_vecs.push(new_vec); free_memory = HEAP.free(); } - drop(long_vec); + drop(alloc_vecs); defmt::warn!( "{} of {} heap memory are allocated after drop", From 815fce43e106d1a686b7f608bf62f3f3d9650153 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 20 Nov 2025 17:52:06 +0100 Subject: [PATCH 7/7] minor tweaks, safety note --- examples/track_usage.rs | 3 +-- src/tlsf.rs | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/track_usage.rs b/examples/track_usage.rs index c6f9a22..d82d005 100644 --- a/examples/track_usage.rs +++ b/examples/track_usage.rs @@ -38,13 +38,12 @@ fn main() -> ! { drop(alloc_vecs); - defmt::warn!( + defmt::info!( "{} of {} heap memory are allocated after drop", HEAP_SIZE - HEAP.free(), HEAP_SIZE ); - // Panic is expected here. semihosting::process::exit(0); } diff --git a/src/tlsf.rs b/src/tlsf.rs index a30d70e..50cea76 100644 --- a/src/tlsf.rs +++ b/src/tlsf.rs @@ -115,6 +115,8 @@ impl Heap { if !inner_mut.initialized { return 0; } + // Safety: We pass the memory block we previously initialized the heap with + // to the `iter_blocks` method. unsafe { inner_mut .tlsf