Clause
← Уся документація

Англійський оригінал змінився після перекладу, тож частина тексту може бути застарілою. Читати англійською

Перекладено з англійського оригіналу · 1e3a39c8-dirty · 2026-10-26

Модель виконання: фрейми й продовження

Як згенеровані функції Erlang викликають, повертають керування, призупиняються й зазнають збою. Модель обрано після порівняння прототипів у розділі порівняння альтернатив; бік її ABI, що стосується згенерованого коду, описано в фреймах і передачах.

Рішення

Кожен процес Erlang виконується на власному пласкому стеку явних фреймів, а згенерований код переходить між функціями лише через гарантовані хвостові передачі (LLVM musttail). Нехвостовий виклик зберігає продовження (continuation) того, хто викликає, у його фреймі й переходить до функції, яку викликають; повернення переходить назад до цього продовження. Тому нативний стек лишається завглибшки в один виклик над планувальником, хоч якою глибокою була б рекурсія Erlang, а процес може зупинитися на будь-якій передачі й пізніше відновитися на будь-якому потоці.

Стан процесу

abi::v1::FrameState стоїть на початку кожного контексту, тож згенерований код читає й оновлює його вбудовано:

ПолеЗначення
stack, top, limitБлок стека (переміщується, коли зростає), його перше вільне слово і слова, яких заштовхування може досягти без зростання чи перевищення межі стека
frameЗсув у словах до заголовка поточного фрейму
registers, live256 регістрів аргументів/результату; перші live слів є коренями під час передачі
reductionsСкільки входів у функції лишилося в кванті часу
slow, failedБіти, що спрямовують передачі до служб (slow_collect, slow_profile); ненульове, поки канал збоїв містить збій

heap_top/heap_limit, runtime і self обслуговують вбудоване виділення, завантаження атомів і self/0 (фрейми й передачі). Призупинений процес записує вхід, з якого він продовжує, у ProcessStack::resume_.

Фрейми

Фрейм — це заголовок із 4 слів, за яким ідуть слоти функції (Y-регістри BEAM): слоти термів, які є коренями, а потім сирі слоти для вивантажених нативних значень.

Слово заголовкаЗначення
previousЗсув заголовка того, хто викликав (фрейми зв'язуються зсувами, ніколи вказівниками)
functionFrameDescriptor функції
resumeІндекс продовження, за яким перемикається тіло, коли керування повертається сюди
handlerІндекс обробника; завжди 0, бо збої повертаються через тих, хто викликав

Дескриптор <symbol>.frame називає модуль, функцію й арність і містить вказівник на код тіла та кількість слотів (abi). Слова заголовка не є термами; обхідник іде за previous і читає кількість слотів із дескрипторів. Нижній фрейм (bottom frame), що належить runtime, лежить під першим викликом кожного процесу й кожного виклику з боку хоста: повернення в нього зберігає результат у першому регістрі й завершує нативний виклик.

Операції

Кожен виклик заштовхує фрейм; листові функції його не пропускають, тож кожна функція зберігає свій запис у трасуванні стека.

Видимість коренів

На кожній передачі й кожній точці безпеки (safepoint) корені процесу такі: слоти термів усіх фреймів на його стеку, x[0..live) і корисне навантаження, список аргументів і терм стека каналу збоїв (корені).

Значення ніколи не переживають передачу в нативних регістрах чи значеннях SSA, окрім параметрів самої передачі. Кожне тіло перезавантажує адресу свого фрейму з stack + frame після входу й читає живі значення зі слотів. У межах одного продовження служба, що може заштовхнути фрейм чи перемістити купу, робить недійсними всі вказівники на слоти й на купу, що зберігаються в значеннях SSA; правило перезавантаження для збирань визначено в розділі правило перезавантаження.

Блок стека зростає подвоєнням і переміщенням, поки це дозволяє обмеження на весь runtime, ніколи не зменшується під час виконання й відокремлений від блоку купи. Обмеження на кількість фреймів немає; слова стека враховуються в обмеженні пам'яті runtime.

Цільові платформи

musttail з однією уніфікованою сигнатурою коду приймається кожною потрібною цільовою платформою на O0 і O2 (прототип нижче, clang 23.1.2):

ЦільСловоРезультат
x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu64хвостові переходи
i686-pc-windows-msvc, i686-unknown-linux-gnu32хвостові переходи
aarch64-unknown-linux-gnu, arm64-apple-macosx14.064хвостові переходи
armv7-unknown-linux-gnueabihf32хвостові переходи

Бекенд повідомляє про помилку, коли не може виконати виклик musttail, тож успішна компіляція і є гарантією. Запасний варіант для майбутньої цілі, що його відхилить: трамплін, де кожен код повертає наступний вказівник на код до циклу планувальника замість переходу; фрейми, корені й індекси продовжень не змінюються. Жодна потрібна ціль цього не потребує.

Реалізація

Порівняння альтернатив

tests/prototypes/execution_model вручну знижує ті самі функції Erlang (sum/1 — рекурсія в тілі, loop/2 — хвостова рекурсія, fail/1, що породжує виняток на глибині N, catcher/1, що його перехоплює) трьома способами й компілює їх для кожної цілі вище на O0 і O2 (python tests/prototypes/execution_model/run.py). На Windows x64 на O2 обрана модель виконала рекурсію в тілі завглибшки 1M за 17 мс із 5 словами на фрейм і 10M хвостових викликів у сталому стеку; її варіант із трампліном пройшов ті самі запуски. Відхилено:

Прийнятні витрати: один непрямий перехід на повернення плюс диспетчеризація switch; значення, живі через виклики, перезавантажуються зі слотів; зворотні трасування нативного налагоджувача показують лише поточну функцію, тоді як трасування стека Erlang беруться з ланцюга фреймів (налагодження).