Когда я впервые услышал о технологии WebAssembly — она сразу показалось мне крутой вещью и мне сразу захотелось попробовать её в деле. От первого желания, до чего-то работающего мне, однако, пришлось потратить немало времени и порой испытать кое-какие разочарования. Для того, чтобы сохранить ваше время и ваши нервы, если вам захочется повторить тот же путь, и написана данная статья.
Предупреждение читателю
Эта статья написана 24-го июня 2016-го года. Поскольку WebAssembly очень молодая и динамично развивающаяся технология, со временем многие описанные в данной статье вещи устареют или полностью изменятся — учитывайте это.
А теперь поехали.
Что такое WebAssembly?
Официальная документация говорит следующее: «WebAssembly или wasm это новый портабельный, эффективный по размеру и скорости загрузки формат компиляции для веба». Эм-м-м-м… Что? Формат чего? Текстовый или бинарный? Да, это откровенно плохое описание. Так что убирайте уже ваши баззворд-бинго карточки и я, на основе моего опыта, дам своё определение:
«WebAssembly или wasm это спецификация байткода для написания производительных, браузеро-независимых компонентов для веба». Это определение, тоже, конечно, не вершина эпистолярного жанра, но я попробую его дополнить. WebAssembly позволяет повысить производительность с помощью использования статически типизированных переменных, которые обходятся на рантайме значительно дешевле динамических. WebAssembly разрабатывается W3C Community Group и планируется быть внедрённым во все основные браузеры. И с этого момента на стол выкладывается киллер-фича: вы сможете писать код веб-компонентов на любом языке программирования.
Теперь звучит лучше, неправда ли?
Давайте начнём
Когда я изучаю какую-нибудь новую вещь, я обычно ищу минимально возможный пример, достаточный для того, чтобы посмотреть, как всё работает. К сожалению, этот подход не очень-то возможен с WebAssembly. В текущего состоянии спецификации wasm — это просто формат байткода. Представьте себе как в каком-нибудь 1996-ом году инженеры Sun Microsystems представляли бы JVM… но без Java. Разговор шел бы как-то так:
"-Эй вы все, зацените какую классную машину для выполнения байткода мы создали!
-Круто! А как под неё писать код?
-А вот так:
-Эм-м-м-м… круто… Я попробую как-нибудь на досуге.
-Отлично, дай нам знать, если будут какие-нибудь проблемы или идеи!
-Да-да. Но я тут немного занят, нужно посмотреть несколько других вещей… Но как только — так сразу!"
И даже это плохой пример, поскольку JVM хотя бы базируется на языке Java, а с WebAssembly у нас нет и этого. Я надеюсь, вы уловили мысль. Если вы представляете байткод без инструмента, который компилирует в этот байткод код какого-нибудь языка программирования — вам будет трудновато продвигать его. Так как же нам всё-таки начать работать с WebAssembly?
Что было до WebAssembly?
Большинство технологий являются результатом развития каких-то предшествующих технологий, в особенности когда планируемой целью является получить некоторую формальную спецификацию. WebAssembly не исключение, это продолжение разработки идей, заложенных когда-то в asm.js, спецификации, предназначенной для написания javascript-кода таким образом, чтобы его было возможно скомпилировать со статической типизацией. Wasm развил эти идеи созданием спецификации байткода, который может быть создан компилятором любого языка программирования, затем переслан через Интернет в виде бинарного файла, пригодного для исполнения любым современным браузером.
asm.js это лишь спецификация для написания javascript-кода с использованием подмножества возможностей языка Javascript. Вы можете написать код на asm.js вручную и, если вам не терпится уже взять и что-то написать — самое время начать.
function MyMathModule(global) {
"use asm";
var exp = global.Math.exp;
function doubleExp(value) {
value = +value;
return +(+exp(+value) * 2.0);
}
return { doubleExp: doubleExp };
}
Это не очень полезная функция, но она написана согласно спецификации asm.js. Если она для вас выглядит слегка глуповато — так знайте, что вы не единственный, кто так считает. Однако, все эти «странные» символы (все эти унарные операторы) необходимы. Они указывают компилятору на типы данных в операциях. Код весьма простой, но если вы где-нибудь ошибётесь — консоль отладки покажет достаточно читабельное сообщение об ошибке.
Если вы захотите использовать данную функцию, сделать это можно как-то так:
var myMath = new MyMathModule(window);
for(var i = 0; i < 5; i++) {
console.log(myMath.doubleExp(i));
}
И, если вы всё сделали верно, то на выходе должны увидеть нечто подобное:
И, наконец, переходим к WebAssembly
На данный момент у нас есть работающий кусочек кода на asm.js. Мы можем пойти на официальную страницу WebAssembly на GitHub и найти там инструменты для компиляции этого кода в wasm. Беда лишь в том, что нам придётся собрать эти инструменты самостоятельно. Это, откровенно говоря, худшая часть всего квеста. Данные инструменты постоянно меняются и время от времени находятся в сломанном состоянии, особенно в плане использования их под Windows.
Для сборки вам понадобятся make и cmake. Если вы работаете под Windows — понадобится ещё и Visual Studio 2015. Вот инструкции по сборке под Mac, а вот — под Windows.
Нужно отметить, что распространение собранных бинарников этих утилит было бы огромным шагом вперёд по популяризации технологии WebAssembly.
Если вы прошли через всё вышеописанное без проблем, то получили папку bin в папке binaryen, где и находятся инструменты для конвертации нашего asm.js кода в wasm. Первый инструмент называется asm2wasm.exe. Он преобразовывает код на asm.js в формат кода .s, который является текстовым представлением абстрактного синтаксического дерева (AST) формата wasm. Запустив asm2wasm на своём asm.js коде, вы получите что-то вроде этого:
(module
(memory 256 256)
(export "memory" memory)
(type $FUNCSIG$dd (func (param f64) (result f64)))
(import $exp "global.Math" "exp" (param f64) (result f64))
(export "doubleExp" $doubleExp)
(func $doubleExp (param $0 f64) (result f64)
(f64.mul
(call_import $exp
(get_local $0)
)
(f64.const 2)
)
)
)
Можно разобрать этот код по строкам, но сейчас я просто хочу подчеркнуть, что поскольку wasm это бинарный формат, просто кликнуть в браузере на чём-то и посмотреть код, как вы привыкли делать это с Javascript, уже не получится (по крайней мере на данный момент). То, что вы увидите, будет очень похоже на код выше.
Следующим шагом будет конвертация этого .s формата в wasm-бинарник, для этого мы воспользуемся утилитой wasm-as.exe. Применив её для вашего .s-файла на выходе вы получите байткод, ради которого мы и затевали всю эту историю.
Теперь возьмите последнюю версию Firefox или Chrome Canary и включите в них WebAssembly.
Для Firefox вам понадобиться открыть about:config и набрать «wasm» в строке поиска. После этого изменить значение опции javascript.options.wasm на true и перезапустить браузер. Для Chrome Canary вам нужно открыть chrome://flags, найти и включить опцию Experimental WebAssembly, после чего перезапустить браузер.
Теперь нам нужно запустить наш модуль в браузере. Для меня это поначалу оказалось проблемой, поскольку совершенно не очевидно, как это сделать. Я открыл консоль в Chrome Canary и попробовал набрать «WebAsse» — и ничего, никаких подсказок. Затем я набрал «Was» и получил подсказку! Этот объект в инспекторе выглядел весьма убого в плане документации. Я опущу весь рассказ о том, как я рылся в поисках работающего пример, скажу лишь что в конце-концов я его нашел в некотором файле JS.md в репозитории WebAssembly. Там было что-то вроде документации и примера, вот он:
fetch("my-math-module.wasm")
.then(function(response) {
return response.arrayBuffer();
})
.then(function(buffer) {
var dependencies = {
"global": {},
"env": {}
};
dependencies["global.Math"] = window.Math;
var moduleBufferView = new Uint8Array(buffer);
var myMathModule = Wasm.instantiateModule(moduleBufferView, dependencies);
console.log(myMathModule.exports.doubleExp);
for(var i = 0; i < 5; i++) {
console.log(myMathModule.exports.doubleExp(i));
}
});
Забросьте это в свой html-файл, поднимите локальный веб-сервер и откройте этот файл в браузере. Вот как это будет выглядеть:
Самое время пойти отправить баг репорт :). Помните, что это всё ещё очень сырая и экспериментальная технология, так что не удивляйтесь возникающим по ходу дела багам.
Примите поздравления!
Вы создали ваш первый WebAssembly-компонент. Что дальше? Ну, мы лишь слегка сбросили покровы тайны. Написание asm.js кода было ключевым моментом данного примера и написание сколько-нибудь нетривиальной функциональности потребует времени и терпения. С использованием emscripten компиляция нетривиальных приложений в asm.js становится значительно проще. Я советую вам почитать спецификацию asm.js, особенно раздел о модели памяти, поскольку многие концепции перешли в WebAssembly напрямую из asm.js. Ещё один важный момент: в данный момент вы не можете передавать массивы как аргументы функции. Есть некоторая договорённость, что это должно измениться, но пока что это не отражено в спецификации. Самое время освежить в памяти логику работы с указателями.
Ещё один нюанс: когда вы начнёте писать нетривиальные вещи на wasm, то, возможно, заметите, что производительность порой может быть медленнее старого доброго Javascript. Просто учтите, что современные движки Javascript во всех браузерах весьма высоко оптимизированы и у wasm займёт какое-то время достичь их эффективности. Теоретический предел производительности у WebAssembly выше, чем у кода на Javascript в текстовой форме, но WebAssembly ещё не готов к промышленному применению.
Автор: Инфопульс Украина