[Перевод] asm.js

Перевел справку по asm.js. Здесь публикуется только первая глава «Введение». Целиком статью в формате chm можно взять на файлообменнике. Для тех кому интересен другой формат, возьмите здесь архивированную папку с «разобранным» chm'-ом и переделывайте так, как вам нужно. При этом меня желательно не упоминать. В эту папку вложен файл проекта asm.hhp. При наличии установленной программы Microsoft HTML Help Workshop, с его помощью можно вновь собрать chm-файл, после внесенных изменений.
Собственно, справка по asm.js — это всего лишь черновик справки, подготавливаемый к asm.js первой версии. Поэтому, некоторые ссылки (немногие) не работают. Кроме этого, в теле самой справки имеется закомментированный текст — авторы справки готовились дополнить её. Некоторая часть из них мною переведена, но оставлена закомментированной. И ещё, дата последнего изменения оригинала — 18 августа 2014 года, возможно она уже просто заброшена.

1   Введение


Данное описание определяет asm.js, вложенный комплекс JavaScript, который может быть использован в качестве низко-уровневого, рационального целевого языка для компиляторов.
Язык asm.js предоставляет абстракцию похожую на виртуальную машину C/C++:
большая бинарная «куча» с эффективной загрузкой и хранением, арифметика целых чисел и чисел с плавающей запятой, определения функций первого порядка и указатели функций.

Модель программирования


Модель программирования asm.js выстроена вокруг арифметики целых чисел и чисел с плавающей запятой, а также виртуальной «кучи», представленной в виде типизированного массива (typed array).
Хотя JavaScript напрямую не предоставляет конструкций для работы с целыми числами, они могут быть сымитированы двумя обходными способами:

  • загрузка и хранение целых чисел может выполняться с помощью API типизированных массивов; и
  • целочисленная арифметика эквивалентна соединению из JavaScript’овских арифметических операторов для чисел с плавающей запятой и приведениям к целым числам, выполняемым побитовыми операторами.


Как пример вышеуказанного, если имеется представление Int32Array из «кучи» с названием HEAP32, то можно загрузить 32-битное целое число с байтовым смещением p:

HEAP32[p >> 2]|0


Сдвиг конвертирует байтовый сдвиг в сдвиг 32-битного элемента, а побитовое приведение обеспечивает, что обращение извне приведет значение undefined обратно к целому числу.

В качестве примера целочисленной арифметики, сложение может быть выполнено получением двух целочисленных значений, сложением их встроенным оператором
сложения и приведением полученного результата обратно к целочисленному значению через побитовый оператор ИЛИ:

(x+y)|0


Данная модель программирования напрямую позаимствована от методов, впервые появившихся в компиляторах Emscripten и Mandreel.

Валидация


Диалект языка программирования asm.js определяется системой статического типа, которая может быть проверена во время синтаксического анализа JavaScript.
Проверка (валидация) кода asm.js разработана как «оплата по мере получения» в том, что она никогда не делается с кодом, которому она не требуется.
Модуль (module) asm.js запрашивает валидацию с помощью специальной вводной директивы (prologue directive),
аналогичной strict mode (строгому режиму) в ECMAScript Edition 5 версии:

function MyAsmModule() {
    "use asm";
    // тело модуля
}


Такое прямое указание позволяет движкам JavaScript избегать выполнения бессмысленной и возможно затратной валидации в другом коде JavaScript, а также выдавать окна с сообщениями об ошибках валидации только когда это уместно.

«Ранняя» компиляция


Поскольку asm.js это вложенный комплекс, строго придерживающийся правилам языка JavaScript, данная спецификация определяет только проверку логики — выполнение семантического анализа остается за JavaScript.
Тем не менее, код asm.js прошедший валидацию поддается «ранней» (ahead-of-time — AOT) компиляции.
Однако, код сгенерированный AOT-компилятором может быть довольно эффективным, показывая:

  • распакованные представления целых чисел и чисел с плавающей запятой;
  • отсутствие проверок типа во время выполнения;
  • отсутствие сборки мусора; и
  • эффективная загрузка и хранение «кучи» (с методами реализации, изменяющимися в зависимости от платформы).


