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

Перекладено з англійського оригіналу · 06042fa · 2026-10-09 · Читати англійською

ABI згенерованого коду (ревізія 4)

Приватний контракт між виходом компілятора та runtime. Це внутрішній для проєкту C++23, несумісний із BEAM і не загальний FFI. Об'єктні файли та runtime мають походити з однієї збірки; старіші ревізії дескрипторів відхиляються до використання.

Заголовки: v1.hpp (типи термів/контексту/ функцій), term.hpp (кодек безпосередніх цілих чисел), status.hpp, builtins.hpp, startup.hpp (запуск програми).

Терми

Терм — це одне беззнакове слово завширшки з вказівник цільової платформи (32 або 64 біти). Розміщення визначаються з налаштованої цілі LLVM, тож код для іншої цілі використовує її ширини.

Функції та символи

Сигнатура точки входу (нативна угода про виклики C, C-компонування не потрібне):

TermWord function(ProcessContext *context, const TermWord *arguments);

Аргументи — позичений, вирівняний за словом масив у порядку вихідного коду (null при арності 0). Контекст живий і передається через виклики без змін. Повернене слово можна використовувати лише після перевірки каналу збоїв.

Символи: clausev1_<hex module>_<hex function>_<arity>, шістнадцятковий код байтів UTF-8 у нижньому регістрі, канонічна десяткова арність; оборотні та незалежні від хоста. Експортовані точки входу зовнішні, інші — внутрішні.

Реєстрація модулів

