aboutsummaryrefslogtreecommitdiff
path: root/mem/include/mem.h
diff options
context:
space:
mode:
Diffstat (limited to 'mem/include/mem.h')
-rw-r--r--mem/include/mem.h149
1 files changed, 149 insertions, 0 deletions
diff --git a/mem/include/mem.h b/mem/include/mem.h
new file mode 100644
index 0000000..30c24fc
--- /dev/null
+++ b/mem/include/mem.h
@@ -0,0 +1,149 @@
1/*
2 * Block-based Memory Allocator.
3 *
4 * Clients should use the macros to define and use allocators. They make the API
5 * type-safe.
6 *
7 * Like a pool/block-based allocator, this allocator stores data in fixed-size
8 * blocks. However, this allocator also supports allocation of contiguous chunks
9 * of a variable number of blocks.
10 *
11 * Chunk information is stored in a separate array so that client data is
12 * contiguous in the main pool of memory and better cached.
13 */
14#pragma once
15
16#include <assert.h>
17#include <stdbool.h>
18#include <stddef.h>
19#include <stdint.h>
20
21/// Define a typed memory allocator backed by a statically-allocated array.
22#define DEF_MEM(MEM, TYPE, NUM_BLOCKS) \
23 typedef struct MEM { \
24 Memory mem; \
25 Chunk chunks[NUM_BLOCKS]; \
26 TYPE blocks[NUM_BLOCKS]; \
27 } MEM;
28
29/// Define a typed memory allocator backed by a dynamically-allocated array.
30#define DEF_MEM_DYN(MEM, TYPE) \
31 typedef struct MEM { \
32 Memory mem; \
33 Chunk* chunks; \
34 TYPE* blocks; \
35 } MEM;
36
37/// Initialize a statically-backed memory allocator.
38#define mem_make(MEM) \
39 { \
40 assert(MEM); \
41 const size_t block_size = sizeof((MEM)->blocks[0]); \
42 const size_t num_blocks = sizeof((MEM)->blocks) / block_size; \
43 mem_make_( \
44 &(MEM)->mem, (MEM)->chunks, (MEM)->blocks, num_blocks, block_size); \
45 }
46
47/// Initialize a dynamically-backed memory allocator.
48#define mem_make_dyn(MEM, num_blocks, block_size) \
49 mem_make_(&(MEM)->mem, 0, 0, num_blocks, block_size)
50
51/// Destroy the allocator.
52///
53/// If the allocator is dynamically-backed, then this function frees the
54/// underlying memory.
55#define mem_del(MEM) mem_del_(&(MEM)->mem)
56
57/// Clear the allocator.
58///
59/// This function frees all of the allocator's blocks. The resulting allocator
60/// is as if it were newly created.
61#define mem_clear(MEM) mem_clear_(&(MEM)->mem)
62
63/// Allocate a new chunk of N blocks.
64/// Return a pointer to the first block of the chunk, or 0 if there is no memory
65/// left.
66/// New chunks are conveniently zeroed out.
67#define mem_alloc(MEM, num_blocks) mem_alloc_(&(MEM)->mem, num_blocks)
68
69/// Free the chunk.
70/// The chunk pointer is conveniently set to 0.
71#define mem_free(MEM, CHUNK) mem_free_(&(MEM)->mem, (void**)CHUNK)
72
73/// Return a pointer to a chunk given the chunk's handle.
74/// The chunk must have been allocated.
75#define mem_get_chunk(MEM, HANDLE) \
76 ((__typeof__((MEM)->blocks[0])*)mem_get_chunk_(&(MEM)->mem, HANDLE))
77
78/// Get the handle to the given chunk.
79#define mem_get_chunk_handle(MEM, CHUNK_PTR) \
80 mem_get_chunk_handle_(&(MEM)->mem, CHUNK_PTR)
81
82/// Iterate over the used chunks of the allocator.
83///
84/// The caller can use 'i' as the index of the current chunk.
85///
86/// It is valid to mem_free() the chunk at each step of the iteration.
87#define mem_foreach(MEM, ITER, BODY) \
88 size_t i = 0; \
89 do { \
90 if ((MEM)->chunks[i].used) { \
91 __typeof__((MEM)->blocks[0])* ITER = &(MEM)->blocks[i]; \
92 (void)ITER; \
93 BODY; \
94 } \
95 i = (MEM)->chunks[i].next; \
96 } while (i);
97
98// -----------------------------------------------------------------------------
99
100/// Chunk information.
101///
102/// Every chunk represents a contiguous array of some number of blocks. The
103/// allocator begins as one big unused chunk.
104///
105/// Allocation looks for a free chunk large enough to hold to requested number
106/// of blocks. If the free chunk is larger than the requested chunk size, then
107/// the requested chunk is carved out of the larger block.
108///
109/// Deallocation frees the chunk back and merges it with free neighbouring
110/// chunks. Two free chunks are never contiguous in memory.
111///
112/// 'next' and 'prev' always point to a valid chunk (e.g., 0). Allocation stops
113/// looking for free chunks when it loops over.
114typedef struct Chunk {
115 size_t num_blocks;
116 size_t prev;
117 size_t next;
118 bool used;
119} Chunk;
120
121typedef struct Memory {
122 size_t block_size_bytes;
123 size_t num_blocks;
124 size_t next_free_chunk;
125 bool dynamic; /// True if blocks and chunks are dynamically-allocated.
126 Chunk* chunks; /// Array of chunk information.
127 uint8_t* blocks; /// Array of blocks;
128} Memory;
129
130/// Create a memory allocator.
131///
132/// 'chunks' and 'blocks' may be user-provided (statically-backed allocator) or
133/// null (dynamically-backed allocator).
134/// - If null, the allocator malloc()s the memory for them.
135/// - If given:
136/// - `chunks` must be at least `num_blocks` chunks.
137/// - `blocks` must be at least `num_blocks` * `block_size_bytes` bytes.
138///
139/// All blocks are zeroed out for convenience.
140bool mem_make_(
141 Memory* mem, Chunk* chunks, void* blocks, size_t num_blocks,
142 size_t block_size_bytes);
143
144void mem_del_(Memory*);
145void mem_clear_(Memory*);
146void* mem_alloc_(Memory*, size_t num_blocks);
147void mem_free_(Memory*, void** chunk_ptr);
148void* mem_get_chunk_(const Memory*, size_t chunk_handle);
149size_t mem_get_chunk_handle_(const Memory*, const void* chunk);