Modules

Pool

moduleSource

Available on macOS, Linux, Windows, Wasm

#import "Pool";

A bump allocator over blocks of memory that are reused or freed all together.

Allocation is cheap and there is no per-item free: you reset the pool to take everything back at once. Use it for data that lives and dies together.

Example↗
pool: Pool;
defer release(*pool);
values := cast(*int) get(*pool, 4 * size_of(int));
for 0..3 values[it] = it * it;
assert(values[3] == 9);
reset(*pool);

Module parameters#

USE_UNMAPPING_ALLOCATOR := false
Gets blocks from the Unmapping_Allocator, which returns them to the OS when the pool is reset.

Procedures#

(pool: *Pool, block_allocator: Allocator = .{})

Chooses the allocator the pool takes its blocks from: block_allocator if given, else context.allocator.

pool: Pool;
set_allocators(*pool);
defer release(*pool);
assert(get(*pool, 8) != null);
↗
(pool: *Pool, nbytes: s64) -> *void

Returns nbytes bytes aligned to the pool's alignment, or null when the underlying allocator fails or the size is too large to represent.

pool: Pool;
defer release(*pool);
a := cast(*int) get(*pool, size_of(int));
b := cast(*int) get(*pool, size_of(int));
a.* = 1;
b.* = 2;
assert(a.* == 1 && b.* == 2);
↗
(pool: *Pool)

Gives all memory back to the pool: oversized blocks are freed, ordinary blocks are kept for reuse (or freed too, with free_memblocks_on_reset).

pool: Pool;
defer release(*pool);
get(*pool, 100);
reset(*pool);
bytes, oversized := calculate_used_memory(pool);
assert(bytes == 0 && oversized == 0);
↗
(pool: *Pool)

Resets the pool and frees all its blocks through block_allocator. The pool can be used again; it takes new blocks on the next get.

pool: Pool;
get(*pool, 100);
release(*pool);
↗
(pool: Pool) -> (inband_size: s64, outband_count: s64)

How much the pool has handed out: the bytes used in ordinary blocks (including their headers and alignment padding) and the number of oversized blocks.

pool: Pool;
defer release(*pool);
get(*pool, 100);
bytes, oversized := calculate_used_memory(pool);
assert(bytes >= 100 && oversized == 0);
get(*pool, 100000);
bytes, oversized = calculate_used_memory(pool);
assert(oversized == 1);
↗
(mode: Allocator_Mode, requested_size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void

The Allocator_Proc for a pool: use it as Allocator.{ pool_allocator_proc, *pool }.

pool: Pool;
set_allocators(*pool); // blocks come from the allocator in effect now
defer release(*pool);

push_allocator(Allocator.{ pool_allocator_proc, *pool });
text := copy_string("in the pool");
assert(text == "in the pool");
↗
()

A small demonstration of the lifecycle: set up, allocate, reset, release. It exists as a smoke test and is not part of the API.

Types#

:: struct

A bump allocator that hands out memory from large blocks and frees it all at once.

memblock_size: s64
oversized_size: s64
alignment: := 8
current_memblock: *Memblock
current_pos: *u8
bytes_left: s64
block_allocator: Allocator
unused_memblocks: *Memblock
used_memblocks: *Memblock
first_used_memblock: *Memblock
out_of_band_allocations: *Memblock
overwrite_memory: := false
free_memblocks_on_reset: := false
logging_options: enum_flags u32 { LOG_OVERSIZED_ALLOCATIONS :: 1; LOG_OVERSIZED_ALLOCATIONS_AS_ERRORS :: 2; LOG_OVERSIZED_ALLOCATIONS_STACK_TRACE :: 4; }
pool: Pool;
defer release(*pool);

numbers := cast(*int) get(*pool, 10 * size_of(int));
for 0..9 numbers[it] = it;
assert(numbers[9] == 9);

allocator := Allocator.{ pool_allocator_proc, *pool };
{
    push_allocator(allocator);
    text := copy_string("allocated from the pool"); // freed by reset, not by free
    assert(text.count == 23);
}

reset(*pool); // everything above is gone
↗

Constants#