Passing Data Between JavaScript and Wasm

11.1 The Core Problem

Core Wasm functions only accept and return numbers (and references). Strings, arrays, and objects must be copied into linear memory, with pointers/lengths passed instead, or handled by binding generators (wasm-bindgen, Embind, jco) or newer features (externref, JS String Builtins, WasmGC, the Component Model).

11.2 Strings (Manual Approach)

(module
  (import "env" "print" (func $print (param i32 i32)))   ;; ptr, len
  (memory (export "memory") 1)
  (data (i32.const 16) "Hello from WAT")
  (global $heap (mut i32) (i32.const 1024))
  (func (export "alloc") (param $n i32) (result i32)
    (local $p i32)
    (local.set $p (global.get $heap))
    (global.set $heap (i32.add (global.get $heap) (local.get $n)))
    (local.get $p))
  (func (export "greet") (call $print (i32.const 16) (i32.const 14))))
let memory;
const decoder = new TextDecoder();
const encoder = new TextEncoder();
const imports = {
  env: { print: (ptr, len) => console.log(decoder.decode(new Uint8Array(memory.buffer, ptr, len))) },
};
const { instance } = await WebAssembly.instantiateStreaming(fetch("str.wasm"), imports);
memory = instance.exports.memory;
instance.exports.greet();

// JS → Wasm: write a string into memory
function passString(str) {
  const bytes = encoder.encode(str);
  const ptr = instance.exports.alloc(bytes.length);
  new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
  return [ptr, bytes.length];
}

11.3 Arrays and Structs

Remember to free what you allocate (export a dealloc/free), and re-create views after memory growth.

// Pass a Float64Array to Wasm
const data = new Float64Array([1.5, 2.5, 3.5]);
const ptr = instance.exports.alloc(data.byteLength);
new Float64Array(memory.buffer, ptr, data.length).set(data);
const avg = instance.exports.average(ptr, data.length);

// Read a C struct { int32 id; float64 value; } at ptr (respect alignment/padding!)
const view = new DataView(memory.buffer);
const id = view.getInt32(ptr, true);          // little-endian
const value = view.getFloat64(ptr + 8, true);

11.4 Higher-Level Options

wasm-bindgen (Rust) Generates JS glue for strings, closures, classes, Promises, and DOM access (via web-sys) Embind (Emscripten) Bind C++ classes/functions/enums to JS

Pass JS objects to Wasm opaquely (no copying) JS String Builtins (Wasm 3.0 era)

Import efficient JS string operations (wasm:js-string) without glue code, used by WasmGC languages

Structs/arrays managed by the host GC (Module 26) Component Model / jco

Interface types (strings, lists, records, variants) via the Canonical ABI (Module 32)

11.5 Minimizing Boundary Overhead

Each JS↔Wasm call is cheap (V8 inlines many wrappers) but data copying isn't. Batch work: pass whole buffers, not per-element calls. Keep hot loops entirely inside Wasm.