A Wasm to Go translator
The input is a Wasm module, and the output is a single Go source file,
with no dependencies beyond the standard library.
To translate a Wasm module to Go use the following command:
wasm2go < input.wasm > output.go
The Go file forms a self contained package,
that exports a structure called Module,
and a New function to initialize it.
The methods of the Module structure are the Wasm module's exported functions,
whereas imports are interfaces New consumes.
The module may also export its global variables (as fields).
And if it imports a memory, you can provide your own allocator.
Only a subset of the Wasm specification will be supported,
as the goal is to translate specific Wasm modules to Go.
For example, we don't need to implement SIMD,
as we can ask (e.g.) LLVM not to emit it.
We also assume the input Wasm modules can be trusted.
At a minimum, you should run Wasm modules through a verifier
before attempting to convert an untrusted module.
The current target is a useful subset of Wasm produced by clang.
This includes most Wasm 1.0 features, with the following exceptions:
- export aliasing (exporting the same function/global under different names);
- export conflicts (after the trivial name mangling we apply);
- importing tables or globals.
It also supports a subset of Wasm 2.0 features:
- bulk memory operations and reference types;
- nontrapping float-to-int conversions;
- sign-extension operators;
- multi-values.
The goal is not to produce particularly readable Go code:
- because Go makes a distinction between statements and expresions,
we use a stack-to-register approach to translate Wasm to Go;
- Wasm control flow is implemented with
goto and labels;
- the distinction in Go between
bool and int32 requires
spurious control flow and type conversions;
- Go's untyped numeric literals require explicit type conversions;
- float operations require type conversions to avoid being combined;
- float literals can't represent negative zero, infinities, or
NaN,
often requiring Float64frombits;
- Go forbids unused variables/labels/etc.
Many of these introduce unnecessary verbosity,
but they're necessary for semantic correctness.
Judge the output by the assembly generated by the Go compiler,
not by how a human would read it.
For little endian CPUs,
we can generate much faster code by using unsafe.
Despite the scary name, the generated code abides by the
rules of unsafe,
and all memory accesses are bounds checked.
To generate big/little endian Go code:
wasm2go -endian=big < input.wasm > output_big.go
wasm2go -endian=little < input.wasm > output_little.go
Both versions will be guarded by a build tag,
so you can add both to your project.
The only other knob is whether to make sure float operations
canonicalize NaNs.
Usage of wasm2go:
-endian string
endianness of the generated code (big or little)
-nanbox
whether to canonicalize NaNs (default true)