
Calls between JavaScript and WebAssembly are finally fast - lainon
https://hacks.mozilla.org/2018/10/calls-between-javascript-and-webassembly-are-finally-fast-%f0%9f%8e%89/
======
bluejekyll
Very exciting! And really amazing work. It appears that WASM, at least in
Firefox, is now going to be on par with JS in terms of performance when
calling other JS facilities. Those comparison times are quite impressive.

I think the thing I found most exciting is at the end: “WebAssembly is getting
more flexible types very soon. Experimental support for the current proposal
is already landed in Firefox Nightly.” Implication being that WASM will have
native access to the DOM.

I think at that point WASM could be used to fully work with the DOM with no
negative costs, right?

~~~
singularity2001
maybe the most important improvement will be direct memory access to the
canvas bitmaps for super fast game rendering.

~~~
bschwindHN
Wouldn't "super fast game rendering" be done by the GPU via WebGL?

~~~
rafaelvasco
For 2D games it's much more convenient to pixel push directly than to deal
with vertex buffers, index buffers, shaders... Unfortunately graphics cards
are not optimized for that scenario.

~~~
bschwindHN
Convenient, but "super fast"?

And though it's a matter of opinion, I disagree. The GPU is easier for me to
use for things like screen-space shading, alpha blending, sprite
transformations and distortions, and it all performs much faster than the
equivalent CPU render code.

------
markdog12
Another interesting flag I found was javascript.options.wasm_cranelift, which
enables a WASM code generator written in Rust:
[https://github.com/CraneStation/cranelift](https://github.com/CraneStation/cranelift)

~~~
bnjbvr
Good find! At the moment it is very very experimental and you might experience
crashes and slowdowns if you're using it in place of the regular flags (that
is why it is enabled only on Nightly builds and will not be enabled on
Beta/Release for a while). We'll make sure to talk about it when the right
time has come.

------
lemmyg
Exposing Web APIs (e.g. DOM) to WASM and projecting them into native languages
seems like it overlaps a lot with defining actual native API projections that
would be shared between browser implementations.

It doesn't read like this is an explicit goal of the project, but are we going
to get this by accident? Being able to use the same X code (where X is your
favourite statically-compilable language) to generate both a WASM version that
runs on the web and a statically-compiled version that links to the Y browser
source (where Y can be whichever browser works for you because they all
support the same native API) would be awesome.

------
mmastrac
Kudos to the author of this post - it takes a lot of skill to explain complex
concepts like this, let alone in plain enough english that someone not skilled
in the art could follow along.

WebAssembly is one of those technologies where we haven't seen the true extent
of its capabilities yet. This is an exciting time to be working in browsers.

