Memory

#load "std/memory.bl"

Toolset for manipulation with the system memory.

Memory allocator

Memory allocators are used across the entire program to manage allocated memory resources used in runtime. Since memory allocation can be an expensive operation in some cases, it's good to provide an explicit API, giving information enough to fix bugs, memory leaks and performance issues.

Memory allocator in BL world is just some context Allocator structure used by an allocator handler function.

Functions like alloc and free internally use allocator set in global executable context application_context variable. Global context allocator is by default set to default_allocator and can be changed as needed.

DEFAULT_ALIGNMENT

DEFAULT_ALIGNMENT : usize : 

Default memory allocation alignment.

File: memory.bl

default_allocator

default_allocator :: 

Default memory allocator using libc malloc internally.

File: memory.bl

default_temporary_allocator

default_temporary_allocator :: 

Default temporary allocator instance using pool internally.

The temporary allocator (used i.e. by tprint function) is useful in cases we don't need to keep allocated memory for a long period of time. In general in such a case we allocate, use and free the memory.

Temporary allocated memory does not need to be explicitly freed (in fact temporary allocator does not free individual allocations at all even if free is called). A large memory block is preallocated instead and every allocation lands into this block. Later (at some safe point of execution) when temporary_reset is called, all previous allocations done via temporary allocator became invalid (marked as a free space in preallocated block) and can be reused lated.

When temporary allocator is not needed anymore, temporary_release shall be called to free all internally preallocated blocks.

Example

main :: fn () s32 {
    // Release allocated memory at the end of the scope.
    defer temporary_release();

    loop i := 0; i < 1000; i += 1 {
         // Reset the allocator here (all previous alocation are invalid since now).
        defer temporary_reset();

        // Allocate memory using temporary allocator.
        int_ptr := cast(*s32) alloc(sizeof(s32), alignof(s32), &default_temporary_allocator);

        // Do something here with allocated memory.
        @int_ptr = i;

        // There is no need to free allocated memory here.
    }
    return 0;
}

Note

The temporary allocator internally use pool allocator, see the documentation for more details.

Important

Temporary allocator instance is thread-local, temporary_release should be called at-exit of all threads if it's used.

File: memory.bl

AllocOp

AllocOp :: enum {
    ALLOCATE;
    REALLOCATE;
    FREE;
}

Specify allocator opratation.

Variants

File: memory.bl

AllocFn

AllocFn :: *fn (ctx: *Allocator, operation: AllocOp, old_ptr: *u8, old_size: usize, new_size: usize, alignment: usize, file: string_view, line: s32) (mem: *u8, err: Error
)

Allocator handle function type.

File: memory.bl

Allocator

Allocator :: struct {
    handler: AllocFn;
}

Default allocator context base. This structure can be used as a base structure for any allocator implementation.

Members

File: memory.bl

alloc

alloc :: fn (size: usize, alignment :: , allocator : *Allocator: , loc :: ) (mem: *u8, err: Error
) #inline

Allocate memory using specified allocator. In case the allocator is null, default application context allocator is used. Returns pointer to the newly allocated memory or fails with Error in case the allocation is not possible. The allocation size must be at least 1 byte and the optional alignment must be value of power of two.

File: memory.bl

realloc

realloc :: fn (old_ptr: *u8, old_size: usize, size: usize, alignment :: , allocator : *Allocator: , loc :: ) (mem: *u8, err: Error
) #inline

Reallocate previously allocated memory using the allocator. In case the allocator is null, default application context allocator is used.

This function tries to resize previously allocated block if possible, otherwise new allocation is done and the old data (of old_size) are copied to the newly allocated memory. No allocation and copying is done in case the old_size is the same as size.

Previously allocated block is freed when resize is not possible.

The allocation alignment can be specified but must be the same as previous one in case we try to reallocate.

Note

When called with null old_ptr, this function behaves the same as alloc and the old_size is ignored.

Warning

The exact behaviour depends on the allocator used, in some cases reallocation is not implemented and new allocation is done everytime.

File: memory.bl

free

free :: fn (ptr: *u8, allocator : *Allocator: , loc :: )  #inline

Free memory previously allocated by specific allocator. In case the allocator is null, the default application context allocator is used. The ptr can be null.

File: memory.bl

memcpy

memcpy :: fn (dest: *u8, src: *u8, size: usize) 

Copy memory of defined size from src to dest. Destination and source size must be at least size bytes.

File: memory.bl

memset

memset :: fn (_dest: *u8, v: u8, size: usize) *u8

Set memory to desired value and return dest pointer. Destination size must be at least size bytes.

File: memory.bl

zeromem

zeromem :: fn (dest: *u8, size: usize) *u8

Zero out dest memory of size and return the dest pointer.

File: memory.bl

swap

swap :: fn (first: *?T, second: *T)  #inline

Swaps content of memory at address first and second.

File: memory.bl

is_aligned

is_aligned :: fn (ptr: *?T, alignment: usize) bool #inline

Checks whether passed pointer ptr is properly aligned by alignment.

File: memory.bl

align_ptr_up

align_ptr_up :: fn (p: *u8, alignment: usize) (p: *u8, adjustment: usize
)

Align pointer p to alignment and return adjusted pointer and number of bytes needed for adjustment.

Warning

Cause panic when alignment is not power of two.

File: memory.bl

alloc_slice

alloc_slice :: fn (slice: *[]?T, n: s64, zero_initialized :: , allocator : *Allocator: , loc :: ) 

Allocate heap memory for n elements in the slice. Newly allocated slice can be zero initialized by setting zero_init to true. Custom allocator can be provided as allocator, the application context allocator is used in case the allocator is null.

Allocated memory must be released by free_slice call.

Example

main :: fn () s32 {
    // Allocate slice of 10 numbers
    sl: []s32;
    alloc_slice(&sl, 10);

    loop i := 0; i < sl.len; i += 1 {
        sl[i] = i;
    }

    // release memory allocated by init
    free_slice(&sl);
    return 0;
}

Note

Zero initialization of allocated memory block can be expensive in case of large number of elements.

File: memory.bl

free_slice

free_slice :: fn (slice: *[]?T, allocator : *Allocator: , loc :: ) 

Release slice memory allocated by alloc_slice call.

Warning

The allocator must match the allocator used in alloc_slice.

File: memory.bl

slice_range

slice_range :: fn (slice: []?T, start: s64, end : s64: -1) []T #inline

Create slice subset defined as range <start-index, end-index). Notice that end index is excluded from range, so slice_range(other, 0, other.len) is valid and returns new slice pointing to the same data as other slice, and with same size.

Indexing rules:

start >= 0
start < slice.len
end >= 0
end <= slice.len

Warning

Function cause panic in case combination of start and end is out of slice range.

File: memory.bl

temporary_reset

temporary_reset :: fn ()  #inline

Reduce allocated memory in temporary allocator storage, but keeps biggest allocated chunk for the later use.

Warning

All resources previously allocated by this allocator became invalid after reset.

Note

Call this method i.e. in every event loop (update) iteration.

File: memory.bl

temporary_release

temporary_release :: fn ()  #inline

Release all memory allocated by temporary allocator, this method is supposed to be called at exit of a program or thread.

Warning

All resources previously allocated by this allocator became invalid after release.

Note

Call this method before main returns.

File: memory.bl