use avr_oxide::deviceconsts::memory;
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
#[cfg(target_arch="avr")]
extern crate alloc;
#[cfg(target_arch="avr")]
pub use alloc::vec;
#[cfg(target_arch="avr")]
pub use alloc::boxed;
#[cfg(target_arch="avr")]
pub use alloc::rc;
#[cfg(target_arch="avr")]
pub use alloc::string;
#[cfg(not(target_arch="avr"))]
pub use std::boxed;
#[cfg(not(target_arch="avr"))]
pub use std::vec;
#[cfg(not(target_arch="avr"))]
pub use std::rc;
#[repr(C,align(2))]
struct FirstFitHeap<const SEGS: usize, const S2: usize> {
v: &'static mut [i16],
pa: [u16; SEGS],
st: [i16; S2],
s: usize,
words_per_seg: usize
}
struct GlobalAllocator<const S:usize, const S2:usize> {
heap: UnsafeCell<FirstFitHeap<S,S2>>
}
#[cfg(target_arch="avr")]
#[global_allocator]
static mut GLOBAL: GlobalAllocator::<{memory::alloc::SMAX}, {memory::alloc::SMAX * 2}> = GlobalAllocator {
heap: UnsafeCell::new(FirstFitHeap::new())
};
#[cfg(target_arch="avr")]
#[alloc_error_handler]
fn _alloc_error(_: core::alloc::Layout) -> ! {
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::OutOfMemory);
}
#[cfg(target_arch="avr")]
pub unsafe fn alloc(layout: Layout) -> *mut u8 {
GLOBAL.alloc(layout)
}
#[cfg(not(target_arch="avr"))]
pub unsafe fn alloc(layout: Layout) -> *mut u8 {
std::alloc::alloc(layout)
}
#[cfg(target_arch="avr")]
pub unsafe fn dealloc(ptr: *mut u8, layout: Layout) {
GLOBAL.dealloc(ptr, layout)
}
#[cfg(not(target_arch="avr"))]
pub unsafe fn dealloc(ptr: *mut u8, layout: Layout) {
std::alloc::dealloc(ptr, layout)
}
#[cfg(target_arch="avr")]
pub fn initialise() {
unsafe {
let heap = avr_oxide::panic_if_none!(GLOBAL.heap.get().as_mut(), avr_oxide::oserror::OsError::InternalError);
let mut heap_start_hi:u8;
let mut heap_start_lo:u8;
core::arch::asm!{
"ldi {}, hi8(__heap_start)",
"ldi {}, lo8(__heap_start)",
out(reg) heap_start_hi,
out(reg) heap_start_lo
}
let heap_start = (heap_start_hi as usize) << 8 | heap_start_lo as usize;
let heap_size = avr_oxide::deviceconsts::memory::SRAM_END as isize - heap_start as isize;
if heap_start == 0 || heap_size < avr_oxide::deviceconsts::memory::alloc::MIN_HEAPSIZE as isize {
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::NotEnoughRam);
}
heap.set_heap_location(heap_start as *mut u8, heap_size as usize);
heap.init();
}
}
unsafe impl<const S:usize, const S2:usize> GlobalAlloc for GlobalAllocator<S,S2> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let heap = avr_oxide::panic_if_none!(self.heap.get().as_mut(), avr_oxide::oserror::OsError::InternalError);
let align_words = match layout.align() {
0 => 0,
1 => 0,
_other => (layout.align()/2)-1
};
let size_words= ((layout.size() + 1) / 2) + align_words;
let ptr = avr_oxide::concurrency::interrupt::isolated(|_isotoken|{
match heap.bl_new(size_words){
0 => {
core::ptr::null_mut()
},
index => {
core::mem::transmute(&heap.v[index])
}
}
});
match (ptr as usize) % layout.align() {
0 => ptr, misalignment => {
let padding_needed = (layout.align() - misalignment) as isize;
let returned_ptr = ptr.offset(padding_needed);
let flag = returned_ptr.offset(-2) as *mut i16; *flag = (padding_needed as i16)/2; returned_ptr
}
}
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
let heap = avr_oxide::panic_if_none!(self.heap.get().as_mut(), avr_oxide::oserror::OsError::InternalError);
let index0:usize = core::mem::transmute(&heap.v[0]);
let mut index = ((ptr as usize) - index0)/2;
avr_oxide::concurrency::interrupt::isolated(|_isotoken|{
if heap.v[index-1] > 0 {
index -= heap.v[index-1] as usize;
}
heap.bl_dispose(index);
});
}
}
impl<const SEGS: usize, const S2: usize> FirstFitHeap<SEGS,S2> {
const fn new() -> Self {
FirstFitHeap {
v: &mut [],
pa: [0; SEGS],
st: [0; S2],
s: 0,
words_per_seg: 0
}
}
fn set_heap_location(&mut self, memory: *mut u8, size_bytes: usize){
unsafe {
self.v = core::slice::from_raw_parts_mut(memory as *mut i16, size_bytes/2)
}
}
fn init(&mut self) {
let heapsize_words = self.v.len() as i16;
if heapsize_words == 0 {
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::NotEnoughRam);
}
self.s = 1;
self.st[1] = heapsize_words;
self.pa[0] = 0;
self.v[0] = heapsize_words;
self.words_per_seg = heapsize_words as usize/SEGS;
self.bl_new(0);
}
fn bl_seg(&self, addr: usize) -> usize {
self.s + (addr/self.words_per_seg)
}
#[optimize(speed)]
fn bl_double(&mut self) {
let heapsize_max = self.v.len() - 1;
if !(self.s <= SEGS/2) {
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::InternalError);
}
for i in self.s ..= 2*self.s-1 {
self.pa[i] = heapsize_max as u16;
}
let mut k = self.s;
while k > 0 {
for i in 0 ..= k-1 {
self.st[2*k+i] = self.st[k+i];
self.st[3*k+i] = 0;
}
k /= 2;
}
self.st[1] = self.st[2];
self.s *= 2;
}
#[optimize(speed)]
fn bl_fix_left(&mut self, addr: usize) {
let heapsize_max = self.v.len() - 1;
let mut j = self.bl_seg(addr);
if (self.v[addr] <= 0) || (self.st[j] as i16 <= self.v[addr]) {
let mut pj = self.pa[j-self.s] as usize; let mut pn = (j+1-self.s) * self.words_per_seg; if pn > heapsize_max { pn = heapsize_max;
}
let mut mx = if pj < pn {
let mut mx = 1i16;
while pj < pn {
if mx < self.v[pj] {
mx = self.v[pj];
}
pj = pj + self.v[pj].abs() as usize;
}
mx
} else {
0
};
self.st[0] = 0; while self.st[j] > mx { self.st[j] = mx;
let sister = match j % 2 {
1 => self.st[j-1], _ => self.st[j+1] };
if mx < sister {
mx = sister;
}
j /= 2;
}
}
}
#[optimize(speed)]
fn bl_fix_right(&mut self, addr: usize) {
let mut j = self.bl_seg(addr);
while j >= 2*self.s && self.s <= SEGS/2 {
self.bl_double();
j = self.bl_seg(addr);
}
if self.pa[j-self.s] > addr as u16 {
self.pa[j-self.s] = addr as u16;
}
let vp = self.v[addr];
self.st[0] = vp; while self.st[j] < vp { self.st[j] = vp;
j /= 2;
}
}
#[optimize(speed)]
fn bl_pred(&self, addr: usize) -> usize {
let mut j = self.bl_seg(addr);
if self.pa[j-self.s] == addr as u16 {
while self.st[j-1] == 0 {
j /= 2;
}
j -= 1;
while j<self.s {
if self.st[2*j+1] > 0 {
j = 2*j+1;
} else {
j *= 2;
}
}
}
let mut qn = self.pa[j-self.s];
let mut q = qn;
while qn != addr as u16 {
q = qn;
qn = qn+self.v[qn as usize].abs() as u16;
}
q as usize
}
#[optimize(speed)]
fn bl_new(&mut self, size: usize) -> usize {
if self.st[1] <= size as i16 { 0
} else {
let n = size +1; let mut j = 1;
while j < self.s {
if self.st[2*j] >= n as i16 {
j *= 2;
} else {
j = 2*j+1;
}
}
let mut p = self.pa[j-self.s] as usize; while self.v[p] < n as i16 {
p = p + self.v[p].abs() as usize;
}
let vp = self.v[p];
self.v[p] = -(n as i16); let fix_left = vp == self.st[j];
if vp > n as i16 {
self.v[p+n] = vp - (n as i16);
if self.bl_seg(p+n) > j {
self.bl_fix_right(p+n);
}
}
if fix_left {
self.bl_fix_left(p);
}
p+1 }
}
#[optimize(speed)]
fn bl_dispose(&mut self, addr: usize) {
let heapsize_max = self.v.len() - 1;
let p = addr-1; let vp = -self.v[p];
if vp > 0 {
self.v[p] = vp; let j = self.bl_seg(p);
let pn = p + vp as usize;
if pn < heapsize_max && self.v[pn] >= 0 {
self.v[p] = vp + self.v[pn];
let jn = self.bl_seg(pn);
if jn > j {
self.pa[jn-self.s] = (p as i16 + self.v[p]) as u16;
self.bl_fix_left(pn);
}
}
let pr = self.bl_pred(p); if self.v[pr] >= 0 {
self.v[pr] += self.v[p];
if self.pa[j-self.s] as usize == p {
self.pa[j-self.s] = (pr as i16 + self.v[pr]) as u16; self.bl_fix_left(p);
}
self.bl_fix_right(pr);
} else if self.v[p] > self.st[j] {
self.bl_fix_right(p);
}
} else {
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::InternalError);
}
}
#[allow(dead_code)]
fn bl_size(&self, addr: usize) -> usize {
let p = self.v[addr-1];
if p > 0 {
avr_oxide::oserror::halt(avr_oxide::oserror::OsError::InternalError);
} else {
-(p+1) as usize
}
}
#[allow(dead_code)]
fn bl_available(&self) -> usize {
self.st[1] as usize - 1
}
#[allow(dead_code)]
fn bl_segments(&self) -> usize {
self.s
}
}
#[cfg(test)]
mod test {
use std::vec::Vec;
use avr_oxide::alloc::*;
#[test]
fn test_create_heap() {
let mut memory = [0u8;1024];
let mut heap = FirstFitHeap::<32,64>::new();
heap.set_heap_location(&mut memory as *mut u8, 1024);
heap.init();
assert_eq!(heap.bl_seg(1), 1);
}
#[test]
fn test_alloc_free() {
let mut memory = [0u8;1024];
let mut heap = FirstFitHeap::<32,64>::new();
heap.set_heap_location(&mut memory as *mut u8, 1024);
heap.init();
let init_free = heap.bl_available();
println!("Heap with {} words available\n", init_free);
let alloc1 = heap.bl_new(128);
println!("First allocation, {} bytes @ {}", heap.bl_size(alloc1), alloc1);
println!("Free space: {}", heap.bl_available());
println!("Segments: {}", heap.bl_segments());
assert!(heap.bl_size(alloc1) >= 128);
assert_eq!(alloc1, 2);
let alloc2 = heap.bl_new(64);
println!("Second allocation, {} bytes @ {}", heap.bl_size(alloc2), alloc2);
println!("Free space: {}", heap.bl_available());
println!("Segments: {}", heap.bl_segments());
assert!(heap.bl_size(alloc2) >= 64);
assert_eq!(alloc2, 131);
let alloc3 = heap.bl_new(27);
println!("Third allocation, {} bytes @ {}", heap.bl_size(alloc3), alloc3);
println!("Free space: {}", heap.bl_available());
println!("Segments: {}", heap.bl_segments());
assert!(heap.bl_size(alloc3) >= 27);
assert_eq!(alloc3, 196);
let alloc4 = heap.bl_new(256);
println!("Fourth allocation, {} bytes @ {}", heap.bl_size(alloc4), alloc4);
println!("Free space: {}", heap.bl_available());
println!("Segments: {}", heap.bl_segments());
assert!(heap.bl_size(alloc4) >= 256);
assert_eq!(alloc4, 224);
let alloc5 = heap.bl_new(31);
println!("Fifth allocation, {} bytes @ {}", heap.bl_size(alloc5), alloc5);
println!("Free space: {}", heap.bl_available());
println!("Segments: {}", heap.bl_segments());
assert!(heap.bl_size(alloc5) >= 31);
assert_eq!(alloc5, 481);
println!("Freeing allocation @ {}", alloc3);
heap.bl_dispose(alloc3);
println!("Free space: {}", heap.bl_available());
println!("Segments: {}", heap.bl_segments());
assert_eq!(heap.bl_available(), 27);
let alloc6 = heap.bl_new(4);
println!("Sixth allocation, {} bytes @ {}", heap.bl_size(alloc6), alloc6);
println!("Free space: {}", heap.bl_available());
println!("Segments: {}", heap.bl_segments());
assert!(heap.bl_size(alloc6) >= 4);
assert_eq!(alloc6, 196);
let alloc7 = heap.bl_new(10);
println!("Seventh allocation, {} bytes @ {}", heap.bl_size(alloc7), alloc7);
println!("Free space: {}", heap.bl_available());
println!("Segments: {}", heap.bl_segments());
assert!(heap.bl_size(alloc7) >= 10);
assert_eq!(alloc7, 201);
heap.bl_dispose(alloc1);
println!("Free space: {}", heap.bl_available());
heap.bl_dispose(alloc2);
println!("Free space: {}", heap.bl_available());
heap.bl_dispose(alloc4);
println!("Free space: {}", heap.bl_available());
heap.bl_dispose(alloc5);
println!("Free space: {}", heap.bl_available());
heap.bl_dispose(alloc6);
println!("Free space: {}", heap.bl_available());
heap.bl_dispose(alloc7);
println!("Free space: {}", heap.bl_available());
assert_eq!(heap.bl_available(), init_free);
}
fn fillspace(addr: *mut u8, size: usize, value: u8) {
unsafe {
for i in 0..size as isize {
*(addr.offset(i)) = value;
}
}
}
fn checkspace(addr: *mut u8, size: usize, value: u8) -> bool {
unsafe {
for i in 0..size as isize {
if *(addr.offset(i)) != value {
return false;
}
}
}
true
}
#[test]
pub fn test_global_alloc() {
unsafe {
let mut memory = [0u8;1024];
let global = GlobalAllocator {
heap: UnsafeCell::new(FirstFitHeap::<32,64>::new())
};
let heap = global.heap.get().as_mut().unwrap();
heap.set_heap_location(&mut memory as *mut u8, 1024);
heap.init();
let addr = global.alloc(Layout::from_size_align(16,2).unwrap());
fillspace(addr, 16,0xde);
println!("Allocated a block @ address: {:?}", addr);
println!("Free space: {}b", heap.bl_available()*2);
assert_eq!(addr as usize % 2, 0); global.dealloc(addr, Layout::from_size_align(16,2).unwrap());
println!("Freed block");
println!("Free space: {}b", heap.bl_available()*2);
println!("Alignment checks");
let discard = global.alloc(Layout::from_size_align(3, 2).unwrap());
let addr1 = global.alloc(Layout::from_size_align(175,4).unwrap());
fillspace(addr1, 175, 0xde);
println!("Allocated a block @ address: {:?}", addr1);
println!("Free space: {}b", heap.bl_available()*2);
assert_eq!(addr1 as usize % 4, 0); let addr2 = global.alloc(Layout::from_size_align(230, 8).unwrap());
fillspace(addr2, 230, 0xde);
println!("Allocated a block @ address: {:?}", addr2);
println!("Free space: {}b", heap.bl_available()*2);
assert_eq!(addr2 as usize % 8, 0); let addr3 = global.alloc(Layout::from_size_align(49, 16).unwrap());
fillspace(addr3, 49, 0xde);
println!("Allocated a block @ address: {:?}", addr3);
println!("Free space: {}b", heap.bl_available()*2);
assert_eq!(addr3 as usize % 16, 0); println!("Checking for data corruption");
assert!(checkspace(addr1, 175,0xde));
assert!(checkspace(addr2, 230,0xde));
assert!(checkspace(addr3, 49,0xde));
println!("Freeing aligned allocations");
global.dealloc(addr1, Layout::from_size_align(175,4).unwrap());
assert!(checkspace(addr2, 230,0xde));
assert!(checkspace(addr3, 49,0xde));
global.dealloc(addr2, Layout::from_size_align(230, 8).unwrap());
assert!(checkspace(addr3, 49,0xde));
global.dealloc(addr3, Layout::from_size_align(49, 16).unwrap());
}
}
#[test]
pub fn soaktest_global_alloc(){
soaktest_global_alloc_sized::<64,8,{8*2}>(); soaktest_global_alloc_sized::<128,8,{8*2}>(); soaktest_global_alloc_sized::<256,16,{16*2}>(); soaktest_global_alloc_sized::<256,32,{32*2}>(); soaktest_global_alloc_sized::<512,64,{64*2}>(); soaktest_global_alloc_sized::<1536,128,{128*2}>(); }
pub fn soaktest_global_alloc_sized<const SIZE:usize,const SMAX:usize,const S2:usize>() {
println!("");
println!("==========================");
println!("== ALLOCATOR SOAKTEST ==");
println!("==========================");
println!(" Heap: {} words", SIZE);
println!(" Segments: {} (max)", SMAX);
let mut memory = [0u16; SIZE];
unsafe {
let global = GlobalAllocator {
heap: UnsafeCell::new(FirstFitHeap::<SMAX,S2>::new())
};
let mut heap = global.heap.get().as_mut().unwrap();
heap.set_heap_location(&mut memory as *mut u16 as *mut u8, {SIZE*2});
heap.init();
for blocksize in 1..SMAX {
println!("Testing blocks of size {}", blocksize);
let mut blocks : Vec<*mut u8> = Vec::new();
let mut i:i32 = 0;
loop {
let block = global.alloc(Layout::from_size_align(blocksize,1).unwrap());
if block != core::ptr::null_mut() {
fillspace(block,blocksize,i as u8);
blocks.push(block);
} else {
break;
}
i += 1;
}
println!(" Allocated {} blocks", blocks.len());
let mut i:i32 = 0;
for block in blocks {
assert!(checkspace(block,blocksize,i as u8));
global.dealloc(block,Layout::from_size_align(blocksize,1).unwrap());
i += 1;
}
}
}
}
}