treewide: going no_std

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia1c001eb099ea8cae9bdf76642b873376a6a6964
This commit is contained in:
raf 2026-03-27 17:29:49 +03:00
commit 472dbfc7e7
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
14 changed files with 856 additions and 143 deletions

12
crates/alloc/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "microfetch-alloc"
description = "Simple, std-free bump allocator for Microfetch"
version.workspace = true
edition.workspace = true
authors.workspace = true
rust-version.workspace = true
license.workspace = true
publish = false
[lints]
workspace = true

106
crates/alloc/src/lib.rs Normal file
View file

@ -0,0 +1,106 @@
//! Simple bump allocator for `no_std` environments. Uses a statically allocated
//! 32KB buffer and provides O(1) allocation with no deallocation support
//! (memory is never freed).
#![no_std]
use core::{
alloc::{GlobalAlloc, Layout},
cell::UnsafeCell,
ptr::null_mut,
};
/// Default heap size is 32KB, should be plenty for Microfetch. Technically it
/// can be invoked with more (or less) depending on our needs but I am quite
/// sure 32KB is more than enough.
pub const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
/// A simple bump allocator that never frees memory.
///
/// This allocator maintains a static buffer and a bump pointer. Allocations are
/// fast (just bump the pointer), but memory is never reclaimed. While you might
/// be inclined to point out that this is ugly, it's suitable for a short-lived
/// program with bounded memory usage.
pub struct BumpAllocator<const N: usize = DEFAULT_HEAP_SIZE> {
heap: UnsafeCell<[u8; N]>,
next: UnsafeCell<usize>,
}
// SAFETY: BumpAllocator is thread-safe because it uses UnsafeCell
// and the allocator is only used in single-threaded contexts (i.e., no_std).
unsafe impl<const N: usize> Sync for BumpAllocator<N> {}
impl<const N: usize> BumpAllocator<N> {
/// Creates a new bump allocator with the specified heap size.
#[must_use]
pub const fn new() -> Self {
Self {
heap: UnsafeCell::new([0; N]),
next: UnsafeCell::new(0),
}
}
/// Returns the number of bytes currently allocated.
#[must_use]
pub fn used(&self) -> usize {
// SAFETY: We're just reading the value, and this is only called
// in single-threaded contexts.
unsafe { *self.next.get() }
}
/// Returns the total heap size.
#[must_use]
pub const fn capacity(&self) -> usize {
N
}
/// Returns the number of bytes remaining.
#[must_use]
pub fn remaining(&self) -> usize {
N - self.used()
}
}
impl<const N: usize> Default for BumpAllocator<N> {
fn default() -> Self {
Self::new()
}
}
unsafe impl<const N: usize> GlobalAlloc for BumpAllocator<N> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe {
let next = self.next.get();
let heap = self.heap.get();
// Align the current position
let align = layout.align();
let start = (*next + align - 1) & !(align - 1);
let end = start + layout.size();
if end > N {
// Out of memory
null_mut()
} else {
*next = end;
(*heap).as_mut_ptr().add(start)
}
}
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
// Bump allocator doesn't support deallocation
// Memory is reclaimed when the program exits
}
}
/// Static bump allocator instance with 32KB heap.
///
/// # Example
///
/// Use this with `#[global_allocator]` in your binary:
///
///
/// ```rust,ignore
/// #[global_allocator]
/// static ALLOCATOR: BumpAllocator = BumpAllocator::new();
/// ```
pub type BumpAlloc = BumpAllocator<DEFAULT_HEAP_SIZE>;