Status: the single-agent surface and real cross-agent substrate ship.
SharedArrayBuffer uses a refcounted shared backing block; $262.agent runs
agents on isolated OS-thread realms; Atomics.wait / notify coordinate over
the shared store. Exact-count FIFO wake ordering and timer/microtask refinements
remain follow-up work.
This document preserves the original scoping and phased design; estimates and future tense below describe the pre-implementation plan. Current aggregate status lives in ROADMAP.md. Sister docs: multi-realm.md (per-realm intrinsics — the same install plumbing SAB/Atomics use) and multi-agent-atomics.md.
SharedArrayBuffer / Atomics is the single largest engine-true gap
in the binary-scored corpus: ~382 built-ins/Atomics + 104
built-ins/SharedArrayBuffer fixtures fail outright (ReferenceError: SharedArrayBuffer), plus ~148 fixtures under
DataView / ArrayBuffer / TypedArray* that reference SAB. Everything
else surfaced in triage (docs triage 2026-06) is a deliberate
divergence (Annex B, strict-only, eval-strict-this) that counts as an
honest fail by design — this is the one big fixable block.
The decisive fact: most of it needs no real concurrency. Counts against the pinned corpus:
| tree | total | use $262.agent (multi-agent) |
single-agent |
|---|---|---|---|
built-ins/Atomics |
382 | 112 | ~270 |
built-ins/SharedArrayBuffer |
104 | 0 | 104 |
SAB-referencing in DataView/ArrayBuffer/TypedArray* |
~148 | ~0 | ~148 |
Cynic is single-agent-per-isolate (no Workers, no threads). On a single
agent, Atomics.* read-modify-write / load / store / compareExchange /
isLockFree are ordinary sequential operations on the backing store;
Atomics.notify always returns 0 (no other agent waits); the only
genuinely-concurrent surface ($262.agent.* spawning agents, the
memory-model litmus tests, cross-agent wait/notify) is the ~112
fixtures we defer.
- QuickJS-ng — the closest reference: ships SAB + Atomics with a
single-process backing store;
Atomics.waituses a real futex only when threads exist, else degrades. Smallest faithful implementation to mirror. - V8 / JavaScriptCore / SpiderMonkey — SAB backed by a refcounted
shared store;
wait/notifyover a per-buffer futex/condvar table;isLockFree(n)true for the platform's lock-free widths (1,2,4, and 8 whereAtomicson 64-bit ints is lock-free). We copy the observable rules (isLockFree results, the §25.4 validation order, the[[CanBlock]]gate onwait) without the threading machinery in phase 1. - XS (Moddable) — embedded, single-agent by default; SAB is a non-detachable ArrayBuffer. Confirms the "SAB ≈ ArrayBuffer minus detach, plus grow" shape is enough for the sequential surface.
Spec: §25.2 SharedArrayBuffer, §25.4 Atomics, §9.7 Agents + §9.8 AgentClusters, §25.4.3 (ValidateIntegerTypedArray / ValidateAtomicAccess), §25.4.{11,12} wait/notify, §25.1.3 (shared vs non-shared ArrayBuffer abstract ops).
ArrayBufferis fully implemented insrc/runtime/builtins/typed_array.zig(constructor,byteLength, resizable +resize,transfer/transferToFixedLength,slice, species,isView). Backing store isJSObject.array_buffer: ?[]u8witharray_buffer_max_byte_length: ?usize(resizable) and thehas_array_buffer_databrand (detached = brand set + slice null) — seesrc/runtime/object.zig.TypedArray(typed_view) andDataView(data_view) views borrow the backing slice and already work overArrayBuffer.- No
SharedArrayBuffer, noAtomics, noshared/agentconcept anywhere (git grepclean). Greenfield, with a strong reuse base.
SAB is an ArrayBuffer that is never detachable and grow-only
(grow, not resize). Reuse the array_buffer slice + brand; add a
discriminator.
JSObject: addarray_buffer_shared: bool = false(or fold into a small enum on the existing brand).IsSharedArrayBuffer(O)= brand set ∧ shared.- Global
SharedArrayBufferconstructor +%SharedArrayBuffer.prototype%, installed per-realm alongsideArrayBuffer(mirror the existinginstall).new SharedArrayBuffer(len [, { maxByteLength }])→ allocate a zeroed store; growable iffmaxByteLengthgiven.- prototype:
byteLength,maxByteLength,growable,grow(n)(grow-only; never shrinks),slice(returns a SAB),@@toStringTag, species (get [Symbol.species]). - never
detached/transfer/resize(those stay ArrayBuffer-only).
- Wire the existing ArrayBuffer.prototype
this-is-sharedarraybuffer.jsguards: methods that step "If IsSharedArrayBuffer(O) throw TypeError" (byteLength/detached/maxByteLength/resizable/resize/slice/transfer/transferToFixedLength) now reach a real SAB instead of aReferenceError→ those ~9 ArrayBuffer fixtures flip to pass. TypedArray/DataViewconstructors accept a SAB-backed buffer (the buffer-arg validation currently keys off the AB brand; broaden to "AB or SAB"). A SAB-backed view is otherwise identical (no detach path).
Yield estimate: 104 (SharedArrayBuffer) + ~148 (SAB-backed views /
ArrayBuffer guards) ≈ ~250 fixtures.
A global Atomics ordinary object (per-realm) with:
add,and,or,sub,xor,exchange— §25.4.8 AtomicReadModifyWrite:ValidateIntegerTypedArray→ValidateAtomicAccess→ ToIntegerOrInfinity/ToBigInt the value → read, op, write back. On a single agent this is a plain sequential read-op-write.compareExchange— §25.4.6, same shape with the expected/replacement compare.load,store— §25.4.{10,13}.isLockFree(n)— §25.4.9: true for 1, 2, 4 (and 8, matching V8 on 64-bit); false otherwise. Pure function of size.notify(ta, index, count)— §25.4.12: validate, then return 0 (no waiters exist on a single agent).wait(ta, index, value, timeout)— §25.4.11: requires an Int32Array/ BigInt64Array over a shared buffer; honor[[CanBlock]](§9.7 — a TypeError when the surrounding agent can't block). The not-equal fast path ("not-equal") and the validation/throw paths are fully testable single-agent; with no other agent to notify, a matching wait either returns"timed-out"(finite timeout) or is a no-op we bound. Most single-agentwaitfixtures exercise validation +not-equal+ zero-timeouttimed-out.waitAsync(§25.4.x, ES2024) — returns{ async: true, value: <promise> }/{ async:false, value:"not-equal"|"timed-out" }; single-agent the promise resolves"timed-out".@@toStringTag= "Atomics".
Engine touch-points: a new src/runtime/builtins/atomics.zig; the
typed-array element read/write helpers in typed_array.zig are reused
for the per-kind load/store. No GC-visible new heap types (Atomics is a
plain object; SAB reuses the ArrayBuffer slot).
Yield estimate: ~270 single-agent Atomics fixtures (213 landed).
Single-agent follow-ups — shipped (Atomics now 268 / ~270):
Atomics.waitAsync(§25.4, ES2024) — returns the{ async, value }record; sync"not-equal"/"timed-out"paths plus a pending Promise for the would-block case (cross-agent resolution deferred).Atomics.pause(TC39 proposal) — no-op hint, validates an optional integral argument, returnsundefined.Atomics.storereturn value isToIntegerOrInfinity(normalizes-0→+0).
Deferred at the end of the single-agent phase:
wait/waitAsynccross-agent resolution + the memory-model litmus tests (~112$262.agentfixtures). The real-agent follow-up subsequently shipped; see multi-agent-atomics.md.waitwith[[CanBlock]] = false(browser-main-thread semantics) — 2 fixtures; needs an agent CanBlock model.
Phase 1 + 2 combined ≈ ~500 fixtures → headline ~89.3 % → ~90.3 %+.
Shipped. Each agent has its own realm and heap on an OS thread; agents share
only a refcounted data block and futex state. The test262-only $262.agent
host hooks provide start, broadcast, report, sleep, and lifecycle operations.
The accepted design and residual work are in
multi-agent-atomics.md.
SharedArrayBuffer+Atomicsare frozen with the other primordials at realm init (the existing freeze pass walks them automatically once installed — no extra work, but confirmObject.isFrozen(Atomics)in atest-sesfixture).- SAB has no detach/transfer, so it sidesteps the ArrayBuffer capability-revocation surface entirely.
- A SAB backing store is process-local in phase 1 (single agent), so
there's no cross-realm sharing concern yet; phase 3 must revisit how a
shared store interacts with per-realm teardown
(
Heap.pending_realm_teardown).
- Unit tests live in
atomics_test.zigandshared_array_buffer_test.zig. - Run
zig build test262 -- --filter=built-ins/SharedArrayBufferandzig build test262 -- --filter=built-ins/Atomicsafter shared-memory changes. --filter=built-ins/DataView/ArrayBufferto confirm the SAB-backed +this-is-sharedarraybufferflips land.test262-safe --gc-threshold=1on the SAB tree (new heap-slot usage).- Use
test262-results.mdfor current counts; the estimates above are historical.