hermes icon indicating copy to clipboard operation
hermes copied to clipboard

WASM support within Hermes?

Open paulshapiro opened this issue 3 years ago • 61 comments

Problem

At present it does not look like Hermes has support for running WASM via a global.WebAssembly.

Solution

Would love to see an option for doing essentially the inverse of this:

https://github.com/react-native-community/jsc-android-buildscripts/blame/4399436b9263992b16070559b81f02fbaa5af67e/scripts/compile/jsc.sh#L58

Additional Context

JS-only polyfills bringing WebAssembly support into the runtime are bound to be slow .. So I was thinking to support WASM running on Android, either a custom-build JSC (but that's not very future-proofed as we're switching to Hermes eventually), or use Hermes with some way to enable WASM, or try to find some react-native module for android to polyfill the lack of a global.WebAssembly. Seems to me that since iOS WebViews support WASM fairly well now, Android demand will be there.

This could aid porting lots of existing apps to React Native, so seems like a good idea.

paulshapiro avatar Dec 04 '20 22:12 paulshapiro

There was some prior discussion about WASM support in this thread.

avp avatar Dec 04 '20 22:12 avp

Thanks - I see.. but I think it's clear there are many strong cases for it. It's almost reducible to arguing why we want WASM at all. Can we re-open discussions somehow?

At the very least, it would obviate many cases necessitating bridging to a native module, not to mention redundant bridge code impl and maintenance.

paulshapiro avatar Dec 04 '20 22:12 paulshapiro

Note that the other discussion was not about Wasm support in Hermes. See my comment here and here.

tmikov avatar Dec 05 '20 00:12 tmikov

@tmikov Note: I'm totally noob in WASM and everything that I've written down below can be nonsense.

One use case I'm dreaming of is an ability to write extensions to databases in JavaScript.

As SQLite is an embedded database it would be awesome to write some function in pure Javascript which can be compiled to WASM.

