SWAR: SIMD Within A Register. Ordinary integer operations, used to process every byte of a word at once, branch-free.
The name and the technique are Randall J. Fisher and Henry G. Dietz, Compiling for SIMD Within a Register, LCPC'98, LNCS 1656:290-304, doi:10.1007/3-540-48319-5_19.
A uint64_t holds eight bytes. An AND, an XOR or a subtract on that word operates on all eight simultaneously, on any CPU, with no vector unit and no intrinsics. The whole technique is arranging for the answer you want to fall out of arithmetic that was going to happen anyway.
Three constants set it up, all derived from the carrier width and not tabulated, and so they are correct at 16, 32 and 64 bits without a #if:
ONES is the broadcast multiplier: multiply a byte by it and you get that byte in every lane. HIGH is where an answer lands. LOW7 is the room a lane has to carry into before it disturbs the answer bit.
lane.has_zero answers "does this word contain a zero byte" in five operations, and every predicate in the module is built on it.
Read it a lane at a time:
No branch, no loop, eight bytes answered. strlen over a long string becomes one of these per word plus a tail.
The test is Alan Mycroft's, posted to comp.arch on 1987-04-27. Sean Eron Anderson's Bit Twiddling Hacks and Henry S. Warren Jr.'s Hacker's Delight chapter 6 both carry it.
The mask table on top of it turns "some lane matched" into "which lane": lane.count says how many, lane.first and lane.last say where, mask.drop_first and mask.drop_last walk them in order, and mask.tail trims the last word to the caller's length. A find needs exactly this.
Called through the table:
Use word.load_al in a walk, not word.load. load takes any address, which it pays for: the type it moves carries EMBED_RAW, which is EMBED_ALIGN(1) and EMBED_ALIAS, and on a target with no unaligned word load the compiler assembles each one out of byte loads and shifts, twelve instructions on Xtensa, eleven on RISC-V, one on ARMv7-M. A walk steps to the first word boundary once and reads the body through load_al, which is a single instruction everywhere. That change alone took cellul.len from 3.788 cycles per byte to 3.055. load is for the address that genuinely is arbitrary, verifying a candidate mid-haystack, say.
Not a knob. A narrower carrier is never faster: a 16-bit load on a 32-bit machine moves the same cache line, occupies the same load port, and then answers for half the lanes.
There is no separate lane width to set. The scanner reads EMBED_WORD_BITS directly, and so nothing can desync the carrier from the word: there is no second number to disagree with the first.
What is a knob is EMBED_WORD_BITS itself. Set it and the carrier and the machine word move together, and that is how the word32 and word16 environments run a narrow machine's scan path on a 64-bit host. See Width, alignment and byte order.
There are no __builtin_ctz, __builtin_popcount or intrinsics anywhere in src/. That is a measured decision and not an aesthetic one:
Avoiding them also means there is no #ifdef ladder per compiler in the scan path, the second reason: every compiler conditional lives in one file (embed_compiler_directives.h), and the scanner is not allowed to add to it.
Measured on gcc 13.2 -O2, x86-64. cycles_per_byte is cycles_per_call / MMGR_SWAR_BYTES, the column the design claim lives in: a wider carrier answers for more bytes per register operation, and so cycles-per-byte should fall as the carrier widens.
| case | 16-bit | 32-bit | 64-bit | 16 → 64 |
|---|---|---|---|---|
| load | 4.70 | 2.03 | 0.89 | 5.3x |
| has_zero | 8.80 | 3.32 | 1.36 | 6.5x |
| le | 7.78 | 3.14 | 1.43 | 5.4x |
| spread | 7.74 | 2.89 | 1.33 | 5.8x |
| eq | 10.09 | 4.30 | 2.32 | 4.4x |
It does fall, roughly halving per doubling. eq sits about 11% above has_zero, the movzbl, imul and xor it adds. Nothing to explain.
Read these as a comparison between rows of one build, never against a number from another machine. What it costs explains how to run them and how to tell a result from noise.
Link-time optimization is essential, and it is worth being blunt about how much. These entries are generated in verbum_scrutor.c by GENERIC_ENTRY and called by name from every other module, and so no translation unit can inline them on its own. mmgr_memor_chr over 512 bytes measured 610 cycles with them as out-of-line calls and 187 when the linker was allowed to inline them. On an ESP32-S3, cellul.len measured 26.6 cycles/byte without LTO against 5.0 with it, worse than the byte-at-a-time ROM strnlen it is compared to. MMGR_LTO defaults to ON for that reason.