Repository navigation
Compile Binaryen to WebAssembly - #2503
Conversation
sbc100
left a comment
There was a problem hiding this comment.
Seems reasonable to me. I actually didn't know we had so much JS-specific stuff in binaryen. I wonder if keep this upstream is the best place for it? Maybe a separate binaryen-js repo would make sense at some point.
| Module['WidenHighSVecI16x8ToVecI32x4'] = Module['_BinaryenWidenHighSVecI16x8ToVecI32x4'](); | ||
| Module['WidenLowUVecI16x8ToVecI32x4'] = Module['_BinaryenWidenLowUVecI16x8ToVecI32x4'](); | ||
| Module['WidenHighUVecI16x8ToVecI32x4'] = Module['_BinaryenWidenHighUVecI16x8ToVecI32x4'](); | ||
| Module['SwizzleVec8x16'] = Module['_BinaryenSwizzleVec8x16'](); |
There was a problem hiding this comment.
Is this all maintained by hand? It seems pretty fragile? Can this not be done by looking at all the keys in Module that start with _Binaryen?
There was a problem hiding this comment.
Yeah, that's all maintained by hand. I believe one reason why it's hard to automate is that not every exported function of the form _Binaryen... can be called like that, for example some of these return the respective constants while others like _BinaryenModuleCreate create a module. But might be possible to simplify by keeping a list of the good ones, doing something like
["WidenHighSVecI16x8ToVecI32x4", ...].forEach(name => Module[name] = Module['_Binaryen' + name]());or renaming the good ones to a common scheme, like _BinaryenOp... so JS can decide based on that?
| wasm.addFunctionType("vI", Binaryen.i32, []); | ||
| // It will cause a fatal error to try to add the same name a second time | ||
| wasm.addFunctionType("vI", Binaryen.i32, []); | ||
| function test(Binaryen) { |
There was a problem hiding this comment.
Is the Binaryen argument here different to the Binaryen in the global scope? If not then maybe we don't need it? Or at least maybe call it something else so its more obvious what it doesn't shadow the global one?
There was a problem hiding this comment.
It's the same as the global one, yeah. Mostly a case of "why not" if we'd ever want to test multiple variants, like a fastcomp and an upstream one, but can as well remove the arguments since we don't do that.
|
Last commit makes it so that the constants are created from arrays, making this a bit less error prone and slightly less code. Tests don't shadow |
|
Appears this already found something that was wrong before Module['GeVecF32x4'] = Module['_BinaryenGeVecF32x4']();
Module['EqVecF64x2'] = Module['_BinaryenGeVecF32x4'](); // here |
One aspect is that running the JS tests also tests the wrapped C-API, so there are benefits in keeping the JS tests upstream. Afaik some tests even exist in JS only. Not ideal ofc, but still better than not having them. |
kripken
left a comment
There was a problem hiding this comment.
Nice, thanks!
I don't remember what the general best practices are here, though - does wabt use the .ready.then approach, and other projects?
Does this make us lose support for building to JS and not to wasm? It would be good to still support both.
Hmm, I thought Wabt has a const wabt = require("wabt");
wabt().then(function(wabt) { ... });This has a major problem though in that (async () => {
let w = await wabt();
})();goes into an endless loop since the awaited object is promise-like again (here: the same object with a I am not aware of general best practices here as something like this is uncommon in JS-space, and if there aren't any I suggest leading by example with a
Yeah, this drops "compiling to just JS". Can try to make two build targets with |
|
I'm not necessarily opposed to dropping the JS target. But one thing that's worried me is performance, specifically, binaryen uses exceptions, and with a JS build the VM can inline the invokes, but in wasm we end up with uninlineable wasm->js->wasm calls. Worth doing some benchmarking here to see if the wasm build is actually faster on a large input. |
|
With the changes there are two targets |
|
Makes sense to me. Overall this looks good! Last thing, please update the Changelog, as this is a large breaking change. |
|
I guess |
| - Binaryen.js can now be compiled to Wasm using the `binaryen_wasm` target. | ||
| Unlike the JS variant, the Wasm variant requires asynchronously awaiting the | ||
| Wasm blob's instantiation and initialization before being usable, using the | ||
| `binaryen.ready` promise, e.g. `binaryen.ready.then(() => ...)`. |
There was a problem hiding this comment.
I think we export as Binaryen (with a capital B)?
There was a problem hiding this comment.
Ah, yeah, the default is Binaryen when loading binaryen.js in the browser, that is just including the script. For the npm package I'm using const binaryen = require("binaryen"); simply because that's the package name and somewhat standard to do. Perhaps it makes sense to unify both to lower case to match the package name?
There was a problem hiding this comment.
Oh, I see. Yeah, let's maybe standardize it on the lowercase name then. But maybe in a separate PR (with a changelog entry)?
So I think this is ready to land, except for the merge conflict.
There was a problem hiding this comment.
Sounds good! Conflict should be resolved if CI doesn't state otherwise.
This PR enables compiling Binaryen to WebAssembly when building binaryen.js. Since WebAssembly is best compiled and instantiated asynchronously in browsers, it also adds a new mechanism to tell if respectively when the module is ready by means of one of the following:
// Using a promise
const binaryen = require("binaryen");
binaryen.ready.then(() => {
... use normally ...
});
// Using await
const binaryen = require("binaryen");
(async () => {
await binaryen.ready;
... use normally ...
})();
// Where top-level await is available
const binaryen = await require("binaryen").ready;
... use normally ...
One can also tell if Binaryen is already ready (for example when assuming it in follow-up code) by:
if (/* we already know that */ binaryen.isReady) {
... use normally ...
} else {
throw Error("Binaryen is supposed to be ready here but isn't");
}
The JS test cases have been updated accordingly by wrapping everything in a test function and invoking it once ready. Documentation will have to be updated as well to cover this of course. New file size is about 2.5mb, even though the Wasm becomes inlined into the JS file which makes distribution across different environments a lot easier.
Also makes building binaryen (to either js or wasm) emit binaryen.js, and not binaryen_js.js etc.
Supersedes and thus fixes #1381
With .ready it also fixes #2452
This PR enables compiling Binaryen to WebAssembly when building binaryen.js. Since WebAssembly is best compiled and instantiated asynchronously in browsers, it also adds a new mechanism to tell if respectively when the module is ready by means of one of the following:
One can also tell if Binaryen is already ready (for example when assuming it in follow-up code) by:
The JS test cases have been updated accordingly by wrapping everything in a
testfunction and invoking it once ready. Documentation will have to be updated as well to cover this of course. New file size is about 2.5mb, even though the Wasm becomes inlined into the JS file which makes distribution across different environments a lot easier.Thoughts? :)
Supersedes and thus fixes #1381
With
.readyit also fixes #2452