From cc214c54912c2980399bf97cf44876b4bf46b9aa Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 19 Sep 2025 16:42:33 -1000 Subject: [PATCH 1/3] Add stack measurement tooling --- cortex-m-stack/Cargo.toml | 11 +++++ cortex-m-stack/src/lib.rs | 88 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 cortex-m-stack/Cargo.toml create mode 100644 cortex-m-stack/src/lib.rs diff --git a/cortex-m-stack/Cargo.toml b/cortex-m-stack/Cargo.toml new file mode 100644 index 0000000..357e5c7 --- /dev/null +++ b/cortex-m-stack/Cargo.toml @@ -0,0 +1,11 @@ +[package] +edition = "2021" +name = "cortex-m-stack" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[lib] +test = false + +[dependencies] +cortex-m = { version = "0.7.6" } diff --git a/cortex-m-stack/src/lib.rs b/cortex-m-stack/src/lib.rs new file mode 100644 index 0000000..b8f2540 --- /dev/null +++ b/cortex-m-stack/src/lib.rs @@ -0,0 +1,88 @@ +#![no_std] +#![no_main] + +// TODO Doc + +use core::arch::asm; + +/// This function grabs the current stack pointer +#[inline(always)] +fn stack_ptr() -> *const u32 { + let x: *const u32; + unsafe { + asm!( + "mov {0}, sp" , + out(reg) x, + options(pure, nomem, nostack), + ); + } + x +} + +extern "C" { + static _stack_start: u32; + static _stack_end: u32; +} + +const STACK_PAINT_VALUE: u32 = 0xcccccccc; + +/// This function reads the paint that has been installed by cortex-m-rt and by any calls to +/// repaint(). It starts at the end of the stack and reads until it finds a location where the +/// referenced memory is not equal to STACK_PAINT_VALUE. In other words, it reports the highest +/// memory location such that it and all locations below it are equal to STACK_PAINT_VALUE. +#[inline(always)] +unsafe fn high_water_mark() -> *const u32 { + let stack_pointer = stack_ptr(); + let stack_end = &_stack_end as *const u32; + let mut curr = stack_end.offset(1); + while curr.read_volatile() == STACK_PAINT_VALUE && curr < stack_pointer { + curr = curr.offset(1); + } + curr.offset(-1) +} + +/// This function reports the maximum stack usage at any point up to the current moment, as the +/// difference between the high water mark and the start of the stack. +#[inline(always)] +pub fn usage() -> usize { + cortex_m::interrupt::free(|_cs| { + let stack_start = unsafe { &_stack_start as *const u32 }; + let hwm = unsafe { high_water_mark() }; + (stack_start as usize) - (hwm as usize) + }) +} + +/// This function "repaints" the stack, starting from after the current stack. It fills the entire +/// remainder of main memory with STACK_PAINT_VALUE, so that calling usage() thereafter will report +/// incremental memory usage. In other words, this function resets the high water mark to the +/// current top of the stack. +#[inline(always)] +pub fn repaint() { + cortex_m::interrupt::free(|_cs| unsafe { + let sp = stack_ptr(); + let hwm = high_water_mark(); + + let mut curr = sp.offset(-1) as *mut u32; + while (curr as *const u32) > hwm { + curr.write_volatile(STACK_PAINT_VALUE); + curr = curr.offset(-1); + } + }); +} + +/// This function measures the stack usage of a specific function by repainting memory from the +/// current stack pointer, then running the function, then measuring how the high-water mark has +/// changed. +#[inline(never)] +pub fn measure(f: F) -> usize +where + F: Fn(), +{ + cortex_m::interrupt::free(|_cs| { + repaint(); + let usage_before = usage(); + f(); + let usage_after = usage(); + usage_after - usage_before + }) +} From 7ff81f4eb6e1da1fb9b144b5547d5a2725a4c28b Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 19 Sep 2025 16:45:42 -1000 Subject: [PATCH 2/3] Add CI for the cortex-m-stack crate --- .github/workflows/stm32.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/stm32.yaml b/.github/workflows/stm32.yaml index b66d157..9e645f5 100644 --- a/.github/workflows/stm32.yaml +++ b/.github/workflows/stm32.yaml @@ -32,6 +32,13 @@ jobs: components: clippy, rustfmt target: thumbv7em-none-eabihf + - name: Verify the cortex-m-stack crate + uses: ./.github/actions/check-crate + with: + path: ./cortex-m-stack + build-tests: false + run-tests: false + - name: Verify the ui-stm32 crate (ev12) uses: ./.github/actions/check-crate with: From 9a31a6d2e492cb02c85bec9e5990aca4bd3074fd Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 19 Sep 2025 16:54:01 -1000 Subject: [PATCH 3/3] Improved documentation --- cortex-m-stack/src/lib.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/cortex-m-stack/src/lib.rs b/cortex-m-stack/src/lib.rs index b8f2540..a5ca4b1 100644 --- a/cortex-m-stack/src/lib.rs +++ b/cortex-m-stack/src/lib.rs @@ -1,7 +1,18 @@ #![no_std] #![no_main] -// TODO Doc +//! # cortex-m-stack +//! +//! This crate is a small addendum to the cortex-m crate's [`paint-stack` +//! feature](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/#paint-stack). That feature enables +//! stack measurement by filling the stack with the fixed value `STACK_PAINT_VALUE = 0xCCCCCCCC`. +//! The tools in this crate allow you to read out stack usage at runtime, by identifying the +//! high-water mark where the stack paint value ends -- whatever has been changed is stack that has +//! been used. +//! +//! We also provide a `repaint()` function that resets memory below the current stack to +//! `STACK_PAINT_VALUE`, and a `measure()` function that measures the stack usage of a function +//! relative to the current function. use core::arch::asm; @@ -24,7 +35,7 @@ extern "C" { static _stack_end: u32; } -const STACK_PAINT_VALUE: u32 = 0xcccccccc; +const STACK_PAINT_VALUE: u32 = 0xCCCCCCCC; /// This function reads the paint that has been installed by cortex-m-rt and by any calls to /// repaint(). It starts at the end of the stack and reads until it finds a location where the @@ -41,8 +52,11 @@ unsafe fn high_water_mark() -> *const u32 { curr.offset(-1) } -/// This function reports the maximum stack usage at any point up to the current moment, as the -/// difference between the high water mark and the start of the stack. +/// Measure stack usage up to this point +/// +/// This function reports the maximum stack usage at any point up to the current moment (since +/// start or since the last repaint), as the difference between the high water mark and the start +/// of the stack. #[inline(always)] pub fn usage() -> usize { cortex_m::interrupt::free(|_cs| { @@ -52,6 +66,8 @@ pub fn usage() -> usize { }) } +/// Repaint memory below the current stack frame +/// /// This function "repaints" the stack, starting from after the current stack. It fills the entire /// remainder of main memory with STACK_PAINT_VALUE, so that calling usage() thereafter will report /// incremental memory usage. In other words, this function resets the high water mark to the @@ -70,6 +86,8 @@ pub fn repaint() { }); } +/// Measure the stack usage of a function +/// /// This function measures the stack usage of a specific function by repainting memory from the /// current stack pointer, then running the function, then measuring how the high-water mark has /// changed.