Goose

Root structs: state with independent storage

Status: root structs v1 and the slot_pool/slice_pool modules are implemented. reusable and reusable[] remain available but are deprecated in favor of the library pools. Owners have independent local/global components, borrowed owner references, per-component lifetime checks and known-global codegen. Whole-owner copying, nesting, serialization and automatic worker transfer remain deferred. The migration guide describes the remaining compatibility differences; the benchmark report records current measurements and performance follow-ups.

Motivation

Structs bundle related state. An ordinary Goose struct is a single contiguous piece of memory and can contain at most one resizable value, on its tail. Root structs lift that storage limitation while restricting where the owner may be declared. Several arrays can grow independently, and fixed fields can live beside them, all accessed through ordinary field syntax. Libraries are an important use: users can instantiate complete library state in one declaration instead of supplying separate arrays and a borrowed context.

Root structs group independently stored values under one owning lifetime. They do not put several growing arrays into one contiguous byte image, relocate fields on growth, or introduce a heap allocator. Every simultaneously live resizable component is still on top of its own data stack.

root struct ParserState {
    tokens: Token[>..] = [],
    work: i64[>..<] = [],
    position: i64 = 0
}

var parser = ParserState { .. };
parse(parser, input);

fn parse(state: ParserState&, input: u8[:]) {
    // Ordinary field access and UFCS, with independent array storage.
}

The illustration assumes a separately declared Token type. Root structs are useful for parser state, parallel arrays, lookup structures with separate metadata, and pools. They remain worthwhile if reusable pools stay built in.

Syntax and explicit contract

Use root struct, retaining struct fields, type arguments, defaults, namespaces, and function-based methods. Store the distinction on the existing struct declaration/instantiation rather than creating a separate family of field-access AST nodes.

Treat root as a contextual modifier immediately before struct at a type declaration. Existing variables, fields, parameters, and functions named root remain legal. Do not reserve the word throughout the language merely to parse this modifier.

The modifier means that owning instances are storage roots. It does not mean that the declaration or instance must be at file scope. An explicitly declared root struct keeps its restrictions even when it has zero or one resizable field, including in generic specializations. Ordinary structs keep their current placement and layout rules. Adding a second resizable field to an ordinary struct remains an error, and the error suggests root struct.

If the root modifier were inferred instead, editing a field or specializing a generic would change a type's placement, copying, and representation contract. A keyword that replaced struct would obscure how much of the existing struct behavior remains. The name local would misdescribe globals, and stack would distinguish little in Goose.

For comparison, Rust infers the restrictions on a dynamically sized struct from an unsized last field, and such a struct still describes one object. C# explicitly marks its restricted ref struct category. Neither has the same rules as this proposal. References: Rust dynamically sized types, C# ref structs.

V1 ownership and placement

Use V1 rule
Local or global var/let Allowed; construct directly at its declaration using a literal and field defaults.
Fields Ordinary fields and multiple independently stored resizable fields, in any order. Their own existing element, construction, and lifetime restrictions still apply.
Root owner inside another struct Forbidden by value, including inside another root struct.
Root owner as an array/slice element or ADT payload Forbidden, including fixed-length arrays.
Reference to an owner Allowed, including optional references and references stored in fields/arrays when ordinary lifetime rules permit.
Function argument By reference. Untyped parameters bind owning lvalues by reference as for nonfixed values.
Function result References to existing owners may be returned under normal lifetime rules; owning results are deferred. Never return a reference to a local owner.
Unannotated binding from an existing owner Binds by reference, consistent with nonfixed lvalues. Explicit .= remains available.
copy, whole-owner assignment, by-value parameters Forbidden. No implicit descriptor copying or ownership transfer.
Temporary owners Forbidden; owning construction requires a named local/global declaration.
Field assignment and array operations Use the field's usual rules, including shrink and construction checks.
Whole-owner raw serialization or C ABI Unsupported; explicitly supported component operations remain available.
Transfer into a worker Unsupported, including implicit capture of a root global. See the threading rules below.

The prohibition on ownership nesting does not prohibit ordinary fields whose types are existing resizable-tailed structs. Such a field is an independently stored component with its existing internal layout. A root struct itself is never such a by-value component in v1. Variable-size ordinary fields likewise retain their construction rules; the enclosing root has no contiguous image.

struct Context { parser: ParserState& }   // Borrowing is allowed.
struct Object { parser: ParserState }     // Owning containment is an error.

