aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt3
-rw-r--r--mempool/include/mempool.h2
-rw-r--r--memstack/CMakeLists.txt30
-rw-r--r--memstack/README.md15
-rw-r--r--memstack/include/memstack.h50
-rw-r--r--memstack/src/memstack.c82
-rw-r--r--memstack/test/memstack_test.c107
-rw-r--r--memstack/test/test.h185
8 files changed, 472 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 04e0e4e..d1fb3ab 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,9 +9,10 @@ add_subdirectory(cstring)
9add_subdirectory(error) 9add_subdirectory(error)
10add_subdirectory(filesystem) 10add_subdirectory(filesystem)
11add_subdirectory(list) 11add_subdirectory(list)
12add_subdirectory(mem)
13add_subdirectory(log) 12add_subdirectory(log)
13add_subdirectory(mem)
14add_subdirectory(mempool) 14add_subdirectory(mempool)
15add_subdirectory(memstack)
15add_subdirectory(plugin) 16add_subdirectory(plugin)
16add_subdirectory(random) 17add_subdirectory(random)
17add_subdirectory(timer) 18add_subdirectory(timer)
diff --git a/mempool/include/mempool.h b/mempool/include/mempool.h
index 245173b..04d0313 100644
--- a/mempool/include/mempool.h
+++ b/mempool/include/mempool.h
@@ -106,8 +106,8 @@
106/// It is valid to mempool_free() the object at each step of the iteration. 106/// It is valid to mempool_free() the object at each step of the iteration.
107#define mempool_foreach(POOL, ITER, BODY) \ 107#define mempool_foreach(POOL, ITER, BODY) \
108 { \ 108 { \
109 size_t i = (POOL)->pool.used; \
110 if ((POOL)->pool.num_used_blocks > 0) { \ 109 if ((POOL)->pool.num_used_blocks > 0) { \
110 size_t i = (POOL)->pool.used; \
111 do { \ 111 do { \
112 if ((POOL)->pool.block_info[i].used) { \ 112 if ((POOL)->pool.block_info[i].used) { \
113 __typeof__((POOL)->object[0])* ITER = \ 113 __typeof__((POOL)->object[0])* ITER = \
diff --git a/memstack/CMakeLists.txt b/memstack/CMakeLists.txt
new file mode 100644
index 0000000..9ad1aa1
--- /dev/null
+++ b/memstack/CMakeLists.txt
@@ -0,0 +1,30 @@
1cmake_minimum_required(VERSION 3.5)
2
3project(memstack)
4
5set(CMAKE_C_STANDARD 23)
6set(CMAKE_C_STANDARD_REQUIRED On)
7set(CMAKE_C_EXTENSIONS Off)
8
9# Library
10
11add_library(memstack
12 src/memstack.c)
13
14target_include_directories(memstack PUBLIC
15 include)
16
17target_link_libraries(memstack PRIVATE
18 cassert)
19
20target_compile_options(memstack PRIVATE -Wall -Wextra)
21
22# Test
23
24add_executable(memstack_test
25 test/memstack_test.c)
26
27target_link_libraries(memstack_test
28 memstack)
29
30target_compile_options(memstack_test PRIVATE -DUNIT_TEST -DNDEBUG -Wall -Wextra)
diff --git a/memstack/README.md b/memstack/README.md
new file mode 100644
index 0000000..7eb950e
--- /dev/null
+++ b/memstack/README.md
@@ -0,0 +1,15 @@
1# Mempool
2
3A memory pool of fixed-sized blocks with O(1) allocation and deallocation.
4
5Each block in the pool is tagged with a boolean value that indicates whether the
6block is free or in use, as well as a pointer to the next free/used block.
7Blocks thus form two lists, a free list and a used list. The allocator
8maintains the two lists when allocating/deallocating blocks.
9
10The allocator's internal data is stored separately from the block data so that
11the data remains tightly packed.
12
13Free blocks in the mempool always remain zeroed out for convenience of
14programming and debugging. If the application's data structures are valid when
15zeroed out, then they do not need to be explicitly initialized.
diff --git a/memstack/include/memstack.h b/memstack/include/memstack.h
new file mode 100644
index 0000000..9a8a7ee
--- /dev/null
+++ b/memstack/include/memstack.h
@@ -0,0 +1,50 @@
1/*
2 * Stack-based allocator.
3 */
4#pragma once
5
6#include <stddef.h>
7#include <stdint.h>
8
9/// Stack-based allocator.
10typedef struct memstack {
11 size_t capacity; // Total size available.
12 uint8_t* base; // Base pointer to memory.
13 uint8_t* watermark; // Pointer to next free area of memory.
14 bool owned; // True if memory is owned by the memstack.
15 bool trap; // Whether to trap when allocating beyond capacity.
16} memstack;
17
18/// Create a stack-based allocator.
19///
20/// `stack` may be user-provided or null.
21/// - If null, the allocator malloc()s the memory for them.
22/// - If given, `stack` must be at least `capacity` bytes.
23///
24/// The memory is zeroed out for convenience.
25bool memstack_make(memstack*, size_t capacity, void* memory);
26
27/// Destroy the stack.
28///
29/// If the allocator owns the memory, then this function frees it.
30void memstack_del(memstack*);
31
32/// Clear the stack.
33void memstack_clear(memstack*);
34
35/// Allocate a new block.
36/// Return null if the block does not fit in the remaining memory.
37/// When there is no space left in the stack, allocation can either trap
38/// (default) or gracefully return null. Call mem_enable_traps() to toggle this
39/// behaviour.
40/// Newly allocated blocks are conveniently zeroed out.
41void* memstack_alloc(memstack*, size_t bytes);
42
43/// Return the stack's used size in bytes.
44size_t memstack_size(const memstack*);
45
46/// Return the stack's total capacity in bytes.
47size_t memstack_capacity(const memstack*);
48
49/// Set whether to trap when attempting to allocate beyond capacity.
50void memstack_enable_traps(memstack*, bool);
diff --git a/memstack/src/memstack.c b/memstack/src/memstack.c
new file mode 100644
index 0000000..10d1e30
--- /dev/null
+++ b/memstack/src/memstack.c
@@ -0,0 +1,82 @@
1#include "memstack.h"
2
3#include <cassert.h>
4
5#include <stdlib.h>
6#include <string.h>
7
8bool memstack_make(memstack* stack, size_t capacity, void* memory) {
9 assert(stack);
10 assert(capacity >= 1);
11
12 // Allocate memory if not user-provided.
13 uint8_t* stack_memory = memory;
14 if (!stack_memory) {
15 stack_memory = calloc(1, capacity);
16 if (stack_memory == nullptr) {
17 return false;
18 }
19 }
20 assert(stack_memory);
21
22 stack->capacity = capacity;
23 stack->base = stack_memory;
24 stack->watermark = stack_memory;
25 stack->owned = (stack_memory != memory);
26 stack->trap = true;
27
28 return true;
29}
30
31void memstack_del(memstack* stack) {
32 assert(stack);
33
34 if (stack->owned && (stack->base != nullptr)) {
35 free(stack->base);
36 stack->base = nullptr;
37 stack->owned = false;
38 }
39
40 stack->capacity = 0;
41 stack->watermark = stack->base;
42}
43
44void memstack_clear(memstack* stack) {
45 assert(stack);
46
47 stack->watermark = stack->base;
48 memset(stack->base, 0, stack->capacity);
49}
50
51void* memstack_alloc(memstack* stack, size_t bytes) {
52 assert(stack);
53
54 if ((memstack_size(stack) + bytes) > stack->capacity) {
55 if (stack->trap) {
56 FAIL("memstack allocation failed, increase the stack's capacity.");
57 }
58 return nullptr; // Block does not fit in remaining memory.
59 }
60
61 // Allocate the block.
62 uint8_t* block = stack->watermark;
63 stack->watermark += bytes;
64 assert(memstack_size(stack) <= stack->capacity);
65
66 return block;
67}
68
69size_t memstack_size(const memstack* stack) {
70 assert(stack);
71 return stack->watermark - stack->base;
72}
73
74size_t memstack_capacity(const memstack* stack) {
75 assert(stack);
76 return stack->capacity;
77}
78
79void memstack_enable_traps(memstack* stack, bool enable) {
80 assert(stack);
81 stack->trap = enable;
82}
diff --git a/memstack/test/memstack_test.c b/memstack/test/memstack_test.c
new file mode 100644
index 0000000..5e9b493
--- /dev/null
+++ b/memstack/test/memstack_test.c
@@ -0,0 +1,107 @@
1#include "memstack.h"
2
3#include "test.h"
4
5#define NUM_INTS 10
6#define CAPACITY (NUM_INTS * sizeof(int))
7
8// Create and destroy a statically-backed stack.
9TEST_CASE(memstack_create) {
10 int memory[CAPACITY];
11
12 memstack stack = {0};
13 memstack_make(&stack, CAPACITY, memory);
14 memstack_del(&stack);
15}
16
17// Create and destroy a dynamically-backed stack.
18TEST_CASE(mem_create_dyn) {
19 memstack stack = {0};
20 memstack_make(&stack, CAPACITY, nullptr);
21 memstack_del(&stack);
22}
23
24// Allocate all N ints.
25TEST_CASE(memstack_allocate_until_full) {
26 memstack stack = {0};
27 memstack_make(&stack, CAPACITY, nullptr);
28
29 for (int i = 0; i < NUM_INTS; ++i) {
30 const int* block = memstack_alloc(&stack, sizeof(int));
31 TEST_TRUE(block != nullptr);
32 }
33
34 TEST_TRUE(memstack_size(&stack) == CAPACITY);
35}
36
37// Allocate all N ints, then free them.
38TEST_CASE(memstack_fill_then_free) {
39 memstack stack = {0};
40 memstack_make(&stack, CAPACITY, nullptr);
41
42 int* blocks[NUM_INTS] = {nullptr};
43 for (int i = 0; i < NUM_INTS; ++i) {
44 blocks[i] = memstack_alloc(&stack, sizeof(int));
45 TEST_TRUE(blocks[i] != nullptr);
46 }
47
48 memstack_clear(&stack);
49
50 TEST_EQUAL(memstack_size(&stack), 0);
51}
52
53// Attempt to allocate blocks past the maximum stack size.
54// The stack should handle the failed allocations gracefully.
55TEST_CASE(memstack_allocate_beyond_max_size) {
56 memstack stack = {0};
57 memstack_make(&stack, CAPACITY, nullptr);
58 memstack_enable_traps(&stack, false);
59
60 // Fully allocate the stack.
61 for (int i = 0; i < NUM_INTS; ++i) {
62 TEST_TRUE(memstack_alloc(&stack, sizeof(int)) != nullptr);
63 }
64
65 // Past the end.
66 for (int i = 0; i < NUM_INTS; ++i) {
67 TEST_EQUAL(memstack_alloc(&stack, sizeof(int)), nullptr);
68 }
69
70 TEST_TRUE(memstack_size(&stack) == CAPACITY);
71}
72
73// Free blocks should always remain zeroed out.
74// This tests the invariant right after creating the stack.
75TEST_CASE(memstack_zero_free_blocks_after_creation) {
76 memstack stack = {0};
77 memstack_make(&stack, CAPACITY, nullptr);
78
79 for (int i = 0; i < NUM_INTS; ++i) {
80 const int* block = memstack_alloc(&stack, sizeof(int));
81 TEST_TRUE(block != nullptr);
82 TEST_EQUAL(*block, 0);
83 }
84}
85
86// Free blocks should always remain zeroed out.
87// This tests the invariant after clearing the stack and allocating a new block.
88TEST_CASE(memstack_zero_free_block_after_free) {
89 memstack stack = {0};
90 memstack_make(&stack, CAPACITY, nullptr);
91
92 for (int i = 0; i < NUM_INTS; ++i) {
93 const int* block = memstack_alloc(&stack, sizeof(int));
94 TEST_TRUE(block != nullptr);
95 TEST_EQUAL(*block, 0);
96 }
97
98 memstack_clear(&stack);
99
100 for (int i = 0; i < NUM_INTS; ++i) {
101 const int* block = memstack_alloc(&stack, sizeof(int));
102 TEST_TRUE(block != nullptr);
103 TEST_EQUAL(*block, 0);
104 }
105}
106
107int main() { return 0; }
diff --git a/memstack/test/test.h b/memstack/test/test.h
new file mode 100644
index 0000000..fd8dc22
--- /dev/null
+++ b/memstack/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