Clause
← Gesamte Dokumentation

Das englische Original wurde seit dieser Übersetzung geändert, daher können Teile davon veraltet sein. Auf Englisch lesen

Übersetzt aus dem englischen Original · 1e3a39c8-dirty · 2026-10-26

ABI des erzeugten Codes (Revision 21)

Privater Vertrag zwischen der Compilerausgabe und der Runtime. Er ist projektinternes C++23, nicht BEAM-kompatibel und kein allgemeines FFI. Objekte und Runtime müssen aus demselben Build stammen; ältere Deskriptorrevisionen werden vor der Verwendung zurückgewiesen.

Header: v1.hpp (Term-, Kontext- und Funktionstypen), term.hpp (Codec für Immediate-Ganzzahlen), status.hpp, builtins.hpp, startup.hpp (Programmstart).

Terme

Ein Term ist ein vorzeichenloses Wort in Zielzeigerbreite (32 oder 64 Bits). Layouts leiten sich vom konfigurierten LLVM-Ziel ab, sodass zielübergreifender Code die Breiten des Ziels verwendet.

Funktionen und Symbole

Eintrittssignatur (native C-Aufrufkonvention, keine C-Linkage erforderlich):

TermWord function(ProcessContext *context, const TermWord *arguments);

Die Argumente sind ein geliehenes, wortausgerichtetes Array in Quelltextreihenfolge (null bei Stelligkeit 0). Der Kontext ist lebendig und wird unverändert durch Aufrufe gereicht. Ein zurückgegebenes Wort ist erst nach Prüfung des Fehlerkanals verwendbar.

Symbole: clausev1_<hex module>_<hex function>_<arity>, kleingeschriebenes Hex der UTF-8-Bytes, kanonische dezimale Stelligkeit; umkehrbar und unabhängig vom Host. Exportierte Eintrittspunkte sind extern, andere intern.

Modulregistrierung

Jedes Modul erzeugt clausev1_<hex module>__0.descriptor und .register. Der Deskriptor enthält ABI-Version, Termbreite, Exporttabelle (Name, Stelligkeit, Host-Eintrittspunkt und den FrameDescriptor, den dynamische Aufrufe betreten), Atomschreibweisen (UTF-8-Paare aus Zeiger und Größe), Deskriptoren für native Records (Atomplätze für Modul, Name und Felder, Export-Flag; native Records) in einer externen Tabelle <prefix>.records, auf die andere Module eines Batches verweisen, Fun-Deskriptoren in einer privaten Tabelle <prefix>.funs (Funs), die kodierten Modulliterale und den Literal-Cache (Bereiche), die beschreibbaren Atom- und Fun-Caches, die erzeugter Code inline liest, und die code_convention seiner Rümpfe. Die Registrierung ruft CLAUSE_register_module_v4(Runtime*) auf, das Version/Breite und alle Exporte validiert, Atome interniert, eine eingefrorene Registry aufbaut und sie zusammen mit dem Code-Image in einer Transaktion veröffentlicht. Doppelte Module ersetzen nie Code; jeder Fehlschlag veröffentlicht nichts (bereits internierte Atome bleiben in der begrenzten Tabelle).

Fehlerkanal (Revision 2)

Fehler werden nicht in Termbits kodiert. Nach jedem erzeugten Aufruf, der kein Endaufruf ist, prüft der Aufrufer den Kanal, bevor er das Ergebnis verwendet oder das nächste Argument auswertet: Er liest das Wort failed des FrameState des Kontexts, das die Runtime gleich CLAUSE_call_failed_v2(context) hält. Bei einem Fehlschlag liefert der Aufgerufene ein ungültiges Null-Wort.

