Разработка

Что такое WebAssembly: революционная технология для веба и не только

Вы наверняка слышали, что WebAssembly очень быстрый. Но что это значит, и что это за технология в целом? Ответы на эти и другие вопросы в публикации.

Начать стоит с важного замечания. Заявления о высокой скорости и производительности WebAssembly (Wasm) или другой технологии не нужно воспринимать буквально. В них обычно идет речь об относительной скорости и производительности, то есть о скорости wasm по сравнению с чем-то. Держите это в уме.

WebAssembly — безопасный и эффективный низкоуровневый бинарный формат для веба. Давайте рассмотрим, что это значит.

Эффективность

Когда кто-то говорит о Wasm, как правило речь идёт о его потенциальной высокой производительности. Объясняют высокую производительность тем, что браузер компилирует Wasm в эффективный машинный код. Однако высокая производительность — не единственное объяснение эффективности WebAssembly.

Wasm изначально проектировался с учётом формата компактных бинарных файлов. Поэтому его можно быстро загружать. Но, что более важно, код Wasm может быть преобразован в машинный прямо во время загрузки. Это называется потоковой компиляцией.

До появления WebAssembly код, написанный на C++ или Rust, перед использованием в вебе приходилось компилировать в JavaScript. Современные виртуальные машины компилируют JavaScript в машинный код, но начать этот процесс, по сути, можно только тогда, когда весь JS-код будет загружен. WebAssembly, напротив, компилируется в машинный код по мере загрузки, что сильно влияет на скорость запуска кода на исполнение. Настолько сильно, что узким местом уже становится скорость загрузки данных по сети, особенно на мобильных устройствах.

Потоковая компиляция — это прекрасно. Но и здесь есть нюансы. Так что сама возможность компилирования кода в Wasm не всегда означает, что это непременно нужно делать. Не так уж и сложно написать такой JS-код, который будет компактнее по размеру, чем порция компилированного в Wasm кода на C++. Ведь коду на JS не нужно нести с собой машинерию для управления памятью или любые другие функции, предоставляемые самим браузером. То есть вам не придётся реализовывать Array или библиотеку для парсинга JSON. Эти возможности уже есть в среде исполнения JavaScript — в браузере.

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

Увы, такие подробности способны смутить иного читателя. Причина тому -- необходимость понимания того, как на самом деле работают и JS, и C++ и насколько они разные. Но не отчаивайтесь! Вы можете не понимать всех тонкостей, и тем не менее применять Wasm с пользой.

Безопасность

Развитие Web напрямую влияет на эволюцию браузеров. Браузеры постоянно наращивают свои возможности, всегда при этом оставаясь гарантами безопасности от злонамеренных действий со стороны. WebAssembly также следует по этому пути.

Wasm выполняется в песочнице, как и JavaScript. Он не имеет доступа к операционной системе, но имеет доступ к тем же API, что и JS. Поэтому WebAssembly не может выполнять произвольные системные вызовы или читать внутреннюю память браузера. Чтобы получить доступ к файловой системе, Wasm должен использовать File API, как JavaScript.

Это может показаться серьёзным ограничением, но безопасность — важный фундамент, на котором должна стоять любая технология.

WebAssembly обеспечивает дополнительную безопасность для таких языков, как C и C++. Классический пример — переполнение буфера, которое используется для внедрения вредоносного кода. Сами принципы работы WebAssembly делают невозможным появление этого класса уязвимостей. Вы можете переполнить буфер, C++ даёт такую возможность. Но внедрить вредоносный код не получится из-за отсутствия исполняемой памяти.

Однако, не стоит думать, что WebAssembly защищает от любых классов уязвимостей. Злоумышленник не сможет произвести атаку с инъекцией кода напрямую, однако у него остаётся возможность перехватить управление потоком исполнения через атаку повторного использования кода (code reuse attack) с помощью непрямых вызовов (indirect calls). Например, переполнение буфера способно вызвать перезапись указателя на функцию, да так, что указатель станет ссылаться на другую функцию. Упомянутые атаки против кода на C++ возможны благодаря самой природе языка. Использование языков вроде Rust позволяет избежать многих уязвимостей, но не всех. Да и для JavaScript существуют свои атаки подобного рода. Они есть и в других языках программирования, включая JavaScript.

