Memory
Memory management in the Playbit C is done with explicit allocators. The type of an allocator is PBMem and is passed as an argument to functions which allocate memory. Every distinct allocation from PBMem allocators have an address-aligment guarantee of PBMem_ALIGNMENT (at least 16 bytes).
Example
int* make_int_array_or_panic(PBMem ma, usize count) {
int* array = PBMemAllocz(ma, count * sizeof(int));
return PBExpectNotNull(array); // panic if allocation failed
}
void main() {
PBMem ma = kPBMemGPA;
int* a = make_int_array(ma, 12);
print("allocated array of 12 integers at %p", a);
PBMemFree(ma, a);
}
Allocators
There are several allocators available "out of the box." Usually you just use kPBMemGPA and that's it, but sometimes a specialized allocator can be useful. For example, if you are parsing an AST, analyzing it and then throwing away the result, PBArena removes the need for freeing memory. A PBMemNullAllocator is useful when testing code that gracefully handles allocation failures.
kPBMemGPAis a general-purpose allocator; a universal "heap allocator" that strikes a balance between performance and memory use. UsekPBMemGPAif you don't know what you want.PBArenaimplements an arena allocator, useful for region-based memory management. It's a hybrid betwen a simple bump allocator and a chained-block allocator.PBMemBufAllocatorimplements a simple bump allocator on top of an externally managed buffer. Useful for testing or for very performance cricital code where control over memory locality is important. Generally not a very useful allocator.PBMemNullAllocatoralways fails to allocate memory, intended to be used for testing.PBMemZeroingAllocatorwraps another allocator and causes all allocations to be zero-initialized, useful for intercepting library code.
PBMem_ALIGNMENT
PBMem_ALIGNMENT is the minimum guaranteed address alignment for allocations. It's at least _Alignof(max_align_t)
PBMem
typedef const struct PBMemAllocator * PBMem;
PBMem is the type of a memory allocator
PBMemAllocator
typedef struct PBMemAllocator {
void* nullable (*f)(PBMem self, void* nullable ptr, usize* nullable nbyte_inout, u32 flags);
} PBMemAllocator;
PBMemAllocator is the type of a memory allocator. It's function is invoked as follows:
| PBMem | libc equivalent |
|---|---|
f(NULL, size, 0) |
malloc(size) |
f(NULL, size, PBMem_ZERO) |
calloc(size, 1) |
f(ptr, size, 0) |
realloc(ptr, size) |
f(ptr, size, PBMem_ZERO) |
realloc(ptr, size2);bzero(ptr2+size1, size2-size1) |
f(ptr, 0, 0) |
free(ptr) |
It's defined as a struct with a function pointer, rather than simply a function, to allow allocator implementations like arenas to store allocator-specific state.
PBMem_ZERO
const int PBMem_ZERO = 1;
allocate zeroed memory
PBMem_ROUNDUP
const int PBMem_ROUNDUP = 2;
PBMem_ROUNDUP requests that nbyte_inout is rounded up to a value that the allocator can allocate without adding any padding.
PBMemAlloc
void* PBMemAlloc(PBMem ma,
usize nbyte);
PBMemAlloc allocates nbyte bytes through allocator ma.
- Returns: NULL on allocation failure.
PBMemAllocz
void* PBMemAllocz(PBMem ma,
usize nbyte);
PBMemAllocz allocates nbyte bytes through allocator ma and zero-initializes the returned memory.
- Returns: NULL on allocation failure.
PBMemRealloc
void* PBMemRealloc(PBMem ma,
void* ptr,
usize nbyte);
PBMemRealloc resizes an allocation owned by ma.
ptr: Pointer to existing allocation. Pass NULL to allocate likePBMemAlloc.nbyte: New size. Pass 0 to free likePBMemFree.- Returns: NULL on allocation failure when growing or creating.
PBMemReallocz
void* PBMemReallocz(PBMem ma,
void* ptr,
usize nbyte);
PBMemReallocz is like PBMemRealloc but requests zero-initialization of newly added bytes.
- Returns: NULL on allocation failure when growing or creating.
PBMemAllocx
void* PBMemAllocx(PBMem ma,
void* ptr,
usize* nbyte_inout,
u32 flags);
PBMemAllocx is the most flexible allocator interface. You can perform all actions provided by PBMemAlloc, PBMemAllocz, PBMemRealloc, PBMemReallocz and PBMemFree, plus get the actual allocated memory region's size back.
nbyte_inout: Points to the requested size and may be increased by the allocator.- Returns: NULL on allocation failure.
PBMemFree
void PBMemFree(PBMem ma,
void* ptr);
PBMemFree releases ptr to allocator ma.
PBMemResize
bool PBMemResize(PBMem ma,
usize elemSize,
void* ptrInOut,
usize* capInOut,
usize newCap,
u32 maFlags);
PBMemResize resizes the capacity of an array *ptrInOut to newCap.
ptrInOut: holds the address of the current memory allocation, updated on successful return.capInOut: holds the current capacity (number of elements) of*ptrInOut, updated on successful return.newCap: The requested new capacity.maFlags: Flags passed toPBMemAllocx.- Returns: true on success, in which case the value of
*ptrInOutmay be a new address and*capInOutholds the new capacity. IfPBMem_ROUNDUPis set inmaFlags,*capInOuthas been rounded up to what's ideal for the memory allocatorma. false if resizing fails, in which case*ptrInOutand*capInOutremain unchanged and the current memory region remains valid.
PBMemGrow
bool PBMemGrow(PBMem ma,
usize elemSize,
void* ptrInOut,
usize* capInOut,
usize addCap,
u32 maFlags);
PBMemGrow attempts to grow the array at *dataPtr to at least *capInOut+addCap, rounding up by allocator-friendly amounts. Usually ~1.5x of current capacity.
ptrInOut: holds the address of the current memory allocation, updated on successful return.capInOut: holds the current capacity (number of elements) of*ptrInOut, updated on successful return.addCap: The requested additional capacity.maFlags: Flags passed toPBMemAllocx.- Returns: true on success, in which case the value of
*ptrInOutmay be a new address and*capInOutholds the new capacity.
PBMemPageSize
usize PBMemPageSize();
PBMemPageSize returns the underlying system's memory page size
PBMemEq
bool PBMemEq(const void* a,
usize aLen,
const void* b,
usize bLen);
PBMemEq returns true if memory at a is equivalent to memory at b
PBMemCmp
int PBMemCmp(const void* a,
usize aLen,
const void* b,
usize bLen);
PBMemCmp returns <0 if a is bytewise less than b, >1 if b < a, 0 if a == b
PBMemoryCopy
void PBMemoryCopy(void* to,
const void* from,
usize size);
PBMemoryCopy is a different name for memcpy
PBMemoryZero
void PBMemoryZero(void* ptr,
usize size);
PBMemoryZero does memset(ptr, 0, size)
PBMemoryEquals
bool PBMemoryEquals(const void* a,
const void* b,
usize size);
PBMemoryZeroStruct
PBMemoryZeroStruct does memset(ptr, 0, sizeof(*ptr))
PBMemRegionsOverlap
bool PBMemRegionsOverlap(const void* a,
usize aLen,
const void* b,
usize bLen);
PBMemRegionsOverlap returns true if two memory regions overlap
kPBMemGPA
extern PBMem kPBMemGPA;
kPBMemGPA is the general-purpose allocator
PBMemGPAStats
typedef struct PBMemGPAStats {
usize totalBytes; // size of heap, in bytes
usize peakBytes; // max space allocated from system, in bytes
usize freeBytes; // free space, in bytes
usize usedBytes; // allocated space, in bytes
usize usedCount; // number of individual, active allocations
} PBMemGPAStats;
PBMemGPAStats describes info about the kPBMemGPA's state
PBMemGPAGetStats
void PBMemGPAGetStats(PBMem m,
PBMemGPAStats* result);
PBMemGPAGetStats retrieves current stats of a GPA.
m: Currently the only valid value iskPBMemGPA
#include <playbit/mem_buf_allocator.h>
PBMemBufAllocator
typedef struct PBMemBufAllocator {
PBMemAllocator allocator;
u8* buf;
usize offs; // allocation offset, in bytes (i.e. memory allocated, in use)
usize cap; // capacity of buf, in bytes
usize lastSize : 56; // size in bytes of most recent allocation
usize flags : 8;
usize lastSize;
u8 flags;
} PBMemBufAllocator;
PBMemBufAllocator_ZEROED
const int PBMemBufAllocator_ZEROED = 1;
initial buf is zeroed
PBMemBufAllocator_THREADSAFE
const int PBMemBufAllocator_THREADSAFE = 2;
enable allocations across threads
PBMemBufAllocatorInit
PBMem PBMemBufAllocatorInit(PBMemBufAllocator* a,
void* buf,
usize bufCap,
u8 flags);
PBMemBufAllocatorInit initializes a bump allocator on top of buf.
If address of buf is not aligned to PBMem_ALIGNMENT, the actual usable size may be smaller than bufCap.
PBMemBufAllocator is not thread safe unless PBMemBufAllocator_THREADSAFE flag is set. If PBMemBufAllocator_THREADSAFE is set, only allocations are supported; resizing (realloc) will fail and free will have no effect.
bufCap: must be at least PBMem_ALIGNMENT.- Returns:
amemory allocator handle which can be used with functions that allocate memory.
PBMemBufAllocatorReset
void PBMemBufAllocatorReset(PBMemBufAllocator* a);
PBMemBufAllocatorReset frees any and all allocations made in the allocator
PBMemBufAllocatorScopeBegin
usize PBMemBufAllocatorScopeBegin(PBMemBufAllocator* a);
PBMemBufAllocatorScopeBegin marks the beginning of a resumable state of the allocator. Any allocations made after a call to PBMemBufAllocatorScopeBegin are freed all at once by a matchin call to PBMemBufAllocatorScopeEnd.
PBMemBufAllocatorScopeEnd
void PBMemBufAllocatorScopeEnd(PBMemBufAllocator* a,
usize scope);
PBMemBufAllocatorScopeBegin ends a region started by PBMemBufAllocatorScopeBegin
scope: The value returned fromacorrespondingPBMemBufAllocatorScopeBegincall.
#include <playbit/mem_null_allocator.h>
PBMemNullAllocator
int PBMemNullAllocator();
PBMemNullAllocator always fails to allocate memory, intended to be used for testing
#include <playbit/mem_zeroing_allocator.h>
PBMemZeroingAllocator
typedef struct PBMemZeroingAllocator {
PBMemAllocator ma;
PBMem source;
} PBMemZeroingAllocator;
PBMemZeroingAllocator wraps another allocator and sets the PBMem_ZERO flag for all allocations
PBMemZeroingAllocatorImpl
void* PBMemZeroingAllocatorImpl(PBMem m,
void* p,
usize* zp,
u32 fl);
PBMemZeroingAllocatorImpl forwards allocation to the wrapped source allocator and forces PBMem_ZERO.
PBMemZeroingAllocatorInit
PBMem PBMemZeroingAllocatorInit(PBMemZeroingAllocator* a,
PBMem source);
PBMemZeroingAllocatorInit initializes a PBMemZeroingAllocator wrapper and returns it as PBMem.
All allocations and reallocations performed through the returned allocator force PBMem_ZERO, regardless of caller flags.
PBMemZeroingAllocatorWrap
macro PBMem PBMemZeroingAllocatorWrap(PBMem sourceAllocator)
PBArena is an arena allocator, useful for region-based memory management. It's a hybrid betwen a simple bump allocator and a chained-block allocator.
Example of basic use:
// create an arena that sources memory blocks from the GPA
PBArena* arena = PBArenaAlloc(.allocator = kPBMemGPA);
int* array1 = PBArenaPush(arena, 4 * sizeof(int), alignof(int), 0);
// free last 4*4 bytes allocated
void PBArenaPop(arena, 4 * sizeof(int));
int* array2 = PBArenaPush(arena, 4 * sizeof(int), alignof(int), 0);
int* array3 = PBArenaPush(arena, 4 * sizeof(int), alignof(int), 0);
// free all memory allocated:
PBArenaReset(arena);
// free the arena itself
PBArenaFree(&arena);
Example use as a PBMem allocator:
PBArena* arena = PBArenaAlloc(.allocator = kPBMemGPA);
PBMem ma = PBMemFromArena(arena);
SomeFunctionThatUsesPBMem(ma);
// free all memory allocated with `ma`
PBArenaFree(&arena);
Example of scoped allocations:
PBArena* arena = PBArenaAlloc(.allocator = kPBMemGPA);
u8* a = PBArenaPush(arena, 128, 1, 0);
{
PBArenaScope(arena);
u8* b = PBArenaPush(arena, 128, 1, 0);
expect(b == a);
// all allocations made in this scope are freed when leaving
}
{
PBArenaScope(arena);
u8* b = PBArenaPush(arena, 128, 1, 0);
expect(b == a);
}
PBArenaFree(&arena);
PBArenaFlags
typedef enum PBArenaFlags PB_ENUM_TYPE(u64){
PBArenaFlag_NoChain = (1 << 0),
PBArenaFlag_FixedBuffer = (1 << 1),
} PBArenaFlags;
PBArena
struct PBArena {
// PBMem
PBMemAllocator ma;
// config
PBMem allocator;
PBArenaFlags flags;
// chains
PBArena* current;
PBArena* nullable prev;
usize basePos;
// arena state
u8* data;
usize pos;
usize size;
// debug
char* debugFile;
int debugLine;
};
PBArenaParams
struct PBArenaParams {
// config
PBMem allocator;
PBArenaFlags flags;
usize initialSize;
void* backingBuffer; // you must set initialSize if using!
// debug
char* debugFile;
int debugLine;
};
PBArenaTemp
struct PBArenaTemp {
PBArena* arena;
usize pos;
};
PBArena_HEADER_SIZE
PBArena_HEADER_SIZE is the number of bytes of overhead per arena slab
PBArenaAllocP
PBArena* PBArenaAllocP(PBArenaParams params);
PBArenaAllocP creates an arena from params.
If params.backingBuffer is set, that memory is used as fixed backing storage.
Otherwise memory is allocated from params.allocator.
- Returns: NULL on invalid parameters or allocation failure.
PBArenaAlloc
macro PBArena* nullable PBArenaAlloc(.field = value ...)
creates an arena from params, a convenience wrapper for PBArenaAllocP((PBArenaParams){ .field = value ... })
- Returns: NULL on invalid parameters or allocation failure
PBArenaFree
void PBArenaFree(PBArena* arena);
PBArenaFree releases all memory owned by *arena and sets *arena to NULL.
PBArenaFromBuffer
PBArena* PBArenaFromBuffer(void* data,
usize size);
PBArenaFromBuffer creates an arena backed by caller-owned memory in [data, data+size).
PBArenaFromAllocator
PBArena* PBArenaFromAllocator(PBMem allocator,
usize size);
PBArenaFromAllocator creates an arena that allocates slab memory from allocator.
PBArenaPush
void* PBArenaPush(PBArena* arena,
usize size,
usize align,
bool zero);
PBArenaPush allocates size bytes from arena with alignment align.
If zero is true, returned bytes are zero-initialized.
- Returns: NULL if there is no room and
arenagrowth is not possible.
PBArenaPop
void PBArenaPop(PBArena* arena,
usize size);
PBArenaPop releases the most-recent size bytes from arena state.
PBArenaPopTo
void PBArenaPopTo(PBArena* arena,
usize pos);
PBArenaPopTo rewinds arena allocation position to absolute offset pos.
PBArenaReset
void PBArenaReset(PBArena* arena);
PBArenaReset rewinds arena to position 0, releasing all allocations.
PBArenaPos
usize PBArenaPos(const PBArena* arena);
PBArenaPos returns the current absolute allocation position.
PBArenaSize
usize PBArenaSize(const PBArena* arena);
PBArenaSize returns total bytes across all slabs currently attached to arena.
PBArenaTempBegin
PBArenaTemp PBArenaTempBegin(PBArena* arena);
PBArenaTempBegin snapshots current arena position for scoped rollback.
PBArenaTempEnd
void PBArenaTempEnd(PBArenaTemp temp);
PBArenaTempEnd rewinds arena to the position captured by temp.
PBMemFromArena
PBMem PBMemFromArena(PBArena* arena);
PBMemFromArena returns an allocator that can be used with anything that accepts PBMem
PBArenaIsEmpty
bool PBArenaIsEmpty(const PBArena* arena);
PBArenaIsEmpty reports whether arena has no usable backing storage. This is true for an arena initialized from a zero-sized buffer or after PBArenaFree has invalidated its slabs.
PBArenaScope
void PBArenaScope(PBArena* arena);
PBArenaScope brackets the current scope in PBArenaTempBegin and PBArenaTempEnd calls, effectively freeing any memory allocated in this arena during the span of the current scope.