aboutsummaryrefslogtreecommitdiff
path: root/mempool
diff options
context:
space:
mode:
Diffstat (limited to 'mempool')
-rw-r--r--mempool/CMakeLists.txt23
-rw-r--r--mempool/README.md20
-rw-r--r--mempool/include/mempool.h91
-rw-r--r--mempool/src/mempool.c85
-rw-r--r--mempool/test/mempool_test.c157
-rw-r--r--mempool/test/test.h185
6 files changed, 561 insertions, 0 deletions
diff --git a/mempool/CMakeLists.txt b/mempool/CMakeLists.txt
new file mode 100644
index 0000000..1ff4ff1
--- /dev/null
+++ b/mempool/CMakeLists.txt
@@ -0,0 +1,23 @@
1cmake_minimum_required(VERSION 3.16)
2
3project(mempool)
4
5# Library
6
7add_library(mempool
8 src/mempool.c)
9
10target_include_directories(mempool PUBLIC
11 include)
12
13target_compile_options(mempool PRIVATE -Wall -Wextra)
14
15# Test
16
17add_executable(mempool_test
18 test/mempool_test.c)
19
20target_link_libraries(mempool_test
21 mempool)
22
23target_compile_options(mempool_test PRIVATE -DUNIT_TEST -DNDEBUG -Wall -Wextra)
diff --git a/mempool/README.md b/mempool/README.md
new file mode 100644
index 0000000..ed2935e
--- /dev/null
+++ b/mempool/README.md
@@ -0,0 +1,20 @@
1# Mempool
2
3A memory pool implementation.
4
5Each block in the pool is tagged with a boolean value that indicates whether the
6block is free or in use. The allocator otherwise maintains little additional
7information. Therefore, some operations have linear-time complexity.
8Specifically:
9
10- Allocating a block scans the pool for the next free block in linear time.
11- Freeing a block runs in constant time.
12- Iterating over the pool's used blocks is linear over the number of blocks in
13 the pool, as opposed to just the number of used blocks.
14
15The allocator's internal data is also stored separately from the main array of
16blocks so that the block data remains tightly packed.
17
18For convenience of programming and debugging, free blocks in the mempool always
19remain zeroed out. If your data structures are valid when zeroed out, then you
20do not need to explicitly initialize them.
diff --git a/mempool/include/mempool.h b/mempool/include/mempool.h
new file mode 100644
index 0000000..41d56e5
--- /dev/null
+++ b/mempool/include/mempool.h
@@ -0,0 +1,91 @@
1#pragma once
2
3#include <assert.h>
4#include <stdbool.h>
5#include <stddef.h>
6#include <stdint.h>
7
8/// Define a typed mempool of the given number of blocks.
9#define DEF_MEMPOOL(POOL, TYPE, NUM_BLOCKS) \
10 typedef struct POOL { \
11 mempool pool; \
12 BlockInfo block_info[NUM_BLOCKS]; \
13 TYPE blocks[NUM_BLOCKS]; \
14 } POOL;
15
16/// Create a new pool.
17#define mempool_make(POOL) \
18 { \
19 assert(POOL); \
20 const size_t block_size = sizeof((POOL)->blocks[0]); \
21 const size_t num_blocks = sizeof((POOL)->blocks) / block_size; \
22 mempool_make_(&(POOL)->pool, (POOL)->block_info, (POOL)->blocks, \
23 num_blocks, block_size); \
24 }
25
26/// Allocate a new block.
27/// Return 0 if there is no memory left.
28#define mempool_alloc(POOL) mempool_alloc_(&(POOL)->pool)
29
30/// Free the block.
31/// The block pointer is conveniently set to 0.
32#define mempool_free(POOL, BLOCK_PTR) \
33 assert(*BLOCK_PTR); \
34 mempool_free_(&(POOL)->pool, (void**)BLOCK_PTR)
35
36/// Return the ith block.
37/// The block must have been allocated.
38#define mempool_get_block(POOL, INDEX) \
39 ((typeof((POOL)->blocks[0])*)mempool_get_block_(&(POOL)->pool, INDEX))
40
41/// Get the index to the given block.
42#define mempool_get_block_index(POOL, BLOCK_PTR) \
43 mempool_get_block_index_(&(POOL)->pool, BLOCK_PTR)
44
45/// Iterate over the used blocks of the pool.
46#define mempool_foreach(POOL, ITER, BODY) \
47 for (size_t i = 0; \
48 i < (sizeof((POOL)->blocks) / sizeof(typeof((POOL)->blocks[0]))); \
49 ++i) { \
50 if (!(POOL)->block_info[i].used) { \
51 continue; \
52 } \
53 typeof((POOL)->blocks[0])* ITER = &(POOL)->blocks[i]; \
54 (void)ITER; \
55 BODY; \
56 }
57
58typedef struct BlockInfo {
59 bool used;
60} BlockInfo;
61
62typedef struct mempool {
63 size_t block_size_bytes;
64 size_t num_blocks;
65 size_t next_free_block;
66 bool full;
67 BlockInfo* block_info;
68 uint8_t* blocks;
69} mempool;
70
71/// Create a pool allocator from user-provided memory.
72/// `BlockInfo` must hold at least `num_blocks` entries.
73/// `blocks` must be at least `num_blocks` * `block_size_bytes` bytes.
74/// All blocks are zeroed out for convenience.
75void mempool_make_(mempool*, BlockInfo*, void* blocks, size_t num_blocks,
76 size_t block_size_bytes);
77
78/// Allocate a new block.
79/// Return 0 if there is no memory left.
80void* mempool_alloc_(mempool*);
81
82/// Free the block.
83/// The block pointer is conveniently set to 0.
84void mempool_free_(mempool*, void** block_ptr);
85
86/// Return the ith block.
87/// The block must have been allocated.
88void* mempool_get_block_(const mempool*, size_t block_index);
89
90/// Get the index to the given block.
91size_t mempool_get_block_index_(const mempool*, const void* block);
diff --git a/mempool/src/mempool.c b/mempool/src/mempool.c
new file mode 100644
index 0000000..b7e8705
--- /dev/null
+++ b/mempool/src/mempool.c
@@ -0,0 +1,85 @@
1#include "mempool.h"
2
3#include <string.h>
4
5static inline size_t min(size_t a, size_t b) { return a < b ? a : b; }
6
7void mempool_make_(mempool* pool, BlockInfo* block_info, void* blocks,
8 size_t num_blocks, size_t block_size_bytes) {
9 assert(pool);
10 assert(block_info);
11 assert(blocks);
12 assert(num_blocks >= 1);
13 pool->block_size_bytes = block_size_bytes;
14 pool->num_blocks = num_blocks;
15 pool->next_free_block = 0;
16 pool->full = false;
17 pool->block_info = block_info;
18 pool->blocks = blocks;
19 memset(blocks, 0, num_blocks * block_size_bytes);
20}
21
22void* mempool_alloc_(mempool* pool) {
23 assert(pool);
24
25 if (pool->full) {
26 return 0;
27 }
28
29 // Allocate the block.
30 void* block = &pool->blocks[pool->next_free_block * pool->block_size_bytes];
31 pool->block_info[pool->next_free_block].used = true;
32
33 // Search for the next free block. If it does not exist, flag the pool full.
34 pool->full = true;
35 for (size_t i = 1; i < pool->num_blocks && i != 0; i++) {
36 pool->next_free_block = (pool->next_free_block + 1) % pool->num_blocks;
37 if (!pool->block_info[pool->next_free_block].used) {
38 pool->full = false;
39 break;
40 }
41 }
42
43 return block;
44}
45
46void mempool_free_(mempool* pool, void** block_ptr) {
47 assert(pool);
48 assert(block_ptr);
49
50 memset(*block_ptr, 0, pool->block_size_bytes);
51
52 const size_t block_index =
53 ((uint8_t*)*block_ptr - pool->blocks) / pool->block_size_bytes;
54 assert(block_index < pool->num_blocks);
55
56 // Disallow double-frees.
57 assert(pool->block_info[block_index].used);
58
59 pool->block_info[block_index].used = false;
60 if (pool->full) {
61 pool->next_free_block = block_index;
62 pool->full = false;
63 } else {
64 // Prefer to allocate blocks towards the start of the pool. This way, blocks
65 // should cluster around this area and the pool should offer better memory
66 // locality for used blocks.
67 pool->next_free_block = min(pool->next_free_block, block_index);
68 }
69
70 *block_ptr = 0;
71}
72
73void* mempool_get_block_(const mempool* pool, size_t block_index) {
74 assert(pool);
75 assert(block_index < pool->num_blocks);
76 assert(pool->block_info[block_index].used);
77 return pool->blocks + block_index * pool->block_size_bytes;
78}
79
80size_t mempool_get_block_index_(const mempool* pool, const void* block) {
81 assert(pool);
82 const size_t block_byte_index = (const uint8_t*)block - pool->blocks;
83 assert(block_byte_index % pool->block_size_bytes == 0);
84 return block_byte_index / pool->block_size_bytes;
85}
diff --git a/mempool/test/mempool_test.c b/mempool/test/mempool_test.c
new file mode 100644
index 0000000..d257922
--- /dev/null
+++ b/mempool/test/mempool_test.c
@@ -0,0 +1,157 @@
1#include "mempool.h"
2
3#include "test.h"
4
5#define NUM_BLOCKS 10
6
7DEF_MEMPOOL(test_pool, int, NUM_BLOCKS);
8
9static int count(test_pool* pool) {
10 int count = 0;
11 mempool_foreach(pool, n, { count++; });
12 return count;
13}
14
15static int sum(test_pool* pool) {
16 int sum = 0;
17 mempool_foreach(pool, n, { sum += *n; });
18 return sum;
19}
20
21// Create a pool.
22TEST_CASE(mempool_create) {
23 test_pool pool;
24 mempool_make(&pool);
25}
26
27// Allocate all N blocks.
28TEST_CASE(mempool_allocate_until_full) {
29 test_pool pool;
30 mempool_make(&pool);
31
32 for (int i = 0; i < NUM_BLOCKS; ++i) {
33 const int* block = mempool_alloc(&pool);
34 TEST_TRUE(block != 0);
35 }
36}
37
38// Allocate all N blocks, then free them.
39TEST_CASE(mempool_fill_then_free) {
40 test_pool pool;
41 mempool_make(&pool);
42
43 int* blocks[NUM_BLOCKS] = {0};
44 for (int i = 0; i < NUM_BLOCKS; ++i) {
45 blocks[i] = mempool_alloc(&pool);
46 TEST_TRUE(blocks[i] != 0);
47 }
48
49 for (int i = 0; i < NUM_BLOCKS; ++i) {
50 mempool_free(&pool, &blocks[i]);
51 TEST_EQUAL(blocks[i], 0); // Pointer should be set to 0 on free.
52 }
53
54 TEST_EQUAL(count(&pool), 0);
55}
56
57// Attempt to allocate blocks past the maximum pool size.
58// The pool should handle the failed allocations gracefully.
59TEST_CASE(mempool_allocate_beyond_max_size) {
60 test_pool pool;
61 mempool_make(&pool);
62
63 // Fully allocate the pool.
64 for (int i = 0; i < NUM_BLOCKS; ++i) {
65 TEST_TRUE(mempool_alloc(&pool) != 0);
66 }
67
68 // Past the end.
69 for (int i = 0; i < NUM_BLOCKS; ++i) {
70 TEST_EQUAL(mempool_alloc(&pool), 0);
71 }
72}
73
74// Free blocks should always remain zeroed out.
75// This tests the invariant right after creating the pool.
76TEST_CASE(mempool_zero_free_blocks_after_creation) {
77 test_pool pool;
78 mempool_make(&pool);
79
80 const int zero = 0;
81 for (int i = 0; i < NUM_BLOCKS; ++i) {
82 const int* block = (const int*)(pool.blocks) + i;
83 TEST_EQUAL(memcmp(block, &zero, sizeof(int)), 0);
84 }
85}
86
87// Free blocks should always remain zeroed out.
88// This tests the invariant after freeing a block.
89TEST_CASE(mempool_zero_free_block_after_free) {
90 test_pool pool;
91 mempool_make(&pool);
92
93 int* val = mempool_alloc(&pool);
94 TEST_TRUE(val != 0);
95 *val = 177;
96
97 int* old_val = val;
98 mempool_free(&pool, &val); // val pointer is set to 0.
99 TEST_EQUAL(*old_val, 0); // Block is zeroed out after free.
100}
101
102// Traverse an empty pool.
103TEST_CASE(mempool_traverse_empty) {
104 test_pool pool;
105 mempool_make(&pool);
106
107 TEST_EQUAL(count(&pool), 0);
108}
109
110// Traverse a partially full pool.
111TEST_CASE(mempool_traverse_partially_full) {
112 const int N = NUM_BLOCKS / 2;
113
114 test_pool pool;
115 mempool_make(&pool);
116
117 for (int i = 0; i < N; ++i) {
118 int* val = mempool_alloc(&pool);
119 TEST_TRUE(val != 0);
120 *val = i + 1;
121 }
122
123 TEST_EQUAL(sum(&pool), N * (N + 1) / 2);
124}
125
126// Traverse a full pool.
127TEST_CASE(mempool_traverse_full) {
128 test_pool pool;
129 mempool_make(&pool);
130
131 for (int i = 0; i < NUM_BLOCKS; ++i) {
132 int* val = mempool_alloc(&pool);
133 TEST_TRUE(val != 0);
134 *val = i + 1;
135 }
136
137 TEST_EQUAL(sum(&pool), NUM_BLOCKS * (NUM_BLOCKS + 1) / 2);
138}
139
140// Get the ith (allocated) block.
141TEST_CASE(mempool_get_block) {
142 test_pool pool;
143 mempool_make(&pool);
144
145 for (int i = 0; i < NUM_BLOCKS; ++i) {
146 int* block = mempool_alloc(&pool);
147 TEST_TRUE(block != 0);
148 *block = i;
149 TEST_EQUAL(mempool_get_block_index(&pool, block), (size_t)i);
150 }
151
152 for (int i = 0; i < NUM_BLOCKS; ++i) {
153 TEST_EQUAL(*mempool_get_block(&pool, i), i);
154 }
155}
156
157int main() { return 0; }
diff --git a/mempool/test/test.h b/mempool/test/test.h
new file mode 100644
index 0000000..fd8dc22
--- /dev/null
+++ b/mempool/test/test.h
@@ -0,0 +1,185 @@
1// SPDX-License-Identifier: MIT
2#pragma once
3
4#ifdef UNIT_TEST
5
6#include <stdbool.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
12 defined(__NetBSD__) || defined(__OpenBSD__)
13#define USE_SYSCTL_FOR_ARGS 1
14// clang-format off
15#include <sys/types.h>
16#include <sys/sysctl.h>
17// clang-format on
18#include <unistd.h> // getpid
19#endif
20
21struct test_file_metadata;
22
23struct test_failure {
24 bool present;
25 const char *message;
26 const char *file;
27 int line;
28};
29
30struct test_case_metadata {
31 void (*fn)(struct test_case_metadata *, struct test_file_metadata *);
32 struct test_failure failure;
33 const char *name;
34 struct test_case_metadata *next;
35};
36
37struct test_file_metadata {
38 bool registered;
39 const char *name;
40 struct test_file_metadata *next;
41 struct test_case_metadata *tests;
42};
43
44struct test_file_metadata __attribute__((weak)) * test_file_head;
45
46#define SET_FAILURE(_message) \
47 metadata->failure = (struct test_failure) { \
48 .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \
49 }
50
51#define TEST_EQUAL(a, b) \
52 do { \
53 if ((a) != (b)) { \
54 SET_FAILURE(#a " != " #b); \
55 return; \
56 } \
57 } while (0)
58
59#define TEST_TRUE(a) \
60 do { \
61 if (!(a)) { \
62 SET_FAILURE(#a " is not true"); \
63 return; \
64 } \
65 } while (0)
66
67#define TEST_STREQUAL(a, b) \
68 do { \
69 if (strcmp(a, b) != 0) { \
70 SET_FAILURE(#a " != " #b); \
71 return; \
72 } \
73 } while (0)
74
75#define TEST_CASE(_name) \
76 static void __test_h_##_name(struct test_case_metadata *, \
77 struct test_file_metadata *); \
78 static struct test_file_metadata __test_h_file; \
79 static struct test_case_metadata __test_h_meta_##_name = { \
80 .name = #_name, \
81 .fn = __test_h_##_name, \
82 }; \
83 static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \
84 __test_h_meta_##_name.next = __test_h_file.tests; \
85 __test_h_file.tests = &__test_h_meta_##_name; \
86 if (!__test_h_file.registered) { \
87 __test_h_file.name = __FILE__; \
88 __test_h_file.next = test_file_head; \
89 test_file_head = &__test_h_file; \
90 __test_h_file.registered = true; \
91 } \
92 } \
93 static void __test_h_##_name( \
94 struct test_case_metadata *metadata __attribute__((unused)), \
95 struct test_file_metadata *file_metadata __attribute__((unused)))
96
97extern void __attribute__((weak)) (*test_h_unittest_setup)(void);
98/// Run defined tests, return true if all tests succeeds
99/// @param[out] tests_run if not NULL, set to whether tests were run
100static inline void __attribute__((constructor(102))) run_tests(void) {
101 bool should_run = false;
102#ifdef USE_SYSCTL_FOR_ARGS
103 int mib[] = {
104 CTL_KERN,
105#if defined(__NetBSD__) || defined(__OpenBSD__)
106 KERN_PROC_ARGS,
107 getpid(),
108 KERN_PROC_ARGV,
109#else
110 KERN_PROC,
111 KERN_PROC_ARGS,
112 getpid(),
113#endif
114 };
115 char *arg = NULL;
116 size_t arglen;
117 sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0);
118 arg = malloc(arglen);
119 sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0);
120#else
121 FILE *cmdlinef = fopen("/proc/self/cmdline", "r");
122 char *arg = NULL;
123 int arglen;
124 fscanf(cmdlinef, "%ms%n", &arg, &arglen);
125 fclose(cmdlinef);
126#endif
127 for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) {
128 if (strcmp(pos, "--unittest") == 0) {
129 should_run = true;
130 break;
131 }
132 }
133 free(arg);
134
135 if (!should_run) {
136 return;
137 }
138
139 if (&test_h_unittest_setup) {
140 test_h_unittest_setup();
141 }
142
143 struct test_file_metadata *i = test_file_head;
144 int failed = 0, success = 0;
145 while (i) {
146 fprintf(stderr, "Running tests from %s:\n", i->name);
147 struct test_case_metadata *j = i->tests;
148 while (j) {
149 fprintf(stderr, "\t%s ... ", j->name);
150 j->failure.present = false;
151 j->fn(j, i);
152 if (j->failure.present) {
153 fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message,
154 j->failure.file, j->failure.line);
155 failed++;
156 } else {
157 fprintf(stderr, "passed\n");
158 success++;
159 }
160 j = j->next;
161 }
162 fprintf(stderr, "\n");
163 i = i->next;
164 }
165 int total = failed + success;
166 fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total,
167 failed, total);
168 exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
169}
170
171#else
172
173#include <stdbool.h>
174
175#define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void)
176
177#define TEST_EQUAL(a, b) \
178 (void)(a); \
179 (void)(b)
180#define TEST_TRUE(a) (void)(a)
181#define TEST_STREQUAL(a, b) \
182 (void)(a); \
183 (void)(b)
184
185#endif