14 ms·
Calls between JavaScript and WebAssembly are finally fast
- freecodyx 8y agosome people are just good with explaining stuff,
- mmastrac 8y agoKudos 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.
- joshmarinacci 8y agoYep. Lin is amazing.
- symboltoproc 8y agoThis definitely deserves praise.
- rayiner 8y agoDon’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?
- sandyarmstrong 8y ago> Who is clicking on this but doesn’t know what a stack frame is? Probably a lot of JavaScript developers who started in web land and are only now starting to be exposed to the underlying architecture.
- kbenson 8y ago> 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 8y agoNotably, 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).
- tomjakubowski 8y agoIt's funny, I know about stacks, call stacks, and call stack frames. And yet the analogy to a literal stack of papers with information written on each one still gave me something new to chew on. (For some reason, I never actually thought to visualize like... a physical stack of data before.)
- DonHopkins 8y agoI made a visual programming and debugging environment for PostScript, a stack based language, that let you perform "direct stack manipulation" by dragging object up and down and on and off of a "spike" stack that impaled the little tabs sticking out of objects. https://medium.com/@donhopkins/the-shape-of-psiber-space-october-1989-19e2dfa4d91e https://medium.com/@donhopkins/the-shape-of-psiber-space-oct...
- rhencke 8y agoAs someone who spent a few years having to debug PostScript with pstack and GhostScript running in a fixed-size command-prompt window in the 2010-ish timeframe... this looks futuristic even today. Was the idea with the direct stack manipulation dragging you describe to provide visual analogues to PostScript commands like roll/pop/etc?
- DonHopkins 8y agoThat's right -- you could rearrange their vertical order to manipulate their place on the stack, or you could lift them off of the stack and let them float by themselves, just to have a visual reference you can click on, drag and drop into other objects, etc. It was very dangerous since you were editing the live data structures of the window system! The most complicated program I used it to debug was itself. It was integrated with NeWS's multithreaded debugger, so when some process hit a bug or a breakpoint, you could "enter" the process and see what was on its stack, peek and poke at the objects, rearrange the stack and edit the objects, then send it on its way. Since PostScript is homoiconic like Lisp, code and objects are made of arrays and dictionaries, which you could visually browse, edit, and execute. Very much inspired by Smalltalk, but with a twist of Lisp and FORTH!
- Waterluvian 8y agoA _lot_ of developers who have graduated to the next layer, who can focus even more on building stuff without worrying about implementation details.
- irrational 8y ago>Who is clicking on this but doesn’t know what a stack frame is? 99% of all the JS developers I've ever met?
- pteredactyl 8y agoFeeling insecure?
- LandR 8y agoHe's not wrong IME.
- pcwalton 8y ago> Who is clicking on this but doesn’t know what a stack frame is? The primary target audience is Web developers interested in new technologies. I'd wager most of them don't know what a stack frame is and would appreciate the explanation.
- golergka 8y agoI hire C# developers, and most of candidates, even with a couple of years of experience, have no idea about how stack works.
- tracker1 8y agoThe majority of developers who work in scripted or higher level languages like C# or Java. For the most part, you don't have to think about them (even in C#/Java interop with C/C++, it's usually a non-issue).
- zmw 8y agoIMO developers who work in scripted or higher level languages and never inspected a call stack have a serious problem (unless they write perfect code that works on the first attempt 100% of the time and never have to debug). I mean, printf debugging is okay, but at some point, preferably early on, you’re expected to graduate out of that (not throwing it out completely, but rather, learn a better way for more complex situations). Plus the fact that most scripting languages throw stack traces all the time.
- rch 8y agoYeah, I stopped reading when I got to the cartoons. It's interesting to me that it's well received generally though -- I can appreciate that it might be helpful.
- fucking_tragedy 8y agoIt was well written, but throughout the entire article I was hoping I'd learn what this thing was and why it was represented this way[1]. [1] On the right: https://2r4s9p1yi1fa2jd7j43zph8r-wpengine.netdna-ssl.com/files/2017/02/02-01-alien03.png https://2r4s9p1yi1fa2jd7j43zph8r-wpengine.netdna-ssl.com/fil...
- andhow 8y agoThat came from one of Lin's first posts about WebAssembly: https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/ https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-...
- drzaiusx11 8y agoThe "thing" on the right is a reference to the aliens in the movie Arrival where the main character (a linguistics professor) communicates with tentacled smoke beings by holding up white board messages.
- fucking_tragedy 8y agoThanks, the reference went right over my head. Was the movie any good?
- tshepang 8y agovery good
- aaronblohowiak 8y agoAll of Lin's stuff is amazing. https://hacks.mozilla.org/category/code-cartoons/ https://hacks.mozilla.org/category/code-cartoons/
- merhard 8y agowasted opportunity to mention apple pie in the example function.
- jokoon 8y agoFunction 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 8y ago> 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 8y agoThe 2nd coming of Flash?
- knightofmars 8y agoYou may have said this in jest and are getting down-voted for it but this isn't an impossible scenario. I think of this article in relation to what WebAssembly means. https://dzone.com/articles/java-getting-back-to-the-browser https://dzone.com/articles/java-getting-back-to-the-browser
- pjmlp 8y agoI trully believe it will happen in a couple of years, unless browser vendors change course regarding WebAssembly. Ongoing POC of running Unity games, .NET, Go, Qt and many others already show it is the direction many are moving into.
- knightofmars 8y agoI agree and think it's amazing. I'm looking forward to that day and when it comes. The other side is that it's also likely to open up a whole new avenue for web based security exploits.
- 8y ago
- Ooveex2C 8y agoDo 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 8y agoAt 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/Reference/Global_Objects/WebAssembly/instantiate https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...), so which function gets called is up to the embedding JS code.
- bluejekyll 8y agoVery 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?
- markharper 8y agoAlmost, that will set the foundation for fully using the DOM APIs. The host bindings proposal mentioned at the end of the article should close the gap.
- benjaminjackman 8y agoMinor clarification: The DOM builtins should be helped by the Reference Types Proposal for WebAssembly. [1] `The host bindings proposal` is going to help speed up new and getter/setter calls. [2] 1: `Currently the only built-ins that we support this for are mostly limited to the math built-ins. That’s because WebAssembly currently only has support for integers and floats as value types. That works well for the math functions because they work with numbers, but it doesn’t work out so well for other things like the DOM built-ins. So currently when you want to call one of those functions, you have to go through JavaScript. That’s what wasm-bindgen does for you. But WebAssembly is getting more flexible types very soon. Experimental support for the current proposal is already landed in Firefox Nightly behind the pref javascript.options.wasm_gc. Once these types are in place, you will be able to call these other built-ins directly from WebAssembly without having to go through JS.` -- https://github.com/WebAssembly/reference-types https://github.com/WebAssembly/reference-types 2: `there are still a couple of built-ins where you will need to go through JavaScript. For example, if those built-ins are called as if they were using new or if they’re using a getter or setter. These remaining built-ins will be addressed with the host-bindings proposal.` -- https://github.com/WebAssembly/host-bindings https://github.com/WebAssembly/host-bindings (BTW I could be mistaken, just quoting the article)
- writepub 8y agoI hope the chrome team is looking at this too, with 65+% market share, it'll be real impactful when chrome makes similar optimizations.
- infogulch 8y agoEditing note: the first sentence under "Optimizing JavaScript » WebAssembly calls" seems to be repeated twice with different phrasings.
- finchisko 8y agoLin 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 8y agoBased 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=8949103 https://bug1319203.bmoattachments.org/attachment.cgi?id=8949... -- 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 8y agoThanks 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.
- eridius 8y agoIt's not clear to me why calling into C++ is expensive to begin with. My best guess is it has to do with switching to a native stack (switching "folders"). So making the stub directly callable from the JIT means not having to change activations. Also presumably it uses the parameter passing conventions and semantics of all other JIT functions (whereas with a C++ function it presumably has to marshal the parameters to the function).
- twoodfin 8y agoI’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 8y agoThe 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.
- repolfx 8y agoIt's pretty hard to do well. If you look at Graal, a lot of the cutting edge research work they've done is about how to do cross-language inlining and subsequent optimisations better. It's more of a function of the JIT compiler design than language or bytecode specs though.
- huang47 8y agoLin is amazing. I do enjoy reading her posts and learn a lot from them.
- catchmeifyoucan 8y agoThis was explained really well! I feel more knowledgeable now.
- markdog12 8y agoAnother 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 8y agoGood 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.
- skrowl 8y agoLooking forward to testing this with Blazor. This was the biggest problem currently.
- steipete 8y agoYou can see the difference in the WebAssembly Benchmark: http://iswebassemblyfastyet.com http://iswebassemblyfastyet.com
- opencl 8y agoOn my machine I got, Firefox WASM: 1809ms Chrome WASM: 2855ms Edge WASM: 12872ms Firefox JS: 5413ms Chrome JS: 4779ms Edge JS: 8512ms
- lossolo 8y agoChrome linux 11835 Firefox linux 2132
- officialchicken 8y agoChrome linux (v69.0.3497.81) 4916 Firefox linux (v62.0.3)1904
- robocat 8y agoFirefox 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 8y ago"The score is the total benchmark time. The lower the score, the better."
- m_eiman 8y agoiPad Pro 9,7 (2016), iOS 12: 19542.
- benjaminjackman 8y agoWhile 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-and-anyref/ https://blog.benj.me/2018/07/04/mozilla-2018-faster-calls-an...
- DonHopkins 8y agoFirefox 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/Assets/Libraries/UnityJS/Scripts/BridgeTransportWebGL.cs#L35 https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass... 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/Assets/Libraries/UnityJS/Scripts/BridgeTransportWebGL.cs#L82 https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass... [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/Assets/Libraries/UnityJS/Scripts/BridgeTransportWebGL.cs#L48 https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass... [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/Assets/Libraries/UnityJS/Scripts/BridgeTransportWebGL.cs#L175 https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass... 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/Assets/Libraries/UnityJS/Plugins/WebGL/UnityJS.jslib#L11 https://github.com/SimHacker/UnityJS/blob/master/UnityJS/Ass... // 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/f0a4e1fb07fd9e8aaba2849fefd1d2239c262a97/UnityJS/Assets/StreamingAssets/game.jss#L348 https://github.com/SimHacker/UnityJS/blob/f0a4e1fb07fd9e8aab... 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/674eda49be12a7081205b6af4bf3986cb76ac7d7/UnityJS/Assets/StreamingAssets/game.jss#L382 https://github.com/SimHacker/UnityJS/blob/674eda49be12a70812... 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.132510/ https://forum.unity.com/threads/monopinvokecallback-in-unity... And about Unity.dyncall and the WebGL runtime: https://forum.unity.com/threads/c-jslib-2-way-communication.323629/ https://forum.unity.com/threads/c-jslib-2-way-communication.... and: https://forum.unity.com/threads/super-fast-javascript-interaction-on-webgl.382734/ https://forum.unity.com/threads/super-fast-javascript-intera...
- nikeee 8y agoDid 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 8y agoNot 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 8y agoThere'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
- hajile 8y agoIs there F# support for Blazor? Not a fan of C# for UI work (especially the kind the browser forces onto you).
- NVRM 8y ago« ... calls between JS and WebAssembly are faster than non-inlined JS to JS function calls. » Good note taken.
- simplecomplex 8y agoFast? 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.
- lemmyg 8y agoExposing 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.