[Перевод] Компилируем С\С++ код в WebAssembly

WebAssembly — это новый бинарный формат, в который могут быть скомпилированы веб-приложения. Он проектируется и реализуется прямо в тот момент, когда вы читаете эти строки и двигают его вперёд разработчики всех основных браузеров. Всё меняется очень быстро! В этой статье мы покажем текущее состояние проекта с достаточно глубоким погружением в инструментарий по работе с WebAssembly.

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

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

Ядро библиотеки Binaryen предназначено для парсинга и генерации WebAssembly, а также представлении его кода в виде абстрактного синтаксического дерева (AST). На основе этих возможностей созданы следующие полезные утилиты:

  • утилита для командной строки, способная загрузить WebAssembly-модуль, распарсить его и выполнить его код как интерпретатор (произвести действия, вывести результат в консоль). Для загрузки модуля и вывода результата используется временная нотация s-выражений, имеющая суффикс .wast (я напомню, что окончательный формат представления бинарных модулей WebAssembly всё ещё в процессе разработки).
  • asm2wasm — утилита для компиляции asm.js в WebAssembly.
  • wasm2asm — утилита для компиляции WebAssembly в asm.js (ещё разрабатывается)
  • s2wasm, которая компилирует .s-файлы (формат, создаваемый новым бекендом для WebAssembly в LLVM) в WebAssembly.
  • wasm.js — порт Binaryen на JavaScript. Это позволит запускать все вышеуказанные инструменты прямо в браузере.

О Binaryen можно посмотреть вот эти слайды.

Ещё раз напомню, что WebAssembly находится в стадии активного проектирования, а значит входные и выходные форматы Binaryen (.wast, .s) не окончательны. Binaryen постоянно обновляется с обновлением спецификации WebAssembly. Степень кардинальности изменений со временем снижается, но гарантировать какой-либо совместимости никто, конечно, пока не может.

Давайте рассмотрим несколько областей, где Binaryen может быть полезен.

Компиляция в WebAssembly с использованием Emscripten

Emscripten может компилировать код на С в asm.js, а Binaryen (с помощью утилиты asm2wasm) может компилировать asm.js в WebAssembly, таким образом связка Emscripten + Binaryen даёт нам полный набор инструментов для компиляции кода на С и С++ в WebAssembly. Вы можете запустить asm2wasm на asm.js коде, но проще позволить Emscripten сделать это за вас, как-то так:

emcc file.cpp -o file.js -s ‘BINARYEN=”path-to-binaryen”’

Emscripten скомпилирует файл file.cpp и на выходе даст вам JavaScript-файл, плюс отдельный файл в формате .wast с WebAssembly. Под капотом Emscripten скомпилирует код в asm.js, сам запустит для него asm2wasm и сохранит результат. Более детально это описано на Вики проекта Emscripten.

Но подождите, какой смысл компилировать что-то в WebAssembly, если браузеры его пока не поддерживают? Отличный вопрос! :) Да, мы пока что не сможем запустить этот код ни в одном браузере. Но мы уже можем кое-что потестировать с его помощью. Итак, мы хотим проверить, верный ли бинарник создала связка Emscripten + Binaryen. Как же это сделать? Для этого мы можем использовать wasm.js, который Emscripten интегрировал в выходной .js-файл, полученный командой вызова emcc (см. выше). wasm.js содержить в себе порт Binaryen на Javascript, включая интерпретатор. Если вы запустите file.js (в node.js или в браузере), то вы получите результат выполнение WebAssembly. Это позволяет нам фактически подтвердить, что скомпилированный бинарник WebAssembly работает верно. Вы можете посмотреть на пример такой программы, плюс ещё пару примеров есть в репозитории для тестовых целей.

Конечно же, мы пока ещё не стоим на твёрдой земле со всеми этими инструментами. Тестовое окружение получается странноватое. С++ код компилируется в WebAssembly и затем выполняется в WebAssembly-интерпретаторе, который сам по себе написан на С++, но портирован на JavaScript. И нет пока никаких других способов запустить всё это. Но у нас есть несколько причин верить полученным результатам:

  • Выходной код проходит все тесты Emscripten. Они включают обработку множества реальных кодовых баз (Python, zlib, SQLite) плюс множество «подозрительных» ситуаций в С и С++. По опыту можно сказать, что если тесты Emscripten проходят для всех этих случаев, то и другой код, обработанный Emscripten будет вести себя нормально
  • Интерпретатор Binaryen проходит все внутренние тесты WebAssembly, определяющие правильность выполнения WebAssembly. Другими словами, когда мы получим поддержку WebAssembly в браузерах, они должны будут вести себя тем же образом (ну разве что работать быстрее).
  • Большую часть работы делает Emscripten, который является стабильным компилятором, давно используемым в продакшене и лишь относительно малая часть поверх него делается с помощью Binaryen (его код составляет всего пару тысяч строк). Меньше кода — меньше багов.

