From c719a86312813c1eed8793aafe6f98eb5ed1b3e0 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sun, 8 Sep 2024 10:58:40 +1100 Subject: [PATCH] [mem]: Document alloc.odin --- core/mem/alloc.odin | 577 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 530 insertions(+), 47 deletions(-) diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index 558e810e3..dbf9af9b2 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -2,66 +2,272 @@ package mem import "base:runtime" -// NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle. +//NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle. + +/* +A request to allocator procedure. + +This type represents a type of allocation request made to an allocator +procedure. There is one allocator procedure per allocator, and this value is +used to discriminate between different functions of the allocator. + +The type is defined as follows: + + Allocator_Mode :: enum byte { + Alloc, + Alloc_Non_Zeroed, + Free, + Free_All, + Resize, + Resize_Non_Zeroed, + Query_Features, + } + +Depending on which value is used, the allocator procedure will perform different +functions: + +- `Alloc`: Allocates a memory region with a given `size` and `alignment`. +- `Alloc_Non_Zeroed`: Same as `Alloc` without explicit zero-initialization of + the memory region. +- `Free`: Free a memory region located at address `ptr` with a given `size`. +- `Free_All`: Free all memory allocated using this allocator. +- `Resize`: Resize a memory region located at address `old_ptr` with size + `old_size` to be `size` bytes in length and have the specified `alignment`, + in case a re-alllocation occurs. +- `Resize_Non_Zeroed`: Same as `Resize`, without explicit zero-initialization. + +*/ Allocator_Mode :: runtime.Allocator_Mode -/* -Allocator_Mode :: enum byte { - Alloc, - Free, - Free_All, - Resize, - Query_Features, - Alloc_Non_Zeroed, - Resize_Non_Zeroed, -} -*/ +/* +A set of allocator features. + +This type represents values that contain a set of features an allocator has. +Currently the type is defined as follows: + + Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; +*/ Allocator_Mode_Set :: runtime.Allocator_Mode_Set -/* -Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; -*/ +/* +Allocator information. + +This type represents information about a given allocator at a specific point +in time. Currently the type is defined as follows: + + Allocator_Query_Info :: struct { + pointer: rawptr, + size: Maybe(int), + alignment: Maybe(int), + } + +- `pointer`: Pointer to a backing buffer. +- `size`: Size of the backing buffer. +- `alignment`: The allocator's alignment. + +If not applicable, any of these fields may be `nil`. +*/ Allocator_Query_Info :: runtime.Allocator_Query_Info -/* -Allocator_Query_Info :: struct { - pointer: rawptr, - size: Maybe(int), - alignment: Maybe(int), -} -*/ -Allocator_Error :: runtime.Allocator_Error /* -Allocator_Error :: enum byte { - None = 0, - Out_Of_Memory = 1, - Invalid_Pointer = 2, - Invalid_Argument = 3, - Mode_Not_Implemented = 4, -} +An allocation request error. + +This type represents error values the allocators may return upon requests. + + Allocator_Error :: enum byte { + None = 0, + Out_Of_Memory = 1, + Invalid_Pointer = 2, + Invalid_Argument = 3, + Mode_Not_Implemented = 4, + } + +The meaning of the errors is as follows: + +- `None`: No error. +- `Out_Of_Memory`: Either: + 1. The allocator has ran out of the backing buffer, or the requested + allocation size is too large to fit into a backing buffer. + 2. The operating system error during memory allocation. + 3. The backing allocator was used to allocate a new backing buffer and the + backing allocator returned Out_Of_Memory. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: Can occur if one of the arguments makes it impossible to + satisfy a request (i.e. having alignment larger than the backing buffer + of the allocation). +- `Mode_Not_Implemented`: The allocator does not support the specified + operation. For example, an arena does not support freeing individual + allocations. +*/ +Allocator_Error :: runtime.Allocator_Error + +/* +The allocator procedure. + +This type represents allocation procedures. An allocation procedure is a single +procedure, implementing all allocator functions such as allocating the memory, +freeing the memory, etc. + +Currently the type is defined as follows: + + Allocator_Proc :: #type proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + location: Source_Code_Location = #caller_location, + ) -> ([]byte, Allocator_Error); + +The function of this procedure and the meaning of parameters depends on the +value of the `mode` parameter. + +## 1. `.Alloc`, `.Alloc_Non_Zeroed` + +Allocates a memory region of size `size`, aligned on a boundary specified by +`alignment`. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Alloc` or `.Alloc_Non_Zeroed`. +- `size`: The desired size of the memory region. +- `alignment`: The desired alignmnet of the allocation. +- `old_memory`: Unused, should be `nil`. +- `old_size`: Unused, should be 0. + +**Returns**: +1. The memory region, if allocated successfully, or `nil` otherwise. +2. An error, if allocation failed. + +**Note**: Some allocators may return `nil`, even if no error is returned. +Always check both the error and the allocated buffer. + +Same as `.Alloc`. + +## 2. `Free` + +Frees a memory region located at the address specified by `old_memory`. If the +allocator does not track sizes of allocations, the size should be specified in +the `old_size` parameter. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Free`. +- `size`: Unused, should be 0. +- `alignment`: Unused, should be 0. +- `old_memory`: Pointer to the memory region to free. +- `old_size`: The size of the memory region to free. This parameter is optional + if the allocator keeps track of the sizes of allocations. + +**Returns**: +1. `nil` +2. Error, if freeing failed. + +## 3. `Free_All` + +Frees all allocations, associated with the allocator, making it available for +further allocations using the same backing buffers. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Free_All`. +- `size`: Unused, should be 0. +- `alignment`: Unused, should be 0. +- `old_memory`: Unused, should be `nil`. +- `old_size`: Unused, should be `0`. + +**Returns**: +1. `nil`. +2. Error, if freeing failed. + +## 4. `Resize`, `Resize_Non_Zeroed` + +Resizes the memory region, of the size `old_size` located at the address +specified by `old_memory` to have the new size `size`. The slice of the new +memory region is returned from the procedure. The allocator may attempt to +keep the new memory region at the same address as the previous allocation, +however no such guarantee is made. Do not assume the new memory region will +be at the same address as the old memory region. + +If `old_memory` pointer is `nil`, this function acts just like `.Alloc` or +`.Alloc_Non_Zeroed`, using `size` and `alignment` to allocate a new memory +region. + +If `new_size` is `nil`, the procedure acts just like `.Free`, freeing the +memory region `old_size` bytes in length, located at the address specified by +`old_memory`. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Resize` or `.Resize_All`. +- `size`: The desired new size of the memory region. +- `alignment`: The alignment of the new memory region, if its allocated +- `old_memory`: The pointer to the memory region to resize. +- `old_size`: The size of the memory region to resize. If the allocator + keeps track of the sizes of allocations, this parameter is optional. + +**Returns**: +1. The slice of the memory region after resize operation, if successfull, + `nil` otherwise. +2. An error, if the resize failed. + +**Note**: Some allocators may return `nil`, even if no error is returned. +Always check both the error and the allocated buffer. */ Allocator_Proc :: runtime.Allocator_Proc -/* -Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location: Source_Code_Location = #caller_location) -> ([]byte, Allocator_Error); -*/ +/* +Allocator. + +This type represents generic interface for all allocators. Currently this type +is defined as follows: + + Allocator :: struct { + procedure: Allocator_Proc, + data: rawptr, + } + +- `procedure`: Pointer to the allocation procedure. +- `data`: Pointer to the allocator data. +*/ Allocator :: runtime.Allocator -/* -Allocator :: struct { - procedure: Allocator_Proc, - data: rawptr, -} -*/ +/* +Default alignment. + +This value is the default alignment for all platforms that is used, if the +alignment is not specified explicitly. +*/ DEFAULT_ALIGNMENT :: 2*align_of(rawptr) +/* +Default page size. + +This value is the default page size for the current platform. +*/ DEFAULT_PAGE_SIZE :: 64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 else 16 * 1024 when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 else 4 * 1024 +/* +Allocate memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Pointer to the allocated memory, or `nil` if allocation failed. +2. Error, if the allocation failed. +*/ @(require_results) alloc :: proc( size: int, @@ -73,6 +279,21 @@ alloc :: proc( return raw_data(data), err } +/* +Allocate memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Slice of the allocated memory region, or `nil` if allocation failed. +2. Error, if the allocation failed. +*/ @(require_results) alloc_bytes :: proc( size: int, @@ -83,6 +304,22 @@ alloc_bytes :: proc( return runtime.mem_alloc(size, alignment, allocator, loc) } +/* +Allocate non-zeroed memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. This procedure +does not explicitly zero-initialize allocated memory region. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Slice of the allocated memory region, or `nil` if allocation failed. +2. Error, if the allocation failed. +*/ @(require_results) alloc_bytes_non_zeroed :: proc( size: int, @@ -93,6 +330,16 @@ alloc_bytes_non_zeroed :: proc( return runtime.mem_alloc_non_zeroed(size, alignment, allocator, loc) } +/* +Free memory. + +This procedure frees memory region located at the address, specified by `ptr`, +allocated from the allocator specified by `allocator`. + +**Inputs**: +- `ptr`: Pointer to the memory region to free. +- `allocator`: The allocator to free to. +*/ free :: proc( ptr: rawptr, allocator := context.allocator, @@ -101,15 +348,42 @@ free :: proc( return runtime.mem_free(ptr, allocator, loc) } +/* +Free a memory region. + +This procedure frees `size` bytes of memory region located at the address, +specified by `ptr`, allocated from the allocator specified by `allocator`. + +**Inputs**: +- `ptr`: Pointer to the memory region to free. +- `size`: The size of the memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. +*/ free_with_size :: proc( ptr: rawptr, - byte_count: int, + size: int, allocator := context.allocator, loc := #caller_location, ) -> Allocator_Error { - return runtime.mem_free_with_size(ptr, byte_count, allocator, loc) + return runtime.mem_free_with_size(ptr, size, allocator, loc) } +/* +Free a memory region. + +This procedure frees memory region, specified by `bytes`, allocated from the +allocator specified by `allocator`. + +**Inputs**: +- `bytes`: The memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. +*/ free_bytes :: proc( bytes: []byte, allocator := context.allocator, @@ -118,10 +392,45 @@ free_bytes :: proc( return runtime.mem_free_bytes(bytes, allocator, loc) } +/* +Free all allocations. + +This procedure frees all allocations made on the allocator specified by +`allocator` to that allocator, making it available for further allocations. +*/ free_all :: proc(allocator := context.allocator, loc := #caller_location) -> Allocator_Error { return runtime.mem_free_all(allocator, loc) } +/* +Resize a memory region. + +This procedure resizes a memory region, `old_size` bytes in size, located at +the address specified by `ptr`, such that it has a new size, specified by +`new_size` and and is aligned on a boundary specified by `alignment`. + +If the `ptr` parameter is `nil`, `resize()` acts just like `alloc`, allocating +`new_size` bytes, aligned on a boundary specified by `alignment`. + +If the `new_size` parameter is `0`, `resize()` acts just like `free`, freeing +the memory region `old_size` bytes in length, located at the address specified +by `ptr`. + +**Inputs**: +- `ptr`: Pointer to the memory region to resize. +- `old_size`: Size of the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The pointer to the resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Note**: The `alignment` parameter is used to preserve the original alignment +of the allocation, if `resize()` needs to relocate the memory region. Do not +use `resize()` to change the alignment of the allocated memory region. +*/ @(require_results) resize :: proc( ptr: rawptr, @@ -135,6 +444,33 @@ resize :: proc( return raw_data(data), err } +/* +Resize a memory region. + +This procedure resizes a memory region, specified by `old_data`, such that it +has a new size, specified by `new_size` and and is aligned on a boundary +specified by `alignment`. + +If the `old_data` parameter is `nil`, `resize()` acts just like `alloc`, +allocating `new_size` bytes, aligned on a boundary specified by `alignment`. + +If the `new_size` parameter is `0`, `resize()` acts just like `free`, freeing +the memory region specified by `old_data`. + +**Inputs**: +- `old_data`: Pointer to the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Note**: The `alignment` parameter is used to preserve the original alignment +of the allocation, if `resize()` needs to relocate the memory region. Do not +use `resize()` to change the alignment of the allocated memory region. +*/ @(require_results) resize_bytes :: proc( old_data: []byte, @@ -146,6 +482,9 @@ resize_bytes :: proc( return runtime.mem_resize(raw_data(old_data), len(old_data), new_size, alignment, allocator, loc) } +/* +Query allocator features. +*/ @(require_results) query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: Allocator_Mode_Set) { if allocator.procedure != nil { @@ -155,6 +494,9 @@ query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: A return nil } +/* +Query allocator information. +*/ @(require_results) query_info :: proc( pointer: rawptr, @@ -168,6 +510,9 @@ query_info :: proc( return } +/* +Free a string. +*/ delete_string :: proc( str: string, allocator := context.allocator, @@ -176,6 +521,9 @@ delete_string :: proc( return runtime.delete_string(str, allocator, loc) } +/* +Free a cstring. +*/ delete_cstring :: proc( str: cstring, allocator := context.allocator, @@ -184,6 +532,9 @@ delete_cstring :: proc( return runtime.delete_cstring(str, allocator, loc) } +/* +Free a dynamic array. +*/ delete_dynamic_array :: proc( array: $T/[dynamic]$E, loc := #caller_location, @@ -191,6 +542,9 @@ delete_dynamic_array :: proc( return runtime.delete_dynamic_array(array, loc) } +/* +Free a slice. +*/ delete_slice :: proc( array: $T/[]$E, allocator := context.allocator, @@ -199,6 +553,9 @@ delete_slice :: proc( return runtime.delete_slice(array, allocator, loc) } +/* +Free a map. +*/ delete_map :: proc( m: $T/map[$K]$V, loc := #caller_location, @@ -206,6 +563,9 @@ delete_map :: proc( return runtime.delete_map(m, loc) } +/* +Free. +*/ delete :: proc{ delete_string, delete_cstring, @@ -214,6 +574,13 @@ delete :: proc{ delete_map, } +/* +Allocate a new object. + +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer to the allocated object, if allocated +successfully, or `nil` otherwise. +*/ @(require_results) new :: proc( $T: typeid, @@ -223,6 +590,14 @@ new :: proc( return new_aligned(T, align_of(T), allocator, loc) } +/* +Allocate a new object with alignment. + +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer, aligned on a boundary specified by +`alignment` to the allocated object, if allocated successfully, or `nil` +otherwise. +*/ @(require_results) new_aligned :: proc( $T: typeid, @@ -233,6 +608,14 @@ new_aligned :: proc( return runtime.new_aligned(T, alignment, allocator, loc) } +/* +Allocate a new object and initialize it with a value. + +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer, aligned on a boundary specified by +`alignment` to the allocated object, if allocated successfully, or `nil` +otherwise. The allocated object is initialized with `data`. +*/ @(require_results) new_clone :: proc( data: $T, @@ -242,6 +625,13 @@ new_clone :: proc( return runtime.new_clone(data, allocator, loc) } +/* +Allocate a new slice with alignment. + +This procedure allocates a new slice of type `T` with length `len`, aligned +on a boundary specified by `alignment` from an allocator specified by +`allocator`, and returns the allocated slice. +*/ @(require_results) make_aligned :: proc( $T: typeid/[]$E, @@ -253,6 +643,12 @@ make_aligned :: proc( return runtime.make_aligned(T, len, alignment, allocator, loc) } +/* +Allocate a new slice. + +This procedure allocates a new slice of type `T` with length `len`, from an +allocator specified by `allocator`, and returns the allocated slice. +*/ @(require_results) make_slice :: proc( $T: typeid/[]$E, @@ -263,6 +659,12 @@ make_slice :: proc( return runtime.make_slice(T, len, allocator, loc) } +/* +Allocate a dynamic array. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial length and capacity of `0`. +*/ @(require_results) make_dynamic_array :: proc( $T: typeid/[dynamic]$E, @@ -272,6 +674,13 @@ make_dynamic_array :: proc( return runtime.make_dynamic_array(T, allocator, loc) } +/* +Allocate a dynamic array with initial length. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity of `0`, and initial length specified by +`len`. +*/ @(require_results) make_dynamic_array_len :: proc( $T: typeid/[dynamic]$E, @@ -282,6 +691,13 @@ make_dynamic_array_len :: proc( return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc) } +/* +Allocate a dynamic array with initial length and capacity. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity specified by `cap`, and initial length +specified by `len`. +*/ @(require_results) make_dynamic_array_len_cap :: proc( $T: typeid/[dynamic]$E, @@ -293,6 +709,13 @@ make_dynamic_array_len_cap :: proc( return runtime.make_dynamic_array_len_cap(T, len, cap, allocator, loc) } +/* +Allocate a map. + +This procedure creates a map of type `T` with initial capacity specified by +`cap`, that is using an allocator specified by `allocator` as its backing +allocator. +*/ @(require_results) make_map :: proc( $T: typeid/map[$K]$E, @@ -303,6 +726,12 @@ make_map :: proc( return runtime.make_map(T, cap, allocator, loc) } +/* +Allocate a multi pointer. + +This procedure allocates a multipointer of type `T` pointing to `len` elements, +from an allocator specified by `allocator`. +*/ @(require_results) make_multi_pointer :: proc( $T: typeid/[^]$E, @@ -313,6 +742,9 @@ make_multi_pointer :: proc( return runtime.make_multi_pointer(T, len, allocator, loc) } +/* +Allocate. +*/ make :: proc{ make_slice, make_dynamic_array, @@ -322,6 +754,23 @@ make :: proc{ make_multi_pointer, } +/* +Default resize procedure. + +When allocator does not support resize operation, but supports `.Alloc` and +`.Free`, this procedure is used to implement allocator's default behavior on +resize. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region of `old_size` bytes located at `old_memory`. +- If `old_memory` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) default_resize_align :: proc( old_memory: rawptr, @@ -343,6 +792,27 @@ default_resize_align :: proc( return } +/* +Default resize procedure. + +When allocator does not support resize operation, but supports +`.Alloc_Non_Zeroed` and `.Free`, this procedure is used to implement allocator's +default behavior on +resize. + +Unlike `default_resize_align` no new memory is being explicitly +zero-initialized. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region of `old_size` bytes located at `old_memory`. +- If `old_memory` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) default_resize_bytes_align_non_zeroed :: proc( old_data: []byte, @@ -354,6 +824,23 @@ default_resize_bytes_align_non_zeroed :: proc( return _default_resize_bytes_align(old_data, new_size, alignment, false, allocator, loc) } +/* +Default resize procedure. + +When allocator does not support resize operation, but supports `.Alloc` and +`.Free`, this procedure is used to implement allocator's default behavior on +resize. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region specified by `old_data`. +- If `old_data` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) default_resize_bytes_align :: proc( old_data: []byte, @@ -383,16 +870,13 @@ _default_resize_bytes_align :: #force_inline proc( return alloc_bytes_non_zeroed(new_size, alignment, allocator, loc) } } - if new_size == 0 { err := free_bytes(old_data, allocator, loc) return nil, err } - if new_size == old_size { return old_data, .None } - new_memory : []byte err : Allocator_Error if should_zero { @@ -403,7 +887,6 @@ _default_resize_bytes_align :: #force_inline proc( if new_memory == nil || err != nil { return nil, err } - runtime.copy(new_memory, old_data) free_bytes(old_data, allocator, loc) return new_memory, err