Skip to content

Stop making the synchronous relays async imports - #375

Merged
rhashimoto merged 1 commit into
rhashimoto:masterfrom
lalexdotcom:sync-relays-plain-imports
Oct 5, 2026
Merged

rhashimoto merged 1 commit into
rhashimoto:masterfrom
lalexdotcom:sync-relays-plain-imports

Conversation

@lalexdotcom

Copy link
Copy Markdown
Contributor

What happens

src/asyncify_imports.json lists every relay twice, ipp and ipp_async and so on, and the Makefile passes it as ASYNCIFY_IMPORTS to both the Asyncify and the JSPI builds. Emscripten already makes every library method marked __async an async import (emsdk 3.1.61, tools/link.py: settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']]), and libadapters.js marks exactly the _async relays. So what the list adds is the synchronous relays.

On the JSPI build, the glue wraps each of those in WebAssembly.Suspending too. When the function returns a plain value, Chromium charges little for the wrapper and Firefox almost as much as a real suspension. In a loop calling one empty import from a dedicated worker, a wrapper whose function returns at once costs 2.2-2.4 µs per call on Firefox 153 and 0.055-0.06 µs on Chromium 151; a plain import, 0.004 µs on both; a real suspension on Firefox, 2.7-3.0 µs. Every synchronous VFS method, authorizer, user function and hook pays it on every call. For example, an application that sets an authorizer and runs the JSPI build on Firefox spends most of each prepare crossing these wrappers.

The change

The list goes, and ASYNCIFY_IMPORTS with it. The _async relays stay async imports through __async, and the synchronous ones become plain imports. dist is rebuilt with emsdk 3.1.61:

  • wa-sqlite.mjs, wa-sqlite.wasm and wa-sqlite-jspi.wasm are unchanged, byte for byte;
  • in wa-sqlite-jspi.mjs and wa-sqlite-async.mjs, the import pattern no longer names the synchronous relays;
  • wa-sqlite-async.wasm is 1104 bytes smaller, since Asyncify no longer instruments around them.

A rebuild of master with the same image reproduces the checked-in dist exactly.

Measured

µs per call, Playwright 1.62.1 (Chromium 151, Firefox 153), a dedicated worker, median of 5 pages, each the median of 5 rounds, the two builds alternated:

Firefox, before after Chromium, before after
JSPI, one-row INSERT, MemoryVFS 361 102 30.1 26.4
JSPI, prepare a 50-column SELECT with an authorizer set 687 140 25 21
JSPI, one-row INSERT, MemoryAsyncVFS 356 100 27.8 25.4
Asyncify, one-row INSERT, MemoryVFS 93 90 33.5 33.9
Asyncify, prepare with an authorizer 153 153 26.7 28
Asyncify, one-row INSERT, MemoryAsyncVFS 90 90 27.4 27

MemoryAsyncVFS gains too because only its open, close, read, write, size, delete and access methods are async; locking, syncing and file controls are the synchronous methods it inherits. The Asyncify build does not move on either engine.

Test

No test changes: the change is in what the glue wraps, and the suite already runs every build. The whole suite passes on Chromium with web-test-runner.config.mjs: 6488 passed, 0 failed, before and after. On Firefox (Playwright), file by file, the results are the same before and after; the only failures are the three vfs_read_freshness tests on OPFSWriteAheadVFS, which need readwrite-unsafe, and sql.test.js was run without its OPFSWriteAheadVFS configuration, whose sql_0005 hangs the harness there (#373).

Checklist

  • I grant to recipients of this Project distribution a perpetual,
    non-exclusive, royalty-free, irrevocable copyright license to reproduce, prepare
    derivative works of, publicly display, sublicense, and distribute this
    Contribution and such derivative works.
  • I certify that I am legally entitled to grant this license, and that this
    Contribution contains no content requiring a license from any third party.

src/asyncify_imports.json listed every relay twice, sync and _async, and
both builds were passed that list. Emscripten already treats the methods
marked __async as async imports, so the list only added the synchronous
relays. On the JSPI build that wraps them in WebAssembly.Suspending, which
Firefox charges almost as much as a real suspension on every call.

dist rebuilt with emsdk 3.1.61; wa-sqlite.mjs/.wasm and
wa-sqlite-jspi.wasm are unchanged.
@rhashimoto

Copy link
Copy Markdown
Owner

Thanks for the PR!

The performance boost on Firefox is great, but doing that while simplifying the build is really great. LGTM.

@rhashimoto
rhashimoto merged commit 96d9118 into rhashimoto:master Oct 5, 2026
1 check passed
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.

2 participants