wasmtime (erlang_wasmtime v0.1.1)
View SourceRun WebAssembly modules natively with Wasmtime and let them call Erlang.
Wat = ~"(module (func (export \"add\") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add))",
{ok, Mod} = wasmtime:compile({wat, Wat}),
{ok, Inst} = wasmtime:instantiate(Mod),
{ok, [3]} = wasmtime:call(Inst, ~"add", [1, 2]).Nothing raises for guest failures: compile, link, trap, WASI and host errors
all come back as {error, Map} with class, kind and message keys.
Each instance owns one OS thread and one Wasmtime store. A call runs on that
thread while the calling process waits in receive; a host function (an
import backed by an Erlang fun) runs in the calling process. Only one call runs
on an instance at a time; concurrent callers are queued.
Summary
Types
Options for compile/2, validate/2 and deserialize/2.
Every failure has a class, a machine-readable kind and a message. Non-zero
WASI exits also carry status; traps carry the wasm frames in trace,
innermost first.
What the linked Wasmtime library can do. A runtime-only build has no
compiler (compile/1 and serialize/1 answer kind => unavailable) and
may have no wat or wasi. See building.md, "Runtime-only builds".
One wasm frame of a trap: the function's index and byte offset, and its names when the module has them.
A host function. Returns the results the guest expects, or {error, Reason} which traps the guest.
A WebAssembly proposal that compile_options() can turn on or off.
A reference the guest handed out: a funcref, an externref or a GC value
(struct, array, any other anyref). Opaque; ref_info/1 says which.
The object stays alive while the term does; drop the term to let the
guest's collector reclaim it. A ref belongs to one instance.
A WebAssembly value. nan, infinity and neg_infinity stand for the floats
Erlang cannot represent; a v128 is a 16-byte binary.
WASI configuration. Nothing is granted by default.
Functions
Read element Index of an array.
The length of an array the guest created.
Write element Index of an array.
Equivalent to await(Inst, Ref, infinity).
Wait for the result of call_async/3, serving host calls meanwhile.
Equivalent to call(Inst, Name, Args, #{}).
Call an exported function and wait for its results.
Start a call and return at once with a reference for await/2,3.
Equivalent to call_ref(Inst, Ref, Args, #{}).
Call a funcref the instance handed out (from a table, a global, a result
or a host function argument), with the options of call/4.
End the guest's input. What is queued is still delivered; after that stdin
reads return end of file and erlang.recv returns -1. Idempotent.
Compile a module from its binary form, or from text as {wat, Text}.
Compile with compile_options/0.
Load a module produced by serialize/1.
Load a module produced by serialize/1 onto the engine for these
compile_options/0. Needed for fuel => true (deserialize/1 covers
the defaults and the fuel engine on its own); the loaded module then
belongs to that engine, which module_options/1 reports.
List what the module exports, as {Name, Kind}.
Wrap an Erlang term as an externref the guest can hold and hand back.
The term an externref/2 reference wraps.
What the linked Wasmtime library can do; see features/0.
Fuel left after the last call, for a module compiled with fuel => true.
Run the instance's garbage collector now. Objects no longer reachable from
the guest or from a ref() are reclaimed and externref/2 terms released.
Fails with kind => busy while the guest runs.
Read an exported global; a reference-typed one gives a ref(), null or {i31, N}.
Write an exported mutable global; kind => immutable for a constant one.
Serve one host call message in a host process.
List what the module imports, as {Module, Name, Kind}.
Equivalent to instantiate(Mod, #{}).
Instantiate a module in its own store and thread.
Interrupt the call running on the instance, from any process.
Size of the default memory as {Pages, Bytes}.
Size of the exported memory called Name as {Pages, Bytes}.
The compile_options/0 a module was compiled or deserialized with.
Read Len bytes at Ptr from the instance's default memory: the export
named memory, or the first exported memory.
Same as read_memory/3 on the exported memory called Name.
Take what the captured stdout and stderr hold and empty them.
The reference carried by every message this instance sends:
{wasmtime_stream, Ref, Kind, Bytes} and {wasmtime_host_call, Ref, ...}.
A process serving several instances matches on it.
What a reference is: #{kind => externref | funcref | struct | array | anyref, instance => Ref} where instance is the ref/1 of the instance it belongs to.
Queue one message for the guest.
Serialize a compiled module into Wasmtime's precompiled form.
Read field Index of a struct the guest created.
Write field Index of a struct; i8 and i16 fields take integers.
Read an element of an exported table: a ref() or null.
Grow an exported table by Delta null elements; returns the previous size.
Grow an exported table by Delta elements holding Init; returns the previous size.
Write an element of an exported table: a ref() of the table's type, or null.
Number of elements in an exported table.
Decode and validate a binary module without compiling it.
Validate against compile_options/0: with proposals disabled, a module using one is refused.
Version of the linked Wasmtime library.
Write Data at Ptr in the default memory. Same rules as read_memory/3.
Same as write_memory/3 on the exported memory called Name.
Types
-opaque call_ref()
-type compile_options() :: #{fuel => boolean(), opt_level => none | speed | speed_and_size, proposals => #{proposal() => boolean()}}.
Options for compile/2, validate/2 and deserialize/2.
fuel: compile with fuel metering (seecall/4).opt_level: Cranelift's optimization level,speedby default;nonecompiles fastest,speed_and_sizetrades some speed for smaller code.proposals: WebAssembly proposals to enable or disable on top of Wasmtime's defaults. Disabling one makes validation refuse modules that use it:#{simd => false, threads => false}for a plugin format that must not need them.
Of these, only fuel is part of a precompiled module's compatibility
check: give it again to deserialize/2 (or rely on deserialize/1, which
tries the fuel engine too). The optimization level and disabled proposals
need nothing at load time. Each distinct option set is one Wasmtime engine,
created on first use and kept; at most 32 exist per VM.
-type error() :: {error, #{class := compile | link | call | trap | host | wasi | memory | global | table | exit, kind := atom(), message := binary(), status => integer(), trace => [frame()]}}.
Every failure has a class, a machine-readable kind and a message. Non-zero
WASI exits also carry status; traps carry the wasm frames in trace,
innermost first.
What the linked Wasmtime library can do. A runtime-only build has no
compiler (compile/1 and serialize/1 answer kind => unavailable) and
may have no wat or wasi. See building.md, "Runtime-only builds".
-type frame() :: #{func_index := non_neg_integer(), func_offset := non_neg_integer(), func_name := binary() | undefined, module_name := binary() | undefined}.
One wasm frame of a trap: the function's index and byte offset, and its names when the module has them.
A host function. Returns the results the guest expects, or {error, Reason} which traps the guest.
-opaque instance()
-opaque module_ref()
-type options() :: #{imports => #{{binary(), binary()} => host_fun()}, wasi => wasi_options(), memory_limit => pos_integer() | unlimited, max_tables => pos_integer() | unlimited, max_table_elements => pos_integer() | unlimited, max_instances => pos_integer() | unlimited, host_timeout => timeout(), host => pid(), stream => pid(), inbox_limit => pos_integer()}.
-type proposal() ::
simd | relaxed_simd | relaxed_simd_deterministic | bulk_memory | multi_value | multi_memory |
memory64 | tail_call | wide_arithmetic | custom_page_sizes | threads | reference_types |
function_references | gc | exceptions.
A WebAssembly proposal that compile_options() can turn on or off.
-opaque ref()
A reference the guest handed out: a funcref, an externref or a GC value
(struct, array, any other anyref). Opaque; ref_info/1 says which.
The object stays alive while the term does; drop the term to let the
guest's collector reclaim it. A ref belongs to one instance.
-type value() :: integer() | float() | nan | infinity | neg_infinity | <<_:128>> | null | ref() | {i31, integer()}.
A WebAssembly value. nan, infinity and neg_infinity stand for the floats
Erlang cannot represent; a v128 is a 16-byte binary.
-type wasi_options() :: #{args => inherit | [iodata()], env => inherit | [{iodata(), iodata()}], dirs => [{Guest :: iodata(), Host :: iodata(), read | write}], stdin => none | inherit | stream | {file, iodata()} | {binary, iodata()}, stdout => none | inherit | stream | {file, iodata()} | capture, stderr => none | inherit | stream | {file, iodata()} | capture, output_limit => pos_integer()}.
WASI configuration. Nothing is granted by default.
args,env: what the guest sees, orinheritfor the VM's own.dirs: preopened directories, read-only unlesswrite.stdin: end of file by default; a file, the VM's stdin, bytes, orstream: whatsend/2queues, as the guest reads it.stdout,stderr: discarded by default; a file, the VM's own,captureinto memory, read withread_output/1, orstream: every write goes to thestreamprocess as{wasmtime_stream, Ref, stdout | stderr, Bytes}at once.output_limit: bytes kept per captured stream (default 16 MB); the guest never sees a short write,read_output/1reports what was dropped.
Functions
-spec array_get(ref(), non_neg_integer()) -> {ok, value()} | error().
Read element Index of an array.
-spec array_len(ref()) -> {ok, non_neg_integer()} | error().
The length of an array the guest created.
-spec array_set(ref(), non_neg_integer(), value()) -> ok | error().
Write element Index of an array.
Equivalent to await(Inst, Ref, infinity).
Wait for the result of call_async/3, serving host calls meanwhile.
Must be called by the process that started the call. With a timeout the
call is cancelled like in call/4.
Equivalent to call(Inst, Name, Args, #{}).
-spec call(instance(), iodata(), [value()], #{timeout => timeout(), fuel => non_neg_integer()}) -> {ok, [value()]} | error().
Call an exported function and wait for its results.
Host functions the guest calls run in this process, so it must be able to
receive messages until the call returns. With timeout the guest is
interrupted when the time is up and {error, #{kind := timeout}} is returned.
With fuel the call may execute that many units of fuel (about one per
instruction) before it traps with kind := out_of_fuel; the module must have
been compiled with fuel => true.
timeout covers guest execution and the wait for it. It cannot fire while
this process is inside one of its own host functions; host_timeout (an
instantiate option) is what bounds the guest there.
Start a call and return at once with a reference for await/2,3.
The call runs on the instance thread while this process does other work.
Host functions are still served by this process, and only while it is
inside await/2,3 (or by the host process when one was given), so a
guest that calls back before await waits until then, within
host_timeout.
Equivalent to call_ref(Inst, Ref, Args, #{}).
-spec call_ref(instance(), ref(), [value()], #{timeout => timeout(), fuel => non_neg_integer()}) -> {ok, [value()]} | error().
Call a funcref the instance handed out (from a table, a global, a result
or a host function argument), with the options of call/4.
-spec close(instance()) -> ok.
End the guest's input. What is queued is still delivered; after that stdin
reads return end of file and erlang.recv returns -1. Idempotent.
-spec compile(binary() | {wat, iodata()}) -> {ok, module_ref()} | error().
Compile a module from its binary form, or from text as {wat, Text}.
Compilation runs on a dirty CPU scheduler. The result is immutable and can be instantiated any number of times, from any process.
A runtime-only build has no compiler: this returns
{error, #{kind := unavailable}} and modules come from deserialize/1.
-spec compile(binary() | {wat, iodata()}, compile_options()) -> {ok, module_ref()} | error().
Compile with compile_options/0.
-spec deserialize(binary()) -> {ok, module_ref()} | error().
Load a module produced by serialize/1.
Wasmtime verifies its own version and the CPU features the code was built
for, not the machine code itself. Only bytes that came from serialize/1,
from a source you trust, may be passed here; a .wasm file goes to
compile/1.
-spec deserialize(binary(), compile_options()) -> {ok, module_ref()} | error().
Load a module produced by serialize/1 onto the engine for these
compile_options/0. Needed for fuel => true (deserialize/1 covers
the defaults and the fuel engine on its own); the loaded module then
belongs to that engine, which module_options/1 reports.
-spec exports(module_ref()) -> [{binary(), func | global | table | memory | tag}].
List what the module exports, as {Name, Kind}.
Wrap an Erlang term as an externref the guest can hold and hand back.
The term is copied; externref_data/1 copies it out again. The object lives
while any ref() to it or the guest reaches it. Fails with
kind => gc_heap_full when Wasmtime cannot allocate; gc/1 may make room.
The term an externref/2 reference wraps.
-spec features() -> features().
What the linked Wasmtime library can do; see features/0.
-spec fuel_remaining(instance()) -> {ok, non_neg_integer()} | error().
Fuel left after the last call, for a module compiled with fuel => true.
Run the instance's garbage collector now. Objects no longer reachable from
the guest or from a ref() are reclaimed and externref/2 terms released.
Fails with kind => busy while the guest runs.
Read an exported global; a reference-typed one gives a ref(), null or {i31, N}.
Write an exported mutable global; kind => immutable for a constant one.
Serve one host call message in a host process.
Call it with every {wasmtime_host_call, Ref, HostId, Key, Args} message the
process receives for Inst; it runs the import fun and replies to the guest.
Returns ignore for a message that is not a host call of this instance, so
it can sit in a receive alongside other messages.
-spec imports(module_ref()) -> [{binary(), binary(), func | global | table | memory | tag}].
List what the module imports, as {Module, Name, Kind}.
-spec instantiate(module_ref()) -> {ok, instance()} | error().
Equivalent to instantiate(Mod, #{}).
-spec instantiate(module_ref(), options()) -> {ok, instance()} | error().
Instantiate a module in its own store and thread.
Nothing is granted by default: no host functions, no WASI, 256 MB of linear memory at most. Options:
imports: map from{Module, Name}to a host fun. An import the module needs and the map does not provide fails withclass => link.wasi: enable WASI preview 1. Seewasi_options/0; withoutdirsthe guest has no filesystem, withoutstdout/stderrits output is discarded. A build without WASI (seefeatures/0) answerskind => unavailable.memory_limit,max_tables,max_table_elements,max_instances: per-store caps enforced by Wasmtime.unlimitedremoves a cap.host_timeout: how long a host function may run before the guest traps (default 30 s).host: a process that serves host calls instead of the caller. It receives{wasmtime_host_call, Ref, HostId, Key, Args}messages and answers them withhandle_host_call/2. Host calls made by the module's start section duringinstantiate/2still go to the caller.
The module's start section runs during instantiation and may call host
functions; a trap there is reported as class => trap. A WASI _start is an
ordinary export and is not run here: call it.
-spec interrupt(instance()) -> ok | not_running.
Interrupt the call running on the instance, from any process.
The call fails with {error, #{class := trap, kind := interrupt}} within one
epoch tick (10 ms), or at once if it is waiting inside a host function.
Returns not_running when the instance is idle.
-spec memory_size(instance()) -> {ok, {non_neg_integer(), non_neg_integer()}} | error().
Size of the default memory as {Pages, Bytes}.
-spec memory_size(instance(), default | iodata()) -> {ok, {non_neg_integer(), non_neg_integer()}} | error().
Size of the exported memory called Name as {Pages, Bytes}.
-spec module_options(module_ref()) -> compile_options().
The compile_options/0 a module was compiled or deserialized with.
-spec read_memory(instance(), non_neg_integer(), non_neg_integer()) -> {ok, binary()} | error().
Read Len bytes at Ptr from the instance's default memory: the export
named memory, or the first exported memory.
Works while the instance is idle or while a host function runs (pass the
instance the host fun received). Fails with kind => busy if the guest is
executing.
-spec read_memory(instance(), default | iodata(), non_neg_integer(), non_neg_integer()) -> {ok, binary()} | error().
Same as read_memory/3 on the exported memory called Name.
-spec read_output(instance()) -> {ok, {binary(), binary(), {non_neg_integer(), non_neg_integer()}}}.
Take what the captured stdout and stderr hold and empty them.
Returns {ok, {Stdout, Stderr, {DroppedOut, DroppedErr}}}; the counters say
how many bytes went past output_limit. Works while the guest runs, so a
long-running guest's output can be drained from another process.
The reference carried by every message this instance sends:
{wasmtime_stream, Ref, Kind, Bytes} and {wasmtime_host_call, Ref, ...}.
A process serving several instances matches on it.
-spec ref_info(ref()) -> #{kind := externref | funcref | struct | array | anyref, instance := reference()}.
What a reference is: #{kind => externref | funcref | struct | array | anyref, instance => Ref} where instance is the ref/1 of the instance it belongs to.
Queue one message for the guest.
The guest reads it through stdin => stream (as bytes, without message
boundaries) or the erlang.recv import (one whole message). Never blocks:
once inbox_limit bytes (default 16 MB) are queued and unread it returns
{error, #{kind := inbox_full}} and the sender retries later. After
close/1 it returns {error, #{kind := closed}}.
-spec serialize(module_ref()) -> {ok, binary()} | error().
Serialize a compiled module into Wasmtime's precompiled form.
The result loads with deserialize/1 without compiling, on the same Wasmtime
version and a CPU with the same features. Use it to compile once at build
time and ship the output, or to keep a cache.
-spec struct_get(ref(), non_neg_integer()) -> {ok, value()} | error().
Read field Index of a struct the guest created.
-spec struct_set(ref(), non_neg_integer(), value()) -> ok | error().
Write field Index of a struct; i8 and i16 fields take integers.
-spec table_get(instance(), iodata(), non_neg_integer()) -> {ok, value()} | error().
Read an element of an exported table: a ref() or null.
-spec table_grow(instance(), iodata(), non_neg_integer()) -> {ok, non_neg_integer()} | error().
Grow an exported table by Delta null elements; returns the previous size.
-spec table_grow(instance(), iodata(), non_neg_integer(), value()) -> {ok, non_neg_integer()} | error().
Grow an exported table by Delta elements holding Init; returns the previous size.
-spec table_set(instance(), iodata(), non_neg_integer(), value()) -> ok | error().
Write an element of an exported table: a ref() of the table's type, or null.
-spec table_size(instance(), iodata()) -> {ok, non_neg_integer()} | error().
Number of elements in an exported table.
Decode and validate a binary module without compiling it.
Cheaper than compile/1 when the question is only whether the bytes are a
well-formed module; the errors have the same shape.
-spec validate(binary(), compile_options()) -> ok | error().
Validate against compile_options/0: with proposals disabled, a module using one is refused.
-spec version() -> binary().
Version of the linked Wasmtime library.
-spec write_memory(instance(), non_neg_integer(), iodata()) -> ok | error().
Write Data at Ptr in the default memory. Same rules as read_memory/3.
-spec write_memory(instance(), default | iodata(), non_neg_integer(), iodata()) -> ok | error().
Same as write_memory/3 on the exported memory called Name.