Wado

WEP: WebAssembly Module Import Support

Context

Wado aims to be a "Wasm only" language, maintaining zero abstraction over WebAssembly. To achieve this goal and enable interoperability with the broader Wasm ecosystem, we need a mechanism to import and integrate existing WebAssembly modules directly into Wado programs.

This capability is essential for:

  1. Standard library implementation: Integrating deterministic math functions (see WEP-2026-01-10-deterministic-libm)
  2. Ecosystem integration: Using existing Wasm libraries (cryptography, parsers, etc.)
  3. Multi-language projects: Composing modules written in different languages (Rust, C, AssemblyScript, etc.)

Current State

Wado supports imports from:

Phase 1 of this proposal adds core-wasm asset imports via use _ from "<path>" with { type: "wat" | "wasm" };. Component Model imports are deferred to a later phase.

Phase 1 (delivered)

Phase 1 covers core wasm imports — the minimum needed to migrate lib/core/libm.wat from a special-cased "bundled" path to a regular asset import.

Syntax

Phase 1 supports both wildcard and named imports:

// Named imports — the loader synthesises Wado bindings from the wasm
// module's export signatures, so each name resolves to a normal Wado
// function with the right type.
use { libm_sin, libm_cos } from "./libm.wat" with { type: "wat" };
let s = libm_sin(1.5);

// Wildcard imports — same loading machinery, no symbols are bound. Useful
// when the asset is referenced indirectly through `pub use` re-exports.
use _ from "./helpers.wasm" with { type: "wasm" };

with { type: "wat" } and with { type: "wasm" } are the only forms recognised as wasm-asset imports. Without the with clause, .wat / .wasm paths fall through to the regular import resolution (which rejects non-.wado schemas via the existing Kiln-missing-with diagnostic).

Semantics

  1. The loader fetches the asset bytes (stdlib lookup for core:*.wat, host.load_source for user paths), runs wat::parse_bytes if kind == Wat, and validates the result.
  2. The bytes are cached in LoadResult::wasm_assets keyed by the canonical namespace string wasm:<canonical-path> (e.g. wasm:core:libm.wat). Each WasmAsset also carries the function-export signatures extracted via wasmparser.
  3. The loader synthesises a Wado source string from those signatures — one pub fn name(...) -> ret; declaration per export, each tagged #[canonical("wasm:<path>", "<export>")] — and runs it through the regular parse/bind/desugar pipeline. The resulting AST module is registered under ModuleSource::Wasm { path, kind }, so named imports (use { libm_sin } from "./libm.wat" ...) resolve through the same path as imports of any other Wado module.
  4. BuiltinRegistry::register_wasm_module folds the synthesised declarations into the registry alongside core:builtin's entries, and FunctionRef::builtin_name + DCE's is_builtin_func recognise ModuleSource::Wasm so calls into a wat asset's exports lower through the same TirImport path as core:builtin declarations.
  5. Codegen looks up each asset by namespace (post-DCE), transforms the module to import its memory from env.memory, prunes to the union of exports actually referenced, and embeds it in the resulting component.

Phase 1 limitations (enforced)

Migration of lib/core/libm.wat

The bundled libm path was the motivating use case. Phase 1 retires the previous special "bundled" namespace and the core:builtin libm declarations:

There is no behaviour change at the user level — f64::sin(x) still routes through the same libm export. The wasm-import path is now the only mechanism the codegen uses for both stdlib and user wasm assets.

Phase 2: Component Model imports

Phase 2 — handling external .wasm components that arrive with their own WIT — is the consumer side of WIT Bundling in Component Binaries. The end-to-end design, current state, open questions, and roadmap for that work live in WIT Interoperability.

This WEP retains only the Phase 1 (core wasm asset import) decisions.