Подання термів
Допущені види: атоми/булеві значення, довільні цілі числа, скінченні числа з рухомою комою binary64, кортежі, правильні/неправильні списки та рядки, map, bitstring і звичайні кортежні record; native records (native records); fun (функціональні значення); локальні pid, порти та посилання (нижче, порти). Кодування слів описано в abi.md.
Володіння
- Складені значення живуть у купі свого процесу
(контракт купи) або в області літералів модуля лише для
читання (області). Вказівники процесу завжди вказують
на початки об’єктів. Допуск (лише збірки Debug; Release довіряє словам,
CHECKED_ADMISSION) перевіряє власний діапазон і форму заголовка, відхиляючи чужі адреси й некоректні форми (допуск); вид і розмір декодуються із заголовка. - Хостовий
Term— рівно одне тривіально копійованеWord, лише зvalue_. Term купи чинний до наступного збирання своєї купи або завершення процесу (корені); він нічого не утримує й не містить купи, токена часу життя чи лічильника збирань. Подальший доступ читає переміщену або звільнену пам’ять і має невизначену поведінку; помилкиstale_termнемає. Знищення безпечне. Фабрики, резервування й буфери повідомлень зберігають власні перевірки часу життя й надалі повідомляютьexpired_context. - Конструювання перевіряє дочірні значення, резервує пам'ять, ініціалізує, а потім публікує за один крок; у разі невдачі пам'ять і лічильники відкочуються.
Term::from_word(word)допускає лише незалежні від власника безпосередні значення;Term::from_word(word, context)також допускає атоми й терми купи цього контексту. Передавання в межах однієї купи зберігає ідентичність.copy_to/ProcessHeap::addкопіюють граф іншого процесу за контрактом викликача про належність до одного runtime; фабрики термів відхиляють чужі вхідні дані зwrong_owner(копіювання між купами).- Комірки живуть, доки збирання сміття не виявить їх недосяжними, або до знищення купи (збирання).
Атоми
AtomStorageна кожен runtime, ліниве інтернування за точним написанням UTF-8, без нормалізації та витіснення.RuntimeOptions::max_atoms1..2^26, за замовчуванням 2^20; назви модулів/експортів враховуються. Уже наявні написання успішно обробляються і при заповненій таблиці.- До 255 скалярних значень Unicode; порожні, NUL і додаткові символи дозволено; пошкоджений/надмірно довгий/сурогатний UTF-8 відхиляється.
- Корисні навантаження походять із загальнопроцесного лічильника й ніколи не використовуються повторно, навіть між runtime. Глобальний індекс вміщує 2^28 номерів, обмежених корисним навантаженням атома в слові на 32-бітних цілях (2^26 ідентифікаторів за весь час життя). Обмеження окремого runtime незмінні. Номери атомів ніколи не залишають вузол: ETF і дискові форми використовують написання.
detail::atom_tableзабезпечує неблокувальне читання за номером атома: фіксовані записи безпосередньо, потім блоки по 4,096 слотів, що зберігаються протягом життя процесу. КожнеAtomStorageволодіє записами за написанням, публікує їх у цьому індексі й допускає лише власні або фіксовані записи. Завершення виводить його слоти з ужитку.- Term атомів нічого не утримують.
atom_spelling()позичає написання до завершення runtime; він іatom_utf8()повідомляютьexpired_contextпісля завершення runtime нефіксованого атома. Перенесення атома між runtime означає інтернування його написання в цільовому runtime, поки вихідний ще живий. - Булеві значення — атоми
true/false. Фіксовані атомиfalse,true,undefined,okтаerror(abi::v1::fixed_atom_spellings) мають слова номерів атомів 0–4 в кожному runtime, з безсмертними статичними записами, знаходяться без блокування й не враховуються ні вsize(), ні вmax_atoms; згенерований код використовує їх як константи. Кожне інше слово атома є приватним для runtime. - Складені літерали (рядки, списки, кортежі й map із літералів, числа з рухомою комою, bignum, константні binary) — це літерали модуля: закодовані під час компіляції, декодовані один раз на runtime під час реєстрації в його купу літералів і спільні (області літералів); цілочислові літерали в малому діапазоні є безпосередніми значеннями.
- Інші літерали атомів завантажуються з кешу атомів модуля
(
ModuleDescriptor::atom_cache: ідентичність runtime, що його закріпив, а потім по одному слову на слот), який заповнює перший runtime, що реєструє модуль; контекст, чийFrameState::runtimeзбігається із закріпленням, читає його без виклику, будь-який інший викликаєCLAUSE_atom_v3. Закріплення повертається, коли сервер коду runtime звільняє модуль. - C++ код runtime записує літеральні атоми як
"name"_atom(atom_names.hpp): один статичнийCachedAtomна написання зберігає останнє інтерноване для нього слово, аAtomStorage::internповертає його після одного неблокувального читання індексу, якщо цей runtime видав його, пропускаючи перевірку, спільне блокування й map написань. Слово іншого або завершеного runtime не проходить цю перевірку й замінюється, тому кілька runtime в одному процесі працюють правильно (вони лише оновлюють кеш). Допоміжні функції вбудованих функцій приймаютьAtomName— написання з таким кешем або без нього. - Інтернування і пошук безпечні для одночасних робочих потоків планувальника (потоки); написання зберігає одне слово.
Цілі числа
- Значення, що вміщуються в 28/60-бітне корисне навантаження цілі, є безпосередніми; більші — незмінні комірки зі знаком і модулем. Нуль і малі значення завжди нормалізуються до безпосередніх. Звуження літералів до ширини хоста немає.
- На
-O2операнди, малість яких доводить виведення типів, пропускають перевірки тегів, а доведений діапазон результату також пропускає перевірку переповнення й запасний шлях (доведення). - Згенеровані
+,-,*пробують вбудований швидкий шлях для двох безпосередніх значень, інакше викликають сервіс runtime:+і-додають або віднімають слова з тегами напряму (a + (b ^ 15)) і переходять до запасного шляху, коли слово переповнюється, що відбувається саме тоді, коли результат виходить за малий діапазон;*обчислює в подвійній ширині з явними межами. Так самоband,bor,bxor,bsr,div,remі порівняння двох малих цілих (вбудовані форми). - Сервіс множить bignum (або малий добуток із переповненням) на мале ціле число машинними розрядами, з комірки операнда безпосередньо в нову; інші операції над bignum проходять через значення довільної точності.
divвідкидає дробову частину в бік нуля;remбере знак діленого; побітові операції використовують нескінченний додатковий код; від'ємна кількість зсуву змінює напрямок; величезні зсуви праворуч насичуються до 0 або -1.- Помилки: неправильні операнди і нульовий дільник →
badarith;abs/1→badarg. - Обмеження: як в ERTS, модуль займає щонайбільше
BIG_ARITY_MAXслів: 4 194 240 біт на 64-бітних цілях (65 535 слів), 4 194 272 на 32-бітних (131 071 слово); десятковий текст відповідно (1 262 593 і 1 262 602 цифри). Більший арифметичний результат викликаєerror:system_limitу тілі (ValueOutcome::system_limit, ABI) і робить guard невдалим; цілочисловий сегмент, що вилучає більше значення, не збігається. Компілятор відхиляє літерал понад 4 194 240 біт (illegal integer, як сканер OTP) і константний зразок понад цю межу (illegal pattern).
Числа з рухомою комою
- Біти IEEE binary64 передаються в runtime як вісім байтів у мережевому порядку; NaN і нескінченність відхиляються. Без fast-math.
+ - *лишаються точними для двох цілих чисел; будь-який операнд із рухомою комою спричиняє використання binary64./завжди перетворює обидва. Нескінченні результати та нульові дільники →badarith.float/1округлює до найближчого парного;round/1при рівновіддаленості округлює від нуля;trunc,floor,ceilповертають довільні цілі числа. Неправильні операнди →badarg.- Точна рівність розрізняє
1і1.0, а також0.0і-0.0; числове порівняння порівнює точну цілу частину і дріб числа з рухомою комою, ніколи не округлюючи ціле число.min/maxпри рівності повертають перший операнд.
Кортежі, списки, рядки
- Кортеж: заголовок з арністю + поля. Cons: слова голови + хвоста.
{}і[]є безпосередніми значеннями. Рядки — це списки кодових точок. Списки не мають обмеження довжини, крім пам'яті (зокрема необов'язкового бюджету купи). Кортежі містять до 16 777 215 елементів (MAX_TUPLE_ARITY,MAX_ARITYVALв OTP); конструктори повідомляють про більший якresource_limit; вбудовані функції (make_tuple,list_to_tuple/1) викликаютьbadarg. - Сервіси:
hd,tl,length,tuple_size,size,elementз нумерацією від одиниці.
Map
- Незмінні таблиці, відсортовані за точним порядком ключів. Дублікати ключів
під час конструювання зберігають останнє значення. Конструювання сортує
ключі (O(n log n) порівнянь; уже впорядковані за зростанням ключі лише
перевіряються). До 32 записів map — це одна комірка купи, а оновлення
вставляє запис двійковим пошуком у копію; понад це — B+ дерево з комірок
купи, що оновлюється копіюванням шляху за O(log n)
(дерева map). Немає обмеження розміру чи
роботи, крім пам'яті, як в OTP. Цілочислові ключі та ключі з рухомою комою
різняться (також
0.0і-0.0, також у вкладених значеннях). - Оновлення
K := Vвимагають наявності ключа;K => Vвставляє або замінює. Оновлення готують нову таблицю і публікують її один раз. - Помилки в тілі:
{badmap, M},{badkey, K}; guard натомість відхиляють. - Сервіси:
is_map,map_size,map_get,is_map_key, конструювання, оновлення.
Bitstring
- Упаковані від старшого біта, з точною довжиною в бітах і обнуленим доповненням. До 64 байтів зберігаються безпосередньо в binary купи, розмір якого відповідає даним; більші значення використовують спільний незмінний буфер поза купою, а зрізи — це комірки sub-binary їхнього оригіналу (binary поза купою). Обмеження розміру немає, крім необов'язкового бюджету купи процесу; цілочислові сегменти записуються без побудови цілого числа такої ж ширини, як сегмент.
- Конструювання готує всі сегменти перед публікацією. Цілочислові сегменти обрізаються; нативний порядок байтів береться з розміщення даних цілі. Цілочисловий сегмент малого значення шириною не більше 64 бітів записується з його машинного слова (цілими байтами одразу на межі байта); інші йдуть шляхом довгої арифметики. Згенерований код будує малі конструкції сталої ширини вбудовано (фрейми та передачі).
- Дописування, як записувані binary в OTP: конструювання, що починається з
цілого binary (
<<Acc/binary, ...>>) понад 64 байти, будує результат у буфері із запасом місця (розмір округлено вгору до степеня двійки, щонайменше 256 байтів), чия комірка поза купою в процесі-творці записує використані біти й місткість (нуль у кожній іншій комірці, зокрема в копіях). Наступне дописування, чиє представлення акумулятора закінчується саме там, записує нові байти в запасне місце, яке не покриває жодне представлення, і повертає довше представлення того самого буфера: sub-binary власної комірки буфера поза купою, яке може сягати за біти цієї комірки. Будь-який інший акумулятор (старіше представлення, акумулятор іншого процесу, невирівняний, повний буфер) копіюється один раз у новий буфер, щонайменше вдвічі більший за повний. Інші представлення буфера ніколи не змінюються. Звільнені буфери використовуються повторно (повторне використання буферів). - Сегменти з рухомою комою: ширина 16/32/64; конструювання може кодувати
нескінченність, але зіставлення відхиляє нескінченні поля та поля NaN.
Зіставлення з рухомою комою нульової ширини вилучає
0.0. - Сегменти UTF-8/16/32 перевіряють скалярні значення, сурогати і обрізання.
- Зіставлення просуває явний бітовий курсор лише в разі успіху;
:allяк явний розмір недійсний. - Сервіси:
is_binary,is_bitstring,bit_size,byte_size(округлює вгору),size(округлює вниз),binary_part/2,3. Помилки →badarg.
Record
- Звичайні record розгортаються в кортежі
{Tag, Fields...}. Оголошення мають передувати використанню; дублікати, невідомі поля, посилання вперед/на себе і недійсні поля з підстановочним знаком є помилками. - Конструювання обчислює поля в порядку оголошення: явне значення, інакше
значення за замовчуванням
_ = V, інакше оголошене значення за замовчуванням, інакшеundefined. Кожне значення за замовчуванням обчислюється окремо для кожного використання. - Зразки перевіряють арність і тег, потім лише перелічені поля.
#r.f— індекс з нумерацією від одиниці (тег на позиції 1). - Доступ до поля перевіряє арність і тег; невдача дає
{badrecord, V}у тілах і відхилення в guard. is_record(V, r)використовує оголошену арність.is_record/3потребує атомного тегу і цілої арності (недодатна → false; неправильні типи →badarg); атом як третій аргумент — це запит щодо native record, і він повертає false. Guard вимагають буквальних аргументів.- Оновлення
Expr#r{f = V, ...}обчислює нові значення в порядку вихідного коду, потімExpr, потім перевіряє арність і тег ({badrecord, Value}у разі невідповідності, також дляExpr#r{}) і будує новий кортеж; інші поля копіюються._ = Vв оновленнях відхиляється; оновлення недопустимі в зразках і guard. record_info(fields | size, r)розгортається під час компіляції в список назв полів або розмір кортежу. Обидва аргументи мають бути буквальними атомами, аr— раніше оголошеним кортежним record; у guard це недопустимо, а локальнаrecord_info/2відхиляється як уже визначена.- Native records: native records (локальні, кваліфіковані, імпортовані та анонімні форми).
Pid і посилання
self/0 повертає pid процесу, що викликає, make_ref/0 — нове посилання;
pid_to_list/1 і ref_to_list/1 повертають їхній текст.
- Pid — безпосереднє слово (молодші чотири біти
0x3), що містить номер процесу. Номери беруться з однієї послідовності на весь процес ОС і ніколи не використовуються повторно, тож runtime допускає слово pid лише тоді, коли сам видав цей номер: підроблене слово (ніколи не видане) або pid іншого runtime даєwrong_owner. Pid завершеного процесу лишається дійсним термом, як в OTP. 32-бітні цілі мають 2^28 номерів на один запуск програми, 64-бітні — 2^60; створення процесу понад цю межу завершується зresource_limit. - Порт — безпосереднє слово (молодші чотири біти
0x7), що містить його номер із власної послідовності без повторного використання, допускається як pid; друкується як#Port<0.N>і впорядковується між fun і pid (порти). - Посилання — комірка купи (
reference: заголовок і нетрасований 64-бітний номер), що допускається як кожен терм купи: лише у власному процесі, копіюється за значенням між процесами. ХостовийTermбез кореня недоступний після збирання або завершення процесу. Номери походять з одного загальнопроцесного лічильника, тому кожне посилання запуску програми унікальне. - Посилання-дескриптор (magic reference в OTP) — це комірка посилання, яка також містить буфер поза купою; для кожної служби це те саме посилання, і воно ділить свій буфер з кожною копією (посилання-дескриптори).
- Друк дотримується локальних ідентичностей OTP: pid як
<0.N.S>(N — молодші 28 бітів його номера, S — решта), посилання як#Ref<0.A.B.C>(C — молодші 18 бітів його номера, B — наступні 32, A — решта), в обох стилях~wі display. Pid упорядковуються за номером, посилання — за номером, тож пізніші посилання програми впорядковуються після раніших.
Порівняння і порядок
Ітеративне, без обмеження роботи, як в OTP: порівняння, включно з пошуками
ключів map, обмежене лише пам’яттю для пар, що очікують, а однакові слова рівні
без обходу. Вирівняні за байтом bitstring порівнюються цілими байтами.
Порядок: числа < атоми < посилання < fun < порти < pid < кортежі < native record
< map < nil < списки < bitstring (порядок fun
між собою).
Атоми порівнюються за написанням UTF-8 (порядок кодових точок); кортежі — за
арністю, потім полями; map — за розміром, потім ключами, потім значеннями;
bitstring — за логічними бітами. Числа (малі цілі, числа з рухомою комою,
bignum за знаком і машинними розрядами), атоми (за написанням у таблиці атомів
через неблокувальний індекс за номером), локальні посилання (за номером),
цілобайтові binary, а також кортежі й списки цих значень із вкладеністю до
8 рівнів упорядковуються безпосередньо за словами й комірками
(detail::word_order), без допуску термів; будь-яка інша пара, число з рухомою
комою проти bignum тієї самої бітової довжини або мале ціле за межами 2^53
проти числа з рухомою комою використовує структурний порядок. =:= іде тим
самим шляхом (detail::word_exact): пари, які він розрізняє за порядком,
нерівні, а одне слово чи два посилання, які він визнає рівними, рівні, тож
receive, що зіставляє посилання відповіді, не допускає жодного терма.
lists:sort/1 і lists:usort/1 сортують слова елементів на місці: списки лише
малих цілих або лише атомів — за словом чи написанням, інші — стабільним
сортуванням за порядком слів.
Друк
format_term (output.hpp)
відображає будь-який допущений терм в одному з двох стилів OTP. Цілі числа,
кортежі (record є кортежами) і вкладеність виглядають однаково в обох.
~w (TermStyle::write) | erlang:display/1 (TermStyle::display) | |
|---|---|---|
| Атоми | У лапках, якщо не починаються з малої літери Latin-1, за якою йдуть символи імені (з @); зарезервовані слова і maybe/else беруться в лапки; символи поза Latin-1 екрануються як \x{H} | У лапках, якщо не починаються з малої літери Latin-1, за якою йдуть літери, цифри або _; для зарезервованих слів і @ особливого правила немає; UTF-8 зберігається |
| Числа з рухомою комою | Найкоротше подання для точного відновлення в розміщенні OTP: 0.1, 100.0, 1.0e16, 1.5e-7 | C %.6e: 1.500000e+00 |
| Списки | Елементи: [104,105], [1,2|3] | Плаский список друкованих байтів Latin-1 друкується як "hi" (сирі байти; екрануються лише \n і ") |
| Bitstring | <<1,2,5:3>> | Друкований ASCII binary друкується як <<"hi">>, інші — як ~w |
| Map | #{k => v,k2 => v2} | #{k=>v,k2=>v2} |
- Map друкуються в порядку ключів map (
maps:iterator(M, ordered), як~kwв OTP). Типовий~wіerlang:display/1в OTP натомість дотримуються внутрішнього розміщення: порядку таблиці атомів для атомних ключів малих map (він різниться між запусками VM) і порядку хешів понад 32 ключі. Clause цей порядок не відтворює. - Відображення ітеративне, тож глибину обмежує лише сам терм. Текст за
замовчуванням обмежено 64 MiB; перевищення (наприклад, через широко спільний
підтерм) завершується з
resource_limitі не повертає часткового тексту. - Еталони:
runtime_printingпорівнює обидва стилі з OTP (усі результати корпусу плюс авторські граничні випадки); рядки display, порядок map яких в OTP є внутрішнім, пропускаються (фікстури).
Clause