Diagnose the prohibited use with the declaration and field/element/argument path, and suggest a reference or a local/global owner. For example:

ParserState is a root struct and cannot be stored by value in Object.parser.
Declare it as a local/global variable, or store ParserState&.

Enforce the rules through aliases, generic instantiation, inferred types, defaults, inferred results, and unreachable/standalone checking as applicable. An apparently fixed-size control record must never escape the owning rules. const and writability follow existing struct and reference rules, including their existing treatment of reference-valued fields; add no blanket transitive immutability rule.

Storage and construction

An instance has a stable control record for ordinary fields and component metadata. Each resizable component has its own stable element region, length header, and access to its data stack. Use existing representations for ordinary components where possible. The control record is an implementation detail, not a fixed-size Goose value that can be copied or serialized.

The number of component stacks is statically determined by the instantiated type and owning declarations. Lifetime-disjoint owners can reuse stack indices as existing locals do. Globals use dedicated component storage. Restore all owned stack watermarks on scope exit, including break, continue, ordinary returns, and return from. Partial construction must also restore acquired storage without publishing an incomplete owner.

Evaluate field initializers once, in declaration order. Preserve checks against observing under-construction storage. A completed owner can subsequently be initialized further by functions taking its reference. Do not introduce destructors, automatic resource cleanup, runtime owner counts, or moves.

A root owner holding data stacks is subject to the existing recursive-cycle restrictions even though its metadata has constant size. It cannot be live across a recursive-cycle call that would require another set of stacks for each activation. Passing an owner declared outside the cycle by reference remains the intended pattern. Capturing such a reference in a nested function or a compile-time function value follows existing capture and escape rules.

Component identity and lifetime analysis

An enclosing variable's lifetime is not sufficient to identify its storage. Represent independently stored components with identities equivalent to an owning declaration plus a field path:

(state, .tokens)
(state, .work)

These share an enclosing lifetime but are different regions. Preserve this distinction through field projection, direct and optional owner references, branches, rebinding, calls, returned references, globals, captures, and stored references. An unknown reference carries a conservative set of possible regions; never promote a lifetime bound to an exact component identity.

Implementation: a synthetic variable represents each component. Class roots can carry the same projection without claiming a concrete owner. A separate projected proof records that a view stays inside that field's region, allowing the checker to exclude sibling fields even after an owner reference was loaded from a holder. Ordinary references read out of the field lose this proof; self-relative links retain it. Joins intersect the proof, weakening drops it, and return/effect summaries preserve it. This is distinct from exact identity, which operations such as index_of still require.

In particular:

Relevant code includes RootAlt/Roots in src/ast.h, RootedAtReceiver/OneArrayOf in src/typecheck_types.h, and SoleResizableIn, growth logs and shrink summaries in src/typecheck_builtins.h. Root-set joins merge entries by variable identity, with separate synthetic identities for independent components; domain tests cover the additional proof's join/equality laws. Existing packed structs still share one region: a field path is not automatically a new region everywhere.

During investigation, small typecheck probes against the available compiler showed that a scalar allocator with separate data and freelist arguments accepts a retained reference across reuse. When those arguments were grouped in a struct of references instead, the checker conservatively assumed that the result could point into the freelist and rejected the next allocation. This is evidence of a current precision limit, not a test of root structs. Include this pattern, and two same-typed owned arrays, in the implementation's acceptance cases.

Code generation and performance

Array access must remain efficient. Grouping arrays in a root struct must not disable existing array optimizations or add an unexplained load on every iteration of a hot loop.

The current backend already has relevant machinery:

A stable control-record reference is a reasonable v1 representation, but field projections must expose their normal header/stack information to those passes. Track every stack a root argument or captured root can reach. Flush and reload cached metadata when a call may mutate it, without assuming two parameters are distinct owners. Rebinding, globals, callbacks and nonlocal returns require the same care as existing references. Preserve observable left-to-right sampling.

Inspect generated C as well as timings. Compare direct array arguments, an ordinary one-tail struct, a root with two arrays, a borrowed context, and the existing reusable pool. Cover reads, growth, freelist churn, same-typed fields, out-of-line calls, aliasing parameters, callbacks, and branches selecting owners. Check that base/length reads are hoisted where valid and that unrelated component mutations do not force unnecessary reloads or lose bounds-check elimination. Exercise O0/O2 and TinyCC/native C; include Windows x64 and other CI targets for ABI-sensitive changes. Record reservation count and code size as well as runtime.

