В трекере Go появился proposal #78189. Он предлагает добавить стандартный пакет
runtime/jit, который позволит Go-рантайму корректно работать с динамически генерируемым кодом.В чём проблема
Go компилирует код заранее. Стек-анвайндер рантайма устроен так, что знает только о функциях, зарегистрированных в
moduledata на этапе линковки. Если на стеке оказывается счётчик команд из динамически сгенерированного кода — JIT-компилятора, WASM-рантайма или встроенной VM — рантайм падает с фатальной ошибкой:
runtime: unexpected return pc for somefunc called from 0x7f4a12340010
fatal error: unknown caller pc
Это затрагивает любую программу, которая встраивает JIT, например, база данных компилирует SQL в нативный код, хостит WASM, использует FFI с трамплинами или встраивает скриптовые VM вроде Lua.
Единственный обходной путь сегодня — не вызывать Go-функции из динамического кода вообще, либо использовать промежуточные C-фреймы через
cgo, что добавляет около 40 наносекунд накладных расходов и требует cgo-тулчейна.Что предлагают
Новый пакет
runtime/jit с минимальным API. Ключевые части:Region — структура, описывающая регион исполняемой памяти. Содержит колбэки для анвайндинга (Next), описания фреймов в трейсбеке (Describe) и сканирования корней GC (ScanStack).Три режима анвайндинга:
UnwindStop — трейсбек обрывается на JIT-фреймеUnwindSkip — рантайм пропускает пользовательские фреймы и продолжаетUnwindDeclare — пропускает и описывает фреймы в трейсбеке.handle := jit.Register(jit.Region{
Lo: uintptr(codeStart),
Hi: uintptr(codeEnd),
Unwind: jit.UnwindDeclare,
Next: myUnwindNext,
Describe: myDescribe,
ScanStack: myScanStack,
})
defer handle.Unregister()Для кооперации со шедулером — функция
Preempt(). JIT-код вызывает её в точках безопасности (заголовки циклов, входы в функции) и возвращает управление Go, если шедулер или GC запросили остановку.Как это работает внутри
Реестр регионов построен на copy-on-write с атомарным свапом указателя. Анвайндер получает доступ к нему без блокировок. Патчи в
traceback.go проверяют пользовательские регионы перед тем, как бросить ошибку на неизвестный счётчик команд. GC получает хук в markroot() для вызова ScanStack.Горутина остаётся в состоянии
_Grunning пока выполняется JIT-код — никакого переключения состояний, никакого освобождения. Это принципиальное отличие от cgocall, который обращается с чужим кодом как с чёрным ящиком.Статус
Proposal открыт в марте 2026 года. Реализация около 500 строк кода, 15 тестов, трамплины для всех 13 Linux-архитектур. Проверена в продакшне в движке базы данных MemCP.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoLive