[Fork/ABI] Make fresh-instance replay activation-state safe - #1096
Draft
brandonpayton wants to merge 15 commits into
Draft
[Fork/ABI] Make fresh-instance replay activation-state safe#1096brandonpayton wants to merge 15 commits into
brandonpayton wants to merge 15 commits into
Conversation
Serialize supported tagged catch state per activation, reject non-reconstructible reference and table state, and require the ABI 43 safety capability across build and host launch boundaries.
Contributor
Phase B-1 matrix build status —
|
| Package | Arch | Status | Sha |
|---|---|---|---|
| icu | wasm32 | built | 3785cb4f |
| libcurl | wasm32 | built | 2e9bfe54 |
| libcxx | wasm32 | built | 0b9e4ae7 |
| libcxx | wasm64 | built | b1e80986 |
| libiconv | wasm32 | built | 2ae7c3ce |
| libpng | wasm32 | built | 00b34f15 |
| libxml2 | wasm32 | built | 4c2d44bb |
| libzip | wasm32 | built | 592acc36 |
| openssl | wasm32 | built | d497113a |
| openssl | wasm64 | built | e622094d |
| sqlite | wasm32 | built | 35544fa8 |
| sqlite | wasm64 | built | ff0ea759 |
| zlib | wasm32 | built | f372595b |
| zlib | wasm64 | built | 5717b46e |
| bc | wasm32 | built | 26c9f859 |
| bzip2 | wasm32 | built | 18344818 |
| coreutils | wasm32 | built | 640ffdd1 |
| cpython | wasm32 | built | 6bb296c9 |
| curl | wasm32 | built | d55e6c3d |
| dash | wasm32 | built | e6a122a8 |
| diffutils | wasm32 | built | b6423d11 |
| dinit | wasm32 | built | 459ad0cd |
| erlang | wasm32 | built | 526bc877 |
| fbdoom | wasm32 | built | 78caa3cd |
| file | wasm32 | built | 039f051d |
| findutils | wasm32 | built | 1cf00e9e |
| gawk | wasm32 | built | 57a33bb3 |
| git | wasm32 | built | 86f7980e |
| grep | wasm32 | built | 6dcbd57a |
| gzip | wasm32 | built | e9487ab0 |
| homebrew-bootstrap | wasm32 | built | 3ca8b809 |
| kandelo-sdk | wasm32 | built | 7948a22d |
| kernel | wasm32 | built | 8b523b39 |
| less | wasm32 | built | e6f4f1d9 |
| lsof | wasm32 | built | 9fd429eb |
| m4 | wasm32 | built | 0b203084 |
| make | wasm32 | built | d30ffaed |
| mariadb | wasm32 | built | 0ab88ecb |
| mariadb | wasm64 | built | d3641197 |
| modeset | wasm32 | built | b1d459dd |
| msmtpd | wasm32 | built | 1676f17c |
| nano | wasm32 | built | 5fb7f665 |
| ncurses | wasm32 | built | 2a8d1a03 |
| netcat | wasm32 | built | edcf82d4 |
| nginx | wasm32 | built | afde4ab2 |
| php | wasm32 | built | e0bae805 |
| posix-utils-lite | wasm32 | built | 7446b2dc |
| ruby | wasm32 | built | a7d611f7 |
| sed | wasm32 | built | d5f738b9 |
| spidermonkey | wasm32 | built | 313e60b9 |
| tar | wasm32 | built | d712156a |
| tcl | wasm32 | built | 3c78c926 |
| unzip | wasm32 | built | 662e6659 |
| userspace | wasm32 | built | 67450243 |
| vim | wasm32 | built | b2d332ff |
| wget | wasm32 | built | 8947f2fb |
| xz | wasm32 | built | ab7ba59e |
| zip | wasm32 | built | af083f1d |
| zstd | wasm32 | built | 694f316b |
| bash | wasm32 | built | 1be72f85 |
| mariadb-test | wasm32 | built | 52fddeeb |
| mariadb-vfs | wasm32 | built | efa6b131 |
| mariadb-vfs | wasm64 | built | e63163f3 |
| nethack | wasm32 | built | 0262e86e |
| node | wasm32 | built | eefc841b |
| redis-vfs | wasm32 | built | 4178da00 |
| shell | wasm32 | failed | — |
| spidermonkey-node | wasm32 | built | f9ac0d7d |
| vim-browser-bundle | wasm32 | built | 4e098ad4 |
| lamp | wasm32 | failed | — |
| nethack-browser-bundle | wasm32 | built | bd33eda7 |
| nginx-php-vfs | wasm32 | failed | — |
| nginx-vfs | wasm32 | failed | — |
| node-vfs | wasm32 | failed | — |
| rootfs | wasm32 | built | 5526de95 |
| wordpress | wasm32 | failed | — |
Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.
Replace the ABI 43 source-shape rejection shortcut with typed activation, exception, GC, static-root, global, table, and segment reconstruction recipes. Lower legacy EH and reentrant loader imports before reachability analysis, remove module-static reference stashes, and retain the fixed linked-frame header and arbitrary chained continuation allocator. Version the activation-safe artifact contract and inventory so stale ABI 42 or native-start artifacts cannot be restamped as safe.
Give Node and browser process Workers the same versioned reference graph, externref authority, imported-state, table-replica, exception, and replay-gate owners. Recreate main and side-module Store-local identities before continuation rewind and release temporary roots on completion, abort, exec, and teardown. Stage dlopen as host-only prepare/next operations followed by ordinary Wasm initializers, serialize pthread loader ownership, replay exact module layouts, and defer signal delivery until libc regains the guest boundary.
Exercise compiler EH, CatchRef reference payloads, GC cycles and aliases, externref authority, mutable globals and tables, worker-import exceptions, abort recovery, and stale-root cleanup. Run real child process Workers instead of same-instance simulations. Cover main, pthread, side-module, constructor and DT_NEEDED loader paths, plus deep arbitrarily chained continuations and transactional ENOMEM recovery in Node and Chromium.
Require ABI 43 activation-state-safe capability, exact role metadata, zero native start sections, and staged-loader inventory across package receipts, indexes, sidecars, Homebrew validation, and shell guards. Build valid compiler-EH fixtures normally instead of routing them to an unsupported bucket. Regenerate the source-bound program projection and standalone resolver, and select the declared LLVM archiver on Darwin so final-key source rebuilds remain reproducible.
Document the activation-byte versus deterministic-recipe invariant, fixed frame footprint, arbitrary chained continuation, complete CatchRef/GC/global/table ownership, non-reentrant staged loader boundary, and truthful host-engine limits. Record the exact package, bottle, index, shell, and VFS rebuild sequence without claiming broad publication or manual browser coverage before those coordinated artifacts exist.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Kandelo promises that software can call
fork()at an arbitrary point in itsexecution. A child process copies linear memory into a fresh WebAssembly
(Wasm) instance, but references, exception objects, mutable globals, tables,
and dynamically loaded module instances do not come with that byte copy.
The old instrumenter stored reference values in module-wide scratch tables and
put only a table slot number in each continuation frame. That was unsafe for
recursion and reentrancy, and it failed in a real child because the fresh
module's scratch tables were empty.
The first draft of this pull request replaced that failure with broad
instrumentation rejection. That was also the wrong contract: ordinary
compiler-generated Wasm references, exceptions, globals, tables, and dynamic
link state need preservation mechanisms, not source-shape exclusions. This
revision gives every valid reference shape an explicit reconstruction owner.
What changed
Function references are named through fresh-instance catalogs; external
references use process-owned handles; exception and Wasm garbage-collection
values use generated typed codecs that preserve aliases and cycles.
Catch,CatchRef,CatchAll,CatchAllRef, legacy exception handling,scalar/vector/reference payloads, multiple arms, distinct targets, recursion,
and compiler cleanup state replay by reconstructing and throwing inside the
child instance. No exception reference crosses JavaScript.
set/fill/copy/init/grow, and data/element segment lifetime haveexplicit module-state owners. Pthread replicas synchronize through a
published generation.
records exact memory/table bases, dependencies, handles, symbol scope,
thread-local storage, initialization state, and table mutations.
protocol. Host imports compile and instantiate only; libc invokes bootstrap,
relocation, and constructors as ordinary Wasm after the import returns.
Instrumented artifacts have no native Wasm start section.
are released after success, abort, process replacement, or worker teardown.
The historical module-static reference stashes are absent.
no ordinary source-function local and no bytes beyond the existing 16-byte
frame header. Catch payload storage overlays the maximum simultaneously live
tuple rather than growing with arm count.
FORK_CAP_ACTIVATION_STATE_SAFE; package/index/host guards also require theexact role metadata, staged-loader inventory, and zero native start sections.
Unsafe ABI 42 output cannot be relabeled as ABI 43.
dependency union. The shared cache has no build-host dimension, so identical
source now receives the same key on macOS and Linux.
before artifact-guard integration tests; a workflow contract guards that
prerequisite.
and Wasm garbage-collection modules. The instrumenter now exposes a bounded
structural identity query that validates the ABI export thunk, optional
delegate, memory model, relocatable role, fork imports, and fork exports
without decoding unrelated function bodies. ABI extraction and fork-import
predicates consume the same record, including for large transformed
programs that exceed WABT's decoder. A source-only WABT fallback is retained
for environments where the Rust tool has not yet been built.
Validation
Run through
scripts/dev-shell.shagainst this implementation:bash scripts/dev-shell.sh bash scripts/ci-run-test-suite.sh fork-instrument— 331 passed, 0 failed.
bash scripts/dev-shell.sh bash scripts/ci-run-test-suite.sh vitest— 2,552 passed, 2 expected ucontext failures, 130 skipped, 3 failures.
Two are the exact PR [Rehearsal/Fork/POSIX] Validate catch-state and spawn fixes before main-first activation #1094-base lazy-archive assertions: that base calls
fetch(url, undefined), while currentmainowns Brandon's one-argumentcorrection in
7d8929e87. The third was an aggregate-suite output race:fork-dlopen-replay-e2eobservedhost/dist/worker-entry.jswhile anothertest was rebuilding that directory; the focused file passed 4/4 immediately
afterward. An earlier aggregate run also exposed stale local
lessandtarmirror generations; rebuilding/reinstalling them through their normalpackage scripts corrected their final keys, and the affected focused
resolver/credentials files passed 12/12.
2 expected ucontext failures, and 8 source-less cases delegated to named WAT
suites.
non-null function-reference payload that the child calls.
root/later allocation rollback, scalar CatchRef, reference-bearing CatchRef,
and aliased Wasm-GC state.
passed.
23/23 passed; loader host-import misuse coverage — 7/7 passed.
bash scripts/dev-shell.sh bash scripts/check-abi-version.sh update— passed.bash scripts/dev-shell.sh bash scripts/check-abi-version.sh— passed.bash scripts/dev-shell.sh bash scripts/test-wasm-artifact-guards.sh—passed, including structural present/missing/invalid ABI state, fork-import
truth, malformed-decoder fail-closed behavior, and the source-only
WABT/Binaryen fallback.
ABI thunks, Wasm64, missing/invalid ABI state, and the command-line output.
The previous remote
preflightandtoolchain-cachefailures were WABTdecode failures on valid ABI 43 modules, not reference-shape rejection.
On exact head
e61c1a1a8,preflightandtoolchain-cacheboth pass withthe structural decoder. The same run's change-scope, package/test-gate
validation, fork-instrument, and cargo-kernel jobs pass. Its ABI 43 library
rebuild phase completed without a failure. Its program phase reached 28
successful jobs before CPython exposed two remaining WABT predicates:
instrumentation and the complete ABI 43 fork contract succeeded, then ABI
extraction returned status 2 before packaging.
13 MiB transformed artifact passes structural ABI, fork-import, and complete
fork-contract validation; after routing those final predicates through the
structural decoder, the full recipe packages and installs
python.wasmandits 14 MiB runtime ZIP.
homebrew-validate-wasm-executable.sh ... 43 wasm32reports
required.inspection pass with the updated guard. Regenerating
program-packages.jsonproduced no diff, so the fix does not change packagecache identities.
30193794024proves the CPython guard fix on6babf5e2f: all 14 foundational library builds and 56 of 62 attemptedprogram builds passed. CPython 3.13.3 built a 13 MiB
python.wasmand14 MiB runtime ZIP, installed both through the package resolver, uploaded
the staged ABI 43 archive, and recorded a successful index entry; job
89775163413passed. The independent rootfs job89775165583also passed,building and staging the 336-path, 16,787,687-byte ABI 43 rootfs. The six
attempted-program failures were exactly
shelland its five directconsumers (
lamp,nginx-php-vfs,nginx-vfs,node-vfs, andwordpress). Every terminal log reaches the same fail-closed ABI 41Homebrew metadata guard; none reaches a fork-instrument or package-specific
transform failure.
320e2bc1bcontains only two documentation commits after thattested implementation SHA. Browser smoke run
30199271143passed, andstaging run
30199271156passed its fork-instrument, cargo-kernel,artifact/package validation, toolchain-cache, and complete preflight jobs.
Preflight computed the same 14-library/62-program matrix because the
incomplete PR ledger cannot safely be reused as a complete fetch-only
snapshot. The remaining staging rebuild and exact-shell run
30199271149were then intentionally cancelled to avoid spending hours rebuilding
unchanged cache keys and reproducing the already classified missing
Homebrew input; no validation failure triggered either cancellation.
30193794103started before that PR generation wascomplete, selected the canonical/source fallback, and resolved 40 of 47
browser inputs. Five composite images truthfully rejected the existing ABI
41 Homebrew shell metadata; MariaDB wasm64 and its VFS lacked
sysroot64inthat fallback. The run failed before shell composition, Node boot, Chromium
boot, or fork replay. The staging matrix's own shell job
89775165119thenmaterialized its ABI 43 package dependencies and independently stopped at
the same ABI 41 Homebrew metadata guard. A complete verified PR generation
therefore cannot exist until the coordinated ABI 43 Homebrew bottle and
metadata rebuild is performed; rerunning alone cannot make this boundary
green.
Playwright cross-origin-isolation smoke.
./run.sh browser --host 127.0.0.1materialization passresolved 56 of 71 requested package entries, rebuilt the 336-path rootfs,
and validated the kernel and core gallery inputs. Redis server, GNU tar,
Tcl, Vim, and wget passed fork instrumentation during this pass, alongside
the already rebuilt Git,
git-remote-http, and nginx artifacts. The commandthen stopped before Vite because the required Homebrew shell metadata
declares ABI 41 while the candidate requires ABI 43.
git diff --check, and changed-shellbash -nchecks — passed.host-independent cache head; package publication-flow and package
build-root/projection contracts passed. After the workflow-only tool
preparation edit, 124/125 passed in one aggregate rerun and
program-resolver-literalshit its known five-second parallel-load timeout;that file immediately passed 2/2 alone in 1.48 seconds. The explicitly
prepared artifact-guard file passed 16/16.
CARGO_BUILD_TARGET=x86_64-unknown-linux-gnuwas byte-identical to ordinarymacOS generation; the clean remote Linux package projection check also
passed.
bash scripts/dev-shell.sh bash build.sh— passed and locally rebuilt thekernel, instrumenter, fixtures, host, ABI 43 package closure, and 336-path
root filesystem image from the corrected final keys. The resulting
rootfs.vfsis 16,787,687 bytes. Nothing was published.Not run or not claimed:
shellcheckis not installed in the declared dev shell. It was attemptedand is not claimed;
bash -nand the executable shell tests above pass.dev-shell
./run.sh browserpreparation now reaches the shell resolver, butcorrectly rejects the existing Homebrew closure's ABI 41 metadata before
Vite starts. Broad Playwright gallery coverage therefore remains unrun until
the coordinated ABI 43 bottle/shell rebuild exists. The focused tests above
did execute real browser process Workers, and exact-head browser smoke is
green.
test-gate-prepareand its split downstream suites did not run.The inherited fetch-only workflow requires a complete program matrix before
materializing the prepared workspace, and there is no ABI 43 last-green
shell artifact to substitute for this new epoch. Loosening that condition
would only move the same missing-artifact failure into preparation.
exec-brk-baseskips because the local MariaDB artifact is still ABI 18.produced all 14 library archives and 56 program archives, but the shell and
five shell-derived images remain unavailable until the coordinated
Homebrew rebuild. Seven further ABI-bound program generations were not
scheduled by the inherited staging policy:
erlang-vfs,perl,perl-vfs,python-vfs,redis,sqlite-cli, andtexlive. The localproof is the 15-generation rootfs closure, not a publication candidate.
the isolated
pr-1096-stagingprerelease ledger. No Homebrew bottle,canonical ABI 43 binary index, canonical shell/VFS artifact, or release
namespace was promoted or activated.
ABI and rebuild scope
ABI 43 changes the fork capability contract, host channel request flags,
reference transaction/archive formats, generated Rust/C/TypeScript constants,
module bootstrap requirements, and artifact inventory.
Every fork-instrumented main program and side module must be rebuilt from raw
linker output. Affected package bottles, package receipts, binary indexes,
shell closure, and VFS images must then be regenerated in that order. The
final-key local proof rebuilt Dash, Bash, ncurses, coreutils, gawk, grep, sed,
bc, file, m4, make, findutils, diffutils, and posix-utils-lite from source.
The coordinated run must re-enable the six expensive packages excluded from
ordinary staging and must give
sqlite-clia reviewed source-build owner orretire that generation; no ABI 42 binary or missing ledger entry can fill
those seven slots.
The complete coordinated publication sequence is recorded in
docs/plans/2026-07-25-abi-43-activation-state-safe-rebuild-plan.md.Stack and merge
This draft remains stacked on PR #1094's exact rehearsal head
6d923c6454dd7174082f25c3d3991d03f86f5ddb. PR #1094 was not modified orpushed. PR #1097 is the active ABI-42 main-first replacement-base work; this
draft will not be restacked until that base is agreed and lands. Before merge,
the restack must preserve Brandon's already-main VFS correction and
attribution.
Do not merge this pull request without Brandon's explicit approval.