first commit
This commit is contained in:
432
arena.h
Normal file
432
arena.h
Normal file
@@ -0,0 +1,432 @@
|
||||
#ifndef BASE_ARENA_H
|
||||
#define BASE_ARENA_H
|
||||
|
||||
#include "base.h"
|
||||
|
||||
// #define _CRT_SECURE_NO_WARNINGS
|
||||
//
|
||||
// #include <assert.h>
|
||||
// #include <stdbool.h>
|
||||
// #include <stdint.h>
|
||||
// #include <stdio.h>
|
||||
// #include <string.h>
|
||||
//
|
||||
// /* ------------------------------------------------------------
|
||||
// Base types
|
||||
// ------------------------------------------------------------ */
|
||||
//
|
||||
// typedef uint8_t u8;
|
||||
// typedef uint32_t u32;
|
||||
// typedef uint64_t u64;
|
||||
// typedef int32_t i32;
|
||||
// typedef int b32;
|
||||
//
|
||||
// /* ------------------------------------------------------------
|
||||
// Size helpers
|
||||
// ------------------------------------------------------------ */
|
||||
//
|
||||
// #define KiB(x) ((u64)(x) * 1024ULL)
|
||||
// #define MiB(x) (KiB(x) * 1024ULL)
|
||||
//
|
||||
// /* ------------------------------------------------------------
|
||||
// Alignment helpers
|
||||
// ------------------------------------------------------------ */
|
||||
//
|
||||
// #define ALIGN_UP_POW2(x, a) (((x) + ((a) - 1)) & ~((a) - 1))
|
||||
//
|
||||
// /* ------------------------------------------------------------
|
||||
// Assert
|
||||
// ------------------------------------------------------------ */
|
||||
//
|
||||
// #ifndef ASSERT
|
||||
// #define ASSERT(x) assert(x)
|
||||
// #endif
|
||||
//
|
||||
/*
|
||||
===============================================================================
|
||||
ARENA USAGE GUIDE
|
||||
===============================================================================
|
||||
|
||||
OVERVIEW
|
||||
--------
|
||||
The arena allocator is a high-performance memory allocator designed for
|
||||
predictable allocation patterns. It supports:
|
||||
|
||||
- Multiple chained memory blocks
|
||||
- Pointer-style allocation with free-list reuse
|
||||
- Stack-style allocation
|
||||
- Optional Swap-back removal for unordered data
|
||||
- Merging compatible arenas
|
||||
|
||||
Memory is committed in page-sized chunks. The arena never commits less than
|
||||
the system page size and grows by allocating new blocks when needed.
|
||||
|
||||
|
||||
CORE CONCEPTS
|
||||
-------------
|
||||
|
||||
Arena Blocks
|
||||
~~~~~~~~~~~~
|
||||
An arena consists of one or more memory blocks linked together.
|
||||
|
||||
Each block contains:
|
||||
- base_pos : The local starting position of a block (bytes)
|
||||
- pos : Global position (bytes)
|
||||
- commit_size : Total committed size (bytes)
|
||||
- reserve_size : Total usable size of the block (bytes)
|
||||
- prev/next: Links to neighboring blocks
|
||||
- the arena pointer points to the current block
|
||||
|
||||
The arena allocates from the current block where the global position is or from the free list.
|
||||
|
||||
Blocks form a single logical address space:
|
||||
global_offset = pos
|
||||
|
||||
|
||||
Global Position Model
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
All allocations are addressed using a global offset. This allows:
|
||||
- Popping across block boundaries
|
||||
- Cross-block swap-back operations
|
||||
- Arena merging by linking blocks
|
||||
|
||||
|
||||
ARENA CONFIGURATION
|
||||
-------------------
|
||||
|
||||
An arena is configured usings the struct arena_params before creation.
|
||||
|
||||
Important parameters:
|
||||
- push_size : Fixed element size in bytes (0 = variable-size arena)
|
||||
- allow_free_list : Enables arena_free() and pointer mode, disabling it will
|
||||
enable stack mode
|
||||
- allow_swapback : Enables arena_swapback_pop(), works in stack mode only
|
||||
- max_nbre_blocks : Maximum number of blocks (0 = unlimited)
|
||||
- growth_policy : How blocks grow (next created block will have the same size
|
||||
or double the size)
|
||||
- commit_policy : How memory is committed (lazily if needed or commit all at
|
||||
block creation)
|
||||
|
||||
|
||||
ARENA CREATION AND DESTRUCTION
|
||||
------------------------------
|
||||
|
||||
We create arenas using
|
||||
mem_arena *arena_create(arena_params *params);
|
||||
|
||||
Behavior:
|
||||
- Create an arena and return the pointer to this arena or NULL if it fails
|
||||
|
||||
Requirements:
|
||||
- The configuration of the arena needs to be injected as an argument using
|
||||
arena_params
|
||||
|
||||
We destroy arenas using
|
||||
void *arena_destroy(mem_arena **arena_ptr);
|
||||
|
||||
Behavior:
|
||||
- Return (void *)1 if it succeeds or NULL if it fails
|
||||
|
||||
|
||||
ALLOCATION (arena_push)
|
||||
-----------------------
|
||||
|
||||
We allocate using one function:
|
||||
void *arena_push(mem_arena **arena_ptr, u64 size, bool non_zero);
|
||||
|
||||
Behavior:
|
||||
- zero can be set to true of false to zero the allocated memory or not
|
||||
- If the current block is full and block_index < max_nbre_blocks, Grows into a
|
||||
new block, setting the new block to arena->next then setting the arena pointer
|
||||
to the new block
|
||||
- The size can be set only if the arena has variable-size elements (push_size ==
|
||||
0)
|
||||
- Return the pointer of the pushed element if it succeeds or NULL if it fails
|
||||
|
||||
Variable-size allocation (allow_free_list == true, pointer mode only):
|
||||
|
||||
- Allocates 'size' bytes
|
||||
- Only available in pointer mode with a free list
|
||||
- Since the size of the elements is variable, when using the free list we loop
|
||||
through all the elements until finding one with enough size, after allocation if
|
||||
there is a remaining we store it as a new entry in the free list. The allocation
|
||||
is slower than fixed-size arenas
|
||||
- If there is not enough memory to push an element we try to create a new block
|
||||
and push the element to it, if there is some memory remaining in the previous
|
||||
block we add it to the free list
|
||||
- max_nbre_blocks determines the behavior of the free list
|
||||
- If max_nbre_blocks > 0 we push directly to the arena until it's full then
|
||||
we use the free list
|
||||
- If max_nbre_blocks = 0 (unlimited number of blocks) we use the free list
|
||||
directly
|
||||
|
||||
Fixed-size allocation (allow_free_list can be true of false):
|
||||
|
||||
- Size is ignored, push_size defines element size (arena config)
|
||||
- Available for both in stack and pointer modes
|
||||
- Caller-provided size is ignored
|
||||
- Required for swap-back correctness
|
||||
- Faster and safer than variable-size mode
|
||||
- If allow_free_list == true (pointer mode), uses the free list first then push
|
||||
|
||||
DEALLOCATION
|
||||
------------
|
||||
POINTER MODE (WITH A FREE LIST)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- Popping requires the pointer that points to the element
|
||||
- The only function allowed to pop is arena_free()
|
||||
|
||||
void *arena_free(mem_arena **arena_ptr, u8 **ptr, u64 size);
|
||||
|
||||
Behavior:
|
||||
- Stores (offset, size) in the free list
|
||||
- Memory is reused on future allocations
|
||||
- Sets the pointer to NULL
|
||||
- The freed memory is not zeroed, we can zero the memory only when allocating
|
||||
- If the element is last, behaves like a pop, decrementing pos, if the position
|
||||
crosses to the previous arena block, set the arena pointer to arena->prev
|
||||
- Return (void *)1 if it succeeds or NULL if it fails
|
||||
|
||||
Requirements:
|
||||
- allow_free_list == true
|
||||
- Correct element size for variable-size arenas
|
||||
|
||||
STACK MODE (HAS NO FREE LIST)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- If allow_swapback = false, we can only pop with arena_pop_to()
|
||||
- If allow_swapback = true, we can pop with arena_pop_to() and
|
||||
arena_swapback_pop()
|
||||
|
||||
Pop to position:
|
||||
void *arena_pop_to(mem_arena **arena_ptr, u64 count);
|
||||
|
||||
- Removes x elements from the top of the stack
|
||||
- Can remove data across blocks
|
||||
- Pops the element by moving the position (pos)
|
||||
- Does not zero memory we can zero the memory only when allocating
|
||||
- If we pop to the previous arena block, set the arena pointer to arena->prev
|
||||
- Return (void *)1 if it succeeds or NULL if it fails
|
||||
|
||||
Requirements:
|
||||
- allow_free_list == false
|
||||
- Fixed-size elements (push_size > 0)
|
||||
- The number of elements to pop
|
||||
|
||||
A macro arena_pop(arena) is available to pop one element
|
||||
|
||||
void *arena_swapback_pop(mem_arena **arena_ptr, u64 index);
|
||||
|
||||
- The top of the stack element is copied into the removed slot, the top of the
|
||||
stack is then reduced by one element
|
||||
- Works across multiple blocks
|
||||
- Return (void *)1 if it succeeds or NULL if it fails
|
||||
- Swapping back the last element will only pop it
|
||||
|
||||
Cases:
|
||||
- Middle of block -> swapback
|
||||
- End of non-last block -> swapback
|
||||
- Last element overall -> pop only
|
||||
|
||||
Requirements:
|
||||
- allow_free_list == false
|
||||
- Fixed-size elements (push_size > 0)
|
||||
- allow_swapback == true
|
||||
|
||||
|
||||
CLEARING THE ARENA
|
||||
------------------
|
||||
|
||||
void *arena_clear(mem_arena **arena_ptr);
|
||||
|
||||
- Resets pos for all blocks
|
||||
- Keeps all committed memory
|
||||
- Does NOT zero memory
|
||||
- Resets the arena pointer to the first arena block
|
||||
- Resets the free-list
|
||||
- Return (void *)1 if it succeeds or NULL if it fails
|
||||
|
||||
|
||||
MERGING ARENAS
|
||||
--------------
|
||||
|
||||
mem_arena *arena_merge(mem_arena **dst_ptr, mem_arena **src_ptr);
|
||||
|
||||
Behavior:
|
||||
Merges arena B into arena A by:
|
||||
- Updating the pos and base_pos and block_index of all the blocks of B
|
||||
- Linking the two arenas by setting arena->prev of the first block of B to the
|
||||
last block of A
|
||||
- If there is empty blocks in arena A, append them to the end of arena B
|
||||
- Resets the arena pointer to arena B
|
||||
- Merges the free list arenas if available
|
||||
- max_nbre_blocks is summed
|
||||
- Return the pointer to the new arena of NULL if it fails
|
||||
|
||||
Requirements:
|
||||
- Configurations must match exactly
|
||||
|
||||
HELPER FUNCTIONS
|
||||
------------------
|
||||
|
||||
mem_arena *arena_block_from_pos(mem_arena *last, u64 global_pos);
|
||||
mem_arena *arena_block_from_index(mem_arena *last, u64 index);
|
||||
mem_arena *arena_block_from_ptr(mem_arena *last, u8 *ptr);
|
||||
u64 arena_pos_from_ptr(mem_arena *arena, void *ptr);
|
||||
void *arena_ptr_from_pos(mem_arena *arena, u64 global_pos);
|
||||
void *arena_ptr_from_index(mem_arena *arena, u64 index);
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
#define ARENA_HEADER_SIZE (sizeof(mem_arena))
|
||||
#define ARENA_ALIGN (sizeof(void *))
|
||||
|
||||
// arena config
|
||||
typedef enum arena_growth_policy {
|
||||
ARENA_GROWTH_NORMAL = 0, // grow by fixed block size
|
||||
ARENA_GROWTH_DOUBLE // double block size on each growth
|
||||
} arena_growth_policy;
|
||||
|
||||
typedef enum arena_commit_policy {
|
||||
ARENA_COMMIT_LAZY = 0, // commit pages on demand
|
||||
ARENA_COMMIT_FULL // commit entire reserve at creation
|
||||
} arena_commit_policy;
|
||||
|
||||
typedef struct arena_params {
|
||||
u64 reserve_size; // size of one arena block
|
||||
u64 commit_size; // initial commit size
|
||||
u64 align; // allocation alignment (0 = default)
|
||||
|
||||
// Element size rules:
|
||||
// - stack mode : push_size > 0 (mandatory)
|
||||
// - pointer fixed : push_size > 0
|
||||
// - pointer variable : push_size == 0
|
||||
u64 push_size;
|
||||
|
||||
struct mem_arena *free_list;
|
||||
b32 allow_free_list; // pointer mode if true
|
||||
|
||||
b32 allow_swapback; // stack mode only
|
||||
|
||||
arena_growth_policy growth_policy;
|
||||
arena_commit_policy commit_policy;
|
||||
|
||||
u32 max_nbre_blocks; // 0 = unlimited
|
||||
} arena_params;
|
||||
|
||||
typedef struct arena_free_node {
|
||||
u64 offset; // offset from arena base
|
||||
u64 size; // size of freed block
|
||||
} arena_free_node;
|
||||
|
||||
// arena definition struct
|
||||
typedef struct mem_arena {
|
||||
// block chaining
|
||||
struct mem_arena *prev;
|
||||
struct mem_arena *next; // valid only on root arena
|
||||
|
||||
// positions
|
||||
u64 base_pos; // of selected block
|
||||
u64 pos; // global pos
|
||||
|
||||
// memory limits
|
||||
u64 reserve_size;
|
||||
u64 commit_size;
|
||||
u64 commit_pos;
|
||||
|
||||
// configuration
|
||||
u64 align;
|
||||
// Element size:
|
||||
// - stack mode : fixed > 0
|
||||
// - pointer fixed : fixed > 0
|
||||
// - pointer variable : 0
|
||||
u64 push_size;
|
||||
|
||||
// Pointer mode only
|
||||
struct mem_arena *free_list;
|
||||
b32 allow_free_list;
|
||||
|
||||
// Stack mode only
|
||||
b32 allow_swapback;
|
||||
|
||||
arena_growth_policy growth_policy;
|
||||
arena_commit_policy commit_policy;
|
||||
u32 max_nbre_blocks;
|
||||
u32 block_index;
|
||||
|
||||
} mem_arena;
|
||||
|
||||
typedef struct mem_arena_temp {
|
||||
mem_arena *arena;
|
||||
u64 pos;
|
||||
} mem_arena_temp;
|
||||
|
||||
// helper functions
|
||||
mem_arena *arena_block_from_pos(mem_arena *last, u64 global_pos);
|
||||
mem_arena *arena_block_from_index(mem_arena *last, u64 index);
|
||||
mem_arena *arena_block_from_ptr(mem_arena *last, u8 *ptr);
|
||||
u64 arena_pos_from_ptr(mem_arena *arena, void *ptr);
|
||||
void *arena_ptr_from_pos(mem_arena *arena, u64 global_pos);
|
||||
void *arena_ptr_from_index(mem_arena *arena, u64 index);
|
||||
|
||||
// arena core functions
|
||||
// creation / destruction
|
||||
mem_arena *arena_create(arena_params *params);
|
||||
void *arena_destroy(mem_arena **arena_ptr);
|
||||
|
||||
// allocation
|
||||
void *arena_push(mem_arena **arena_ptr, u64 size, bool non_zero);
|
||||
|
||||
// pointer mode only
|
||||
// - fixed-size arena : size is ignored
|
||||
// - variable-size arena: size is mandatory
|
||||
void *arena_free(mem_arena **arena_ptr, u8 **ptr, u64 size);
|
||||
|
||||
// stack mode only
|
||||
void *arena_pop_to(mem_arena **arena_ptr, u64 count);
|
||||
void *arena_swapback_pop(mem_arena **arena_ptr, u64 index);
|
||||
|
||||
// utilities
|
||||
void *arena_clear(mem_arena **arena_ptr);
|
||||
|
||||
mem_arena *arena_merge(mem_arena **dst_ptr, mem_arena **src_ptr);
|
||||
|
||||
// temp arenas
|
||||
/* create a temporary arena on top of an existing arena, it:
|
||||
* takes an existing arena, marks it's position, allocate beyond this position
|
||||
* and free it until the marked position when not needed*/
|
||||
mem_arena_temp arena_temp_begin(mem_arena *arena);
|
||||
void arena_temp_end(mem_arena_temp temp);
|
||||
|
||||
mem_arena_temp arena_scratch_get(mem_arena **conflicts, u32 num_conflicts);
|
||||
void arena_scratch_release(mem_arena_temp scratch);
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define THREAD_LOCAL __declspec(thread)
|
||||
#else
|
||||
#define THREAD_LOCAL __thread
|
||||
#endif
|
||||
|
||||
// Helpers
|
||||
// Pointer mode only
|
||||
/* Fixed-size arena: size is implicit, can pass 0 */
|
||||
#define PUSH_STRUCT(arena, T) \
|
||||
((T *)arena_push((arena), 0, true)) // Zeroes the memory
|
||||
#define PUSH_STRUCT_NZ(arena, T) ((T *)arena_push((arena), 0, false))
|
||||
|
||||
#define PUSH_ARRAY(arena, T, n) ((T *)arena_push((arena), 0, true))
|
||||
#define PUSH_ARRAY_NZ(arena, T, n) ((T *)arena_push((arena), 0, false))
|
||||
|
||||
/* Variable-size arena helpers: REQUIRE explicit size via arena_push() */
|
||||
#define ARENA_PUSH(arena, size) arena_push((arena), (size), true)
|
||||
#define ARENA_PUSH_NZ(arena, size) arena_push((arena), (size), false)
|
||||
|
||||
#define arena_pop(arena_ptr) arena_pop_to((arena_ptr), 1)
|
||||
|
||||
u32 plat_get_pagesize(void);
|
||||
|
||||
void *plat_mem_reserve(u64 size);
|
||||
b32 plat_mem_commit(void *ptr, u64 size);
|
||||
b32 plat_mem_decommit(void *ptr, u64 size);
|
||||
b32 plat_mem_release(void *ptr, u64 size);
|
||||
|
||||
#endif // BASE_ARENA_H
|
||||
Reference in New Issue
Block a user