Browse Source

[mem]: Adjust the docs on the buddy allocator

flysand7 11 months ago
parent
commit
e90f5d2528
2 changed files with 197 additions and 13 deletions
  1. 192 13
      core/mem/allocators.odin
  2. 5 0
      core/mem/doc.odin

+ 192 - 13
core/mem/allocators.odin

@@ -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
 arena allocator does not keep track of any metadata associated with the
 allocations and their locations, it is impossible to free individual
 allocations and their locations, it is impossible to free individual
 allocations.
 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)
 @(require_results)
 arena_allocator :: proc(arena: ^Arena) -> Allocator {
 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
 This procedure allocates `size` bytes of memory aligned on a boundary specified
 by `alignment`. The allocated memory region is not explicitly zero-initialized.
 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
 This procedure allocates `size` bytes of memory aligned on a boundary specified
 by `alignment`. The allocated memory region is not explicitly zero-initialized.
 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`.
 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
 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
 start of each allocated memory region. Each header contains the amount of
 padding bytes between that header and end of the previous allocation.
 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)
 @(require_results)
 small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator {
 small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator {
@@ -1608,6 +1606,9 @@ dynamic_arena_allocator :: proc(a: ^Dynamic_Arena) -> Allocator {
 
 
 /*
 /*
 Destroy a dynamic arena.
 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_destroy :: proc(a: ^Dynamic_Arena) {
 	dynamic_arena_free_all(a)
 	dynamic_arena_free_all(a)
@@ -1646,12 +1647,28 @@ _dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_locatio
 	return
 	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)
 @(private, require_results)
 dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
 dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
 	data, err := dynamic_arena_alloc_bytes(a, size, loc)
 	data, err := dynamic_arena_alloc_bytes(a, size, loc)
 	return raw_data(data), err
 	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)
 @(require_results)
 dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
 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)
 	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
 	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)
 @(require_results)
 dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) {
 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)
 	data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc)
 	return raw_data(data), err
 	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)
 @(require_results)
 dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
 dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
 	n := align_formula(size, a.alignment)
 	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
 	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) {
 dynamic_arena_reset :: proc(a: ^Dynamic_Arena, loc := #caller_location) {
 	if a.current_block != nil {
 	if a.current_block != nil {
 		append(&a.unused_blocks, a.current_block, loc=loc)
 		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.
 	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_free_all :: proc(a: ^Dynamic_Arena, loc := #caller_location) {
 	dynamic_arena_reset(a)
 	dynamic_arena_reset(a)
 	for block in a.unused_blocks {
 	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)
 	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)
 @(require_results)
 dynamic_arena_resize :: proc(
 dynamic_arena_resize :: proc(
 	a: ^Dynamic_Arena,
 	a: ^Dynamic_Arena,
@@ -1732,6 +1793,22 @@ dynamic_arena_resize :: proc(
 	return raw_data(bytes), err
 	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)
 @(require_results)
 dynamic_arena_resize_bytes :: proc(
 dynamic_arena_resize_bytes :: proc(
 	a: ^Dynamic_Arena,
 	a: ^Dynamic_Arena,
@@ -1750,6 +1827,22 @@ dynamic_arena_resize_bytes :: proc(
 	return bytes, err
 	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)
 @(require_results)
 dynamic_arena_resize_non_zeroed :: proc(
 dynamic_arena_resize_non_zeroed :: proc(
 	a: ^Dynamic_Arena,
 	a: ^Dynamic_Arena,
@@ -1762,6 +1855,22 @@ dynamic_arena_resize_non_zeroed :: proc(
 	return raw_data(bytes), err
 	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)
 @(require_results)
 dynamic_arena_resize_bytes_non_zeroed :: proc(
 dynamic_arena_resize_bytes_non_zeroed :: proc(
 	a: ^Dynamic_Arena,
 	a: ^Dynamic_Arena,
@@ -1823,17 +1932,25 @@ dynamic_arena_allocator_proc :: proc(
 }
 }
 
 
 
 
-
+/*
+Header of the buddy block.
+*/
 Buddy_Block :: struct #align(align_of(uint)) {
 Buddy_Block :: struct #align(align_of(uint)) {
 	size:    uint,
 	size:    uint,
 	is_free: bool,
 	is_free: bool,
 }
 }
 
 
+/*
+Obtain the next buddy block.
+*/
 @(require_results)
 @(require_results)
 buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block {
 buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block {
 	return (^Buddy_Block)(([^]byte)(block)[block.size:])
 	return (^Buddy_Block)(([^]byte)(block)[block.size:])
 }
 }
 
 
+/*
+Split the block into two, by truncating the given block to a given size.
+*/
 @(require_results)
 @(require_results)
 buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block {
 buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block {
 	block := block
 	block := block
@@ -1854,6 +1971,9 @@ buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block {
 	return nil
 	return nil
 }
 }
 
 
+/*
+Coalesce contiguous blocks in a range of blocks into one.
+*/
 buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) {
 buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) {
 	for {
 	for {
 		// Keep looping until there are no more buddies to coalesce
 		// 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)
 @(require_results)
 buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Block {
 buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Block {
 	assert(size != 0)
 	assert(size != 0)
@@ -1945,6 +2068,9 @@ buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Bl
 	return nil
 	return nil
 }
 }
 
 
+/*
+The buddy allocator data.
+*/
 Buddy_Allocator :: struct {
 Buddy_Allocator :: struct {
 	head: ^Buddy_Block,
 	head: ^Buddy_Block,
 	tail: ^Buddy_Block,
 	tail: ^Buddy_Block,
@@ -1954,7 +2080,12 @@ Buddy_Allocator :: struct {
 /*
 /*
 Buddy allocator.
 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)
 @(require_results)
 buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator {
 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) {
 buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint, loc := #caller_location) {
 	assert(data != nil)
 	assert(data != nil)
 	assert(is_power_of_two(uintptr(len(data))), "Size of the backing buffer must be power of two", loc)
 	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
 	b.alignment = alignment
 }
 }
 
 
+/*
+Get required block size to fit in the allocation as well as the alignment padding.
+*/
 @(require_results)
 @(require_results)
 buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint {
 buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint {
 	size := size
 	size := size
@@ -1993,12 +2133,26 @@ buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint {
 	return actual_size
 	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)
 @(require_results)
 buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) {
 buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) {
 	bytes, err := buddy_allocator_alloc_bytes(b, size)
 	bytes, err := buddy_allocator_alloc_bytes(b, size)
 	return raw_data(bytes), err
 	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)
 @(require_results)
 buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) {
 buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) {
 	bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size)
 	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
 	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)
 @(require_results)
 buddy_allocator_alloc_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) {
 buddy_allocator_alloc_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) {
 	bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size)
 	bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size)
 	return raw_data(bytes), err
 	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)
 @(require_results)
 buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) {
 buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) {
 	if size != 0 {
 	if size != 0 {
@@ -2034,6 +2202,14 @@ buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint)
 	return nil, nil
 	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 {
 buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Error {
 	if ptr != nil {
 	if ptr != nil {
 		if !(b.head <= ptr && ptr <= b.tail) {
 		if !(b.head <= ptr && ptr <= b.tail) {
@@ -2046,6 +2222,9 @@ buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Erro
 	return nil
 	return nil
 }
 }
 
 
+/*
+Free all memory to the buddy allocator.
+*/
 buddy_allocator_free_all :: proc(b: ^Buddy_Allocator) {
 buddy_allocator_free_all :: proc(b: ^Buddy_Allocator) {
 	alignment := b.alignment
 	alignment := b.alignment
 	head := ([^]byte)(b.head)
 	head := ([^]byte)(b.head)

+ 5 - 0
core/mem/doc.odin

@@ -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
 happens all called procedures will inherit the new context and use the same
 allocator.
 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
 ## Alignment
 
 
 An address is said to be *aligned to `N` bytes*, if the addresses's numeric
 An address is said to be *aligned to `N` bytes*, if the addresses's numeric