Clause
← Gesamte Dokumentation

Übersetzt aus dem englischen Original · 06042fa · 2026-10-09 · Auf Englisch lesen

ABI des erzeugten Codes (Revision 4)

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-/ 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, und fun-Deskriptoren in einer privaten Tabelle <prefix>.funs (Funs). 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 CLAUSE_call_failed_v2(context), bevor er das Ergebnis verwendet oder das nächste Argument auswertet. 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 native 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, der 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}); 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 lesen MapOperation::key_at/value_at ({Map, Position} in kanonischer Schlüsselreihenfolge) und zeigen die verbleibende Eingabe eines Zips mit MapOperation::iterator, OTPs Kette {K, V, Next}, die in none endet. 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, step 19; 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 void(void *context). 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, CLAUSE_integer_v1, CLAUSE_float_v1, CLAUSE_map_v1, CLAUSE_bits_v1, CLAUSE_record_v1 (Erzeugen/Lesen/Aktualisieren/Matchen/Testen von native records unter einem RecordCheck; Ergebnisse bad_record, bad_field, no_match), CLAUSE_make_fun_v1 (baut einen 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.

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).

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