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.

Supported Operation

- [x] Allocate
- [x] Reallocate
- [x] Free
- [ ] Release
- [ ] Reset

File: memory.bl

default_temporary_allocator

default_temporary_allocator :: 

Default temporary allocator instance using pool internally (by default set into the application context).

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.

Underlying allocator works with thread-local data storage (it's safe to use it in threads without any synchronization required). But, when used from thread, each thread-local instance must be terminated by temporary_release call. In case you use async module the release is called automatically when internal threads exits.

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.

File: memory.bl

AllocOp

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

Specify allocator opratation.

Variants

File: memory.bl

AllocFn

AllocFn :: *fn (ctx: *Allocator, operation: AllocOp, ptr: *u8, 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 :: DEFAULT_ALIGNMENT, preferred_allocator : *Allocator: null, loc :: #call_location) (mem: *u8, err: Error) #inline

Allocate memory using specified preferred_allocator. In case the preferred_allocator is null, default application context allocator is used.

Returns pointer to the newly allocated memory capable to handle size bytes, 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. Use alignof helper function to resolve the best memory alignment for the required type.

File: memory.bl

realloc

realloc :: fn (ptr: *u8, size: usize, alignment :: DEFAULT_ALIGNMENT, preferred_allocator : *Allocator: null, loc :: #call_location) (mem: *u8, err: Error) #inline

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

Behavior depends on allocator being used. Usually when the previous allocation pointer ptr is specified, the implementation should try to resize already allocated block of memory to the requested size. In case resize is not possible, or is not supported by the allocator, new memory block is allocated to handle size of bytes, and data from the previous block are copied (memory area with size equal the lesser of the new and the old allocation sizes) into the newly allocated block. The previous allocated block is freed.

The allocation size must be at least 1 byte and the optional alignment must be value of power of two. Use alignof helper function to resolve the best memory alignment for the required type.

In case the ptr is null, behavior is supposed to be the same as alloc.

File: memory.bl

new

new :: fn (T: type, noinit :: false, preferred_allocator : *Allocator: null, loc :: #call_location) (ptr: *T, err: Error) #inline

Allocates new object of type T on heap using preferred_allocator, in case the allocator is not specified, the current context allocator is used.

Newly allocated memory block is zero initialized by default unless the noinit is true.

Use free to release allocated memory when it's not needed anymore.

File: memory.bl

new_slice

new_slice :: fn (TElement: type, element_count: s64, noinit :: false, preferred_allocator : *Allocator: null, loc :: #call_location) (slice: []TElement, err: Error)

Allocates new slice of 'element_count' elements of 'TElement' type.

Newly allocated memory block is zero initialized by default unless the noinit is true.

Use free_slice to release allocated memory when it's not needed anymore.

File: memory.bl

free

free :: fn (ptr: *u8, preferred_allocator : *Allocator: null, loc :: #call_location)  #inline

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

File: memory.bl

free_slice

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

Release slice memory allocated by alloc_slice or new_slice call. The input slice is set to the zero initialized state.

Warning: The allocator must match the allocator used by 'alloc_slice' or 'new_slice'.

File: memory.bl

reset_allocator

reset_allocator :: fn (allocator: *Allocator, loc :: #call_location)  #inline

Invoke the reset operation on the allocator. If supported, the allocator should reset its internal state (make all allocation invalid) and reuse already allocated memory eventually for following allocations.

File: memory.bl

release_allocator

release_allocator :: fn (allocator: *Allocator, loc :: #call_location)  #inline

Invoke the release operation on the allocator. If the operation is supported, the allocator should release all resources and free all internally allocated memory.

File: memory.bl

memcpy

memcpy :: fn (destination: *u8, source: *u8, size: usize) 

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

File: memory.bl

memset

memset :: fn (destination: *u8, value: u8, size: usize) *u8

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

File: memory.bl

memmove

memmove :: fn (destination: *u8, source: *u8, size: usize) *u8

Copy 'size' bytes of data from memory location at 'source' to the 'destination' and returns 'destination'.

File: memory.bl

zeromem

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

Zero out destination memory of size and return the original destination pointer. This function is internally optimized to zero the destination memory in 64 bit blocks if possible.

File: memory.bl

zeroinit

zeroinit :: fn (ptr: *?T) *T #inline

Zero initialize memory block at ptr and sizeof(T). Returns passed ptr this might be useful in case of "inline chaining".

Example

foo :: zeroinit(array_push(&my_array));

File: memory.bl

zero_slice

zero_slice :: fn (slice: []?T)  #inline

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 :: true, allocator : *Allocator: null, loc :: #call_location) Error

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 allocated memory
    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

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 application context 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 the application context temporary allocator.

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

Note: This method is implicitly called at exit of executable (after main).

File: memory.bl

ptr_shift_bytes

ptr_shift_bytes :: fn (ptr: *?T, bytes: s64) *T #inline

Produce right-shift of input ptr by count of bytes.

File: memory.bl

ptr_diff

ptr_diff :: fn (a: *?T1, b: *?T2) s64 #inline

Calculates pointer difference a - b.

File: memory.bl