ErgebnisTransport
Musterabweichung, Ablehnung durch GuardFortsetzung beim nächsten Kandidaten; Kanal unberührt
Erschöpfte Klauselnerror:function_clause; error:{case_clause, Value} mit eigenen Nutzdaten bei einem case; error:if_clause; error:{try_clause, Value} bei den of-Klauseln eines try; error:{else_clause, Value} bei den else-Klauseln eines maybe
Fehlgeschlagener Match im Rumpferror:{badmatch, Value} mit eigenen Nutzdaten
Comprehensionserror:{bad_generator, Tail}, error:{bad_filter, Value}, error:{bad_generators, Inputs} (ErrorReason 16-18); die Ablehnung durch einen strikten Generator ist {badmatch, Element}
Record-Zugriff, ungültige Argumente, Arithmetik, Mapsbadrecord, badarg, badarith, badmap/badkey
Feld eines nativen Records fehltErrorReason::badfield (20), Nutzdaten {{Module, Name}, Field}
Aufruf eines Werts (F(Args))ErrorReason::badfun (22, Nutzdaten der Wert), badarity (23, Nutzdaten {Fun, Args}), undef (24), verzeichnet von CLAUSE_apply_v1 (Funs)
Dynamische Aufrufe (M:F(Args), apply/2,3, fun M:F/A mit Variablen)badarg bei einem Modul oder einer Funktion, die kein Atom ist, einer unechten Argumentliste oder einer ungültigen Stelligkeit; undef, wenn kein Modul des Programms die Funktion exportiert; badfun/badarity wie oben (Funs)
Externe native Konstruktion ohne WertErrorReason::novalue (21), Nutzdaten {{Module, Name}, Field}
Ganzzahlergebnis über der GrößengrenzeService-Ergebnis ValueOutcome::system_limit (3): ein Guard lehnt ab, ein Rumpf löst error:system_limit aus (ErrorReason 19)
Ungültiger linker Operand eines Kurzschlussoperators{badarg, Value}
Infrastruktur (OOM, Limits, Besitz, intern)CallError::runtime_failure mit exaktem Status
erlang:error/1,2,3, exit/1, throw/1, erlang:raise/3raised_error/raised_exit/raised_throw: Klasse aus der ID, die eigenen Nutzdaten sind der ganze Grund
erlang:halt/0,1CallError::halted mit halt_status (und Slogan)

Gründe sind typisierte IDs, die von CLAUSE_raise_v2 verzeichnet werden; die drei raised_*-IDs wählen die Klasse exit oder throw (sonst error) und tragen einen beliebigen Term als Grund. error/2,3 lösen über CLAUSE_error_v1(context, reason, args) aus, das zusätzlich eine Liste args für den obersten Stack-Frame behält. Der erste Fehlschlag gewinnt; verschachtelte Aufrufe teilen den Kanal. GeneratedInvocation ist der Host-Gültigkeitsbereich: Er prüft ausstehende Fehlschläge vor dem Eintritt und nach der Rückkehr, kopiert Ergebnis oder Fehler und leert erst beim äußersten Austritt (auch bei C++-Ausnahmen). Keine Ausnahme überquert erzeugte Eintrittspunkte. Aufrufer roher Eintrittspunkte müssen eine GeneratedInvocation öffnen; normale Hosts verwenden ResolvedFunction::call.

catch Expr leitet jede Fehlerprüfung und jedes Auslösen innerhalb von Expr an einen Handler-Block um, der CLAUSE_catch_v1(context, slot) aufruft. Bei einer ausstehenden Erlang-Ausnahme schreibt er den catch-Wert in den Wurzelplatz und leert den Kanal: den geworfenen Term, {'EXIT', Reason} bei einem exit oder {'EXIT', {Reason, []}} bei einem error (typisierte Gründe werden zu ihren OTP-Termen wie {badmatch, V}; der Stack ist unten beschrieben). Halts und Infrastrukturfehler bleiben ausstehend, und die eigene Prüfung des Handlers geht weiter zum umschließenden Handler oder zum Funktionsaustritt. Innerhalb von Expr gemachte Bindungen sind danach unsicher, daher führt die Zusammenführung nur den Wert zusammen.

try Body of ... catch ... end schützt auf dieselbe Weise nur Body. Sein Handler ruft CLAUSE_exception_v2(context, class_slot, reason_slot, stack_slot) auf, das das Klassenatom (error, exit oder throw), den Grund und den Stacktrace-Term in Wurzelplätze schreibt und den Kanal leert (Halts und Infrastrukturfehler bleiben ausstehend wie bei catch). catch-Klauseln matchen dann Class:Reason mit gewöhnlichen Mustern und Guards; eine weggelassene Klasse matcht throw, und eine benannte Stack-Variable bindet den Stack-Term. Wenn keine passt, verzeichnet CLAUSE_reraise_v2(context, class, reason, stack) die Ausnahme erneut mit einem raised_*-Grund und demselben Stack, die sich genau wie das Original meldet und fangen lässt. of-Klauseln wählen anhand des Rumpfwerts aus und lösen {try_clause, Value} aus (ErrorReason::try_clause = 14); Ausnahmen innerhalb von of-Klauseln und Handlern gehen an den umschließenden Handler.

