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

Ausführungsmodell: Frames und Fortsetzungen

Wie erzeugte Erlang-Funktionen aufrufen, zurückkehren, sich unterbrechen und scheitern. Das Modell wurde nach dem Prototypvergleich in verglichene Alternativen gewählt; die Seite des erzeugten Codes in seiner ABI steht in Frames und Übergaben.

Entscheidung

Jeder Erlang-Prozess läuft auf seinem eigenen flachen Stack expliziter Frames, und erzeugter Code wechselt zwischen Funktionen nur durch garantierte Endübergaben (LLVM musttail). Ein Nicht-Endaufruf speichert die Fortsetzung (continuation) des Aufrufers in dessen Frame und springt zum Aufgerufenen; eine Rückkehr springt zu dieser Fortsetzung zurück. Der native Stack bleibt daher einen Aufruf tief über dem Scheduler, unabhängig von der Erlang-Rekursionstiefe, und ein Prozess kann bei jeder Übergabe anhalten und später auf einem beliebigen Thread fortfahren.

Prozesszustand

abi::v1::FrameState steht am Anfang jedes Kontexts, sodass erzeugter Code ihn inline liest und aktualisiert:

FeldBedeutung
stack, top, limitDer Stack-Block (wird beim Wachsen verschoben), sein erstes freies Wort und die Wörter, die ein Push erreichen darf, ohne zu wachsen oder die Stack-Grenze zu überschreiten
frameWort-Offset des aktuellen Frame-Headers
registers, liveDie 256 Argument-/Ergebnisregister; die ersten live Wörter sind bei einer Übergabe Wurzeln
reductionsVerbleibende Funktionseintritte in der Zeitscheibe
slow, failedBits, die Übergaben an die Dienste schicken (slow_collect, slow_profile); ungleich null, solange der Fehlerkanal einen Fehlschlag hält

heap_top/heap_limit, runtime und self dienen der Inline-Allokation, dem Laden von Atomen und self/0 (Frames und Übergaben). Ein unterbrochener Prozess vermerkt den Eintritt, an dem er fortfährt, in ProcessStack::resume_.

Frames

Ein Frame ist ein Header aus 4 Wörtern, gefolgt von den Slots der Funktion (BEAM-Y-Register): Term-Slots, die Wurzeln sind, dann Roh-Slots für ausgelagerte native Werte.

Header-WortBedeutung
previousOffset des Headers des Aufrufers (Frames verketten sich über Offsets, nie über Zeiger)
functionDer FrameDescriptor der Funktion
resumeFortsetzungsindex, über den der Rumpf verzweigt, wenn die Kontrolle hierher zurückkehrt
handlerHandler-Index; immer 0, da Fehlschläge durch die Aufrufer zurückkehren

Der Deskriptor <symbol>.frame nennt Modul, Funktion und Stelligkeit und hält den Code-Zeiger des Rumpfs und die Slot-Anzahlen (ABI). Header-Wörter sind keine Terme; ein Walker folgt previous und liest Slot-Anzahlen aus Deskriptoren. Ein Boden-Frame (bottom frame) im Besitz der Runtime liegt unter dem ersten Aufruf jedes Prozesses und jedes Host-Aufrufs: Die Rückkehr in ihn speichert das Ergebnis im ersten Register und beendet den nativen Aufruf.

Operationen

Jeder Aufruf pusht einen Frame; Blattfunktionen lassen ihn nicht aus, sodass jede Funktion ihren Eintrag im Stacktrace behält.

Sichtbarkeit der Wurzeln

Bei jeder Übergabe und jedem Safepoint sind die Wurzeln eines Prozesses: die Term-Slots aller Frames auf seinem Stack, x[0..live) sowie Nutzlast, Argumentliste und Stack-Term des Fehlerkanals (Wurzeln).

Werte überleben eine Übergabe nie in nativen Registern oder SSA-Werten, außer den Parametern der Übergabe selbst. Jeder Rumpf lädt seine Frame-Adresse nach dem Betreten neu aus stack + frame und liest lebende Werte aus Slots. Innerhalb einer Fortsetzung macht ein Dienst, der einen Frame pushen oder den Heap verschieben kann, jeden in SSA-Werten gehaltenen Slot-Zeiger und Heap-Zeiger ungültig; die Neulade-Regel für Speicherbereinigungen ist in Neulade-Regel festgelegt.

Der Stack-Block wächst durch Verdoppeln und Verschieben, solange die runtimeweite Grenze es erlaubt, schrumpft während der Laufzeit nie und ist vom Heap-Block getrennt. Er hat keine Grenze für die Anzahl der Frames; Stack-Wörter zählen zur Speichergrenze der Runtime.

Zielplattformen

musttail mit einer einheitlichen Code-Signatur wird von jeder erforderlichen Zielplattform bei O0 und O2 akzeptiert (Prototyp unten, clang 23.1.2):

ZielWortErgebnis
x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu64Endsprünge
i686-pc-windows-msvc, i686-unknown-linux-gnu32Endsprünge
aarch64-unknown-linux-gnu, arm64-apple-macosx14.064Endsprünge
armv7-unknown-linux-gnueabihf32Endsprünge

Das Backend meldet einen Fehler, wenn es einen musttail-Aufruf nicht einhalten kann, daher ist eine erfolgreiche Kompilierung die Garantie. Rückfallebene für eine künftige Zielplattform, die ihn ablehnt: ein Trampolin, bei dem jeder Code den nächsten Code-Zeiger an die Scheduler-Schleife zurückgibt, statt zu springen; Frames, Wurzeln und Fortsetzungsindizes bleiben unverändert. Keine erforderliche Zielplattform braucht es.

Implementierung

Verglichene Alternativen

tests/prototypes/execution_model senkt dieselben Erlang-Funktionen (sum/1 Rumpfrekursion, loop/2 Endrekursion, fail/1 löst in Tiefe N aus, catcher/1 fängt es ab) auf drei Arten von Hand ab und kompiliert sie für jede obige Zielplattform bei O0 und O2 (python tests/prototypes/execution_model/run.py). Unter Windows x64 bei O2 führte das gewählte Modell eine Rumpfrekursion der Tiefe 1M in 17 ms mit 5 Wörtern pro Frame aus und 10M Endaufrufe mit konstantem Stack; seine Trampolin-Variante bestand dieselben Läufe. Verworfen:

Akzeptierte Kosten: ein indirekter Sprung pro Rückkehr plus ein Switch-Dispatch; über Aufrufe hinweg lebende Werte werden aus Slots neu geladen; Backtraces nativer Debugger zeigen nur die aktuelle Funktion, während Erlang-Stacktraces aus der Frame-Kette stammen (Debugging).