MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
mmgr.h
Go to the documentation of this file.
1/* MMgr - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2 * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Commercial OR LicenseRef-Educational
3 *
4 * Every use falls under AGPL-3.0-or-later unless you hold explicit permission, which is either a
5 * negotiated commercial licensing contract or an educator's license issued to you personally.
6 */
7/**
8 * @file mmgr.h
9 * @brief The embedded_types headers every module builds on.
10 * @author dstroy0 (Douglas Quigg) <dquigg123@gmail.com>
11 * @date 2026-08-30
12 *
13 * @note Every module header includes this one. The widths, the word, the static assertion, the
14 * attribute wrappers, the dispatch layout assertions and the entry point macros all come from
15 * embedded_types, and nothing here declares an MMgr copy of any of them.
16 * @note Only the two gated module headers below are included from here. A module includes what it
17 * needs itself, and an umbrella could not also be the header each module includes.
18 * @note mmgr_string_shim.h is separate. Including it changes what the <string.h> names mean, which
19 * is a decision a consumer makes for itself.
20 */
21#ifndef MMGR_H
22#define MMGR_H
23
24#include "embed_compiler_directives.h"
25#include "embed_dispatch_layout.h"
26#include "embed_types.h"
27
28/**
29 * @brief The DMA module, reached only when MMGR_ENABLE_DMA is set.
30 *
31 * @note memoriam_praetereo.h guards its own contents on MMGR_ENABLE_DMA as well.
32 */
33#if MMGR_ENABLE_DMA
35#endif
36
37/**
38 * @brief The external memory module, reached only when MMGR_ENABLE_EXTRAM is set.
39 *
40 * @note memoria_externa.h guards its own contents on MMGR_ENABLE_EXTRAM as well.
41 */
42#if MMGR_ENABLE_EXTRAM
44#endif
45
46/**
47 * @brief Alignment a caller declares its own storage at before handing those bytes to a module.
48 *
49 * @note Nothing under src reads it. The tests, the benches and the region-edges example are what
50 * write EMBED_ALIGN(MMGR_ALIGN_BYTES) on their arrays. The default is 16.
51 * @note Has to be a power of two, which the build checks below. It reaches an alignment specifier,
52 * and one given anything else is ill-formed. The check is here to name the knob, where the
53 * diagnostic would otherwise point at whichever array was declared with it.
54 * @warning Not the alignment a locus_carcerum cell comes back at. That is MMGR_CARCER_ALIGN, which is
55 * sizeof(embed_word), and nothing asserts the two agree.
56 */
57#ifndef MMGR_ALIGN_BYTES
58
59#define MMGR_ALIGN_BYTES 16u
60#endif
61#if (MMGR_ALIGN_BYTES < 1) || ((MMGR_ALIGN_BYTES & (MMGR_ALIGN_BYTES - 1)) != 0)
62#error "MMGR_ALIGN_BYTES must be a power of two - an alignment specifier takes nothing else"
63#endif
64
65/**
66 * @brief Attribute that places a pool in external memory.
67 *
68 * @note Named here and filled by the port. Which section a part puts external memory in is the
69 * part's business, and so this library names the knob and supplies no value for it. A build
70 * defines it ahead of this header, or on the command line.
71 * @note Read only by ParsMemoriaeExternum, which is declared only where MMGR_ENABLE_EXTRAM is set.
72 * @warning Empty by default, and an external pool then lands wherever the linker puts an ordinary
73 * one. Nothing diagnoses that. A build enabling external memory has to supply this.
74 */
75#ifndef MMGR_EXTRAM_ATTR
76#define MMGR_EXTRAM_ATTR
77#endif
78
79/**
80 * @brief Marks a declaration so that a reference to it fails the build, carrying a message.
81 *
82 * @param[in] msg_ Text the diagnostic reports, as a string literal.
83 * @note Reported at the site that referenced the declaration, and so the message names what a caller did.
84 * The linker can only say what it found. This is the reason to reach for it over a token: the
85 * linker names a symbol, this names the mistake.
86 * @warning The diagnostic arrives only where a reference survives to the end of compilation, and so what
87 * this marks has to be something nothing legitimately calls.
88 * @warning Expands to nothing where EMBED_HAS_ATTRIBUTE(error) is 0. A reference then compiles and
89 * the misuse goes unreported.
90 */
91#if EMBED_HAS_ATTRIBUTE(error)
92#define MMGR_ERROR_ATTR(msg_) __attribute__((error(msg_)))
93#else
94#define MMGR_ERROR_ATTR(msg_)
95#endif
96
97/**
98 * @brief States which argument of an allocation entry gives the extent of what it returns.
99 *
100 * @param[in] arg_ One-based position of the byte count in the entry's parameter list.
101 * @note Feeds the compiler's object-size analysis, as lets a write past the cell a caller
102 * was given be reported at the line that wrote it. Without it the returned pointer carries the
103 * extent of the pool it came out of, and a cell overrun reads as a legal access.
104 * @note Costs nothing. Measured byte-identical with and without, both for a direct call and for one
105 * through a dispatch table.
106 * @warning The bound survives only where the call is not folded away. An entry reached through its
107 * dispatch table keeps it; the same entry inlined at a direct call collapses to an offset
108 * into the pool and the cell-level extent is gone.
109 * @warning Expands to nothing where EMBED_HAS_ATTRIBUTE(alloc_size) is 0, ignoring arg_. The
110 * diagnostics it would have raised are not raised, and nothing reports that.
111 */
112#if EMBED_HAS_ATTRIBUTE(alloc_size)
113#define MMGR_ALLOC_SIZE(arg_) __attribute__((alloc_size(arg_)))
114#else
115#define MMGR_ALLOC_SIZE(arg_)
116#endif
117
118/**
119 * @brief Fails the link when a pool name is declared in a second translation unit.
120 *
121 * @param[in] name_ Pool being declared.
122 * @note An initialized object with external linkage, and so two units declaring one pool name give the
123 * linker two strong definitions of this symbol and it refuses them by name. The enumerator
124 * guard cannot see across a unit boundary; this is what does.
125 * @note The pool's own array stays static, and so it is this token and not the bytes that collide. That
126 * keeps the storage private to its unit while the name stays answerable across all of them.
127 * @warning Costs one object per pool, in .bss or .data depending on the toolchain. Fixed and small,
128 * but not nothing on a part where pools are the point.
129 */
130#define MMGR_PARS_TOKEN(name_) int mmgr_pars_token_##name_ = 0
131
132/**
133 * @brief Fails the build when a pool name is declared a second time.
134 *
135 * @param[in] name_ Pool being declared.
136 * @note The enumerator is the guard. A second declaration of one name collides on it, and the
137 * compiler prints the identifier, and so the identifier reads as the reason and names no
138 * mechanism.
139 * @note Two pools of one name would sit at unique addresses and the language would take them. The
140 * name is all a consumer is handed. A name meaning internal memory in one
141 * place and external in another leaves the placement out of reach of the line that uses it,
142 * and those two differ in what a DMA engine can address and in settling time.
143 * @warning One translation unit is as far as this guard reaches. The enumerator is settled while a
144 * unit is compiled. A pool of the same name declared in a separate unit does not collide
145 * on it. Catching that one is the linker's to do, over a symbol it can see.
146 */
147#define MMGR_PARS_DECLARED_ONCE(name_) \
148 enum \
149 { \
150 name_##_declared_twice_but_a_pool_symbol_names_one_region_only = 0 \
151 }
152
153/**
154 * @brief Fails the build when a pool is dressed a second time.
155 *
156 * @param[in] name_ Pool being claimed.
157 * @note Emitted by whatever lays state over a pool - a cellblock, a ring, anything that writes its
158 * own records into those bytes. It sits here and in none of them, because the rule
159 * is the pool's and not a dresser's, and a copy in each would let the next dresser be written
160 * without one.
161 * @note Keyed on the pool alone, with no dresser's name in it. Every other symbol a site emits
162 * carries the site's name, and so two sites over one pool collide only there and each would
163 * lay its own records over the same bytes while holding that it owned them.
164 * @note Costs nothing. An enumerator emits no storage and is settled while the unit compiles.
165 * @warning Says a pool is dressed once. It says nothing about the bytes being reached directly,
166 * which is a separate question with separate guards.
167 */
168#define MMGR_PARS_CLAIMED_ONCE(name_) \
169 enum \
170 { \
171 name_##_claimed_twice_but_a_pool_is_dressed_once = 0 \
172 }
173
174/**
175 * @brief Declares a pool of bytes in internal memory.
176 *
177 * @param[in] name_ Name the pool is reached by, which is also the array.
178 * @param[in] bytes_ Bytes in it.
179 * @note A pool is a block of bytes, alone. Hand it to LocusCarcerum to have it dressed as
180 * a cellblock, to mmgr_anular_init to have it dressed as a ring, or to a memor entry to work
181 * on it as the bytes it is. A pool nobody hands anywhere is a pool that exists and is unclaimed.
182 * @note The count is carried as name_##_bytes as well as laid down as the array. A consumer has
183 * the extent without deriving it. The assertion is what compares the two, and it has content
184 * only because the two are produced separately.
185 * @note Aligned to MMGR_ALIGN_BYTES, and that leaves that knob the contract it is documented as.
186 * No caller writes it out.
187 */
188#define ParsMemoriaeInternae(name_, bytes_) \
189 MMGR_PARS_DECLARED_ONCE(name_); \
190 MMGR_PARS_TOKEN(name_); \
191 enum \
192 { \
193 name_##_bytes = (bytes_) \
194 }; \
195 EMBED_ALIGN(MMGR_ALIGN_BYTES) static uint8_t mmgr_pars_storage_##name_[bytes_]; \
196 static const uint8_t *const name_ EMBED_UNUSED = mmgr_pars_storage_##name_; \
197 EMBED_STATIC_ASSERT(sizeof(mmgr_pars_storage_##name_) == name_##_bytes, #name_ " is not the size it was declared")
198
199/**
200 * @brief Declares a pool whose bytes the system gives at run time: its name, with no storage and no size.
201 *
202 * @param[in] name_ Name the pool is reached by.
203 * @note The program asks the system for the bytes once, at the size it finds it needs, and hands them to the
204 * dresser. MemoriaAnularis over this pool gives a ring whose address the linker settles, whose state
205 * names the bytes wherever the system put them.
206 * @note For bytes that must come from the system: a graphics device placing a buffer over them opens the
207 * allocation they are, which an array in the program's image is not.
208 */
209#define ParsMemoriaeHospes(name_) \
210 MMGR_PARS_DECLARED_ONCE(name_); \
211 MMGR_PARS_TOKEN(name_)
212
213/**
214 * @brief Declares a pool of bytes in external memory.
215 *
216 * @param[in] name_ Name the pool is reached by, which is also the array.
217 * @param[in] bytes_ Bytes in it.
218 * @note The same declaration as ParsMemoriaeInternae, carrying MMGR_EXTRAM_ATTR. Which memory a pool
219 * sits in is settled by which of the two a caller writes, and nothing afterwards inspects an
220 * address to find out.
221 * @warning Declared only where MMGR_ENABLE_EXTRAM is set. A build without external memory fails
222 * on the name, where it would otherwise quietly place the bytes internally.
223 */
224#if MMGR_ENABLE_EXTRAM
225#define ParsMemoriaeExternum(name_, bytes_) \
226 MMGR_PARS_DECLARED_ONCE(name_); \
227 MMGR_PARS_TOKEN(name_); \
228 enum \
229 { \
230 name_##_bytes = (bytes_) \
231 }; \
232 EMBED_ALIGN(MMGR_ALIGN_BYTES) static uint8_t mmgr_pars_storage_##name_[bytes_] MMGR_EXTRAM_ATTR; \
233 static const uint8_t *const name_ EMBED_UNUSED = mmgr_pars_storage_##name_; \
234 EMBED_STATIC_ASSERT(sizeof(mmgr_pars_storage_##name_) == name_##_bytes, #name_ " is not the size it was declared")
235#endif
236
237/**
238 * @brief Runtime assertion hook, inert here.
239 *
240 * @param[in] cond_ Condition the caller expects to hold.
241 * @param[in] msg_ String literal describing the expectation.
242 * @note An expectation the library asserts is one a correct caller cannot break, and so the form here
243 * pays nothing for it. It expands to a sizeof, which type checks cond_ and never evaluates it.
244 * @note A build wanting a failed expectation to report and stop defines its own ahead of this
245 * header, and the guard below stands down. test/support/mmgr_host_traps.h does this
246 * for the checks environment, and it is where the stdio and stdlib that reporting needs live.
247 * @warning Not a runtime check and cannot be read as one. A failed expectation produces no
248 * diagnostic and no trap in this form.
249 * @warning cond_ must carry no side effect, since this form never evaluates it.
250 */
251#ifndef MMGR_ASSERT
252#define MMGR_ASSERT(cond_, msg_) ((void)sizeof((cond_) ? 1 : 0), (void)0)
253#endif
254
255/**
256 * @brief Stops the program on an illegal call, in every build.
257 *
258 * @param[in] msg_ String literal naming what was violated.
259 * @note Separate from MMGR_ASSERT because the two answer different questions. MMGR_ASSERT states an
260 * expectation a correct caller cannot break, and it is inert in the build that ships. This one
261 * marks a call that is illegal to make, and so it has to hold in that build too.
262 * @note Reached where continuing would be worse than stopping. A release handed a prisoner from
263 * another cellblock is the case it exists for: the caller has shown it does not know which
264 * cellblock owns that memory, and so its next release and its next allocation are both suspect,
265 * and on a maximum security cellblock a quiet return would leave bytes unzeroed that the
266 * caller believes were wiped.
267 * @note Does not return. Every call site is written on that basis and carries no fallback after it.
268 * @note The stop is unconditional and the diagnostic is not. The form here halts and names nothing,
269 * as keeps this header free of libc.
270 * @warning A build may define its own before including this header, and the form below is then not
271 * used. A target reporting a fault through its own handler wants that, and so does a test
272 * harness, which would otherwise hang on this form and never report a failure.
273 * @warning This form spins. A target without abort has no other way to stop, and a halt is what a
274 * debugger can catch.
275 */
276#ifndef MMGR_FATAL
277// No libc here, and so the trap is a halt. The cast to void discards msg_, which nothing reads on this
278// arm and would otherwise be an unused argument
279#define MMGR_FATAL(msg_) \
280 do \
281 { \
282 (void)(msg_); \
283 for (;;) \
284 { \
285 } \
286 } while (0)
287#endif
288
289#endif
Placement between internal and external memory, and a two-buffer index.
DMA channels: the completion event, the port hooks, the praet dispatch table, and the schedule and de...