Single-producer, single-consumer. A byte ring, a segment view over the same bytes, and loculus keepouts.
It moves what Locus carcerum: the region handed out. The segment view passes whole buffers by index, and nothing is copied across the boundary.
A byte ring. available, vacant, put, read, read_byte, peek, consume. Capacity must be a power of two: the wrap is a mask instead of a modulo.
A segment view. seg_next, seg_publish, seg_front, seg_release, seg_at, seg_inflight. The producer fills a segment and publishes its index; the consumer reads that index and releases it when done. The bytes never move.
Loculi. loculus_ready, loculus_next, loculus_hold, loculus_keepout, loculus_drop, loculus_mark. A numbered hold that records a region to keep out of, for a reader walking bytes in place. Their free and pinned masks are one machine word each, and so MMGR_RING_LOCULI_MAX is EMBED_WORD_BITS. MMGR_RING_LOCULI is the build knob under that ceiling, and a static assert names it if it is set higher; a build with no use for them sets it to 0 and gets the keepout storage back.
The caller declares the ring and supplies the bytes. Nothing of the state is reachable from outside:
MemoriaAnularis declares the ring's storage and claims the pool. The claim stops those same bytes being dressed a second time, as a cellblock, or as another ring, which would put two sets of records over one region with nothing at run time to report it.
.segment_bytes gives the segments their size where the capacity divided by the count is not the size wanted. The capacity can then be any size that holds them, since a segment's place is its index times its size and the segment counters wrap by the count, which stays a power of two. Such a ring has no byte stream: vacant reads 0, put is refused and read takes nothing.
Filling and draining:
Passing whole segments by index instead, and so nothing is copied across the boundary:
seg_next and seg_front answer whether there is one and write the index through .out_index, and therefore a caller never needs a sentinel index to mean "none".
SPSC only. Two producers on one ring is a broken program and not a slow one. There is no check and no assert: exactly one producer advances head and exactly one consumer advances tail. For that reason alone acquire and release ordering is enough and no entry on those two needs a read-modify-write.
put is all or nothing. It checks the whole span against vacant first. A partial write never happens and a half span is never visible. It answers EMBED_FALSE instead of writing what fits.
peek copies what you ask for regardless of whether it arrived. Read available first. A count above the ring's capacity is clamped there, since one lap is all two runs can express.
Nothing is ever zeroed. A loculus keeps its bytes after a drop. A restream can run again over the same region. Bytes read are not scrubbed behind the cursor either.
Capacity must be a power of two. The index wrap is a mask. nsegs likewise, and at most cap.
The buffer is aligned before the ring gets it. The ring does not check and has no way to.
loculus_next is a SWAR popcount fold and not a builtin. Same reasoning as the rest of the library, see Doing eight bytes at a time.
Self-contained. It reaches nothing outside config: the span mover, the bit index and the span type it records are all defined in its own .c, which is also where <stdatomic.h> stays.
Generated reference · Who owns what for what a pointer's lifetime is
Decides where a buffer should live when there is more than one kind of memory.
A part with both internal SRAM and external PSRAM, where the trade is real: internal is fast and scarce, external is large and slower, and some of it cannot be reached by DMA.
Every entry takes one argument pack, as the rest of the library does, and so the six figures are named and not positional:
The two-buffer index is separate, and acts on a PingPong the caller owns:
dma_required is not advisory. On parts where the DMA engine cannot address external memory, passing EMBED_TRUE stops the answer being PLACE_PSRAM.
PLACE_FAIL is a real answer, not an error code. Handle it.
Three access strategies. Not one thing under three names.
One dispatch table, proxim (src/proximus_operor/proximus_operor.h:218). The strategy is in the entry name and not in a table of its own, and the may-alias part is in the type every entry moves and not in a third entry to pick.
| entry | strategy | use when |
|---|---|---|
| load, put | unaligned | the address may be anything |
| al_load, al_put | aligned | you know the alignment holds |
| mmgr_migro_word | may alias | the type the above move. A load cannot be reordered against a store of another type |
Merging them is a miscompile the compiler cannot report.
An aligned load emitted for an address that is not aligned faults on some machines and silently returns a wrong value on others. A load without the may-alias marking lets the optimizer reorder it against a store it genuinely conflicts with, and the reordering is legal, because you told the compiler the pointers could not alias.
This is also why tools/dev_env/names.tsv keeps three infixes for one module. Collapsing them onto one stem merged seven symbol pairs, including the aligned and the unaligned load.
aequus does not check. It is a promise you are making and not a request to verify.
Unaligned is not always slower. On x86-64 proxim.u32 is one mov. The distinction matters most on parts where it is not.