ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
arena.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file arena.cpp
6 * @brief Unified double-ended server arena (Phase 1 core). See arena.h for the model.
7 */
8
9#include "server/mmgr/arena.h"
10#include <string.h>
11
12namespace
13{
14// Persistent-pool block header: a chain of these spans [0, persist_end).
15struct ABlk
16{
17 size_t size; ///< payload bytes
18 size_t used; ///< 0 = free, 1 = in use
19};
20// Header size, rounded up to the arena alignment so payloads stay aligned.
21const size_t AHDR = (sizeof(ABlk) + (PC_ARENA_ALIGN - 1)) & ~(size_t)(PC_ARENA_ALIGN - 1);
22
23inline size_t align_up(size_t n) // persist path only; the scratch path uses pc_arena_align_up
24{
25 return (n + (PC_ARENA_ALIGN - 1)) & ~(size_t)(PC_ARENA_ALIGN - 1);
26}
27} // namespace
28
29void pc_arena_init(pc_arena *a, void *base, size_t size)
30{
31 // Align the base up to the strongest supported alignment and the size down, so a
32 // scratch borrow up to PC_ARENA_MAX_ALIGN is met by aligning its offset alone.
33 uintptr_t b = (uintptr_t)base;
34 uintptr_t ab = (b + (PC_ARENA_MAX_ALIGN - 1)) & ~(uintptr_t)(PC_ARENA_MAX_ALIGN - 1);
35 size_t adj = (size_t)(ab - b);
36 a->base = (uint8_t *)ab;
37 a->size = (size > adj) ? ((size - adj) & ~(size_t)(PC_ARENA_ALIGN - 1)) : 0;
38 a->persist_end = 0;
39 a->scratch_top = a->size;
40 a->persist_used = 0;
41 a->persist_hw = 0;
42 a->scratch_hw = 0;
43}
44
45// ---------------------------------------------------------------------------
46// Persistent end (first-fit, grows up from the bottom)
47// ---------------------------------------------------------------------------
48
50{
51 n = align_up(n ? n : PC_ARENA_ALIGN);
52
53 // First-fit over the existing block chain.
54 size_t off = 0;
55 while (off < a->persist_end)
56 {
57 ABlk *b = (ABlk *)(a->base + off);
58 if (!b->used && b->size >= n)
59 {
60 // Split if the remainder can hold another header + a minimum payload.
61 if (b->size >= n + AHDR + PC_ARENA_ALIGN)
62 {
63 ABlk *nb = (ABlk *)(a->base + off + AHDR + n);
64 nb->size = b->size - n - AHDR;
65 nb->used = 0;
66 b->size = n;
67 }
68 b->used = 1;
69 a->persist_used += b->size;
70 void *pl = a->base + off + AHDR;
71 memset(pl, 0, b->size);
72 return pl;
73 }
74 off += AHDR + b->size;
75 }
76
77 // No reusable block: carve a fresh one from the free middle (grow the boundary up),
78 // but only if it will not cross the scratch end.
79 size_t need = AHDR + n;
80 if (a->persist_end + need <= a->scratch_top && a->persist_end + need >= need)
81 {
82 ABlk *b = (ABlk *)(a->base + a->persist_end);
83 b->size = n;
84 b->used = 1;
85 void *pl = a->base + a->persist_end + AHDR;
86 a->persist_end += need;
87 if (a->persist_end > a->persist_hw)
88 {
89 a->persist_hw = a->persist_end;
90 }
91 a->persist_used += n;
92 memset(pl, 0, n);
93 return pl;
94 }
95 return nullptr; // fail closed
96}
97
99{
100 if (!p)
101 {
102 return;
103 }
104 ABlk *b = (ABlk *)((uint8_t *)p - AHDR);
105 if (b->used)
106 {
107 b->used = 0;
108 // The false half is unreachable: persist_used only ever accumulates this same block's
109 // own size (at alloc time), so it can never be smaller than b->size while b->used was true.
110 if (a->persist_used >= b->size) // GCOVR_EXCL_BR_LINE
111 {
112 a->persist_used -= b->size;
113 }
114 }
115
116 // Coalesce adjacent free blocks front-to-back.
117 size_t off = 0;
118 while (off < a->persist_end)
119 {
120 ABlk *cur = (ABlk *)(a->base + off);
121 size_t next_off = off + AHDR + cur->size;
122 if (!cur->used && next_off < a->persist_end)
123 {
124 ABlk *nxt = (ABlk *)(a->base + next_off);
125 if (!nxt->used)
126 {
127 cur->size += AHDR + nxt->size; // merge; recheck this block
128 continue;
129 }
130 }
131 off = next_off;
132 }
133
134 // If the last block is free, hand it back to the free middle (shrink the boundary).
135 off = 0;
136 size_t last = 0;
137 while (off < a->persist_end)
138 {
139 last = off;
140 ABlk *cur = (ABlk *)(a->base + off);
141 off += AHDR + cur->size;
142 }
143 if (a->persist_end > 0 && !((ABlk *)(a->base + last))->used)
144 {
145 a->persist_end = last;
146 }
147}
148
149// ---------------------------------------------------------------------------
150// Scratch end (bump, grows down from the top)
151// ---------------------------------------------------------------------------
152
154{
156}
157
158// ---------------------------------------------------------------------------
159// Observability
160// ---------------------------------------------------------------------------
161
163{
164 size_t mid = (a->scratch_top > a->persist_end) ? a->scratch_top - a->persist_end : 0;
165 return mid > AHDR ? mid - AHDR : 0; // usable payload of one new persistent block
166}
167
169{
170 return a->persist_used;
171}
172
173// ===========================================================================
174// Multi-region set (DRAM base + PSRAM extension)
175// ===========================================================================
176
178{
179 s->count = 0;
180}
181
182bool pc_arena_set_add(pc_arena_set *s, void *base, size_t size)
183{
184 if (s->count >= PC_ARENA_MAX_REGIONS)
185 {
186 return false;
187 }
188 pc_arena *r = &s->region[s->count];
189 pc_arena_init(r, base, size);
190 if (r->size < AHDR + PC_ARENA_ALIGN)
191 {
192 return false; // too small to hold even one block
193 }
194 s->count++;
195 return true;
196}
197
199{
200 for (size_t i = 0; i < s->count; i++)
201 {
202 void *p = pc_arena_persist_alloc(&s->region[i], n);
203 if (p)
204 {
205 return p;
206 }
207 }
208 return nullptr; // fail closed
209}
210
212{
213 if (!p)
214 {
215 return;
216 }
217 uint8_t *b = (uint8_t *)p;
218 for (size_t i = 0; i < s->count; i++)
219 {
220 pc_arena *r = &s->region[i];
221 if (b >= r->base && b < r->base + r->size)
222 {
224 return;
225 }
226 }
227}
228
229void *pc_arena_set_scratch_alloc_aligned(pc_arena_set *s, size_t n, size_t align)
230{
231 for (size_t i = 0; i < s->count; i++)
232 {
233 void *p = pc_arena_scratch_alloc_aligned(&s->region[i], n, align);
234 if (p)
235 {
236 return p;
237 }
238 }
239 return nullptr; // fail closed
240}
241
246
248{
250 m.count = s->count;
251 for (size_t i = 0; i < s->count; i++)
252 {
253 m.top[i] = s->region[i].scratch_top;
254 }
255 return m;
256}
257
259{
260 size_t n = m->count < s->count ? m->count : s->count;
261 for (size_t i = 0; i < n; i++)
262 {
263 pc_arena_scratch_release(&s->region[i], m->top[i]);
264 }
265}
266
268{
269 for (size_t i = 0; i < s->count; i++)
270 {
272 }
273}
274
276{
277 size_t t = 0;
278 for (size_t i = 0; i < s->count; i++)
279 {
280 t += pc_arena_free_bytes(&s->region[i]);
281 }
282 return t;
283}
284
286{
287 size_t t = 0;
288 for (size_t i = 0; i < s->count; i++)
289 {
290 t += pc_arena_persist_used(&s->region[i]);
291 }
292 return t;
293}
294
296{
297 size_t t = 0;
298 for (size_t i = 0; i < s->count; i++)
299 {
300 t += pc_arena_scratch_used(&s->region[i]);
301 }
302 return t;
303}
size_t pc_arena_set_scratch_used(const pc_arena_set *s)
Scratch bytes allocated, summed over all regions.
Definition arena.cpp:295
void * pc_arena_set_scratch_alloc_aligned(pc_arena_set *s, size_t n, size_t align)
Aligned scratch alloc from the first region that fits (see pc_arena_scratch_alloc_aligned()).
Definition arena.cpp:229
bool pc_arena_set_add(pc_arena_set *s, void *base, size_t size)
Add a region [base, base+size); regions are searched in the order added.
Definition arena.cpp:182
void * pc_arena_scratch_alloc(pc_arena *a, size_t n)
Bump-allocate n transient bytes at the baseline alignment (PC_ARENA_ALIGN).
Definition arena.cpp:153
void * pc_arena_set_scratch_alloc(pc_arena_set *s, size_t n)
Scratch alloc from the first region that fits (see pc_arena_scratch_alloc()).
Definition arena.cpp:242
size_t pc_arena_set_persist_used(const pc_arena_set *s)
Persistent payload bytes allocated, summed over all regions.
Definition arena.cpp:285
void pc_arena_set_scratch_reset(pc_arena_set *s)
Reset scratch in every region.
Definition arena.cpp:267
void pc_arena_set_persist_free(pc_arena_set *s, void *p)
Free a persistent pointer, routed to its owning region by address.
Definition arena.cpp:211
void pc_arena_set_scratch_release(pc_arena_set *s, const pc_arena_mark *m)
Restore every region's scratch position to m (frees scratch made since).
Definition arena.cpp:258
pc_arena_mark pc_arena_set_scratch_mark(const pc_arena_set *s)
Capture the scratch position of every region.
Definition arena.cpp:247
void pc_arena_persist_free(pc_arena *a, void *p)
Free a pointer previously returned by pc_arena_persist_alloc().
Definition arena.cpp:98
size_t pc_arena_free_bytes(const pc_arena *a)
Free bytes in the middle (max a single new allocation could take, minus a header).
Definition arena.cpp:162
void * pc_arena_set_persist_alloc(pc_arena_set *s, size_t n)
Persistent alloc from the first region that fits (see pc_arena_persist_alloc()).
Definition arena.cpp:198
void pc_arena_set_init(pc_arena_set *s)
Initialize an empty set (no regions yet).
Definition arena.cpp:177
size_t pc_arena_persist_used(const pc_arena *a)
Persistent payload bytes currently allocated.
Definition arena.cpp:168
void * pc_arena_persist_alloc(pc_arena *a, size_t n)
Allocate n zero-initialized bytes of long-lived storage.
Definition arena.cpp:49
void pc_arena_init(pc_arena *a, void *base, size_t size)
Initialize a over the region [base, base+size).
Definition arena.cpp:29
size_t pc_arena_set_free_bytes(const pc_arena_set *s)
Total free middle bytes summed over all regions.
Definition arena.cpp:275
Unified double-ended server arena (Phase 1: core allocator, one region).
void pc_arena_scratch_reset(pc_arena *a)
Free ALL scratch allocations in O(1).
Definition arena.h:162
#define PC_ARENA_MAX_ALIGN
Strongest alignment a scratch borrow may request; the region base is aligned to it.
Definition arena.h:46
#define PC_ARENA_ALIGN
Baseline alignment (bytes) applied to every allocation and to headers.
Definition arena.h:37
#define PC_ARENA_MAX_REGIONS
Max regions in a pc_arena_set (DRAM base + PSRAM extension).
Definition arena.h:216
void pc_arena_scratch_release(pc_arena *a, size_t mark)
Free every scratch allocation made since mark (a value from pc_arena_scratch_mark()).
Definition arena.h:152
void * pc_arena_scratch_alloc_aligned(pc_arena *a, size_t n, size_t align)
Bump-allocate n bytes of transient storage, aligned to align.
Definition arena.h:109
size_t pc_arena_scratch_used(const pc_arena *a)
Scratch bytes currently allocated.
Definition arena.h:198
A scratch savepoint across every region of a pc_arena_set.
Definition arena.h:228
size_t top[PC_ARENA_MAX_REGIONS]
Definition arena.h:229
size_t count
Definition arena.h:230
A set of pc_arena regions searched in insertion (preference) order.
Definition arena.h:221
pc_arena region[PC_ARENA_MAX_REGIONS]
Definition arena.h:222
size_t count
Regions in use.
Definition arena.h:223
Double-ended arena over one region [base, base+size).
Definition arena.h:57
size_t persist_used
Bytes currently handed out by the persistent pool (payload).
Definition arena.h:62
size_t scratch_top
Scratch occupies [scratch_top, size).
Definition arena.h:61
uint8_t * base
Region start.
Definition arena.h:58
size_t scratch_hw
High-water of scratch use (size - min scratch_top).
Definition arena.h:64
size_t persist_hw
High-water of persist_end (for sizing).
Definition arena.h:63
size_t size
Region length in bytes.
Definition arena.h:59
size_t persist_end
Persistent pool occupies [0, persist_end).
Definition arena.h:60