Низкоуровневый бинарный формат

WebAssembly создавался в первую очередь как цель для компиляции, а не как язык, на котором будут писать вручную, хоть это и возможно. В основном вы будете писать код на более человекочитаемых языках, вроде C++ и Rust, и лишь затем компилировать ваш код в код машиночитаемый, который ещё называют "байт-кодом" (bytecode).

Пример кода WebAssembly

Пример кода C/C++, скомпилированного в байт-код


Байт-код похож на нативный машинный код, который использует ваш компьютер. Однако байт-код создан для виртуальной, а не реальной машины. Это делает код достаточно низкоуровневым, чтобы можно было производить оптимизации, но всё ещё портируемом, что позволяет не думать об архитектуре процессора (x64, ARM, и т.п.) на стороне конечного пользователя. Веб-браузеры имеют встроенные виртуальные машины для выполнения WebAssembly. Но вот ведь в чём штука: WebAssembly применим не только для Web. Об этом ниже.

Возможно, вы знакомы с другими форматами байт-кода, например, .Net CIL (CLR) или Java Bytecode (JVM). Вы могли бы подумать, почему разработчики не встроили в браузер один из этих (или других подобных) вариантов. Чтобы объяснить причину, придётся углубиться в технические нюансы, но здесь обойдёмся без этого. Достаточно понимать, что разные виртуальные машины имеют разные и часто несовместимые цели. Например, файлы Wasm могут быть верифицированы и скомпилированы виртуальной машиной на лету, что обеспечивает возможность потоковой компиляции. Другие форматы байт-кода не всегда обеспечивают такую возможность.

Существует множество других причин. Но даже если разработчики браузеров обеспечили бы поддержку одного из форматов байт-кода, вы не смогли бы исполнять существующие бинарные файлы как есть, так как в данном случае не соблюдается принцип безопасности: изоляция кода от операционной системы. Например, JVM имеет неограниченный доступ к файловой системе с помощью API типа java.io — запретной для веба технологии. Вместо этого разработчики WebAssembly выбрали путь исполнения в песочнице и использования существующих Web API.

WebAssembly уничтожит JavaScript?

Нет, JavaScript никуда не денется. Этот язык создан для того, чтобы на нём писали люди. Wasm создан для того, чтобы в него компилировался код, написанный людьми на C++, Rust и так далее. Фактически, по причине молодости WebAssembly на данный момент во многих случаях JavaScript всё ещё остаётся лучшим выбором для решения задач, связанных с веб. И при всех своих недостатках современный JS — отличный язык программирования.

Но никто не может предсказать будущее. WebAssembly не предназначен для того, чтобы уничтожить JavaScript. Но нельзя исключить, что в будущем какой-то язык программирования сможет конкурировать с JS в вебе благодаря компиляции в Wasm. Если это случится, скорее всего речь будет идти о новом языке типа Dart, Elm, Reason, созданном для веба.

Придётся ли компилировать JavaScript в Wasm?

JavaScript — предельно динамический язык. Только взгляните на этот забавный пример.

Пример кода JS


Если вы захотите скомпилировать произвольный JS-код в WebAssembly, например, библиотеку Lodash, вам придётся поставлять вместе с кодом ещё и солидную часть среды исполнения JavaScript. А чтобы достичь и той производительности, которая присуща браузерной реализации JS, вам придётся вместе с кодом поставлять среду исполнения практически целиком. Это непрактично из-за слишком большого размера файлов.

Поэтому компиляция JavaScript в Wasm в целом не очень хорошая идея. Но жёстко ограниченно подмножество JavaScript или аналогичный диалект могут быть быстрыми.

Например, Facebook использует оптимизатор JavaScript — Prepack. В целом этот проект не связан с Wasm. Но разработчики оптимизатора ищут способы безопасной статической компиляции некоторых паттернов кода JavaScript в WebAssembly. Пока неясно, насколько перспективны эти эксперименты.

