Skip to content
Snippets Groups Projects
arena.h 5.63 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Arena allocation
    // Michal Vlasák, FIT CTU, 2023
    
    
    Michal Vlasák's avatar
    Michal Vlasák committed
    #pragma once
    
    #include <stddef.h>
    #include <stdalign.h>
    
    // This header defines the API of two different Arena allocators - `Arena` and
    // `Garena`. In general arena allocators all "bump allocate" their memory. I.e.
    // they have a pointer to chunk of available memory from which they allocate
    // sequentially -- when allocation is requested the returned memory is in the
    // beginning of the first chunk: former free space pointer is returned and
    // new one is formed by incrementing the former by the allocated size ("bumped").
    // This is very efficient, because it wastes no space on metadata and the code
    // for allocation is very fast.
    // metadata
    //
    // The problem is that since individual allocations are not tracked, it is not
    // possible to arbitrarily free allocations. But it is possible to deallocate
    // _all_ the memory at once. This may seem too restricting, but in fact a lot of
    // problem domains generate a lot of temporary objects and then get rid of all
    // of them (e.g. a web server serving a single request).
    //
    // Another problem with arena allocation is what to do when they exhaust the
    // free memory. There are a few options:
    //
    //  (1) Abort, since the memory is exhausted. Easy to do, makes sense e.g. for
    //     microcontrollers with limited amount or memory, or programs with known
    //     upper bound on memory consumption.
    //
    //  (2) Allocate a new chunk of memory to be used in addition to the old one (old
    //     allocations are kept in old chunk(s). New allocations are  performed in a
    //     new chunk). This has the advantage that all pointers to previously
    //     allocated data are kept valid, the disadvantage is that the memory is no
    //     longer contiguous.
    //
    //  (3) Allocate a new (bigger) chunk memory and _move_ the old allocations to
    //      the new space. New allocations will be made in the rest of the new space.
    //      This is dual to the previous approach, pointers to previous allocations
    //      an be invalidated on every allocation, but the memory is kept contiguous
    //      at all times.
    //
    //  (4) Extend the current chunk by allocating the memory immediately following
    //      it. This is a refinement that has advantages of both of the previous
    //      approaches, while having none of their limitations. The problem is
    //      achieving it. On modern systems it can be due to the use of virtual
    //      memory.
    //
    // There are many great resources on Arenas and associated topics, I can
    // recommend at the very least:
    //
    //  - https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator
    //  - https://www.gingerbill.org/article/2019/02/01/memory-allocation-strategies-001/
    //  - https://en.wikipedia.org/wiki/Region-based_memory_management
    
    // This particular arena implementation is the approach (2). No pointers are ever
    // invalidated. The chunks are allocated with `malloc` and freed with `free`.
    // The current allocation position (even across chunks) can be saved and
    // restored, so not all allocated memory has to be deallocated at once. But the
    // "stack" principle still has to be obeyed.
    //
    // The `Arena` struct is defined in this header but should be regarded as an
    // implementation detail. `arena_` functions should be used for all manipulation
    // with the arena.
    typedef struct Arena Arena;
    
    // `arena_init` and `arena_destroy` are called on to initialize and deinitialize
    // an Arena.
    
    void arena_init(Arena *arena);
    void arena_destroy(Arena *arena);
    
    // `arena_alloc` allocates a piece of memory of particular `size`.
    void *arena_alloc(Arena *arena, size_t size);
    
    // Save current allocation position and restore to a previous allocation
    // position. Saves should be paired with respective restores (the stack
    // principle).
    size_t arena_save(Arena *arena);
    void arena_restore(Arena *arena, size_t pos);
    
    
    
    // The `GArena` ("growable arena") is an implementation of the approach (3).
    // When free space is exhausted new bigger chunk is allocated and old objects
    // are moved to it. Thus pointers returned by this allocator have to be used
    // carefully.
    //
    // Despite this limitation, with careful use (see the below macros), growable
    // arena essentially is a dynamic array and different parts of it can even
    // store different types of objects. The utility functions below are public,
    // but are only used in private parts of the parser and currently undocumented.
    typedef struct GArena GArena;
    
    void garena_init(GArena *arena);
    void garena_destroy(GArena *arena);
    
    void *garena_alloc(GArena *arena, size_t size, size_t alignment);
    
    size_t garena_save(GArena *arena);
    void *garena_restore(GArena *arena, size_t pos);
    
    
    #define move_to_arena(arena, garena, start, type) \
    	move_to_arena_((arena), (garena), (start), alignof(type))
    void *move_to_arena_(Arena *arena, GArena *garena, size_t start, size_t alignment);
    
    
    void *garena_mem(GArena *arena);
    void *garena_from(GArena *arena, size_t start, size_t alignment);
    
    #define garena_cnt(arena, type) \
    	garena_cnt_from_((arena), 0, sizeof(type))
    
    #define garena_cnt_from(arena, start, type) \
    	garena_cnt_from_((arena), (start), sizeof(type))
    
    size_t garena_cnt_from_(GArena *arena, size_t start, size_t elem_size);
    
    #define garena_push(arena, type) \
    	((type *)garena_alloc((arena), sizeof(type), alignof(type)))
    
    #define garena_push_value(arena, type, value) \
    	do { \
    		type tmp_pushed_ = (value); \
    		*garena_push((arena), type) = tmp_pushed_; \
    	} while (0)
    
    
    
    // PRIVATE IMPLEMENTATION DETAILS
    
    typedef struct ArenaChunk ArenaChunk;
    struct Arena {
    	ArenaChunk *current;
    	size_t prev_size_sum;
    };
    
    struct ArenaChunk {
    	size_t size;
    	size_t pos;
    	ArenaChunk *prev;
    	// Allocated chunk memory follows.
    };
    
    struct GArena {
    	unsigned char *mem;
    	size_t pos;
    	size_t capacity;
    };