Files
gex/fast-ls-baseline.commit
velzieandClaude Opus 5 3f014aa56c Threads are Workers, and the shared build is the one the page loads
Two shapes existed for a guest thread: a Worker of its own, or a turn on the
process's Worker under a round-robin scheduler in the shim. Which one a run
got was decided by whether its memory happened to be shared, not by anyone
choosing -- and on the demo site it was always the second, because the page
could not load a shared build even in principle. So the shape this project
wants had never run there, and the shape that had was the one nothing was
tested against.

The inline shape is gone. With it goes the `THREAD_WORKERS` switch and
`?inlinethreads=1`, the round-robin `Pick` over a process's whole thread
table, the safepoint preemptor that handed the stack to a sibling every
fourth slice, the inline branch of `clone`, and the `park_until_ready` guard
that would not park while a sibling might run. None of it has a caller now
that a Worker owns exactly one entry of the table; `Pick` asks about `cur()`
and nothing else.

Turning the other shape on took five things agreeing, because a memory
import's `shared` flag is part of its type:

    seed.shared.wasm          gex.memory    SHARED  1280..65535
    gex_browser.shared.wasm   env.memory    SHARED  1280..65535
    gex_kernel.wasm           env.process   SHARED  1..65535
    closure/debian            gex.memory    SHARED
    closure/busybox           gex.memory    SHARED

Four of the five were already built and three were already shipped. The shim
was not: the shared build has been produced on every run for as long as it
has existed and copied nowhere the page could fetch it, so the one artifact
that lets a guest thread be a Worker was the one artifact missing. That is
`cp` and a line in `build.sh`.

`TextDecoder.decode` refuses a view backed by a `SharedArrayBuffer` -- "the
provided ArrayBufferView value must not be shared" -- and every string the
runtime reads out of a guest is such a view. The first one thrown is the
first message anything tries to print, so a process died inside its own
logging before it could say why. `utf8()` copies when the buffer is shared
and does not otherwise; sixteen call sites go through it.

Measured before any of it, because a shared memory cannot be relocated and
reserves its maximum up front, once per process, and `GUEST_MEM_BYTES` is a
page short of 4 GiB: sixty-four of them allocate at the full maximum without
complaint. The reservation is address space, not pages.

Where it leaves the demos: `busybox` and `debian` both green on the shared
build with no inline path at all. `debian-deb` now reaches the archive it
could not reach before and aborts in `dpkg-deb --info` with `free(): invalid
pointer`, exit 134 -- which is #126, reproducing for the first time in the
configuration the site will actually ship, and the reason `apt install`
cannot work yet.

What this gives up: a run whose memory is not shared can no longer start a
guest thread. It refuses in `#drainThread` rather than quietly handing the
Worker a structured *copy* of the address space, where a futex is never woken
and a mutex is held by nobody.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01DED4yFovUttLMGJ7tMfj4B
2026-08-12 01:54:18 -04:00

2 lines
9 B
Plaintext