Asm.js comes to Chakra and Microsoft Edge

Original author: Gaurav Seth, Abhijith Chatra
  • Transfer
Asm.js on Microsoft Edge

A few months ago, we announced the launch of Asm.js. Asm.js support has been one of the 10 most requested uservoice requests for Microsoft Edge since launching in December 2014. We have made good progress since then: in Windows 10 Insider Preview, starting with build 10074, you can try Asm .js on Chakra and Microsoft Edge.

What is Asm.js?


Asm.js is a strict subset of JavaScript that can be used as a low-level and efficient language for the compiler. As a subset of asm.js describes a limited virtual machine for languages ​​with insecure memory access like C and C ++. The combination of static and dynamic checks allows JavaScript engines to use techniques like specialized compilation without insurance or AOT-of-Time compilation for correct asm.js code.


Such techniques help JavaScript to execute with “predictable” and “close to native” performance, both of which are non-trivial to achieve in the framework of normal compiler optimizations for dynamic languages ​​like JavaScript.

Given the difficulty of writing asm.js code manually, today asm.js is mainly done by transcompiling C / C ++ code using tools like Emscripten . The result is used as part of a web platform along with technologies such as WebGL and Web Audio. Game engines, such as Unity and Unreal , are starting to introduce early or experimental support for games on the web without using plugins, using a combination of asm.js and other related technologies.

How can I try with Asm.js in Microsoft Edge?


To enable Asm.js support in Microsoft Edge, go to the about: flags page in Microsoft Edge and enable the “Enable asm.js” flag, as shown below:



Embedding Asm.js in the Chakra code execution thread


To add Asm.js to Chakra, the components highlighted in green have been added or changed from the basic code execution model described in the article about improving JavaScript performance in Windows 10 .



Key changes:

  • The ASM.js validator , which allows Chakra to detect asm.js code and verify that it matches the asm.js specification .
  • Generation of optimized type-specific code . Given that asm.js only supports native types (int, double, float, or SIMD values), Chakra uses type information to generate type-specific code based on the code on asm.js.
  • Faster execution of type-specific code by the Chakra interpreter by using type information to eliminate packing and unpacking of numerical values ​​and by eliminating the need to generate a data profile for interpretation. For non-asm.js JavaScript code, the Chakra interpreter creates and maintains data profiles for the JIT compiler to provide highly optimized JIT code generation. The type-specific bytecode created on the basis of the correct asm.js code already contains all the information necessary for the JIT compiler to create optimized machine code. A type-specific interpreter is 4-5 times faster than an interpreter with data profiles.
  • Faster compilation by the Chakra JIT compiler . Asm.js code is usually generated using the LLVM compiler and Emscripten tools . The Chakra JIT compiler takes into account the optimizations already made that are present in the asm.js code generated by the LLVM compiler. For example, our JIT compiler does not pass the embed code, focusing on the embedding already done by the LLMV compiler. The exclusion of code analysis for such JIT optimizations not only helps save CPU cycles and battery usage when generating code, but also significantly improves the speed of the JIT compiler.
  • Predictable performance by eliminating insurance in compiled asm.js code. Given the dynamic nature of JavaScript, all modern JavaScript engines support some form of transition to the execution of non-optimized code called insurances (“bailout” in this case is translated as insurance). They work when the engines realize that the assumptions made by the JIT compiler are no longer true for the compiled code. Insurance not only affects overall performance, but also makes lead times unpredictable. Taking advantage of the safe type restrictions for the correct asm.js code, the code generated by the Chakra JIT compiler is guaranteed to not require insurance.


JIT compiler optimizations for Asm.js


In addition to the changes to the code execution process described above, the Chakra JIT compiler also takes advantage of the restrictions imposed by the asm.js. These enhancements help the Chakra JIT compiler generate code that will execute with near-native performance, a cherished goal for all dynamic language JIT compilers. Let's take a closer look at two such optimizations.

Exclude Assistant Calls and Insurance


Helper and insurance code acts as a powerful lifeline for compiled dynamic language code. If any of the assumptions made by the JIT compiler during code compilation becomes incorrect, there should be a safe mechanism for the correct execution of the code.

Helpers can be used to handle unexpected situations during the operation, after which control can be returned to the JIT code. The insurance handles situations in which the JIT code cannot recover after the occurrence of unpredictable conditions and the control needs to be transferred back to the interpreter to perform the rest of the current function.

For example, if a variable seems to be an integer, and the compiler generated code with this assumption, the appearance of a real number should not affect the correctness. In such cases, an assistant or insurance code is used to continue execution, even with reduced performance. For asm.js code, Chakra does not generate additional helpers or insurance code.

To understand why this is not necessary, let's look at an example of a squaring function inside an asm.js module, which has been simplified to explain the concept:

function PhysicsEngine(global, foreign, heap) {
    "use asm";
    …
    // Function Declaration
    function square(x) {
        x = +x;
        return +(x*x);
    }
    …
}


