Виконувані файли
Контракт для програм, зібраних через clau -o або збиранням проєкту.
Позиційні вхідні файли або рівно одна обрана ціль проєкту компонуються в шлях
-o; збирання проєкту компонує кожну обрану ціль-виконуваний файл у її
output з маніфесту (компонування,
проєкти).
Вибір точки входу
Точка входу — це експортована функція арності 1, що отримує список аргументів.
Обрана FUNCTION без FUNCTION/1 натомість може бути експортованою
FUNCTION/0: вона виконується без аргументів програми (опції runtime однаково
вилучаються). Суфікс /0 або /1 вибирає лише цю арність, тож
--entry benchmarks:run/0 запускає run/0, навіть коли run/1 теж
експортовано. Типово (без вибору) це завжди main/1.
| Джерело | Запис | Область дії |
|---|---|---|
| CLI | --entry MODULE[:FUNCTION[/ARITY]] | Позиційний пакет або єдина обрана ціль проєкту |
| Маніфест | entry = "MODULE[:FUNCTION[/ARITY]]" у таблиці [[targets]] | Ця ціль |
| Типово | Єдиний escript, інакше єдиний модуль, що експортує main/1 | Лише коли запитано виконуваний файл (-o або ціль проєкту з output) |
FUNCTIONтипово дорівнюєmain. Імена — це текст атома без лапок: 1–255 скалярних значень Unicode, коректний UTF-8, без керівних символів і без:. Кінцевий/DIGITSпісляFUNCTION— це арність, і вона має бути0або1; інші скісні риски лишаються частиною імені. Інші записи — це помилки використання (CLI, код виходу 2) або помилки маніфесту (код виходу 1).- Маніфест декодується повністю, тож некоректний
entryдає збій навіть у необраних цілях (маніфест). --entryу CLI перевизначає ключ маніфесту й вимагає рівно однієї обраної цілі. Він конфліктує з діями перевірки/друку та--new-project; ці дії ігноруютьentryманіфесту (як ігноруютьoutput).- Явна точка входу перевіряється в кожному режимі компіляції (типовому,
--emit, інспекції IR/типів) разом зі звичайною семантичною діагностикою.
| Збій | Діагностика (код виходу 1) |
|---|---|
| Модуля немає в пакеті | <origin>: entry module M is not among the compiled modules (походження: --entry або маніфест file:line:col [target t] (entry)) |
Немає ні F/1, ні F/0 | <file>:<line>:<col>: entry function M:F/1 is not defined на оголошенні модуля |
Явної F/A немає | ... entry function M:F/A is not defined; found F/N на визначенні з тим самим іменем, інакше на оголошенні модуля |
| Лише інші арності | ... is not defined; found F/N, but the entry takes the argument list (FUNCTION/1) or nothing (FUNCTION/0) на цьому визначенні |
F/1 (інакше F/0) не експортовано | <file>:<line>:<col>: entry function M:F/A is not exported на визначенні |
Нічого не обрано, немає експорту main/1 | no entry point: no module exports main/1; choose the entry with --entry MODULE[:FUNCTION[/ARITY]] (an exported FUNCTION/1, or FUNCTION/0 run without the program arguments; FUNCTION defaults to main, ARITY to 1, else 0) |
| Нічого не обрано, кілька | ambiguous entry point: main/1 is exported by a, b; choose the entry with ... (та сама підказка) |
Для цілей проєкту підказка також називає ключ маніфесту: ... --entry MODULE[:FUNCTION[/ARITY]] or with entry = "MODULE[:FUNCTION[/ARITY]]" in this target's [[targets]] table of the project manifest ..., наприклад:
[[targets]]
name = "app"
sources = ["src/*.erl"]
entry = "app:start" # calls app:start/1 (or app:start/0); "app:start/0" calls app:start/0; plain "app" calls app:main/1
Аргументи
Entry(Argv) отримує правильний список рядків (списків кодових точок Unicode)
без імені програми й початкових опцій runtime, в іншому
незмінних і в тому самому порядку, як escript.
- POSIX: байти кожного аргументу декодуються як UTF-8; байт, що не починає коректної послідовності, стає кодовою точкою цього байта (запасний варіант Latin-1).
- Windows: широкий (UTF-16) вектор аргументів CRT, розбитий за тими самими
правилами, що й
argv; непарний сурогат стає U+FFFD. - Жодного іншого розбору опцій, розгортання шаблонів (globbing) чи змінних середовища runtime не виконує.
Опції runtime
Runtime читає свої опції зі змінної середовища CLAUSE_FLAGS (слова розділено
пробілами й табуляціями, без лапок), а потім із початкових аргументів
командного рядка, тож командний рядок має перевагу. Обидва джерела
розбираються однаково; значення йде в наступному аргументі або після =.
| Опція | Ефект |
|---|---|
--max-atoms N | Розмір таблиці атомів, від 1 до 2^26 (67 108 864); типово 2^20 (1 048 576), як +t в OTP |
--max-heap BYTES | Обмеження на блок купи кожного процесу, фрагменти й binary поза купою, щонайменше мінімальна купа (233 слова); типово без обмеження |
--max-stack BYTES | Обмеження на стек фреймів кожного процесу; типово без обмеження |
--min-heap-size WORDS | Типовий min_heap_size кожного процесу, щонайменше 1, округлюється вгору до розміру купи, як +hms в OTP; не більше --max-heap; типово 233 (розміри) |
--max-memory BYTES | Обмеження на пам'ять усіх процесів разом (купи, binary поза купою, стеки); типово без обмеження |
--schedulers N | Робочі потоки планувальника, що виконують процеси, від 1 до 1 024; типово один на CPU з --scheduler-cpus, інакше один на логічний процесор, на якому дозволено виконувати програму (за її маскою спорідненості), як активні планувальники OTP (робочі потоки) |
--scheduler-cpus SET | Прив’язати робочий потік N до N-го CPU з SET, циклічно, як +sbt/+sct в OTP: performance (найшвидший клас ядер: P-ядра гібридного CPU, інакше всі CPU), all або список на кшталт 0-15 чи 2,4,6; кожен указаний CPU має входити до маски спорідненості. Типово без прив’язки (прив’язка до CPU) |
--profile FILE | Записати у FILE після завершення програми звіт про входи й власний час для кожної функції та кожного процесу (профілювання); типово вимкнено |
--args-file FILE | Файл опцій на кшталт vm.args в OTP: зарезервовано, повідомляє runtime option --args-file is not implemented |
-- | Завершує опції runtime; усі наступні аргументи йдуть до Entry |
У командному рядку розбір зупиняється на першому аргументі, що не є опцією
runtime, тож prog data --max-atoms 9 передає програмі всі три аргументи. У
CLAUSE_FLAGS кожне слово має бути опцією runtime. Недійсне значення, слово в
змінній, що не є опцією, або --args-file зупиняють програму до реєстрації
будь-якого модуля: clau: runtime failure: <reason>, код виходу 70. Кількість
процесів не обмежена, а пам'ять типово не має обмеження. Значення в байтах
десяткові без суфіксів і округлюються донизу до цілих слів; програма, що
досягає обмеження, завершується збоєм resource_limit, код виходу 70
(обмеження пам'яті runtime).
Статус виходу
| Результат | Статус |
|---|---|
| Точка входу повертає значення (будь-яке) | 0 |
erlang:halt() | 0 |
erlang:halt(N), невід'ємне ціле число | N (хости POSIX зберігають молодші 8 бітів) |
erlang:halt(Slogan) з рядком | Гасло в stderr, потім 1 (без аварійного дампу) |
erlang:halt(abort) | Нативний abort (без скидання буферів) |
Будь-який виняток, що виходить за межі точки входу, включно з throw і exit(normal) | Звіт у stderr, 1 |
| Процес точки входу завершено сигналом виходу (процеси) | Повідомляється як неперехоплений exit, 1; причина normal: 0 |
| Збій запуску runtime чи інфраструктури (невідповідність ABI, реєстрація, пам'ять до входу) | Повідомлення в stderr, 70 |
| Пам'ять хоста вичерпано (відмовлено в зростанні купи, binary поза купою чи стека; типово без обмеження пам'яті, вичерпання пам'яті) | clau: runtime failure: entry call failed: out_of_memory, 70 |
Недійсні аргументи halt/1 породжують badarg у того, хто викликає. Коли
точка входу завершується, програма виходить: інші процеси зупиняються без
подальшого виконання, як із halt/1 в OTP після повернення з escript. Halt
у будь-якому процесі завершує програму з його статусом, а збій runtime у
будь-якому процесі завершує її з 70; виняток у запущеному процесі завершує
лише цей процес (процеси).
halt/0,1, error/1,2,3, exit/1 і throw/1 можна викликати з префіксом
erlang: або без нього; локальне визначення чи
-compile({no_auto_import, ...}) залишає некваліфіковане ім'я локальним, як в
OTP. halt/2 недоступна. halt(N) зберігає молодші 31 біт будь-якого
невід'ємного цілого числа, як OTP. Гасло (slogan) — це правильний список
щонайбільше з 1 023 кодових точок Unicode. Halt розмотує точку входу через
перевірений канал помилок, як помилку, тож зупиняє програму лише після
згенерованого прибирання.
Потоки виводу
- stdout: вивід
standard_io(io:format/1,2,io:put_chars/1,erlang:display/1). Буферизований; скидається на кожному шляху виходу, окрімabort. - stderr: звіт про неперехоплений виняток, вивід
standard_error, збої runtime і звіти про помилки інших процесів, що аварійно завершилися (процеси). - Звіт — це один рядок
uncaught exception <class>: <reason in ~w form>, без фреймів стека. Його точний текст не є стабільним інтерфейсом; тести зіставляють його за шаблоном.
Об'єкт запуску
Компіляція з явною точкою входу (--entry або entry маніфесту) додає модуль
запуску після модулів пакета. З --emit він публікується як
clausev1_start.{obj,o,ll,bc} поряд з артефактами модулів (ім'я не може
збігтися з артефактом модуля). Він містить сталий
abi::v1::StartupDescriptor
(startup.hpp): ревізію ABI, ширину
терма, кожен дескриптор модуля в порядку джерел, записи модуля/функції точки
входу та прапорець escript. Його int main(int, char **) викликає
CLAUSE_main_v1 з runtime, яка:
- Перевіряє дескриптор запуску й кожен дескриптор модуля на ревізію ABI та ширину до реєстрації будь-чого; невідповідність дає код виходу 70.
- Запускає runtime і реєструє всі модулі; будь-який збій зупиняє все до точки входу й відкидає runtime (код виходу 70), тож жоден код Erlang не виконується з неповним пакетом.
- Створює процес точки входу, будує argv і ставить виклик
M:F/1у чергу як головний процес, потім виконує його й кожен процес, який він запускає, на робочих потоках планувальника (процеси), доки той не завершиться. - Відображає результат на статус виходу вище, друкуючи звіти після скидання
stdout, потім звільняє кожен процес і завершує роботу runtime на кожному
шляху (окрім
halt(abort)).
clau -o компонує ці об'єкти сам (компонування). Ручне
компонування
(рецепт нативної обв'язки без
вихідного файлу обв'язки):
& $tool --emit obj --entry app --artifact-dir build/app app.erl helper.erl
clang-cl /MT build/app/*.obj build/debug/lib/clause_runtime.lib /Fe:app.exe
Будь-яке Clang-сумісне компонування об'єктів із Clause::generated_program
працює так само (див. tests/compiler/linking/startup.cmake).
Компонування
clau [-O0|-O2|-Os] -o PATH a.erl b.erl ... (або
--project FILE [--target T] -o PATH для однієї обраної цілі) компілює пакет
у пам'яті, додає об'єкт запуску для точки входу і компонує
виконуваний файл:
clau -O2 -o build/demo examples/compile/answer.erl examples/compile/client.erl
./build/demo # build/demo.exe on Windows
-Os додатково розміщує кожну згенеровану функцію й функцію runtime та кожен
об'єкт даних в окремій секції й компонує з --gc-sections (ELF),
-dead_strip (Mach-O) або /OPT:REF /OPT:ICF (MSVC), тож код, якого не
досягає жоден шлях від точки входу, видаляється.
--lto компілює модулі й модуль запуску в біткод LLVM замість об'єктних
файлів і компонує з -flto -fuse-ld=lld, тож LLD оптимізує згенерований код
програми як одне ціле разом з архівом нативного runtime. Дескриптори модулів і
запуску, експортовані точки входу й main лишаються вживаними й зберігаються;
програми поводяться так само, як без цієї опції. Біткод цього компілятора
читає лише LLD, тож --lto приймається для цілей Windows MSVC і ELF; інші
завершуються збоєм до компонування з
--lto is not supported for target <triple>: it needs LLD. --lto потребує
виводу виконуваного файлу (-o або збирання проєкту з компонуванням).
Встановлені Clang/LLD мають бути того самого релізу LLVM, що й clau (23.1).
Живучість експортів
Виконуваний файл пропускає експорти, яких не може досягти, тож dead-stripping
у -Os і --lto видаляють бібліотечний код, який програма ніколи не
викликає. Одразу після розв'язання викликів, до виведення типів, аналіз усього
пакета починається з функції входу й позначає живим усе, що жива функція
викликає напряму (також через -import і автоімпорти OTP), кожен fun F/A і
літеральний fun M:F/A, а також літеральні цілі apply/3, spawn/3,
spawn_link/3, spawn_monitor/3, spawn_opt/4,5, spawn_request/3,5,
hibernate/3 і make_fun/3 (кожну арність M:F, коли список аргументів не
літеральний), і file:write/2, щойно живими стають io:format/3,
io:fwrite/3, io:put_chars/2 або io:nl/1 (їхні вбудовані функції пишуть
через нього на пристрій-процес). Аналіз лишається консервативним:
- модуль, атом імені якого використовується як значення будь-де в живому коді
(передається, зберігається, надсилається,
?MODULE, тег record,M:F(...)з функцією, відомою лише під час виконання,fun M:F/Aз частиною, відомою лише під час виконання), зберігає всі експорти, що також зберігає callback-функції модулів поведінок, запущених за іменем; - щойно живий код містить і виклик з модулем, відомим лише під час виконання
(
M:F(...),apply/3або spawn зі змінним модулем,fun M:F/Aзі змінним модулем,binary_to_term/1,2), і вбудовану функцію, що створює атоми (list_to_atom/1,list_to_existing_atom/1,binary_to_atom/1,2,binary_to_existing_atom/1,2,binary_to_term/1,2,fun_info/1,2,fun_info_mfa/1,process_info/1,2,loaded/0,pre_loaded/0), зберігаються всі експорти всіх модулів; - модуль, скомпільований з
{clause_liveness, strict}, гарантує, що його атоми ніколи не іменують викликаний код: його теги record й літерали атомів, крім власного імені (але неM:F(...)чиapply/3з літеральним модулем і функцією, відомою лише під час виконання), вбудовані функції, що створюють атоми, іbinary_to_term/1,2не враховуються в жодному з правил (строга живучість).
Функція, якої не досягає нічого живого, взагалі не отримує коду. Пропущений
експорт зберігає своє ім'я в таблиці експортів модуля без коду, тож
function_exported/3 і module_info(exports) відповідають так, ніби її
експортовано, тоді як динамічний виклик, що до неї доходить, породжує undef
(відмінності). Таке
можливе, лише коли ім'я модуля надходить ззовні аналізу: з трасування стека,
терма винятку чи даних ззовні програми. --verbose трасує результат для
кожного модуля (phase=liveness detail="dropped-exports=3" або
detail="kept-all-exports reason='m' is used as a value in main:main/1").
Функція, якої не досягає нічого живого, також ніколи не проходить виведення
типів і перевірку щодо свого -spec: виведення охоплює лише живий код, що
швидше для програм, які використовують малу частину великих бібліотечних
модулів, і може лише звузити факти живих функцій, бо недосяжні місця виклику
більше не об'єднують свої аргументи. Тому специфікація, якій суперечить лише
недосяжний код, повідомляється для артефактів --emit, але не для
виконуваного файлу.
--keep-exports (або опція проєкту keep_exports = true) зберігає кожен
експорт доступним для виклику. Аналіз застосовується до виконуваних файлів
(-o або збирання проєкту з компонуванням) і до інспекцій IR та асемблера із
заданою точкою входу (--entry або entry з маніфесту цілі), які тоді
показують код виконуваного файлу; артефакти --emit, призначені для інших
програм, зберігають усі експорти.
-g зберігає таблиці рядків Erlang для налагоджувачів: у виконуваному файлі
(ELF) або в <name>.pdb, опублікованому поруч із ним (цілі MSVC)
(налагодження).
PATHвідносний щодо місця виклику. Для цілей Windows до імені файлу без розширення додається.exe. Його каталог має існувати.- Компонувальник:
--linker PATH(шлях чи ім'я програми), інакшеclang++абоclangзPATH, потім (Windows)%ProgramFiles%/LLVM/bin. Він запускається як<clang> --driver-mode=g++ --target=<triple> -o <staged> <objects> <runtime>, тож Clang обирає компонувальник платформи й бібліотеки runtime C/C++ (на Windows він сам знаходить MSVC і SDK; оболонка розробника не потрібна). - Runtime:
--runtime-library PATH, інакше архівclause_runtimeзі збирання, що створилоclau(шлях записано відносно виконуваного файлу, наприкладbin/../lib/clause_runtime.lib). Кожен нативний об'єкт в архіві має відповідати архітектурі й формату об'єктів цілі; тому--target-tripleдля іншої цілі потребує runtime, зібраного для неї. - Об'єкти й виконуваний файл готуються в приватному каталозі
.clause-link-*поряд із виводом, який потім видаляється; він лишається там, а не в каталозі збирання, бо остаточне перейменування не повинно перетинати томи. Вивід замінюється лише після успішного компонування, тож за будь-якого збою наявний файл лишається без змін. Вивід не може бути каталогом або псевдонімом вхідного файлу. - Попередження компонувальника передаються в stderr;
--linkerі--runtime-libraryвимагають--outputабо збирання проєкту з компонуванням. - Збирання проєкту без
-oкомпонують кожну обрану ціль, що маєoutputчиentry, у її вивід із маніфесту, створюючи відсутні каталоги, і замінюють виводи лише після того, як усі обрані цілі скомпоновано (проєкти).
| Збій (код виходу 1) | Діагностика |
|---|---|
| Немає Clang | cannot find clang++ or clang on PATH; install LLVM/Clang or pass --linker / linker not found: X |
| Немає runtime | runtime library not found: P; build the clause_runtime target or pass --runtime-library |
| Не архів | runtime library is not a static library: P: ... |
| Неправильна ціль | runtime library P contains x86_64 coff objects, but the executable targets T; ... |
| Помилка компонування | linking O failed: <clang> exited with status N:, а за ним вивід компонувальника (перші 64 КіБ) |
| Неправильне призначення | output directory does not exist: D, artifact destination is not a regular file: O, artifact destination aliases an input: O |
Escript
Вихідний файл, перший рядок якого починається з #!, компілюється як escript
у будь-якому режимі й з будь-яким ім'ям файлу (позиційні вхідні файли чи файли
.erl проєкту). Правила відповідають escript з OTP 29 для скриптів у
вихідних кодах:
- Рядок
#!ігнорується. Необов'язковий коментар у рядку 2 і рядок емулятора%%!(рядок 2 або рядок 3 після коментаря) є коментарями; аргументи%%!не можуть застосовуватися до скомпільованого коду й дають попередження. - Якщо перша форма не
-module(...), модулем стає<file name with '.' replaced by '_'>__escript(включно з?MODULE). OTP додає суфікс із часовою міткою/унікальним значенням; Clause зберігає ім'я детермінованим. Синтезоване оголошення займає рядок 1, тож номери подальших рядків не змінюються. main/1обов'язкова (escript does not define main/1) і неявно експортується; інші функції підлягають звичайним правилам експорту.-mode(compile | interpret | debug | native)приймається й ігнорується; інші значення є помилками. Поза escript-modeлишається непідтримуваним.- Точка входу: без
--entryєдиний escript у пакеті є точкою входу (він має перевагу над модулями, що експортуютьmain/1); кілька escript дають неоднозначність. - Статус виходу як в
escriptOTP: виняток, що виходить за межі точки входу escript, дає код виходу 127 зescript: exception <class>: <reason>у stderr; інші рядки таблиці статусів виходу застосовуються без змін. - Файли без
#!— звичайні модулі. (escript file.erlв OTP пропустив би їхній перший рядок; Clause цього не робить.) Попередньо скомпільовані escript у вигляді beam і архівів не підтримуються.
Порівняння з OTP
Еталони для фікстур програм виконують
точку входу під OTP за тими самими правилами
(оракул); той самий оракул
генерує
еталонні випадки виконуваних файлів,
які наскрізний запускач компонує й виконує за кожної політики. Відмінності від
escript для звичайних модулів: неперехоплені винятки дають код виходу 1
замість 127, і main/1 має бути експортована. Вихідні коди escript зберігають
правила OTP (див. вище).
Clause