Do not promise zero overhead based only on an inlined benchmark. Keep existing reusable until a migration has demonstrated acceptable behavior and performance. For initial root support, report material regressions explicitly and fix basic integration with existing optimizations before calling the feature complete.

Two call lowerings improve on passing the descriptor's address:

  1. Known-owner specialization. Where all incoming calls prove exactly one global owner, accesses use that owner's fields and component caches. See the implemented scope. Preserving local-owner identity across emitted call boundaries remains future work. A unique type is not proof of a unique live instance: repeated declarations, recursion, worker instances, aliases and reentrancy must be considered.
  2. Field-by-field lowering. Pass the headers, stack handles and component addresses a descriptor holds as separate arguments, which is the pool calling convention. Writes still go through those addresses, so owner identity is preserved. A parameter whose reference is stored, returned or captured keeps the descriptor. See field-by-field root parameters.

Keep the v1 language contract independent of these ABI strategies so that further optimizations do not require user code changes.

Initial native measurement

python bench/root_structs.py --exe <goose.exe> --n 32000000 --reps 9 compares push_ref.goose with push_root.goose: the same RNG, item writes, retained references, checksums, and two owner instances. The harness marks both C fill helpers GS_NOINLINE, preserving the call boundary at Goose O0 and O2 while compiling the C with native optimization. It saves timings, generated C, executable sizes, and stack reports. Times include process startup.

Windows x64, 2026-10-16, median of nine runs after one warmup:

Native compiler Goose level Separate array arguments Root argument
MSVC 19.51 O0 88.14 ms 88.88 ms
clang-cl 22.1 O0 85.57 ms 85.64 ms
MSVC 19.51 O2 86.78 ms 87.18 ms
clang-cl 22.1 O2 86.48 ms 85.37 ms

Both use five data-stack regions (including rendering) and produce identical checksums and executable sizes per compiler: 148,480 bytes with MSVC and 155,648 with clang-cl. The root version adds 536 bytes of generated standalone C. This probe shows no material regression for this workload; it is not a claim about all containers or platforms. Freelist-policy comparisons remain part of the separate reusable migration experiment.

root_owner_caches.goose and the baseline runner also check generated C: top and length caches for both independent fields, base/length hoisting for indexed reads, and runtime correctness when owner/owner, owner/array, or owner/global arguments alias. Such aliases intentionally keep the shared memory top.

Serialization, workers, and other boundaries

V1 has no root-level to_bytes, bytes_of, from_bytes, raw equality/copying, or public foreign value layout. A root owner has no single contiguous image, even when every component is individually flat. Do not accidentally accept it through existing flatness or fixed-size predicates.

Serializing or viewing an eligible component, such as pool.items, remains an ordinary operation under the existing image-safety and lifetime rules. Such an image contains the items, not the root's other fields or freelist. Loading it into an existing component obeys ordinary assignment/shrink rules. Libraries that need a coordinated snapshot can define an explicit data format later.

There are existing reusable-to-worker cases: test/threads/thread_globals.goose copies both a reusable slot pool and a reusable slice pool from the spawning instance, preserving freelist state, and tests a worker spawning another worker. Current codegen explicitly copies the freelist beside the data. Preserve that behavior for existing reusable.

Root v1 rejects passing an owning root to thread_spawn or a queue, and sending a reference to an owning root. It also rejects any direct or transitive use of a root global by a worker, since that use would require implicitly copying the owner. Diagnose the reachable global/call path. A root local constructed within a worker needs no transfer and is allowed. An unrelated root global unused by the worker must not reject the program. Existing rules still govern passing eligible components as copied data; never transfer descriptor pointers or infer transferability from flat contents.

Worker snapshot support can follow later by reconstructing independent storage for every component, copying the relevant metadata, and handling only reference forms for which transfer is already legal. This is an explicit compatibility gap to resolve before claiming that stdlib pools replace every reusable use.

Formatting and reflection must distinguish inspecting a reference from copying or serializing its owner. Retain field access and UFCS; support inspection by reference where it does not request a forbidden value operation. Do not add array operator overloading merely to disguise a container's items field.

Separate follow-up: reusable pools in the stdlib

import slot_pool; provides a fixed-size slot allocator:

