-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Compile to WebAssembly #2011
Comments
Definitely open to it. We're also about to give napi a shot also. It's an exciting time for native extensions. |
There already is an official libsass Emscripten project https://github.com/medialize/sass.js |
Yeah but with wasm we can in theory entirely ditch vendor binaries for Node
8+
…On 10 Jun. 2017 11:54 am, "Nick Schonning" ***@***.***> wrote:
There already is an official libsass Emscripten project
https://github.com/medialize/sass.js
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#2011 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAjZWCpQgdjK6yHR0HJ41REgUVvD7Azvks5sCfdBgaJpZM4N16hz>
.
|
Dropping binaries would be fantastic... I wonder how well will that setup perform |
@mafintosh I'm starting look into this seriously, but I'm struggling with finding resources on how to do this for native extensions in Node. Do you know of any other projects that have made this switch to WASM that I can reference? |
@xzyfer I tried to install the Emscripten SDK on my computer to compile libsass to WASM; good news, it compiles without changing the C++ code! I follow the following steps:
$ make clean && make --eval="CC=emcc
> CXX=emcc"
The In return ext.getBinaryPath() by return require('./libsass.js'); // libsass.js is the file generated by emcc And it outputs the following:
I hope I'm helping! |
Thanks for this work @aduh95. You got further than I did when I looked at it. For now I would comment out the |
for anyone interested in this topic, I've prepped small poc at #2220 for further discussions. |
Thank you! I've recently got sassc running as web assembly, but haven't had
much luck with node-gyp. I'll take a closer look this weekend.
…On 19 Jan. 2018 5:41 pm, "OJ Kwon" ***@***.***> wrote:
for anyone interested in this topic, I've prepped small poc at #2220
<#2220> for further discussions.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2011 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAjZWHsXziurKG7LXrcgNOfsR2shmt-Xks5tMDkFgaJpZM4N16hz>
.
|
For anyone who's interested in this further, I spawned https://github.com/kwonoj/libsass-asm based on my PR #2220 to push this further. So far it passes sass-spec for 3.5.4. |
I came here after watching @callahad's amazing talk about Webassembly where he mentioned this project's potential for Webassembly. Thought this might be of interest to the people who are currently putting time in this! |
Is there a plan to use WebAssembly (libsass-asm) as a replacement for libsass directly? As in, is it being seriously considered? Or are we still in the POC phase? I periodically run into issues with node-sass/libsass, particularly when switching Node versions because the binaries don't match up. But also, I'd like to remove the network dependency from my toolchain, and currently downloading libsass is the only thing I haven't been able to avoid. Understand that everyone is busy - feels like we're on the edge of some sort of utopia... it's very exciting (and I can't wait!). |
Compiling LibSass to WASM is easy. The difficulty is in compiling the C++ code that binds LibSass to Node. This is difficult in part because we need to rely on libuv for async custom importers, and the async render function. This is pretty much where we are stuck right now. The situation may improve when some kind of background worker or threading API lands in WebAssembly. |
Reading through this PR it appears that threading is available behind an experimental flag in Node 10. Obviously there’s risk that might change - but is that enough to “unstuck” you? Is it even what you actually need? Really wishing I could C and help. Might be time to level up... Thank you for your prompt response @xzyfer :) |
Isn't there a way to use libsass only as a self-contained library without any other bindings to eg. file io? We could extract the core functionality into a
i "leveled up" to rust, so im now even less willing to learn/"level down" to c :( |
FWIW, the folks of sass.js have an experimental WASM branch (which they apparently don't actively operate on) which works pretty nicely — I've at least tested it successfully in browsers: |
maybe this could be used to compile it to wasm: |
So the main issue is with how to call asynchronous JS functions (importers, sass functions) from libsass. I see two possible ways to do it: a) Run libsass in a Worker via worker_threads (only on Node 10+, and behind a flag in 10.*) and use SharedArrayBuffer + Atomics to implement a lock, allowing the worker to synchronously wait for the importer. That "hard part" is already implemented in https://github.com/hyperdivision/async-wasm. I guess @mafintosh already knows about that project, since he's the author. ;) b) Modify libsass to avoid the need for async - make it somehow possible to suspend the libsass code and resume it when the importer is done. This sounds difficult, but the sass.js code linked by @loilo earlier actually already does it! It happens automatically thanks to Emscripten. They use https://github.com/emscripten-core/emscripten/wiki/Emterpreter and emterpreter_whitelist.json. I think this looks pretty promising, but one possible problem is that sass.js only supports custom importers and not custom functions. Adding custom functions might make that whitelist much longer and force most of libsass code to the emterpreter "slow path". So it would need some benchmarking to find out how much slower is it. |
Good news! I did it! 🎉 I have a working "node-sass-wasm" which supports the full node-sass API, including asynchronous importers and functions (thanks to worker_threads), and passes all of test/api.js and test/spec.js. There are no more binaries to download, it has zero dependencies, and it doesn't need rebuilding for every future major version of Node. After some remaining minor cleanups, I'll put it on my GitHub so that everyone can take a look and try it. Stay tuned. Bad news! It's a total rewrite! 💔 It basically doesn't share any code with node-sass. Especially not the C++ part - it's all done with Emscripten and Embind instead of V8 API and Nan. (More details incoming once I publish the code.) I'd really like to contribute this code to node-sass so everyone can benefit from it, but I don't have any idea how. Node-sass still needs to support old Node versions and it's not clear to me how to combine them - two parallel codebases sound bad... Node-sass maintainers, what do you think? |
Wow, congratulations! Have you also rewritten libsass or has this been handled with emscripten? What is the node oldest version it can run on? |
I'm curious to see the implementation. Update from our end: there's work going on right now to enable WASI un LibSass as well as libuv. This is the work we've been waiting for in order to ship node-sass as WASM. We've investigate emscripten before but the resulting WASM module was large, and the performance suffered significantly. |
I'll try to publish the code ASAP so you can take a look. Sorry, it's not quite ready yet. I didn't rewrite libsass. I'm compiling libsass with Emscripten and wrapping it with Embind, with a bit of special sauce to handle async importers/functions. It can run on node >= 10.5.0 with the The wasm file is 1.8 MB, which is not a lot. (The binding.node file in node-sass alone is 3.8 MB, and with all dependencies |
Could do a @next or major version bump which supports Node 10 or above? |
From our point of view we've been waiting for the ecosystem and WASM to catch up. The reality is it's not ready our use case. Which isn't to say it's not possible, but the results thus far have negated the benefits of node-sass over other native JS implementations. Supporting the full node-sass API is a very impressive effort and I'm excited to see it. But if we can't overcome the start up or run performance issues we're unlikely to progress down this path. Alternative pure JS implementations exist at the cost slower compilation speeds. We'll continue to invest in improving the installation process as much as npm and node allow us to. And there is on going work in LibSass to reduce the memory footprint and further improve Sass compilation times - widening the gap between pure JS implementations. |
@xzyfer are these times measured somewhere? (the pure JS compilation and the C++ compilation) |
It's worth noting that folks working on WASM and WASI are seriously looking specifically at the issues faced by node-sass and LibSass as part of their work to evolve the specs. |
@jasonwilliams there have been various benchmarks over the years. You'll want to pay close attention to the language used in these benchmarks. I suggest looking at the raw numbers if they exist. Some that I'm aware of are: |
Most of these are out of date now, and non of them include the recent changes on LibSass master (unreleased). |
The prototype is ready. 🎉 Take a look: https://github.com/TomiBelan/node-sass-wasm A very unprofessional benchmark says test/api.js + test/spec.js runs in 14.4 seconds on node-sass but in 25.6 seconds on node-sass-wasm. So it's not only the startup time, the execution is also ~2x slower. This is worrying to me, because most of these tests are sass-spec tests which shouldn't spend a lot of time in the binding layer or Emscripten system code. Is this performance penalty intrinsic to WebAssembly itself? It might also be interesting to compare it vs sass.js and @kwonoj's libsass-asm. Anyway, @xzyfer I think I agree: it's best for node-sass to keep using the native library for now. I hope node-sass-wasm can serve as inspiration but currently it's not realistic to merge it - especially because of (un)supported Node versions, but also because of performance. So I won't send it as a PR, but of course feel free to reuse any pieces that look useful (in line with MIT). Still, I'll put node-sass-wasm on npm, as some users might find it to be a good middle ground between node-sass and native JS implementations. Should I make it a scoped package like I'm also curious about your WASI comments, do you have any links? I thought WASI is just a libc-like / POSIX-like "system interface" which didn't sound terribly relevant. Does WASI solve the ABI problem, e.g. converting std::string <-> JS String, and more complex structures? That was the main reason to use Emscripten for me. |
I have no deeper insight into WebAssembly perf limits, but I had a similar disappointing experience when I compiled the Rust-written YAML parser Unfortunately I couldn't get any information from WASM engineers about how WASM performance roughly relates to native — maybe besides Surma's talk on this year's Google I/O, stating that WASM is not |
@loilo I'm curious whats pref difference between js-yaml and native? |
This may be of interest WebAssembly/design#1287
…On Sat., 22 Jun. 2019, 10:25 am Bnaya Peretz, ***@***.***> wrote:
@loilo <https://github.com/loilo> I'm curious whats pref difference
between js-yaml and native?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2011?email_source=notifications&email_token=AAENSWGILOYWLWWDSE2ERKDP3XOWTA5CNFSM4DOXVBZ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYKD7VA#issuecomment-504643540>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAENSWAMDK347G5AC5M574LP3XOWTANCNFSM4DOXVBZQ>
.
|
What exactly do you mean by "prefs difference"? |
Does natively-compiled |
Ah, okay — haven't tried that out. I'm completely lacking experience in the systems programming language space anyway, so I can only hope that my perf tests were fair. If you're interested in the setup, I've written down the approach I took in this Gist. |
I suggest checking out the postcard benchmark repo I posted above. It makes
it to run simple sass benchmark against other implementations.
…On Sat., 22 Jun. 2019, 3:21 pm Florian Reuschel, ***@***.***> wrote:
Ah, okay — haven't tried that out. I'm completely lacking experience in
the systems programming language space anyway, so I can only hope that my
perf tests were fair.
If you're interested in the setup, I've written down the approach I took
in this Gist
<https://gist.github.com/loilo/02c7a5e29c44e31b208ebbfe036277d1>.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2011?email_source=notifications&email_token=AAENSWAZHLTBIWMM5XDQKQDP3YRMDA5CNFSM4DOXVBZ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYKJKMQ#issuecomment-504665394>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAENSWFYRJXSOE5RZ2ZOSUDP3YRMDANCNFSM4DOXVBZQ>
.
|
I'd suggest adding in some perf tracing calls at various points (before parse, after parse, before marshalling, after marshalling, etc) and then hooking them up to the performance api. my guess is that marshalling the data into the js world is likely the main slowdown, although there is probably about a 5-15% hit for the parser as well. You might also want to try node master, where we have set node up to make wasm memory ops about 30% faster (coming in v13 unfortunately, due to relying on signals) |
Agree with @devsnek. If slowdown happening in main routine you could try change |
A 2x slowdown with wasm is surprising, but one possible reason is crossing the JS/wasm boundary - VM overhead, type conversion overhead, etc. C, C++, and Rust are at a disadvantage when you need to communicate a lot with JS (a future wasm GC might help for GC languages). Skimming the code, it seems like this issue might be relevant, but @TomiBelan it sounds like you think it's not? Another possible reason is JS can inline at runtime while wasm doesn't. The flags @MaxGraey mentioned might help, but there's no guarantee the LLVM optimizer will manage to do the right thing at compile time... It may be interesting to run the node profiler on the js and wasm builds, if nothing else helps. |
In the last months, I was porting some hashing algorithms to WASM and I got some very good results: https://www.npmjs.com/package/hash-wasm#benchmark By example in SHA-1, I was able to archive 17x speedup compared to the fastest JavaScript only implementation. So, I see a huge potential in migrating node-sass to WASM. With a hand-tuned JS-WASM boundary, I think near-native performance can be archived. |
My intuition is telling me that the C++ library being optimized for GCC compilation might need to be ported to LLVM for enhancing greater performance on WebAssembly. Thus, explaining why Mozilla advises to use their programming language Rust as it provides such native low-level optimization through typing, safety and execution. LLVM optimized C/C++ code need a near complete rewrite. So we could experiment first with a LLVM-IR Wasm a compilation chain, to produce and optimize highly LLVM IR from code and then decompile to C++ which would turn into Wasm using emscripten. Edit: see example here https://gist.github.com/alloy/d86b007b1b14607a112f |
There is probably no need to go that low-level. Writing a custom C API interface and using that from JS could run at optimal speed. By doing it that way you have a direct call from JS to C (C to JS is possible too). That will still have some VM overhead from crossing the boundary, but it would be small unless you cross it for a very small amount of work. Typically you would write some data and then do a single call to process it all, instead of doing many individual calls for a single item, etc. How much work that would be would depend on the size of the API layer here, but usually it's very little additional C and JS. I'd be happy to help someone with any interfacing questions if there are any. |
No... maybe I was misexpressed myself in my first post. Why would you build a C to JS API for Wasm code?? |
@StEvUgnIn I may have misunderstood you, sorry if so. But The standard approach for using wasm from JS at high speed is to write a C API for the wasm. C APIs are well defined and can be called directly from JS with very little overhead. That is, JS literally calls the wasm exports, which are defined by the C API. Tiny example (without all the details): // foo.cpp
// C API function
extern "C" int foo() {
return 10;
} // foo.js
// call the C API
console.log(wasmExports.foo()); // prints 10 |
That is why LLVM offers the possibility to produce intermediate representation with LLVM IR (*.ll, *.bc). The only thing to do is to look at the documentationm, optimize the C++ compilation to LLVM IR, convert LLVM IR to Wasm. Now LLVM 10 along clang even supports targetting WebAssembly.
We could try this option if it brings more optimization than node-sass-wasm brings. Any alternative deserves to be experimented. Even a fallback to asm.js for unsupported node.js versions. |
Since Node 8 supports WebAssembly it would be nice to compile libsass to that using emscripten so people wouldn't need to compile the c++ or depend on a prebuild.
The text was updated successfully, but these errors were encountered: