Skip to content

Commit 51c44ea

Browse files
committed
Add the simplest heap
1 parent 8437c09 commit 51c44ea

File tree

6 files changed

+237
-2
lines changed

6 files changed

+237
-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: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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::vec::Vec;
23+
use core::mem::{size_of, 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+
31+
fn test_global_heap() {
32+
assert_eq!(HEAP.used(), 0);
33+
34+
let mut xs: Vec<i32> = alloc::vec![1];
35+
xs.push(2);
36+
xs.extend(&[3, 4]);
37+
38+
// do not optimize xs
39+
core::hint::black_box(&mut xs);
40+
41+
assert_eq!(xs.as_slice(), &[1, 2, 3, 4]);
42+
assert!(HEAP.used() >= size_of::<i32>() * xs.len());
43+
}
44+
45+
fn test_allocator_api() {
46+
// small local heap
47+
const HEAP_SIZE: usize = 16;
48+
let mut heap_mem: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
49+
let local_heap: Heap = Heap::empty();
50+
unsafe { local_heap.init(&raw mut heap_mem as usize, HEAP_SIZE) }
51+
52+
assert_eq!(local_heap.used(), 0);
53+
54+
let mut v: Vec<u16, Heap> = Vec::new_in(local_heap);
55+
v.push(0xCAFE);
56+
v.extend(&[0xDEAD, 0xFEED]);
57+
58+
// do not optimize v
59+
core::hint::black_box(&mut v);
60+
61+
assert_eq!(v.as_slice(), &[0xCAFE, 0xDEAD, 0xFEED]);
62+
}
63+
64+
#[entry]
65+
fn main() -> ! {
66+
{
67+
const HEAP_SIZE: usize = 1024;
68+
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
69+
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
70+
}
71+
72+
#[allow(clippy::type_complexity)]
73+
let tests: &[(fn() -> (), &'static str)] = &[
74+
(test_global_heap, "test_global_heap"),
75+
(test_allocator_api, "test_allocator_api"),
76+
];
77+
78+
for (test_fn, test_name) in tests {
79+
hprintln!("{}: start", test_name);
80+
test_fn();
81+
hprintln!("{}: pass", test_name);
82+
}
83+
84+
// exit QEMU with a success status
85+
debug::exit(debug::EXIT_SUCCESS);
86+
#[allow(clippy::empty_loop)]
87+
loop {}
88+
}

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: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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 = NonNull::new(self.heap.borrow(cs).borrow_mut().alloc(layout))
81+
.ok_or(AllocError)?;
82+
Ok(NonNull::slice_from_raw_parts(rst, size))
83+
}),
84+
}
85+
}
86+
87+
unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
88+
}
89+
}
90+
91+
struct SimplestHeap {
92+
arena: *mut u8,
93+
remaining: usize,
94+
size: usize,
95+
}
96+
97+
unsafe impl Send for SimplestHeap {}
98+
99+
impl SimplestHeap {
100+
const fn empty() -> Self {
101+
Self {
102+
arena: ptr::null_mut(),
103+
remaining: 0,
104+
size: 0,
105+
}
106+
}
107+
108+
fn init(&mut self, start_addr: *mut u8, size: usize) {
109+
self.arena = start_addr;
110+
self.remaining = size;
111+
self.size = size;
112+
}
113+
114+
fn free(&self) -> usize {
115+
self.remaining
116+
}
117+
118+
fn used(&self) -> usize {
119+
self.size - self.remaining
120+
}
121+
122+
fn alloc(&mut self, layout: Layout) -> *mut u8 {
123+
if layout.size() > self.remaining {
124+
return ptr::null_mut();
125+
}
126+
127+
// `Layout` contract forbids making a `Layout` with align=0, or align not power of 2.
128+
// So we can safely use a mask to ensure alignment without worrying about UB.
129+
let align_mask_to_round_down = !(layout.align() - 1);
130+
131+
self.remaining -= layout.size();
132+
self.remaining &= align_mask_to_round_down;
133+
self.arena.wrapping_add(self.remaining)
134+
}
135+
}

0 commit comments

Comments
 (0)