Class CellReadResolver

Class Documentation

class CellReadResolver

Resolves one consumer cell’s operand fetches to table reads. Installed on a scratch cache for one consumer cell at a time (begin_consumer resets the per-operand read cursors); fetch is consulted by evaluate_impl ahead of every other probe.

Positional matching — the invariant that makes a cursor per operand value sufficient, and the reason a Read carries no leg number:

  1. the table emits a consumer’s Read entries in production-tree leg order (cell_table_builder.hpp walks CellTableInputs::operands_of, which is per-leg with repetition, left leg then right leg);

  2. the executor fetches a consumer’s legs in that same order (evaluate_impl requests the left operand, then the right);

  3. therefore the i-th surviving Read of one value in cursor_ is the i-th leg of that value, and popping the front matches legs to reads with no leg index anywhere. begin_consumer asserts (1) by checking each per-value cursor is ordered by table position;

  4. every table value in a consumer’s production tree is a direct leg. A non-leg intermediate node of that tree is a transient (not a value of the table) and fetch reports it as such; a table value reached from an intermediate node instead of a leg has no Read of its own and fetch throws rather than borrowing another leg’s read.

Public Functions

inline CellReadResolver(CellRegistry &reg, std::function<std::optional<std::size_t>(std::size_t)> vid_of_hash)
inline void begin_consumer(CellId consumer)

Resets the per-operand read cursors to every Read of consumer, in table order &#8212; i.e. in the consumer’s production-tree leg order, which is what makes the front of a per-value cursor the next leg to fetch (see the positional-matching invariant on this class).

inline CellId consumer() const
inline CellRegistry const &registry() const noexcept

The registry this resolver reads from &#8212; the storage the table owns. Read-only: a caller that wants to observe what a cell currently holds (a test probe, a diagnostic) goes through here; production is the executor’s business.

inline std::optional<ResultPtr> fetch(std::size_t operand_node_hash, BatchContext const &ctx)
Returns:

nullopt when operand_node_hash is not a value of the table (a transient of this production tree, evaluated in place by the caller), or when the matched Read’s source is a leaf cell with no current result yet (first touch: the caller must evaluate the leaf and call record_leaf; the cursor entry is left unconsumed so the same Read is served &#8212; and consumed &#8212; by a later fetch once the leaf is recorded). Any other matched Read

whose source has no current result throws naming the consumer cell, the source cell and the value (spec section 4: “missing entry or non-resident source: throw with both

ids”)

&#8212; a well-formed table guarantees a Build cell’s own source is always resident when read (registry lookups go by residency, see CellRegistry::cell_of, and persistent cross-call values are seeded into the registry at entry, see run_ordered_schedule_pre_results), so this is a genuine table/tree or recording gap, never deferred. Otherwise the sliced source: each (pos, key) of the matched Read’s slice is applied as slice_mode(pos, range.first, range.second) with range taken from the ctx entry whose level.key() equals key. Throws when the consumer has no remaining Read of that value at all (table/tree disagreement), or when a declared slice names a loop instance absent from ctx.

inline void record_leaf(std::size_t hash, ResultPtr r)

Records a leaf’s freshly evaluated result in the registry (called after the leaf evaluator runs on hash's first touch): a no-op if hash is not a value of the table or names no Leaf cell.

inline bool operand_drained(std::size_t operand_node_hash) const

Whether operand_node_hash's operand is currently safe for in-place accumulation &#8212; the registry-derived counterpart of the CacheManager::chain_holds_shared(f.left) check that eval.hpp's in-place gate applies on the forest path.

True in exactly two cases:

  • operand_node_hash is not a value of this table at all (a private transient of the current production tree, e.g. an intermediate running total of an accumulate-in-place Sum chain that was never promoted to its own cell) &#8212; no table cell could possibly be sharing it, exactly as chain_holds_shared() reports “not held” for the analogous untracked case; or

  • it is a table value, has been fetch()'d at least once, and its most recent fetch’s source cell (last_served_source_) is currently CellRegistry::drained &#8212; i.e. that read spent the source’s last declared life (never true for a persistent cell, which never drains) and the registry has already let go of its own reference (CellRegistry::read moves the value out on that read), so nothing this evaluation will read it again.

A table value never yet fetch()'d through this resolver (no entry in last_served_source_) is reported not drained &#8212; the safe default. The registry may well be holding that value for other readers (it is a table cell, so some cell owns it), and this resolver has no evidence either way; answering “drained” would license an in-place mutation of a buffer this evaluation is going to read again. Out-of-place costs one allocation; the wrong answer corrupts a shared value.

inline std::size_t served() const noexcept

Diagnostic: the number of operand fetches this resolver has actually served (table-driven, sliced-per-Read) rather than deferring to the caller (a transient, a leaf’s first touch, or a value this call’s runtime cache-halt gate skipped &#8212; see fetch's own doc comment).