Виконувані файли
Контракт для програм, зібраних через clau -o або збіркою проєкту.
Позиційні вхідні файли або рівно одна обрана ціль проєкту компонуються в
шлях -o; збірка проєкту компонує кожну обрану ціль-виконуваний файл у її
output з маніфесту (компонування, проєкти).
Вибір точки входу
Точка входу — це експортована функція арності 1, що отримує список аргументів.
| Джерело | Запис | Область дії |
|---|---|---|
| CLI | --entry MODULE[:FUNCTION] | Позиційний пакет або єдина обрана ціль проєкту |
| Маніфест | entry = "MODULE[:FUNCTION]" у таблиці [[targets]] | Ця ціль |
| Типово | Єдиний escript, інакше єдиний модуль, що експортує main/1 | Лише коли запитано виконуваний файл (-o або ціль проєкту з output) |
FUNCTIONтипово дорівнюєmain. Імена — це текст атома без лапок: 1–255 скалярних значень Unicode, коректний UTF-8, без керівних символів і без:. Інші записи — це помилки використання (CLI, код виходу 2) або помилки маніфесту (код виходу 1).entry— необов'язковий ключ схеми 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 | <file>:<line>:<col>: entry function M:F/1 is not defined на оголошенні модуля |
| Лише інші арності | ... is not defined; found F/N, but the entry receives one argument (the argument list) на цьому визначенні |
F/1 не експортовано | <file>:<line>:<col>: entry function M:F/1 is not exported на визначенні |
Нічого не обрано, немає експорту main/1 | no entry point: no module exports main/1; choose the entry with --entry MODULE[:FUNCTION] (an exported FUNCTION/1; FUNCTION defaults to main) |
| Нічого не обрано, кілька | ambiguous entry point: main/1 is exported by a, b; choose the entry with ... (та сама підказка) |
Для цілей проєкту підказка також називає ключ маніфесту: ... --entry MODULE[:FUNCTION] or with entry = "MODULE[:FUNCTION]" in this target's [[targets]] table of the project manifest ..., наприклад:
[[targets]]
name = "app"
sources = ["src/*.erl"]
entry = "app:start" # calls app:start/1; 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 | Обмеження на стек фреймів кожного процесу; типово без обмеження |
--max-memory BYTES | Обмеження на пам'ять усіх процесів разом (купи, binary поза купою, стеки); типово без обмеження |
--schedulers N | Робочі потоки планувальника, що виконують процеси, від 1 до 1 024; типово один на логічний процесор, як +S в OTP (робочі потоки) |
--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>, за яким пізніше йдуть фрейми стека (крок 15). Його точний текст не є стабільним інтерфейсом; тести зіставляють його за шаблоном.
Об'єкт запуску
Компіляція з явною точкою входу (--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), тож код, якого не
досягає жоден шлях від точки входу, видаляється.
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