[mem]: Adjust the docs on the buddy allocator

This commit is contained in:
flysand7
2024-09-14 10:03:04 +11:00
parent f16ed256ea
commit e90f5d2528
2 changed files with 197 additions and 13 deletions

View File

@@ -112,6 +112,12 @@ allocation occupies the next adjacent region of memory in the buffer. Since
arena allocator does not keep track of any metadata associated with the
allocations and their locations, it is impossible to free individual
allocations.
The arena allocator can be used for temporary allocations in frame-based memory
management. Games are one example of such applications. A global arena can be
used for any temporary memory allocations, and at the end of each frame all
temporary allocations are freed. Since no temporary object is going to live
longer than a frame, no lifetimes are violated.
*/
@(require_results)
arena_allocator :: proc(arena: ^Arena) -> Allocator {
@@ -423,7 +429,7 @@ scratch_alloc_bytes :: proc(
}
/*
Allocate memory from scratch allocator.
Allocate non-initialized memory from scratch allocator.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment`. The allocated memory region is not explicitly zero-initialized.
@@ -441,7 +447,7 @@ scratch_alloc_non_zeroed :: proc(
}
/*
Allocate memory from scratch allocator.
Allocate non-initialized memory from scratch allocator.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment`. The allocated memory region is not explicitly zero-initialized.
@@ -499,7 +505,7 @@ scratch_alloc_bytes_non_zeroed :: proc(
}
/*
Free memory to scratch allocator.
Free memory to the scratch allocator.
This procedure frees the memory region allocated at pointer `ptr`.
@@ -1176,14 +1182,6 @@ Each subsequent allocation will get the next adjacent memory region.
The metadata is stored in the allocation headers, that are located before the
start of each allocated memory region. Each header contains the amount of
padding bytes between that header and end of the previous allocation.
## Properties
**Performance characteristics**: TODO
**Has a backing allocator**: No
**Saves metadata**: Allocation header before each allocation.
*/
@(require_results)
small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator {
@@ -1608,6 +1606,9 @@ dynamic_arena_allocator :: proc(a: ^Dynamic_Arena) -> Allocator {
/*
Destroy a dynamic arena.
This procedure frees all allocations, made on a dynamic arena, including the
unused blocks, as well as the arrays for storing blocks.
*/
dynamic_arena_destroy :: proc(a: ^Dynamic_Arena) {
dynamic_arena_free_all(a)
@@ -1646,12 +1647,28 @@ _dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_locatio
return
}
/*
Allocate memory from a dynamic arena.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment` from a dynamic arena `a`. The allocated memory is
zero-initialized. This procedure returns a pointer to the newly allocated memory
region.
*/
@(private, require_results)
dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
data, err := dynamic_arena_alloc_bytes(a, size, loc)
return raw_data(data), err
}
/*
Allocate memory from a dynamic arena.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment` from a dynamic arena `a`. The allocated memory is
zero-initialized. This procedure returns a slice of the newly allocated memory
region.
*/
@(require_results)
dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
bytes, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
@@ -1661,12 +1678,28 @@ dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_l
return bytes, err
}
/*
Allocate non-initialized memory from a dynamic arena.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment` from a dynamic arena `a`. The allocated memory is not explicitly
zero-initialized. This procedure returns a pointer to the newly allocated
memory region.
*/
@(require_results)
dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
return raw_data(data), err
}
/*
Allocate non-initialized memory from a dynamic arena.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment` from a dynamic arena `a`. The allocated memory is not explicitly
zero-initialized. This procedure returns a slice of the newly allocated
memory region.
*/
@(require_results)
dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
n := align_formula(size, a.alignment)
@@ -1696,6 +1729,12 @@ dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc :
return ([^]byte)(memory)[:size], nil
}
/*
Reset the dynamic arena.
This procedure frees all the allocations, owned by the dynamic arena, excluding
the unused blocks.
*/
dynamic_arena_reset :: proc(a: ^Dynamic_Arena, loc := #caller_location) {
if a.current_block != nil {
append(&a.unused_blocks, a.current_block, loc=loc)
@@ -1712,6 +1751,12 @@ dynamic_arena_reset :: proc(a: ^Dynamic_Arena, loc := #caller_location) {
a.bytes_left = 0 // Make new allocations call `_dynamic_arena_cycle_new_block` again.
}
/*
Free all memory from a dynamic arena.
This procedure frees all the allocations, owned by the dynamic arena, including
the unused blocks.
*/
dynamic_arena_free_all :: proc(a: ^Dynamic_Arena, loc := #caller_location) {
dynamic_arena_reset(a)
for block in a.unused_blocks {
@@ -1720,6 +1765,22 @@ dynamic_arena_free_all :: proc(a: ^Dynamic_Arena, loc := #caller_location) {
clear(&a.unused_blocks)
}
/*
Resize an allocation.
This procedure resizes a memory region, defined by its location, `old_memory`,
and its size, `old_size` to have a size `size` and alignment `alignment`. The
newly allocated memory, if any is zero-initialized.
If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`,
allocating a memory region `size` bytes in size, aligned on a boundary specified
by `alignment`.
If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing
the memory region located at an address specified by `old_memory`.
This procedure returns the pointer to the resized memory region.
*/
@(require_results)
dynamic_arena_resize :: proc(
a: ^Dynamic_Arena,
@@ -1732,6 +1793,22 @@ dynamic_arena_resize :: proc(
return raw_data(bytes), err
}
/*
Resize an allocation.
This procedure resizes a memory region, specified by `old_data`, to have a size
`size` and alignment `alignment`. The newly allocated memory, if any is
zero-initialized.
If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`,
allocating a memory region `size` bytes in size, aligned on a boundary specified
by `alignment`.
If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the
memory region located at an address specified by `old_memory`.
This procedure returns the slice of the resized memory region.
*/
@(require_results)
dynamic_arena_resize_bytes :: proc(
a: ^Dynamic_Arena,
@@ -1750,6 +1827,22 @@ dynamic_arena_resize_bytes :: proc(
return bytes, err
}
/*
Resize an allocation without zero-initialization.
This procedure resizes a memory region, defined by its location, `old_memory`,
and its size, `old_size` to have a size `size` and alignment `alignment`. The
newly allocated memory, if any is not explicitly zero-initialized.
If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`,
allocating a memory region `size` bytes in size, aligned on a boundary specified
by `alignment`.
If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the
memory region located at an address specified by `old_memory`.
This procedure returns the pointer to the resized memory region.
*/
@(require_results)
dynamic_arena_resize_non_zeroed :: proc(
a: ^Dynamic_Arena,
@@ -1762,6 +1855,22 @@ dynamic_arena_resize_non_zeroed :: proc(
return raw_data(bytes), err
}
/*
Resize an allocation.
This procedure resizes a memory region, specified by `old_data`, to have a size
`size` and alignment `alignment`. The newly allocated memory, if any is not
explicitly zero-initialized.
If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`,
allocating a memory region `size` bytes in size, aligned on a boundary specified
by `alignment`.
If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing
the memory region located at an address specified by `old_memory`.
This procedure returns the slice of the resized memory region.
*/
@(require_results)
dynamic_arena_resize_bytes_non_zeroed :: proc(
a: ^Dynamic_Arena,
@@ -1823,17 +1932,25 @@ dynamic_arena_allocator_proc :: proc(
}
/*
Header of the buddy block.
*/
Buddy_Block :: struct #align(align_of(uint)) {
size: uint,
is_free: bool,
}
/*
Obtain the next buddy block.
*/
@(require_results)
buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block {
return (^Buddy_Block)(([^]byte)(block)[block.size:])
}
/*
Split the block into two, by truncating the given block to a given size.
*/
@(require_results)
buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block {
block := block
@@ -1854,6 +1971,9 @@ buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block {
return nil
}
/*
Coalesce contiguous blocks in a range of blocks into one.
*/
buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) {
for {
// Keep looping until there are no more buddies to coalesce
@@ -1887,6 +2007,9 @@ buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) {
}
}
/*
Find the best block for storing a given size in a range of blocks.
*/
@(require_results)
buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Block {
assert(size != 0)
@@ -1945,6 +2068,9 @@ buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Bl
return nil
}
/*
The buddy allocator data.
*/
Buddy_Allocator :: struct {
head: ^Buddy_Block,
tail: ^Buddy_Block,
@@ -1954,7 +2080,12 @@ Buddy_Allocator :: struct {
/*
Buddy allocator.
TODO
The buddy allocator is a type of allocator that splits the backing buffer into
multiple regions called buddy blocks. Initially, the allocator only has one
block with the size of the backing buffer. Upon each allocation, the allocator
finds the smallest block that can fit the size of requested memory region, and
splits the block according to the allocation size. If no block can be found,
the contiguous free blocks are coalesced and the search is performed again.
*/
@(require_results)
buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator {
@@ -1964,6 +2095,12 @@ buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator {
}
}
/*
Initialize the buddy allocator.
This procedure initializes the buddy allocator `b` with a backing buffer `data`
and block alignment specified by `alignment`.
*/
buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint, loc := #caller_location) {
assert(data != nil)
assert(is_power_of_two(uintptr(len(data))), "Size of the backing buffer must be power of two", loc)
@@ -1981,6 +2118,9 @@ buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint,
b.alignment = alignment
}
/*
Get required block size to fit in the allocation as well as the alignment padding.
*/
@(require_results)
buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint {
size := size
@@ -1993,12 +2133,26 @@ buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint {
return actual_size
}
/*
Allocate memory from a buddy allocator.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment`. The allocated memory region is zero-initialized. This procedure
returns a pointer to the allocated memory region.
*/
@(require_results)
buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) {
bytes, err := buddy_allocator_alloc_bytes(b, size)
return raw_data(bytes), err
}
/*
Allocate memory from a buddy allocator.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment`. The allocated memory region is zero-initialized. This procedure
returns a slice of the allocated memory region.
*/
@(require_results)
buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) {
bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size)
@@ -2008,12 +2162,26 @@ buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte,
return bytes, err
}
/*
Allocate non-initialized memory from a buddy allocator.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment`. The allocated memory region is not explicitly zero-initialized.
This procedure returns a pointer to the allocated memory region.
*/
@(require_results)
buddy_allocator_alloc_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) {
bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size)
return raw_data(bytes), err
}
/*
Allocate non-initialized memory from a buddy allocator.
This procedure allocates `size` bytes of memory aligned on a boundary specified
by `alignment`. The allocated memory region is not explicitly zero-initialized.
This procedure returns a slice of the allocated memory region.
*/
@(require_results)
buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) {
if size != 0 {
@@ -2034,6 +2202,14 @@ buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint)
return nil, nil
}
/*
Free memory to the buddy allocator.
This procedure frees the memory region allocated at pointer `ptr`.
If `ptr` is not the latest allocation and is not a leaked allocation, this
operation is a no-op.
*/
buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Error {
if ptr != nil {
if !(b.head <= ptr && ptr <= b.tail) {
@@ -2046,6 +2222,9 @@ buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Erro
return nil
}
/*
Free all memory to the buddy allocator.
*/
buddy_allocator_free_all :: proc(b: ^Buddy_Allocator) {
alignment := b.alignment
head := ([^]byte)(b.head)

View File

@@ -48,6 +48,11 @@ Operations such as `new`, `free` and `delete` by default will use
happens all called procedures will inherit the new context and use the same
allocator.
We will define one concept to simplify the description of some allocator-related
procedures, which is ownership. If the memory was allocated via a specific
allocator, that allocator is said to be the *owner* of that memory region. To
note, unlike Rust, in Odin the memory ownership model is not strict.
## Alignment
An address is said to be *aligned to `N` bytes*, if the addresses's numeric