Учебник по использованию WebAssembly с Emscripten и C / C ++ (даже если вы не знакомы с C / C ++)

JavaScript, который был классно разработан за 10 дней еще в 1995 году, до недавнего времени оставался единственным языком, который вы могли использовать изначально в Интернете. Только в 2017 году была выпущена WebAssembly 1.0, что сделало его вторым языком, поддерживаемым всеми основными веб-браузерами.

Что такое WebAssembly?

Как следует из названия, вы можете рассматривать WebAssembly как язык ассемблера для Интернета (хотя позже мы увидим, почему он также полезен вне браузера). Как и в случае с другими языками ассемблера, о которых вы слышали в CS101, на самом деле никто не пишет код непосредственно в WebAssembly, кроме Ben Smith 😉. Вместо этого мы, простые смертные, обычно используем его как цель компиляции, т.е. вы берете код, написанный на других языках (например, C, C ++ и Rust), компилируете этот код в WebAssembly и запускаете его в браузере .

О чем весь этот шум?

WebAssembly - это большое дело ™, потому что, в отличие от других технологий (таких как JVM), это стандарт, рожденный в результате сотрудничества между всеми основными поставщиками браузеров, и он был разработан с учетом модели веб-песочницы. Вот что делает WebAssembly таким привлекательным.

Кстати, именно поэтому WebAssembly может быть очень полезен вне браузера, о чем мы поговорим позже.

Давайте напишем WebAssembly!

В качестве нашей программы Hello World мы найдем готовый код на C для вычисления MD5-хэша строки, скомпилируем его в WebAssembly и запустим в браузере!

Шаг 1. Настройте среду

Emscripten - популярный инструмент, используемый для компиляции кода из C / C ++ в WebAssembly. Это мощный инструмент, потому что он предоставляет множество очень полезных функций, облегчающих нам жизнь при переносе кода на WebAssembly. Сюда входят оболочки для компиляторов C / C ++, автоматически сгенерированный шаблонный код JavaScript для запуска нашего кода WebAssembly, виртуальная файловая система (для кода, который должен читать / писать файлы) и доступ к множеству предварительно портированных библиотек (например, zlib или SDL).

Хотя установка Emscripten с нуля может занять некоторое время, я сделал образ Docker, который вы можете использовать, чтобы упростить этот процесс:

# Fetch image containing Emscripten
$ docker pull robertaboukhalil/emsdk:1.38.30
# Create a local folder for our WebAssembly adventures
$ mkdir ~/wasm
$ cd ~/wasm
# Launch a container called "wasm" (in the background)
$ docker run -dt --name wasm \
    --volume ~/wasm:/src \
    -p 12345:80 \
    robertaboukhalil/emsdk:1.38.30

Параметр --volume позволяет нам запускать Emscripten внутри контейнера и извлекать сгенерированные .wasm файлы непосредственно из ~/wasm (т.е. нет необходимости передавать файлы из контейнера на хост!). Обратите внимание, что мы также предоставляем порт 80 контейнера как порт 12345, и я настроил образ контейнера таким образом, чтобы он автоматически запускал веб-сервер, чтобы вы могли напрямую просматривать .html файлы, выводимые Emscripten, как мы увидим ниже.

Уф, теперь мы готовы использовать WebAssembly! 😅

Шаг 2. Скомпилируйте код в WebAssembly

В нашем примере с хешем MD5 мы будем использовать это репо: https://github.com/pod32g/MD5.

Зайдем внутрь контейнера и получим код:

# Go inside the container we created above
$ docker exec -it wasm bash
# Now we're inside the container!
$ git clone https://github.com/pod32g/MD5.git

Внутри папки MD5 вы увидите файл md5.c:

$ ls
README.md  md5.c

Вы можете увидеть полный код здесь, но вот псевдокод, чтобы проиллюстрировать структуру кода:

# md5.c
function md5(myString):
  return some_complicated_math(myString)
function main(argv):
  # Expect user to provide a string as input:
  #   argv[0] = name of executable
  #   argv[1] = string to hash
  if length(argv) < 2:
    print("usage: %s 'string'\n", argv[0])
    return 1
  # Calculate and output the hash
  hash = md5(argv[1])
  print(hash)

По сути, main() выбирает строку ввода, предоставленную пользователем, и вызывает функцию md5() для этой строки.

Отложив на секунду WebAssembly, если бы мы хотели скомпилировать md5.c в старый добрый двоичный файл, мы бы сделали:

$ gcc -o md5 md5.c

Поскольку Emscripten предоставляет оболочки для таких инструментов, как gcc, компиляция в WebAssembly выполняется просто:

$ emcc -o md5.html md5.c

Все, что мы сделали, это использовали оболочку Emscripten gcc под названием emcc (Emscripten также предоставляет оболочки для g++, make, cmake и configure, называемые em++, emmake, emcmake и emconfigure соответственно). Мы также запрашиваем у Emscripten .html выходной файл вместо двоичного файла.

За кулисами Emscripten создает скомпилированный файл WebAssembly md5.wasm, но он также генерирует md5.js и md5.html, которые представляют собой шаблонный код, который нам нужен для автоматической инициализации нашего модуля WebAssembly.

Шаг 3. Запуск нашего кода в браузере

Если вы следовали приведенным выше инструкциям по созданию контейнера wasm, вы сможете запустить http: // localhost: 12345 / MD5 / md5.html в своем браузере и увидеть что-то вроде этого:

Чтобы протестировать наш новый инструмент, откройте консоль разработчика и введите следующее, чтобы вычислить хэш md5 строки test:

> Module.callMain([ "test" ])
098f6bcd4621d373cade4e832627b4f6

