GCC Code Coverage Report


Directory: src/
File: src/standalone/memory_sa.c
Date: 2026-10-23 09:17:32
Exec Total Coverage
Lines: 160 163 98.2%
Functions: 20 20 100.0%
Branches: 46 56 82.1%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2026 Tiger Data, Inc.
3 * Licensed under the PostgreSQL License. See LICENSE for details.
4 *
5 * memory_standalone.c - Arena allocator implementation
6 *
7 * Implements a memory context system compatible with PostgreSQL's semantics
8 * but without PostgreSQL dependencies. Used for standalone testing.
9 */
10
11 #include <assert.h>
12 #include <stdlib.h>
13 #include <string.h>
14
15 #include "standalone/memory_sa.h"
16
17 /* Thread-local current context */
18 _Thread_local VsMemCtx vs_current_memctx = NULL;
19
20 /* Align size up to alignment boundary */
21 static inline size_t
22 948436 align_up(size_t size, size_t alignment)
23 {
24 948436 return (size + alignment - 1) & ~(alignment - 1);
25 }
26
27 /* Allocate a new block */
28 static VsArenaBlock *
29 6638 arena_block_create(size_t min_size)
30 {
31 6638 size_t block_size = min_size > VS_ARENA_BLOCK_SIZE ? min_size
32 : VS_ARENA_BLOCK_SIZE;
33 /* Include space for header, aligned */
34 6638 size_t header_size = align_up(sizeof(VsArenaBlock), VS_ARENA_ALIGNMENT);
35 6638 size_t total_size = align_up(header_size + block_size, VS_ARENA_ALIGNMENT);
36
37 6638 VsArenaBlock *block = aligned_alloc(VS_ARENA_ALIGNMENT, total_size);
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6638 times.
6638 if (!block)
39 ✗ return NULL;
40
41 6638 block->next = NULL;
42 6638 block->size = block_size;
43 6638 block->used = 0;
44 6638 return block;
45 }
46
47 /* Free a block */
48 static void
49 6638 arena_block_free(VsArenaBlock *block)
50 {
51 6638 free(block);
52 6638 }
53
54 /* Get data pointer for block */
55 static inline void *
56 155863 arena_block_data(VsArenaBlock *block)
57 {
58 155863 size_t header_size = align_up(sizeof(VsArenaBlock), VS_ARENA_ALIGNMENT);
59 155863 return (char *)block + header_size;
60 }
61
62 /*
63 * Each allocation is prefixed with a size_t header storing the
64 * requested size. This lets vs_realloc copy min(old, new) bytes
65 * without reading past the old allocation.
66 */
67 #define ALLOC_HDR_SIZE align_up(sizeof(size_t), VS_ARENA_ALIGNMENT)
68
69 /* Allocate from arena with specified alignment */
70 static void *
71 153939 arena_alloc(VsArena *arena, size_t size, size_t alignment)
72 {
73
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 153937 times.
153939 if (size == 0)
74 2 return NULL;
75
76 153937 size_t aligned_size = align_up(size, alignment);
77 153937 size_t need = ALLOC_HDR_SIZE + aligned_size;
78
79 /* Try current block first */
80
2/2
✓ Branch 0 taken 149225 times.
✓ Branch 1 taken 4712 times.
153937 if (arena->current)
81 {
82 149225 void *data = arena_block_data(arena->current);
83 149225 uintptr_t base = (uintptr_t)data + arena->current->used;
84
85 /* Align the user pointer; header sits just before it */
86 149225 uintptr_t user_ptr = align_up(base + ALLOC_HDR_SIZE, alignment);
87 149225 uintptr_t hdr_ptr = user_ptr - ALLOC_HDR_SIZE;
88 149225 size_t end = (hdr_ptr - (uintptr_t)data) + need;
89
90
2/2
✓ Branch 0 taken 147299 times.
✓ Branch 1 taken 1926 times.
149225 if (end <= arena->current->size)
91 {
92 147299 *(size_t *)hdr_ptr = size;
93 147299 arena->current->used = end;
94 147299 arena->total_allocated += need;
95 147299 return (void *)user_ptr;
96 }
97 }
98
99 /*
100 * Need a new block. Request extra space for alignment padding.
101 * Worst case: we need (alignment - 1) extra bytes to align.
102 */
103 6638 size_t extra_for_alignment = alignment > VS_ARENA_ALIGNMENT
104 ? alignment - VS_ARENA_ALIGNMENT
105 6638 : 0;
106 6638 VsArenaBlock *block = arena_block_create(need + extra_for_alignment);
107
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6638 times.
6638 if (!block)
108 ✗ return NULL;
109
110 /* Link new block */
111 6638 block->next = arena->blocks;
112 6638 arena->blocks = block;
113 6638 arena->current = block;
114
115 /* Allocate from new block — align the user pointer */
116 6638 void *data = arena_block_data(block);
117 6638 uintptr_t base = (uintptr_t)data;
118 6638 uintptr_t user_ptr = align_up(base + ALLOC_HDR_SIZE, alignment);
119 6638 uintptr_t hdr_ptr = user_ptr - ALLOC_HDR_SIZE;
120 6638 size_t end = (hdr_ptr - base) + need;
121
122 6638 *(size_t *)hdr_ptr = size;
123 6638 block->used = end;
124 6638 arena->total_allocated += need;
125 6638 return (void *)user_ptr;
126 }
127
128 /* Create a new arena context */
129 VsMemCtx
130 5276 vs_memctx_create(VsMemCtx parent, const char *name)
131 {
132 /* Allocate arena struct from parent or malloc */
133 VsArena *arena;
134
2/2
✓ Branch 0 taken 1616 times.
✓ Branch 1 taken 3660 times.
5276 if (parent)
135 1616 arena = arena_alloc(parent, sizeof(VsArena), VS_ARENA_ALIGNMENT);
136 else
137 3660 arena = aligned_alloc(
138 VS_ARENA_ALIGNMENT,
139 align_up(sizeof(VsArena), VS_ARENA_ALIGNMENT));
140
141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5276 times.
5276 if (!arena)
142 ✗ return NULL;
143
144 5276 memset(arena, 0, sizeof(VsArena));
145 5276 arena->name = name;
146 5276 arena->parent = parent;
147 5276 arena->block_size = VS_ARENA_BLOCK_SIZE;
148
149 /* Link into parent's child list */
150
2/2
✓ Branch 0 taken 1616 times.
✓ Branch 1 taken 3660 times.
5276 if (parent)
151 {
152 1616 arena->next_sibling = parent->first_child;
153 1616 parent->first_child = arena;
154 }
155
156 5276 return arena;
157 }
158
159 /* Invoke all reset callbacks for an arena */
160 static void
161 135034 invoke_reset_callbacks(VsArena *arena)
162 {
163 135034 VsMemCtxCallback *cb = arena->reset_callbacks;
164
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 135034 times.
135040 while (cb)
165 {
166 6 cb->func(cb->arg);
167 6 cb = cb->next;
168 }
169 135034 }
170
171 /* Delete arena and all children */
172 void
173 5278 vs_memctx_delete(VsMemCtx ctx)
174 {
175
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5276 times.
5278 if (!ctx)
176 2 return;
177
178 5276 VsArena *arena = ctx;
179
180 /* Invoke callbacks before destroying anything */
181 5276 invoke_reset_callbacks(arena);
182
183 /* Recursively delete children first */
184 5276 VsArena *child = arena->first_child;
185
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 5276 times.
5320 while (child)
186 {
187 44 VsArena *next = child->next_sibling;
188 44 vs_memctx_delete(child);
189 44 child = next;
190 }
191
192 /* Unlink from parent */
193
2/2
✓ Branch 0 taken 1616 times.
✓ Branch 1 taken 3660 times.
5276 if (arena->parent)
194 {
195 1616 VsArena **pp = &arena->parent->first_child;
196
3/4
✓ Branch 0 taken 1624 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1616 times.
1624 while (*pp && *pp != arena)
197 8 pp = &(*pp)->next_sibling;
198
1/2
✓ Branch 0 taken 1616 times.
✗ Branch 1 not taken.
1616 if (*pp)
199 1616 *pp = arena->next_sibling;
200 }
201
202 /* Free all blocks */
203 5276 VsArenaBlock *block = arena->blocks;
204
2/2
✓ Branch 0 taken 6634 times.
✓ Branch 1 taken 5276 times.
11910 while (block)
205 {
206 6634 VsArenaBlock *next = block->next;
207 6634 arena_block_free(block);
208 6634 block = next;
209 }
210
211 /* Free arena struct if it was top-level (no parent) */
212
2/2
✓ Branch 0 taken 3660 times.
✓ Branch 1 taken 1616 times.
5276 if (!arena->parent)
213 3660 free(arena);
214 }
215
216 /* Reset arena - free all allocations but keep arena */
217 void
218 129760 vs_memctx_reset(VsMemCtx ctx)
219 {
220
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 129758 times.
129760 if (!ctx)
221 2 return;
222
223 129758 VsArena *arena = ctx;
224
225 /* Invoke callbacks before destroying anything */
226 129758 invoke_reset_callbacks(arena);
227
228 /* Recursively delete children */
229 129758 VsArena *child = arena->first_child;
230
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 129758 times.
129762 while (child)
231 {
232 4 VsArena *next = child->next_sibling;
233 4 vs_memctx_delete(child);
234 4 child = next;
235 }
236 129758 arena->first_child = NULL;
237 129758 arena->reset_callbacks = NULL; /* Callbacks were in arena memory */
238
239 /* Free all blocks except first (if any) */
240
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 129736 times.
129758 if (arena->blocks)
241 {
242 22 VsArenaBlock *keep = arena->blocks;
243 22 VsArenaBlock *block = keep->next;
244
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 22 times.
26 while (block)
245 {
246 4 VsArenaBlock *next = block->next;
247 4 arena_block_free(block);
248 4 block = next;
249 }
250 22 keep->next = NULL;
251 22 keep->used = 0;
252 22 arena->blocks = keep;
253 22 arena->current = keep;
254 }
255 else
256 {
257 129736 arena->current = NULL;
258 }
259 129758 arena->total_allocated = 0;
260 }
261
262 /* Allocate from current context */
263 void *
264 78388 vs_alloc(size_t size)
265 {
266
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78388 times.
78388 assert(vs_current_memctx != NULL);
267 78388 return arena_alloc(vs_current_memctx, size, VS_ARENA_ALIGNMENT);
268 }
269
270 void *
271 11138 vs_alloc0(size_t size)
272 {
273 11138 void *ptr = vs_alloc(size);
274
1/2
✓ Branch 0 taken 11138 times.
✗ Branch 1 not taken.
11138 if (ptr)
275 11138 memset(ptr, 0, size);
276 11138 return ptr;
277 }
278
279 void *
280 3349 vs_memctx_alloc(VsMemCtx ctx, size_t size)
281 {
282 3349 return arena_alloc(ctx, size, VS_ARENA_ALIGNMENT);
283 }
284
285 void *
286 2 vs_memctx_alloc0(VsMemCtx ctx, size_t size)
287 {
288 2 void *ptr = vs_memctx_alloc(ctx, size);
289
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (ptr)
290 2 memset(ptr, 0, size);
291 2 return ptr;
292 }
293
294 void *
295 276 vs_realloc(void *ptr, size_t size)
296 {
297
2/2
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 176 times.
276 if (!ptr)
298 100 return vs_alloc(size);
299
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 174 times.
176 if (size == 0)
300 2 return NULL;
301
302 174 size_t old_size = *(size_t *)((char *)ptr - ALLOC_HDR_SIZE);
303 174 void *new_ptr = vs_alloc(size);
304 174 memcpy(new_ptr, ptr, old_size < size ? old_size : size);
305 174 return new_ptr;
306 }
307
308 /* Free is a no-op in arena mode */
309 void
310 32894 vs_free(void *ptr)
311 {
312 (void)ptr; /* Memory freed on context reset/delete */
313 32894 }
314
315 /* Aligned allocation from current context */
316 void *
317 70586 vs_alloc_aligned(size_t size, size_t alignment)
318 {
319
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70586 times.
70586 assert(vs_current_memctx != NULL);
320 70586 return arena_alloc(vs_current_memctx, size, alignment);
321 }
322
323 void
324 69618 vs_free_aligned(void *ptr)
325 {
326 (void)ptr; /* No-op in arena mode */
327 69618 }
328
329 /* Switch context, return old context */
330 VsMemCtx
331 19548 vs_memctx_switch(VsMemCtx ctx)
332 {
333 19548 VsMemCtx old = vs_current_memctx;
334 19548 vs_current_memctx = ctx;
335 19548 return old;
336 }
337
338 /* Get total bytes allocated in context */
339 size_t
340 16 vs_memctx_total_allocated(VsMemCtx ctx)
341 {
342
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 return ctx ? ctx->total_allocated : 0;
343 }
344
345 /* Register a callback to be called on context reset or delete */
346 void
347 6 vs_memctx_register_reset_callback(
348 VsMemCtx ctx, VsMemCtxCallback *cb, void (*func)(void *), void *arg)
349 {
350 6 cb->func = func;
351 6 cb->arg = arg;
352 6 cb->next = ctx->reset_callbacks;
353 6 ctx->reset_callbacks = cb;
354 6 }
355