Linear Memory¶
Core WebAssembly memory is a contiguous byte buffer owned by the instance. The guest sees offsets inside that buffer; the host must translate every pointer and length before touching memory.
Common patterns:
- exported functions return scalar values directly,
- strings and byte arrays are represented as pointer plus length,
- the guest exports allocation and deallocation helpers,
- the host writes input data into guest memory, calls an exported function, then reads the result range back.
Validate bounds on both reads and writes. Never trust a pointer just because it came from the guest. A valid pointer can still point to a value with the wrong shape for the protocol you expected.
For structured plugin contracts, use WIT and the Component Model. The canonical ABI still uses linear memory underneath, but the generated bindings own the lowering and lifting rules.
Instance Memory Policy¶
WebAssembly memory is measured in 64 KiB pages. MemoryLimits stores the
initial and maximum page counts, with the WebAssembly maximum capped at 65536
pages.
Use WasmMemoryPolicy when instantiating a module. The policy bounds every
imported and defined memory, limits the number of memories, and reserves no more
than the aggregate growth budget:
val memoryPolicy =
WasmMemoryPolicy(
maxBytesPerMemory = 64L * 1024L * 1024L,
maxTotalBytes = 96L * 1024L * 1024L,
maxMemories = 2,
)
val instance =
Instance.builder(module)
.withMemoryPolicy(memoryPolicy)
.build()
Both byte limits must be positive multiples of 64 KiB. At build time, imported
memories are rejected when their current or declared maximum size exceeds the
per-memory or aggregate budget. Defined memories receive effective maximums
that respect both budgets, so subsequent memory.grow operations cannot exceed
them. The same policy is applied by the selected platform engine; it is not
necessary to duplicate it with WasmtimeExecutionConfig.maxMemoryBytes.
withMemoryLimits(...) remains as a compatibility API for a module with at
most one defined memory. It does not account for imported memories or aggregate
growth and rejects modules with multiple defined memories. Prefer
withMemoryPolicy(...) for untrusted or multi-memory modules.
Default Limits¶
Without withMemoryPolicy(...) or withMemoryLimits(...), defined memories
still receive default bounds. Each defined memory is capped at
WasmtimeExecutionConfig.maxMemoryBytes (256 MiB unless configured) and the
instance may declare at most WasmtimeExecutionConfig.maxMemories memories (16
unless configured). A module whose initial memory size exceeds the per-memory cap
fails with UninstantiableException before any memory is allocated, and a larger
declared maximum is reduced to the cap. Imported memories are host-owned and are
not re-checked. Defined tables are checked the same way against
WasmtimeExecutionConfig.maxTableElements (1,000,000 elements unless configured;
WasmtimeUnlimitedResourceLimit disables the check), and array.new /
array.new_default in constant expressions accept at most 10,000,000 elements.
These defaults exist so that a hostile module cannot make the host allocate
gigabytes during Instance.builder(module).build(). They are not a substitute
for an explicit policy: set WasmMemoryPolicy to budget memory for untrusted
plugins, including the aggregate across memories.
The host materializes instance memories and tables while building the instance,
within the limits above, before handing the module to the engine. The memories
reachable through Instance.memory(...) after a successful build are owned by
the active platform engine. On JVM, Android, and iOS this means the linked
Wasmtime backend; on wasmJs this means the browser or Node WebAssembly engine.