Page MenuHomeWildfire Games

[extreme POC] Compile AssemblyScript to WASM within Spidermonkey & run the webAssembly module
Needs ReviewPublic

Authored by wraitii on May 17 2022, 8:49 PM.
This revision needs review, but there are no reviewers specified.

Details

Reviewers
None
Summary

Test on GitHub: https://github.com/wraitii/0ad/tree/WASM

This is, I stress, a proof of concept. I'm mostly uploading it so that it's out there.
Requires D3807 (Promises), an little extra (hacked in in ScriptContext), and D4575 (and a little extra). Also done on SM91 (D4428) because I could.

The idea is the following:

  • WebAssembly is a low-level language that runs in a VM. It is designed to be fast, and with some further JITTING about as fast as native code.
  • Many languages can be compiled to wasm. This includes C++, rust, and many others.
  • Spidermonkey can run web-assembly.
  • WebAssembly is somewhat interoperable with javascript (there seems to be a lot of fine prints to this, but that's the idea).
  • There exists a language called AssemblyScript (https://www.assemblyscript.org/, Apache 2 licence) that looks a lot like typescript (a 'typed' superset of Javascript). That compiler is coded in javascript.
  • If we can run the AssemblyScript compiler in Spidermonkey, we can compile AS to WASM, then load the WASM and call it from Spidermonkey, interoperably with native Javascript and C++.

With all of the above, we can just write AssemblyScript and run it directly as if it were a javascript module (essentially).

The main advantage is that WASM is supposed to be much faster than JavaScript, since it isn't JITted but compiled Ahead-Of-Time, like C++, while retaining 'rather good' compilation times, and, using AssemblyScript, we might be able to have a somewhat easy language to write in.
This would also lead to enabling loading of other WASM modules written in other languages, if modders want to do it, which ultimately may make 0 A.D. _more_ moddable.

Drawbacks include the difficulty of running the assemblyScript compiler, and the cost of AOT compilation of files, as well as potential limitation as the JS-WASM-C++ boundary. I think WASM might also be less compatible than Javascript but I need to clarify that.

Ultimately, I think we might consider this approach for things that are too slow in JS, but also too annoying to move to C++. Perhaps some components, more likely for example the UnitAI FSM or the modifier multimap as first targets. If performance is indeed improved, it might be a worthwhile approach.
More generally, I think running WASM is a net positive -> it makes it easier to write for 0 A.D., and that's just good.


What is actually done here:

  • Loading WebAssembly module basically just requires us to provide an OffThread promise runner (the bit added to ScriptContext). Then that basically just works™
  • Running the AssemblyScript compiler is a little trickier, since it's designed to run in a browser/Node environment.
    • We need to shim console.
    • We need to have ES module support.
    • We need to modify the files a little so that the import paths are correct.
    • We need support for the module 'meta' hook, which loads import.meta (trivial, I do nothing).
    • We need to ship the files. I've put them in a separate mod for convenience, ultimately I think we would want to abstract that away from the JS entirely is we were to support it.

What the example is:

  • Upon arriving in the main menu, it loads an ES Module
  • That ES Module loads the ASC compiler function
  • Then it compiles a trivial AS module
  • Then it runs it for demo.
Test Plan

If you can repro all the required dependencies, run the code. Agree that this might be desirable.

See GitHub branch: https://github.com/wraitii/0ad/tree/WASM

Event Timeline

wraitii created this revision.May 17 2022, 8:49 PM

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/7756/display/redirect

Build failure - The Moirai have given mortals hearts that can endure.

Link to build: https://jenkins.wildfiregames.com/job/macos-differential/6060/display/redirect

wraitii edited the summary of this revision. (Show Details)
wraitii edited the test plan for this revision. (Show Details)
wraitii edited the summary of this revision. (Show Details)
wraitii published this revision for review.May 17 2022, 10:15 PM
Stan awarded a token.May 18 2022, 9:20 AM

Recap of a talk with Mozilla SM devs today on their Matrix chat:

  • JS-WASM & JS-C++ interop can ultimately be expected to have about the same 'cost'. A dev said that the large cost was that IonCompiled code needed to dump registers in a GC-aware manner, in case the C++/Wasm call triggered a GC.
  • C++ <-> WASM interop is currently not implemented, and would have to go through JS, which I think makes the whole idea too inefficient right now. Though implementing c++ <-> wasm direct interop might be relatively easy to implement.
  • Given that we're gonna need the above in any case for the engine, this sounds like a somewhat complex path to optimisations.
Stan added a subscriber: Stan.May 18 2022, 1:53 PM

So it's not worthwile at all?

In D4653#198028, @Stan wrote:

So it's not worthwile at all?

It's worthwhile if/when SM adds better C++ <-> WASM interop, I think. Whether that's anytime soon is anyone's guess. Could also be a relevant area to work in.

I think C++ <-> WASM interop would have performance more comparable to lua scripting, that is almost native performance in some respect.

Stan added a comment.May 19 2022, 10:11 AM

That still sounds like something that should be tested. I wonder if the speed loss going through JS interfaces would not be mitigated by having a very fast component.

How hard would it be to create a performance heavy component in JS and in WASM and compare performance ?

In D4653#198191, @Stan wrote:

That still sounds like something that should be tested. I wonder if the speed loss going through JS interfaces would not be mitigated by having a very fast component.

Yes, that's a bit of a question.

How hard would it be to create a performance heavy component in JS and in WASM and compare performance ?

It's fairly difficult, partly because so much of our JS is just calling into C++ and querying data. We really don't have much that's just computation on the JS side. I think the map library might be the best use case, but haven't delved into it too much.

Stan added a comment.May 19 2022, 1:39 PM

How about the projectile code ?

In D4653#198209, @Stan wrote:

How about the projectile code ?

The projectile code is more something that should be rewritten in pure C++ IMO. But you're right, that particular function might work depending on the details.