Skip to content

fix(bb.js): re-enable browser tests with a local CRS; restore typed C++ exceptions on threaded wasm - #25767

Draft
charlielye wants to merge 3 commits into
nextfrom
cl/bbjs-wasm-browser-fixes
Draft

charlielye wants to merge 3 commits into
nextfrom
cl/bbjs-wasm-browser-fixes

Conversation

@charlielye

@charlielye charlielye commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Two fixes for bb.js wasm, found while re-enabling the browser tests.

Browser tests re-enabled, with the CRS served locally

The browser tests were disabled in #21596 for "Failed to fetch" errors. Each run starts a fresh browser profile, so bb.js downloads the CRS from the CDN every time, and under CI that download was failing.

  • The headless runner answers the CRS hosts itself with a Playwright route (headless-test/src/local_crs.ts). The route serves ~/.bb-crs, the cache barretenberg/crs/bootstrap.sh fills.
  • CI's machine images keep only the uncompressed G1 file, so the route compresses points on the fly (x, with y's low bit in bit 255). This reproduces the CDN's g1_compressed.dat byte for byte.
  • The tests then need no network and run isolated without NET=1.
  • verify_honk_proof needs 2^20 points. That's past bb.js's 2^19 default, which was lowered from 2^20 (feat(bb): chonk inputs revisit, smaller default bb.js CRS #23400) while these tests were off. prove_and_verify now takes --srs-size, and that test passes 2^20. The bb.js default is unchanged.

Run in CI's isolated container (ci3/docker_isolate, no network): verify_honk_proof and a_1_mul both prove and verify. Before the change, a_1_mul failed with TypeError: Failed to fetch.

Threaded wasm: the main instance couldn't catch exceptions by type

The host learned a module's memory import by instantiating it on the memory it then handed the real main instance.

  • Cause: instantiating runs the module's start function. On a shared memory, wasm-ld's __wasm_init_memory initializes the memory once, and sets up the main thread's TLS only in the first instance to get there. That first instance was the throwaway one.
  • Effect: the real main instance ran with TLS base 0. No C++ catch by type matched, since the exception-handling personality reads thread-local state. So every error from threaded bb.js (threads > 1) came back as Bb: unknown exception, and thread-locals ran without their initializers.
  • Fix: no probe. JS can't read a compiled module's memory limits, but the generator has the module:
    • Package generation takes each wasm at its build location (--package-wasm-module <name>=<path>, e.g. barretenberg/cpp/build-wasm-threads/bin/barretenberg.wasm), reads its memory import (ipc-codegen/src/wasm_memory.ts), embeds the limits in the generated wasm.ts, and installs the module into the package's wasm/. bb.js-api's separate copy step is gone.
    • The ipc-runtime host creates the memory from those limits (moduleMemory), and requires them for a module that imports its memory. A module run in place of the package's own (wasmPath) must declare the same memory; a mismatch says so.
    • ci-barretenberg now builds bb before generating bb.js-api (generation needs the wasm), then runs bb's tests.
  • Tests: a hand-written module whose start function claims a flag the way __wasm_init_memory does, and reports whether its instance won, on the calling thread and on a worker. Plus the declared-maximum cap, the missing-declaration error and the mismatch message.
  • Also: a worker whose init fails is now terminated; it used to keep node alive.
  • End to end: the browser now reports prover trying to get too many points in MemBn254CrsFactory! 524288 vs 1048576 instead of Bb: unknown exception.

Not in this PR

In the browser, the threads bb's pool spawns during a call don't start until that call returns. The thread workers' scripts are fetched through the busy main worker. So the first threaded call (CRS decompression in the test) runs single-threaded. Starting the pool in an earlier call (Init) would fix that; this PR leaves it for a follow-up.

🤖 Generated with Claude Code

charlielye and others added 3 commits November 7, 2026 14:25
…ved locally

The browser tests were disabled for "Failed to fetch" errors: each run starts a fresh browser
profile, so bb.js downloads its CRS from the CDN every time, and that download failed under CI.
The headless runner now answers the CRS hosts itself (Playwright route) from ~/.bb-crs, the cache
barretenberg/crs/bootstrap.sh fills, compressing G1 points from the uncompressed file when that is
all a machine has (CI's images keep only that). The tests then need no network, so they run
isolated without NET=1.

verify_honk_proof needs 2^20 points, past bb.js's 2^19 default (lowered while these tests were
off), so prove_and_verify takes --srs-size and that test passes it. The runner's top-level error
no longer calls every failure "Failed to start test server".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rted on

createMemory validates the memory import with a dry instantiation on the memory it then returns.
Instantiating runs the module's start function, and on a shared memory wasm-ld's
__wasm_init_memory initializes the memory once, setting up the main thread's TLS only in the
instance that gets there first: the dry one. The real main instance was left with its TLS base at
0, so its thread-locals sat at the bottom of memory with their initializers unapplied, and no C++
catch by type matched (the personality reads thread-local state). Every error bb.js raised from a
threaded module came back as "Bb: unknown exception".

Return a fresh memory after the dry instantiation. The test is a module whose start function
claims a flag the way __wasm_init_memory does and reports whether its instance won.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on, read at generation

The host used to learn a module's memory import by instantiating it on the memory it was
about to use. That ran the start function (__wasm_init_memory), which claims the one-time
init, so the real main instance got __tls_base 0: every threaded C++ exception lost its type
and bb.js reported "Bb: unknown exception".

JS can't read a compiled module's memory limits, but the generator has the module. Package
generation now takes each wasm at its build location (`--package-wasm-module <name>=<path>`),
reads its memory import, records the limits in the generated package and installs the module
into the package's wasm/ dir. The host creates the memory from those limits (moduleMemory) with
no probe, and requires them when a module imports its memory.

- ipc-codegen: wasm_memory.ts import-section reader; generate.ts installs the modules.
- bb.js-api and the echo example generate from their build outputs; bb.js-api's copy step goes.
- ci-barretenberg builds bb before generating bb.js-api, then runs bb's tests.
- A worker whose init fails is terminated instead of keeping node alive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@charlielye
charlielye force-pushed the cl/bbjs-wasm-browser-fixes branch from 698de72 to 149931b Compare November 7, 2026 14:53

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant