Компіляція
clau компілює модулі Erlang/OTP 29 через LLVM у перевірене IR, біткод або
нативні об'єктні файли. -o/--output компонує позиційні вхідні файли (або одну
вибрану ціль проєкту), їхній стартовий об'єкт точки входу і
runtime у виконуваний файл (компонування); збирання
проєктів компонують виконувані цілі в їхні виводи з маніфесту
(проєкти). Згенеровані об'єктні файли також можна
запускати через обв'язку C++, скомпоновану з runtime (див. приклад нижче).
Прийнята підмножина вихідного коду
Іменовані модулі звичайної мови OTP 29: впорядковані клаузи функцій зі
зразками і guard; зіставлення, конструктори,
оператори та вбудовані функції (вбудовані функції); case,
if, receive, catch, try ... of ... catch Class:Reason:Stack ... after
з трасуваннями стека, maybe ... else ... end;
comprehension для списків, binary і map (зразки);
кортежні та native records; fun, локальні,
віддалені й динамічні виклики (M:F(...), apply/2,3);
процеси і порти. Виклики виконуються на явних
фреймах процесу з належними хвостовими викликами
(модель виконання); рекурсію в тілі
обмежує стек процесу (за замовчуванням необмежений), а не нативний стек.
Модулі, які пакет називає, але не оголошує, беруться з
бібліотеки. Кваліфікатори зіставлення в comprehension
експериментальної можливості compr_assign відхиляються як
[heap expressions] notimpl.
Прийняті атрибути: module, export, file, кортежний і нативний record,
export_record, import_record, форми type/spec, doc/moduledoc,
behaviour/behavior (поведінки), будь-який
інформаційний атрибут (vsn, author, copyright, deprecated,
dialyzer, визначений користувачем -name(Term); зберігаються для
module_info(attributes)),
-import(Module, [F/A, ...]) (імпорти) і -compile
з параметрами, що не змінюють нічого з того, що генерує Clause:
{no_auto_import, ...}, параметри попереджень (nowarn_*, warn_*,
{nowarn_unused_function, [...]}) і підказки щодо оптимізації, налагодження
та звітування (inline, {inline, [F/A]}, {inline_size, N},
{inline_effort, N}, inline_list_funcs, debug_info, deterministic,
report*, verbose, ...) і власний параметр Clause
{clause_liveness, strict} (сувора досяжність).
{parse_transform, Module} запускає
перетворення на Erlang/OTP хоста перед аналізом
(parse transforms). on_load, nifs, export_all, інші
параметри compile і параметризовані модулі відхиляються; діагностика
називає їх, наприклад
[behavior-changing attributes] notimpl [module="m" operation="-compile option export_all"].
Кожен модуль отримує module_info/0,1. Вихідні файли, що починаються з #!,
дотримуються правил escript (неявний модуль і
експорт main/1, -mode приймається). Форми type/spec аналізуються, але
ніколи не змінюють згенерований код. Режими лише для синтаксису
(--parse-check, --print-ast, --print-source, ...) приймають повну
граматику.
Сувора досяжність
-compile({clause_liveness, strict}). повідомляє
аналізу досяжності експортів, що модуль
ніколи не називає викликаний код через власні атоми. Його атомні літерали
не утримують експорти інших модулів (власне ім'я все ще утримує власні,
оскільки кортежі помилок називають його для format_error/1), теги record
не утримують жодних, а вбудовані функції, що створюють атоми, і
binary_to_term/1,2 не вважаються ані джерелом імен модулів, ані динамічним
викликом. Також він не може передавати імена інших модулів у вбудовані
функції, що викликають їх за іменем, як-от spawn/4 з вузлом або
erlang:get_module_info/1,2. Його власні виклики за іменем усе ще
враховуються: M:F(...) або apply/3 з літеральним модулем і функцією,
визначеною під час виконання, утримують експорти цього модуля, а виклики з
модулем, визначеним під час виконання, є динамічними викликами й досягають
модулів, які називають атоми інших модулів.
Параметр не змінює згенерованого коду, лише набір експортів, які зберігає
виконуваний файл. Хибна обіцянка вилучає експорт, який модуль викликає за
іменем, що потім спричиняє undef. Бібліотечні модулі задають його там, де
їхні атоми — лише дані: epp (імена токенів і макросів, машини, застосунків),
erl_error (атом-заповнювач у надрукованих списках аргументів; error_info
називає модулі коду, що спричинив помилку), erl_features (список
можливостей beam-файлу) і file (причини помилок, теги запитів; модулі,
названі в запиті io, є атомами відправника запиту). Без нього apply/3 у
file:io_request/3 і list_to_atom/1 у epp утримували кожен експорт
програми, що препроцесує файл, включно з усім erl_eval; з ним (і
обчисленням умов -if у C++) додатковий час її компіляції порівняно з
hello-world скорочується приблизно на 40%.
Запуск прикладу зі скомпільованими модулями
client:main/1 робить приклад програмою:
./build/debug/bin/clau -O2 -o build/demo examples/compile/answer.erl examples/compile/client.erl
./build/demo # prints 42, -7 and {record,map,binary,list,integer,other}
Ті самі модулі можна запустити й через обв'язку C++. З Developer PowerShell для Windows x64 і зібраним компілятором:
$tool = './build/debug/bin/clau.exe'
& $tool -O0 --emit obj --artifact-dir build/example-aot examples/compile/answer.erl examples/compile/client.erl
cmake -S examples/compile -B build/example-native -G Ninja -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_BUILD_TYPE=Debug "-DGENERATED_DIR:PATH=$((Resolve-Path build/example-aot).Path)"
cmake --build build/example-native
./build/example-native/bin/Debug/compiled_modules.exe
Вона друкує 42, -7, record, map, binary, list, integer, other,
по одному на рядок. Обв'язка явно реєструє модулі, створює контекст і декодує
результати; це приклад хоста, а не робоча точка входу. В Unix використовуйте
build/debug/bin/clau, clang++ і
-DGENERATED_DIR="$PWD/build/example-aot". CTest codegen_examples виконує
ці команди за кожної політики.
Інші дії над тими самими вихідними файлами:
& $tool -O2 --emit llvm-ir --artifact-dir build/example-ir examples/compile/answer.erl examples/compile/client.erl
& $tool --print-types --verbose examples/compile/answer.erl examples/compile/client.erl
& $tool --print-ir --print-optimized-ir examples/compile/answer.erl examples/compile/client.erl
& $tool -O2 --no-type-specialization --verbose examples/compile/answer.erl examples/compile/client.erl
Друк abstract format
--print-abstr (дія фронтенду) друкує кожен розібраний модуль як форми OTP
abstract format, які
epp передав би parse transform, по одному терму file:consult/1 на рядок —
ті самі терми, що записує erlc +to_abstr (перетворення).
Друк вихідного коду
--print-source (дія фронтенду, як --print-ast) друкує кожен розібраний
модуль як вихідний код Erlang; --print-types друкує той самий текст з
анотаціями типів. Принтер (print_source, expression_source, type_source
у printing.hpp; compiler/src/printing/source_*) придатний для повторного
використання:
- Форми йдуть одна за одною в порядку вихідного коду, з порожнім рядком
навколо кожної функції; форма
-file, яку препроцесор додає перед модулем, пропускається, а ті, що оточують включені файли, зберігаються. - Текст — це розібраний синтаксис: макроси розгорнуто, включення вбудовано, а
коментарі, початкове написання та розміщення втрачено. Клаузи та блокові
вирази (
case,if,receive,try,maybe,begin, fun з більш ніж одним рядком) займають рядки з відступом у чотири колонки; усе інше — в одному рядку. Дужки беруться лише з власних груп вихідного коду. - Надрукований текст розбирається назад у те саме синтаксичне дерево, а
повторний друк дає той самий текст (CTest
printing_sourceна фікстурах, що розбираються). SourceNotesдодає до виразу примітку, що друкується як завершальний коментар% Textпісля пунктуації рядка, де вираз закінчується, і рядки коментарів над формою. Примітки мають лише вирази на цілий рядок (вирази тіла та значення, що зіставляється вcase; по одному на рядок, найзовнішній), тож довгі вкладені вирази лишаються читабельними, а текст лишається Erlang. Зразки й guard приміток не мають.
Параметри
| Параметр | Поведінка |
|---|---|
--emit obj|llvm-ir|llvm-bc | Публікує один артефакт на модуль |
--artifact-dir DIR | Корінь артефактів (потребує --emit) |
-o PATH / --output PATH | Компонує виконуваний файл (компонування); конфліктує з --emit |
--linker PATH, --runtime-library PATH | Драйвер Clang і архів runtime для -o |
--no-cache | Не читати й не записувати кеші збирання |
--cache-inference-in-dirs PATTERN | Можна повторювати: модулі у відповідних каталогах зберігають результати виведення типів у кеші збирання, на додачу до неявних каталогів застосунків OTP (кеш виведення) |
--build-dir DIR | Де Clause тримає тимчасові файли (у DIR/tmp); замінює build_dir кожної вибраної цілі проєкту; позиційні вхідні файли без нього його не мають (каталог збирання) |
--entry MODULE[:FUNCTION[/ARITY]] | Функція точки входу виконуваного файлу з арністю 1 (інакше з арністю 0; /0 або /1 вибирає одну); перевіряється в кожному режимі компіляції й додає стартовий артефакт clausev1_start (виконувані файли); інспекції IR і асемблера тоді пропускають те, чого вона не може досягти (живучість експортів) |
--target-triple TRIPLE | Цільова машина; --target — це вибір цілі проєкту |
-O0 / -O2 / -Os | Загальний код за замовчуванням + LLVM O0 / обмежена спеціалізація + LLVM O2 / LLVM Os, без спеціалізації, одна секція на символ і вилучення компонувальником коду та даних, на які немає посилань |
--no-type-specialization | Вимикає варіанти незалежно від порядку параметрів |
--jobs <n> | Оптимізувати й генерувати об'єктні модулі в n потоках (за замовчуванням: усі апаратні потоки), кожен модуль у власному контексті LLVM; об'єктні файли однакові за будь-якої кількості; --jobs 1, інспекції IR та асемблера зберігають усі модулі в одному контексті |
--lto | Компонує модулі як біткод з оптимізацією під час компонування LLD (цілі Windows MSVC і ELF; потребує -o або збирання проєкту з компонуванням) (компонування) |
--keep-exports | Зберігає кожен експорт виконуваного файлу доступним для виклику, замість пропускати ті, яких він не може досягти; потребує -o, збирання проєкту з компонуванням або інспекції IR/асемблера (живучість експортів) |
--shell | Скомпонувати інтерактивну оболонку Erlang і всі бібліотечні модулі, зберігши можливість викликати кожен експорт; без --entry виконуваний файл запускає оболонку, тож clau --shell -o erl без вхідних файлів збирає оболонку лише над бібліотекою; потребує -o або збирання проєкту з компонуванням (інтерактивна оболонка) |
--fips=PATH | Зафіксувати програму в режимі FIPS з наданим користувачем провайдером OpenSSL FIPS у PATH (модуль і fipsmodule.cnf, що копіюються поруч із виконуваним файлом); передбачає --with-crypto; відсутній або некоректний провайдер зупиняє збирання (FIPS) |
--with-crypto[=PATH] | Скомпонувати архів crypto runtime і додати бібліотечний модуль crypto до пакета для програм, що досягають crypto лише через динамічні виклики (програми, які викликають його, і так компонуються з ним); PATH — встановлений OpenSSL ≥ 3.1.2 або дерево вихідного коду для компонування замість того, з яким зібрано Clause; потребує -o або збирання проєкту з компонуванням (crypto) |
-g | Таблиці рядків Erlang в об'єктних файлах і скомпонованих виконуваних файлах (CodeView/PDB для цілей MSVC, DWARF для решти) (налагодження) |
--print-ir / --print-optimized-ir | Перевірене IR до/після проходів LLVM, з рядками вихідного коду Erlang як коментарями |
--print-asm | Асемблер цілі для кожного модуля після вибраного конвеєра, де кожна послідовність машинного коду має коментар з інструкцією IR і рядком Erlang, з яких вона походить (лістинги асемблера) |
--print-types | Кожен модуль як вихідний код Erlang з анотаціями виведених типів (семантика); зупиняється перед LLVM |
--verbose | Події фаз [pp], [parse] і [comp] у stderr, потім зведення індексу каталогів [index] ... |
--print-stage-times | [time] <stage> <subject>: <ms> ms у stderr: plan для кожного маніфесту проєкту (його завантаження й розгортання шаблонів кожної вибраної цілі), preprocess, parse і transform (модулі з parse transforms) для кожного вихідного файлу, compile для кожного пакета (кількість модулів, ціль проєкту), link для кожного виконуваного файлу; (cached) іде після етапу, що повторно використав кешований результат. Перед підсумком compile пакета йдуть його фази з тим самим суб'єктом: index, calls, liveness (виконувані файли та інспекції з точкою входу), declared-types, inference-cache (з N of M covered modules reused, кеш виведення), inference, inference-store, contracts, lower, optimize, emit (ті, що виконувалися; за виведення об'єктних файлів у кількох потоках --jobs, optimize також переміщує кожен модуль у власний контекст LLVM і перевіряє його, а emit перевіряє й генерує модулі паралельно). Після inference: рядки inference pass N з кроками роботи проходу з бюджету роботи виведення (N of BUDGET work steps) і тим, як він завершився (inputs changed, fun results changed, settled або останній прохід після неусталених проходів), із назвою функції, що виконувалася, коли бюджет вичерпався, потім (кожен прохід після першого) ; skipped: N components, W work steps: компоненти, збережені з останнього обходу, бо нічого з прочитаного ними не змінилося, і вартість тих обходів (врахована в робочих кроках проходу), потім ; N lattice joins — виконані проходом об'єднання ґратки (вкладені теж враховуються), з , costly: C components given term() inputs, коли дорогі рекурсивні компоненти втратили локальні входи (виведення типів), а далі (кожен прохід, що збирав локальні входи) ; inputs: F functions, S slots changed (kinds) з видом зміни кожного зміненого вхідного слота (first за відсутності попереднього, integer bound, union member на верхньому рівні, shape, deeper для вкладеності контейнерів, function types, term()) і N fun reads not final (значення fun F/A, що прочитали зведення, яке згодом змінилося), потім ; graph: N nodes (+M), W widenings від попереднього проходу і ; clean: C of K components (A inputs differ, B callees differ), X changed, T ms (P% of pass), S work steps: компоненти, чиї входи членів і зведення безпосередньо викликаних функцій дорівнюють попередньому проходу, ті з них, чиї зведення все одно змінилися, і витрачені на них час та робота; потім inference lattice (без часу) з кількістю виконань кожної операції ґратки за всі проходи (join двох членів, вкладені теж враховуються, widen, meet, within, subtract, truncate) і кількістю викликів, що повторили операцію з тими самими операндами раніше у своєму проході й у будь-якому проході (їх обслуговують відповіді, збережені графом, а вкладені операції відповіді не виконуються й не враховуються повторно), потім об'єднання іншої кількості членів і пошуки вкладеності контейнерів із тими, для яких граф зберіг відповідь (відстеження повторів уповільнює профільоване виведення приблизно на 7%); потім inference churn з часом збирання входів і кількістю локальних вхідних слотів, що змінилися один раз, двічі, ... за всі проходи, і inference churned <module>:<function>/<arity> #<argument> (без часу) для 10 найчастіше змінюваних слотів із видом останньої зміни та фактами до й після неї (кожен обрізано до 200 символів); потім (без часу) до 10 компонентів inference clean-changed <module>:<function>/<arity>, які були незмінними за входами й викликами, але змінилися, з проходом першого виявлення й частотою (залежність, відмінна від входів і прямих викликів, як-от fun F/A); потім (без часу) inference facts N після кожного проходу, що збирав входи: факти кожного зведення й локального входу, найбільший і середній розмір їхнього дерева (вузли враховуються один раз на шлях), найширше об'єднання, кількість тих, що досягають бюджетів глибини контейнерів, членів об'єднання й елементів кортежу/map, зведення з найбільшою кількістю (32) типів функцій і залежні факти; потім inference largest з найбільшим фактом кожного з 3 найдовших за часом компонентів (обрізано до 200 символів); потім inference slowest <module>:<function>/<arity> для 10 функцій (рекурсивні групи називають одного члена and N more), що забрали найбільше часу за всі проходи, з їхніми кроками роботи й часткою цієї роботи в бюджеті проходів, які їх виконували (877450 work steps, 43.9% of 10 passes of 200000 budget), widened to term(), коли бюджет розширив один із їхніх фактів, а також об'єднаннями ґратки, виконаними їхніми обходами, і створеними вузлами графа; потім inference costliest <module>:<function>/<arity> для 10 функцій тривалістю щонайменше 10 мс із найбільшим часом на робочий крок, з цим часом у мікросекундах, робочими кроками, об'єднаннями ґратки (і об'єднаннями на робочий крок) та створеними вузлами; потім inference rounds <module>:<function>/<arity> and N more для 10 найповільніших рекурсивних груп: розв'язання за всі проходи, раунди щодо обмеження на одне розв'язання (8 + 4 на члена), розв'язання, що досягли обмеження або витратили свою восьму частину бюджету проходу (кожен член розширено до term()), розв'язання, що одразу здалися, бо так зробив попередній прохід (remembered), або завершилися з вичерпаним бюджетом проходу, обходи членів (і пропущені, бо нічого з прочитаного ними не змінилося, і відтворені: пізніше розв'язання повторило останній обхід члена, бо нічого з прочитаного ним не змінилося) і частка обходів та відтворень, що змінили зведення (зросло, змінилися входи типів функцій або повернулося до одного з останніх 4 зведень члена), та змінені члени на раунд останнього розв'язання в десяти групах раундів, потім ; N solves repeated the previous fixed point, M reached it after R rounds in total: розв'язання, що завершилися на зведеннях, якими завершився попередній прохід, і розв'язання, що досягли їх до збіжності, з кількістю витрачених раундів (які можна було б пропустити, почавши з них). Кешована компіляція фаз не друкує |
--asn1-option OPTION, --asn1-outdir DIR, --generate-asn1 | Компілювати входи .asn/.asn1/.set.asn у Module.erl і Module.hrl з параметром asn1ct (можна повторювати), у DIR (додається до шляху include) або лише згенерувати їх (ASN.1) |
--print-env | Друкує розв'язане середовище компіляції як TOML у stdout і завершується, не обробляючи вихідних файлів (звіт про середовище) |
--print-inputs | Препроцесує й розбирає вхідні файли, додає модулі, які вони називають, як це робить компіляція, потім перелічує кожен вихідний файл пакета в stdout і зупиняється перед компіляцією (проєкти) |
- Без
--emitкомпіляція перевіряє об'єктні файли в пам'яті й нічого не записує поза каталогом збирання. - Позиційні вхідні файли утворюють один пакет; кожна ціль проєкту — окремий пакет.
- Корені артефактів:
build/aot(позиційні) абоbuild/aot/<hex-target>у каталозі маніфесту. Явні корені задаються відносно каталогу виклику. - Імена — оборотний hex:
answer→clausev1_616e73776572__0.obj(.oдля ELF/Mach-O,.ll,.bc); стартовий об'єкт —clausev1_start.obj. - Усі пакети компілюються і готуються до публікації. Невдачі нічого не публікують і зберігають попередні виводи; заміна атомарна для кожного файлу, а не для пакета.
--emitконфліктує з-o; перемикачі компіляції конфліктують із діями лише фронтенду та--new-project.--print-typesвідхиляє параметри цілі/оптимізації.- Знімки IR — це асемблер LLVM; кілька знімків розділяються екранованими
заголовками-коментарями і не є одним модулем, що розбирається. Для вхідних
даних інструментів використовуйте
--emit llvm-ir. Коментарі з рядками вихідного коду показують початковий текст (нерозгорнуті макроси) і переживають оптимізацію завдяки налагоджувальним розташуванням.
Лістинги асемблера
--print-asm запускає вибраний конвеєр (як --print-optimized-ir), а потім
друкує асемблер кожного вхідного модуля з тієї самої цільової машини, що
записує об'єктні файли, по одному модулю. Коментарі використовують маркер
цілі (# на x86, // на AArch64 ELF, ; на Apple, @ на ARM):
# erlang answer.erl:3: value() -> 42.
# ir store i64 687, ptr %frame.slots, align 8
movq $687, 32(%r15,%r12,8)
# ir br i1 %4, label %return.inline, label %return.service
- Коментар
irвідкриває кожну послідовність інструкцій, вибраних з однієї інструкції IR, записаної так, як її друкує--print-optimized-ir(та сама нумерація значень,switchзгорнуто в один рядок, без!dbg). Код, переміщений чи об'єднаний між інструкціями, повторює або пропускає коментарі так, як каже таблиця рядків. - Коментар
erlangназиває фізичний файл, рядок і текст, як коментарі з вихідним кодом в IR, коли рядок змінюється в межах функції. Інструкції, які пониження ні до чого не прив'язало (обгортки, налаштування фрейму), його не мають. - Лістинг компілює копію, чия таблиця рядків нумерує інструкції IR замість
рядків Erlang; директиви цієї таблиці рядків, налагоджувальні секції й мітки
пропускаються. Після повторного асемблювання він дає об'єктний файл, який
записує
--emit obj. - З іншими інспекціями кожен модуль друкує
before,afterіassemblyпо черзі, кожне після заголовка (stage=assembly). Як і інспекція IR, він відхиляє--emit,--artifact-dir,--outputі--print-types. Лістинги x86 використовують синтаксис AT&T. - З
--entryінспекції IR і асемблера пропускають функції, яких точка входу не може досягти, як це робить виконуваний файл (--keep-exportsїх зберігає).
Звіт про середовище
--print-env перевіряє весь командний рядок (конфлікти, читабельність
позиційних вхідних файлів, планування проєкту), потім друкує те, що
використала б ця команда, як один документ TOML у stdout, завершується з
кодом 0 і не читає жодного вихідного файлу. Шляхи абсолютні, з /.
# clau compile environment (--print-env)
command = ["clau", "--print-env", "-O2", "-o", "app", "main.erl"] # as given
actions = ["link"] # see below
working_directory = "/work"
[[targets]] # one per selected project target; one for positional inputs
name = "app" # project targets only
project = "/work/project.toml"
sources = ["/work/src/main.erl"] # globs and search paths resolved
library_directory = "/clau/library" # application dirs, searched before source_search_paths
build_dir = "/work/build" # "" for positional inputs without --build-dir
entry = "main:main" # or "auto" when linking without --entry
output = "/work/app" # linking only: final name (".exe" on Windows)
linker = "/usr/bin/clang++" # linking only; "" when none is found
runtime_library = "/clau/lib/libclause_runtime.a"
[targets.options] # manifest option names
source_search_paths = [] # directories, patterns expanded
include_dirs = [] # search order, patterns expanded
defines = []
applications = { "stdlib" = "/otp/lib/stdlib" } # last mapping wins
enabled_features = ["maybe_expr"] # OTP defaults plus changes
[targets.backend]
target_triple = "x86_64-unknown-linux-gnu" # host when not given
optimization = "O2"
type_specialization = true # O2 without --no-type-specialization
debug_info = false
lto = false
keep_exports = false # --keep-exports or the project option
shell = false # --shell or the project option
emit = "none" # or obj, llvm-ir, llvm-bc
artifact_dir = "/work/build/aot"
(Масиви друкуються по одному елементу на рядок.) actions перелічує дії
фронтенду (print-pp, parse-check, print-ast, print-source,
print-abstr, інакше preprocess-check; print-inputs сам замінює їх),
інакше інспекції (print-types, print-ir, print-optimized-ir,
print-asm), інакше emit, link (-o) або compile. У збиранні проєкту
цілі з output також компонуються. Модулі, знайдені пізніше за іменем (через
source_search_paths або бібліотеку), не перелічуються: щоб їх знайти,
потрібен розбір (--print-inputs робить це). --print-env не можна
поєднувати з --new-project.
Кеш компіляції
Пакет із каталогом збирання (кожна ціль проєкту, позиційні вхідні файли з
--build-dir) зберігає результат компіляції в
кеші збирання: діагностику в порядку появи, збій
і, коли їх потребує виконуваний файл чи --emit, об'єктні файли (або біткод)
кожного модуля й модуля запуску. Пізніше збирання того самого пакета з тими
самими параметрами відтворює діагностику й компонує або публікує збережені
об'єктні файли точно так само, як свіжі; --print-stage-times тоді показує
[time] compile ... (cached). --print-ir, --print-optimized-ir,
--print-asm і --print-types завжди обчислюють наново, а --no-cache не
читає й не записує.
- Одиниця повторного використання: увесь пакет. Аналіз, виведення типів, бюджети спеціалізації (на ціль), живучість експортів і модуль запуску охоплюють кожен модуль, тож об'єктний файл одного модуля залежить від фактів інших (типи функцій, які викликаються, record, поведінки, які функції лишаються живими). Ці факти наразі не мають канонічної форми для хешування окремо для кожного модуля, тож ключ на модуль не можна було б довести коректним; будь-яка зміна будь-якого модуля пакета перекомпільовує пакет. Лише результати виведення типів зберігаються для кожного модуля в охоплених каталогах бібліотек (кеш виведення), тож така перекомпіляція використовує їх повторно.
- Ключ. Версія
clauта ідентичність виконуваного файлу; шлях і відбиток кожного вхідного файлу (кожен файл, який фронтенд прочитав для нього, шлях і байти, по порядку: вихідний файл, його включення та файлиinclude_lib, а також відповідь його parse transforms; параметри препроцесування; згенерований текст заповнювачів), наперед визначені форми й статус escript; розв'язаний триплет, рівень оптимізації,--emit,--no-type-specialization,-g,--lto,--keep-exports, чи компонується виконуваний файл, вибір точки входу й ціль проєкту. Записи однієї цілі з іншими параметрами зберігаються поруч. - Сховище. Кожен вивід — окрема частина, тож об'єктний файл, більший за вбудований ліміт сховища, стає файлом-blob з адресацією за вмістом, спільним, коли два набори параметрів дають ідентичні об'єктні файли.
Бекенд
- Один контекст LLVM і одна цільова машина на пакет. Триплет хоста використовує процесор і можливості хоста; чужі триплети — загальний процесор. PIC, мала модель коду.
- Бекенди: X86, ARM, AArch64 (перетин із SDK). Невідомі або відсутні бекенди дають помилку; запасного варіанта з хостом немає.
- Перевірка виконується до і після оптимізації; вона перевіряє коректність IR, а не правильність Erlang.
- Бюджети на ціль: 16 384 модулі, 1 500 000 вузлів AST на модуль, 16 000 000 на пакет. Серіалізований вивід: 64 MiB на модуль, 256 MiB на пакет. Перевищення бюджету — звичайна діагностика ресурсів.
SDK LLVM
Стабільний LLVM 23.1.x, щонайменше 23.1.1. LLVM — залежність хоста: архітектура, стандартна бібліотека C++ і CRT Windows мають відповідати інструменту компілятора. Код проєкту зберігає винятки/RTTI; жоден виняток не може розкручуватися через LLVM. Runtime ніколи не використовує LLVM.
- Пошук перевіряє стандартні префікси (
/usr,/usr/local,/opt/homebrew,/opt/local,/opt/llvm, Linuxbrew,/Library/Developer/Toolchains, Program Files у Windows), зокрема розміщення з версіями. Дерева збирання відхиляються. LLVM_DIRявно вибирає SDK; недійсний вибір дає помилку без запасного варіанта.- Якщо нічого не знайдено, CMake завантажує зафіксований архів 23.1.2
(з перевіркою SHA-256) у
thirdparty/для Windows x64/ARM64, Linux x64/ARM64 або macOS ARM64.CLAUSE_DOWNLOAD_LLVM=OFFвимикає завантаження. - Пробне компонування під час конфігурування перевіряє сумісність ABI.
Еталонне налаштування Windows x64: clang-cl 23.1.2 хоста в
C:/Program Files/LLVM/bin, SDK
thirdparty/clang+llvm-23.1.2-x86_64-pc-windows-msvc, інструменти Visual
Studio 18 x64, Windows SDK 10.0.26100.0, Ninja, /MT,
_ITERATOR_DEBUG_LEVEL=0. Автоматичний вибір застосовує налаштування /MT та
ітераторів; явний LLVM_DIR — ні, і тоді пробне компонування завершується
невдачею.
PCRE2
Вбудовані функції re у runtime використовують PCRE2 10.47 — реліз, який
постачає OTP 29 (erts/emulator/pcre/vendor.info), тож синтаксис шаблонів,
параметри й захоплення поводяться як в OTP: 8-бітні кодові одиниці, підтримка
Unicode, без JIT, стандартний pcre2_match (патч OTP для поступання не
перенесено). PCRE2 має ліцензію BSD-3-Clause з винятком PCRE2 для
скомпільованих двійкових файлів; жоден файл PCRE2 не закомічено. CMake
(cmake/Pcre2Dependencies.cmake) розв'язує його в такому порядку:
-DCLAUSE_PCRE2_ROOT=<source directory>має перевагу (без завантаження); версія в йогоconfigure.acмає бути 10.47.- Встановлений PCRE2 версії щонайменше 10.47 (
CLAUSE_PCRE2_SYSTEM=ON, типово): пакет CMake (find_package(pcre2 CONFIG COMPONENTS 8BIT), перевага статичним бібліотекам), інакше pkg-configlibpcre2-8, де статичнаlibpcre2-8.aмає перевагу над спільною бібліотекою. - Зафіксований реліз, завантажений у
thirdparty/pcre2-10.47і перевірений (pcre2-10.47.tar.bz2, SHA-25647fe8c99461250d42f89e6e8fdaeba9da057855d06eb7fc08d9ca03fd08d7bc7);-DCLAUSE_DOWNLOAD_PCRE2=OFFвимикає завантаження.
Каталог вихідного коду (1 або 3) збирає об'єктну бібліотеку clause_pcre2 з
перелічених файлів .c з поширюваними config.h.generic, pcre2.h.generic
і pcre2_chartables.c.dist, скопійованими в дерево збирання (без кроку
генерації, без прапорців попереджень проєкту, поза clang-tidy і Lizard), а її
об'єктні файли додаються до самого clause_runtime: архів самодостатній, і
кожен споживач (компонування clau, вкладені тестові програми,
runtime_link, examples/compile) отримує PCRE2 разом із ним. Встановлена
бібліотека натомість стає додатковим входом компонувальника, записаним у
clause_runtime.link поруч з архівом (один вхід на рядок), який clau
додає до кожного компонування; спільна бібліотека робить виконувані файли
залежними від неї під час виконання. За платформами: Windows використовує
завантаження (системний PCRE2 на практиці не шукається); Linux і macOS
використовують встановлений PCRE2 10.47 або новіший, якщо він є, інакше
завантаження. PCRE2 завжди компонується в runtime, а також у програми, які
ніколи не викликають re.
zlib
Модуль zlib у runtime і стиснуті зовнішні терми використовують zlib
1.3.2 — реліз, який постачає OTP 29 (erts/emulator/zlib/vendor.info),
тож стиснуті байти дорівнюють байтам OTP. CMake компілює вихідні файли його
бібліотеки (cmake/ZlibDependencies.cmake, clause_add_runtime_zlib) в
об'єктну бібліотеку clause_runtime_zlib, чиї об'єктні файли приєднуються до
clause_runtime, як і в PCRE2; -DCLAUSE_RUNTIME_ZLIB_ROOT=<source directory>
має перевагу, інакше реліз завантажується в thirdparty/zlib-1.3.2 і
перевіряється (SHA-256
bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16) на кожному
хості. Ліцензія zlib — ліцензія zlib. LLVM компілятора має власний zlib; вони
ніколи не зустрічаються в одному двійковому файлі.
zstd
Модуль zstd у runtime використовує zstd 1.5.7 — реліз, який постачає
OTP 29 (erts/emulator/zstd/vendor.info), тож кадри дорівнюють кадрам OTP.
CMake компілює вихідні файли його бібліотеки (common, compress, decompress;
однопотоково, без асемблерного декодера; cmake/ZstdDependencies.cmake,
clause_add_runtime_zstd) в об'єктну бібліотеку clause_runtime_zstd, чиї
об'єктні файли приєднуються до clause_runtime, як і в zlib;
-DCLAUSE_RUNTIME_ZSTD_ROOT=<source directory> має перевагу, інакше реліз
завантажується в thirdparty/zstd-1.5.7 і перевіряється (SHA-256
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3); це той
самий архів, який використовує збирання LLVM для Windows. Ліцензія zstd —
BSD-3-Clause (або GPL-2.0).
OpenSSL
Вбудовані функції crypto у runtime використовують libcrypto від OpenSSL,
зафіксовано 4.1.0, приймається будь-який реліз від 3.1.2 (останнього з
провайдером, що пройшов валідацію FIPS), в окремому архіві
clause_runtime_crypto, з яким компонуються лише програми, що викликають
crypto. Джерело бібліотеки, її збирання з вихідного коду в Clause і clau,
компонування (--with-crypto[=PATH]) і режим FIPS (--fips=PATH) описано в
crypto.
Інші передумови: CMake ≥ 3.28, компілятор C++23, Boost ≥ 1.90, toml++ 3.4.0 та інструменти перевірки якості. OTP потрібен лише для аудитів за бажанням і регенерації фікстур.
Clause