Later driver written in JSI can (assume JSI and WASM can interact and in high-performant way) can consume and run that WASM with native performance (???) (with help of https://www.sqlite.org/c3ref/create_function.html), returning to Javascript only probably filtered subset of data.

How would it be performat to call Javascript function as Application-Defined SQL Functions through JSI?

likern avatar Dec 13 '20 20:12 likern

@tmikov I see, thanks. I still think there are strong arguments and use-cases for it, and it would bring the engine to closer feature parity with e.g. JSC. What would it take for me to implement WASM support in Hermes?

paulshapiro avatar Dec 14 '20 21:12 paulshapiro

Getting wasam support would be awesome. Has anyone got some solutions ?

ShivamJoker avatar Jul 02 '21 11:07 ShivamJoker

I am curious: why would you prefer Wasm, when you can ship actual natively compiled C++ code with your RN app?

tmikov avatar Jul 08 '21 20:07 tmikov

@tmikov how can I do that ? I've seen native modules but it's with Java only can you give any guide or reference.

ShivamJoker avatar Jul 08 '21 21:07 ShivamJoker

@ShivamJoker hmm, that is a good question. I know that it is possible and it is not technically difficult - after all, RN has C++ code, so it is doing exactly that already. Basically you use jsi::Function::createFromHostFunction() to create instances of native functions callable from JS. Then you register these functions as properties in a JS object, even in the global object, so JS can call them. That's it.

In terms of a specific Howto, how to actually setup your RN project for that, I can't really help you - that is more of a question for React Native. It is not Hermes specific.

tmikov avatar Jul 08 '21 22:07 tmikov

I am curious: why would you prefer Wasm, when you can ship actual natively compiled C++ code with your RN app?

react-native-web compatibility for one.

CooCooCaCha avatar Aug 24 '21 17:08 CooCooCaCha

@tmikov We can use local database SQLite or GlueSQL https://github.com/gluesql/gluesql. Compile to WASM and continue working with full-blown sql database with all the features.

likern avatar Aug 24 '21 18:08 likern

FWIW, we have added initial support for Wasm encoded as Asm.js. You can use wasm2js to transform your Wasm module to Asm.js and compile and run the result with Hermes. That was possible even before, but latest Hermes can optionally recognize the Asm.js intrinsics and optimize them, resulting in about 5x speed up.

It is still very early, highly experimental, and definitely unsupported, but it does work. It requires a custom build of the latest Hermes with the HERMES_RUN_WASM flag, as well as -funsafe-intrinsics when invoking the compiler. For now it can only be used to run trusted code.

A small C/C++ example:

unsigned add(const unsigned * arr, unsigned count) {
    unsigned res = 0;
    while (count--) res += *arr++;
    return res;
}

Previously the inner loop was compiled very naively like this:

L2:
    LoadFromEnvironment r9, r2, 3
    RShift            r8, r4, r7
    GetByVal          r8, r9, r8
    ToInt32           r8, r8
    AddN              r8, r8, r1
    ToInt32           r1, r8
    AddN              r9, r4, r6
    ToInt32           r4, r9
    SubN              r9, r3, r5
    ToInt32           r3, r9
    Mov               r0, r1
    JmpTrue           L2, r3

With -funsafe-intrinsics it looks much better:

L2:
   LoadFromEnvironment r7, r2, 3
   Loadi32           r7, r7, r4
   Add32             r1, r7, r1
   Add32             r4, r4, r6
   Sub32             r3, r3, r5
   Mov               r0, r1
   JmpTrue           L2, r3

Lots of more optimizations are possible, but as I said this is very early work.

tmikov avatar Aug 25 '21 01:08 tmikov

@tmikov What sort of shape is wasm support in at this point? Does wasm2js still require a custom build of Hermes? Are things likely to be unstable if we try it?

evelant avatar Jan 08 '22 02:01 evelant

@evelant there has been no change since my last post. The tentative plan is to get an intern to work on the next phase (Wasm bytecode support), but I haven't gotten one yet.

tmikov avatar Jan 10 '22 17:01 tmikov

We really need WASM support in RN while we are using some universal platform supported bundler like Taro or RN-web. We are building cross platform(web / native / mini programs) apps that depend on trusted WASM machines to work.

yiheyang avatar Feb 07 '22 13:02 yiheyang

@tmikov Are the optimizations for asm.js still experimental per the above or have they been included in more recent versions of Hermes?

evelant avatar Feb 13 '22 17:02 evelant

@tmikov point me in the right direction with your tentative plan and I'll see if my progress on a related implementation can make a dent

franz-fletcher avatar Apr 05 '22 01:04 franz-fletcher

Sincere question: what is the advantage of using WASM in a RN app where actual native C++ is available? WASM makes a lot of sense in a browser, but a RN app by definition ships a lot of native code already.

tmikov avatar Apr 05 '22 01:04 tmikov

Sincere question: what is the advantage of using WASM in a RN app where actual native C++ is available? WASM makes a lot of sense in a browser, but a RN app by definition ships a lot of native code already.

just refer to my comment above, as we don't want to bind our native code again just for the RN side.

yiheyang avatar Apr 05 '22 03:04 yiheyang

Sincere question: what is the advantage of using WASM in a RN app where actual native C++ is available? WASM makes a lot of sense in a browser, but a RN app by definition ships a lot of native code already.

It is a valid question, and native C++ bindings via JSI and TurboModules are a very thorough implementation of an interface that will enhance existing libraries in the Native Module ecosystem. We will all benefit from Partners and contributors who have the capacity and context necessary to provide refactored native modules that incorporate the new architecture. I think that, however, there is a slight disincentivising barrier to entry to developing a native module with C++ to implement an optimization task that is trivial but computationally expensive.

WASM technology is mature and there are some great binaries that are helping React Developers ship quality near-native web applications. As a result, they are empowered to experiment with lower level languages like Go and Rust, which have less intimidating learning curves and implementation taxes.

I do not think you are making the wrong assumption, and I do believe that native C++ bindings are the only way to create a native module which can benefit from all your hard work into everything the new architecture has to offer.

I really believe that if the vision is to create an ecosystem in which react developers can confidently deliver solutions across all platforms, it is worthwhile to take WASM support as a strategic step toward creating a pathway that encourages further exploration into the new native module architecture.

franz-fletcher avatar Apr 05 '22 04:04 franz-fletcher

Keep in mind that WASM doesn't automatically provide near-native performance. WASM is more or less binary encoding of a subset of JavaScript. There is nothing that magically makes it fast.

Obtaining near-native performance from WASM requires a sophisticated ahead-of-time compiler or a sophisticated JIT. However, unlike other JS VMs, Hermes doesn't already implement a JavaScript JIT on which to base its WASM JIT. One of the main goals of Hermes was to develop a minimalistic JS runtime, with very fast startup time and minimal memory, under the assumption that UI is not very CPU intensive, precluding a JIT. As with everything else, it is a trade-off.

So, near-native WASM performance in Hermes would require adding a new advanced JIT. Such a JIT (for example based on LLVM) could easily rival the rest of Hermes in size and complexity. This is certainly technically possible, however we would be doubling the size and complexity of the VM, while also adding significant memory and latency overhead (compiling WASM to optimized native takes significant amount of time and memory) only to achieve near-native performance. At the same time, React Native already can use fully native C++ performance. It is simply not a very constructive investment.

It is not all bad. The approach to WASM on which we have been slowly working on, and which I have described in prior posts in this task, would not double the size of Hermes and would not add significant memory overhead. However it does not approach native performance, since it is still running interpreted bytecode. Yes, up to 5x faster than JS in Hermes, but probably still at least 10x slower than native. Again, it is all about trade-offs.

We also have plans to go one step further and implement a minimalistic baseline JIT for the WASM-flavoured Hermes bytecode. "Baseline" means that the JIT doesn't perform optimizations, it simply eliminates the interpreter dispatch overhead. It results in up to 2x speed up, which is still far from "near-native", but it is small, low latency, and low memory.

There is one extra point - a JIT is not allowed on iOS, so WASM will never execute with near-native performance there, no matter what JIT Hermes has.

So, given that:

  1. It will not approach near native performance
  2. It will never run fast on iOS (at least until Apple changes their security policies)

a native module using JSI remains the best option if performance really matters.

tmikov avatar Apr 06 '22 00:04 tmikov

Keep in mind that WASM doesn't automatically provide near-native performance. WASM is more or less binary encoding of a subset of JavaScript. There is nothing that magically makes it fast.

Obtaining near-native performance from WASM requires a sophisticated ahead-of-time compiler or a sophisticated JIT. However, unlike other JS VMs, Hermes doesn't already implement a JavaScript JIT on which to base its WASM JIT. One of the main goals of Hermes was to develop a minimalistic JS runtime, with very fast startup time and minimal memory, under the assumption that UI is not very CPU intensive, precluding a JIT. As with everything else, it is a trade-off.

So, near-native WASM performance in Hermes would require adding a new advanced JIT. Such a JIT (for example based on LLVM) could easily rival the rest of Hermes in size and complexity. This is certainly technically possible, however we would be doubling the size and complexity of the VM, while also adding significant memory and latency overhead (compiling WASM to optimized native takes significant amount of time and memory) only to achieve near-native performance. At the same time, React Native already can use fully native C++ performance. It is simply not a very constructive investment.

It is not all bad. The approach to WASM on which we have been slowly working on, and which I have described in prior posts in this task, would not double the size of Hermes and would not add significant memory overhead. However it does not approach native performance, since it is still running interpreted bytecode. Yes, up to 5x faster than JS in Hermes, but probably still at least 10x slower than native. Again, it is all about trade-offs.

We also have plans to go one step further and implement a minimalistic baseline JIT for the WASM-flavoured Hermes bytecode. "Baseline" means that the JIT doesn't perform optimizations, it simply eliminates the interpreter dispatch overhead. It results in up to 2x speed up, which is still far from "near-native", but it is small, low latency, and low memory.

There is one extra point - a JIT is not allowed on iOS, so WASM will never execute with near-native performance there, no matter what JIT Hermes has.

So, given that:

  1. It will not approach near native performance
  2. It will never run fast on iOS (at least until Apple changes their security policies)

a native module using JSI remains the best option if performance really matters.

Thank you very much for clarification. Me, as most others, thought that WASM is fast because of WASM itself (it's "magic"), not because of JIT.

Now I get understanding V8 made it fast.

likern avatar Apr 06 '22 13:04 likern

Keep in mind that WASM doesn't automatically provide near-native performance. WASM is more or less binary encoding of a subset of JavaScript. There is nothing that magically makes it fast. Obtaining near-native performance from WASM requires a sophisticated ahead-of-time compiler or a sophisticated JIT. However, unlike other JS VMs, Hermes doesn't already implement a JavaScript JIT on which to base its WASM JIT. One of the main goals of Hermes was to develop a minimalistic JS runtime, with very fast startup time and minimal memory, under the assumption that UI is not very CPU intensive, precluding a JIT. As with everything else, it is a trade-off. So, near-native WASM performance in Hermes would require adding a new advanced JIT. Such a JIT (for example based on LLVM) could easily rival the rest of Hermes in size and complexity. This is certainly technically possible, however we would be doubling the size and complexity of the VM, while also adding significant memory and latency overhead (compiling WASM to optimized native takes significant amount of time and memory) only to achieve near-native performance. At the same time, React Native already can use fully native C++ performance. It is simply not a very constructive investment. It is not all bad. The approach to WASM on which we have been slowly working on, and which I have described in prior posts in this task, would not double the size of Hermes and would not add significant memory overhead. However it does not approach native performance, since it is still running interpreted bytecode. Yes, up to 5x faster than JS in Hermes, but probably still at least 10x slower than native. Again, it is all about trade-offs. We also have plans to go one step further and implement a minimalistic baseline JIT for the WASM-flavoured Hermes bytecode. "Baseline" means that the JIT doesn't perform optimizations, it simply eliminates the interpreter dispatch overhead. It results in up to 2x speed up, which is still far from "near-native", but it is small, low latency, and low memory. There is one extra point - a JIT is not allowed on iOS, so WASM will never execute with near-native performance there, no matter what JIT Hermes has. So, given that:

  1. It will not approach near native performance
  2. It will never run fast on iOS (at least until Apple changes their security policies)

a native module using JSI remains the best option if performance really matters.

Thank you very much for clarification. Me, as most others, thought that WASM is fast because of WASM itself (it's "magic"), not because of JIT.

Now I get understanding V8 made it fast.

I second this. It's always great to get a better understanding of the architectural decisions behind the journey to JSI and the New Architecture.

I ran into CXX — safe interop between Rust and C++ yesterday. Haven't found time to play with it but it could open the door to some level of Rust support for Native Modules.

franz-fletcher avatar Apr 06 '22 14:04 franz-fletcher

The high profile implementations of Wasm have been getting more and more complex. For example, Mozilla had to implement multi-level JIT for Wasm, in order to balance fast startup with high performance. In other words, they use the base level JIT initially to get things running right away, albeit slower, then later optimize important functions with a higher level JIT. This is what JS engines have been doing with JavaScript for a long time and is precisely the thing Hermes was built to avoid. So, it is ironic that we are coming back to it, but this time from the Wasm angle.

We will keep working on supporting Wasm. I believe we can go a long way with our minimalistic approach and make performance acceptable in most cases, especially on Android (with a baseline JIT). But admittedly, it is not a high priority. I am still waiting for an intern to work on the next stage.

tmikov avatar Apr 06 '22 20:04 tmikov

One interesting idea to think about: perhaps it might be possible to provide build scripts, workflows and APIs in Hermes to make using native code look similar to using Wasm.

tmikov avatar Apr 06 '22 20:04 tmikov

One interesting idea to think about: perhaps it might be possible to provide build scripts, workflows and APIs in Hermes to make using native code look similar to using Wasm.

That would be great! I think the desire for wasm support probably boils down to the use case -- at least in my case as a react-native developer I want an easy and seamless way to write high performance cross platform code that runs outside of the main react-native JS thread.

AFAIK the only way to do that now is to manually compile some code for multiple platforms and manually write bridging code to RN, definitely not seamless or easy for most people. Wasm support seems like it would make it much easier, just target wasm from pretty much any language and include your module.

If workflows can be developed to make using native code in popular languages (in particular I'm interested in Rust) as easy as using wasm would be a nice solution too, although it might not be as portable as using wasm if people need to run the code for example in react-native-web.

evelant avatar Apr 09 '22 14:04 evelant

One interesting idea to think about: perhaps it might be possible to provide build scripts, workflows and APIs in Hermes to make using native code look similar to using Wasm.

Something like assembly script would be amazing for integration that goes along the lines of the idea you've proposed, especially with simplifying typings between the JavaScript component declarations and their usage in native code.

franz-fletcher avatar Apr 09 '22 15:04 franz-fletcher

Another idea -- rather than adding wasm support to hermes with all the complexity that seems to entail might it make more sense to integrate an existing wasm runtime like wasmer? https://github.com/wasmerio/wasmer

I'm not sure if that suggestion makes sense in this context however, I don't know enough about the native side of RN to know if such a thing is feasible.

evelant avatar Apr 09 '22 20:04 evelant

FWIW, we have added initial support for Wasm encoded as Asm.js. You can use wasm2js to transform your Wasm module to Asm.js and compile and run the result with Hermes. That was possible even before, but latest Hermes can optionally recognize the Asm.js intrinsics and optimize them, resulting in about 5x speed up.

It is still very early, highly experimental, and definitely unsupported, but it does work. It requires a custom build of the latest Hermes with the HERMES_RUN_WASM flag, as well as -funsafe-intrinsics when invoking the compiler. For now it can only be used to run trusted code.

A small C/C++ example:

unsigned add(const unsigned * arr, unsigned count) {
    unsigned res = 0;
    while (count--) res += *arr++;
    return res;
}

Previously the inner loop was compiled very naively like this:

L2:
    LoadFromEnvironment r9, r2, 3
    RShift            r8, r4, r7
    GetByVal          r8, r9, r8
    ToInt32           r8, r8
    AddN              r8, r8, r1
    ToInt32           r1, r8
    AddN              r9, r4, r6
    ToInt32           r4, r9
    SubN              r9, r3, r5
    ToInt32           r3, r9
    Mov               r0, r1
    JmpTrue           L2, r3

With -funsafe-intrinsics it looks much better:

L2:
   LoadFromEnvironment r7, r2, 3
   Loadi32           r7, r7, r4
   Add32             r1, r7, r1
   Add32             r4, r4, r6
   Sub32             r3, r3, r5
   Mov               r0, r1
   JmpTrue           L2, r3

Lots of more optimizations are possible, but as I said this is very early work.

@tmikov do you have an example repo for this? would I need a hermes fork or just a patch?

gabimoncha avatar Jun 23 '22 09:06 gabimoncha

@gabimoncha this is part of Hermes but is controlled by the HERMES_RUN_WASM build flag, which is disabled in the default build.

tmikov avatar Jun 23 '22 17:06 tmikov