Всё это показывает, что мы уже имеем некоторый результат, можем скомпилировать код на С и С++ в WebAssembly и даже как-то его запустить.

Заметьте, что WebAssembly — всего лишь ещё одна новая фича, и, отвлёкшись от неё, всё остальное в Emscripten работает по-прежнему: Emscripten позволяет использовать libc и syscalls, OpenGL/WebGL код, интеграцию с браузерами, интеграцию с node.js и т.д. В результате проекты, которые уже используют Emscripten, смогут переключиться на WebAssembly просто добавлением нового параметра командной строки. И это позволит С++ проектам быть скомпилированным в WebAssembly и работать в браузерах без всяких усилий.

Использование нового экспериментального бекенда к LLVM для WebAssembly с Emscripten

Мы только что увидели новый важный этап в развитии Emscripten, который дал ему возможность создавать WebAssembly-модули и даже тестировать их работу. Но на этом работа не останавливается: это было всего-лишь использование текущего компилятора asm.js, вместе с утилитой asm2wasm. Есть новый бекенд к LLVM для WebAssembly (вернее пока ещё нет, но активно пишется) — прямо в основной ветке разработки LLVM. И, хотя он ещё и не готов для реального применения, со временем он станет очень важным инструментом. Binaryen поддерживает формат его выходных данных.

LLVM-бекенд для WebAssembly, как и большинство LLVM-бекендов, создаёт ассемблерный код, в данном случае в специальном .s-формате. Этот формат близок к WebAssembly, но не прямо идентичен ему — он скорее похож на вывод компилятора С (линейный список инструкций, одна инструкция на строку), чем на абстрактное синтаксическое дерево WebAssembly. Данный .s-файл может быть преобразован в WebAssembly достаточно тривиальным способом (в общем-то, Binaryen включает утилиту s2wasm, которая именно это и делает — посмотрите насколько она проста). Вы можете запустить её саму по себе, или использовать для этого Emscripten, который теперь поддерживает новую опцию WASM_BACKEND, которую вы можете использовать вот так:

emcc file.cpp -o file.js -s ‘BINARYEN=”path-to-binaryen”’ -s WASM_BACKEND=1

Обратите внимание, что вам также нужно использовать опцию BINARYEN, поскольку s2wasm это часть Binaryen. Когда все эти опции указаны, Emscripten использует новый бекенд для WebAssembly вместо использования компилятора asm.js. После вызова бекенда и получения от него файла в .s-формате Emscripten вызовет s2wasm для конвертации в WebAssembly. Несколько примеров программ, которые вы уже можете собрать подобным способом, вы можете найти на Вики проекта Emscripten.

Таким образом, у нас есть два способа собрать WebAssembly-модуль с использованием Binaryen:

  1. Emscripten + asm.js backend + asm2wasm, что работает уже прямо сейчас и должно быть относительно простым и приемлимым вариантом
  2. Emscripten + новый бекенд для WebAssembly + s2wasm, который пока не полностью рабочий вариант, но с развитием бекенда для WebAssembly будет выходить на первый план.

Цель на данный момент — сделать переход от первого способа ко второму как можно менее сложной. В идеале всё должно свестить к замене одного аргумента в командной строке.

Таким образом у нас формируется чёткий план:

  1. Используем Emscripten для генерации asm.js кода (уже сегодня)
  2. Переходим на генерацию WebAssembly через asm2wasm (уже можно, но ещё не готовы браузеры)
  3. Переходим на генерацию WebAssembly через новый LLVM бекенд (как только он будет готов)

Каждый шаг даёт новые преимущества пользователям (скорость!) и практически не вызывает сложностей у разработчиков.

В заключение хочу сказать, что хотя данная статья и написана о Binaryen в контексте его применения с Emscripten, это всё-ещё отдельная библиотека для WebAssembly общего применения. Если у вас есть идеи создания некоторых инструментов для работы с WebAssembly — вы можете взять библиотеку Binaryen и работать с ней, не оглядываясь на Emscripten, LLVM или что-то ещё.

© Habrahabr.ru