Код, который не удается проверить должен вернуться к исполнению по традиционным меркам, например, интерпретации и/или оперативной компиляции.

Связывание


Использование модуля asm.js требует вызова его функции для получения объекта, содержащего экспортирование модуля; это называется связывание.
Модулю asm.js, через связывание, также может быть задан доступ к стандартным библиотекам и пользовательским функциям JavaScript.
Реализация AOT должна выполнять определенные динамические проверки для проверки, во время компиляции, предположений о связываемых библиотеках для использования их в компилируемом коде.

Этот рисунок отображает простейшую архитектуру AOT реализации, которая в противном случае использует обычный интерпретатор.
Если либо динамическая, либо статическая валидация не удается, реализация должна вернуться назад к интерпретатору.
Но если оба вида валидации выполнены успешно, вызывается экспортирование модуля, выполняющего бинарный исполняемый код, сгенерированный AOT компиляцией.
d23a11069bab486e948e90e17d64cfe3.png

Внешний код и данные


Внутри модуля asm.js, весь код полностью типизирован статически и ограничен очень жестким диалектом asm.js.
Тем не менее, имеется возможность для взаимодействия с признанными стандартными JavaScript библиотеками и даже пользовательскими динамическими функциями JavaScript.

Модуль asm.js может принимать до трех дополнительных параметров, предоставляя доступ к внешнему JavaScript’овским коду и данным:

  • объект стандартная библиотека (standard library), предоставляет доступ к ограниченному вложенному набору JavaScript’овских стандартных библиотек;
  • интерфейс внешней функции (foreign function interface — FFI), предоставляет доступ к пользовательским внешним JavaScript функциям; и
  • буфер «кучи» (heap buffer), предоставляет отдельный ArrayBuffer, действующий в качестве asm.js «кучи».


Эти объекты позволяют asm.js делать вызов во внешний JavaScript (и совместно с внешним JavaScript’ом использовать буфер «кучи»). И наоборот, экспортирование объекта, возвращенного из модуля позволяет внешнему JavaScript’у делать вызов в asm.js.

Так что в общем случае, объявление asm.js модуля выглядит как:

function MyAsmModule(stdlib, foreign, heap) {
    "use asm";
// тело модуля...
return { export1: f1, export2: f2, // ... }; }


Параметры функций в asm.js снабжаются метками типа, посредством явного воздействия на входе функции:

function geometricMean(start, end) {
  start = start|0; // переменная start типа int
  end = end|0;     // переменная end типа int
  return +exp(+logSum(start, end) / +((end - start)|0));
}


Эти метки служат двум целям: во-первых, для снабжения типа функции сигнатурой, так чтобы валидатор мог обеспечить соблюдение того, что все вызовы к функции правильно типизированы;
во-вторых, для гарантии, что даже если функция экспортирована и вызывается внешним JavaScript’ом, её аргументы динамически приводятся к предполагаемому типу.
Это гарантирует, что реализация AOT может использовать неупакованные значения представлений, зная, что по завершении динамического приведения, тело функции никогда не потребует каких-либо проверок типа во время выполнения.

Сложим все вместе


Ниже небольшой, но полный пример модуля asm.js.

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 }; }


В движке JavaScript, который поддерживает AOT компилирование asm.js, вызов модуля на собственном глобальном объекте и буфере «кучи» привяжет экспортированный объект к использованию статически компилированными функциями.

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


С другой стороны, вызов модуля на объекте стандартной библиотеки, содержащей нечто другое чем истина, Math.exp или Math.log будут не в состоянии произвести AOT-компилированный код.

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

© Habrahabr.ru