- Float full stack: literals (fraction/exponent; `0..10` still a range), f64
opcodes 34-41, @table column, WAL bit-exact replay, json fractions in and
shortest-round-trip out. IEEE-quiet — FDIV never traps where DIV does.
- Bytes: a wo_str with its own class id, so alloc/free/copy are shared but no
Text builtin accepts one; len/at/slice/eq/concat, base64 both ways, json
boundary as base64; TEXT_COPY preserves the kind.
- No implicit Int/Float mixing (WO-E201 in the typechecker, not the emitter,
which picks the opcode from one side and would misread the other).
- One IEEE deviation: float_cmp total order (NaN last, -0.0 == +0.0) for
indexes and order-by, keys canonicalized to match. `?Float` nil is a
reserved quiet NaN — the zero word is +0.0, WO_NIL_SCALAR's bits are -2.0.
- Renderer prefers fixed over exponential in 1e-6..1e21: pure shortest makes
a price of 900.0 read `9e+02`. One renderer for interp/json/float_to_text.
- Fixed en route: lexer double-counted the leading digit; is_scalar_shaped
took Float/Bytes as Int-shaped; Bytes ownership needed a shared heap-scalar
predicate or temps never dropped; order-by bit-compared negatives backwards.
- Iteration 17: `kind = "library"` (absent = program; bad value = WO-E109),
entry-less check mode retiring the `--emit` workaround, Go's `internal/` as
WO-E108 at the consumer's `use`. Driver-only; VM/.wob/GC untouched.
- Framework reorg: internal/{parse,serve}.wo; http/form.wo split out to keep
media_type/form_values public (parse.wo had grown public surface).
- Docs: link audit (97 -> 88 broken, conflict markers resolved, 2 duplicate
stories removed), 00-code-review verified 26/27, iterations re-sequenced.
- Also carries the pre-staged pub(read)/using/#if work from the index.
- Gates: corpus 103/0, test_wal 156/0, web-app 26/0, oop-accept ALL MET.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5.4 KiB
Iteration 8 — shard-actor runtime (the 8+11 concurrency arc, part 1)
Format: fiberloom
product/story-iteration-template. Part of Story — one language, one runtime, one database, one binary.REFINED 2026-08-20 (developer decisions, no code): iterations 8 and 11 are one arc — the scheduler, fibers on it, then serving — because the database-ownership decision below makes a fiberless multi-shard server block a whole thread per cross-shard call. The arc's driving workload is iteration 24 (chat: WebSocket pub/sub). The pre-existing plan (
docs/superpowers/plans/2026-08-01-shard-actor-vm-runtime.md) predates inferred GC (7b), the unified surface, and the DB decision — it is a source of ideas, NOT the plan of record; the arc starts with a fresh brainstorm → spec → plan.
Settled decisions (2026-08-20)
- The database is an actor. The engine (
wo_db+ WAL) lives on one owner shard; every query/write from another shard is a message send, and results come back materialized (queries already copy rows out — the model was built for this). Doctrine-pure: no lock, no shared mutable state. Consequence, accepted deliberately: callers must PARK while the reply travels, which is why 8 and 11 ship as one arc. (Rejected: a coarse engine lock — bends the doctrine and caps write scaling anyway; partitioned tables — the real scale answer, but it waits for a measured need, not v1.) - One concurrency surface: everything is an actor address.
spawnreturns an address whether the spawnee lands on this shard (a fiber) or another (placement policy's call);sendalways moves ownership; a same-heap send skips the ring and is cheap. The language never shows a fiber-vs-actor split. (This dissolves iteration 11's "handle vs address" open question.) - Driving workload: chat (iteration 24) — rooms, broadcast, N concurrent WebSocket clients, one binary. The arc's acceptance is the chat sample's, not only synthetic corpora.
- Order: 22 → the 8+11 arc → 23. The io_uring write path waits for the arc (its batch boundary is the shard tick) and for 22's baseline.
Goals
- The runtime scales past one core the doctrine way: pinned thread-per-core shards, each owning its own heap and event loop; cross-shard communication is a message send that moves ownership — shared mutable state never exists.
- The language grows
spawn/send(the unified address surface); garbage collection stays per-shard (7b's collector is already per-shard by construction), so no global pause appears at any core count. - The database keeps its single-writer truth by BEING an actor on its owner shard.
Acceptance Criteria
- What to achieve?
- Given a program spawning actors across shards,
- when an owned object is sent to another shard,
- then the sender can no longer touch it (compile-time move), the receiver owns it, and its eventual free routes back to its allocation-home arena.
- What to achieve?
- Given debug builds with shard-ownership asserts,
- when the deterministic actor corpus runs under ASan and TSan,
- then zero races, zero leaks, and identical output across runs.
- What to achieve?
- Given a reference to a TRACED object (GC-ness is inferred since
7b — there is no
@gcto write), - when code attempts to send it cross-shard,
- then the compiler rejects it: aliased references cannot cross
heap boundaries, and the diagnostic names the inferred-traced class
and why it is traced. (This criterion originally said
@gc; restated 2026-08-20 in inference terms — same rule, current language.)
- Given a reference to a TRACED object (GC-ness is inferred since
7b — there is no
- What to achieve?
- Given handlers on serving shards querying and writing through the DB-owner shard,
- when the employee/web-app matrices run multi-shard,
- then every answer is byte-identical to the single-shard run and the WAL's ack-after-durable contract is unchanged.
Out Of Scope
- Cross-shard transactions (2PC) — the DB actor serializes writers, so
iteration 18's
transaction { }is unaffected; distributing it is a later story. - Fiber details beyond the shared scheduler substrate — part 2 (iteration 11) owns them.
- WebSocket framing — the framework's (iteration 24's) job.
Info
- The C proving ground (
docs/plan/exploration/c-runtime/, phases A–F: epoll loops, eventfd mail) is the substrate this lifts intowovm. (Path restated 2026-08-20; the oldruntime/wo-rt.creference was stale — that tree was removed with the Rust runtime.) - The VM's object header has carried a shard id since iteration 2 — no relayout.
- Gated by the benchmark: landing the arc means re-running 22 at the concurrency scale it unlocks and recording the before/after delta; it is also where 23 gets a thread to overlap durability against.
Proposed Solution
Fresh brainstorm → spec → plan for the WHOLE arc (8+11), staged: the pinned-worker scheduler + shard-stamped heaps + MPSC mailboxes + the unified spawn/send surface and the traced-send rejection; fibers on that scheduler (part 2's document); the DB-actor migration; then iteration 24 proves it. The 2026-08-01 plan is reference material for the mailbox and heap-stamping shapes only.