DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
arena.h
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.h
6 * @brief Unified double-ended server arena (Phase 1: core allocator, one region).
7 *
8 * One contiguous region is shared by two allocators that grow toward each other, with
9 * the free space floating in the middle:
10 *
11 * [ persistent --grows up--> | free | <--grows down-- scratch ]
12 * low addr (floating boundary) high addr
13 *
14 * - **Persistent** (bottom): a first-fit free-list. Long-lived objects that are freed
15 * individually, in arbitrary order (e.g. per-connection state). Grows up into the
16 * middle only as far as the scratch end allows; a freed top block shrinks it back.
17 * - **Scratch** (top): a bump allocator reclaimed in bulk. Transient per-dispatch
18 * buffers. `scratch_reset()` empties it in O(1); `mark`/`release` give nested savepoints.
19 *
20 * Whichever side needs more room grows into the shared middle - that is the win over two
21 * fixed pools. Both ends fail closed (return NULL) rather than crossing the boundary.
22 *
23 * All state lives in ::DetArena (no globals), so it is unit-testable and can back several
24 * arenas (a DRAM base and a PSRAM extension, in a later phase). No heap; no stdlib.
25 *
26 * @author Douglas Quigg (dstroy0)
27 * @date 2026
28 */
29
30#ifndef DETERMINISTICESPASYNCWEBSERVER_DET_ARENA_H
31#define DETERMINISTICESPASYNCWEBSERVER_DET_ARENA_H
32
33#include <stddef.h>
34#include <stdint.h>
35
36/** @brief Baseline alignment (bytes) applied to every allocation and to headers. */
37#define DET_ARENA_ALIGN 8u
38
39/** @brief Strongest alignment a scratch borrow may request; the region base is aligned to it. */
40#define DET_ARENA_MAX_ALIGN 16u
41
42/**
43 * @brief Double-ended arena over one region `[base, base+size)`.
44 *
45 * `persist_end` and `scratch_top` are byte offsets from `base`; the free middle is
46 * `[persist_end, scratch_top)`. The persistent pool owns `[0, persist_end)` (a chain of
47 * first-fit blocks); scratch owns `[scratch_top, size)` (bump). Initialize with
48 * det_arena_init(); do not touch the fields directly.
49 */
50typedef struct
51{
52 uint8_t *base; ///< Region start.
53 size_t size; ///< Region length in bytes.
54 size_t persist_end; ///< Persistent pool occupies [0, persist_end).
55 size_t scratch_top; ///< Scratch occupies [scratch_top, size).
56 size_t persist_used; ///< Bytes currently handed out by the persistent pool (payload).
57 size_t persist_hw; ///< High-water of persist_end (for sizing).
58 size_t scratch_hw; ///< High-water of scratch use (size - min scratch_top).
59} DetArena;
60
61/**
62 * @brief Initialize @p a over the region `[base, base+size)`.
63 *
64 * @p base should be DET_ARENA_ALIGN-aligned; @p size must be at least a few blocks.
65 */
66void det_arena_init(DetArena *a, void *base, size_t size);
67
68// --- persistent end (first-fit, individual free, grows up) ------------------
69
70/**
71 * @brief Allocate @p n zero-initialized bytes of long-lived storage.
72 *
73 * First-fits an existing free block; otherwise carves a new block from the free middle
74 * (growing the persistent end up) if it will not cross the scratch end.
75 * @return aligned, zeroed pointer, or NULL if the arena cannot satisfy it.
76 */
77void *det_arena_persist_alloc(DetArena *a, size_t n);
78
79/**
80 * @brief Free a pointer previously returned by det_arena_persist_alloc().
81 *
82 * Coalesces with adjacent free blocks; if the freed block sits at the top of the
83 * persistent region it returns that space to the free middle (shrinks the boundary).
84 * Passing NULL is a no-op.
85 */
86void det_arena_persist_free(DetArena *a, void *p);
87
88// --- scratch end (bump, bulk reset, grows down) -----------------------------
89
90/**
91 * @brief Bump-allocate @p n bytes of transient storage, aligned to @p align.
92 *
93 * @param align power-of-two alignment for the returned pointer; clamped to
94 * `[DET_ARENA_ALIGN, DET_ARENA_MAX_ALIGN]`.
95 * @return aligned pointer (NOT zeroed), or NULL if it would cross the persistent end.
96 */
97void *det_arena_scratch_alloc_aligned(DetArena *a, size_t n, size_t align);
98
99/** @brief Bump-allocate @p n transient bytes at the baseline alignment (DET_ARENA_ALIGN). */
100void *det_arena_scratch_alloc(DetArena *a, size_t n);
101
102/** @brief Capture the current scratch position (a savepoint for det_arena_scratch_release()). */
103size_t det_arena_scratch_mark(const DetArena *a);
104
105/** @brief Free every scratch allocation made since @p mark (a value from det_arena_scratch_mark()). */
106void det_arena_scratch_release(DetArena *a, size_t mark);
107
108/** @brief Free ALL scratch allocations in O(1). */
110
111// --- observability ----------------------------------------------------------
112
113/** @brief Free bytes in the middle (max a single new allocation could take, minus a header). */
114size_t det_arena_free_bytes(const DetArena *a);
115
116/** @brief Persistent payload bytes currently allocated. */
117size_t det_arena_persist_used(const DetArena *a);
118
119/** @brief Scratch bytes currently allocated. */
120size_t det_arena_scratch_used(const DetArena *a);
121
122// ===========================================================================
123// Multi-region extension: a DRAM base + an optional PSRAM extension.
124// ===========================================================================
125//
126// A ::DetArenaSet chains a few ::DetArena regions in preference order (add DRAM
127// first, PSRAM second). Allocations try each region in turn and take the first
128// that fits, so hot state stays in fast internal RAM and only the overflow
129// spills into external RAM. Frees are routed to the owning region by address.
130// This is how "arena extension" works: enable PSRAM by adding a second region;
131// leave it out and the set is just the single DRAM arena.
132
133/** @brief Max regions in a ::DetArenaSet (DRAM base + PSRAM extension). */
134#ifndef DET_ARENA_MAX_REGIONS
135#define DET_ARENA_MAX_REGIONS 2u
136#endif
137
138/** @brief A set of ::DetArena regions searched in insertion (preference) order. */
139typedef struct
140{
142 size_t count; ///< Regions in use.
144
145/** @brief A scratch savepoint across every region of a ::DetArenaSet. */
146typedef struct
147{
149 size_t count;
151
152/** @brief Initialize an empty set (no regions yet). */
154
155/**
156 * @brief Add a region `[base, base+size)`; regions are searched in the order added.
157 * @return true if added, false if the set is full or the region is too small.
158 */
159bool det_arena_set_add(DetArenaSet *s, void *base, size_t size);
160
161/** @brief Persistent alloc from the first region that fits (see det_arena_persist_alloc()). */
162void *det_arena_set_persist_alloc(DetArenaSet *s, size_t n);
163
164/** @brief Free a persistent pointer, routed to its owning region by address. */
166
167/** @brief Aligned scratch alloc from the first region that fits (see det_arena_scratch_alloc_aligned()). */
168void *det_arena_set_scratch_alloc_aligned(DetArenaSet *s, size_t n, size_t align);
169
170/** @brief Scratch alloc from the first region that fits (see det_arena_scratch_alloc()). */
171void *det_arena_set_scratch_alloc(DetArenaSet *s, size_t n);
172
173/** @brief Capture the scratch position of every region. */
175
176/** @brief Restore every region's scratch position to @p m (frees scratch made since). */
178
179/** @brief Reset scratch in every region. */
181
182/** @brief Total free middle bytes summed over all regions. */
184
185/** @brief Persistent payload bytes allocated, summed over all regions. */
187
188/** @brief Scratch bytes allocated, summed over all regions. */
190
191#endif // DETERMINISTICESPASYNCWEBSERVER_DET_ARENA_H
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
#define DET_ARENA_MAX_REGIONS
Max regions in a DetArenaSet (DRAM base + PSRAM extension).
Definition arena.h:135
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
A scratch savepoint across every region of a DetArenaSet.
Definition arena.h:147
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
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