Getting started
View SourceThis guide takes you from a .wasm file to a call from Erlang. You need it the
first time you embed a module; the other guides cover host functions, WASI and
the build.
Add the dependency
%% rebar.config
{deps, [{erlang_wasmtime, "0.1.1"}]}.Run rebar3 compile. The first build downloads the Wasmtime C API and builds
priv/wasmtime_nif.so.
Compile a module
From a binary:
{ok, Bin} = file:read_file("plugin.wasm"),
{ok, Mod} = wasmtime:compile(Bin).From text:
{ok, Mod} = wasmtime:compile({wat, ~"""
(module
(memory (export "memory") 1)
(func (export "add") (param i32 i32) (result i32)
local.get 0 local.get 1 i32.add))
"""}).compile/1 runs on a dirty CPU scheduler. The module is immutable: compile once,
instantiate as often as you like.
Inspect it
[] = wasmtime:imports(Mod),
[{~"memory", memory}, {~"add", func}] = wasmtime:exports(Mod).Instantiate and call
{ok, Inst} = wasmtime:instantiate(Mod),
{ok, [3]} = wasmtime:call(Inst, ~"add", [1, 2]).Arguments and results follow the function's type: i32/i64 as integers,
f32/f64 as floats (with nan, infinity, neg_infinity for what an Erlang
float cannot hold), v128 as a 16-byte binary.
Handle failure
{error, #{class := trap, kind := integer_division_by_zero}} =
wasmtime:call(Inst, ~"div", [1, 0]).Every error is {error, Map} with class, kind and message. The instance
survives a trap and can be called again.
Put a time limit on a call
{error, #{kind := timeout}} = wasmtime:call(Inst, ~"loop", [], #{timeout => 100}).Or from another process while it runs:
ok = wasmtime:interrupt(Inst).Run a call without blocking
{ok, Ref} = wasmtime:call_async(Inst, ~"work", [Arg]),
... do something else ...
{ok, [Result]} = wasmtime:await(Inst, Ref, 5000).The call runs on the instance thread while this process continues. await
must run in the process that started the call, because host functions are
served there, while it waits; a guest that calls the host earlier waits for
await (within host_timeout), or use a host process. Two async calls on
one instance run one after the other.
Compile options
{ok, Mod} = wasmtime:compile(Bin, #{
opt_level => speed_and_size,
proposals => #{simd => false, threads => false, gc => false},
fuel => true}).opt_level: Cranelift'snone,speed(default) orspeed_and_size.proposals: turn WebAssembly proposals off (or on) relative to Wasmtime's defaults. A module that uses a disabled one is refused bycompile/2andvalidate/2withclass => compile, which is how you pin down what a plugin format may contain.fuel: compile with fuel metering, see below.
module_options/1 returns what a module was compiled with. Each distinct
option set is one Wasmtime engine, created on first use (at most 32 per
VM). Of the options only fuel is checked when a precompiled module is
loaded (deserialize/2); the optimization level and disabled proposals
need nothing at load time.
Bound a call by instructions instead of time
{ok, Mod} = wasmtime:compile(Bin, #{fuel => true}),
{ok, Inst} = wasmtime:instantiate(Mod),
{error, #{kind := out_of_fuel}} = wasmtime:call(Inst, ~"loop", [], #{fuel => 1000000}),
{ok, Left} = wasmtime:fuel_remaining(Inst).Fuel counts instructions (about one unit each), so the same input always
stops at the same point, unlike timeout. It costs a few percent of speed
and must be chosen at compile time; fuel belongs to the instance and is
consumed across calls until set again.
Read traps with their frames
{error, #{kind := unreachable, trace := [#{func_name := ~"inner"} | _]}} =
wasmtime:call(Inst, ~"outer", []).trace lists the wasm frames innermost first, with the function index and
byte offset, and names when the module carries a name section.
Globals and tables
{ok, 7} = wasmtime:global_get(Inst, ~"counter"),
ok = wasmtime:global_set(Inst, ~"counter", 8),
{ok, 4} = wasmtime:table_size(Inst, ~"handlers"),
{ok, 4} = wasmtime:table_grow(Inst, ~"handlers", 2).A constant global refuses global_set. Table elements and reference-typed
globals are references: table_get/3 and table_set/4 read and write them,
table_grow/4 fills new slots with one. See references for
what a reference term is and what you can do with it.
Read and write memory
ok = wasmtime:write_memory(Inst, 0, <<"hello">>),
{ok, <<"hello">>} = wasmtime:read_memory(Inst, 0, 5),
{ok, {Pages, Bytes}} = wasmtime:memory_size(Inst).Memory is reachable while the instance is idle, or from inside a host function.
During a call it returns {error, #{kind := busy}}. These act on the export
named memory (or the first exported memory); read_memory/4,
write_memory/4 and memory_size/2 take an export name for modules with
several memories.
Notes
- One call runs on an instance at a time. Calls from several processes are queued in order.
- The process that calls
wasmtime:callmust stay able to receive messages: host functions and results arrive there. - Drop every reference to an instance and its thread, store and memory are freed by the garbage collector.