Zum Hauptinhalt springen

WebAssembly-Integration mit JavaScript-BigInt

· 5 Minuten Lesezeit
Alon Zakai

Das JS-BigInt-Integration-Feature macht es einfach, 64-Bit-Ganzzahlen zwischen JavaScript und WebAssembly zu übertragen. Dieser Beitrag erklärt, was das bedeutet und warum es nützlich ist, einschließlich der Vereinfachung für Entwickler, der Beschleunigung des Codes und auch der Verkürzung der Build-Zeiten.

64-Bit-Ganzzahlen

JavaScript-Zahlen sind Doubles, das heißt 64-Bit-Gleitkommawerte. Solche Werte können jede 32-Bit-Ganzzahl mit voller Präzision enthalten, aber nicht alle 64-Bit-Werte. WebAssembly hingegen unterstützt 64-Bit-Ganzzahlen vollständig, den Typ i64. Ein Problem tritt auf, wenn die beiden verbunden werden: Wenn beispielsweise eine Wasm-Funktion ein i64 zurückgibt, wirft die VM eine Ausnahme, wenn sie aus JavaScript aufgerufen wird, und zwar etwa so:

TypeError: Wasm-Funktionssignatur enthält einen illegalen Typ

Wie der Fehler besagt, ist i64 kein gültiger Typ für JavaScript.

Historisch gesehen war die beste Lösung hierfür die „Legalisierung“ des Wasm. Legalisierung bedeutet, dass Wasm-Imports und -Exports so umgewandelt werden, dass sie gültige Typen für JavaScript verwenden. In der Praxis wurden dadurch zwei Dinge erreicht:

  1. Ersetzen eines 64-Bit-Ganzzahlenparameters durch zwei 32-Bit-Werte, die die niedrigen bzw. hohen Bits darstellen.
  2. Ersetzen eines 64-Bit-Ganzzahlenrückgabewertes durch einen 32-Bit-Wert, der die niedrigen Bits darstellt, und Verwendung eines weiteren 32-Bit-Wertes für die hohen Bits.

Betrachten Sie beispielsweise dieses Wasm-Modul:

(module
(func $send_i64 (param $x i64)
..))

Legalisierung würde das in Folgendes umwandeln:

(module
(func $send_i64 (param $x_low i32) (param $x_high i32)
(local $x i64) ;; der tatsächliche Wert, den der restliche Code verwenden wird
;; Code zum Kombinieren von $x_low und $x_high in $x
..))

Legalisierung wird auf der Werkzeugseite durchgeführt, bevor sie die VM erreicht, die sie ausführt. Beispielsweise enthält die Binaryen-Toolchain-Bibliothek einen Pass namens LegalizeJSInterface, der diese Transformation durchführt, und wird in Emscripten automatisch ausgeführt, wenn es erforderlich ist.

Nachteile der Legalisierung

Die Legalisierung funktioniert für viele Dinge gut genug, hat aber Nachteile, wie den zusätzlichen Arbeitsaufwand zum Kombinieren oder Aufteilen von 32-Bit-Teilen in 64-Bit-Werte. Es ist selten, dass dies in einem entscheidenden Pfad geschieht, aber wenn es der Fall ist, kann die Verlangsamung bemerkbar sein - wir werden später einige Zahlen sehen.

Ein weiterer Nachteil ist, dass die Legalisierung für Benutzer sichtbar ist, da sie die Schnittstelle zwischen JavaScript und Wasm verändert. Hier ist ein Beispiel:

// beispiel.c

#include <stdint.h>

extern void send_i64_to_js(int64_t);

int main() {
send_i64_to_js(0xABCD12345678ULL);
}
// beispiel.js

mergeInto(LibraryManager.library, {
send_i64_to_js: function(value) {
console.log("JS empfangen: 0x" + value.toString(16));
}
});

Dies ist ein kleines C-Programm, das eine Funktion einer JavaScript-Bibliothek aufruft (das heißt, wir definieren eine externe C-Funktion in C und implementieren sie in JavaScript als einfachen und direkten Weg, zwischen Wasm und JavaScript zu kommunizieren). Alles, was dieses Programm tut, ist, ein i64 an JavaScript zu senden, wo wir versuchen, es auszugeben.

Wir können das mit

emcc beispiel.c --js-library beispiel.js -o out.js

Wenn wir es ausführen, erhalten wir nicht das erwartete Ergebnis:

node out.js
JS empfangen: 0x12345678

Wir haben 0xABCD12345678 gesendet, aber nur 0x12345678 erhalten 😔. Was hier passiert, ist, dass die Legalisierung dieses i64 in zwei i32s aufgeteilt hat, und der Code hat nur die niedrigen 32 Bits erhalten und einen weiteren übergebenen Parameter ignoriert. Um die Dinge richtig zu behandeln, müssten wir etwa Folgendes tun:

  // Das i64 wird in zwei 32-Bit-Parameter „low“ und „high“ aufgeteilt.
send_i64_to_js: function(low, high) {
console.log("JS empfangen: 0x" + high.toString(16) + low.toString(16));
}

Wenn wir das nun ausführen, erhalten wir

JS empfangen: 0xabcd12345678

Wie Sie sehen können, ist es möglich, mit der Legalisierung zu leben. Aber es kann ziemlich nervig sein!

Die Lösung: JavaScript-BigInts

JavaScript hat jetzt BigInt-Werte, die Ganzzahlen beliebiger Größe darstellen können, sodass sie 64-Bit-Ganzzahlen korrekt darstellen können. Es ist naheliegend, diese zu verwenden, um i64s aus Wasm darzustellen. Genau das macht die JS-BigInt-Integrationsfunktion!

Emscripten unterstützt die Wasm-BigInt-Integration, die wir verwenden können, um das ursprüngliche Beispiel (ohne Hacks zur Legalisierung) einfach durch Hinzufügen von -s WASM_BIGINT zu kompilieren:

emcc example.c --js-library example.js -o out.js -s WASM_BIGINT

Anschließend können wir es ausführen (beachten Sie, dass wir Node.js derzeit ein Flag übergeben müssen, um die BigInt-Integration zu aktivieren):

node --experimental-wasm-bigint a.out.js
JS erhielt: 0xabcd12345678

Perfekt, genau das, was wir wollten!

Und nicht nur ist dies einfacher, sondern auch schneller. Wie bereits erwähnt, ist es in der Praxis selten, dass i64-Konvertierungen auf einem heißen Pfad stattfinden, aber wenn dies der Fall ist, kann die Verlangsamung spürbar sein. Wenn wir das obige Beispiel in einen Benchmark umwandeln und viele Aufrufe von send_i64_to_js ausführen, ist die BigInt-Version 18 % schneller.

Ein weiterer Vorteil der BigInt-Integration besteht darin, dass die Toolchain die Legalisierung vermeiden kann. Wenn Emscripten nicht legalisieren muss, hat es möglicherweise keine Arbeit am Wasm, das LLVM ausgibt, was die Build-Zeiten beschleunigt. Sie können diese Geschwindigkeitssteigerung erzielen, wenn Sie mit -s WASM_BIGINT bauen und keine anderen Flags bereitstellen, die Änderungen erfordern. Zum Beispiel funktioniert -O0 -s WASM_BIGINT (aber optimierte Builds führen den Binaryen-Optimizer aus, der für die Größe wichtig ist).

Fazit

Die WebAssembly-BigInt-Integration wurde in mehreren Browsern implementiert, darunter Chrome 85 (veröffentlicht am 25.08.2020), sodass Sie es heute ausprobieren können!