Ещё один вариант — написание кода на языке, который похож на JavaScript, но не является им. AssemblyScript похож на TypeScript. Знакомые с JavaScript программисты могут использовать его, чтобы переписать критически важные для производительности компоненты JS-приложения.

AssemblyScript — многообещающая технология. Но она пока не подходит для создания приложений от начала до конца. Большая часть ограничений AssemblyScript связана с будущими возможностями Wasm. Важно не путать AssemblyScript с TypeScript. TypeScript представляет собой надмножество JavaScript, поэтому вся функциональность JS доступна в TS.

WebAssembly 1.0 aka MVP

Главный барьер для стремительного роста популярности Wasm — поэтапная разработка этой технологии. Из-за неё не все возможности WebAssembly можно широко применять. Версия 1.0, которую также назвали MVP, лучше всего подходит для языков типа C/C++ и Rust, но не всегда хорошо работает даже с ними.

Это правильное намеренное решение, так как стандарты языков и технологий постоянно совершенствуются. Разработчики получают обратную связь и принимают решения на её основе. Это позволяет избежать выпуска откровенно неудачных решений, которые потом практически невозможно изъять из обращения.

В будущем Wasm будет поддерживать высокоуровневые языки, например, Java и OCaml. Даже Dart, Elm и Reason когда-то будут компилироваться в WebAssembly. Одним из «отсутствующих кусочков» всё ещё является сборщик мусора (garbage collection, GC). GC работает в Wasm, но пока не очень эффективно.

Также проблемой остаётся прямой доступ к API DOM/HTML. Сейчас WebAssembly должен делать вызов с помощью JS, когда необходимо обратиться к WEB API. Wasm не может напрямую обрабатывать сгенерированные WEB API объекты. Поэтому инструментам вроде Emscripten и wasm-bindgen приходится делать дополнительную работу, чтобы скрыть этот факт от вас.

Как работать с WebAssembly

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

Если вы пишете на C/C++, используйте Emscripten. Он оборачивает Clang/LLVM и выдаёт JavaScript-код, который может взаимодействовать с WEB API.

Если вы пишете на Rust, вам повезло. Разработчики ядра Rust активно работают над совместимостью с WebAssembly. Подробности можно узнать из книги Rust and WebAssembly.

Есть и другие языки, которые работают над поддержкой Wasm, например, Go.

Не Web, да и не ассемблер

В этой публикации речь идёт преимущественно об использовании WebAssembly в браузере. Стоит отметить, что название технологии вводит в заблуждение. Wasm — не ассемблер, это текстовое представление бинарного байт-кода.

Разработчики WebAssembly понимают, что само по себе сотрудничество нескольких технологических гигантов для выработки единого и свободного стандарта команд — беспрецедентное событие. И тем сильнее они заинтересованы в том, чтобы стандарт вообще не был жёстко к привязан к браузерам.

Что ещё важнее, WebAssembly не предназначен исключительно для веба. В настоящее время использование Wasm вне браузеров набирает обороты. Ниже несколько конкретных примеров.

  • Nebulet. Микроядро операционной системы для запуска Wasm.
  • wasmjit, виртуальная машина WebAssembly.
  • Wasmer. Универсальная среда исполнения Wasm.
  • Воркеры Cloudflare для запуска Wasm.
  • Fastly Terrarium, среда для развёртывания Wasm.
  • Ewasm. Виртуальная машина WebAssembly.
  • Pariti wasmi, интерпретатор Wasm.

Заключение

WebAssembly можно использовать в современных браузерах. Это обеспечивает преимущества, когда крайне важна производительность. Однако сфера использования Wasm не ограничивается вебом. Это обещает интересное будущее для разработки в целом.

Адаптированный перевод публикации WebAssembly: Neither Web, Nor Assembly, but Revolutionary by Allen Conway. Мнение автора оригинальной публикации может не совпадать с мнением администрации «Хекслета».

Дмитрий Дементий 7 дней назад
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →