Ennix/stack.h

177 lines
4.5 KiB
C

#pragma once
#ifndef DEFAULT_ALIGNMENT
#define DEFAULT_ALIGNMENT (2*sizeof(void *))
#endif
bool is_power_of_two(uintptr_t x) {
return (x & (x-1)) == 0;
}
typedef struct Stack {
unsigned char *buf;
size_t buf_len;
size_t prev_offset;
size_t curr_offset;
} Stack;
typedef struct Stack_Allocation_Header {
size_t prev_offset;
size_t padding;
} Stack_Allocation_Header;
void stack_init(Stack *s, void *backing_buffer, size_t backing_buffer_length) {
s->buf = (unsigned char *)backing_buffer;
s->buf_len = backing_buffer_length;
s->prev_offset = 0;
s->curr_offset = 0;
}
size_t calc_padding_with_header(uintptr_t ptr, uintptr_t alignment, size_t header_size) {
uintptr_t p, a, modulo, padding, needed_space;
assert(is_power_of_two(alignment));
p = ptr;
a = alignment;
modulo = p & (a-1); // (p % a) as it assumes alignment is a power of two
padding = 0;
needed_space = 0;
if (modulo != 0) { // Same logic as 'align_forward'
padding = a - modulo;
}
needed_space = (uintptr_t)header_size;
if (padding < needed_space) {
needed_space -= padding;
if ((needed_space & (a-1)) != 0) {
padding += a * (1+(needed_space/a));
} else {
padding += a * (needed_space/a);
}
}
return (size_t)padding;
}
void *stack_alloc_align(Stack *s, size_t size, size_t alignment) {
uintptr_t curr_addr, next_addr;
size_t padding;
Stack_Allocation_Header *header;
assert(is_power_of_two(alignment));
if (alignment > 128) {
// As the padding is 8 bits (1 byte), the largest alignment that can
// be used is 128 bytes
alignment = 128;
}
curr_addr = (uintptr_t)s->buf + (uintptr_t)s->curr_offset;
usize sah_size = sizeof(Stack_Allocation_Header);
padding = calc_padding_with_header(curr_addr, (uintptr_t)alignment, sah_size);
if (s->curr_offset + padding + size > s->buf_len) {
// Stack allocator is out of memory
return NULL;
}
s->prev_offset = s->curr_offset; // Store the previous offset
s->curr_offset += padding;
next_addr = curr_addr + (uintptr_t)padding;
header = (Stack_Allocation_Header *)(next_addr - sizeof(Stack_Allocation_Header));
header->padding = padding;
header->prev_offset = s->prev_offset; // store the previous offset in the header
s->curr_offset += size;
return memset((void *)next_addr, 0, size);
}
// Because C does not have default parameters
void *stack_alloc(Stack *s, size_t size) {
return stack_alloc_align(s, size, DEFAULT_ALIGNMENT);
}
void stack_free(Stack *s, void *ptr) {
if (ptr != NULL) {
uintptr_t start, end, curr_addr;
Stack_Allocation_Header *header;
size_t prev_offset;
start = (uintptr_t)s->buf;
end = start + (uintptr_t)s->buf_len;
curr_addr = (uintptr_t)ptr;
if (!(start <= curr_addr && curr_addr < end)) {
assert(0 && "Out of bounds memory address passed to stack allocator (free)");
return;
}
if curr_addr >= start+(uintptr_t)s->offset {
// Allow double frees
return;
}
header = (Stack_Allocation_Header *)(curr_addr - sizeof(Stack_Allocation_Header));
// Calculate previous offset from the header and its address
prev_offset = (size_t)(curr_addr - (uintptr_t)header->padding - start);
if (prev_offset != header->prev_offset) {
assert(0 && "Out of order stack allocator free");
return;
}
// Reset the offsets to the previous allocation
s->curr_offset = s->prev_offset;
s->prev_offset = header->prev_offset;
}
}
void *stack_resize_align(Stack *s, void *ptr, size_t old_size, size_t new_size, size_t alignment) {
if (ptr == NULL) {
return stack_alloc_align(s, new_size, alignment);
} else if (new_size == 0) {
stack_free(s, ptr);
return NULL;
} else {
uintptr_t start, end, curr_addr;
size_t min_size = old_size < new_size ? old_size : new_size;
void *new_ptr;
start = (uintptr_t)s->buf;
end = start + (uintptr_t)s->buf_len;
curr_addr = (uintptr_t)ptr;
if (!(start <= curr_addr && curr_addr < end)) {
assert(0 && "Out of bounds memory address passed to stack allocator (resize)");
return NULL;
}
if (curr_addr >= start + (uintptr_t)s->offset) {
// Treat as a double free
return NULL;
}
if old_size == size {
return ptr;
}
new_ptr = stack_alloc_align(s, new_size, alignment);
memmove(new_ptr, ptr, min_size);
return new_ptr;
}
}
void *stack_resize(Stack *s, void *ptr, size_t old_size, size_t new_size) {
return stack_resize_align(s, ptr, old_size, new_size, DEFAULT_ALIGNMENT);
}
void stack_free_all(Stack *s) {
s->offset = 0;
}