Skip to content

Commit 92c9704

Browse files
committed
Add a simple heap
1 parent 8437c09 commit 92c9704

File tree

6 files changed

+259
-2
lines changed

6 files changed

+259
-2
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ jobs:
5858
- run: qemu-system-arm --version
5959
- run: cargo run --target thumbv7m-none-eabi --example llff_integration_test --all-features
6060
- run: cargo run --target thumbv7m-none-eabi --example tlsf_integration_test --all-features
61+
- run: cargo run --target thumbv7m-none-eabi --example simplest_integration_test --all-features
6162

6263
clippy:
6364
name: Clippy

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ name = "embedded-alloc"
2424
version = "0.6.0"
2525

2626
[features]
27-
default = ["llff", "tlsf"]
27+
default = ["llff", "tlsf", "simplest"]
2828
allocator_api = []
2929

3030
# Use the Two-Level Segregated Fit allocator
3131
tlsf = ["rlsf", "const-default"]
32-
# Use the LinkedList first-fit allocator
32+
# Use the LinkedList first-fit allocator
3333
llff = ["linked_list_allocator"]
34+
# Use the simplest allocator
35+
simplest = []
3436

3537
[dependencies]
3638
critical-section = "1.0"
@@ -56,6 +58,10 @@ required-features = ["allocator_api", "llff"]
5658
name = "tlsf_integration_test"
5759
required-features = ["allocator_api", "tlsf"]
5860

61+
[[example]]
62+
name = "simplest_integration_test"
63+
required-features = ["allocator_api", "simplest"]
64+
5965
[[example]]
6066
name = "global_alloc"
6167
required-features = ["llff"]

examples/global_alloc.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use cortex_m_rt::entry;
1010
use embedded_alloc::LlffHeap as Heap;
1111
// Two-Level Segregated Fit Heap allocator (feature = "tlsf")
1212
// use embedded_alloc::TlsfHeap as Heap;
13+
// use embedded_alloc::SimplestHeap as Heap;
1314

1415
#[global_allocator]
1516
static HEAP: Heap = Heap::empty();

