Class CellRegistry

Nested Relationships

Nested Types

Class Documentation

class CellRegistry

Runtime side of the cell table: the current result of each cell and its remaining life. This is the owner of those results (the explicit-value-cells design): 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, without spending life (the value just arrived from a prior top-level call; nothing has read it yet this call). 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 and restores its life from the table (a new batch’s/iteration’s production of a cell whose prior life was drained starts fresh). Fill-once: a non-persistent cell already holding a value it was not read past 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

Non-decrementing peek: the cell’s current result, or null if unset.

inline ResultPtr read(CellId c)

Decrementing read: throws if the cell has no current result, or (for a non-persistent cell) its life is already exhausted. The read that spends a non-persistent cell’s last life also drops the registry’s own reference (the cell has no reader left this evaluation, and 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 restores both value and life via set.

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

non-null) whether this read spent the cell’s last life.

In-place eligibility is re-derived from this table’s own life / persistent directly (see CellReadResolver::operand_drained, which queries drained() rather than this flag). The exhausted output tells a reader whether it took ownership of the buffer: the ordered executor’s Assemble step seeds its running sum with the first batch’s partial when this read exhausted the source, and with a Result::clone of it otherwise (a source with life left, or a persistent one, is still going to be read again from that very buffer, so accumulating into it would corrupt every later read).

inline ResultPtr read(CellId c, bool *exhausted)
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, only its declared life/persistence say how long the result is then read for. 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 void forgo(CellId c, std::size_t count)

Spends count of cell c's declared reads without taking the value: the accounting half of a read the executor decided not to perform. A consumer the runtime cache-halt skipped (nothing left this evaluation reads its result, or a produce_if_absent cell that is resident and so is not re-produced) never calls read, so without this its source’s life would never reach zero — the source would stay resident to the end of the evaluation (pinning the memory) and keep looking shared to every later reader, which is exactly what disables in-place accumulation.

Releases the value when the life reaches zero, exactly as the draining read does (bytes accounted, fill-once mark cleared so a later production of the cell is a fresh one). A no-op for a persistent cell, which never drains, and for count == 0. Throws when count exceeds the remaining life: forgoing more reads than the table declared is an accounting bug in the caller, and silently saturating would release a value another consumer is still owed (see remaining_life, which the ordered executor consults for the one case where a skipped consumer’s visits legitimately outnumber one production’s budget).

inline std::size_t remaining_life(CellId c) const
Returns:

cell c's remaining declared life (0 for a spent cell; a persistent cell’s life is never spent, so this is informational there).

inline std::size_t live_bytes() const
Returns:

the sum of Result::size_in_bytes() over every slot currently holding a value (persistent and non-persistent alike).

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

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

inline bool drained(CellId c) const
Returns:

whether cell c is a spent non-persistent cell: no value held and no life left to spend. A persistent cell is never drained, 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) &#8212; 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.