riscv_stack/
lib.rs

1#![no_std]
2#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
3
4use core::{arch::asm, mem::size_of, ops::Range};
5
6/// The value used to paint the stack.
7pub const STACK_PAINT_VALUE: u32 = 0xCCCC_CCCC;
8
9/// The [Range] currently in use for the current hart's stack.
10///
11/// Note: the stack is defined in reverse, as it runs from 'start' to 'end' downwards.
12/// Hence this range is technically empty because `start >= end`.
13///
14/// *Important*: `end` represents one past the last value in the stack belonging to current hart,
15/// so do not attempt to write to it, as you'd overwrite the start of stack of another hart.
16///
17/// If you want to use this range to do range-like things, use [stack_rev] instead.
18#[inline]
19pub fn stack() -> Range<*mut u32> {
20    unsafe extern "C" {
21        static mut _stack_start: u32;
22        static _hart_stack_size: usize;
23    }
24
25    // Current hart's ID
26    let hartid: usize;
27    // SAFETY: We are just reading from a CSR
28    unsafe { asm!("csrr {}, mhartid", out(reg) hartid) };
29
30    // The _hart_stack_size symbol's value, which is the size obviously,
31    // is represented by the address of the symbol.
32    //
33    // So we have to first make a fake pointer then treat it as an actual usize.
34    let stksz = &raw const _hart_stack_size as usize;
35
36    // Each hart has equal (_hart_stack_size) stack sizes.
37    //
38    // Thus the Nth hart's stack can be found by offsetting from the very top of stack
39    // down to _hart_stack_size * hartid.
40    //
41    // The below safety requirements would only end up violated if linker script is incorrect.
42    // The linker script from `riscv-rt` should satisfy these requirements.
43    //
44    // SAFETY: Linker script must ensure that `_stack_start - (_hart_stack_size * hartid)`
45    // is always within the available stack space.
46    let start = unsafe { (&raw mut _stack_start).byte_sub(hartid * stksz) };
47    // SAFETY: Linker script must also ensure that the above address, offset downward by another
48    // _hart_stack_size, is always within the available stack space and does not interfere
49    // with another hart's stack.
50    let end = unsafe { start.byte_sub(stksz) };
51
52    // But we want to ensure boundaries are 4 byte aligned before dereferencing them.
53    //
54    // So different harts will actually have slightly different stack sizes depending
55    // on if _hart_stack_size is divisble by 4 or not.
56    let start = start.map_addr(|p| p & !0b11);
57    let end = end.map_addr(|p| p & !0b11);
58
59    start..end
60}
61
62/// The [Range] currently in use for the current hart's stack,
63/// defined in reverse such that [Range] operations are viable.
64///
65/// Hence the `end` of this [Range] is one past where the current hart's stack starts,
66/// so it is important not to write to it, otherwise the end of another hart's stack may be overwritten.
67#[inline]
68pub fn stack_rev() -> Range<*mut u32> {
69    // SAFETY: Range states start <= x < end, so when reversing, we add 1 to each bound
70    // to keep meaning
71    unsafe { stack().end.add(1)..stack().start.add(1) }
72}
73
74/// Convenience function to fetch the current hart's stack pointer.
75#[inline]
76pub fn current_stack_ptr() -> *mut u32 {
77    let res;
78    // SAFETY: Just reading the stack pointer nothing crazy
79    unsafe { asm!("mv {}, sp", out(reg) res) };
80    res
81}
82
83/// The number of bytes that are reserved for the current hart's stack at compile time.
84///
85/// Note: Although all harts have equal stack space reserved, their effective stack space
86/// may differ slightly due to alignment issues.
87#[inline]
88pub fn stack_size() -> usize {
89    // SAFETY: start >= end. If this is not the case your linker did something wrong.
90    unsafe { stack().start.byte_offset_from_unsigned(stack().end) }
91}
92
93/// The number of bytes of the current hart's stack that are currently in use.
94#[inline]
95pub fn current_stack_in_use() -> usize {
96    // SAFETY: start >= end. If this is not the case your linker did something wrong.
97    unsafe { stack().start.byte_offset_from_unsigned(current_stack_ptr()) }
98}
99
100/// The number of bytes of the current hart's stack that are currently free.
101///
102/// If the stack has overflowed, this function returns 0.
103#[inline]
104pub fn current_stack_free() -> usize {
105    stack_size().saturating_sub(current_stack_in_use())
106}
107
108/// What fraction of the current hart's stack is currently in use.
109#[inline]
110pub fn current_stack_fraction() -> f32 {
111    current_stack_in_use() as f32 / stack_size() as f32
112}
113
114/// Paint the part of the current hart's stack that is currently not in use.
115///
116/// **Note:** this can take some time, and an ISR could possibly interrupt this process,
117/// dirtying up your freshly painted stack.
118/// If you wish to prevent this, run this inside a critical section using `riscv::interrupt::free`.
119///
120/// Runs in *O(n)* where *n* is the size of the stack.
121/// This function is inefficient in the sense that it repaints the entire stack,
122/// even the parts that still have the [STACK_PAINT_VALUE].
123#[inline(never)]
124pub fn repaint_stack() {
125    // SAFETY: `stack()` has ensured we are staying within the bounds of the current hart's stack
126    unsafe {
127        asm!(
128            "0:",
129            "bgeu {ptr}, sp, 1f",
130            "sw {paint}, 0({ptr})",
131            "addi {ptr}, {ptr}, 4",
132            "j 0b",
133            "1:",
134            ptr = inout(reg) stack().end.add(1) => _,
135            paint = in(reg) STACK_PAINT_VALUE,
136        )
137    };
138}
139
140/// Finds the number of bytes that have not been overwritten on the current hart's stack since the last repaint.
141///
142/// In other words: shows the worst case free stack space since [repaint_stack] was last called.
143///
144/// This measurement can only ever be an ESTIMATE, and not a guarantee, as the amount of
145/// stack can change immediately, even during an interrupt while we are measuring, or
146/// by a devious user or compiler that re-paints the stack, obscuring the max
147/// measured value. This measurement MUST NOT be used for load-bearing-safety
148/// guarantees, only as a (generally accurate but non-guaranteed) measurement.
149///
150/// Runs in *O(n)* where *n* is the size of the stack.
151#[inline(never)]
152pub fn stack_painted() -> usize {
153    let res: *const u32;
154    // SAFETY: As per the [rust reference], inline asm is allowed to look below the
155    // stack pointer. We read the values between the end of stack and the current stack
156    // pointer, which are all valid locations.
157    //
158    // In the case of interruption, there could be false negatives where we don't see
159    // stack that was used "behind" our cursor, however this is fine because we do not
160    // rely on this number for any safety-bearing contents, only as a metrics estimate.
161    //
162    // [rust reference]: https://doc.rust-lang.org/reference/inline-assembly.html#r-asm.rules.stack-below-sp
163    unsafe {
164        asm!(
165            "0:",
166            "bgeu {ptr}, sp, 1f",
167            "lw {value}, 0({ptr})",
168            "bne {value}, {paint}, 1f",
169            "addi {ptr}, {ptr}, 4",
170            "j 0b",
171            "1:",
172            ptr = inout(reg) stack().end.add(1) => res,
173            value = out(reg) _,
174            paint = in(reg) STACK_PAINT_VALUE,
175            options(nostack, readonly)
176        )
177    };
178    // SAFETY: res >= stack.end() because we start at stack.end()
179    unsafe { res.byte_offset_from_unsigned(stack().end) }
180}
181
182/// Finds the number of bytes that have not been overwritten on the current hart's stack since the last repaint using binary search.
183///
184/// In other words: shows the worst case free stack space since [repaint_stack] was last called.
185///
186/// Uses binary search to find the point after which the stack is written.
187/// This will assume that the stack is written in a consecutive fashion.
188/// Writing somewhere out-of-order into the painted stack will not be detected.
189///
190/// Runs in *O(log(n))* where *n* is the size of the stack.
191///
192/// **Danger:** if the current (active) stack contains the [STACK_PAINT_VALUE] this computation may be very incorrect.
193///
194/// # Safety
195///
196/// This function aliases the inactive stack, which is considered to be Undefined Behaviour.
197/// Do not use if you care about such things.
198pub unsafe fn stack_painted_binary() -> usize {
199    // SAFETY: we should be able to read anywhere on the stack using this,
200    // but this is considered UB because we are aliasing memory out of nowhere.
201    // Will probably still work though.
202    let slice =
203        unsafe { &*core::ptr::slice_from_raw_parts(stack().end.add(1), current_stack_free() / 4) };
204    slice.partition_point(|&word| word == STACK_PAINT_VALUE) * size_of::<usize>()
205}