examples/simplest_integration_test.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//! This is a very basic smoke test that runs in QEMU
2+
//! Reference the QEMU section of the [Embedded Rust Book] for more information
3+
//!
4+
//! This only tests integration of the allocator on an embedded target.
5+
//! Comprehensive allocator tests are located in the allocator dependency.
6+
//!
7+
//! After toolchain installation this test can be run with:
8+
//!
9+
//! ```bash
10+
//! cargo +nightly run --target thumbv7m-none-eabi --example simplest_integration_test --all-features
11+
//! ```
12+
//!
13+
//! [Embedded Rust Book]: https://docs.rust-embedded.org/book/intro/index.html
14+
15+
#![feature(allocator_api)]
16+
#![no_main]
17+
#![no_std]
18+
19+
extern crate alloc;
20+
extern crate panic_semihosting;
21+
22+
use alloc::collections::LinkedList;
23+
use core::mem::MaybeUninit;
24+
use cortex_m_rt::entry;
25+
use cortex_m_semihosting::{debug, hprintln};
26+
use embedded_alloc::SimplestHeap as Heap;
27+
28+
#[global_allocator]
29+
static HEAP: Heap = Heap::empty();
30+
const HEAP_SIZE: usize = 30 * 1024;
31+
32+
fn test_global_heap() {
33+
const ELEMS: usize = 250;
34+
35+
let mut allocated = LinkedList::new();
36+
for _ in 0..ELEMS {
37+
allocated.push_back(0);
38+
}
39+
for i in 0..ELEMS {
40+
allocated.push_back(i as i32);
41+
}
42+
43+
assert_eq!(allocated.len(), 2 * ELEMS);
44+
45+
for _ in 0..ELEMS {
46+
allocated.pop_front();
47+
}
48+
49+
for i in 0..ELEMS {
50+
assert_eq!(allocated.pop_front().unwrap(), i as i32);
51+
}
52+
}
53+
54+
fn test_allocator_api() {
55+
// small local heap
56+
const HEAP_SIZE: usize = 256;
57+
let mut heap_mem: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
58+
let local_heap: Heap = Heap::empty();
59+
unsafe { local_heap.init(heap_mem.as_mut_ptr() as usize, HEAP_SIZE) }
60+
61+
const ELEMS: usize = 2;
62+
63+
let mut allocated = LinkedList::new_in(local_heap);
64+
for _ in 0..ELEMS {
65+
allocated.push_back(0);
66+
}
67+
for i in 0..ELEMS {
68+
allocated.push_back(i as i32);
69+
}
70+
71+
assert_eq!(allocated.len(), 2 * ELEMS);
72+
73+
for _ in 0..ELEMS {
74+
allocated.pop_front();
75+
}
76+
77+
for i in 0..ELEMS {
78+
assert_eq!(allocated.pop_front().unwrap(), i as i32);
79+
}
80+
}
81+
82+
#[entry]
83+
fn main() -> ! {
84+
{
85+
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
86+
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
87+
}
88+
89+
#[allow(clippy::type_complexity)]
90+
let tests: &[(fn() -> (), &'static str)] = &[
91+
(test_global_heap, "test_global_heap"),
92+
(test_allocator_api, "test_allocator_api"),
93+
];
94+
95+
for (test_fn, test_name) in tests {
96+
hprintln!("{}: start", test_name);
97+
test_fn();
98+
hprintln!("{}: pass", test_name);
99+
}
100+
101+
// exit QEMU with a success status
102+
debug::exit(debug::EXIT_SUCCESS);
103+
#[allow(clippy::empty_loop)]
104+
loop {}
105+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55

66
#[cfg(feature = "llff")]
77
mod llff;
8+
#[cfg(feature = "simplest")]
9+
mod simplest;
810
#[cfg(feature = "tlsf")]
911
mod tlsf;
1012

1113
#[cfg(feature = "llff")]
1214
pub use llff::Heap as LlffHeap;
15+
#[cfg(feature = "simplest")]
16+
pub use simplest::Heap as SimplestHeap;
1317
#[cfg(feature = "tlsf")]
1418
pub use tlsf::Heap as TlsfHeap;

src/simplest.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
use core::alloc::{GlobalAlloc, Layout};
2+
use core::cell::RefCell;
3+
use core::ptr;
4+
use critical_section::Mutex;
5+
6+
/// The simplest possible heap.
7+
///
8+
/// # Safety
9+
///
10+
/// This heap does **NOT** free allocated memory.
11+
pub struct Heap {
12+
heap: Mutex<RefCell<SimplestHeap>>,
13+
}
14+
15+
impl Heap {
16+
/// Create a new UNINITIALIZED heap allocator
17+
///
18+
/// You must initialize this heap using the
19+
/// [`init`](Self::init) method before using the allocator.
20+
pub const fn empty() -> Heap {
21+
Heap {
22+
heap: Mutex::new(RefCell::new(SimplestHeap::empty())),
23+
}
24+
}
25+
26+
/// Initializes the heap
27+
///
28+
/// This function must be called BEFORE you run any code that makes use of the
29+
/// allocator.
30+
///
31+
/// `start_addr` is the address where the heap will be located.
32+
///
33+
/// `size` is the size of the heap in bytes.
34+
///
35+
/// # Safety
36+
///
37+
/// Obey these or Bad Stuff will happen.
38+
///
39+
/// - This function must be called exactly ONCE.
40+
/// - `size > 0`
41+
pub unsafe fn init(&self, start_addr: usize, size: usize) {
42+
critical_section::with(|cs| {
43+
self.heap
44+
.borrow(cs)
45+
.borrow_mut()
46+
.init(start_addr as *mut u8, size);
47+
});
48+
}
49+
50+
/// Returns an estimate of the amount of bytes in use.
51+
pub fn used(&self) -> usize {
52+
critical_section::with(|cs| self.heap.borrow(cs).borrow().used())
53+
}
54+
55+
/// Returns an estimate of the amount of bytes available.
56+
pub fn free(&self) -> usize {
57+
critical_section::with(|cs| self.heap.borrow(cs).borrow().free())
58+
}
59+
}
60+
61+
unsafe impl GlobalAlloc for Heap {
62+
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
63+
critical_section::with(|cs| self.heap.borrow(cs).borrow_mut().alloc(layout))
64+
}
65+
66+
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
67+
}
68+
69+
#[cfg(feature = "allocator_api")]
70+
mod allocator_api {
71+
use super::*;
72+
use core::alloc::{AllocError, Allocator};
73+
use core::ptr::NonNull;
74+
75+
unsafe impl Allocator for Heap {
76+
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
77+
match layout.size() {
78+
0 => Ok(NonNull::slice_from_raw_parts(layout.dangling(), 0)),
79+
size => critical_section::with(|cs| {
80+
let rst = self.heap.borrow(cs).borrow_mut().alloc(layout);
81+
if rst == ptr::null_mut() {
82+
Err(AllocError)
83+
} else {
84+
Ok(NonNull::new(rst))
85+
}
86+
}),
87+
}
88+
}
89+
90+
unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
91+
}
92+
}
93+
94+
struct SimplestHeap {
95+
arena: *mut u8,
96+
remaining: usize,
97+
size: usize,
98+
}
99+
100+
unsafe impl Send for SimplestHeap {}
101+
102+
impl SimplestHeap {
103+
const fn empty() -> Self {
104+
Self {
105+
arena: ptr::null_mut(),
106+
remaining: 0,
107+
size: 0,
108+
}
109+
}
110+
111+
fn init(&mut self, start_addr: *mut u8, size: usize) -> Self {
112+
Self {
113+
arena: start_addr,
114+
remaining: size,
115+
size,
116+
}
117+
}
118+
119+
fn free(&self) -> usize {
120+
self.remaining
121+
}
122+
123+
fn used(&self) -> usize {
124+
self.size - self.remaining
125+
}
126+
127+
fn alloc(&mut self, layout: Layout) -> *mut u8 {
128+
if layout.size() > self.remaining {
129+
return ptr::null_mut();
130+
}
131+
132+
// `Layout` contract forbids making a `Layout` with align=0, or align not power of 2.
133+
// So we can safely use a mask to ensure alignment without worrying about UB.
134+
let align_mask_to_round_down = !(layout.align() - 1);
135+
136+
self.remaining -= layout.size();
137+
self.remaining &= align_mask_to_round_down;
138+
self.arena.wrapping_add(self.remaining)
139+
}
140+
}

0 commit comments

Comments
 (0)