C и C++ в Wasm: Emscripten

Знакомимся с Emscripten — главным мостом из C/C++ в Wasm.

Emscripten — это тулчейн на базе LLVM, компилирующий C и C++ в WebAssembly вместе с эмуляцией привычного системного окружения.

Зачем нужен Emscripten

Огромный пласт быстрых библиотек написан на C и C++: видеокодеки, движки игр, физика, графика. Переписывать их на JS бессмысленно. Emscripten берёт существующий C/C++ код и компилирует в Wasm, причём так, чтобы он работал в браузере, где нет привычных файлов, потоков и системных вызовов. Это связь между огромной экосистемой C и вебом.

Простой пример на C

Возьмём функцию на C и пометим её для экспорта в Wasm. Исходник на C мы помечаем language-text — он не исполняется в браузерной песочнице, его компилируют тулчейном:

// add.c
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
    return a + b;
}

Макрос EMSCRIPTEN_KEEPALIVE говорит компилятору не выбрасывать функцию при оптимизации и экспортировать её. Компилируем командой emcc:

emcc add.c -o add.js -s EXPORTED_RUNTIME_METHODS=ccall,cwrap
# создаст add.wasm + add.js (склейка для загрузки)

Что генерирует Emscripten

Помимо .wasm, Emscripten обычно создаёт JS-обёртку. Она грузит модуль, настраивает память и эмулирует системное окружение: виртуальную файловую систему, printf (через console.log), даже SDL/OpenGL поверх WebGL. Благодаря этому код, который раньше писал в файлы и рисовал в окно, начинает работать в браузере почти без изменений.

В нативном CЧто делает Emscripten в вебе
printfвыводит через console.log
файлы (fopen)виртуальная ФС в памяти / IndexedDB
SDL / OpenGLрисует через Canvas / WebGL
malloc/freeработа с линейной памятью

Логика на JS для проверки

Чтобы убедиться, что наша C-функция считает верно, повторим её логику на JS — результат будет тот же, что и у скомпилированного Wasm:

function add(a, b) { return a + b; }   // эквивалент C-функции
console.log("add(7, 8) =", add(7, 8));
console.log("add(-3, 10) =", add(-3, 10));

Вывод:

add(7, 8) = 7 + 8 = 15
add(-3, 10) = 7

Как работает под капотом

Emscripten использует LLVM: компилятор Clang переводит C/C++ в промежуточное представление LLVM IR, а затем бэкенд генерирует из него Wasm. Эмуляция окружения (libc, файлы, графика) — это библиотеки на JS и Wasm, которые Emscripten подмешивает к вашему коду. Поэтому простой printf("Hi") в браузере на самом деле вызывает цепочку: ваш код → эмулированный libc → импортированная JS-функция вывода → console.log.

Частые ошибки

  • Функция «пропала» — без EMSCRIPTEN_KEEPALIVE или списка экспортов оптимизатор удалит неиспользуемую функцию.
  • Ждать прямой работы с файлами диска — в браузере это виртуальная ФС, а не реальный диск.
  • Огромный размер — эмуляция libc и графики тянет вес; для маленьких задач это избыточно.

Итоги

  • Emscripten компилирует C/C++ в Wasm на базе LLVM/Clang.
  • Он эмулирует системное окружение: printf, файлы, SDL/OpenGL.
  • Команда emcc создаёт .wasm и JS-склейку.
  • EMSCRIPTEN_KEEPALIVE сохраняет функцию от удаления оптимизатором.
Проверьте себя
1. Что делает Emscripten кроме компиляции в .wasm?
AТолько сжимает код
BГенерирует JS-обёртку и эмулирует системное окружение (printf, файлы, SDL/OpenGL)
CШифрует исходники
DЗапускает код на сервере
2. Зачем нужен макрос EMSCRIPTEN_KEEPALIVE?
AУскоряет функцию
BСохраняет функцию от удаления оптимизатором и помечает на экспорт
CДелает функцию приватной
DОтключает оптимизации полностью
3. Во что превращается printf при компиляции через Emscripten для браузера?
AВ системный вызов ОС
BВ цепочку через эмулированный libc к console.log
CВ запись на диск
DОн не поддерживается