DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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
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) + (DET_ARENA_ALIGN - 1)) & ~(size_t)(DET_ARENA_ALIGN - 1);
22
23inline size_t align_up(size_t n)
24{
25 return (n + (DET_ARENA_ALIGN - 1)) & ~(size_t)(DET_ARENA_ALIGN - 1);
26}
27} // namespace
28
29void det_arena_init(DetArena *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 DET_ARENA_MAX_ALIGN is met by aligning its offset alone.
33 uintptr_t b = (uintptr_t)base;
34 uintptr_t ab = (b + (DET_ARENA_MAX_ALIGN - 1)) & ~(uintptr_t)(DET_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)(DET_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 : DET_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 + DET_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 a->persist_hw = a->persist_end;
89 a->persist_used += n;
90 memset(pl, 0, n);
91 return pl;
92 }
93 return nullptr; // fail closed
94}
95
97{
98 if (!p)
99 return;
100 ABlk *b = (ABlk *)((uint8_t *)p - AHDR);
101 if (b->used)
102 {
103 b->used = 0;
104 if (a->persist_used >= b->size)
105 a->persist_used -= b->size;
106 }
107
108 // Coalesce adjacent free blocks front-to-back.
109 size_t off = 0;
110 while (off < a->persist_end)
111 {
112 ABlk *cur = (ABlk *)(a->base + off);
113 size_t next_off = off + AHDR + cur->size;
114 if (!cur->used && next_off < a->persist_end)
115 {
116 ABlk *nxt = (ABlk *)(a->base + next_off);
117 if (!nxt->used)
118 {
119 cur->size += AHDR + nxt->size; // merge; recheck this block
120 continue;
121 }
122 }
123 off = next_off;
124 }
125
126 // If the last block is free, hand it back to the free middle (shrink the boundary).
127 off = 0;
128 size_t last = 0;
129 while (off < a->persist_end)
130 {
131 last = off;
132 ABlk *cur = (ABlk *)(a->base + off);
133 off += AHDR + cur->size;
134 }
135 if (a->persist_end > 0 && !((ABlk *)(a->base + last))->used)
136 a->persist_end = last;
137}
138
139// ---------------------------------------------------------------------------
140// Scratch end (bump, grows down from the top)
141// ---------------------------------------------------------------------------
142
143void *det_arena_scratch_alloc_aligned(DetArena *a, size_t n, size_t align)
144{
145 if (align < DET_ARENA_ALIGN)
146 align = DET_ARENA_ALIGN;
147 if (align > DET_ARENA_MAX_ALIGN)
148 align = DET_ARENA_MAX_ALIGN; // the base only guarantees this much
149 n = align_up(n ? n : DET_ARENA_ALIGN);
150 if (a->scratch_top < n)
151 return nullptr;
152 // The base is DET_ARENA_MAX_ALIGN-aligned, so aligning the offset down aligns the pointer.
153 size_t nt = (a->scratch_top - n) & ~(size_t)(align - 1);
154 if (nt < a->persist_end || nt > a->scratch_top)
155 return nullptr; // would cross the persistent end (or underflow)
156 a->scratch_top = nt;
157 size_t used = a->size - a->scratch_top;
158 if (used > a->scratch_hw)
159 a->scratch_hw = used;
160 return a->base + a->scratch_top;
161}
162
167
169{
170 return a->scratch_top;
171}
172
174{
175 // A mark is an earlier (higher) scratch_top; releasing frees everything below it.
176 if (mark >= a->scratch_top && mark <= a->size)
177 a->scratch_top = mark;
178}
179
181{
182 a->scratch_top = a->size;
183}
184
185// ---------------------------------------------------------------------------
186// Observability
187// ---------------------------------------------------------------------------
188
190{
191 size_t mid = (a->scratch_top > a->persist_end) ? a->scratch_top - a->persist_end : 0;
192 return mid > AHDR ? mid - AHDR : 0; // usable payload of one new persistent block
193}
194
196{
197 return a->persist_used;
198}
199
201{
202 return a->size - a->scratch_top;
203}
204
205// ===========================================================================
206// Multi-region set (DRAM base + PSRAM extension)
207// ===========================================================================
208
210{
211 s->count = 0;
212}
213
214bool det_arena_set_add(DetArenaSet *s, void *base, size_t size)
215{
217 return false;
218 DetArena *r = &s->region[s->count];
219 det_arena_init(r, base, size);
220 if (r->size < AHDR + DET_ARENA_ALIGN)
221 return false; // too small to hold even one block
222 s->count++;
223 return true;
224}
225
227{
228 for (size_t i = 0; i < s->count; i++)
229 {
230 void *p = det_arena_persist_alloc(&s->region[i], n);
231 if (p)
232 return p;
233 }
234 return nullptr; // fail closed
235}
236
238{
239 if (!p)
240 return;
241 uint8_t *b = (uint8_t *)p;
242 for (size_t i = 0; i < s->count; i++)
243 {
244 DetArena *r = &s->region[i];
245 if (b >= r->base && b < r->base + r->size)
246 {
248 return;
249 }
250 }
251}
252
253void *det_arena_set_scratch_alloc_aligned(DetArenaSet *s, size_t n, size_t align)
254{
255 for (size_t i = 0; i < s->count; i++)
256 {
257 void *p = det_arena_scratch_alloc_aligned(&s->region[i], n, align);
258 if (p)
259 return p;
260 }
261 return nullptr; // fail closed
262}
263
268
270{
271 DetArenaMark m;
272 m.count = s->count;
273 for (size_t i = 0; i < s->count; i++)
274 m.top[i] = s->region[i].scratch_top;
275 return m;
276}
277
279{
280 size_t n = m->count < s->count ? m->count : s->count;
281 for (size_t i = 0; i < n; i++)
282 det_arena_scratch_release(&s->region[i], m->top[i]);
283}
284
286{
287 for (size_t i = 0; i < s->count; i++)
289}
290
292{
293 size_t t = 0;
294 for (size_t i = 0; i < s->count; i++)
295 t += det_arena_free_bytes(&s->region[i]);
296 return t;
297}
298
300{
301 size_t t = 0;
302 for (size_t i = 0; i < s->count; i++)
303 t += det_arena_persist_used(&s->region[i]);
304 return t;
305}
306
308{
309 size_t t = 0;
310 for (size_t i = 0; i < s->count; i++)
311 t += det_arena_scratch_used(&s->region[i]);
312 return t;
313}
size_t det_arena_persist_used(const DetArena *a)
Persistent payload bytes currently allocated.
Definition arena.cpp:195
void det_arena_set_persist_free(DetArenaSet *s, void *p)
Free a persistent pointer, routed to its owning region by address.
Definition arena.cpp:237
void det_arena_persist_free(DetArena *a, void *p)
Free a pointer previously returned by det_arena_persist_alloc().
Definition arena.cpp:96
void det_arena_set_init(DetArenaSet *s)
Initialize an empty set (no regions yet).
Definition arena.cpp:209
void * det_arena_set_scratch_alloc(DetArenaSet *s, size_t n)
Scratch alloc from the first region that fits (see det_arena_scratch_alloc()).
Definition arena.cpp:264
size_t det_arena_set_scratch_used(const DetArenaSet *s)
Scratch bytes allocated, summed over all regions.
Definition arena.cpp:307
size_t det_arena_free_bytes(const DetArena *a)
Free bytes in the middle (max a single new allocation could take, minus a header).
Definition arena.cpp:189
void * det_arena_set_persist_alloc(DetArenaSet *s, size_t n)
Persistent alloc from the first region that fits (see det_arena_persist_alloc()).
Definition arena.cpp:226
void det_arena_set_scratch_release(DetArenaSet *s, const DetArenaMark *m)
Restore every region's scratch position to m (frees scratch made since).
Definition arena.cpp:278
void det_arena_set_scratch_reset(DetArenaSet *s)
Reset scratch in every region.
Definition arena.cpp:285
void * det_arena_persist_alloc(DetArena *a, size_t n)
Allocate n zero-initialized bytes of long-lived storage.
Definition arena.cpp:49
void * det_arena_set_scratch_alloc_aligned(DetArenaSet *s, size_t n, size_t align)
Aligned scratch alloc from the first region that fits (see det_arena_scratch_alloc_aligned()).
Definition arena.cpp:253
void det_arena_scratch_reset(DetArena *a)
Free ALL scratch allocations in O(1).
Definition arena.cpp:180
void det_arena_init(DetArena *a, void *base, size_t size)
Initialize a over the region [base, base+size).
Definition arena.cpp:29
size_t det_arena_scratch_used(const DetArena *a)
Scratch bytes currently allocated.
Definition arena.cpp:200
void det_arena_scratch_release(DetArena *a, size_t mark)
Free every scratch allocation made since mark (a value from det_arena_scratch_mark()).
Definition arena.cpp:173
DetArenaMark det_arena_set_scratch_mark(const DetArenaSet *s)
Capture the scratch position of every region.
Definition arena.cpp:269
void * det_arena_scratch_alloc(DetArena *a, size_t n)
Bump-allocate n transient bytes at the baseline alignment (DET_ARENA_ALIGN).
Definition arena.cpp:163
size_t det_arena_set_free_bytes(const DetArenaSet *s)
Total free middle bytes summed over all regions.
Definition arena.cpp:291
void * det_arena_scratch_alloc_aligned(DetArena *a, size_t n, size_t align)
Bump-allocate n bytes of transient storage, aligned to align.
Definition arena.cpp:143
size_t det_arena_scratch_mark(const DetArena *a)
Capture the current scratch position (a savepoint for det_arena_scratch_release()).
Definition arena.cpp:168
bool det_arena_set_add(DetArenaSet *s, void *base, size_t size)
Add a region [base, base+size); regions are searched in the order added.
Definition arena.cpp:214
size_t det_arena_set_persist_used(const DetArenaSet *s)
Persistent payload bytes allocated, summed over all regions.
Definition arena.cpp:299
Unified double-ended server arena (Phase 1: core allocator, one region).
#define DET_ARENA_ALIGN
Baseline alignment (bytes) applied to every allocation and to headers.
Definition arena.h:37
#define DET_ARENA_MAX_REGIONS
Max regions in a DetArenaSet (DRAM base + PSRAM extension).
Definition arena.h:135
#define DET_ARENA_MAX_ALIGN
Strongest alignment a scratch borrow may request; the region base is aligned to it.
Definition arena.h:40
A scratch savepoint across every region of a DetArenaSet.
Definition arena.h:147
size_t top[DET_ARENA_MAX_REGIONS]
Definition arena.h:148
size_t count
Definition arena.h:149
A set of DetArena regions searched in insertion (preference) order.
Definition arena.h:140
size_t count
Regions in use.
Definition arena.h:142
DetArena region[DET_ARENA_MAX_REGIONS]
Definition arena.h:141
Double-ended arena over one region [base, base+size).
Definition arena.h:51
size_t size
Region length in bytes.
Definition arena.h:53
size_t scratch_hw
High-water of scratch use (size - min scratch_top).
Definition arena.h:58
size_t persist_used
Bytes currently handed out by the persistent pool (payload).
Definition arena.h:56
size_t scratch_top
Scratch occupies [scratch_top, size).
Definition arena.h:55
size_t persist_hw
High-water of persist_end (for sizing).
Definition arena.h:57
uint8_t * base
Region start.
Definition arena.h:52
size_t persist_end
Persistent pool occupies [0, persist_end).
Definition arena.h:54