Я люблю Интернет. Это современная сверхдержава по распространению информации и расширению прав и возможностей человека. Конечно, у него есть свои недостатки, такие как троллинг (в значительной степени возможный благодаря анонимности) и проблемы с конфиденциальностью, не говоря уже о проблемах собственности и нарушении авторских прав, которые вскоре вступят в силу с вызывающей разногласия статьей 13. Но давайте на мгновение забудем об этом и поразимся технологическим инновациям Интернета и браузерам, которые их поддерживают.

Я впервые научился кодировать на Javascript, и с тех пор многие высмеивали его за то, что мне это понравилось. Да, я знаю, что есть странные элементы, подобные этому драгоценному камню: [] == ![] // true, но он стал одним из самых распространенных языков на планете благодаря Интернету, браузерам и интерпретаторам, запускающим код (Google V8 и «Firefox's SpiderMonkey " назвать несколько).

По мере того, как я углублялся в веб-разработку, я заметил новое имя на блоке: WebAssembly. Как студент-информатик и разработчик, я считаю, что один из лучших способов чему-то научиться - это попытаться понять, почему инженеры, создавшие это, сделали такой дизайнерский выбор. Итак, вот краткий обзор некоторых интересных принципов дизайна в WebAssembly, а также того, почему я думаю, что все должны быть в восторге.

Зачем нам WebAssembly?

Итак, прежде всего, всем моим поклонникам Javascript - нет, не беспокойтесь. Когда Javascript только появился, он был разработан для облегчения использования, но с тех пор стал выполнять много тяжелой работы. Возможно, он использовался для манипулирования некоторыми элементами DOM, некоторой проверки на стороне клиента в формах, но не всего, что сейчас пытаются сделать в сети. Уж точно не запущены полноценные игры.

Почему Javascript не такой быстрый или отличный? Одна из основных причин в том, что это интерпретируемый язык. Сканирование кода строка за строкой и выполнение, к счастью, с компиляторами Just-in-Time, значительно повысили эффективность, но все же есть лишь некоторые возможности для улучшения. Но даже в этом случае существует проблема с динамической типизацией Javascript, которая снижает производительность.

Алекс Данило обсуждал улучшения, которые WebAssembly может внести в своем Google I / O talk в 2017 году. Что действительно показало неэффективность, так это его пример add(a, b) функции и сложность, которую интерпретаторы Javascript должны пройти, чтобы понять ее смысл.

WebAssembly открывает дверь для компиляции, которая открывает еще одну дверь для оптимизации. Его способность использовать исходный язык C / C ++ позволяет выполнять некоторую статическую проверку типов, что помогает повысить скорость. Это то, что разработчики Mozilla Foundation поняли и хотели исправить. Подводя итог этому отличному видео, Javascript был разработан для людей, и браузерам оставалось попытаться сделать его быстрым; WebAssembly был разработан как целевой язык для компиляторов, которые браузеры уже могли быстро запускать.

Осознание того, что у нас может быть два варианта запуска кода в движках, было захватывающей перспективой - и все четыре основных браузера (Chrome, Safari, Firefox и IE) начали планы разрешить своим движкам запускать Javascript и WebAssembly. Опять же, позвольте мне повторить… WebAssembly не заменяет Javascript.

Зачем компилировать код?

Компиляция кода на самом деле означает взятие его с одного (исходного) языка и перевод на другой (целевой) язык. Это невероятно упрощенное понимание компиляции. Большинство современных конвейеров компиляции включают в себя гораздо больше этапов, которые позволяют нам действительно тонко настраивать и оптимизировать наш код, делая его более быстрым и энергоэффективным.

Первые шаги обычно включают лексический, синтаксический и семантический анализаторы, чтобы перевести код на какой-то промежуточный язык, идеально подходящий для оптимизации. Затем мы оптимизируем независимо, генерируем целевой код и затем, возможно, оптимизируем в зависимости от оборудования или среды.

Все проекты сначала нужно начинать с малого, и инженеры Mozilla решили начать с исходного языка C / C ++ и, используя существующий набор инструментов под названием LLVM (не аббревиатура), они будут компилировать с его использованием.

Первоначально поиск более производительной сети начался с asm.js (по крайней мере, в описании WebAssembly. См. PNaCL - предыдущие попытки Google) небольшого подмножества Javascript, который мог быть целью компиляции для программ C / C ++, которые использовали аннотации и другие умные уловки для улучшения производительности Javascript.