Кожен модуль генерує clausev1_<hex module>__0.descriptor і .register. Дескриптор містить версію ABI, ширину терма, таблицю експорту (ім'я, арність, хостова точка входу та FrameDescriptor, у який входять динамічні виклики), написання атомів (пари вказівник/розмір UTF-8), дескриптори native records (слоти атомів модуля, імені та полів, прапорець експорту; native records) у зовнішній таблиці <prefix>.records, на яку посилаються інші модулі пакета, і дескриптори funs у приватній таблиці <prefix>.funs (funs). Реєстрація викликає CLAUSE_register_module_v4(Runtime*), яка перевіряє версію/ширину та всі експорти, інтернує атоми, будує заморожений реєстр і публікує його разом з образом коду в одній транзакції. Дублікати модулів ніколи не замінюють код; будь-який збій нічого не публікує (уже інтерновані атоми залишаються в обмеженій таблиці).

Канал збоїв (ревізія 2)

Помилки не кодуються в бітах терма. Після кожного нехвостового згенерованого виклику викликач перевіряє CLAUSE_call_failed_v2(context) перед використанням результату чи обчисленням наступного аргументу. У разі збою викликана функція повертає недійсне нульове слово.

РезультатПередавання
Незбіг зі зразком, відхилення guardПродовження до наступного кандидата; канал не змінюється
Вичерпано клаузиerror:function_clause; error:{case_clause, Value} із власним корисним навантаженням для case; error:if_clause; error:{try_clause, Value} для клауз of у try; error:{else_clause, Value} для клауз else у maybe
Збій зіставлення в тіліerror:{badmatch, Value} із власним корисним навантаженням
Comprehensionserror:{bad_generator, Tail}, error:{bad_filter, Value}, error:{bad_generators, Inputs} (ErrorReason 16-18); відхилення строгого генератора — {badmatch, Element}
Доступ до record, хибні аргументи, арифметика, mapsbadrecord, badarg, badarith, badmap/badkey
Відсутнє поле native recordErrorReason::badfield (20), корисне навантаження {{Module, Name}, Field}
Виклик значення (F(Args))ErrorReason::badfun (22, корисне навантаження — саме значення), badarity (23, корисне навантаження {Fun, Args}), undef (24), записуються CLAUSE_apply_v1 (funs)
Динамічні виклики (M:F(Args), apply/2,3, fun M:F/A зі змінними)badarg для модуля чи функції, що не є атомом, неправильного списку аргументів або недійсної арності; undef, коли жоден модуль програми не експортує функцію; badfun/badarity, як вище (funs)
Зовнішнє створення native record без значенняErrorReason::novalue (21), корисне навантаження {{Module, Name}, Field}
Цілочисловий результат понад обмеження розміруРезультат сервісу ValueOutcome::system_limit (3): guard відхиляє, тіло генерує error:system_limit (ErrorReason 19)
Недійсний лівий операнд лінивого оператора{badarg, Value}
Інфраструктура (OOM, обмеження, володіння, внутрішні)CallError::runtime_failure з точним Status
erlang:error/1,2,3, exit/1, throw/1, erlang:raise/3raised_error/raised_exit/raised_throw: клас визначається ідентифікатором, власне корисне навантаження — уся причина
erlang:halt/0,1CallError::halted з halt_status (і гаслом)

Причини — це типізовані ідентифікатори, записані CLAUSE_raise_v2; три ідентифікатори raised_* вибирають клас exit або throw (інакше error) і несуть будь-який терм як причину. error/2,3 генерують виняток через CLAUSE_error_v1(context, reason, args), яка також зберігає список args для верхнього фрейму стека. Перемагає перший збій; вкладені виклики спільно використовують канал. GeneratedInvocation — це хостова область: вона перевіряє незавершені збої перед входом і після повернення, копіює результат або помилку та очищає канал лише на найзовнішньому виході (також при винятках C++). Жоден виняток не перетинає згенеровані точки входу. Ті, хто викликає сирі точки входу, мають відкрити GeneratedInvocation; звичайні хости використовують ResolvedFunction::call.

catch Expr перенаправляє кожну перевірку збою та генерування винятку всередині Expr до блоку-обробника, який викликає CLAUSE_catch_v1(context, slot). Для незавершеного винятку Erlang вона записує значення catch у кореневий слот і очищає канал: кинутий терм, {'EXIT', Reason} для exit або {'EXIT', {Reason, []}} для error (типізовані причини стають своїми термами OTP, як-от {badmatch, V}; стек описано нижче). Зупинки (halt) та інфраструктурні збої залишаються незавершеними, а власна перевірка обробника переходить до обробника, що охоплює, або до виходу з функції. Зв'язування, зроблені всередині Expr, після цього небезпечні, тож точка злиття об'єднує лише значення.

try Body of ... catch ... end так само захищає лише Body. Його обробник викликає CLAUSE_exception_v2(context, class_slot, reason_slot, stack_slot), яка записує атом класу (error, exit або throw), причину та терм трасування стека в кореневі слоти й очищає канал (зупинки та інфраструктурні збої залишаються незавершеними, як і для catch). Потім клаузи catch зіставляють Class:Reason звичайними зразками та guards; пропущений клас відповідає throw, а іменована змінна стека зв'язується з термом стека. Коли жодна клауза не підходить, CLAUSE_reraise_v2(context, class, reason, stack) знову записує виняток із причиною raised_* і тим самим стеком, що звітується та перехоплюється точно так само, як оригінал. Клаузи of вибирають за значенням тіла та генерують {try_clause, Value} (ErrorReason::try_clause = 14); винятки всередині клауз of та обробників переходять до обробника, що охоплює.

maybe не потребує сервісу: кожен ?= — це звичайне зіставлення, чия гілка незбігу виходить із тіла до виходу maybe з незіставленим (уже вкоріненим) значенням. Без else це значення є результатом; інакше клаузи else вибирають за ним, як клаузи case, і генерують {else_clause, Value} (ErrorReason::else_clause = 15), коли жодна не підходить.

Comprehensions використовують наявні сервіси з кількома операціями: накопичені елементи обертаються конструкцією reverse (ContainerConstruction::reverse = 2, значення {List, Tail}); binary comprehension об'єднує свої частини за допомогою BitOperation::concat ({List}), а map comprehension будує свій map за допомогою MapOperation::from_list ({Pairs}, пізніші ключі перемагають). Генератори map читають MapOperation::key_at/value_at ({Map, Position} у канонічному порядку ключів) і показують залишок вхідних даних zip за допомогою MapOperation::iterator — ланцюжка OTP {K, V, Next}, що закінчується none. Генератори bitstring використовують звичайне видобування за зразком плюс фінальний сегмент binary/all для решти.

try ... after A end додає другий захист навколо тіла та всіх клауз of і catch. На звичайному шляху A виконується після того, як вибране значення вкорінено, а значення A відкидається. Обробник after отримує виняток через CLAUSE_exception_v2, виконує другу копію A і повторно генерує виняток через CLAUSE_reraise_v2; виняток чи збій усередині A натомість виходить через обробник, що охоплює, замінюючи оригінал. Зупинки та інфраструктурні збої пропускають A. Кореневі слоти належать фрейму функції, тож кожен шлях звільняє їх на виході з функції.

Трасування стека

Кожен кореневий фрейм називає свою згенеровану функцію приватним abi::v1::FrameDescriptor (дескриптор модуля, слоти атомів імені модуля та функції, арність). Коли записується виняток Erlang, канал копіює 8 найвнутрішніших іменованих фреймів (типове значення BEAM backtrace_depth); терм [{Module, Function, Arity, []}, ...] будується лише тоді, коли його запитує обробник, catch або звіт. Верхній фрейм показує список аргументів error/2,3 замість арності, коли цей аргумент є списком.

erlang:raise(Class, Reason, Stack) (CLAUSE_reraise_v2) приймає стеки, які приймає BEAM: правильний список із {M, F, A} (доповнених розташуванням []) або {M, F, A, Location} з атомами M, F і списком Location, обрізаний до 8 елементів; після цього стек зберігається як заданий, і фрейми більше не захоплюються. Недійсний клас чи стек нічого не записує, і виклик обчислюється в badarg, як в OTP. erlang:get_stacktrace/0 відхиляється з текстом lint OTP 29 про видалення ("removed").

Відмінності від OTP, усі видимі лише в терміні стека:

Фрейми та передавання керування

Згенеровані функції виконуються на явних фреймах (модель виконання, step 19; сервіси в frames.hpp). Кожна функція має дескриптор <symbol>.frame (FrameDescriptor: дескриптор модуля, слоти атомів модуля та функції, арність, код тіла, кількість слотів, кількість слотів термів; зовнішній для експортованих функцій) і внутрішнє <symbol>.body типу void(void *context). Експортований <symbol> зберігає сигнатуру TermWord(Context *, const TermWord *) як хостова точка входу, що викликає CLAUSE_invoke_v1(context, frame, arguments).

Сервіси runtime

Згенерований код викликає перевірені сервіси C++: CLAUSE_exact_v1 (точна рівність), CLAUSE_immediate_v1 (предикати/запити безпосередніх значень), CLAUSE_construct_v1, CLAUSE_inspect_v1, CLAUSE_integer_v1, CLAUSE_float_v1, CLAUSE_map_v1, CLAUSE_bits_v1, CLAUSE_record_v1 (створення/читання/оновлення/зіставлення/перевірка native record під RecordCheck; результати bad_record, bad_field, no_match), CLAUSE_make_fun_v1 (побудова fun з FunDescriptor). Кожен повертає успіх, семантичну помилку (badarg/badarith/...) або інфраструктурний збій і записує вихідні дані лише в разі успіху. CLAUSE_display_v1 (output.hpp) друкує один рядок erlang:display/1 і дає true; семантичної помилки в нього немає. CLAUSE_halt_v1 ніколи не завершується успішно: вона записує запит на зупинку (CallError::halted з кодом завершення) або badarg, тож викликач розгортає стек. Імена для компонувальника відповідають декоруванню C++ Itanium або Microsoft для цільової платформи.

CLAUSE_builtin_frame_v1(context, builtin) (builtins.hpp) повертає FrameDescriptor робочої вбудованої функції з індексом builtin у abi::v1::bridge_builtins (лише доповнюваний); згенерований код входить у нього як у функцію з аргументами в регістрах, а помилки та збої йдуть у перевірений канал (вбудовані функції). FrameDescriptor з null-тілом є вбудованою функцією: вхід у нього виконує вбудовану функцію на регістрах і повертається до викликача або призупиняє процес на продовженні вбудованої функції, коли вона виконала trap (порції).

abi::v1::dispatch_builtin викликає зареєстровані хостом вбудовані функції за байтами модуля/функції, масивом аргументів і арністю, повертаючи Status; вихідні дані записуються лише в разі успіху.

StatusЗначенняОпис
ok0Успіх
not_implemented1Відома відкладена можливість, повідомляється один раз
invalid_argument2Недійсні вхідні дані
diagnostic_failure3Не вдалося доставити звіт
out_of_memory4Виділення пам'яті не вдалося, стан відкочено
busy5Живі контексти або диспетчеризація, що виконується
wrong_owner6Об'єкт належить іншому runtime/контексту
resource_limit7Обмеження або бюджет вичерпано
stopped8Власника вимкнено
abi_mismatch9Відрізняється версія або ширина терма
internal_error10Неочікуваний збій локалізовано
unknown_builtin11Сигнатуру не зареєстровано і немає в каталозі
erlang_error12Записано структуровану помилку Erlang
output_failure13Стандартне виведення відхилило запис

Ревізії

РевізіяЗміна
1Початкові дескриптори та точки входу
2Перевірений канал збоїв
3Написання атомів і слоти в дескрипторах
4Обов'язкові згенеровані кореневі області
5Явні фрейми процесів і передавання керування
6Дескриптори native records у дескрипторах модулів, CLAUSE_record_v1
7Дескриптори funs у дескрипторах модулів, CLAUSE_make_fun_v1, CLAUSE_apply_v1
8Дескриптори експорту називають свій FrameDescriptor; сервіси динамічних викликів CLAUSE_call_v1, CLAUSE_apply_list_v1, CLAUSE_call_list_v1, CLAUSE_make_external_fun_v1