WASMIX - WASM In haXe
DISCLAIMER: this library is in early stages of development.
With wasmix, you can use a subset of Haxe and have it transpiled to WASM.
class Example {
static public function fib(n:Int) {
return if (n < 2) 1 else fib(n - 1) + fib(n - 2);
}
}
final example = wasmix.Compile.module(Example);
trace(example.fib(10));// traces 89
You can optionally pass a number of flags, like so:
wasmix.Compile.module(Example, { async: false, skip: false, validate: false });
All flags are false by default, and behave as follows:
async- if set totrue, you will receive aPromiseinstead of just the instiated moduleskip- if set totrue, you will receive an object with exactly the same structure as the WASM module, but it will run in JavaScriptvalidate- if set totrue, will attempt to validate the WASM via whatever nodejs version you have installed. Has no effect forskip: true.
Compiler flags
wasmix.minify.imports- will minify names used in imports
Goal
The goal of this library is to allow you to:
- Use familiar syntax to write WASM code and rely on Haxe for:
- auto completion
- type checking
- optimizations (e.g. loop unrolling, inlining) ... currently not fully supported, because WASM compilation runs before various filters
- code organization (structure your code with packages and modules, distribute as normal haxe libraries if you wish)
- Make calling from Haxe/JS -> WASM and WASM -> Haxe/JS straight forward
- Produce your WASM during your normal build - the WASM code is currently just embedded in base64 to avoid the need for further bundling etc.
- Allow to skip WASMification for debugging purposes
- Avoid relying on external tools (e.g. WAT)
This library is not intended for running arbitrary Haxe code at the speed of WASM. Support for language features is somewhat limited (you should expect proper compile time errors if you use something unsupported - if you don't, please file a bug). More often, this is by design. The main use case is to offload number crunching into the WASM runtime. Compared to hot JavaScript code, the speed gains are currently modest, in the range of 10% to 100% (which in a way is also a testament to modern JavaScript runtimes).
Supported Types
Bool,Int,Floatandjs.lib.BigInt: Supported natively as I32, I32, F64 and I64 respectively- Typed Arrays: in
wasmix.bufferyou will find typed arrays that correspond to those injs.lib(the signature differs slightly for various reasons), provided they are in the wasm module's memory - any other typed arrays will throw exceptions. - Enums: Supported by bridging into JS, so constructing/destructuring does come at a significant overhead. For example doubling the value of a
haxe.ds.Option.Some(read index, read param, construct newOption) takes ~5x more time in WASM than Haxe/JS: 20ns vs. 5ns. That still means you can do it 50x in a micro second (or 50000x in a millisecond). Just don't do it in a hot loop. - Classes: Generally, you can use any Haxe class from WASM, but you should note that methods which are not inlined will have an overhead for bridging. Inlined methods on the other hand will have to be wasmix compatible.
String: Supported by bridging into JS. Any string literals you have are passed into the WASM module as references. Concatenation and comparison are implemented by bridging into JS.- Instances: you can instantiate any Haxe classes (normal and extern) and call any methods on them, all by bridging into JS.
Array: array literals are created by bridging into JS and otherwise work like normal instances.abstractover any supported type.
Typed Arrays
Within the WASM runtime, Typed Arrays are passed around as I64, with the low 32 bits being the offset and the high 32 bits being the length. Instead of using js.lib you will have to use wasmix.runtime. The primary reason is that js.lib.Float32Array operates on double precision floats, whereas in WASM we want it to stick to Float32. You can always call toJS to get the corresponding js.lib type to operate on the full interface in JS (it's really just an unsafe cast).
Typed array access is currently not bound checked. If you read outside the bounds of an array, you get garbage at best and an exception at worst (if it attempts to read outside defined memory). This is primarily for performance reasons. Since each WebAssembly module has its own isolated memory, the security implications of this are manageable. If indices are based on user input and you don't sanitize them, attackers could garble/extract data in the isolated memory or cause crashes.
Not supported (yet?)
Null<Int>|Null<Float>|Null<Bool>- nullable primitives don't really fly in WASM (and error handling is not optimal here yet either). There are multiple options:- Coerce all to a default value as is the case on static platforms, potentially risking inconsistent behavior between JS ans WASM.
- Box them, at the cost of performance.
- Anonymous object: this is easy enough to change and soon will.
- Local functions: you can only have inline local functions. The main issue is that local functions need all sorts of runtime support like a garbage collector and a place to store captured variables and what not.
- First class functions: you can only call static/instance methods or inline local functions directly. You cannot use functions as values.
- Type parameters: support for type parameters is limited, in particular because numeric primitives do not mix with other values in WASM. Perhaps constrained type parameters will work fully in the future. If you need generic functions, they will either have to be inlined or live in a non-wasmixed class, where you can call them via bridging.
Future plans
SIMD
The next big thing to figure out will be how to make SIMD instructions available in wasmix in a way that is reasonably readable and can work in JS too. This is currently subject to investigation. Suggestions are welcome.
Instantiable modules
Currently, wasmix only allows for static members, where methods live entirely in WebAssembly, while fields live in the Haxe world. There are two reasons for this:
- global state is thus shared between WASM and JS.
- in the future, it will be possible to instantiate classes, so that their instance fields become true WebAssembly globals, while they share their static state via the original Haxe class.