К сожалению, в нем отсутствовал один важный принцип проектирования, лежащий в основе желаемого: портативность. Различные движки Javascript давали разные оценки производительности, но это было четким указанием на то, что это может быть хорошим подходом.

Разработчики WebAssembly решили, что их целевым представлением будет двоичный формат, обеспечивающий плотное линейное кодирование абстрактного синтаксиса… Это много слов, так что давайте разберемся с этим.

Плотная часть относится к высокоуровневой цели достижения формата, эффективного по размеру и времени загрузки. Интернет - это отправка данных по проводам, и хотя существует множество проектов по уменьшению задержки, один надежный способ добиться этого - отправлять меньше данных. Еще один важный аспект - повышенная скорость декодирования благодаря индексации массива по сравнению с поиском по словарю (если использовался сжатый текстовый формат). Подробнее об этом выборе дизайна читайте здесь.

Что такое ват?

Двоичный формат, в который компилируются программы C и C ++, представляет собой .wasm файлы, они имеют отображение 1: 1 прямо в (в некоторой степени) читаемый человеком текстовый формат. Эти файлы помечены как .wat, этот WasmExplorer отлично подходит для понимания текстового представления и того, как оно соотносится с исходным кодом. Возьмем простой пример.

Здесь много чего происходит, поэтому давайте не торопимся и объясним концепции по мере их появления.

Во-первых, это странное слово module, откуда оно взялось? Mejin Leechor подробно рассказал о модулях в Javascript и описывает их как структуру и границы кода. Это очень похоже на идею модулей WebAssembly (и в будущем есть планы попытаться интегрировать с модулями es6).

Прямо из документации мы видим, что модуль является распространяемой, загружаемой и исполняемой единицей кода в WebAssembly. Модули могут иметь следующие разделы, каждый со своей уникальной ответственностью: импорт, экспорт, запуск, глобальный, память, данные, таблица, элементы, функция и код. А пока давайте просто посмотрим, что у нас есть в нашем модуле.

Первое объявление - (type $type0 (func (param i32) (result i32))). Это тесно связано с вызовом таблицы в следующей строке. Мы объявляем новый тип с подписью func, который принимает 32-битный целочисленный параметр и возвращает 32-битное целое число. Если бы мы снова использовали функцию, которую мы написали, нам пришлось бы сделать call_indirect в нашем table, а затем мы могли бы провести некоторую проверку типов, чтобы убедиться, что все правильно. В рамках минимально жизнеспособного продукта разрешена только одна таблица, но в будущем планируется разрешить использование нескольких таблиц и их индексацию.

Следующее объявление - (table 0 anyfunc). Раздел таблицы зарезервирован для определения нуля или более таблиц. Таблица похожа на линейную память в том смысле, что они представляют собой массивы с изменяемым размером, которые содержат ссылки. 0 ссылается на тот факт, что у нас ничего нет в нашей таблице, но нам все равно нужно предоставить единственное возможное значение MVP anyfunc (функция).

Проблема разработчиков была связана с безопасностью. Если функция хотела вызвать другую функцию, предоставление ей прямого доступа к функции, хранящейся в линейной памяти, было небезопасным. Вместо этого функции хранятся в таблице, готовые к индексации при необходимости. Лин Кларк написал отличную статью, в которой более подробно описаны таблицы (используемые при импорте) и то, как они обеспечивают лучшую безопасность.

Затем у нас есть объявление (memory 1), это линейная память, используемая модулем, и мы заявляем, что нам нужна 1 страница памяти (64 КБ).

Следующее объявление - (export "memory" memory). Экспорт - это то, что возвращается хосту во время создания экземпляра. По сути, это те интересные фрагменты, которые мы хотим получить от кода WebAssembly.

Структура довольно проста (export <name-of-export> (<type> <name/index>)), поэтому здесь мы просто экспортируем память, которую мы объявили в предыдущей строке. Это обеспечивает прямой доступ к памяти в нашем коде Javascript в виде ArrayBuffer, что значительно повышает эффективность, поскольку нет обратных и переадресованных вызовов через границу WASM / JS. Точно так же мы экспортируем нашу функцию с (export "main" $func0).

Теперь немного более интересный момент - наш код и его представление.

Прежде чем двигаться дальше, это прекрасная возможность представить еще один компонент дизайна: стековую машину.

