From 3e2973ad3b28cb8526e0d52183b1c2b64aee05b8 Mon Sep 17 00:00:00 2001 From: husky Date: Mon, 30 Jun 2025 17:44:35 -0700 Subject: [PATCH] initial noalloc implementation --- .gitignore | 7 +- Cargo.lock | 7 ++ Cargo.toml | 11 +++ src/lib.rs | 9 ++ src/noalloc.rs | 209 +++++++++++++++++++++++++++++++++++++++++++++++ src/tests.rs | 32 ++++++++ src/withalloc.rs | 0 7 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/noalloc.rs create mode 100644 src/tests.rs create mode 100644 src/withalloc.rs diff --git a/.gitignore b/.gitignore index ad67955..f2f13a1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,9 @@ target # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ + + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e54ce89 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "atomalloc" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ab354b2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "atomalloc" +version = "0.1.0" +edition = "2024" + +[dependencies] + +[features] +default = ["no_alloc", "with_alloc"] +no_alloc = [] +with_alloc = [] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2747493 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +#![no_std] + +#[cfg(feature = "no_alloc")] +pub mod noalloc; +#[cfg(feature = "with_alloc")] +pub mod withalloc; + +#[cfg(test)] +mod tests; \ No newline at end of file diff --git a/src/noalloc.rs b/src/noalloc.rs new file mode 100644 index 0000000..a6ffaf2 --- /dev/null +++ b/src/noalloc.rs @@ -0,0 +1,209 @@ +pub struct AtomAlloc { + pub atoms: [Option>; SLOTS], + pub head: Option, +} + +#[derive(Eq, PartialEq, Debug)] +pub struct Atom { + pub start: usize, + pub frame_count: usize, +} + +impl Default for AtomAlloc { + fn default() -> Self { + Self::new() + } +} + +impl AtomAlloc { + pub const fn new() -> Self { + Self { + atoms: [const { None }; SLOTS], + head: None, + } + } + + fn head(&self) -> &Option> { + // self.head should always point to either a valid atom or a none + if let Some(index) = self.head { + &self.atoms[index] + } else { + &None + } + } + + fn push(&mut self, atom: Atom) { + if atom.frame_count == 0 { + // empty atom, do nothing + return; + } + if let Some(index) = self.head { + // if there's an atom at the head, add one and put an atom there + self.atoms[index + 1] = Some(atom); + self.head = Some(index + 1); + } else { + // no atom, add the first atom + self.head = Some(0); + self.atoms[0] = Some(atom); + } + } + + fn pop(&mut self) -> Option> { + if let Some(index) = self.head { + // if there's an atom at the head... + if index == 0 { + // if the atom is the first one, unset head and return first atom + self.head = None; + self.atoms[0].take() + } else { + // take and subtract head + self.head = Some(index - 1); + self.atoms[index - 1].take() + } + } else { + None + } + } + + /// # add_memory + /// this function will add memory to the allocator, starting at `start` + /// `frame_count` is the amount of frames that are available after `start` + /// + /// no alignment guarantees are made, + /// so if you want alignment then do that before inserting the frames + pub fn add_memory(&mut self, start: usize, frame_count: usize) { + self.push(Atom { start, frame_count }) + } + + /// # allocate + /// this function will find a free space of the given `frame_count`; + /// the starting address will be returned, once again with no alignment guarantees + /// will return `None` if there is not enough memory to allocate the given `frame_count` + /// if there is enough time, it may be beneficial to call `defragment()` beforehand + pub fn allocate(&mut self, frame_count: usize) -> Option { + // is the head atom good enough? + let top = self.pop()?; + if top.frame_count >= frame_count { + // allocate from the start + self.push(Atom { + start: top.start + (frame_count * FRAME_SIZE), + frame_count: top.frame_count - frame_count, + }); + Some(top.start) + } else { + // search all of the slots + let mut search_stack = [const { None }; SLOTS]; + search_stack[0] = Some(top); + let mut head = 0; + + let mut found = None; + + while found.is_none() { + let top = self.pop()?; + if top.frame_count >= frame_count { + found = Some(top); + } else { + head += 1; + search_stack[head] = Some(top); + } + } + + // refill + while head != 0 { + let top = search_stack[head].take()?; + head -= 1; + self.push(top); + } + + if let Some(found) = found { + // this is the atom we can allocate from + self.push(Atom { + start: found.start + (frame_count * FRAME_SIZE), + frame_count: found.frame_count - frame_count, + }); + Some(found.start) + } else { + None + } + } + } + + /// # deallocate + /// this function is a mirror of `add_memory()`, you must provide the amount of frames + /// allocated, it won't work with a raw memory length + pub fn deallocate(&mut self, start: usize, frame_count: usize) { + self.add_memory(start, frame_count); + } + + /// # defragment + /// this function will rearrange the atoms available in the allocator so that they + /// follow each other, and merge touching atoms + /// + /// this function does not check for overlapping atoms + pub fn defragment(&mut self) { + if let Some(head) = self.head { + if head == 0 { + return; + } + // first we will sort the atoms, i'm just gonna use insertion sort cause its simple + let mut idx = 1; + while idx <= head { + let atom = self.atoms[idx].take().expect("None before head!"); + // go back through and insert the atom there + let mut idx2 = idx; + while idx2 > 0 { + idx2 -= 1; + // fixme: this can be optimized using std::mem::swap + // take the previous atom + let prev_atom = self.atoms[idx2].take().expect("None before head!"); + // if the previous atom is greater, place current before it + if prev_atom.start > atom.start { + self.atoms[idx2 + 1] = Some(prev_atom); + } else { + // place atom here + self.atoms[idx2 + 1] = Some(prev_atom); + self.atoms[idx2] = Some(atom); + break; + } + if idx2 == 0 { + self.atoms[idx2] = Some(atom); + break; + } + } + idx += 1; + } + + // should be sorted now, merge touching atoms + let mut stack = [const { None }; SLOTS]; + + // clippy, shut up + #[allow(clippy::needless_range_loop)] + for i in 0..=head { + stack[head - i] = self.atoms[i].take() + } + + let mut idx = head; + let mut idx2 = 0; + let mut atom = stack[head].take().expect("None before head!"); + loop { + // take next atom + if idx == 0 { + self.atoms[idx2] = Some(atom); + break; + } + let next = stack[idx - 1].take().expect("None before head!"); + if atom.start + (atom.frame_count * FRAME_SIZE) >= next.start { + // merge + atom.frame_count += next.frame_count; + } else { + // cannot merge anymore, push and take + self.atoms[idx2] = Some(atom); + idx2 += 1; + atom = next; + } + idx -= 1; + } + self.head = Some(idx2); + } + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..9a039cb --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,32 @@ +use crate::noalloc::{Atom, AtomAlloc}; + +#[test] +fn noalloc() { + let mut alloc: AtomAlloc<16, 1> = AtomAlloc::default(); + + assert_eq!(alloc.head, None); + + alloc.add_memory(1024, 64); // [(1024, 64)...] + assert_eq!(alloc.atoms[0], Some(Atom { start: 1024, frame_count: 64 })); + assert_eq!(alloc.head, Some(0)); + + // allocation + + let start = alloc.allocate(10); // [(1034, 54)...] + assert_eq!(alloc.atoms[0], Some(Atom { start: 1034, frame_count: 54 })); + assert_eq!(start, Some(1024)); + assert_eq!(alloc.head, Some(0)); + + // deallocation + + alloc.deallocate(1024, 10); // [(1034, 54), (1024, 10)...] + assert_eq!(alloc.atoms[0], Some(Atom { start: 1034, frame_count: 54 })); + assert_eq!(alloc.atoms[1], Some(Atom { start: 1024, frame_count: 10 })); + assert_eq!(alloc.head, Some(1)); + + // defragmentation + + alloc.defragment(); // [(1024, 64)...] + assert_eq!(alloc.atoms[0], Some(Atom { start: 1024, frame_count: 64 })); + assert_eq!(alloc.head, Some(0)); +} \ No newline at end of file diff --git a/src/withalloc.rs b/src/withalloc.rs new file mode 100644 index 0000000..e69de29