maybe braucht keinen Service: Jedes ?= ist ein gewöhnlicher Match, dessen Abweichungskante den Rumpf zum Austritt des maybe mit dem nicht gematchten (bereits verwurzelten) Wert verlässt. Ohne else ist dieser Wert das Ergebnis; andernfalls wählen else-Klauseln anhand seiner aus wie case-Klauseln und lösen {else_clause, Value} (ErrorReason::else_clause = 15) aus, wenn keine passt.

Comprehensions verwenden die bestehenden Services mit einigen Operationen: Die angesammelten Elemente werden von der Konstruktion reverse umgekehrt (ContainerConstruction::reverse = 2, Werte {List, Tail}), die das Ergebnis in einer einzigen Heap-Reservierung baut; Listengeneratoren testen und lesen Cons-Zellen inline; eine Binary-Comprehension fügt ihre Teile mit BitOperation::concat ({List}) zusammen, und eine Map-Comprehension baut ihre Map mit MapOperation::from_list ({Pairs}, spätere Schlüssel gewinnen). Map-Generatoren holen einmal MapOperation::entries (11, {Map}): eine Zelle mit den Schlüsseln und Werten an den Wörtern 1 + 2i und 2 + 2i in kanonischer Schlüsselreihenfolge (eine klassische Map selbst oder eine tupelförmige Kopie der Einträge einer Off-Heap-Map, die nur erzeugter Code liest), und lesen jeden Eintrag inline; key_at/value_at ({Map, Position}) bleiben für andere Aufrufer. Die verbleibende Eingabe eines Zips wird mit MapOperation::iterator gezeigt, der Kette {K, V, Next} von OTP, die in none endet. MapOperation::update_owned (10) nimmt die Operanden von update für eine Map-Aktualisierung, deren Quelle laut Nachweis des Compilers danach nicht mehr gelesen wird: Eine Off-Heap-Quell-Map wird an Ort und Stelle aktualisiert und ihr Stub als verbraucht (leer) zurückgelassen. Bitstring-Generatoren verwenden die gewöhnliche Musterextraktion plus ein abschließendes binary/all-Segment für den Rest.

try ... after A end fügt einen zweiten Schutz um den Rumpf und alle of- und catch-Klauseln hinzu. Auf dem normalen Pfad läuft A, nachdem der ausgewählte Wert verwurzelt ist, und sein Wert wird verworfen. Der after-Handler nimmt die Ausnahme mit CLAUSE_exception_v2 entgegen, führt eine zweite Kopie von A aus und löst mit CLAUSE_reraise_v2 erneut aus; eine Ausnahme oder ein Fehlschlag innerhalb von A verlässt stattdessen über den umschließenden Handler und ersetzt das Original. Halts und Infrastrukturfehler überspringen A. Wurzelplätze gehören zum Funktions-Frame, sodass jeder Pfad sie beim Funktionsaustritt freigibt.

Stacktraces

Jeder Wurzel-Frame benennt seine erzeugte Funktion mit einem privaten abi::v1::FrameDescriptor (Moduldeskriptor, Atomplätze für Modul- und Funktionsname, Stelligkeit). Wenn eine Erlang-Ausnahme verzeichnet wird, kopiert der Kanal die innersten 8 benannten Frames (BEAMs Standard-backtrace_depth); der Term [{Module, Function, Arity, []}, ...] wird erst gebaut, wenn ein Handler, catch oder ein Bericht danach fragt. Der oberste Frame zeigt statt der Stelligkeit die Argumentliste von error/2,3, wenn dieses Argument eine Liste ist.

erlang:raise(Class, Reason, Stack) (CLAUSE_reraise_v2) akzeptiert die Stacks, die BEAM akzeptiert: eine echte Liste aus {M, F, A} (ergänzt um eine []-Ortsangabe) oder {M, F, A, Location} mit Atomen M, F und einer Liste Location, gekürzt auf 8 Einträge; der Stack wird dann wie angegeben behalten, und Frames werden nicht mehr erfasst. Eine ungültige Klasse oder ein ungültiger Stack verzeichnet nichts, und der Aufruf ergibt badarg, wie in OTP. erlang:get_stacktrace/0 wird mit dem Lint-Text „removed“ von OTP 29 zurückgewiesen.

Unterschiede zu OTP, alle nur im Stack-Term sichtbar:

Frames und Übergaben

Erzeugte Funktionen laufen auf expliziten Frames (Ausführungsmodell; Services in frames.hpp). Jede Funktion hat einen Deskriptor <symbol>.frame (FrameDescriptor: Moduldeskriptor, Atomplätze für Modul und Funktion, Stelligkeit, Rumpfcode, Anzahl der Plätze, Anzahl der Termplätze; extern bei exportierten Funktionen) und einen internen <symbol>.body vom Typ abi::v1::Code, void(void *context, TermWord x0, ...) mit abi::v1::register_arguments Term-Parametern; mit einer von Clang gebauten Runtime auf x86-64 und AArch64 verwenden die Rümpfe und jede Übergabe die Konvention preserve_none, wie es abi::v1::Code dort tut: Rümpfe kehren nie normal zurück und sichern daher keine callee-gesicherten Register; die Registrierung weist ein Modul zurück, dessen code_convention (0 keine, 1 C, 2 preserve_none) nicht die der Runtime ist. Ein exportiertes <symbol> behält die Signatur TermWord(Context *, const TermWord *) als Host-Eintrittspunkt, der CLAUSE_invoke_v1(context, frame, arguments) aufruft.

Runtime-Services

Erzeugter Code ruft geprüfte C++-Services auf: CLAUSE_exact_v1 (exakte Gleichheit), CLAUSE_immediate_v1 (Prädikate/Abfragen auf Immediates), CLAUSE_construct_v1, CLAUSE_inspect_v1 (für andere Nutzer; erzeugter Code testet und liest Tupel- und Cons-Zellen inline), CLAUSE_integer_v1, CLAUSE_float_v1, CLAUSE_map_v1, CLAUSE_bits_v1, CLAUSE_record_v1 (Erzeugen/Lesen/Aktualisieren/Matchen/Testen nativer Records unter einem RecordCheck; Ergebnisse bad_record, bad_field, no_match), CLAUSE_make_fun_v1 (baut eine Fun aus einem FunDescriptor). Jeder liefert Erfolg, semantischen Fehler (badarg/badarith/...) oder Infrastrukturfehler und schreibt Ausgaben nur bei Erfolg. CLAUSE_display_v1 (output.hpp) gibt eine Zeile von erlang:display/1 aus und ergibt true; er hat keinen semantischen Fehler. CLAUSE_halt_v1 hat nie Erfolg: Er verzeichnet eine Halt-Anforderung (CallError::halted mit dem Exit-Status) oder badarg, sodass der Aufrufer abwickelt. Linker-Schreibweisen folgen dem Itanium- oder Microsoft-C++-Mangling des Ziels.

Inline-Formen (codegen/lowering_inline.cpp): Typtests (is_atom, is_integer, is_number, is_boolean, is_tuple, is_list, is_bitstring, is_float, is_map, is_pid, is_port, is_reference, is_function/1, natives is_record/1, is_binary/1 über das Bitlängenwort) lesen Tags und nach einem Box-Tag-Test die Header-Art (abi::v1::HeaderKind); sie rufen nie einen Service auf. Vergleiche zweier Immediates (==, /=) oder zweier kleiner Ganzzahlen (<, =<, >, >=), element/2, tuple_size/1, bit_size/1 und byte_size/1 (aus dem Bitlängenwort, solange die Größe eine kleine Ganzzahl ist), hd/1, tl/1, die booleschen Operatoren, band, bor, bxor, bsr, bsl (solange das Ergebnis klein bleibt) und kleines div/rem laufen inline; self/0 liest FrameState::self. Sie laufen inline, wo ihre Operanden es erlauben, und rufen sonst CLAUSE_immediate_v1 auf, das jeden Fehler- und Bignum-Fall behält. setelement/3 mit kleinem Index innerhalb eines geboxten Tupels kopiert das Tupel in die freien Wörter des Heap-Blocks und ersetzt den Platz inline; andernfalls oder ohne Platz betritt es das Bridge-Builtin.

CLAUSE_builtin_frame_v1(context, builtin) (builtins.hpp) liefert den FrameDescriptor des Produktions-Builtins mit Index builtin in abi::v1::bridge_builtins (nur anhängend); erzeugter Code betritt ihn wie eine Funktion mit den Argumenten in den Registern, und Fehler und Fehlschläge gehen an den geprüften Kanal (Builtins). Ein FrameDescriptor mit leerem Rumpf ist ein Builtin: Sein Betreten führt das Builtin auf den Registern aus und kehrt zum Aufrufer zurück oder suspendiert den Prozess an der Fortsetzung des Builtins, wenn es getrappt hat (Portionen).

Ein direktes Builtin (BuiltinCall::direct: reine Umwandlungen wie integer_to_list/1, atom_to_list/1, list_to_tuple/1, setelement/3, phash2/1) trappt nie, wartet nie und wirkt nie auf einen anderen Prozess. Erzeugter Code ruft es mit CLAUSE_builtin_call_v1(context, builtin, arguments) auf, das seinen Rumpf sofort ausführt und das Ergebnis zurückgibt, ohne Frame-Übergabe, ohne Fortsetzungspunkt und ohne Safepoint beim Eintritt; es verbraucht eine Reduktion, und ein Fehlschlag geht wie bei einem Service an den geprüften Kanal. Ein schnelles Builtin (BuiltinCall::quick: length/1, binary_to_list/1, list_to_binary/1, iolist_to_binary/1) wird zunächst ebenso aufgerufen; sein begrenzter Schnellpfad gibt bei Ablehnung 0 zurück (längere Eingaben, andere Formen, jeder Fehler und immer bei aufgezeichneten Aufrufen), und der erzeugte Code betritt dann wie bei jedem Bridge-Builtin dessen Frame.

abi::v1::dispatch_builtin ruft vom Host registrierte Builtins über Modul-/Funktionsbytes, Argumentarray und Stelligkeit auf und liefert einen Status; Ausgaben werden nur bei Erfolg geschrieben.

StatusWertBedeutung
ok0Erfolg
not_implemented1Bekanntes zurückgestelltes Feature, einmal gemeldet
invalid_argument2Ungültige Eingabe
diagnostic_failure3Zustellung des Berichts gescheitert
out_of_memory4Allokation gescheitert, Zustand zurückgerollt
busy5Lebende Kontexte oder laufender Dispatch
wrong_owner6Objekt gehört zu einer anderen Runtime bzw. einem anderen Kontext
resource_limit7Obergrenze oder Budget erschöpft
stopped8Besitzer heruntergefahren
abi_mismatch9Version oder Termbreite weicht ab
internal_error10Unerwarteter Fehlschlag abgefangen
unknown_builtin11Signatur nicht registriert oder katalogisiert
erlang_error12Strukturierter Erlang-Fehler verzeichnet
output_failure13Die Standardausgabe hat einen Schreibvorgang abgelehnt

Revisionen

RevisionÄnderung
1Erste Deskriptoren und Eintrittspunkte
2Geprüfter Fehlerkanal
3Atomschreibweisen und -plätze in Deskriptoren
4Verpflichtende erzeugte Wurzel-Gültigkeitsbereiche
5Explizite Prozess-Frames und Übergaben
6Deskriptoren für native Records in Moduldeskriptoren, CLAUSE_record_v1
7Fun-Deskriptoren in Moduldeskriptoren, CLAUSE_make_fun_v1, CLAUSE_apply_v1
8Exportdeskriptoren benennen ihren FrameDescriptor; Services für dynamische Aufrufe CLAUSE_call_v1, CLAUSE_apply_list_v1, CLAUSE_call_list_v1, CLAUSE_make_external_fun_v1
9Jeder Kontext beginnt mit abi::v1::FrameState; Inline-Übergaben
10Führende X-Register als Code-Parameter übergeben
11Feste Wörter für false und true
12Inline-Heap-Felder heap_top/heap_limit in FrameState
13Feste undefined, ok, error; ModuleDescriptor::atom_cache und FrameState::runtime
14Literalbereiche der Module: ModuleDescriptor::literals, literal_words
15FrameState::self, das Pid-Wort des Kontexts
16Code verwendet preserve_none auf x86-64 und AArch64 mit einer von Clang gebauten Runtime; ModuleDescriptor::code_convention
17Sechs X-Register als Code-Parameter
18sub_binary-Zellen (HeaderKind::sub_binary, sub_*_word) und Inline-Extraktion von Rest
19End- und Grenzwörter für Anhänge in refc_binary-Zellen (refc_append_*_word, 8 Nutzdatenwörter) und Inline-Anhänge
20MapOperation::entries und Inline-Map-Generatoren
21ModuleDescriptor::fun_cache und Inline-Closures
22Direkte Builtins: BuiltinName::direct, CLAUSE_builtin_call_v1
23Schnelle Builtins: BuiltinName::call (BuiltinCall), 0 von CLAUSE_builtin_call_v1 lehnt ab
24RecordDescriptor::definition (die Feldliste als Bytes im External Term Format für records:get_definition/2) und ModuleDescriptor::features (Atomplätze der aktivierten Features für erl_features:used/1)
25Kodierte Modulliterale und der Literal-Heap: ModuleDescriptor::literals enthält kodierte Bytes (literal_bytes), literal_cache, literal_count; CLAUSE_literal_v1