Module.callMain() - служебная функция, предоставляемая связующим кодом Emscripten (md5.js), которая вызывает функцию main() нашей программы C со списком аргументов (в нашем случае только одним). В процессе callMain() также преобразует строку test в массив целых чисел (поскольку Wasm понимает только int и float!).

Шаг 4: простое приложение

Конечно, в реальной жизни мы не просим пользователя открыть консоль разработчика, так как же нам использовать WebAssembly в наших приложениях?

Вот простой app.html файл, иллюстрирующий эту идею. Когда страница загружается, она запрашивает у пользователя строку, запускает на ней наш код WebAssembly и выводит пользователю хеш MD5. Для этого мы расширим объект Module, о котором я упоминал выше - подробности выходят за рамки этой статьи, но комментарии должны дать вам представление о том, как это работает:

<!-- app.html -->
<script type="text/javascript">
var Module = {
    // Don't run main() on page load
    noInitialRun: true,
    // When Wasm module is ready, ask user for a string
    onRuntimeInitialized: () => {
        let myString = prompt("Enter a string:");
        Module.callMain([myString]);
    },
    // Redirect stdout to alert()
    print: txt => alert(`The MD5 hash is: ${txt}`)
};
</script>
<script src="md5.js"></script>

Теперь вы можете запустить http: // localhost: 12345 / MD5 / app.html и хешировать интересующую строку:

И вуаля! Всего за несколько шагов мы перенесли готовую утилиту с C на WebAssembly.

Конечно, это был простой пример: у нас есть единственный файл C с простыми входными данными, в котором мы предоставляем только функцию main(), не нуждаемся в поддержке файловой системы, никакой графики и никаких тяжелых вычислений. Тем не менее, надеюсь, этот пример дает вам представление о том, как код компилируется для WebAssembly.

⚠️ Осторожно: впереди бесстыдная затычка️ ⚠️

Если вы готовы погрузиться в WebAssembly более глубоко (и как портировать гораздо более сложные кодовые базы, такие как awk или Pacman ), посмотрите мою книгу Повышайте уровень с WebAssembly , практическое и доступное руководство по использованию WebAssembly в ваших собственных веб-приложениях 😊

Примеры использования в реальном мире

Теперь, когда мы ознакомились с нашим примером «Hello MD5 World», давайте рассмотрим реальные варианты использования WebAssembly.

1) Используйте WebAssembly, чтобы хирургическим путем ускорить работу ваших веб-приложений.

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

Если библиотека, выполняющая необходимый анализ данных, уже существует и написана на C / C ++ / Rust, WebAssembly - очевидный выбор. В таких случаях не стоит тратить усилия на перенос кода на JavaScript в первую очередь (а затем переходить к его проверке и оптимизации!).

Ресурсы:

2) Перенос десктопных приложений и игр на WebAssembly

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

Ресурсы:

  • Практический пример: как AutoCAD использовал WebAssembly для переноса своей 30-летней кодовой базы в Интернет без необходимости переписывать все с нуля 😅
  • Демо: воспроизвести в браузере клон Doom3, созданный с помощью WebAssembly.
  • Учебник: Как перенести в Интернет простую игру Asteroids (написанную на C)

3) Перенос инструментов командной строки в Интернет

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

Ресурсы:

  • Код: SQLite работает в браузере!
  • Учебник: как создать площадку для инструмента командной строки jq

4) WebAssembly вне браузера

Несмотря на Интернет в названии, полезность WebAssembly выходит далеко за рамки браузера. Фактически, вы можете использовать его как JVM-подобный способ запуска .wasm двоичного файла на любой платформе, при условии, что платформа поддерживает среду выполнения WebAssembly, такую ​​как Wasmtime или Wasmer.

Но если мы хотим продуктивно запускать код WebAssembly вне браузера, как мы поддерживаем такие функции, как файлы, потоки и сокеты? Ответ заключается в том, что нам нужен какой-то стандартный интерфейс, который WebAssembly может использовать для связи с внешним миром для запроса ресурсов. Чтобы решить эту проблему, ведется постоянная работа по стандартизации системного интерфейса WebAssembly (или WASI), цель которой - сделать это возможным.

Мы все еще только начинаем запускать WebAssembly вне браузера (о WASI было объявлено всего несколько месяцев назад, в марте 2019 года), но, как вы увидите в ресурсах ниже, мы живем в захватывающие времена!

Ресурсы:

  • Запись в блоге: подробное знакомство с WASI, как это работает и почему это полезно
  • Демо: запускать двоичные файлы WebAssembly из командной строки с помощью Wasmer.
  • Учебник: использование библиотеки Wasmer's Go для выполнения программ WebAssembly

5) Бессерверная веб-сборка

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

Ресурсы:

  • Учебник: бессерверный Rust с AWS Lambda и WebAssembly
  • Учебное пособие: запуск бессерверной WebAssembly на воркерах Cloudflare
  • Сообщение в блоге: Как быстро запускает WebAssembly на периферии, чтобы значительно улучшить время выполнения

Заключительное слово

Хотя мы все еще находимся на ранних стадиях, WebAssembly уже используется в реальных приложениях, будь то перенос целых интерфейсов командной строки / игр / настольных приложений в Интернет, хирургическое применение его для ускорения вычислений внешнего интерфейса или даже его использование. вне браузера. В ближайшем будущем WebAssembly получит поддержку потоков, инструкций SIMD, сборки мусора и возможности прямого управления DOM, и это лишь некоторые из них.

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

Я надеюсь, что эта статья сделала WebAssembly немного менее похожим на волшебство 🧙 и немного больше похожим на мощный инструмент в вашем наборе инструментов.

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