root struct slot_pool<T> {
    items: T[>..] = [],
    free_slots: i64[>..<] = []
}

var pool = slot_pool<i64> { .. };
let r .= pool.alloc_ref(42);
pool.free(pool.items.index_of(r));

The implementation uses the same LIFO free-index policy as scalar reusable, with allocation, reference-returning allocation and checked free. Tests compare allocation traces, retained references and slot contents against the builtin. The API reference describes the supported element types and current boundaries; the benchmark report compares equivalent native workloads. The existing reusable implementation remains available unchanged.

stdlib/slice_pool.goose packages its items, node array and tree state in one root owner. Its three tree operations are externs (stdlib/slice_pool_internal.goose) that hand nodes and tree to a copy of the builtin's runtime span tree: first-fit placement, coalescing and tail reuse, with overlap-safe relocation in Goose. The copy checks what it reads from those fields, which the program can write, and aborts on a node number or count that does not fit. Differential tests compare positions, retained views and every backing element under allocation, free and resize traces. See its API and benchmark report. Both pool types require fixed-size elements. Worker snapshots remain a compatibility gap; global component paths and destination-aware slot construction are implemented.

Preserve the existing safety contract: all published elements remain valid typed values, a free merely marks storage reusable, and a stale reference may observe a replacement value. Double frees are logical errors unless a library adds detection. Freeing does not promise decommit, reservation release, or destruction. Slice relocation does not retarget existing views; fragmentation, high-water storage and copying costs remain.

Expose items explicitly for indexing, slicing and ordinary array algorithms. Its length is the high-water length, not a live-slot count. Live-only iteration requires additional metadata or work. Coordinated reset must update both data and metadata and satisfy the data array's normal shrink rules. Goose currently lacks field privacy: internal-field conventions and checked indexing preserve memory safety, but arbitrary user edits may violate pool invariants. Privacy is a separate possible improvement, not a prerequisite for root structs.

The migration requires the following capabilities; completed ones are noted:

Global component paths

Implemented for mutable direct array fields of global var root owners: Node&<u32 in nodes.items>, u32 in nodes.items, and u32 upto nodes.items. Owner names may be namespace qualified. The target must be a grow-only array; aliases, reference crossings, indices and nested field paths are rejected. in nodes still means exactly that global, with no implicit .items selection.

The component identity is reserved before types are instantiated, so recursive declarations such as Node linking into slot_pool<Node>.items keep the same identity throughout checking. The owner's field resolves to that existing component when its type becomes known. Owner-reference specializations retain named component identity through helper calls. Width checks, exact-root rules, index/bound type identity and the index array's no-shrink rule remain in force. Bounds analysis identifies a named component with the corresponding field expression; codegen uses its existing global header and cached base directly.

Types can use array bases and lengths without an identifier expression. These dependencies participate in call-cache synchronization and worker checks. An implicit dependency on a root component is rejected in workers, just like an explicit use of its owner. Image verification flushes cached counts before checking serialized indices against global array lengths.

Validation includes namespace/alias round-trips, cyclic node/pool declarations, global field initializers, owner-reference forwarding, compact-width overflow, index bounds and image validation, cross-field/owner rejections, no-shrink checks through owner and array parameters, and implicit worker dependencies. The full native baseline passes at O0/O2 with TinyCC and runtime-debug coverage; 24 focused fixtures also pass with compiler assertions enabled. Optional native modules and sanitizer configurations were not run for this extension.

Destination-aware library construction

alloc_with<T, F>(pool: slot_pool<T>&) -> T& is implemented using ordinary library control flow: remove a free index and assign F() into that slot, or return pool.items.push(F()). It calls the zero-argument builder once, after selecting the destination, and accepts fresh fixed-size aggregates with self-relative links or pool-relative self. Use items.index_of on the returned reference when an index is needed.

The general compiler prerequisites landed on master separately; see result construction. Fresh results are distinct from copies, receiving roots flow through calls independently of argument slots, and fixed relative aggregates construct through destination pointers at every optimization level. Named functions, trailing blocks, forwarding, recursion and nonlocal returns share these rules. A block still infers its own result type; an explicit reference-result function can supply a reference-valued element. alloc_ref(value) and alloc_index(value) retain their ordinary argument evaluation and copy requirements.

