MULE 0.0.1
Multi-Use Library Essentials
Loading...
Searching...
No Matches
mem.c
1#include "mem/mem.h"
2
3#include <assert.h>
4#include <stdlib.h>
5#include <string.h>
6
7struct ArenaBlock {
8 ArenaBlock *prev; /**< Older block, or null. */
9 size_t used; /**< Bytes of `data` handed out. */
10 size_t cap; /**< Bytes in `data`. */
11 alignas(max_align_t) char data[]; /**< The memory itself. */
12};
13
14void *mem_realloc(const Allocator *a, void *old, size_t old_size,
15 size_t new_size) {
16 if (a != NULL) return a->fn(a->ctx, old, old_size, new_size);
17 /* realloc(p, 0) is undefined in C23, so freeing is its own branch. */
18 if (new_size == 0) {
19 free(old);
20 return NULL;
21 }
22#ifdef __APPLE__
23 /* macOS 26 keeps a large block grown by realloc dirty after free, so
24 * growing leaked memory. A fresh block and a copy returns it. */
25 if (old != NULL && new_size > old_size) {
26 void *p = malloc(new_size);
27 if (p == NULL) return NULL;
28 memcpy(p, old, old_size);
29 free(old);
30 return p;
31 }
32#endif
33 return realloc(old, new_size);
34}
35
36/* Carve `size` bytes out of `b`, or return null if they do not fit. */
37static void *mem_bump_(ArenaBlock *b, size_t size, size_t align) {
38 size_t pad = (size_t)-(uintptr_t)(b->data + b->used) & (align - 1);
39 size_t start, end;
40 if (ckd_add(&start, b->used, pad) ||
41 ckd_add(&end, start, size) ||
42 end > b->cap)
43 return NULL;
44 b->used = end;
45 return b->data + start;
46}
47
48void *arena_alloc(Arena *a, size_t size, size_t align) {
49 assert(align != 0 && (align & (align - 1)) == 0);
50 void *p = a->head != NULL ? mem_bump_(a->head, size, align) : NULL;
51 if (p == NULL) {
52 /* Room for the worst-case padding too, so the bump cannot fail. */
53 size_t cap, bytes;
54 if (ckd_add(&cap, size, align - 1)) return NULL;
55 if (cap < MEM_ARENA_BLOCK) cap = MEM_ARENA_BLOCK;
56 if (ckd_add(&bytes, cap, sizeof(ArenaBlock))) return NULL;
57
58 ArenaBlock *b = malloc(bytes);
59 if (b == NULL) return NULL;
60 *b = (ArenaBlock){ .prev = a->head, .cap = cap };
61 a->head = b;
62 p = mem_bump_(b, size, align);
63 }
64 a->last = p;
65 return p;
66}
67
68static void *mem_arena_fn_(void *ctx, void *old, size_t old_size,
69 size_t new_size) {
70 Arena *a = ctx;
71
72 /* The newest allocation ends at head->used, so it can move that mark. */
73 if (old != NULL && old == a->last) {
74 ArenaBlock *b = a->head;
75 size_t start = (size_t)((char *)old - b->data);
76 size_t end;
77 if (new_size == 0) {
78 b->used = start;
79 a->last = NULL;
80 return NULL;
81 }
82 if (!ckd_add(&end, start, new_size) && end <= b->cap) {
83 b->used = end;
84 return old;
85 }
86 }
87
88 if (new_size == 0) return NULL; /* older allocations wait for reset */
89 void *p = arena_alloc(a, new_size, alignof(max_align_t));
90 if (p != NULL && old != NULL)
91 memcpy(p, old, old_size < new_size ? old_size : new_size);
92 return p;
93}
94
96 a->allocator = (Allocator){ .fn = mem_arena_fn_, .ctx = a };
97 return &a->allocator;
98}
99
101 if (a->head == NULL) return;
102 for (ArenaBlock *b = a->head->prev; b != NULL;) {
103 ArenaBlock *prev = b->prev;
104 free(b);
105 b = prev;
106 }
107 a->head->prev = NULL;
108 a->head->used = 0;
109 a->last = NULL;
110}
111
113 for (ArenaBlock *b = a->head; b != NULL;) {
114 ArenaBlock *prev = b->prev;
115 free(b);
116 b = prev;
117 }
118 /* Keep the allocator, so Strs that point at it can still allocate. */
119 *a = (Arena){ .allocator = a->allocator };
120}
AllocatorFn * fn
Called with ctx and the mem_realloc arguments.
Definition mem.h:57
Allocator allocator
Filled by arena_allocator().
Definition mem.h:89
void * ctx
Passed to fn unchanged.
Definition mem.h:58
ArenaBlock * head
Newest block.
Definition mem.h:87
void * last
Most recent allocation, for in-place growth.
Definition mem.h:88
void * mem_realloc(const Allocator *a, void *old, size_t old_size, size_t new_size)
Resize old from old_size to new_size bytes through a.
Definition mem.c:14
#define MEM_ARENA_BLOCK
Bytes in a regular arena block.
Definition mem.h:79
void arena_reset(Arena *a)
Forget every allocation and keep the newest block for reuse.
Definition mem.c:100
const Allocator * arena_allocator(Arena *a)
An Allocator that draws from a.
Definition mem.c:95
void * arena_alloc(Arena *a, size_t size, size_t align)
Hand out size bytes from a.
Definition mem.c:48
void arena_free(Arena *a)
Release every block.
Definition mem.c:112
Where a module gets its memory.
Definition mem.h:56
Bump allocator over a chain of blocks.
Definition mem.h:86
One block of an arena.
Definition mem.c:7
char data[]
The memory itself.
Definition mem.c:11
ArenaBlock * prev
Older block, or null.
Definition mem.c:8
size_t used
Bytes of data handed out.
Definition mem.c:9
size_t cap
Bytes in data.
Definition mem.c:10