The square function in the code example above translates two x64-machine instructions into asm.js code, excluding the prolog and epilogue generated for this function.

xmm1 = MOVAPS xmm0 //Перемещение нативного double во временный регистр 
xmm0 = MULSD xmm0, xmm1 //Умножение


For comparison, the compiled code generated for the square function, with asm.js turned off, includes about 10 machine instructions. Including:
  • Check that the parameter is marked as double, if not, then go to the helper, converting from any type to double
  • Extracting a real value from a marked double
  • Multiplication of values
  • Convert result back to marked double


The Chakra JIT compiler is able to generate efficient code for asm.js, taking advantage of the type information for variables, which does not change over the lifetime of the program. The Asm.js validator and linker also take this into account. Since all internal variables in asm.js have a native type (int, double, float or SIMD values), internal functions calmly use native types without packing them into JavaScript variables for passing between functions. In compiler terminology, this is often called direct calls and the exception of packaging or transformations when working with data in asm.js code. At the same time, for external calls to or from JavaScript, all incoming variables are converted to native types, and outgoing native types are converted to variables when packaging.

Boundary check exception when accessing typed arrays


In a previous post, we talked about how the Chakra JIT compiler implements optimization of automatically typed arrays , while taking border checks outside arrays, thereby improving the performance of operations with an array inside a loop by up to 40%. Given the type restrictions in asm.js, the Chakra JIT compiler completely excludes border checks for access to typed arrays for all compiled asm.js code, regardless of the location in the code (inside or outside the loop or function) or the type of the typed array. It also eliminates bounds checking for persistent and non-persistent indexed queries on typed arrays. Together, these optimizations make it possible to achieve near-native performance when working with typed arrays in asm.js code.

The following is a simplified example of storing in a typed array with a constant shift and one x64 machine instruction generated by the Chakra compiler for the corresponding code on asm.js:


int8ArrayView[4] = 25; //JavaScript-код
[Address] = MOV 25 //Одна машинная инструкция для хранения значения


When compiling the asm.js code, the Chakra JIT compiler can pre-compute the final address corresponding to int8ArrayView [4] directly during the compilation of the asm.js code (the first call to the asm.js module) and therefore generate one instruction corresponding to saving in a typed array. For comparison, the compiled code generated for the same operation from non asm.js JavaScript code leads to about 10-15 machine instructions, which is pretty significant. The operation includes the following steps:
  • Loading a typed array from a variable in a closure (in asm.js, typed arrays are already captured by the closure)
  • Checking that the variable from the closure is actually a typed array, if not, then the safety code is called
  • Check that the index is within the length of the typed array; if the conditions are not met, then there is a transition to the code of the assistant or insurance
  • Saving a value in an array buffer


The checks above are removed from the asm.js code due to strict restrictions on what can be changed inside the asm.js module. For example, a variable pointing to a typed array in the asm.js module does not change. If it changes, then this is not the correct code on asm.js, which is caught at the stage of code validation. In such cases, the code is processed just like any other JavaScript function.

Improved scripting and increased productivity from Asm.js


Already from the first results that you can try in a fresh build of the Windows 10 preview, we saw some cool scripts that benefit in performance from asm.js support in Chakra and Microsoft Edge. If you want to play it yourself, start games like Angry Bots , Survival Shooter , Tappy Chicken or try some of the fun asm.js demos here .



In terms of performance improvements, there are several asm.js speed meters that are constantly evolving. Already with preliminary support, asm.js Chakra and Microsoft Edge work more than 300% faster in Unity Benchmark and about 200% faster in individual tests like zlib used in Google Octane and Apple Jet Stream test suites.


Unity Benchmark scores for 64-bit browsers (Click to enlarge)
(System info: Intel Core (TM) i7 CPU 860 @ 2.80GHz (4 cores), 12GB RAM running 64 bit Windows 10 Preview)
(Scores have been scaled so that IE11 = 1.0, to fit onto a single chart)


What next?


This is the first step towards integrating asm.js into the web platform behind Microsoft Edge. We are pleased with the results, but not yet done everything. We are working on fine-tuning the code execution chain in Chakra to support Asm.js - we collect data to make sure that the current architecture approach works well on real asm.js usage scenarios, analyze and try to improve performance, functionality, tool support, etc. P. before enabling this option by default.

Once it is turned on, asm.js will work not only in Microsoft Edge, but will also be available in Windows applications written in HTML / CSS / JS, as well as in WebView, since it uses the EdgeHTML rendering engine that comes with MicrosoftEdge on Windows 10.

We also want to thank our colleagues from Mozilla, with whom we worked closely from the very beginning of the implementation of support for asm.js, and Unity for their support and cooperation in the implementation of asm.js in Chakra and Microsoft Edge. And a special thank you to all the developers and users of Windows 10 and Microsoft Edge who left us valuable feedback. Continue in the same spirit! You can email us on Twitter at @MSEdgeDev or through Connect .

Also popular now: