Term-Darstellungen
Zugelassene Arten: Atome/Booleans, beliebige Ganzzahlen, endliche binary64-Gleitkommazahlen, Tupel, echte/unechte Listen und Zeichenketten, Maps, Bitstrings und gewöhnliche Tupel-Records; native Records (native Records); Funs (Funktionswerte); lokale Pids, Ports und Referenzen (unten, Ports). Wortkodierungen stehen in abi.md.
Besitz
- Zusammengesetzte Werte leben im Heap ihres Prozesses
(Heap-Vertrag) oder im schreibgeschützten Literalbereich
eines Moduls (Bereiche). Prozesszeiger benennen
immer Objektanfänge. Die Zulassung (nur Debug-Builds; Release vertraut den
Wörtern,
CHECKED_ADMISSION) prüft den besessenen Bereich und die Form des Headers und weist fremde Adressen und ungültige Formen ab (Zulassung); Art und Umfang werden aus dem Header dekodiert. - Ein Host-
Termist genau einWord, trivial kopierbar, mit nurvalue_. Ein Heap-Term ist bis zur nächsten Bereinigung seines Heaps oder zum Prozessende gültig (Wurzeln); er pinnt nichts und trägt weder Heap noch Lebensdauer-Token oder Bereinigungszähler. Ein Zugriff danach liest verschobenen oder freigegebenen Speicher und ist undefiniert; es gibt keinenstale_term-Fehler. Die Zerstörung ist sicher. Fabriken, Reservierungen und Nachrichtenpuffer behalten ihre eigenen Lebensdauerprüfungen und melden weiterhinexpired_context. - Die Konstruktion validiert Kinder, reserviert, initialisiert und veröffentlicht dann in einem Schritt; ein Fehlschlag macht Speicher und Zähler rückgängig.
Term::from_word(word)lässt nur besitzerunabhängige Immediates zu;Term::from_word(word, context)lässt zusätzlich Atome und Heap-Terme dieses Kontexts zu. Die Übergabe innerhalb desselben Heaps bewahrt die Identität.copy_to/ProcessHeap::addkopieren einen Graphen eines anderen Prozesses unter einem Vertrag mit dem Aufrufer über dieselbe Runtime; Term-Fabriken weisen fremde Eingaben mitwrong_ownerab (Kopieren zwischen Heaps).- Zellen leben, bis eine Speicherbereinigung sie als unerreichbar erkennt, oder bis zum Abbau des Heaps (Speicherbereinigung).
Atome
- Pro Runtime eine
AtomStorage, verzögert interniert nach exakter UTF-8-Schreibweise, ohne Normalisierung oder Verdrängung.RuntimeOptions::max_atoms1..2^26, Standard 2^20; Modul- und Exportnamen zählen mit. Bereits vorhandene Schreibweisen gelingen auch bei voller Kapazität. - Bis zu 255 Unicode-Skalare; leere Atome, NUL und Zeichen aus den Zusatzebenen sind erlaubt; fehlerhaftes/überlanges/Surrogat-UTF-8 wird abgewiesen.
- Nutzdaten stammen aus einem prozessweiten Zähler und werden selbst über Runtimes hinweg nie wiederverwendet. Der globale Index fasst 2^28 Nummern, auf 32-Bit-Zielen durch die Atom-Nutzdaten des Wortes begrenzt (2^26 Identitäten über die Lebensdauer). Grenzen pro Runtime bleiben unverändert. Atomnummern verlassen nie einen Knoten: ETF- und Plattenformate verwenden Schreibweisen.
detail::atom_tableermöglicht sperrfreies Lesen nach Atomnummer: feste Datensätze direkt, dann Blöcke von 4.096 Slots für die Prozesslebensdauer. JederAtomStoragebesitzt seine Datensätze nach Schreibweise, veröffentlicht sie in diesem Index und lässt nur eigene oder feste Datensätze zu. Der Abbau zieht seine Slots zurück.- Atom-Terme pinnen nichts.
atom_spelling()leiht bis zum Abbau der Runtime; diese Funktion undatom_utf8()meldenexpired_context, nachdem die Runtime eines nicht festen Atoms abgebaut wurde. Ein Atom zwischen Runtimes zu bewegen heißt, seine Schreibweise im Ziel zu internieren, während die Quell-Runtime lebt. - Booleans sind die Atome
true/false. Die festen Atomefalse,true,undefined,okunderror(abi::v1::fixed_atom_spellings) haben in jeder Runtime die Wörter der Atomnummern 0–4, mit unsterblichen statischen Datensätzen, werden ohne Sperre gefunden und weder vonsize()noch gegenmax_atomsgezählt; erzeugter Code verwendet sie als Konstanten. Jedes andere Atomwort ist privat für die Runtime. - Zusammengesetzte Literale (Zeichenketten, Listen, Tupel und maps aus Literalen, Gleitkommazahlen, Bignums, konstante binaries) sind Modulliterale: zur Kompilierzeit kodiert, bei der Registrierung einmal pro Runtime in deren Literal-Heap dekodiert und gemeinsam genutzt (Literalbereiche); Ganzzahlliterale im kleinen Bereich sind Immediates.
- Andere Atomliterale werden aus dem Atom-Cache des Moduls geladen
(
ModuleDescriptor::atom_cache: die Identität der beanspruchenden Runtime, dann ein Wort pro Slot), den die erste Runtime füllt, die das Modul registriert; ein Kontext, dessenFrameState::runtimedem Anspruch entspricht, liest ihn ohne Aufruf, jeder andere ruftCLAUSE_atom_v3auf. Der Anspruch wird zurückgegeben, wenn der Code-Server der Runtime das Modul freigibt. - Runtime-C++ schreibt literale Atome als
"name"_atom(atom_names.hpp): Ein statischerCachedAtompro Schreibweise hält das zuletzt dafür internierte Wort, undAtomStorage::interngibt es nach einem sperrfreien Indexzugriff zurück, wenn diese Runtime es vergeben hat, und überspringt Validierung, geteilte Sperre und Schreibweisen-map. Ein Wort einer anderen oder abgebauten Runtime besteht diese Prüfung nicht und wird ersetzt, sodass mehrere Runtimes in einem Prozess korrekt bleiben (sie aktualisieren nur den Cache). Builtin-Helfer nehmenAtomName, eine Schreibweise mit oder ohne solchen Cache. - Internieren und Nachschlagen sind sicher gegenüber nebenläufigen Scheduler-Workern (Threads); eine Schreibweise behält ein Wort.
Ganzzahlen
- Werte, die in die 28/60-Bit-Nutzdaten des Ziels passen, sind Immediates; größere sind unveränderliche Zellen mit Vorzeichen und Betrag. Null und kleine Werte werden immer zu Immediates normalisiert. Literale werden nicht auf die Host-Breite verengt.
- Bei
-O2überspringen Operanden, die die Inferenz als klein beweist, ihre Tag-Prüfungen, und ein bewiesener Ergebnisbereich überspringt auch die Überlaufprüfung und den Rückfallpfad (Beweise). - Erzeugte
+,-,*versuchen bei zwei Immediates einen Inline-Schnellpfad, andernfalls rufen sie den Runtime-Dienst auf:+und-addieren oder subtrahieren die getaggten Wörter direkt (a + (b ^ 15)) und weichen aus, wenn das Wort überläuft, was genau dann geschieht, wenn das Ergebnis den kleinen Bereich verlässt;*rechnet in doppelter Breite mit expliziten Grenzen. Ebensoband,bor,bxor,bsr,div,remund Vergleiche zweier kleiner Ganzzahlen (Inline-Formen). - Der Dienst multipliziert ein Bignum (oder ein übergelaufenes kleines Produkt) mit einer kleinen Ganzzahl Limb für Limb, direkt aus der Zelle des Operanden in die neue; andere Bignum-Operationen verwenden einen Mehrfachpräzisionswert.
divschneidet in Richtung null ab;remübernimmt das Vorzeichen des Dividenden; bitweise Operationen verwenden unendliches Zweierkomplement; negative Verschiebungsweiten kehren die Richtung um; riesige Rechtsverschiebungen sättigen auf 0 oder -1.- Fehler: falsche Operanden und Divisor null →
badarith;abs/1→badarg. - Grenzen: wie in ERTS ein Betrag von höchstens
BIG_ARITY_MAXWörtern: 4.194.240 Bit auf 64-Bit-Zielen (65.535 Wörter), 4.194.272 auf 32-Bit (131.071 Wörter); der Dezimaltext folgt daraus (1.262.593 bzw. 1.262.602 Ziffern). Ein größeres arithmetisches Ergebnis löst in einem Rumpferror:system_limitaus (ValueOutcome::system_limit, ABI) und lässt einen Guard fehlschlagen; ein Ganzzahlsegment, das einen größeren Wert extrahiert, matcht nicht. Der Compiler weist ein Literal über 4.194.240 Bit ab (illegal integer, wie der Scanner von OTP) und ebenso ein konstantes Muster darüber (illegal pattern).
Gleitkommazahlen
- IEEE-binary64-Bits werden der Runtime als acht Bytes in Netzwerk-Bytereihenfolge übergeben; NaN und Unendlich werden abgewiesen. Kein Fast-Math.
+ - *bleiben bei zwei Ganzzahlen exakt; jeder Gleitkomma-Operand verwendet binary64./konvertiert immer beide. Nicht endliche Ergebnisse und Divisoren null →badarith.float/1rundet zur nächsten geraden Zahl;round/1rundet bei Gleichstand von null weg;trunc,floor,ceilgeben beliebige Ganzzahlen zurück. Ungültige Operanden →badarg.- Exakte Gleichheit unterscheidet
1von1.0und0.0von-0.0; der numerische Vergleich vergleicht den exakten ganzzahligen Teil und den Bruchteil der Gleitkommazahl, ohne die Ganzzahl je zu runden.min/maxgeben bei Gleichstand den ersten Operanden zurück.
Tupel, Listen, Zeichenketten
- Tupel: Stelligkeits-Header + Felder. Cons: Kopf- und Restwort.
{}und[]sind Immediates. Zeichenketten sind Listen von Codepoints. Listen haben keine Längengrenze außer dem Speicher (ein optionales Heap-Budget eingeschlossen). Tupel fassen bis zu 16.777.215 Elemente (MAX_TUPLE_ARITY,MAX_ARITYVALvon OTP); Konstruktoren melden ein größeres alsresource_limit; Builtins (make_tuple,list_to_tuple/1) lösenbadargaus. - Dienste:
hd,tl,length,tuple_size,size, einsbasierteselement.
Maps
- Unveränderliche Tabellen, sortiert nach exakter Schlüsselordnung. Doppelte
Schlüssel bei der Konstruktion behalten den letzten Wert. Die Konstruktion
sortiert die Schlüssel (O(n log n) Vergleiche; bereits aufsteigende Schlüssel
werden nur geprüft). Bis zu 32 Einträgen ist eine Map eine Heap-Zelle, und
eine Aktualisierung fügt per binärer Suche in eine Kopie ein; darüber ist sie
ein B+-Baum aus Heap-Zellen, aktualisiert durch Pfadkopie in O(log n)
(Map-Bäume). Keine Größen- oder Arbeitsgrenze
außer dem Speicher, wie in OTP. Ganzzahl- und Gleitkommaschlüssel
unterscheiden sich (auch
0.0gegenüber-0.0, auch verschachtelt). - Aktualisierungen mit
K := Verfordern den Schlüssel;K => Vfügt ein oder ersetzt. Aktualisierungen bereiten eine neue Tabelle vor und veröffentlichen sie einmal. - Fehler im Rumpf:
{badmap, M},{badkey, K}; Guards weisen stattdessen ab. - Dienste:
is_map,map_size,map_get,is_map_key, Konstruktion, Aktualisierung.
Bitstrings
- Gepackt, MSB zuerst, mit exakter Bitlänge und genulltem Padding. Bis zu 64 Bytes liegen inline in einem auf die Daten zugeschnittenen Heap-Binary; größere Werte verwenden einen gemeinsamen unveränderlichen Puffer außerhalb des Heaps, und Ausschnitte sind Sub-Binary-Zellen ihres Originals (Binaries außerhalb des Heaps). Es gibt keine Größengrenze außer einem optionalen Heap-Budget des Prozesses; Ganzzahlsegmente werden geschrieben, ohne eine Ganzzahl in der Breite des Segments aufzubauen.
- Die Konstruktion bereitet alle Segmente vor dem Veröffentlichen vor. Ganzzahlsegmente werden abgeschnitten; die native Bytereihenfolge stammt aus dem Datenlayout des Ziels. Ein Ganzzahlsegment eines kleinen Werts mit einer Breite von höchstens 64 Bit wird aus seinem Maschinenwort geschrieben (an einer Bytegrenze ganze Bytes auf einmal); andere nehmen den Pfad mit Mehrfachpräzision. Erzeugter Code baut kleine Konstruktionen konstanter Breite inline (Frames und Übergaben).
- Anhängen, wie bei den beschreibbaren Binaries von OTP: Eine Konstruktion,
die mit einem ganzen Binary beginnt (
<<Acc/binary, ...>>) und mehr als 64 Bytes umfasst, baut ihr Ergebnis in einem Puffer mit Reserveplatz (Größe auf eine Zweierpotenz aufgerundet, mindestens 256 Bytes), dessen Off-Heap-Zelle im erzeugenden Prozess die belegten Bits und die Kapazität vermerkt (null in jeder anderen Zelle, Kopien eingeschlossen). Ein späteres Anhängen, dessen Akkumulator-Ansicht genau dort endet, schreibt die neuen Bytes in den Reserveplatz, den keine Ansicht abdeckt, und gibt eine längere Ansicht desselben Puffers zurück: ein Sub-Binary der eigenen Off-Heap-Zelle des Puffers, das über die Bits dieser Zelle hinausreichen darf. Jeder andere Akkumulator (eine ältere Ansicht, die eines anderen Prozesses, eine nicht ausgerichtete, ein voller Puffer) wird einmal in einen neuen Puffer kopiert, der einen vollen mindestens verdoppelt. Andere Ansichten des Puffers ändern sich nie. Freigegebene Puffer werden wiederverwendet (Pufferwiederverwendung). - Gleitkommasegmente: Breiten 16/32/64; die Konstruktion darf Unendlich
kodieren, das Matching weist aber unendliche/NaN-Felder ab.
Gleitkomma-Matches der Breite null extrahieren
0.0. - UTF-8/16/32-Segmente validieren Skalare, Surrogate und Abschneidung.
- Das Matching rückt einen expliziten Bit-Cursor nur bei Erfolg vor;
:allals explizite Größe ist ungültig. - Dienste:
is_binary,is_bitstring,bit_size,byte_size(rundet auf),size(rundet ab),binary_part/2,3. Fehler →badarg.
Records
- Gewöhnliche Records werden zu Tupeln
{Tag, Fields...}expandiert. Deklarationen müssen vor der Verwendung stehen; Duplikate, unbekannte Felder, Vorwärts-/Selbstreferenzen und ungültige Wildcard-Felder sind Fehler. - Die Konstruktion wertet Felder in Deklarationsreihenfolge aus: expliziter
Wert, sonst
_ = V-Wildcard-Standardwert, sonst deklarierter Standardwert, sonstundefined. Jeder Standardwert wird pro Verwendung separat ausgewertet. - Muster prüfen Stelligkeit und Tag, dann nur die aufgeführten Felder.
#r.fist der einsbasierte Index (Tag an Position 1). - Feldzugriff prüft Stelligkeit und Tag; ein Fehlschlag ist
{badrecord, V}in Rümpfen und eine Abweisung in Guards. is_record(V, r)verwendet die deklarierte Stelligkeit.is_record/3braucht ein Atom-Tag und eine ganzzahlige Stelligkeit (nicht positiv → false; falsche Typen →badarg); ein Atom als drittes Argument ist die Abfrage für native Records und gibt false zurück. Guards erfordern literale Argumente.- Die Aktualisierung
Expr#r{f = V, ...}wertet die neuen Werte in Quelltextreihenfolge aus, dannExpr, prüft dann Stelligkeit und Tag ({badrecord, Value}bei Abweichung, auch fürExpr#r{}) und baut ein neues Tupel; die übrigen Felder werden kopiert._ = Vwird in Aktualisierungen abgewiesen; Aktualisierungen sind in Mustern und Guards unzulässig. record_info(fields | size, r)wird zur Kompilierzeit zur Liste der Feldnamen oder zur Tupelgröße expandiert. Beide Argumente müssen literale Atome sein undrein zuvor deklarierter Tupel-Record; in Guards ist es unzulässig, und ein lokalesrecord_info/2wird als bereits definiert abgewiesen.- Native Records: native Records (lokale, qualifizierte, importierte und anonyme Formen).
Pids und Referenzen
self/0 gibt die Pid des aufrufenden Prozesses zurück, make_ref/0 eine neue
Referenz; pid_to_list/1 und ref_to_list/1 geben ihren Text zurück.
- Eine Pid ist ein Immediate-Wort (niedrigste vier Bits
0x3), das die Nummer des Prozesses enthält. Nummern stammen aus einer prozessweiten Sequenz und werden nie wiederverwendet, sodass eine Runtime ein Pid-Wort nur zulässt, wenn sie diese Nummer vergeben hat: Ein gefälschtes Wort (nie vergeben) oder die Pid einer anderen Runtime istwrong_owner. Die Pid eines beendeten Prozesses bleibt ein gültiger Term, wie in OTP. 32-Bit-Ziele haben 2^28 Nummern pro Programmlauf, 64-Bit-Ziele 2^60; einen Prozess darüber hinaus zu erzeugen schlägt mitresource_limitfehl. - Ein Port ist ein Immediate-Wort (niedrigste vier Bits
0x7), das seine Nummer aus einer eigenen, nie wiederverwendeten Sequenz enthält, und wird wie eine Pid zugelassen; er wird als#Port<0.N>ausgegeben und ordnet sich zwischen Funs und Pids ein (Ports). - Eine Referenz ist eine Heap-Zelle (
reference: Header plus eine nicht verfolgte 64-Bit-Nummer), die wie jeder Heap-Term zugelassen wird: nur in ihrem eigenen Prozess, zwischen Prozessen als Wert kopiert. Auf einen unverwurzelten Host-Termdarf nach Bereinigung oder Prozessende nicht zugegriffen werden. Nummern stammen aus einem prozessweiten Zähler, sodass jede Referenz eines Programmlaufs eindeutig ist. - Eine Handle-Referenz (eine magic reference von OTP) ist eine Referenzzelle, die zusätzlich einen Puffer außerhalb des Heaps hält; sie ist für jeden Dienst dieselbe Referenz und teilt ihren Puffer mit jeder Kopie (Handle-Referenzen).
- Die Ausgabe folgt den lokalen Identitäten von OTP: eine Pid als
<0.N.S>(N die niedrigsten 28 Bits ihrer Nummer, S der Rest), eine Referenz als#Ref<0.A.B.C>(C die niedrigsten 18 Bits ihrer Nummer, B die nächsten 32, A der Rest), sowohl im~w- als auch im Display-Stil. Pids sind nach Nummer geordnet, Referenzen nach Nummer, sodass spätere Referenzen eines Programms nach früheren eingeordnet werden.
Vergleich und Ordnung
Iterativ ohne Arbeitsgrenze, wie in OTP: Nur der Speicher für ausstehende Paare
begrenzt einen Vergleich, Schlüsselsuchen in Maps eingeschlossen, und
identische Wörter sind ohne Durchlauf gleich. Byteausgerichtete Bitstrings
vergleichen ganze Bytes auf einmal. Ordnung: Zahlen < Atome < Referenzen <
Funs < Ports < Pids < Tupel < native Records < Maps < nil < Listen < Bitstrings
(zur Ordnung der Funs untereinander).
Atome werden nach UTF-8-Schreibweise verglichen (Codepoint-Reihenfolge); Tupel
nach Stelligkeit, dann Feldern; Maps nach Größe, dann Schlüsseln, dann Werten;
Bitstrings nach logischen Bits. Zahlen (kleine Ganzzahlen, Gleitkommazahlen,
Bignums nach Vorzeichen und Limbs), Atome (nach ihren Schreibweisen in der
Atomtabelle über den sperrfreien Nummernindex), lokale Referenzen (nach
Nummer), ganzbytige binaries sowie bis zu 8 Ebenen tief verschachtelte Tupel
und Listen daraus werden direkt aus ihren Wörtern und Zellen geordnet
(detail::word_order), ohne Zulassung als Term; jedes andere Paar, eine
Gleitkommazahl gegenüber einem Bignum gleicher Bitlänge oder eine kleine
Ganzzahl jenseits von 2^53 gegenüber einer Gleitkommazahl verwendet die
strukturelle Ordnung. =:= nimmt denselben Pfad (detail::word_exact): Paare,
die er unterschiedlich einordnet, sind ungleich, und ein Wort oder zwei
Referenzen, die er als gleich erkennt, sind gleich, sodass ein receive beim
Matchen der Referenz einer Antwort keinen Term zulässt. lists:sort/1 und
lists:usort/1 sortieren Elementwörter an Ort und Stelle: Listen nur kleiner
Ganzzahlen oder nur von Atomen nach Wort oder Schreibweise, andere mit einer
stabilen Sortierung nach Wortordnung.
Ausgabe
format_term (output.hpp)
stellt jeden zugelassenen Term in einem von zwei OTP-Stilen dar. Ganzzahlen,
Tupel (Records sind Tupel) und Verschachtelung sehen in beiden gleich aus.
~w (TermStyle::write) | erlang:display/1 (TermStyle::display) | |
|---|---|---|
| Atome | In Anführungszeichen, außer ein Latin-1-Kleinbuchstabe steht am Anfang und Namenszeichen (mit @) folgen; reservierte Wörter und maybe/else stehen in Anführungszeichen; jenseits von Latin-1 als \x{H} maskiert | In Anführungszeichen, außer ein Latin-1-Kleinbuchstabe steht am Anfang und alphanumerische Zeichen oder _ folgen; für reservierte Wörter und @ gilt keine Sonderregel; UTF-8 bleibt erhalten |
| Gleitkommazahlen | Kürzeste verlustfreie Darstellung im OTP-Layout: 0.1, 100.0, 1.0e16, 1.5e-7 | C %.6e: 1.500000e+00 |
| Listen | Elemente: [104,105], [1,2|3] | Eine flache Liste druckbarer Latin-1-Bytes wird als "hi" ausgegeben (rohe Bytes; nur \n und " maskiert) |
| Bitstrings | <<1,2,5:3>> | Ein druckbares ASCII-Binary wird als <<"hi">> ausgegeben, andere wie ~w |
| Maps | #{k => v,k2 => v2} | #{k=>v,k2=>v2} |
- Maps werden in Reihenfolge der Map-Schlüssel ausgegeben
(
maps:iterator(M, ordered), wie OTP~kw). Das Standard-~wvon OTP underlang:display/1folgen stattdessen dem internen Layout: Atomtabellen-Reihenfolge für Atom-Schlüssel kleiner Maps (sie variiert zwischen VM-Läufen) und Hash-Reihenfolge über 32 Schlüssel. Clause bildet diese Reihenfolge nicht nach. - Die Darstellung ist iterativ, daher ist die Tiefe nur durch den Term
begrenzt. Der Text ist standardmäßig auf 64 MiB begrenzt; wird das
überschritten (etwa durch einen vielfach geteilten Teilterm), schlägt sie mit
resource_limitfehl und liefert keinen Teiltext. - Goldens:
runtime_printingvergleicht beide Stile mit OTP (alle Korpusergebnisse plus selbst verfasste Grenzfälle); Display-Zeilen, deren OTP-Map-Reihenfolge intern ist, werden übersprungen (Fixtures).
Clause