Hello folks. Today I'm excited to share with you about some work I've been hacking on in Firefox's WebAssembly (AKA Wasm) engine recently.
The tl;dr summary: starting in Firefox 78 (released June 30, 2020), you will be able to write WebAssembly functions that pass 64-bit integers to JavaScript (where they will turn into BigInts) and vice versa.
(see Lin Clark's excellent Cartoon Intro to WebAssembly if you're not familiar with WebAssembly)
Wasm comes with a JavaScript API that allows a Wasm program to interact with JavaScript,
by exchanging values through mechanisms such as function calls between the languages,
globals,
or shared linear memory.
The initial MVP release of Wasm came with four built-in types: i32
, i64
, f32
, and f64
(32-bit and 64-bit
integers and 32-bit and 64-bit floats, respectively).
All but one of these types could be used to talk with JavaScript programs from the start by converting to a Number appropriately. But i64s were disallowed.
Concretely, a simple example program (with a single function that returns an i64
)
like this one:
(module
(func (export "f") (result i64)
(i64.const 42)))
would produce an error like this when you would try to run it from JS:
> WebAssembly.instantiateStreaming(fetch('../out/main.wasm')).
then(obj => console.log(obj.instance.exports.f())).catch(console.error);
[error]: TypeError: cannot pass i64 to or from JS
(you can try this out in WebAssembly Studio: select
a wat
project and put the above code in the .wat
and .js
files respectively)
In Firefox 78, you won't get an error. Instead, the JS caller will receive a BigInt
value 42n
and can continue its computation.
One of the practical ramifications of this limitation in previous releases
was that tools that produce Wasm
code would have to legalize it by transforming the code to accept or produce
multiple 32-bit integers instead. For example, a function with a signature
(param i64)
would be translated to one with (param i32 i32)
. This can
increase compiled code size and complicate the JS logic in your program.
The reason that the limitation existed was that until relatively recently, there was no good way to represent a 64-bit integer value in JS; the Number type in JS doesn't allow you to represent the full range of a 64-bit integer.
That's all changed since BigInt (arbitrary-length integers) became part of the JS standard and started shipping in browsers. In Firefox, it's been available since 2019 after my colleagues at Igalia shipped it (see Andy Wingo's blog post about that).
With the now implemented "JavaScript BigInt to WebAssembly i64 integration" proposal, i64 values from Wasm get converted to BigInts on their way out to JS as you saw in the earlier example. On the other hand, JS code can send a BigInt (e.g., as an argument to a Wasm function) to Wasm and it will get converted to an i64. In the case that a BigInt's value exceeds the i64 range, it is fit into the range with a modulo operation.
Support for this proposal has also landed in tools like
Emscripten already as well (you have to pass a WASM_BIGINT
flag to use it).
It was possible to fill in this gap in the JS API thanks to a long line of work on BigInt in JS standards and engines. Some of my colleagues at Igalia such as Dan Ehrenberg and Caio Lima have been pushing forward a lot of the work on BigInt, which we talked about a little bit in a recent blog post.
Much of the BigInt/i64 interoperation work in Firefox was originally done by Sven Sauleau, who championed the spec proposal along with Dan Ehrenberg. Ms2ger from Igalia also worked on the spec text and tests. Many thanks to the engineers at Mozilla (Lars Hansen, Benjamin Bouvier, and André Bargull) who helped review the work and provided bug fixes.
Finally, our work on WebAssembly at Igalia is sponsored by Tech at Bloomberg. Thanks to them for making this work on the web platform possible! They have been a great partner in Igalia's mission to help build an expressive and open web platform for everyone.