Owning Iterator Payloads v2
September 10, 2026 ยท View on GitHub
Status: implemented private payload profile; HOSTED GREEN under the v0.4.0 release baseline. The broader iterator and public-library goals remain incomplete.
Audience: language, ownership, cleanup, backend, and standard-library contributors.
This profile extends Owning Iterators v1 to consuming
iteration of Vec<Bytes>. It defines the ownership boundary for Iter<Bytes>
and IterStep<Bytes>, and for for own traversal, without widening borrowed
vector access or the public ABI. The implementation selects Prelude
v8, CleanupPlan v13, and Graph v45. Each is selected only when the exact
retained Iter<Bytes>/IterStep<Bytes> types, including the local Done case
and signatures, are present; scalar Prelude v7, CleanupPlan v10-v12, and
earlier graphs remain unchanged. The dedicated Wasm owned-iterator imports
are specified below and exercised by the focused runtime corpus.
Payload transfer
vec_into_iter<Bytes> transfers the existing vector allocation into an owning
iterator. iter_next<Bytes> consumes one iterator generation and either
settles exhaustion or yields one Bytes item and one successor Iter<Bytes>.
The item and the rest move exactly once through a detached-prefix carrier. The
iterator authority owns the initialized range [cursor, len); the detached
prefix is transferred as the yielded Bytes and is never cleaned up by the
iterator. No vector operation accepts that carrier. No byte buffer is cloned,
and no iterator backing allocation is introduced.
Before the commit boundary, iter_next<Bytes> validates the cursor and live
Bytes range. It then atomically detaches the item and advances the successor.
Canonical Yield owner order is the item Bytes followed by the rest
Iter<Bytes>. The successor retains the remaining initialized payloads in
their original order. Existing vector validity, initialized-slot tracking, and
drop order remain authoritative: the iterator does not weaken or reinterpret
the original Vec validity facts. The initialized suffix remains iterator-owned
while the detached prefix is removed from that ownership window.
Exhaustion settles the vector store exactly once. Early iterator drop settles
only unyielded payloads; a yielded item is governed by its receiving owner.
Arguments stage left to right. Read, capacity, and contract failures select a
sticky status before publication and clean up every still-staged owner. A
failed call publishes no item or successor. for own uses this same consuming
protocol and preserves the same transfer and failure rules. Its item binding is
Own and its condition is Borrow; it does not admit a borrowed VecGet or
an implicit clone.
Closed boundaries and replay
vec_get<Bytes> remains closed because borrowed access cannot return an owning
payload. The additive payload path must preserve the existing scalar Vec slots,
validity facts, cleanup plans, and public descriptors. Exact source, HIR, graph,
ProgramRoot, native, and Core Wasm projections must replay the same selected
payload carrier and ownership transitions. Forged or stale carrier, type,
case, cleanup, or source bindings fail closed and confer no authority.
This profile adds no ambient allocator, host capability, transport, or public generic iterator ABI. Generic authored iterator implementations, lazy closure adapters and broader payload types remain separate scope. The scalar callback and closure profiles are implemented but do not widen this Bytes payload ABI.
Graph and cache binding
The additive profile selects the exact Graph v45 binding for retained
Iter<Bytes> and IterStep<Bytes> declarations and their local Done and
Yield signatures. Its graph binds Prelude v8, while CleanupPlan v13 records
the detached-prefix ownership and successor settlement order. The cache
codec round-trips that v13 cleanup plan; decoded plans remain untrusted until
independent HIR replay rejects stale or substituted bindings. Scalar Prelude
v7, CleanupPlan v10-v12, and earlier graph and cache bytes remain unchanged.
Focused local evidence
The original local corpus remains a historical witness; the implemented
release corpus is now hosted green. The executable iterator library filter,
owned_iterator workspace filter, and owned_iterator_payloads owned-data
filter cover empty and exhausted Vec<Bytes>, ordered yields,
multi-byte payloads, early drop, complete for own traversal, item and rest
failure, sticky cleanup, repeated exact settlement, and cross-layer replay on
the interpreter, native C11 -O0/-O2, and Core Wasm. Separate scalar-iterator
and owned-Vec preservation cases pass. Borrowed
VecGet<Bytes> remains rejected. Wasm tests additionally reject no-write
success, invalid step tags, and borrowed payload carriers before compiler
commit. Malformed-provider tests inspect the retained host state and perform
explicit host cleanup; they do not claim automatic settlement after a trap.
The existing Linux iterator selector includes these cases.
Core Wasm host boundary
Only this owned-iterator profile selects these imports from env:
spx_iter_bytes_into_v2(vec: i64, out_iter: i32) -> i32
spx_iter_bytes_next_v2(iter: i64, cursor: i64, out_step: i32) -> i32
spx_iter_bytes_drop_v2(iter: i64, cursor: i64) -> void
The 16-byte iterator frame contains a distinct opaque iterator handle followed
by a 64-bit cursor. The 32-byte step contains a 32-bit tag, a zero reserved
word, a Bytes carrier, the successor iterator handle, and successor cursor in
that order. into writes a cursor of zero. next writes a complete Yield
with tag 1 and the detached existing byte owner, or an all-zero Done after
settling the exhausted backing store. The compiler pre-fills the output frame
with 0xA5. Zero and 0xA5A5A5A5A5A5A5A5 are reserved handle values;
unchanged poison handles, an invalid tag, nonzero reserved word, or malformed
Done fields reject before compiler ownership commits. The host must write
the complete frame on success.
The status-returning imports retain existing Vec status codes: zero succeeds;
nonzero codes 1 through 3 commit neither output nor ownership. Complete handle,
window, payload, and output-range validation precedes mutation. The
(handle, cursor) pair is the authority and the old pair is invalidated
when next succeeds. The host may rotate the numeric handle or retain it with
the updated cursor, but the successor handle must be nonzero and nonpoison and
the successor cursor must be cursor + 1; no fresh backing allocation is
required. It retypes the vector's backing-store authority without adding a
second backing allocation, and its distinct iterator
registry prevents old Vec operations from accepting iterator handles. Drop
settles only the initialized suffix in index order, then the backing store.
Scalar iterator imports, layouts, and module bytes remain unchanged.