extern "wasm", and the Host Takes Its Buffers From the ModuleStatus: Superseded by ADR-284 — its D1-D4 are D22-D25 there
Date: September 19, 2026
Target Version: Nikaia 0.0.9
Related: ADR-125 (the library boundary), ADR-037 (target), ADR-062 (a library build is a target), ADR-119 (a target without an operating system), ADR-124 (extern is reserved with its construct)
ADR-125 wrote that the string after extern names the physical
calling convention and reserved "wasm" for a WebAssembly export. Read
again, that was a second word for the same thing: a WebAssembly module’s
exports are C-convention functions — that is what the emitted Rust’s
extern "C" produces on wasm32, and what every toolchain that targets the
web does. What differs on the web is not the convention; it is the memory
and the thread. The host cannot hand the module a pointer into its own
heap, because the module has one linear memory and sees nothing else; and
the host has one thread that must not be blocked, because it is the page’s.
Part III 15.3 says that user_parallelism = no and wasm32-unknown fit; what it lacked was the library shape on that target.
extern "C" is the only string, and the target says WebAssembly.[build]
target = "wasm32-unknown"
artifact = "c-library"
produces <package>.wasm, a generated <package>.js (an ES module the
browser and Node load alike) and <package>.d.ts, from the same pub extern
"C" fn declarations, the same ledger and the same rules as the header: the
JavaScript file is the header for a host that has no headers. There is no
extern "wasm"; a declaration that writes it is refused with the message
the convention is "C" on every target; the target names WebAssembly.
Everything ADR-125 decides holds — status codes, out-parameters,
Untrusted at entry, init and shutdown, the poison after a panic, the
handle lock, the naming — except where memory and thread differ, which is
D2 and D3.
The caller still owns the memory (ADR-125 D3) — but the only memory the module can read is its own, so the caller obtains it there:
<package>_alloc(size: i32, align: i32) -> i32 // an offset into linear memory
<package>_free(ptr: i32, size: i32, align: i32)
The host asks for a buffer, writes its text into it, calls the entry point
with offset and length, reads the result out of the result buffer it also
asked for, and frees both. The generated .js does all of that: a JavaScript
caller writes hello.render("name") and receives a string, and the offsets
never show. set_allocator does not exist on this target, because there is
one memory and the module already manages it.
A handle is an offset the host keeps, freed through <package>_<Type>_free
as everywhere; the .js wraps it in a class with a free() method and,
where the host offers FinalizationRegistry, registers it so that a
forgotten handle is freed when the wrapper is collected — a convenience the
generated file says is not a guarantee.
.js makes it a Promise.The host’s thread may not block, so the blocking form of
ADR-125 D6 is not exported on this target. A pausing entry
point is exported once, with the completion callback and the ticket of
ADR-129 — and the module’s executor is driven from the host’s
event loop: the generated .js registers the module’s wakers with the
host’s timers and I/O, so that a pause on a fetch or a timer resumes when
the host resolves it. The .js exposes each such function as one returning
a Promise, with cancel reachable through an AbortSignal the caller may
pass, which maps to <package>_cancel.
A sync entry point is exported plainly and the .js calls it plainly.
std offers.Part III 17.2’s availability table decides per target already: on
wasm32-unknown there is no filesystem, no thread and no socket of the
module’s own, and a program that calls fs::read_to_string is refused at
compile time as it is today. What the host has — fetch, timers, storage —
reaches the module through the same mechanism the target already uses for
its I/O worker (Part I 8.4’s sidecar as a Web Worker), and the entry points
change nothing about it.
.js beside the
.h, both generated.extern, and no second word to keep meaning
something..js and .d.ts
generator, the executor bridge to the host’s event loop, and the refusal
for "wasm"..js is what changes, not the declaration..wasm runs in Node without one, and
what wasm cannot reach on Node — a file, a socket — is ADR-131’s
open question.wasm32 with shared memory and atomics), which
would let user_parallelism = yes onto this target; it stays no there
as Part III 15.3 says.Nothing. The order of work, after ADR-125 §5:
wasm32-unknown with the two
allocation exports and without set_allocator and the blocking forms;.js and .d.ts generator from the ledger, buffers and handles
hidden;#111 carries it.