3 min read

Yesterday, Mozilla announced that in the latest version of Firefox Beta, calls between JS and WebAssembly are faster than non-inlined JS to JS function calls. They have made these optimizations keeping two aspects of engine’s work in mind: reducing bookkeeping and cutting out intermediaries.

How they made WebAssembly function calls faster

They have optimized the calls in both directions, that is, from JavaScript to WebAssembly and WebAssembly to JavaScript with their recent work in Firefox. All these optimizations have been done to make the engine’s work easier.

The improvements fall into two groups:

  • Reducing bookkeeping: This means getting rid of unnecessary work to organize stack frames
  • Cutting out intermediaries: This means taking the most direct path between functions

How they optimized WebAssembly to JavaScript calls

The browser engine has to deal with two different kinds of languages while going through your code even if the code is all written in JavaScript: bytecode and machine code. The engine needs to be able to go back and forth between these two languages.

When it does these jumps, it needs to have some information in place, like the place from where it needs to resume. The engine also must separate the frames that it needs. To organize its work, the engine gets a folder and puts this information in it.

When the Firefox developers first added WebAssembly support, they had a different type of folder for it. So even though JIT-ed JavaScript code and WebAssembly code were both compiled and speaking machine language, it was treated as if they were speaking different languages.

This was unnecessarily costly in two ways:

  • An unnecessary folder is created which adds up setup and teardown costs
  • It requires trampolining through C++ to create the folder and do other setup

They fixed this by generalizing the code to use the same folder for both JIT-ed JavaScript and WebAssembly. This made calls from WebAssembly to JS almost as fast as JS to JS calls.

How they optimized JavaScript to WebAssembly calls

JavaScript and WebAssembly use different customs even if they are speaking the same language. For instance, to handle dynamic types, JavaScript uses something called boxing. As JavaScript doesn’t have explicit types, they need to be figured out at runtime.

To keep track of the types of values, the engine attaches a tag to the value. This turns one simple operation into four operations. This is the reason why WebAssembly expects parameters to be unboxed and doesn’t box its return values. Since it is statically typed, it doesn’t need to add this overhead.

So, before the engine gives the parameters to the WebAssembly function, the engine needs to unbox the values and put them in registers. It has to go through C++ again to prepare the values when going from JS to WebAssembly. Going to this intermediary step is a huge cost, especially for something that’s not that complicated.

To solve this, they took the code that C++ was running and made it directly callable from JIT code. So, when the engine goes from JavaScript to WebAssembly, the entry stub unboxes the values and places them in the right place.

Along with these calls, they have also optimized monomorphic and built-in calls.

To understand the optimizations well, check out Lin Clark’s official announcement on Mozilla’s website.

Read Next

Mozilla updates Firefox Focus for mobile with new features, revamped design, and Geckoview for Android

Mozilla releases Firefox 62.0 with better scrolling on Android, a dark theme on macOS, and more

Mozilla, Internet Society, and web foundation wants G20 to address “techlash” fuelled by security and privacy concerns