wa_embedder
A WebAssembly-to-BEAM compiler for the Erlang ecosystem, written in Erlang. It
parses a .wasm binary (via wa_parser),
translates each function to Erlang abstract format, and compiles and loads the
result as a native BEAM module that you can call directly.
This is the Erlang package. Elixir projects can depend on it directly, or use
the companion wa_embedder_ex package
for an idiomatic Elixir API (WaEmbedder.compile/1..4 + WaEmbedder.ImportError).
Repository layout (monorepo)
Two hex packages live in this repository:
| Path | Package | Tool | Role |
|---|---|---|---|
| repo root | wa_embedder |
rebar3 | The compiler core (pure Erlang) |
wa_embedder_ex/ |
wa_embedder_ex |
mix | Thin Elixir wrapper + the test suite |
test_data/ |
— | cmake | Shared WAT/C .wasm fixtures |
The dependency direction is one-way: wa_embedder_ex (Elixir) → wa_embedder
(Erlang) → wa_parser (Erlang). The core has no Elixir dependency.
Installation (rebar3)
%% rebar.config
{deps, [{wa_embedder, "~> 0.1"}]}.
Usage
{module, Mod} = wa_embedder:compile("module.wasm"),
Result = Mod:some_exported_fun(Arg).
compile/1,2,3,4 compiles and loads the module and returns the
code:load_binary/3 result ({module, ModuleName} on success). Modules with
imports take an imports map resolving each WASM import to an Erlang target
(MFA tuple, external fun, or closure); unsatisfiable imports raise
erlang:error({import_error, Map}).
Float values on the BEAM
The BEAM cannot represent an infinity or a NaN: no literal spells one,
binary_to_term/1 rejects the bit patterns, and even 1.0e308 * 10.0 raises
badarith. A wasm f32/f64 is therefore an ordinary Erlang float() for
as long as it is finite, and one of
'+inf' | '-inf' | {nan, Payload} | {'-nan', Payload}
once it is not — the very shapes wa_parser
0.1.3 decodes a non-finite constant to. Payload is the raw fraction field
with the quiet bit included (16#400000 for a bare f32 nan,
16#8000000000000 for a bare f64 one), so abs, neg, copysign,
reinterpret, f32x4.extract_lane and a memory round trip preserve the bits
exactly. An arithmetic operation that produces a NaN returns the canonical
quiet NaN of its width, whose payload the spec leaves unspecified.
{module, Mod} = wa_embedder:compile("quickjs.wasm"),
Mod:some_f64_function() %=> :"+inf" | {:nan, 16#8000000000000} | 1.5
Host functions bound to a wasm import see the same representation, so a host
that passes an f64 argument back must be able to produce those terms.
Overflow of finite operands is a separate matter and unchanged:
f64.add(1.7e308, 1.7e308) still raises badarith, because the BEAM cannot
hold the result either.
Development
A nix develop shell provides erlang, elixir, rebar3, and the fixture
toolchain (cmake, ninja, wabt, binaryen). Dependencies default to their
published hex releases:
- Core → parser (side repo): the core depends on the published
{wa_parser, "~> 0.1"}hex package;rebar3fetches it from hex. To develop against a sibling../wa_parsercheckout instead, opt in manually with a rebar3 checkout (mkdir -p _checkouts && ln -sfn ../../wa_parser _checkouts/wa_parser); a checkout takes precedence over the hex dep. Nothing wires it for you (remove it before packaging). - Wrapper → core (in-repo): the wrapper depends on the published
{:wa_embedder, "~> 0.1"}hex package by default. To build against the Erlang core at the repo root instead, opt in manually by exportingWA_EMBEDDER_PATH=..beforemix(e.g.WA_EMBEDDER_PATH=.. mix test).
Build and test
# Erlang core (repo root) — uses the published wa_parser hex dep
rebar3 compile
# Elixir wrapper + full fixture suite (in wa_embedder_ex/).
# The core is not on hex yet, so build the wrapper against the in-repo core:
cd wa_embedder_ex && WA_EMBEDDER_PATH=.. mix test
The test suite lives in wa_embedder_ex/test/ and compiles the WAT/C fixtures
under the repo-root test_data/ (built by CMake/Ninja before the suite runs).
Once wa_embedder is published, mix test works without WA_EMBEDDER_PATH.
Building the fixtures manually
cd test_data
cmake -B build -G Ninja
ninja -C build # WAT fixtures (default); `ninja -C build c_fixtures` for the C chain
Packaging
# Core: uses the published {wa_parser, "~> 0.1"} dep. (If you added a manual
# _checkouts override for parser dev, remove it first: rm -rf _checkouts.)
rebar3 hex build # or: make package
# Wrapper: unset WA_EMBEDDER_PATH so the {:wa_embedder, "~> 0.1"} hex dep is used
cd wa_embedder_ex && env -u WA_EMBEDDER_PATH mix hex.build # or: make package
Publish order: publish wa_embedder (core) first, then wa_embedder_ex (the
wrapper's hex dep on the core cannot resolve until the core is on hex).
License
Released into the public domain under the Unlicense.