Just-in-Time-Codegenerierung in WebAssembly

Just-in-Time (JIT)-Codegenerierung ist eine wichtige Taktik bei der Implementierung einer Programmiersprache. Die Generierung von Laufzeitcode ermöglicht es einem Programm, sich auf die spezifischen Daten zu spezialisieren, auf denen es ausgeführt wird. Bei einem Programm, das eine Programmiersprache implementiert, betrifft diese Spezialisierung das auszuführende Programm und möglicherweise die vom Programm verwendeten Daten.

In der Regel funktioniert es so, dass das Programm Bytes für den Befehlssatz der Maschine generiert, auf der es läuft, und dann die Kontrolle an diese Befehle übergibt.

Normalerweise muss das Programm seinen generierten Code in einem Speicher ablegen, der speziell als ausführbar gekennzeichnet ist. Diese Funktion fehlt jedoch in WebAssembly. Wie erfolgt dann eine Just-in-Time-Kompilierung in WebAssembly?

Webassembly als Harvard-Architektur

In einer von Neumman-Maschine, wie derjenigen, über die Sie dies wahrscheinlich lesen, teilen sich Code und Daten einen Adressraum. Es gibt nur einen Zeigertyp, und er kann auf alles zeigen: Bytes, die die sin-Funktion implementieren, die Zahl 42, Zeichen in Cookies oder irgendetwas. WebAssembly unterscheidet sich darin, dass sein Code zur Laufzeit nicht adressierbar ist. Funktionen in einem WebAssembly-Modul werden fortlaufend bei 0 beginnend nummeriert, und die WebAssembly-Aufrufanweisung nimmt den Aufgerufenen als unmittelbaren Parameter.

Um also einem WebAssembly-Programm Code hinzuzufügen, müssen Sie das Programm irgendwie um weitere Funktionen erweitern. Angenommen, wir machen dies irgendwie möglich - dass Ihr WebAssembly-Modul, das N Funktionen hatte, jetzt N + 1 Funktionen hat und die Funktion N die neue ist, die von Ihrem Programm generiert wird. Wie würden wir es nennen? Da call-Anweisungen den Angerufenen fest codieren, werden vorhandene Funktionen 0 bis N-1 ihn nicht aufrufen.

Hier ist die Antwort call_indirect. Zur Erinnerung: Diese Anweisung nimmt den Aufgerufenen als Operanden und nicht als unmittelbaren Parameter, wodurch sie die zur Laufzeit aufgerufene Funktion auswählen kann. Der aufgerufene Operand ist ein Index in eine Tabelle von Funktionen. Herkömmlicherweise wird Tabelle 0 als indirekte Funktionstabelle bezeichnet, weil sie einen Eintrag für jede Funktion enthält, die wahrscheinlich das Ziel eines indirekten Aufrufs ist.

Vor diesem Hintergrund hat unser Problem zwei Teile: (1) wie man ein WebAssembly-Modul um eine neue Funktion erweitert und (2) wie man das ursprüngliche Modul dazu bringt, den neuen Code aufzurufen.

spätes Binden von Hilfs-Web-Assembly-Modulen

Die Schlüsselidee dabei ist, dass das Hauptprogramm zum Hinzufügen von Code ein neues WebAssembly-Modul generieren muss, das diesen Code enthält. Dann führen wir eine Bindungsphase durch, um diesen neuen Code zum Leben zu erwecken und verfügbar zu machen.

Systemlinker wie ld erfordern normalerweise einen vollständigen Satz von Symbolen und Verschiebungen, um Referenzen zwischen Archiven aufzulösen. Wenn wir jedoch einen späten Link von JIT-generiertem Code ausführen, können wir eine Abkürzung nehmen: Das Hauptprogramm kann Speicheradressen direkt in den von ihm generierten Code einbetten. Daher würde das generierte Modul Speicher aus dem Hauptmodul importieren. Alle Verweise aus dem generierten Code auf das Hauptmodul können so direkt eingebunden werden.

Das generierte Modul würde auch die Tabelle der indirekten Funktionen aus dem Hauptmodul importieren. (Wir würden sicherstellen, dass das Hauptmodul seinen Speicher und seine indirekte Funktionstabelle über die Toolchain exportiert.) Wenn das Hauptmodul das generierte Modul erstellt, bettet es auch eine spezielle Patch-Funktion in das generierte Modul ein. Diese Funktion würde die neuen Funktionen zur indirekten Funktionstabelle des Hauptmoduls hinzufügen und alle Bewegungen im Speicher des Hauptmoduls ausführen. Alle Hauptmodulverweise auf generierte Funktionen werden über die Patch-Funktion installiert.

Wir planen zwei Late-Link-Implementierungen, aber beide teilen den grundlegenden Mechanismus eines generierten WebAssembly-Moduls mit einer Patch-Funktion.

dynamische Bindung über Laufzeit

Eine Implementierung eines Linkers dient dazu, dass das Hauptmodul die Laufzeit veranlasst, dynamisch ein neues WebAssembly-Modul zu instanziieren. Die Laufzeit würde den Speicher des Hauptmoduls und die indirekte Funktionstabelle als Importe bereitstellen, wenn das generierte Modul instanziiert wird.

Der Vorteil dynamischer Verknüpfungen besteht darin, dass ein WebAssembly-Modul live aktualisiert werden kann, ohne dass eine erneute Instanziierung oder spezielle Laufzeit-Checkpointing-Unterstützung erforderlich ist.< /p>

Im Kontext des Webs kann die JIT-Kompilierung durch das betreffende WebAssembly-Modul ausgelöst werden, indem die JavaScript-Funktionalität aufgerufen wird, oder wir können ein Pull-basiertes Modell verwenden, um dem JavaScript-Host zu ermöglichen, die WebAssembly-Instanz nach ausstehenden JIT abzufragen Code.

Für WASI-Bereitstellungen benötigen Sie Host-Funktionalität. Entweder Sie importieren ein Modul, das Runtime-JIT-Fähigkeiten bereitstellt...

Just-in-Time-Codegenerierung in WebAssembly

Just-in-Time (JIT)-Codegenerierung ist eine wichtige Taktik bei der Implementierung einer Programmiersprache. Die Generierung von Laufzeitcode ermöglicht es einem Programm, sich auf die spezifischen Daten zu spezialisieren, auf denen es ausgeführt wird. Bei einem Programm, das eine Programmiersprache implementiert, betrifft diese Spezialisierung das auszuführende Programm und möglicherweise die vom Programm verwendeten Daten.

In der Regel funktioniert es so, dass das Programm Bytes für den Befehlssatz der Maschine generiert, auf der es läuft, und dann die Kontrolle an diese Befehle übergibt.

Normalerweise muss das Programm seinen generierten Code in einem Speicher ablegen, der speziell als ausführbar gekennzeichnet ist. Diese Funktion fehlt jedoch in WebAssembly. Wie erfolgt dann eine Just-in-Time-Kompilierung in WebAssembly?

Webassembly als Harvard-Architektur

In einer von Neumman-Maschine, wie derjenigen, über die Sie dies wahrscheinlich lesen, teilen sich Code und Daten einen Adressraum. Es gibt nur einen Zeigertyp, und er kann auf alles zeigen: Bytes, die die sin-Funktion implementieren, die Zahl 42, Zeichen in Cookies oder irgendetwas. WebAssembly unterscheidet sich darin, dass sein Code zur Laufzeit nicht adressierbar ist. Funktionen in einem WebAssembly-Modul werden fortlaufend bei 0 beginnend nummeriert, und die WebAssembly-Aufrufanweisung nimmt den Aufgerufenen als unmittelbaren Parameter.

Um also einem WebAssembly-Programm Code hinzuzufügen, müssen Sie das Programm irgendwie um weitere Funktionen erweitern. Angenommen, wir machen dies irgendwie möglich - dass Ihr WebAssembly-Modul, das N Funktionen hatte, jetzt N + 1 Funktionen hat und die Funktion N die neue ist, die von Ihrem Programm generiert wird. Wie würden wir es nennen? Da call-Anweisungen den Angerufenen fest codieren, werden vorhandene Funktionen 0 bis N-1 ihn nicht aufrufen.

Hier ist die Antwort call_indirect. Zur Erinnerung: Diese Anweisung nimmt den Aufgerufenen als Operanden und nicht als unmittelbaren Parameter, wodurch sie die zur Laufzeit aufgerufene Funktion auswählen kann. Der aufgerufene Operand ist ein Index in eine Tabelle von Funktionen. Herkömmlicherweise wird Tabelle 0 als indirekte Funktionstabelle bezeichnet, weil sie einen Eintrag für jede Funktion enthält, die wahrscheinlich das Ziel eines indirekten Aufrufs ist.

Vor diesem Hintergrund hat unser Problem zwei Teile: (1) wie man ein WebAssembly-Modul um eine neue Funktion erweitert und (2) wie man das ursprüngliche Modul dazu bringt, den neuen Code aufzurufen.

spätes Binden von Hilfs-Web-Assembly-Modulen

Die Schlüsselidee dabei ist, dass das Hauptprogramm zum Hinzufügen von Code ein neues WebAssembly-Modul generieren muss, das diesen Code enthält. Dann führen wir eine Bindungsphase durch, um diesen neuen Code zum Leben zu erwecken und verfügbar zu machen.

Systemlinker wie ld erfordern normalerweise einen vollständigen Satz von Symbolen und Verschiebungen, um Referenzen zwischen Archiven aufzulösen. Wenn wir jedoch einen späten Link von JIT-generiertem Code ausführen, können wir eine Abkürzung nehmen: Das Hauptprogramm kann Speicheradressen direkt in den von ihm generierten Code einbetten. Daher würde das generierte Modul Speicher aus dem Hauptmodul importieren. Alle Verweise aus dem generierten Code auf das Hauptmodul können so direkt eingebunden werden.

Das generierte Modul würde auch die Tabelle der indirekten Funktionen aus dem Hauptmodul importieren. (Wir würden sicherstellen, dass das Hauptmodul seinen Speicher und seine indirekte Funktionstabelle über die Toolchain exportiert.) Wenn das Hauptmodul das generierte Modul erstellt, bettet es auch eine spezielle Patch-Funktion in das generierte Modul ein. Diese Funktion würde die neuen Funktionen zur indirekten Funktionstabelle des Hauptmoduls hinzufügen und alle Bewegungen im Speicher des Hauptmoduls ausführen. Alle Hauptmodulverweise auf generierte Funktionen werden über die Patch-Funktion installiert.

Wir planen zwei Late-Link-Implementierungen, aber beide teilen den grundlegenden Mechanismus eines generierten WebAssembly-Moduls mit einer Patch-Funktion.

dynamische Bindung über Laufzeit

Eine Implementierung eines Linkers dient dazu, dass das Hauptmodul die Laufzeit veranlasst, dynamisch ein neues WebAssembly-Modul zu instanziieren. Die Laufzeit würde den Speicher des Hauptmoduls und die indirekte Funktionstabelle als Importe bereitstellen, wenn das generierte Modul instanziiert wird.

Der Vorteil dynamischer Verknüpfungen besteht darin, dass ein WebAssembly-Modul live aktualisiert werden kann, ohne dass eine erneute Instanziierung oder spezielle Laufzeit-Checkpointing-Unterstützung erforderlich ist.< /p>

Im Kontext des Webs kann die JIT-Kompilierung durch das betreffende WebAssembly-Modul ausgelöst werden, indem die JavaScript-Funktionalität aufgerufen wird, oder wir können ein Pull-basiertes Modell verwenden, um dem JavaScript-Host zu ermöglichen, die WebAssembly-Instanz nach ausstehenden JIT abzufragen Code.

Für WASI-Bereitstellungen benötigen Sie Host-Funktionalität. Entweder Sie importieren ein Modul, das Runtime-JIT-Fähigkeiten bereitstellt...

What's Your Reaction?

like

dislike

love

funny

angry

sad

wow