~~~
rayiner
Don’t get me wrong this is a great article. But I found the analogy with the
people and pieces of paper a bit hard to read. (The analogy with the people
made me think the author was talking about some sort of prototype delegation
chain, [https://giamir.com/alternative-patterns-in-JS-OLOO-
style](https://giamir.com/alternative-patterns-in-JS-OLOO-style), rather than
nested function calls.) Who is clicking on this but doesn’t know what a stack
frame is?

~~~
kbenson
> Who is clicking on this but doesn’t know what a stack frame is?

JS developers that don't have experience in lower level languages or some CS
concepts. In other words, probably a good portion of the expected audience
given it's about JS and possible improvements, and is likely to trickle into
the news sources to cater to those people.

~~~
btown
Notably, with the advent of things like Unity that compile to WebAssembly,
people could be programming frequent communications between WebAssembly and
JS, without ever having learned a low-level language involving pointers (which
would be the situation in which one would learn about stack frames).

------
steipete
You can see the difference in the WebAssembly Benchmark:
[http://iswebassemblyfastyet.com](http://iswebassemblyfastyet.com)

~~~
officialchicken
Chrome linux (v69.0.3497.81) 4916

Firefox linux (v62.0.3)1904

~~~
robocat
Firefox v62.0.3 doesn't have this feature enabled?

From article: "This means that in the latest version of Firefox __* Beta __*,
calls between JS and WebAssembly are faster than non-inlined JS to JS function
calls. "

~~~
lambda
"The score is the total benchmark time. The lower the score, the better."

------
Ooveex2C
Do wasm -> builtin calls, e.g. DOM methods still call the JS code if JS code
has been monkey-patched?

E.g. if userscript replaces the built-in window.fetch() API to modify page
behavior will wasm also be intercepted?

~~~
andhow
At the moment, wasm can only call functions that were passed as values of the
import object which is passed to to one of the instantiation functions (e.g.
[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate)), so
which function gets called is up to the embedding JS code.

------
finchisko
Lin is awesome, but reading this nice article I have one noob question.

> We took the code that C++ was running — the entry stub — and made it
> directly callable from JIT code. When the engine goes from JavaScript to
> WebAssembly, the entry stub un-boxes the values and places them in the right
> place. With this, we got rid of the C++ trampolining.

So they replaced unboxing C++ trampoline with entry stub. But isn't that stub
technically written in C++ too?

~~~
bzbarsky
Based on a quick look through
[https://bugzilla.mozilla.org/show_bug.cgi?id=1319203](https://bugzilla.mozilla.org/show_bug.cgi?id=1319203)
it looks like the entry stub is generated by the JIT itself. So no, the entry
stub is not written in C++. It gets output by the JIT as part of the generated
jitcode.

Specifically, see the GenerateJitEntry bits in
[https://bug1319203.bmoattachments.org/attachment.cgi?id=8949...](https://bug1319203.bmoattachments.org/attachment.cgi?id=8949103)
\-- the masm.whatever() calls are calls into an architecture-dependent
assembler that will output the relevant machine instructions.

There is still an out-of-line codepath that does call into a C++-implemented
entry stub in some cases when the argument conversions involved are too
complicated to do them directly in the JIT-generated code.

~~~
mathw
Thanks for this, I was also quite confused as to why the "jumping through C++"
stages were so expensive. Thinking about what you've said, it makes a lot more
sense that you're moving between two worlds and marshalling arguments back and
forth in a variety of expensive ways. It's a shame Lin didn't explain that a
little more in an otherwise excellent article, but I guess she was focussing
on the JS/JIT/WASM story.

------
jokoon
Function calls from WASM to JS don't seem to have improved so much.

I don't know what I want more, DOM calls from WASM, or C++ modules. A web
without javascript would be welcome.

~~~
nkozyra
> A web without javascript would be welcome.

Why? What is it that wasm or c++ provides you in making a DOM call that modern
JS does not?

~~~
pjmlp
The 2nd coming of Flash?

~~~
danShumway
Hopefully not.

Some devs _are_ treating WASM as an entire runtime and outputing canvas/webGL
code. I think that is mostly a mistake, and I'll be very disappointed if that
becomes the defacto approach for new web devs.

However, the cool thing about WASM is that it's _just_ the code part. So you
can build an app that's still normal CSS/HTML, just like every web-app should
be, and then you can replace all of your JS with WASM (with the current
exception of a little bit of JS-to-DOM binding code which Mozilla has promised
to eventually get rid of).

In the world of Flash/Java you're essentially working in a completely separate
platform, it just happens to be launched from a web browser. So shortcuts
don't work, accessibility is awful, users can't customize the page, extensions
are broken, etc, etc...

With WASM, you don't have to be in that position. You can say, "I'm a web
developer, I treat the web like a medium, I write responsive HTML/CSS. I
_just_ use C instead of Javascript."

~~~
pjmlp
> In the world of Flash/Java you're essentially working in a completely
> separate platform, it just happens to be launched from a web browser. So
> shortcuts don't work, accessibility is awful, users can't customize the
> page, extensions are broken, etc, etc...

True, but until Web development catches up with the state of Flash and other
native RAD GUI designers, it is a very attractive proposition.

One thing I hate when doing web development is playing around with CSS and tag
soup while trying to make it work the same way across all target platforms
required by the customer, that in native code would be a couple of graphical
calls to the visualization engine and platform widgets, which also provide
layout managers.

Stuff like Web Components and Houdini would make it better, but who knows when
they will be widely available.

~~~
danShumway
> while trying to make it work the same way across all target platforms

This goes back to what I was talking about with treating the web like a
medium, not a distribution platform. Fundamentally, the web is about giving
you a "controlled" way to give up control. The native paradigm is "I want
control over what each individual pixel looks like", and the web paradigm is,
"heck off, well-written apps don't do that."

I understand why it's attractive, and why it'll continue to be attractive. And
I _highly_ agree about Houdini being an exciting development (I don't
personally think that Web Components are offering much, but whatever). I'm
really looking forward to being able to polyfill CSS. But that difference
you're talking about is probably not ever going to go away completely, because
the web is optimizing to solve different problems than most native frameworks.

Not to say one approach is better than the other (Flash had some legitimate
use cases), but I find that there is an actual cultural difference between how
web-apps and native apps are designed and how they balance between user
control and developer control. It's not just technology.

------
benjaminjackman
While searching for more information on a configuration value mentioned in the
article (javascript.options.wasm_gc) I came across another article on the
topic, which I also found interesting because it's a bit more technical:

[https://blog.benj.me/2018/07/04/mozilla-2018-faster-calls-
an...](https://blog.benj.me/2018/07/04/mozilla-2018-faster-calls-and-anyref/)

------
writepub
I hope the chrome team is looking at this too, with 65+% market share, it'll
be real impactful when chrome makes similar optimizations.

------
huang47
Lin is amazing. I do enjoy reading her posts and learn a lot from them.

------
nikeee
Did anyone re-implement the basic React API surface, using Wasm as a backend
for the Virtual DOM? To me, it sounds like this would be an interesting
project.

~~~
dstaley
Not totally the same, but Blazor[1] is a similar framework powered by dotnet
on wasm. As a React dev, looking at a Blazor component feels very familiar.
I'd love to see something implemented in Go or another language without all
the extra baggage dotnet comes with currently.

[1] [https://blazor.net](https://blazor.net)

~~~
fersc
There's also yew[1], which cites ReactJS as a direct influence. It implements
a virtualDOM and is quite fast[2]. One of my main motivations for learning
Rust right now.

[1][https://github.com/DenisKolodin/yew](https://github.com/DenisKolodin/yew)

[2][https://github.com/DenisKolodin/todomvc-perf-
comparison](https://github.com/DenisKolodin/todomvc-perf-comparison)

------
twoodfin
I’m a little surprised that making cross-language inlining trivial wasn’t
(apparently) a design goal of the original WASM spec.

(Maybe it’s functionally easy but getting the heuristics right is hard?
Curious what the primary obstacles are for Mozilla or others.)

~~~
notriddle
The article pretty much described what the big obstacle was: boxing. If WASM
had boxing, then it wouldn't be any faster than JavaScript and there'd be no
point. Since JavaScript requires boxing, there's no way to avoid the impedance
mismatch at the boundary between them, and a JIT that wants to support both
has to be aware of both boxed and unboxed numbers.

The WASM spec already tries to avoid pretty much every other big problem that
would've confounded attempts to cross-language inline. For example, WASM
requires structured control flow (IOW, WASM doesn't have `goto`), avoids type
punning in favor of a reinterpret primitive, uses function addresses that are
orthogonal to memory addresses, and allows non-deterministic floating point
bit representations, all to match up closer with the way existing JavaScript
JITs already work. But they can't do anything about boxing without either
breaking the web or making WebAssembly essentially a binary encoding of the
JavaScript language.

------
DonHopkins
Firefox loads and runs Unity3D WebGL apps MUCH faster than Chrome.

The point of UnityJS is to tightly and efficiently integrate Unity3D and
JavaScript, so it does a lot of JavaScript <=> C# calls, and I'm looking
forward to it getting even faster!

[https://github.com/SimHacker/UnityJS](https://github.com/SimHacker/UnityJS)

You can pass delegates to C# functions that are directly callable into
JavaScript using some magic PInvoke attributes and the Unity Runtime.dynCall
function.

Declare a delegate that describes the signature of your C# function you want
to call from JavaScript:

[https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass...](https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Assets/Libraries/UnityJS/Scripts/BridgeTransportWebGL.cs#L35)

    
    
        public delegate int AllocateTextureDelegate(int width, int height);
    

Then declare a C# static method with the MonoPInvokeCallback attribute, to
implement you C# function:

[https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass...](https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Assets/Libraries/UnityJS/Scripts/BridgeTransportWebGL.cs#L82)

    
    
        [MonoPInvokeCallback(typeof(AllocateTextureDelegate))]
        public static int AllocateTexture(int width, int height) { ... }
    

Then pass those specially marked delegates to JavaScript and stash them in JS
variables when you initialize (it doesn't work unless you use the magic
MonoPInvokeCallback attribute):

[https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass...](https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Assets/Libraries/UnityJS/Scripts/BridgeTransportWebGL.cs#L48)

    
    
        [DllImport(PLUGIN_DLL)]
        public static extern void _UnityJS_HandleAwake(AllocateTextureDelegate allocateTextureCallback, FreeTextureDelegate freeTextureCallback, LockTextureDelegate lockTextureCallback, UnlockTextureDelegate unlockTextureCallback);
    

[https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass...](https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Assets/Libraries/UnityJS/Scripts/BridgeTransportWebGL.cs#L175)

    
    
        public override void HandleAwake()
        {
            //Debug.Log("BridgeTransportWebGL: HandleAwake: this: " + this + " bridge: " + bridge);
    
            _UnityJS_HandleAwake(
                AllocateTexture,
                FreeTexture,
                LockTexture,
                UnlockTexture);
        }
    

In the awake function on the JavaScript side of your Unity WebGL extension (a
.jslib file), wrap the C# delegate in a JavaScript thunk that calls into it
via Runtime.dynCall:

[https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass...](https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Assets/Libraries/UnityJS/Plugins/WebGL/UnityJS.jslib#L11)

    
    
        // Called by Unity when awakened.
        _UnityJS_HandleAwake: function _UnityJS_HandleAwake(allocateTextureCallback, freeTextureCallback, lockTextureCallback, unlockTextureCallback)
        { [...]
            function _UnityJS_AllocateTexture(width, height)
            {
                //console.log("UnityJS.jslib: _UnityJS_AllocateTexture: width: " + width + " height: " + height + " allocateTextureCallback: " + allocateTextureCallback);
                var result = Runtime.dynCall('iii', allocateTextureCallback, [width, height]);
                //console.log("UnityJS.jslib: _UnityJS_AllocateTexture: result: " + result);
                return result;
            };
            window.bridge._UnityJS_AllocateTexture = _UnityJS_AllocateTexture;
    

Then you can call the C# method from JavaScript:

[https://github.com/SimHacker/UnityJS/blob/f0a4e1fb07fd9e8aab...](https://github.com/SimHacker/UnityJS/blob/f0a4e1fb07fd9e8aaba2849fefd1d2239c262a97/UnityJS/Assets/StreamingAssets/game.jss#L348)

    
    
        params.cache.backgroundSharedTextureID = id = 
            window.bridge._UnityJS_AllocateTexture(params.width, params.height);
    

This is zillions of time faster and more flexible than using Unity's terrible
SendMessage technique to send messages from JS=>C#, whose only parameter is a
single string, and which inefficiently dispatches messages by looking up Unity
objects by name, and is asynchronous and can't return a result.

I use this technique to efficiently copy binary textures and arrays of numbers
between JavaScript and C#. MUCH better than serializing it as JSON, or base 64
encoded PNG files in a data: url (yuck!).

[https://github.com/SimHacker/UnityJS/blob/674eda49be12a70812...](https://github.com/SimHacker/UnityJS/blob/674eda49be12a7081205b6af4bf3986cb76ac7d7/UnityJS/Assets/StreamingAssets/game.jss#L382)

    
    
        function DrawToCanvas(params, drawer, success, error)
        { [...]
    
                        var id = params.pie.backgroundSharedTextureID;
                        if (!id) {
                            params.pie.backgroundSharedTextureID = id = 
                                window.bridge._UnityJS_AllocateTexture(params.width, params.height);
                            //console.log("game.js: DrawToCanvas: WebGL: AllocateTexture: width: " + params.width + " height: " + params.height + " id: " + id);
                        }
                        var imageData =
                            context.getImageData(0, 0, params.width, params.height);
                        window.bridge._UnityJS_UpdateTexture(id, imageData);
                        texture = {
                            type: 'sharedtexture',
                            id: id
                        };
                        success(texture, params);
                        canvasNode.parentNode.removeChild(canvasNode);
    

This lets me draw 2D user interface stuff, pie charts, diagrams, data
visualizations, etc, in JavaScript with canvas, d3, or whatever library I
like, and then efficiently use those images in Unity3D as user interface
overlays, 3D textures, etc. It works great, and it's smooth and interactive,
mixing up 2D canvas graphics with 3D Unity stuff!

Unity is sorely lacking a decent 2D drawing library like canvas, not to
mention fancy stuff built on top of it like d3.

I'm currently working on the plumbing to send binary arrays of floats from
JavaScript to Unity, so I can pass them right into shaders!

Here's some discussion about the magic MonoPInvokeCallback attribute:

[https://forum.unity.com/threads/monopinvokecallback-in-
unity...](https://forum.unity.com/threads/monopinvokecallback-in-
unity.132510/)

And about Unity.dyncall and the WebGL runtime:

[https://forum.unity.com/threads/c-jslib-2-way-
communication....](https://forum.unity.com/threads/c-jslib-2-way-
communication.323629/)

and:

[https://forum.unity.com/threads/super-fast-javascript-
intera...](https://forum.unity.com/threads/super-fast-javascript-interaction-
on-webgl.382734/)

------
NVRM
« ... calls between JS and WebAssembly are faster than non-inlined JS to JS
function calls. »

Good note taken.

------
skrowl
Looking forward to testing this with Blazor. This was the biggest problem
currently.

------
infogulch
Editing note: the first sentence under "Optimizing JavaScript » WebAssembly
calls" seems to be repeated twice with different phrasings.

------
freecodyx
some people are just good with explaining stuff,

------
catchmeifyoucan
This was explained really well! I feel more knowledgeable now.

------
merhard
wasted opportunity to mention apple pie in the example function.

------
simplecomplex
Fast? What makes webapps slow isn’t JavaScript, it’s downloading 2 fucking
megs of total shit to show the user a bunch of text and form inputs.