Регистрация и стековые машины

В простейшем случае компьютеры потребляют входные данные и производят выходные данные. Когда «машина» выполняет программу, она может делать это разными способами. Два основных подхода - это регистровые и стековые машины. В регистровой машине параметры функций хранятся в ячейках памяти, а затем ими манипулируют в зависимости от выполняемой программы.

Простая, но несколько ошибочная аналогия - это кухня и приготовление рецепта. Ингредиенты хранятся в разных местах, вы их получаете и делаете что-то, что можете положить куда-нибудь на другой день или сразу же съесть (ням). Он далек от совершенства, но, надеюсь, вы уловили идею.

С другой стороны, штабелеукладчики используют другую модель. Представьте, что вы журналист или секретарь, ваша работа - читать письма и отвечать на них. Вы «выскакиваете» верхнюю букву из своей стопки и начинаете писать ответ, в то время как кто-то другой приходит с дополнительной работой и «подталкивает» к началу стопки. Это те, которые вам нужно будет сделать дальше. Опять же, сильно упрощено, но это должно помочь визуализировать механику.

WebAssembly использует модель стекового компьютера для выполнения кода. Если вам не хватает хорошего чтения и вы занимаетесь семантикой программирования, статья Повышение скорости Интернета с помощью WebAssembly действительно хороша. Это также указывает на то, почему они выбирают представление стековой машины: Организация стека - это просто способ достичь компактного представления программы, поскольку было показано, что она меньше, чем машина регистров со ссылкой на этот документ, в котором было обнаружено … размер байт-кода регистровой машины всего на 26% больше, чем у соответствующей стековой .

Хотя подход стековой машины не обязательно быстрее, он предлагает меньший байт-код; невероятно важная цель дизайна для транзакций через Интернет.

Итак, как мы можем понимать текстовый формат как стековую машину. По мере того как мы читаем код построчно, мы в конечном итоге помещаем аргументы в стек, затем извлекаем их, выполняем некоторые вычисления и возвращаем результат. И повторить.

Поначалу может показаться немного странным иметь текстовый формат, если в конце концов он будет скомпилирован в двоичный формат для сжатия. Но в Интернете всегда была политика просмотра исходного кода, и именно поэтому разработчики WebAssembly создали текстовый формат. Чтобы пойти еще дальше и избежать конфликтов синтаксиса, они использовали Lisp-подобный стиль s-выражения.

Безопасность и песочница

Одним из основных источников ошибок (и эксплойтов) в небезопасных языках является переполнение буфера. C и C ++ почти взаимозаменяемы с этой идеей, и это один из первых аспектов, которым вас учат при изучении этих языков. В обмен на небольшие накладные расходы WebAssembly добавляет эту страховочную сетку, применяя индексированную память фиксированного размера (хотя определенная память может быть увеличена).

На локальные переменные нашей функции, например example$var0, не ссылаются по адресу, а вместо этого они индексируются, обеспечивая уровень безопасности. Доступ предоставляется с помощью команд get_local и set_local, которые происходят в индексном пространстве локальных переменных.

Безопасность памяти была главным приоритетом при разработке WebAssembly. Прямо из документации: « Линейная память - песочница ; он не является псевдонимом для другой линейной памяти, внутренних структур данных механизма выполнения, стека выполнения, локальных переменных или другой памяти процесса ». Лин Кларк, опять же, написал отличную статью, описывающую это.

Основная идея сравнима с объектом Javascript ArrayBuffer - с изменяемым размером и проверкой по границам. То, что мы пытаемся достичь, - это изоляция программ, чтобы предотвратить распространение ошибок и вредоносного кода и повреждение данных, к которым у него не должно быть доступа.

Что умеет WebAssembly?

Одной из основных конечных целей WebAssembly была революция в возможностях графики в Интернете. Классические примеры - ZenGarden от EpicGames и Танки!.

Благодаря своему дизайну WebAssembly знаменует поворотный момент в веб-разработке. В арсенале Интернета появился новый инструмент для создания потрясающих впечатлений и обмена информацией. WebAssembly обеспечивает меньший размер кода, более быстрое выполнение, большую безопасность и много места для расширяемости. С такими идеями, как потоки, примитивы с одной инструкцией и несколькими данными (SIMD) и выполнение с нулевой стоимостью на горизонте, возможности WebAssembly, похоже, будут только расширяться.