Changelog
View Source0.2.1
Changed
array.copy and array.fill do less work per element. Over ten thousand
elements, a copied element costs 7.0 reductions where it cost 19.9, and a
filled one 6.0 where it cost 7.8.
array.copy built three lists per copy and read the array's length from the
object table once per element, re-answering what the range check had already
answered. The loop lives in wasm_heap now, beside the accounting it has to go
through: it reads the source array's default once rather than per element, and
decides an overlapping copy by direction instead of taking a snapshot of the
source. A partial array.fill counts down rather than walking a list of the
indices it is about to use.
Behaviour does not change, including the rule that an overlapping array.copy
behaves as though an intermediate copy were taken.
0.2.0
Changed
The supervision tree is one supervisor per subsystem. Five servers under one
intensity => 5, period => 10 shared a budget, so losing the module cache
repeatedly could take the engine, the keeper and the code slots with it, and
the tables went too. Each subsystem now has its own supervisor and its own
10 in 60, and wasm_store owns the long-lived tables.
A wrong-typed call argument answers {link, argument_type} where it used to
answer {malformed, internal}. malformed is the decode class and an argument
is not a decode concern; the kind is new, the class has changed, and anything
matching on the old pair needs updating.
max_memory_pagesnow covers garbage-collected objects as well as linear memory. A workload under a tight ceiling that allocates structs or arrays can be refused where it was not. The node page budget widens the same way, somemory.growcan return -1 because a guest filled the object store. Both were unbounded before: a guest filling a twenty-million element array took 1.8 GB withmax_heap_wordsset,process_flag(max_heap_size, ...)set on the process running it, andpages_in_usereading zero throughout, because a struct or an array is a row in ETS and ETS is not process heap.max_heap_wordsis documented as what it always was: a ceiling on terms on the caller's own heap, applied by the caller. It never covered guest memory of either kind, and no longer reads as though it might.
Fixed
A store of few large objects was never collected. Every rule in
wasm_heapcounted objects:major_due/1compared a row count against a floor of 4096, so a workload replacing one large array per call never got a major collection, and a minor one leaves the old generation alone by design. Four rounds of a fifty thousand element array left all four, sixteen megabytes, with one reachable. Bothmajor_due/1andshould_collect/1now read bytes as well as rows, with agc_min_major_pagesfloor. A workload that allocates heavily and keeps nothing does about 14% more work and stops leaking; one with a stable live set is unaffected.atomic.fencewas rejected as invalid. The decoder and the interpreter both knew it and the validator had no clause, so every module carrying a fence failed to load. The specification suite does not exercise it.A tree death leaked the node page budget permanently. The counter lives in
persistent_termand outlives the supervision tree; the registry that says who holds those pages does not. Pages charged when the tree died could never be released, and it accumulated across application restarts.wasm_keepernow reconciles the two when it starts.A limits map that could not mean what it said was ignored.
#{max_depth => lots}failed open, because every integer sorts before every atom, so a guest could recurse a million frames under a ceiling the embedder believed it had set.wasm_limits:validate/1existed and nothing called it.The compiled tier computed on ill-typed arguments.
wasm_execchecks arity and the tier never reaches it, so the same call answered differently depending on whether the function was hot; and a float passed for ani32was rejected by the interpreter and hadi32.addrun on it by the tier. Both are checked once now, before either engine is chosen.
0.1.1
wasm:compile/1 takes the text format:
{ok, M} = wasm:compile({wat, ~"(module (func (export \"f\") (result i32) i32.const 7))"}).load/1 still takes the binary format only: the cache is keyed on a content
hash, and a module built from text takes a fresh identity every time.
Fixed
Seven lifecycle defects found by an audit of the previous release.
- A reader killed inside
wasm_heap:lease/1orunlease/2stranded a count nothing could give back, and the object store never collected again. - A keeper restart dropped every per-instance memory ceiling, so an instance
created with
max_memory_pagesgrew past it. - A process calling instances it does not destroy kept one table array and one compiled entry per instance, without bound.
sock_send_toleaked a socket when the send failed, and another when the guest's output pointer was out of bounds.atomic.waitreported a wakeup that never happened when the notifier died between claiming a waiter and sending to it.wasm_engine's per-instance limits table had no callers and is gone.
0.1.0
First public release. The versions before it were developed in a private repository and are not published; this is the whole runtime as one release.
The Hex package is erlang_wasm; the OTP application inside it is wasm.
{deps, [{wasm, {pkg, erlang_wasm}}]}.What it does
A WebAssembly runtime written in Erlang/OTP. Decoding, validation, instantiation, execution, linear memory and WASI preview 1 are implemented in Erlang. The only native code is an optional file NIF that closes a time-of-check-to-time-of-use window in WASI path resolution, and the runtime falls back to a pure Erlang resolver when it is absent.
Proposals
WebAssembly 1.0 core, bulk memory, reference types, multi-value, multiple memories, memory64, SIMD, relaxed SIMD, tail calls, typed function references, exception handling, garbage collection, threads and shared memories, sign extension, and saturating float-to-int conversion.
Both formats are read: the binary format, and the text format as .wat modules
and .wast scripts.
WASI
Forty-four preview 1 syscalls. Directories and sockets are granted by naming
what may be reached, with nothing reachable by default. See docs/wasi.md and
docs/security.md.
The compiled tier
Hot functions are lowered to Core Erlang, compiled and loaded into a fixed pool
of sixteen pre-interned module names, so no atom is ever derived from a guest's
bytes. Off by default; see docs/compiled-tier.md for when it pays and when it
does not.
Errors and limits
Nothing raises. A malformed binary, an ill-typed module, a trap and a resource
limit all come back as {error, Error} carrying a class, a machine-readable
kind, the specification's message text and context. Memory pages, tables and
globals are held by holder tokens whose owning process's death releases them,
so a killed worker cannot leak a page.
Conformance
64,774 core specification assertions across 215 suites, with an empty skip
baseline, and 65,481 of them replayed through generated code. Seventy-two of
the 72 wasi-testsuite cases pass with the NIF, 68 without it. Neither suite is
vendored; docs/features.md says how to clone them.