Wasmtime 47 включила сборку мусора и исключения по умолчанию
Bytecode Alliance объявила, что с выходом Wasmtime 47 два предложения по расширению WebAssembly, Wasm GC (сборка мусора) и Wasm Exceptions (исключения), включены по умолчанию. До этого языкам с объектной моделью данных (объекты и ссылки, а не сырые указатели и память) приходилось встраивать собственный сборщик мусора прямо в .wasm-бинарник: это раздувало размер файлов и лишало разработчиков привычных техник вроде stack maps и обхода стека для поиска GC-корней.
Предложение Wasm GC расширяет язык WebAssembly: программа может определять собственные типы struct и array и отношения подтипов, а временем жизни и удалением экземпляров этих типов теперь занимается рантайм. Пример из статьи, определение узла бинарного дерева как struct с полями key, left, right, value; новый экземпляр создаётся инструкцией struct.new, поля читаются и записываются через struct.get и struct.set.
Предложение Wasm Exceptions даёт языкам с исключениями (throw/try/catch) прямую поддержку на уровне рантайма вместо самодельных соглашений о вызовах, где каждая функция вручную возвращает флаг «было исключение или нет», а каждый вызов обязан его проверять, это раздувало бинарники и добавляло накладные расходы на обычном, безошибочном пути выполнения. С новым предложением рантайм реализует throw/try/catch классическим механизмом раскрутки стека (unwinding), который не создаёт накладных расходов на нормальном пути.
В Wasmtime сборщик мусора реализован как простой копирующий коллектор в стиле Чейни с двумя полупространствами: «активным», куда идёт выделение памяти, и «холостым». Во время сборки живые объекты копируются из холостого пространства в новое активное, а все ссылки на GC-корни (например, в стековых кадрах Wasm) обновляются на новые адреса. Выделение памяти, простой bump-pointer, без барьеров чтения или записи. Куча GC построена поверх обычной линейной памяти WebAssembly: ссылка на GC-объект, это не указатель, а 32-битный индекс внутри линейной памяти. Такая конструкция даёт защиту в глубину (даже баг в коллекторе, повреждающий кучу, не позволит вредоносной программе выйти за пределы песочницы к памяти хоста), скорость (страницы виртуальной памяти работают как guard pages вместо явных проверок границ, интеграция с пулом аллокатора инстансов сохраняет время создания инстанса на уровне 5 микросекунд, а 32-битные ссылки компактнее 64-битных указателей на 64-битных машинах) и переносимость (уже написанный код работы с линейной памятью работает на всех поддерживаемых платформах, включая «голое железо»).
Чтобы повысить уверенность в корректности коллектора, команда расширила фаззинг-инфраструктуру: генератор wasm-smith научили порождать Wasm-программы с использованием GC, и добавили два специализированных фаззера, один нацелен на сложные графы объектов, ссылки на типы и отношения подтипов, второй ищет повреждения кучи из-за багов коллектора или ошибок оптимизатора компилятора.
Авторы прямо предупреждают об ограничениях: основное внимание пока уделено корректности коллектора, а не производительности, по пропускной способности и задержкам он пока уступает движкам вроде V8 и SpiderMonkey, за плечами которых десятилетия оптимизаций. Коллектор в первую очередь настроен под типичный сценарий использования Wasmtime в проде: множество мелких недолговечных инстансов, каждый из которых обрабатывает одну-две задачи и затем выбрасывается вместе со своей GC-кучей, а не под один долгоживущий процесс.
В планах команды, расширить alias-анализ компилятора информацией о GC-типах для более агрессивных оптимизаций (перенос store-to-load, устранение избыточных загрузок из памяти), а следующая крупная функциональная веха, прототип интеграции GC с component model поверх ленивого приведения значений (lazy value lowering): это позволит языкам со сборкой мусора обходиться без служебной линейной памяти при передаче данных между компонентами.
Ключевые факты
- Wasmtime 47 включила Wasm GC и Wasm Exceptions по умолчанию, оба предложения уже вошли в основную спецификацию WebAssembly.
- Wasm GC позволяет языкам с объектной моделью данных не тащить собственный сборщик мусора внутри .wasm-бинарника, рантайм сам управляет временем жизни объектов через типы struct и array.
- Коллектор Wasmtime, копирующий, в стиле Чейни, с двумя полупространствами; построен поверх линейной памяти WebAssembly, а GC-ссылка, это 32-битный индекс, а не указатель.
- Wasm Exceptions заменяет самодельные соглашения о вызовах с ручной проверкой флага ошибки на конструкции throw/try/catch с нулевыми накладными расходами на обычном пути выполнения.
- Производительность коллектора пока уступает V8 и SpiderMonkey, команда сфокусировалась на корректности (расширенный фаззинг через wasm-smith и два новых специализированных фаззера) и на сценарии множества мелких недолговечных инстансов.
Почему это важно
До сих пор WebAssembly был удобной целью компиляции в основном для языков с ручным управлением памятью, C, C++, Rust. Языкам с объектной моделью данных, которые полагаются на сборку мусора и исключения (Java, Kotlin, C#, OCaml, Dart и подобные), приходилось встраивать собственный сборщик мусора внутрь .wasm-файла, что раздувало бинарники и не давало использовать типичные для нативных GC техники вроде stack maps. Включение Wasm GC и Wasm Exceptions по умолчанию в популярном рантайме означает, что для этих языков WebAssembly становится практичной целью компиляции без прежних компромиссов по размеру файлов и производительности вызовов.
Кому это важно
Разработчикам компиляторов и тулчейнов для языков с GC и исключениями, которые целятся в WebAssembly (авторы бэкендов для Kotlin, Java, OCaml, Dart, Scheme и подобных языков); инженерам, встраивающим Wasmtime как песочницу выполнения, для edge-функций, плагинов, serverless-нагрузок, которым теперь доступен более широкий спектр языков без самодельного GC внутри модуля; и сообществу WebAssembly в целом, поскольку оба предложения официально вошли в основную спецификацию.
Как это применить
Функциональность доступна уже сейчас: начиная с Wasmtime 47 GC и исключения включены по умолчанию, отдельно активировать их не нужно. Разработчикам тулчейнов стоит проверить компиляцию своих языков с использованием типов struct и array из Wasm GC (в статье это иллюстрирует пример узла бинарного дерева с полями key, left, right, value, создаваемого инструкцией struct.new и читаемого через struct.get и struct.set) и конструкций throw/try/catch вместо самодельной обработки ошибок. Авторы прямо приглашают опробовать новую поддержку и прислать отзывы.
Можно ли доверять
Источник, официальный блог Bytecode Alliance, разработчиков Wasmtime, и это первичное объявление о релизе, а не пересказ третьих лиц. Технические детали, устройство коллектора, механизм фаззинга, оптимизации памяти, описаны конкретно и проверяемо. При этом команда прозрачно указывает на ограничения текущей реализации: производительность коллектора пока не оптимизирована и уступает движкам с многолетней историей, так что материал не пытается приукрасить готовность инструмента.
Риски и подводные камни
Коллектор Wasmtime новый и, по признанию самих авторов, ещё не прошёл «десятилетия performance-инжиниринга», по пропускной способности и задержкам он уступает V8 и SpiderMonkey. Он оптимизирован под сценарий множества мелких недолговечных инстансов, а не под один долгоживущий процесс с большой кучей, для таких нагрузок поведение коллектора может быть далёким от ожидаемого. Интеграция GC с component model пока не готова и остаётся следующим пунктом роадмапа, так что для передачи данных между компонентами языкам с GC пока по-прежнему нужны обходные пути.
«Мы рады, что достигли этой вехи! Опробуйте новую поддержку GC и исключений в Wasmtime и расскажите нам, как это работает.»
— команда Wasmtime (Bytecode Alliance)