[Перевод] 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 компиляцией.
Внешний код и данные
Внутри модуля 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