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:
- Standard library implementation: Integrating deterministic math functions (see WEP-2026-01-10-deterministic-libm)
- Ecosystem integration: Using existing Wasm libraries (cryptography, parsers, etc.)
- Multi-language projects: Composing modules written in different languages (Rust, C, AssemblyScript, etc.)
Current State
Wado supports imports from:
.wadomodules (local files, integrated at IR level during compilation)core:*namespace (core library, written in Wado)wasi:*namespace (WASI interfaces, mapped to WIT)https:URLs (remote modules)
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
- The loader fetches the asset bytes (stdlib lookup for
core:*.wat,host.load_sourcefor user paths), runswat::parse_bytesifkind == Wat, and validates the result. - The bytes are cached in
LoadResult::wasm_assetskeyed by the canonical namespace stringwasm:<canonical-path>(e.g.wasm:core:libm.wat). EachWasmAssetalso carries the function-export signatures extracted viawasmparser. - 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 underModuleSource::Wasm { path, kind }, so named imports (use { libm_sin } from "./libm.wat" ...) resolve through the same path as imports of any other Wado module. BuiltinRegistry::register_wasm_modulefolds the synthesised declarations into the registry alongsidecore:builtin's entries, andFunctionRef::builtin_name+ DCE'sis_builtin_funcrecogniseModuleSource::Wasmso calls into a wat asset's exports lower through the sameTirImportpath ascore:builtindeclarations.- 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)
- Imports. A wasm asset may import only
env.memory. Any other import (env.foo, multiple memories, non-memory imports) is a compile-time error. - Start sections. Wasm assets may not contain a
startsection. (Side-effecting init at instantiation time is not supported in Phase 1.) - Single memory. At most one memory definition.
- Export shape. Each function export must use only the core wasm subset
{i32, i64, f32, f64, v128}for parameters and at most one result. Reference-typed parameters and multi-return are rejected up-front with a pointed diagnostic. - Re-exported imports. A wasm export that aliases an imported function is rejected; only module-defined functions can be exposed to Wado.
- Origin detection (
@custom "wado-compiler"marker). Not used in Phase 1; all assets go through the core-linking path. - WIT type extraction. Not used in Phase 1; types come from
#[canonical(...)]declarations on Wado-side functions, not from the wasm module itself.
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:
lib/builtins/wado-bundled-libm.wat→lib/core/libm.watwado-compiler/src/bundled.rs→ folded intostdlib.rs(get_stdlib_wasm_assetreturns the bytes by canonical path)core:prelude/primitive.wadoname-imports the libm exports directly from../libm.wat, renaming each onto its Wado-side identifier (libm_sin as f64_sin, …,libm_log as f64_ln, etc.) at the import site, and calls them as ordinary functions in place of the previousbuiltin::f64_sin(x)style. There is no intermediate stdlib module —primitive.wadois the only consumer, so acore:libmre-exporter would be pure indirection.- The libm function declarations have been removed from
lib/core/builtin.wado. embed_bundled_modulesincodegen/component.rsis generalised intoembed_imported_wasm_modules, driven by post-DCE imports grouped by namespace.
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.
