RFC-002 — Wasm Component Model wrapper¶
Status: Draft (v0.9 spec-freeze prep).
Tracks amendments: A47 (Component Model deferred, core modules
ship in v1.0), A97 (mighty:web/dom interface, canonical-ABI return-
area bridge OPEN).
Target release: v1.1.
Owner: unassigned — design owner needed before promotion.
Implementation Status¶
NOT YET SHIPPED. Forward-looking RFC.
Adjacent v0.13..v0.23 work that does not moot this RFC but narrows its scope:
- v0.13..v0.19 — WASI Preview 2 + user-supplied WIT
(
[wit]manifest section; default forwasm32-wasi; everystd.*lowering goes through versioned P2 imports). Preview1 adapter is opt-in viaAdapterEmbed::new(kind, bytes); the vendored bytes were removed in v0.19. - v0.23 —
mty:web/canvas@0.1+mty:web/input@0.1WIT pair shipped, withWIT_IMPORT_*/WIT_EXPORT_*drift-guards.wasm32-webembedded-core-module regression harness verified the core module IS embedded at byte offset 189 (the long-standing "header-only component" suspicion was wrong).
The remaining v1.0 OPEN items the RFC must close are unchanged:
wit-componentend-to-end wrap (currently rawwasm-encodercore modules).- Canonical-ABI return bridge for
string/option<string>returns frommty:web/dom(get-text,querystill returnu32handles in v1.0). - preview2 /
wasi-cli-compatible host adapter for non-browser execution.
The window opened 2026-05-26 (close 2026-07-25, 60-day track) is substantive: reviewers can still drive the wit-component shape.
Cross-references:
v1.0-rc.md§22.3, §22.4, §24.5 — current Wasm-component contract.RFC_DASHBOARD.md— live window status.
Summary¶
Promote the v1.0 core-Wasm-module output of mty build --target
wasm32-web (and --target wasm32-wasi) to a real Wasm Component
Model binary. Use wit-component to wrap the core module against
the curated WIT interfaces under mty:caps/* and mty:web/*,
implement the canonical ABI's return-area protocol for string and
option<string> returns, and ship a preview2 / wasi-cli-compatible
host adapter for non-browser execution.
This RFC subsumes the OPEN sub-task in A97 (canonical-ABI bridge for
get-text and query).
Motivation¶
The v1.0 contract emits a core Wasm module with capability imports
declared as plain function imports under a mighty module namespace.
Hosts must provide ad-hoc bridge code; the WIT contracts under
mty:caps/* exist in docs/internals/ but are not enforced at link
time. Three consequences:
- No type-checked host binding. A host that misspells an import
gets a runtime
WebAssembly.LinkErrorrather than a build-time diagnostic. - No language portability. A Mighty Wasm artefact cannot be
consumed by other component-model-aware toolchains (e.g. Spin,
wasmtime's
--component) without a hand-written shim. - Canonical ABI gap for strings.
mighty:web/dom.get-textand.queryreturnoption<string>in the WIT sketch but emitu32handles in v1.0 because the core ABI cannot return a variable- length result. Browsers consume handles via a separateread-string-handle(u32) -> stringshim.
Adopting the Component Model closes all three.
Detailed design¶
Pipeline¶
MtyIR → core .wasm (wasm-encoder, existing)
→ wit-component embed → .wasm with metadata
→ wit-component new → component .wasm
mty build --target wasm32-web (and --target wasm32-wasi) gains a
two-stage post-pass:
- Embed WIT metadata. The crate's WIT files (under
crates/mty-codegen-wasm/wit/mty/caps/*.witandmty/web/*.wit) are embedded in the core module viawit_component::embed_component_metadata. - Build component.
wit_component::ComponentEncoder::new() .module(&core_bytes)?.encode()?produces the final component.
Behind a --core-only opt-out flag for hosts that still need the
v1.0 shape (e.g. existing browser embeds that depend on the
mighty.set_text_handle import name).
WIT contract authority¶
The WIT files in crates/mty-codegen-wasm/wit/ become normative
spec artefacts. Promote them to docs/spec/wit/v1.1/ and pin
their versions:
package mty:caps@1.1.0;
interface fs {
variant io-err {
not-found(string),
denied(string),
forbidden(string),
}
read: func(path: string) -> result<list<u8>, io-err>;
write: func(path: string, bytes: list<u8>) -> result<_, io-err>;
exists: func(path: string) -> bool;
list-dir: func(path: string) -> result<list<string>, io-err>;
}
interface net { ... }
interface clock { ... }
package mty:web@1.1.0;
interface dom {
set-text: func(id: string, text: string);
get-text: func(id: string) -> option<string>; // RFC-002: real string
on-click: func(id: string, callback-tag: string);
query: func(selector: string) -> option<string>; // RFC-002: real string
}
The v0.4 backwards-compatibility imports
(get-element-by-id: func(id: string) -> option<u32> etc.) remain in
a separate mty:web-compat@1.0.0 package for one major cycle, then
drop in v2.0.
Canonical ABI return-area bridge¶
The Component Model canonical ABI returns variable-length values through a caller-supplied return area: the callee writes the result and the lifted import unpacks it. For Mighty:
- The MtyIR
Call { func: BuiltinId::DomOp("get_text"), .. }lowers in the wasm32-web backend to: - allocate a 12-byte (ptr + len + discriminant) return area on the
stack via the canonical-ABI
cabi_reallocentry, - emit a call to the lifted
mty:web/dom.get-textimport passing(id_ptr, id_len, ret_area), - synthesise an
Option[Str]MtyIR value from the return-area contents. - The runtime ABI bridge in
sdust_runtime::codegen_abigains amighty_alloc_return_area(size, align) -> *mut u8symbol that wraps the active arena's bump allocator (A50).
The lifted Rust binding is auto-generated by wit-bindgen.
Host adapter¶
A new crate mty-wasm-host ships:
- A
wasmtime::component::Linker-based host that wiresmty:caps/fs,mty:caps/net,mty:caps/clockagainst the existingFsCap/NetCap/ clock surface from the runtime, sharing the same capability-narrowing semantics (A23, A100). - A WASI preview2 adapter that maps
wasi:cli/run.runonto a Mightyfn main(...) -> Unit!RunErr. - A test harness that runs the conformance corpus under
wasmtime --component.
Browser embedding¶
The v1.0 browser embed receives a parallel update:
wasm_bindgenbecomesjco transpile(Bytecode Alliance's JavaScript transpiler for components).- The DOM cap implementation moves from hand-written JS into
crates/mty-codegen-wasm/host-js/dom.tsand is compiled by jco. - The
set_text_handle(u32) -> ()v0.4 shim is preserved behind alegacy-handleopt-in for in-flight web apps.
Drawbacks¶
- Toolchain weight.
wit-component+wasmtime::componentadd significant build-time dependencies. Behind awasm-componentcargo feature, default on for wasm targets only. - Browser-side jco churn. Browser hosts that integrate Mighty must adopt jco's runtime shape. The legacy shim limits the blast radius for the v1.0 → v1.1 transition.
- Increased artefact size. A component is typically 5–15% larger than the equivalent core module due to canonical-ABI glue. Acceptable for the type-safety win; offset somewhat by enabling cross-tool optimisations.
Alternatives considered¶
- Keep core modules forever. Defeats the interop motivation.
- Hand-rolled WIT bindings (no
wit-component). Saves the dependency cost but every WIT change becomes a hand-edit chore; regressions from the v0.5 dogfood pass discouraged this. - Component Model 0.3 (preview). More ergonomic resource handles but unstable upstream. Re-evaluate for v2.0.
Unresolved questions¶
- Should
mighty:web/dommigrate to use Component Model resources (e.g.resource element { get-text: func() -> option<string>; }) instead of pass-by-id strings? Cleaner API, but a bigger break from the v1.0 surface. Punt to RFC-002.b. - How do imported components compose with the Mighty capability model?
e.g. a third-party component that imports
mty:caps/net— should the importer'ssandboxblock be able to narrow it? Initial answer: yes, via the samecap.host(...)algebra (A23), but the import binding needs to thread the narrowing into the canonical ABI call. - Stability of
mty:caps/*1.1 vs 1.x evolution: do we follow WIT's major-version pinning strictly, or allow additive minor bumps?
Adoption plan¶
- v1.1-alpha.1:
--target wasm32-componentopt-in flag; legacywasm32-webandwasm32-wasikeep emitting core modules. - v1.1-alpha.2: WIT files promoted to
docs/spec/wit/v1.1/;mty-wasm-hostcrate ships. - v1.1-beta: default flip —
wasm32-web/wasm32-wasiemit components by default;--core-onlyopts back into v1.0 shape. - v1.1.0: A47 reclassified FROZEN; A97 closes (
option<string>returns now go through canonical ABI). - v2.0: drop
--core-only; dropmty:web-compat@1.0.0shim.
A 60-day public comment window (longer than RFC-001 because of the toolchain blast radius) opens with v1.1-alpha.1.