No default element or uninitialized published slot is needed. Appending publishes the new length only after construction finishes. A nonlocal exit leaves that length unchanged. Reuse removes the free index before running the builder and does not roll back that removal or initializer side effects on an early exit. A slot freed by the builder stays available for the next allocation. Invalid recycled indices fail bounds checks before the builder. Existing construction, lifetime, root and copy checks remain in force.

The builder fixtures cover fresh and recycled slots, neighbour links, self in both relative forms, forwarding, explicit reference results, non-defaultable elements, nonlocal exits, and freelist side effects. The publication fixture mirrors test/storage/alloc_half_built.goose and produces the same output. Rejection fixtures cover copies, wrong roots on cached calls, short-lived local targets and conflicting item growth.

Known root identity across function calls

Codegen preserves a root parameter's identity when every incoming call names the same global owner. This is independent of in pool.items types: ordinary root structs and pools of ordinary values qualify too. After Goose optimization, an analysis follows direct global references, immutable uncaptured aliases and parameter forwarding. Recursive edges participate in the same fixed point; no body is emitted until all its callers agree. Conflicting or unknown inputs retain the generic descriptor path. The analysis neither changes typechecking nor creates a specialization per owner.

In a qualifying helper, the parameter aliases the global's existing component locations. Multiple parameters naming that owner share its top/length caches, including with direct global accesses in the body. The global effects propagate through callers so nested calls and builder callbacks flush/reload the relevant caches. A separately supplied array reference can alias an owner component and still takes the conservative uncached path. Return-destination caching also continues to account for the helper's access to global storage.

The call interface and argument evaluation stay intact. All index, lifetime, construction and shrink checks remain in force. In particular, knowing the owner does not make the contents of its public freelist trustworthy. No singleton assumption about a root type is needed: several instances are valid, and a helper shared by different owners retains its descriptors.

This extension specializes accesses to proven global owners. Local owners still benefit from existing Goose inlining/alias projection, and from the field-by-field interface below; preserving their identity across an emitted function boundary would need additional specialization. Mutable, captured, stored, optional and otherwise opaque root references are conservative, as are implicit formatting and tag-dispatch entry points. The restriction on Goose inlining into recursive functions remains unchanged.

Field-by-field root parameters

A root reference parameter that its function only projects, aliases or forwards to another such parameter is passed as its descriptor's members: each resizable component's header and stack, as a fat reference parameter is (ParamFields), and each other component's address. The body rebuilds the descriptor in a local that its projections read. An owner's descriptor never changes once the owner is made, so the copy stands for it while the call runs. A parameter that its function returns, stores, captures, hands to a builtin or to an entry point with a fixed interface (exports, workers, formatting and tag dispatch), or forwards to a parameter that keeps its descriptor, keeps the descriptor's address (FindSplitRootParams in src/codegen_frames.h). A parameter proven to name one global owner keeps the one pointer, which its body does not read. test/codegen/root_split_params.goose covers both kinds.

Against the descriptor-pointer interface, on the slot and slice pool microbenchmarks at Goose O1/O2 (October 2026), this is a few percent faster on slot_pool under TinyCC and within run-to-run noise elsewhere. The remaining distance to reusable was not the calling interface. Under TinyCC, the slot pool paid for a pop's check right after its own length test, for temporaries and for struct copies, which general codegen changes have since removed (slot_pool.md); the index into items from the public freelist keeps its check. The slice pool's span tree is checked Goose code, where reusable[] calls the runtime's C (gs_spans_*). The JIT compiles that runtime C natively, so the gap there is about 2.5x on the fragmented and resize workloads.

Validation covers recursive forwarding, a recursive helper used by multiple globals, independent local pools, aliasing owner parameters, separately passed components, nested-call cache synchronization, self-relative builders, freelist mutation inside a builder and corrupt indices failing before builder effects. The full Windows baseline passes with native O0/O2, TinyCC, runtime-debug, compiler assertions in focused fixtures, bootstrap and C-shape checks. See the application measurements and microbenchmarks for performance and its limits.

Checked slice position

Builtin free_slice/realloc_slice can check alignment and complete containment at runtime when provenance cannot prove their input slice belongs to the pool. index_of currently requires stronger static provenance. Expose a general checked slice-to-array position operation, now implemented as range_index: elide checks when proven, otherwise check alignment and the full range at runtime. Every empty slice, including a foreign or default slice, returns zero. Nonempty foreign storage aborts. No library pointer arithmetic or unchecked index fabrication is needed.

Preserve all-build checks for invalid frees, negative/overflowing allocation lengths and element bounds, with validation before metadata changes where the current contract requires it. Slice allocations initialize every added element; relocating slices retain the restriction on self-relative element types. Expose any needed length validation as a general array capability rather than copying runtime constants into the library. This is implemented as check_length, which validates a proposed total element count without changing storage or promising that a later allocation will succeed. slice_pool validates requested counts and input slices before changing tree metadata. Allocation and free support defaultable self-relative elements; resize still requires copying, as it does for the builtin.

Policy and compatibility

Library variants may add occupancy checks, generations, different span policies, or logical freelist counts over grow-only metadata arrays. Generational handles are an optional API and cannot protect escaped raw references. See slotmap's versioned keys for a useful comparison, not a replacement for Goose's existing reuse semantics.

Deprecate reusable and reusable[] in documentation and recommend the library types for new code. Keep the syntax, diagnostics and runtime behavior available for existing code. Global component index/relative types, destination-aware slot construction and worker-local library pools are implemented. Removal is a later change, after worker transfer has a reviewed migration path. Continue measuring application workloads as well as allocation-heavy microbenchmarks; small regressions alone do not require retaining a language-level allocator.

V2 candidates

Implementation sequence and acceptance

  1. Frontend foundation. Parse and round-trip the contextual modifier; carry it on struct metadata through generic instantiation and AST cloning. Add syntax coverage and precise errors. During incremental development, reject semantic uses explicitly until their storage support exists: do not allow a root to fall through ordinary fixed/resizable codegen.
  2. Placement, storage classification and provenance. Introduce an explicit owning-layout distinction rather than treating the fixed metadata as a fixed Goose value. Audit size/flatness/copy/containment queries. Implement component identities and their join, specialization and effect-summary behavior.
  3. Ownership and codegen. Allocate and restore all component stacks; support literals/defaults, locals/globals, references, field projection, aliases, captures and calls. Integrate top caches, call reachability and view hoisting. Reject unsupported transfers, copies, nested owners and temporaries before codegen. Update spec, reference, implementation notes and editor support when the feature is usable, without advertising scaffolding as working support.
  4. Library-state acceptance. Demonstrate one-declaration multi-array state used by generic functions, with references retained across unrelated field operations. Test actual runtime behavior and generated code, not just parsing. This milestone does not depend on a reusable replacement.
  5. Pool migration experiment. Port copyable-element slot allocation, compare code and timings, then address the separate construction/path/slice features above and evaluate slice pools and existing worker behavior.

Required focused tests include:

Exercise aliases that refer to the same owner as well as distinct owners.

Run the appropriate focused tests during each implementation slice, then the repository's baseline compiler/runtime suite for the complete feature. Extend test/compiler_roots.cpp when changing root-set laws. Keep performance probes repeatable and compare equivalent algorithms; do not conflate a different freelist policy with root-reference overhead.

V1 validation record

After rebasing onto master cce94b91, the Windows x64 baseline passed on 2026-10-27 using an assertion-enabled Debug compiler, MSVC and the hosted TinyCC JIT: 1,406 parse/round-trip/typecheck checks, 671 native program checks, 191 runtime-debug checks and 826 JIT runs. The compiler root-domain tests, generated-C cache/hoisting assertions, bootstrap, formatter, language server, error fixtures, samples and runnable reference examples also passed.

Root alias, recursive capture, pool equivalence and allocator model fixtures also run at O1. Root/defer integration runs at O0/O1/O2 and with runtime checks, covering holder aliases, both pool types, early returns, loop exits, recursive unwinding and nonlocal returns. Formatter coverage includes root declarations and named component paths. All 40 VS Code extension tests passed with the rebased compiler, with no skips.

The baseline includes native worker-local ownership and transitive worker-global rejection; TinyCC does not run worker programs. Optional audio/gfx/physics/UI modules were not built. Linux/macOS and sanitizer CI were not run locally. The benchmark reports retain their dated 2026-10-25 measurements; this validation run did not remeasure performance after the rebase.

Implementation steps 1–4 are complete. Step 5 has both pool implementations, differential tests and repeatable native/TinyCC/JIT benchmarks. Current application results and unresolved performance work are in the pool workload report. Explicit flat worker snapshots are demonstrated; automatic transfer remains a v2 candidate.