The wasm Component Model spec's Binary.md defines the byte format of canon-async builtins but is silent on how the resulting core funcs surface as imports for the host to satisfy. Today this is a tooling decision: wit-component emits a shim module ("wit-component:shim") with integer-named imports from the empty module namespace (("", "0"), ("", "1"), ...), recording the canon-op identity ("task.return", "stream.read", etc.) in the shim's function-name custom section.
This works for runtimes that compile-everything-statically (wasmtime), because they don't actually use the import names — they compile trampolines directly from the parsed canon entries. It works less well for name-based host-binding runtimes that satisfy imports by (module, name) lookup (interpreters, JIT engines that don't compile to native, runtimes like WACS).
I ran into this while implementing WASIp3 support in WACS
Per the spec, name sections are optional and strippable. Common release-pipeline tools strip them by default (wasm-opt --strip-debug, wasm-tools strip, jco bundling). Once the function-name subsection is stripped from a wit-component-emitted shim:
- The shim's structure (imports from
"" with integer names, funcref table) is recoverable.
- However, the per-shim canon-op identity is not — the position-to-op mapping is wit-component's internal emit order, not derivable from anything else in the binary.
The host runtime can recognize "this is a shim" but cannot bind it. i.e. Embedders that strip names from canon-async-using components have broken hostability for name-resolving runtimes.
This is structurally the same problem the resource handle-table convention solved by standardizing ("[export]<iface>", "[resource.{new,drop,rep}]<name>") import names: the canon-op identity moved into the wire-level import declaration where it can't be stripped without breaking module structure.
Proposal
Standardize a canon-async import convention at the spec level.
Reserved module name + semantic function names.
Reserve a module name (e.g., cm:canon, matching the wasm:js-string builtin namespace pattern) for canon-async imports. Function names match wasmtime's Trampoline::symbol_name() spellings (kebab-case: "task-return", "stream-new", "waitable-set-wait", etc.) with the typeidx encoded for ops that carry one (e.g., "stream-new$5"). The shim module's indirection becomes optional / vestigial. Strip-resistant by construction.
References
The wasm Component Model spec's Binary.md defines the byte format of canon-async builtins but is silent on how the resulting core funcs surface as imports for the host to satisfy. Today this is a tooling decision: wit-component emits a shim module (
"wit-component:shim") with integer-named imports from the empty module namespace (("", "0"),("", "1"), ...), recording the canon-op identity ("task.return","stream.read", etc.) in the shim's function-name custom section.This works for runtimes that compile-everything-statically (wasmtime), because they don't actually use the import names — they compile trampolines directly from the parsed canon entries. It works less well for name-based host-binding runtimes that satisfy imports by
(module, name)lookup (interpreters, JIT engines that don't compile to native, runtimes like WACS).I ran into this while implementing WASIp3 support in WACS
Per the spec, name sections are optional and strippable. Common release-pipeline tools strip them by default (
wasm-opt --strip-debug,wasm-tools strip, jco bundling). Once the function-name subsection is stripped from a wit-component-emitted shim:""with integer names, funcref table) is recoverable.The host runtime can recognize "this is a shim" but cannot bind it. i.e. Embedders that strip names from canon-async-using components have broken hostability for name-resolving runtimes.
This is structurally the same problem the resource handle-table convention solved by standardizing
("[export]<iface>", "[resource.{new,drop,rep}]<name>")import names: the canon-op identity moved into the wire-level import declaration where it can't be stripped without breaking module structure.Proposal
Standardize a canon-async import convention at the spec level.
Reserved module name + semantic function names.
Reserve a module name (e.g.,
cm:canon, matching thewasm:js-stringbuiltin namespace pattern) for canon-async imports. Function names match wasmtime'sTrampoline::symbol_name()spellings (kebab-case:"task-return","stream-new","waitable-set-wait", etc.) with the typeidx encoded for ops that carry one (e.g.,"stream-new$5"). The shim module's indirection becomes optional / vestigial. Strip-resistant by construction.References
crates/wit-component/src/encoding.rs:1147-1218(shim + fixups module construction).crates/environ/src/component/info.rs:1180+(Trampoline::symbol_name()— the closest existing canonical spelling).[resource.{new,drop,rep}]<name>convention already in Binary.md for handle-table intrinsics.