Skip to content

canon-async builtin import-name convention does not survive name-section stripping #654

@kelnishi

Description

@kelnishi

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions