MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
cellularum_laboro.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 cellularum_laboro.h
9 * @brief Bounded string work: the three argument types, the calls, and the cellul dispatch table.
10 * @author dstroy0 (Douglas Quigg) <dquigg123@gmail.com>
11 * @date 2026-08-29
12 *
13 * @note The calls taking CatenaFinitaCfg are bounded by a cap the caller states, and so no walk runs
14 * past it even where the bytes carry no terminator. That bound makes these usable
15 * where a libc string call is not, since a missing terminator stops a walk and not
16 * running it off the end.
17 * @note ws and digit are the exception. Each reads the single byte at args->at and never consults
18 * cap, because a one byte test has nothing to run past.
19 * @note The TransfiguroCfg conversions carry no bound at all. Each reads until the first byte that is
20 * not part of the number, and so the caller owes them such a byte inside readable storage. A
21 * terminator is the usual one.
22 * @note The VerboProgrediorCfg pair take their operands by value and read no memory, and so
23 * neither carries a bound.
24 * @warning Nothing here allocates or hands back ownership. Every pointer in and out is the caller's,
25 * and the addresses find and chr return point into the src they were given.
26 */
27#ifndef MMGR_CELLULARUM_LABORO_H
28#define MMGR_CELLULARUM_LABORO_H
29
31
32#include "mmgr.h"
33
34EMBED_BEGIN_DECLS
35
36/**
37 * @brief Most needle offsets the search sieve tests per candidate word.
38 *
39 * @note A ceiling and not a count. cellul_pick_rows takes the smallest of this, the needle length
40 * and MMGR_SWAR_BYTES. A short needle is sieved on fewer offsets than this allows.
41 * @note cellul_find_core sizes two arrays by it, the chosen offsets and their broadcasts, and reads
42 * element 0 of each outside the loop that walks the rest.
43 * @warning Taken only when MMGR_SIEVE_ROWS is not already defined. A build may supply its own.
44 * @warning A build's own value must be at least 1. At 0 both arrays are declared zero length and the
45 * search still reads element 0 of each, and nothing here asserts against it.
46 */
47#ifndef MMGR_SIEVE_ROWS
48
49#define MMGR_SIEVE_ROWS 1u
50#endif
51
52/**
53 * @brief Longest haystack a one or two byte needle is settled over by a mask chain and not by
54 * building a sieve.
55 *
56 * @note Case sensitive searches only. The chain compares its broadcasts without folding them. A
57 * folded search takes the sieve at every length, whatever this is set to.
58 * @note Defaults to no limit, which folds the test away: `read_cap <= SIZE_MAX` is true for every
59 * size_t. A default build emits no comparison and no second path is chosen at run time.
60 * @note The chain settles every start position in a word at once, with no anchor to choose and
61 * nothing to verify afterwards. A two byte needle costs it a second read a step and a one byte
62 * needle none. The sieve picks its offsets out of the cost table before a haystack byte is
63 * read, reads a word per offset on top of the one it tests for the terminator, and proves
64 * every surviving lane in full. Measured with a two byte needle, cycles for the whole call:
65 *
66 * n 8 64 2048
67 * Xtensa chain 124 489 13391
68 * Xtensa sieve 187 607 15494
69 * RISC-V chain 124 488 13393
70 * RISC-V sieve 219 680 17059
71 *
72 * The chain wins at every length on both parts, and so the default is no limit. The knob
73 * is kept because that is a measurement instead of a proof, and a part or a workload that
74 * disagrees should be able to say so without editing the walk.
75 * @warning Taken only when MMGR_FIND_CHAIN_MAX is not already defined. A build may supply its own.
76 * Zero sends every needle through the sieve.
77 */
78#ifndef MMGR_FIND_CHAIN_MAX
79
80#define MMGR_FIND_CHAIN_MAX SIZE_MAX
81#endif
82
83/**
84 * @brief Arguments for the string calls, where each call reads only the members it needs.
85 *
86 * @note Members left unset are zero, and the calls that ignore them never read them.
87 */
88typedef struct
89{
90 const char *const src; /**< Bytes to read [BORROWS]. */
91 const size_t cap; /**< Bytes readable from src, and for copy the bytes writable at dst. */
92 const char *const other; /**< Second operand for diff, eq, starts, find and has [BORROWS]. */
93 const size_t other_cap; /**< Bytes readable from other. */
94 const size_t other_len; /**< Needle length find and has take when non-zero, and not measuring. */
95 char *const dst; /**< Destination for copy [BORROWS]. */
96 const size_t at; /**< Offset into src for len, ws and digit. */
97 const uint8_t byte; /**< Byte sought by chr. */
98 const embed_bool ci; /**< Fold case in diff, eq, starts, find and has. */
100
101/**
102 * @brief Arguments for the single-step compares used to drive a walk.
103 *
104 * @note step_word reads word_left and word_right. step_byte reads byte_left and byte_right.
105 * @note Both operands come by value, and so neither call reads memory and neither needs a bound.
106 */
107typedef struct
108{
109 const embed_word word_left; /**< First word for step_word. */
110 const embed_word word_right; /**< Second word for step_word. */
111 const uint8_t byte_left; /**< First byte for step_byte. */
112 const uint8_t byte_right; /**< Second byte for step_byte. */
113 const embed_bool ci; /**< Fold case before comparing. */
114 const embed_bool end_wins; /**< A terminator in the same lane counts as a match. */
116
117/**
118 * @brief Arguments for the conversions from text to number.
119 *
120 * @note Every one of them reads src, and sets end when it is given.
121 */
122typedef struct
123{
124 const char *const src; /**< Text to convert [BORROWS]. */
125 const char **const end; /**< Optional target set past the number, or back to src when none was read [BORROWS]. */
127
128/**
129 * @brief Type of the cellul dispatch table.
130 *
131 * @note EMBED_TABLE_LAYOUT asserts the seventeen members sit at consecutive EMBED_FUNCTION_POINTER_BYTES offsets, with
132 * nothing else.
133 * @note Byte and wire verbs are not here. rd_str and mpint_fixed read a length off the wire instead
134 * of out of a string, and so they belong to the byteio module and act on its spans.
135 */
136typedef struct
137{
138 CatenaFinitaCfg (*init)(const CatenaFinitaCfg *args); /**< Copies the argument struct. */
139 size_t (*len)(const CatenaFinitaCfg *args); /**< Bytes before the terminator. */
140 size_t (*diff)(const CatenaFinitaCfg *args); /**< Offset of the first differing byte. */
141 embed_bool (*eq)(const CatenaFinitaCfg *args); /**< Whether both end together with no difference. */
142 embed_bool (*starts)(const CatenaFinitaCfg *args); /**< Whether src begins with other. */
143 const char *(*find)(const CatenaFinitaCfg *args); /**< First occurrence of other in src. */
144 embed_bool (*has)(const CatenaFinitaCfg *args); /**< Whether find would report a match. */
145 const char *(*chr)(const CatenaFinitaCfg *args); /**< First occurrence of byte in src. */
146 size_t (*copy)(const CatenaFinitaCfg *args); /**< Bounded copy, terminated unless cap is 0. */
147 embed_bool (*ws)(const CatenaFinitaCfg *args); /**< Whether src[at] is whitespace. */
148 embed_bool (*digit)(const CatenaFinitaCfg *args); /**< Whether src[at] is a decimal digit. */
149 embed_iword (*step_word)(const VerboProgrediorCfg *args); /**< One word compare driving a walk. */
150 embed_iword (*step_byte)(const VerboProgrediorCfg *args); /**< One byte compare driving a walk. */
151 embed_iword (*to_long)(const TransfiguroCfg *args); /**< Text to signed integer. */
152 embed_word (*to_ulong)(const TransfiguroCfg *args); /**< Text to unsigned integer. */
153 double (*to_double)(const TransfiguroCfg *args); /**< Text to double. */
154 float (*to_float)(const TransfiguroCfg *args); /**< Text to float. */
156EMBED_TABLE_LAYOUT(CellularumLaboroNs, init, len, diff, eq, starts, find, has, chr, copy, ws, digit, step_word,
157 step_byte, to_long, to_ulong, to_double, to_float);
158
159/**
160 * @brief Returns a copy of the argument struct.
161 *
162 * @param[in] args Struct to copy [BORROWS].
163 * @return A copy of *args.
164 * @note Copies the members only. Nothing they point at is read.
165 */
167
168/**
169 * @brief Returns the bytes in src before its terminator, starting at args->at.
170 *
171 * @param[in] args Bytes src, the extent cap, and the start offset at [BORROWS].
172 * @return Bytes before the terminator, at most cap minus at.
173 * @note Returns cap minus at when no terminator is found in range.
174 * @warning args->at must not exceed args->cap, and src must be readable to args->cap. The last partial
175 * word is loaded whole and masked after, and so up to MMGR_SWAR_BYTES - 1 bytes past cap are read.
176 */
178
179/**
180 * @brief Returns the offset of the first byte where src and other differ.
181 *
182 * @param[in] args Bytes src and other, the extent cap, and ci [BORROWS].
183 * @return Offset of the first difference, or cap when the two agree throughout.
184 * @note A terminator does not end the scan. cap is the only bound.
185 * @warning Both src and other must be readable for cap bytes. The last partial word is loaded whole and
186 * masked after, and up to MMGR_SWAR_BYTES - 1 bytes past cap are read from each.
187 */
189
190/**
191 * @brief Reports whether src and other hold the same terminated string.
192 *
193 * @param[in] args Bytes src and other, the extent cap, and ci [BORROWS].
194 * @return EMBED_TRUE when both reach a terminator with no difference before it.
195 * @warning Both src and other must be readable for cap bytes. The last partial word is loaded whole and
196 * masked after, and up to MMGR_SWAR_BYTES - 1 bytes past cap are read from each.
197 */
198embed_bool mmgr_cellul_eq(const CatenaFinitaCfg *args);
199
200/**
201 * @brief Reports whether src begins with other.
202 *
203 * @param[in] args Bytes src and other, the extent cap, and ci [BORROWS].
204 * @return EMBED_TRUE when other reaches its terminator with no difference before it.
205 * @note An empty other matches any src.
206 * @warning Both src and other must be readable for cap bytes. The last partial word is loaded whole and
207 * masked after, and up to MMGR_SWAR_BYTES - 1 bytes past cap are read from each.
208 */
209embed_bool mmgr_cellul_starts(const CatenaFinitaCfg *args);
210
211/**
212 * @brief Finds the first occurrence of other within src.
213 *
214 * @param[in] args Haystack src with cap, needle other with other_cap, and ci [BORROWS].
215 * @return Address inside src, or NULL when there is no match [BORROWS].
216 * @note An empty needle returns src. A needle longer than cap returns NULL.
217 * @warning src must be readable for cap bytes and other for other_cap bytes. The needle is loaded a
218 * whole word at a time and masked after, and so up to MMGR_SWAR_BYTES - 1 bytes past other_cap
219 * are read. The haystack walk holds itself inside cap.
220 */
221const char *mmgr_cellul_find(const CatenaFinitaCfg *args);
222
223/**
224 * @brief Reports whether other occurs within src.
225 *
226 * @param[in] args Haystack src with cap, needle other with other_cap, and ci [BORROWS].
227 * @return EMBED_TRUE when mmgr_cellul_find reports a match.
228 * @warning src must be readable for cap bytes and other for other_cap bytes, with the needle read past
229 * other_cap exactly as mmgr_cellul_find describes.
230 */
231embed_bool mmgr_cellul_has(const CatenaFinitaCfg *args);
232
233/**
234 * @brief Finds the first occurrence of args->byte in src, before the terminator.
235 *
236 * @param[in] args Bytes src, the extent cap, and the byte sought [BORROWS].
237 * @return Address inside src, or NULL when the byte does not occur [BORROWS].
238 * @note A byte of 0 returns the terminator's own address, or src plus cap when no terminator is in range.
239 * @warning src must be readable for cap bytes. The last partial word is loaded whole and masked after,
240 * and up to MMGR_SWAR_BYTES - 1 bytes past cap are read.
241 */
242const char *mmgr_cellul_chr(const CatenaFinitaCfg *args);
243
244/**
245 * @brief Copies src into dst, writing at most cap bytes including the terminator.
246 *
247 * @param[in,out] args Source src, destination dst, and the destination extent cap [BORROWS].
248 * @return Bytes copied and not counting the terminator.
249 * @note A cap of 0 writes nothing at all and not even a terminator.
250 * @warning dst must be writable for cap bytes and src readable for cap minus one.
251 */
253
254/**
255 * @brief Tests src[at] for whitespace.
256 *
257 * @param[in] args Bytes src and the offset at [BORROWS].
258 * @return EMBED_TRUE for space, tab, newline, carriage return, form feed or vertical tab.
259 * @warning src[at] must be readable. cap does not bound this call.
260 */
261embed_bool mmgr_cellul_ws(const CatenaFinitaCfg *args);
262
263/**
264 * @brief Tests src[at] for a decimal digit.
265 *
266 * @param[in] args Bytes src and the offset at [BORROWS].
267 * @return EMBED_TRUE for the ten decimal digits, '0' through '9'.
268 * @warning src[at] must be readable. cap does not bound this call.
269 */
270embed_bool mmgr_cellul_digit(const CatenaFinitaCfg *args);
271
272/**
273 * @brief Compares one word pair and reports whether a walk should continue.
274 *
275 * @param[in] args Words word_left and word_right, with ci and end_wins [BORROWS].
276 * @return MMGR_SWAR_GO to continue, MMGR_SWAR_YES on agreement, MMGR_SWAR_NO on difference.
277 * @note MMGR_SWAR_YES means word_left's terminator arrived before the first difference.
278 * @note end_wins makes a terminator in the same lane as the difference count as agreement.
279 */
281
282/**
283 * @brief Compares one byte pair and reports whether a walk should continue.
284 *
285 * @param[in] args Bytes byte_left and byte_right, with ci and end_wins [BORROWS].
286 * @return MMGR_SWAR_GO to continue, MMGR_SWAR_YES on agreement, MMGR_SWAR_NO on difference.
287 * @note A terminating byte_left gives MMGR_SWAR_YES when byte_right also terminates, or when
288 * end_wins is set.
289 */
291
292/**
293 * @brief Reads a signed decimal integer from args->src.
294 *
295 * @param[in,out] args Text src and the optional end target [BORROWS].
296 * @return The value, negated when a minus sign was read.
297 * @note Skips leading whitespace, then accepts one optional '+' or '-'.
298 * @note When end is not NULL it is set past the last digit, or back to src when none was read.
299 * @warning The read stops at the first byte that is not part of the number. No length bounds it.
300 * @warning The digit accumulator is embed_word wide and wraps on a longer run.
301 */
302embed_iword mmgr_cellul_to_long(const TransfiguroCfg *args);
303
304/**
305 * @brief Reads an unsigned decimal integer from args->src.
306 *
307 * @param[in,out] args Text src and the optional end target [BORROWS].
308 * @return The accumulated value.
309 * @note Skips leading whitespace, then accepts one optional '+'. A '-' stops the read.
310 * @note When end is not NULL it is set past the last digit, or back to src when none was read.
311 * @warning The read stops at the first byte that is not part of the number. No length bounds it.
312 * @warning The digit accumulator is embed_word wide and wraps on a longer run.
313 */
314embed_word mmgr_cellul_to_ulong(const TransfiguroCfg *args);
315
316/**
317 * @brief Reads a decimal floating point number from args->src.
318 *
319 * @param[in,out] args Text src and the optional end target [BORROWS].
320 * @return The assembled value.
321 * @note Accepts whitespace, one optional sign, digits, one optional point, then an optional exponent.
322 * @note An exponent is read only when at least one digit was seen before it.
323 * @note When end is not NULL it is set past the number, or back to src when no digit was read.
324 * @warning The read stops at the first byte that is not part of the number. No length bounds it.
325 */
327
328/**
329 * @brief Reads a decimal floating point number from args->src and narrows it to float.
330 *
331 * @param[in,out] args Text src and the optional end target [BORROWS].
332 * @return The value from mmgr_cellul_to_double, narrowed to float.
333 * @note Accepts the same input as mmgr_cellul_to_double, with only the result width differing.
334 * @warning The read stops at the first byte that is not part of the number. No length bounds it.
335 */
337
338/**
339 * @brief Dispatch table instance named cellul, with each member set to its mmgr_cellul_ function.
340 */
341EMBED_TABLE_STORAGE CellularumLaboroNs cellul EMBED_UNUSED = {
342 .init = mmgr_cellul_init,
343 .len = mmgr_cellul_len,
344 .diff = mmgr_cellul_diff,
345 .eq = mmgr_cellul_eq,
346 .starts = mmgr_cellul_starts,
347 .find = mmgr_cellul_find,
348 .has = mmgr_cellul_has,
349 .chr = mmgr_cellul_chr,
350 .copy = mmgr_cellul_copy,
351 .ws = mmgr_cellul_ws,
352 .digit = mmgr_cellul_digit,
353 .step_word = mmgr_cellul_step_word,
354 .step_byte = mmgr_cellul_step_byte,
355 .to_long = mmgr_cellul_to_long,
356 .to_ulong = mmgr_cellul_to_ulong,
357 .to_double = mmgr_cellul_to_double,
358 .to_float = mmgr_cellul_to_float,
359};
360
361EMBED_END_DECLS
362
363#endif
embed_bool mmgr_cellul_eq(const CatenaFinitaCfg *args)
Reports whether src and other hold the same terminated string.
size_t mmgr_cellul_len(const CatenaFinitaCfg *args)
Returns the bytes in src before its terminator, starting at args->at.
double mmgr_cellul_to_double(const TransfiguroCfg *args)
Reads a decimal floating point number from args->src.
embed_word mmgr_cellul_to_ulong(const TransfiguroCfg *args)
Reads an unsigned decimal integer from args->src.
embed_iword mmgr_cellul_to_long(const TransfiguroCfg *args)
Reads a signed decimal integer from args->src.
embed_bool mmgr_cellul_has(const CatenaFinitaCfg *args)
Reports whether other occurs within src.
embed_bool mmgr_cellul_digit(const CatenaFinitaCfg *args)
Tests src[at] for a decimal digit.
const char * mmgr_cellul_chr(const CatenaFinitaCfg *args)
Finds the first occurrence of args->byte in src, before the terminator.
embed_iword mmgr_cellul_step_word(const VerboProgrediorCfg *args)
Compares one word pair and reports whether a walk should continue.
size_t mmgr_cellul_copy(const CatenaFinitaCfg *args)
Copies src into dst, writing at most cap bytes including the terminator.
CatenaFinitaCfg mmgr_cellul_init(const CatenaFinitaCfg *args)
Returns a copy of the argument struct.
embed_iword mmgr_cellul_step_byte(const VerboProgrediorCfg *args)
Compares one byte pair and reports whether a walk should continue.
embed_bool mmgr_cellul_ws(const CatenaFinitaCfg *args)
Tests src[at] for whitespace.
float mmgr_cellul_to_float(const TransfiguroCfg *args)
Reads a decimal floating point number from args->src and narrows it to float.
embed_bool mmgr_cellul_starts(const CatenaFinitaCfg *args)
Reports whether src begins with other.
static const CellularumLaboroNs cellul
Dispatch table instance named cellul, with each member set to its mmgr_cellul_ function.
const char * mmgr_cellul_find(const CatenaFinitaCfg *args)
Finds the first occurrence of other within src.
size_t mmgr_cellul_diff(const CatenaFinitaCfg *args)
Returns the offset of the first byte where src and other differ.
The embedded_types headers every module builds on.
Arguments for the string calls, where each call reads only the members it needs.
const size_t other_len
const uint8_t byte
const embed_bool ci
const char *const other
const size_t other_cap
const char *const src
Type of the cellul dispatch table.
embed_iword(*) step_word(const VerboProgrediorCfg *args)
embed_bool(*) digit(const CatenaFinitaCfg *args)
CatenaFinitaCfg(*) init(const CatenaFinitaCfg *args)
embed_bool(*) starts(const CatenaFinitaCfg *args)
double(*) to_double(const TransfiguroCfg *args)
embed_iword(*) step_byte(const VerboProgrediorCfg *args)
embed_bool(*) has(const CatenaFinitaCfg *args)
size_t(*) diff(const CatenaFinitaCfg *args)
embed_bool(*) ws(const CatenaFinitaCfg *args)
embed_iword(*) to_long(const TransfiguroCfg *args)
size_t(*) len(const CatenaFinitaCfg *args)
float(*) to_float(const TransfiguroCfg *args)
embed_bool(*) eq(const CatenaFinitaCfg *args)
size_t(*) copy(const CatenaFinitaCfg *args)
embed_word(*) to_ulong(const TransfiguroCfg *args)
Arguments for the conversions from text to number.
const char *const src
const char **const end
Arguments for the single-step compares used to drive a walk.
const embed_bool ci
const embed_word word_left
const embed_word word_right
const embed_bool end_wins
SWAR byte tests, the lane constants, the arguments, and the lane, mask and word dispatch tables.