275 lines
7.8 KiB
Odin
275 lines
7.8 KiB
Odin
package sectr
|
|
|
|
import "core:mem"
|
|
import "core:slice"
|
|
|
|
//region Fixed Stack
|
|
|
|
StackFixed :: struct ( $ Type : typeid, $ Size : u32 ) {
|
|
idx : u32,
|
|
items : [ Size ] Type,
|
|
}
|
|
|
|
stack_push :: proc( using stack : ^ StackFixed( $ Type, $ Size ), value : Type ) {
|
|
verify( idx < len( items ), "Attempted to push on a full stack" )
|
|
|
|
items[ idx ] = value
|
|
idx += 1
|
|
}
|
|
|
|
stack_pop :: proc( using stack : ^StackFixed( $ Type, $ Size ) ) {
|
|
verify( idx > 0, "Attempted to pop an empty stack" )
|
|
|
|
idx -= 1
|
|
if idx == 0 {
|
|
items[idx] = {}
|
|
}
|
|
}
|
|
|
|
stack_peek_ref :: proc( using stack : ^StackFixed( $ Type, $ Size ) ) -> ( ^Type) {
|
|
last := max( 0, idx - 1 ) if idx > 0 else 0
|
|
return & items[last]
|
|
}
|
|
|
|
stack_peek :: proc ( using stack : ^StackFixed( $ Type, $ Size ) ) -> Type {
|
|
last := max( 0, idx - 1 ) if idx > 0 else 0
|
|
return items[last]
|
|
}
|
|
|
|
//endregion Fixed Stack
|
|
|
|
//region Stack Allocator
|
|
|
|
// TODO(Ed) : This is untested and problably filled with bugs.
|
|
/* Growing Stack allocator
|
|
This implementation can support growing if the backing allocator supports
|
|
it without fragmenting the backing allocator.
|
|
|
|
Each block in the stack is tracked with a doubly-linked list to have debug stats.
|
|
(It could be removed for non-debug builds)
|
|
*/
|
|
|
|
StackAllocatorBase :: struct {
|
|
backing : Allocator,
|
|
|
|
using links : DLL_NodeFL(StackAllocatorHeader),
|
|
peak_used : int,
|
|
size : int,
|
|
data : [^]byte,
|
|
}
|
|
|
|
StackAllocator :: struct {
|
|
using base : ^StackAllocatorBase,
|
|
}
|
|
|
|
StackAllocatorHeader :: struct {
|
|
using links : DLL_NodePN(StackAllocatorHeader),
|
|
block_size : int,
|
|
padding : int,
|
|
}
|
|
|
|
stack_allocator :: proc( using self : StackAllocator ) -> ( allocator : Allocator ) {
|
|
allocator.procedure = stack_allocator_proc
|
|
allocator.data = self.base
|
|
return
|
|
}
|
|
|
|
stack_allocator_init :: proc( size : int, allocator := context.allocator ) -> ( stack : StackAllocator, alloc_error : AllocatorError )
|
|
{
|
|
header_size := size_of(StackAllocatorBase)
|
|
|
|
raw_mem : rawptr
|
|
raw_mem, alloc_error = alloc( header_size + size, mem.DEFAULT_ALIGNMENT )
|
|
if alloc_error != AllocatorError.None do return
|
|
|
|
stack.base = cast( ^StackAllocatorBase) raw_mem
|
|
stack.size = size
|
|
stack.data = cast( [^]byte) (cast( [^]StackAllocatorBase) stack.base)[ 1:]
|
|
|
|
stack.top = cast(^StackAllocatorHeader) stack.data
|
|
stack.bottom = stack.top
|
|
return
|
|
}
|
|
|
|
stack_allocator_destroy :: proc( using self : StackAllocator )
|
|
{
|
|
free( self.base, backing )
|
|
}
|
|
|
|
stack_allocator_init_via_memory :: proc( memory : []byte ) -> ( stack : StackAllocator )
|
|
{
|
|
header_size := size_of(StackAllocatorBase)
|
|
|
|
if len(memory) < (header_size + Kilobyte) {
|
|
verify(false, "Assigning a stack allocator less than a kilobyte of space")
|
|
return
|
|
}
|
|
|
|
stack.base = cast( ^StackAllocatorBase) & memory[0]
|
|
stack.size = len(memory) - header_size
|
|
stack.data = cast( [^]byte ) (cast( [^]StackAllocatorBase) stack.base)[ 1:]
|
|
|
|
stack.top = cast( ^StackAllocatorHeader) stack.data
|
|
stack.bottom = stack.top
|
|
return
|
|
}
|
|
|
|
stack_allocator_push :: proc( using self : StackAllocator, block_size, alignment : int, zero_memory : bool ) -> ( []byte, AllocatorError )
|
|
{
|
|
// TODO(Ed): Make sure first push is fine.
|
|
verify( block_size > Kilobyte, "Attempted to push onto the stack less than a Kilobyte")
|
|
top_block_ptr := memory_after_header( top )
|
|
|
|
theoretical_size := cast(int) (uintptr(top_block_ptr) + uintptr(block_size) - uintptr(bottom))
|
|
if theoretical_size > size {
|
|
// TODO(Ed) : Check if backing allocator supports resize, if it does attempt to grow.
|
|
return nil, .Out_Of_Memory
|
|
}
|
|
|
|
top_block_slice := slice_ptr( top_block_ptr, top.block_size )
|
|
next_spot := uintptr( top_block_ptr) + uintptr(top.block_size)
|
|
|
|
header_offset_pad := calc_padding_with_header( uintptr(next_spot), uintptr(alignment), size_of(StackAllocatorHeader) )
|
|
header := cast( ^StackAllocatorHeader) (next_spot + uintptr(header_offset_pad) - uintptr(size_of( StackAllocatorHeader)))
|
|
header.padding = header_offset_pad
|
|
header.prev = last
|
|
header.block_size = block_size
|
|
|
|
curr_block_ptr := memory_after_header( header )
|
|
curr_block := slice_ptr( curr_block_ptr, block_size )
|
|
|
|
curr_used := cast(int) (uintptr(curr_block_ptr) + uintptr(block_size) - uintptr(self.top))
|
|
self.peak_used += max( peak_used, curr_used )
|
|
|
|
dll_push_back( & base.links.last, header )
|
|
|
|
if zero_memory {
|
|
slice.zero( curr_block )
|
|
}
|
|
|
|
return curr_block, .None
|
|
}
|
|
|
|
stack_allocator_resize_top :: proc( using self : StackAllocator, new_block_size, alignment : int, zero_memory : bool ) -> AllocatorError
|
|
{
|
|
verify( new_block_size > Kilobyte, "Attempted to resize the last pushed on the stack to less than a Kilobyte")
|
|
top_block_ptr := memory_after_header( top )
|
|
|
|
theoretical_size := cast(int) (uintptr(top_block_ptr) + uintptr(top.block_size) - uintptr(bottom))
|
|
if theoretical_size > size {
|
|
// TODO(Ed) : Check if backing allocator supports resize, if it does attempt to grow.
|
|
return .Out_Of_Memory
|
|
}
|
|
|
|
if zero_memory && new_block_size > top.block_size {
|
|
added_ptr := top_block_ptr[ top.block_size:]
|
|
added_slice := slice_ptr( added_ptr, new_block_size - top.block_size )
|
|
slice.zero( added_slice )
|
|
}
|
|
|
|
top.block_size = new_block_size
|
|
return .None
|
|
}
|
|
|
|
stack_allocator_pop :: proc( using self : StackAllocator ) {
|
|
base.links.top = top.prev
|
|
base.links.top.next = nil
|
|
}
|
|
|
|
stack_allocator_proc :: proc(
|
|
allocator_data : rawptr,
|
|
mode : AllocatorMode,
|
|
block_size : int,
|
|
alignment : int,
|
|
old_memory : rawptr,
|
|
old_size : int,
|
|
location : SourceCodeLocation = #caller_location
|
|
) -> ([]byte, AllocatorError)
|
|
{
|
|
stack := StackAllocator { cast( ^StackAllocatorBase) allocator_data }
|
|
|
|
if stack.data == nil {
|
|
return nil, AllocatorError.Invalid_Argument
|
|
}
|
|
|
|
switch mode
|
|
{
|
|
case .Alloc, .Alloc_Non_Zeroed:
|
|
{
|
|
return stack_allocator_push( stack, block_size, alignment, mode == .Alloc )
|
|
}
|
|
case .Free:
|
|
{
|
|
if old_memory == nil {
|
|
return nil, .None
|
|
}
|
|
|
|
start := uintptr(stack.data)
|
|
end := start + uintptr(block_size)
|
|
curr_addr := uintptr(old_memory)
|
|
|
|
verify( start <= curr_addr && curr_addr < end, "Out of bounds memory address passed to stack allocator (free)" )
|
|
|
|
block_ptr := memory_after_header( stack.last )
|
|
|
|
if curr_addr >= start + uintptr(block_ptr) {
|
|
return nil, .None
|
|
}
|
|
|
|
dll_pop_back( & stack.last )
|
|
}
|
|
case .Free_All:
|
|
// TODO(Ed) : Review that we don't have any header issues with the reset.
|
|
stack.bottom = stack.top
|
|
stack.top.next = nil
|
|
stack.top.block_size = 0
|
|
|
|
case .Resize, .Resize_Non_Zeroed:
|
|
{
|
|
// Check if old_memory is at the first on the stack, if it is, just grow its size
|
|
// Otherwise, log that the user cannot resize stack items that are not at the top of the stack allocated.
|
|
if old_memory == nil {
|
|
return stack_allocator_push(stack, block_size, alignment, mode == .Resize )
|
|
}
|
|
if block_size == 0 {
|
|
return nil, .None
|
|
}
|
|
|
|
start := uintptr(stack.data)
|
|
end := start + uintptr(block_size)
|
|
curr_addr := uintptr(old_memory)
|
|
|
|
verify( start <= curr_addr && curr_addr < end, "Out of bounds memory address passed to stack allocator (resize)" )
|
|
|
|
block_ptr := memory_after_header( stack.top )
|
|
if block_ptr != old_memory {
|
|
ensure( false, "Attempted to reszie a block of memory on the stack other than top most" )
|
|
return nil, .None
|
|
}
|
|
|
|
if old_size == block_size {
|
|
return byte_slice( old_memory, block_size ), .None
|
|
}
|
|
|
|
stack_allocator_resize_top( stack, block_size, alignment, mode == .Resize )
|
|
return byte_slice( block_ptr, block_size ), .None
|
|
}
|
|
case .Query_Features:
|
|
{
|
|
feature_flags := ( ^AllocatorModeSet)(old_memory)
|
|
if feature_flags != nil {
|
|
(feature_flags ^) = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features}
|
|
}
|
|
return nil, .None
|
|
}
|
|
case .Query_Info:
|
|
{
|
|
return nil, .Mode_Not_Implemented
|
|
}
|
|
}
|
|
|
|
return nil, .None
|
|
}
|
|
|
|
//endregion Stack Allocator
|