GCC Code Coverage Report


Directory: src/
File: src/standalone/memory_sa.c
Date: 2026-09-30 11:11:31
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 920189 align_up(size_t size, size_t alignment)
23 {
24 920189 return (size + alignment - 1) & ~(alignment - 1);
25 }
26
27 /* Allocate a new block */
28 static VsArenaBlock *
29 6350 arena_block_create(size_t min_size)
30 {
31 6350 size_t block_size = min_size > VS_ARENA_BLOCK_SIZE ? min_size
32 : VS_ARENA_BLOCK_SIZE;
33 /* Include space for header, aligned */
34 6350 size_t header_size = align_up(sizeof(VsArenaBlock), VS_ARENA_ALIGNMENT);
35 6350 size_t total_size = align_up(header_size + block_size, VS_ARENA_ALIGNMENT);
36
37 6350 VsArenaBlock *block = aligned_alloc(VS_ARENA_ALIGNMENT, total_size);
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6350 times.
6350 if (!block)
39 ✗ return NULL;
40
41 6350 block->next = NULL;
42 6350 block->size = block_size;
43 6350 block->used = 0;
44 6350 return block;
45 }
46
47 /* Free a block */
48 static void
49 6350 arena_block_free(VsArenaBlock *block)
50 {
51 6350 free(block);
52 6350 }
53
54 /* Get data pointer for block */
55 static inline void *
56 151260 arena_block_data(VsArenaBlock *block)
57 {
58 151260 size_t header_size = align_up(sizeof(VsArenaBlock), VS_ARENA_ALIGNMENT);
59 151260 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 149410 arena_alloc(VsArena *arena, size_t size, size_t alignment)
72 {
73
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 149408 times.
149410 if (size == 0)
74 2 return NULL;
75
76 149408 size_t aligned_size = align_up(size, alignment);
77 149408 size_t need = ALLOC_HDR_SIZE + aligned_size;
78
79 /* Try current block first */
80
2/2
✓ Branch 0 taken 144910 times.
✓ Branch 1 taken 4498 times.
149408 if (arena->current)
81 {
82 144910 void *data = arena_block_data(arena->current);
83 144910 uintptr_t base = (uintptr_t)data + arena->current->used;
84
85 /* Align the user pointer; header sits just before it */
86 144910 uintptr_t user_ptr = align_up(base + ALLOC_HDR_SIZE, alignment);
87 144910 uintptr_t hdr_ptr = user_ptr - ALLOC_HDR_SIZE;
88 144910 size_t end = (hdr_ptr - (uintptr_t)data) + need;
89
90
2/2
✓ Branch 0 taken 143058 times.
✓ Branch 1 taken 1852 times.
144910 if (end <= arena->current->size)
91 {
92 143058 *(size_t *)hdr_ptr = size;
93 143058 arena->current->used = end;
94 143058 arena->total_allocated += need;
95 143058 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 6350 size_t extra_for_alignment = alignment > VS_ARENA_ALIGNMENT
104 ? alignment - VS_ARENA_ALIGNMENT
105 6350 : 0;
106 6350 VsArenaBlock *block = arena_block_create(need + extra_for_alignment);
107
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6350 times.
6350 if (!block)
108 ✗ return NULL;
109
110 /* Link new block */
111 6350 block->next = arena->blocks;
112 6350 arena->blocks = block;
113 6350 arena->current = block;
114
115 /* Allocate from new block — align the user pointer */
116 6350 void *data = arena_block_data(block);
117 6350 uintptr_t base = (uintptr_t)data;
118 6350 uintptr_t user_ptr = align_up(base + ALLOC_HDR_SIZE, alignment);
119 6350 uintptr_t hdr_ptr = user_ptr - ALLOC_HDR_SIZE;
120 6350 size_t end = (hdr_ptr - base) + need;
121
122 6350 *(size_t *)hdr_ptr = size;
123 6350 block->used = end;
124 6350 arena->total_allocated += need;
125 6350 return (void *)user_ptr;
126 }
127
128 /* Create a new arena context */
129 VsMemCtx
130 5056 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 1576 times.
✓ Branch 1 taken 3480 times.
5056 if (parent)
135 1576 arena = arena_alloc(parent, sizeof(VsArena), VS_ARENA_ALIGNMENT);
136 else
137 3480 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 5056 times.
5056 if (!arena)
142 ✗ return NULL;
143
144 5056 memset(arena, 0, sizeof(VsArena));
145 5056 arena->name = name;
146 5056 arena->parent = parent;
147 5056 arena->block_size = VS_ARENA_BLOCK_SIZE;
148
149 /* Link into parent's child list */
150
2/2
✓ Branch 0 taken 1576 times.
✓ Branch 1 taken 3480 times.
5056 if (parent)
151 {
152 1576 arena->next_sibling = parent->first_child;
153 1576 parent->first_child = arena;
154 }
155
156 5056 return arena;
157 }
158
159 /* Invoke all reset callbacks for an arena */
160 static void
161 130016 invoke_reset_callbacks(VsArena *arena)
162 {
163 130016 VsMemCtxCallback *cb = arena->reset_callbacks;
164
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 130016 times.
130022 while (cb)
165 {
166 6 cb->func(cb->arg);
167 6 cb = cb->next;
168 }
169 130016 }
170
171 /* Delete arena and all children */
172 void
173 5058 vs_memctx_delete(VsMemCtx ctx)
174 {
175
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5056 times.
5058 if (!ctx)
176 2 return;
177
178 5056 VsArena *arena = ctx;
179
180 /* Invoke callbacks before destroying anything */
181 5056 invoke_reset_callbacks(arena);
182
183 /* Recursively delete children first */
184 5056 VsArena *child = arena->first_child;
185
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 5056 times.
5098 while (child)
186 {
187 42 VsArena *next = child->next_sibling;
188 42 vs_memctx_delete(child);
189 42 child = next;
190 }
191
192 /* Unlink from parent */
193
2/2
✓ Branch 0 taken 1576 times.
✓ Branch 1 taken 3480 times.
5056 if (arena->parent)
194 {
195 1576 VsArena **pp = &arena->parent->first_child;
196
3/4
✓ Branch 0 taken 1584 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1576 times.
1584 while (*pp && *pp != arena)
197 8 pp = &(*pp)->next_sibling;
198
1/2
✓ Branch 0 taken 1576 times.
✗ Branch 1 not taken.
1576 if (*pp)
199 1576 *pp = arena->next_sibling;
200 }
201
202 /* Free all blocks */
203 5056 VsArenaBlock *block = arena->blocks;
204
2/2
✓ Branch 0 taken 6348 times.
✓ Branch 1 taken 5056 times.
11404 while (block)
205 {
206 6348 VsArenaBlock *next = block->next;
207 6348 arena_block_free(block);
208 6348 block = next;
209 }
210
211 /* Free arena struct if it was top-level (no parent) */
212
2/2
✓ Branch 0 taken 3480 times.
✓ Branch 1 taken 1576 times.
5056 if (!arena->parent)
213 3480 free(arena);
214 }
215
216 /* Reset arena - free all allocations but keep arena */
217 void
218 124962 vs_memctx_reset(VsMemCtx ctx)
219 {
220
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 124960 times.
124962 if (!ctx)
221 2 return;
222
223 124960 VsArena *arena = ctx;
224
225 /* Invoke callbacks before destroying anything */
226 124960 invoke_reset_callbacks(arena);
227
228 /* Recursively delete children */
229 124960 VsArena *child = arena->first_child;
230
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 124960 times.
124964 while (child)
231 {
232 4 VsArena *next = child->next_sibling;
233 4 vs_memctx_delete(child);
234 4 child = next;
235 }
236 124960 arena->first_child = NULL;
237 124960 arena->reset_callbacks = NULL; /* Callbacks were in arena memory */
238
239 /* Free all blocks except first (if any) */
240
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 124948 times.
124960 if (arena->blocks)
241 {
242 12 VsArenaBlock *keep = arena->blocks;
243 12 VsArenaBlock *block = keep->next;
244
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
14 while (block)
245 {
246 2 VsArenaBlock *next = block->next;
247 2 arena_block_free(block);
248 2 block = next;
249 }
250 12 keep->next = NULL;
251 12 keep->used = 0;
252 12 arena->blocks = keep;
253 12 arena->current = keep;
254 }
255 else
256 {
257 124948 arena->current = NULL;
258 }
259 124960 arena->total_allocated = 0;
260 }
261
262 /* Allocate from current context */
263 void *
264 78238 vs_alloc(size_t size)
265 {
266
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78238 times.
78238 assert(vs_current_memctx != NULL);
267 78238 return arena_alloc(vs_current_memctx, size, VS_ARENA_ALIGNMENT);
268 }
269
270 void *
271 10849 vs_alloc0(size_t size)
272 {
273 10849 void *ptr = vs_alloc(size);
274
1/2
✓ Branch 0 taken 10849 times.
✗ Branch 1 not taken.
10849 if (ptr)
275 10849 memset(ptr, 0, size);
276 10849 return ptr;
277 }
278
279 void *
280 2542 vs_memctx_alloc(VsMemCtx ctx, size_t size)
281 {
282 2542 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 157 vs_realloc(void *ptr, size_t size)
296 {
297
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 155 times.
157 if (!ptr)
298 2 return vs_alloc(size);
299
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 153 times.
155 if (size == 0)
300 2 return NULL;
301
302 153 size_t old_size = *(size_t *)((char *)ptr - ALLOC_HDR_SIZE);
303 153 void *new_ptr = vs_alloc(size);
304 153 memcpy(new_ptr, ptr, old_size < size ? old_size : size);
305 153 return new_ptr;
306 }
307
308 /* Free is a no-op in arena mode */
309 void
310 33913 vs_free(void *ptr)
311 {
312 (void)ptr; /* Memory freed on context reset/delete */
313 33913 }
314
315 /* Aligned allocation from current context */
316 void *
317 67054 vs_alloc_aligned(size_t size, size_t alignment)
318 {
319
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67054 times.
67054 assert(vs_current_memctx != NULL);
320 67054 return arena_alloc(vs_current_memctx, size, alignment);
321 }
322
323 void
324 66142 vs_free_aligned(void *ptr)
325 {
326 (void)ptr; /* No-op in arena mode */
327 66142 }
328
329 /* Switch context, return old context */
330 VsMemCtx
331 19132 vs_memctx_switch(VsMemCtx ctx)
332 {
333 19132 VsMemCtx old = vs_current_memctx;
334 19132 vs_current_memctx = ctx;
335 19132 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