asm.js

Original author: David Herman, Luke Wagner, Alon Zakai
  • Transfer
Translated help on asm.js. Only the first chapter of the Introduction is published here. Entire article in chm format can be taken on file hosting . For those who are interested in a different format, take the archived folder with the “disassembled” chm here and redo it as you need. However, it is advisable not to mention me. The asm.hhp project file is located in this folder. If you have installed the Microsoft® HTML Help Workshop program, you can use it to reassemble the chm file after the changes are made.

Actually, help on asm.js is just a draft help, prepared for asm.js of the first version. Therefore, some links (few) do not work. In addition, in the body of the certificate itself there is a commented text - the authors of the certificate were preparing to supplement it. Some of them have been translated by me, but left commented. And yet, the date of the last change of the original is August 18, 2014, maybe it's already just abandoned.

1. Introduction


This description defines asm.js , a nested JavaScript framework that can be used as a low-level, rational target language for compilers.

The asm.js language provides an abstraction similar to a C / C ++ virtual machine: a large binary "heap" with efficient loading and storage, arithmetic of integers and floating point numbers, first-order function definitions, and function pointers.

Programming model


The asm.js programming model is built around the arithmetic of integers and floating-point numbers, as well as a virtual “heap” represented as a typed array .

Although JavaScript does not directly provide integer constructs, they can be simulated in two workarounds:

  • loading and storing integers can be done using the typed arrays API; and
  • integer arithmetic is equivalent to joining JavaScript's arithmetic operators for floating point numbers and casting to integers by bitwise operators.

As an example of the above, if there is an Int32Array view from the heap named HEAP32, then you can load a 32-bit integer with a byte offset p:

HEAP32 [p >> 2] | 0

A shift converts a byte shift into a shift of a 32-bit element, and a bitwise cast ensures that accessing from the outside will cause the value undefined back to an integer.

As an example of integer arithmetic, addition can be accomplished by obtaining two integer values, adding them with the built-in addition operator, and casting the result back to the integer value via the bitwise OR operator:

(x + y) | 0

This programming model is directly borrowed from the methods that first appeared in the Emscripten and Mandreel compilers .

Validation


The dialect of the asm.js programming language is determined by a static type system that can be checked during JavaScript parsing.

The verification (validation) of the asm.js code is designed as “pay-as-you-go” in that it is never done with code that does not require it.

Module (module) asm.js validation requests via a special lead-directive (prologue directive), similar strict mode (strict regime) in ECMAScript Edition version 5:

function MyAsmModule () {
    "use asm";
    // module body
}

This direct indication allows JavaScript engines to avoid meaningless and possibly costly validation in other JavaScript code, and also to display windows with validation error messages only when appropriate.

Early compilation


Since asm.js is a nested complex that strictly adheres to the rules of the JavaScript language, this specification defines only a logic check - the execution of semantic analysis is left to JavaScript.

However, asm.js validated code lends itself to "early" (ahead-of-time - AOT) compilation. However, the code generated by the AOT compiler can be quite efficient, showing:

  • Unpacked representations of integers and floating point numbers
  • lack of type checks at runtime;
  • lack of garbage collection; and
  • efficient loading and storage of the heap (with implementation methods that vary depending on the platform).

Code that fails to verify should return to execution by traditional standards, such as interpretation and / or online compilation.

Binding


Using the asm.js module requires calling its function to obtain an object containing the export of the module; this is called binding .

The asm.js module, through binding, can also be set to access standard JavaScript libraries and user-defined functions.
An AOT implementation must perform certain dynamic checks to verify, at compile time, the assumptions about linked libraries for use in compiled code.

This figure depicts the simplest architecture of an AOT implementation that otherwise would use a conventional interpreter.
If either dynamic or static validation fails, the implementation should go back to the interpreter.
But if both types of validation are successful, the export of the module executing the binary executable code generated by the AOT compilation is called.



External code and data


Inside the asm.js module, all code is fully typed statically and limited to the very rigid asm.js. However, it is possible to interact with recognized standard JavaScript libraries and even custom dynamic JavaScript functions.

The asm.js module can take up to three additional parameters, providing access to external JavaScript code and data:

  • the standard library object , provides access to a limited nested set of JavaScript standard libraries;
  • interface of an external function (foreign function interface - FFI), provides access to the user external JavaScript functions; and
  • buffer "heap" (heap buffer), provides a separate ArrayBuffer , acting as asm.js «heap."

These objects allow asm.js to make calls in external JavaScript (and use the heap buffer together with external JavaScript). Conversely, exporting the object returned from the module allows external JavaScript to make a call to asm.js.

So in general, the declaration of the asm.js module looks like:

function MyAsmModule (stdlib, foreign, heap) {
    "use asm"; 
// module body ...
return { export1: f1, export2: f2, // ... }; }

The parameters of functions in asm.js are supplied with type labels, by means of an explicit action on the input of the function:

function geometricMean (start, end) {
  start = start | 0; // start variable of type int
  end = end | 0; // end variable of type int
  return + exp (+ logSum (start, end) / + ((end - start) | 0));
}

These labels serve two purposes: first, to provide a signature for the type of the function, so that the validator can ensure that all calls to the function are correctly typed;
secondly, to ensure that even if the function is exported and called by external JavaScript, its arguments are dynamically cast to the intended type.

This ensures that the AOT implementation can use unpacked view values, knowing that when the dynamic cast is complete, the function body will never require any type checks at runtime.

Put it all together


Below is a small but complete example of the asm.js. module

function GeometricMean (stdlib, foreign, buffer) {
  "use asm";
  var exp = stdlib.Math.exp;
  var log = stdlib.Math.log;
  var values ​​= new stdlib.Float64Array (buffer);
  function logSum (start, end) {
    start = start | 0;
    end = end | 0;
    var sum = 0.0, p = 0, q = 0;
// asm.js forces byte addressing of the heap by requiring shifting by 3 for (p = start << 3, q ​​= end << 3; (p | 0) <(q | 0); p = (p + 8) | 0) { sum = sum + + log (values ​​[p >> 3]); } return + sum; } function geometricMean (start, end) { start = start | 0; end = end | 0; return + exp (+ logSum (start, end) / + ((end - start) | 0)); } return {geometricMean: geometricMean}; }

In the JavaScript engine that supports AOT compilation of asm.js, calling the module on its own global object and the heap buffer will bind the exported object to use by statically compiled functions.

var heap = new ArrayBuffer (0x10000); // 64k heap
init (heap, START, END); // fill a region with input values
var fast = GeometricMean (window, null, heap); // produce exports object linked to AOT-compiled code
fast.geometricMean (START, END); // computes geometric mean of input values

On the other hand, calling a module on a standard library object containing something other than true, Math.exp or Math.log will fail to produce AOT-compiled code.

var bogusGlobal = {
  Math: {
    exp: function (x) {return x; },
    log: function (x) {return x; }
  },
  Float64Array: Float64Array
};
var slow = GeometricMean (bogusGlobal, null, heap); // produces purely-interpreted / JITted version
console.log (slow.geometricMean (START, END)); // computes bizarro-geometric mean thanks to bogusGlobal

Also popular now: