Class CellRegistry

Nested Relationships

Nested Types

Class Documentation

class CellRegistry

Runtime side of the cell table: the current result of each cell. This is the owner of those results: it tracks the live byte total across held slots, enforces fill-once (a non-persistent cell produced twice without an intervening clear is a duplicate producer – a bug, not a legitimate replay), and seeds/publishes persistent cells through a PersistentValueStore so they survive across top-level evaluation calls without depending on the scope caches’ own persistence. Bound cells are cleared at the start of every batch of a loop instance they are bound to (the per-batch scratch reset, expressed on cells).

Orientation convention, shared with the CacheManager scope caches: a cell holds the value in its node’s canonical orientation, and every reader applies that node’s own canon_phase once. A producer therefore stores apply_phase(node, result) – evaluate_impl hands back the oriented result, and the phase is an involution – matching CacheManager::store_and_access (

cache.store_and_access(node,

apply_phase(node, rb))

, readers apply_phase again, roots mult_by_phase). Storing the oriented value here instead would double-apply the phase at every read and flip the sign of every value whose canonicalization needed an odd permutation.

Public Functions

inline explicit CellRegistry(CellTable const &table, CellRegistryHooks hooks = {})
inline void seed_persistent()

Seeds every persistent cell whose canonical hash is currently held by hooks_.persistent with the store’s value. A no-op for a cell the store does not (yet) hold, and entirely a no-op when hooks_.persistent or hooks_.hash_of is unset. Intended to run once, right after construction, before the first read of this evaluation call (see run_ordered_schedule_pre_results).

inline void set(CellId c, ResultPtr v)

Production: overwrites the cell’s current result. Fill-once: a non-persistent cell already holding a value that was neither taken, released nor cleared since (filled_since_clear) is a duplicate producer – see CellRegistryHooks::strict_fill_once (throws a named Exception, defaulting to the SEQUANT_UT_STRICT_FILL_ONCE env gate; a plain SEQUANT_ASSERT otherwise). Persistent cells are excluded from this check, exactly as CacheManager::entry::store excludes its own persistent entries: they legitimately re-store across batch replays and repeated top-level evaluation calls (e.g. successive CC iterations), often with no table-declared clear between productions. A persistent cell is published to hooks_.persistent (when set) on every production, mirroring what it now holds.

inline ResultPtr peek(CellId c) const

The cell’s current result, or null if unset; never throws.

inline ResultPtr read(CellId c)

A read: the cell’s current result, which stays in the cell for its later readers. Throws if the cell has none.

inline ResultPtr take(CellId c)

The last read of the cell in its epoch (ReleasePlan::last_read, ReleasePlan::last_assemble): the reader takes the value over and the registry drops its own reference. Holding on would both pin the memory and make the buffer look shared to the reader that just took it – the same thing CacheManager::entry::access() does at the same point for the scope cache. A later production of the cell is a fresh one. A persistent cell is never given away: for it this is read. Throws if the cell has no result.

inline void release(CellId c) noexcept

Lets the cell’s result go: the schedule has no reader of it left in this epoch (ReleasePlan::after). A no-op for a persistent cell and for a cell holding nothing (its last reader took it, or it was not produced this epoch).

inline void clear_bound_to(LoopKey const &k)

Batch start: drops every cell bound to loop instance k – the per-batch scratch reset, expressed on cells instead of on a whole scope’s storage. A cell is bound to k either explicitly (detail::bound_instances: a carried sliced position or a partial_over reduction on k) or implicitly, for a non-persistent cell whose own declared home scope’s deepest loop instance is k: the executor re-runs whatever this cell’s tree position computes fresh every batch of that position’s innermost enclosing loop (a step’s own block for a Build; a block-close aggregation for an Assemble), independent of whether the value it holds happens to carry k as a mode or a reduced axis – a “whole” cell unsliced and unsummed on its own home loop is still re-run every batch of it; how long the result is then read for is the release plan’s business. A persistent cell is never cleared here (by definition it is bound to no loop instance – see TableCell::persistent – so neither check ever matches it; the explicit continue is a defensive redundant guard). A produce_if_absent

cell is the one exception to the implicit rule: it is precisely a cell whose home scope sits inside a loop it is not bound to, and the table says it is produced on first visit and reused on every later batch of that loop (the executor’s Build step skips its production while it is resident), so the implicit “its scope’s innermost

loop is \p k” clear would defeat the flag by dropping it at every batch boundary. Such a cell is cleared only on an instance it is genuinely bound to. Also resets the fill-once mark of every cell it clears: the boundary this crosses is exactly the one

set's fill-once check must not treat as a duplicate producer across.

inline std::size_t live_bytes() const
Returns:

the sum, over every slot currently holding a value (persistent and non-persistent alike), of the Result::size_in_bytes() it measured when stored. A value that grows while held (a pending view that materializes) is counted at its stored size.

template<typename F>
inline void for_each_live(F &&f) const

Invokes f(CellId, ResultPtr const&) for every slot currently holding a value – the registry-side source on_peak_liveset walks once storage moves fully onto the table.

inline bool holds(ResultPtr const &value) const
Returns:

whether some slot currently holds value (pointer identity). Read-only: spends no life.

inline bool drained(CellId c) const
Returns:

whether cell c is a non-persistent cell holding no value: taken by its last reader, released, cleared, or not produced yet. A persistent cell is never given away, so it is never reported as drained even when it happens to hold no value yet (e.g. before its first production this evaluation).

inline CellTable const &table() const
inline std::optional<CellId> build_cell_at(std::size_t vid, CellScope const &scope) const
inline std::optional<CellId> assemble_cell_at(std::size_t vid, CellScope const &scope) const
inline std::optional<CellId> leaf_cell(std::size_t vid) const
inline std::optional<CellId> cell_of(std::size_t vid, CellScope const &scope) const

The value’s form visible at scope, by residency (not exact scope equality) – the table’s own visibility contract, decided by the one shared rule detail::deepest_visible_form states (deepest resident form wins; at equal depth the earlier candidate does), which the table builder’s read-source selection uses too. Build cells are offered first, which is how a tie prefers a Build. Leaf cells are not candidates here (see leaf_cell): a leaf has no scope in this sense.