WebAssembly ist ein Binärformat zur Ausführung von Code im Web, das schnelle Startzeiten ermöglicht (kleinerer Download und viel schnelleres Parsen in Browsern im Vergleich zu JS oder asm.js). Emscripten kompiliert standardmäßig nach WebAssembly, aber Sie können für ältere Browser auch nach JS kompilieren.
Für einige historische Hintergründe siehe diese Folien und diesen Blogpost.
WebAssembly wird standardmäßig ausgegeben, ohne dass spezielle Flags erforderlich sind.
Hinweis
Wenn Sie WebAssembly nicht möchten, können Sie es deaktivieren mit etwas wie
emcc [..args..] -sWASM=0
Hinweis
Die Entscheidung, nach Wasm oder JS zu kompilieren, kann in der Linking-Phase getroffen werden; sie hat keinen Einfluss auf die Objektdateien.
Emscripten gibt WebAssembly seit Version 1.39.0 (Oktober 2019) über das Upstream LLVM Wasm-Backend aus. Zuvor unterstützte Emscripten auch das alte fastcomp-Backend, welches in 2.0.0 (August 2020) entfernt wurde.
Es gibt einige Unterschiede, die Sie zwischen den beiden Backends bemerken könnten, wenn Sie von fastcomp auf Upstream aktualisieren
Das Wasm-Backend ist streng beim Verknüpfen von Dateien mit unterschiedlichen Feature-Sets – wenn beispielsweise eine Datei mit Atomics erstellt wurde, eine andere jedoch nicht, wird zur Link-Zeit ein Fehler ausgegeben. Dies verhindert mögliche Bugs, kann aber bedeuten, dass Sie Anpassungen am Build-System vornehmen müssen.
WASM=0 verhält sich in den beiden Backends unterschiedlich. In fastcomp geben wir asm.js aus, während wir in Upstream JS ausgeben (da nicht alle Wasm-Konstrukte in asm.js ausgedrückt werden können). Außerdem implementiert der JS-Support dieselbe externe WebAssembly.* API, sodass der Start standardmäßig genau wie bei Wasm asynchron erfolgt; dies können Sie mit WASM_ASYNC_COMPILATION steuern (selbst wenn WASM=0).
Das Wasm-Backend verwendet standardmäßig Wasm-Objektdateien. Das bedeutet, dass die Codegenerierung bereits beim Kompilierungsschritt erfolgt, was den Linking-Schritt viel schneller macht – wie bei einem normalen nativen Compiler. Zum Vergleich: In fastcomp gibt der Kompilierungsschritt LLVM-IR in Objektdateien aus.
Normalerweise würden Sie dies nicht bemerken, aber einige Compiler-Flags beeinflussen die Codegenerierung, wie z.B. DISABLE_EXCEPTION_CATCHING. Solche Flags müssen während der Codegenerierung übergeben werden. Am einfachsten und sichersten ist es, alle -s Flags sowohl zur Kompilier- als auch zur Link-Zeit zu übergeben.
Sie können Link Time Optimization (LTO) mit den üblichen LLVM-Flags aktivieren (-flto, -flto=full, -flto=thin, sowohl zur Kompilier- als auch zur Link-Zeit; beachten Sie jedoch, dass Thin LTO derzeit nicht intensiv getestet ist und daher reguläres LTO empfohlen wird).
Bei fastcomp wurden LTO-Optimierungsdurchläufe standardmäßig nicht ausgeführt; dafür war die Übergabe von --llvm-lto 1 erforderlich. Beim LLVM-Backend werden LTO-Durchläufe auf allen Objektdateien ausgeführt, die im Bitcode-Format vorliegen.
Ein weiterer Punkt, den Sie bemerken könnten, ist, dass die Link-Phase von fastcomp in der Lage ist, einige kleinere Arten der Link-Time-Optimierung durchzuführen, selbst wenn LTO nicht gesetzt ist. Das LLVM-Backend erfordert für diese Dinge die tatsächliche Aktivierung von LTO.
wasm-ld, der vom Wasm-Backend verwendete Linker, erfordert, dass Bibliotheken (.a-Archive) Symbolindizes enthalten. Dies entspricht dem Verhalten des nativen GNU-Linkers. Während emar solche Indizes standardmäßig erstellt, erkennen native Tools wie GNU ar und GNU strip das WebAssembly-Objektformat nicht und können keine Archivindizes erstellen. Insbesondere wenn Sie GNU strip auf eine Archivdatei anwenden, die WebAssembly-Objektdateien enthält, wird der Index entfernt, was das Archiv zur Link-Zeit unbrauchbar macht.
Siehe auch die Blocker-Bugs im Wasm-Backend und die mit dem Wasm-Backend getaggten Issues.
WebAssembly kann "trappen" – eine Exception auslösen – bei Dingen wie Division durch Null, dem Runden eines sehr großen Floats zu einem Int und so weiter. In asm.js wurden solche Dinge stillschweigend ignoriert, da sie in JavaScript keinen Fehler auslösen. Dies ist ein Unterschied zwischen JavaScript und WebAssembly, den Sie bemerken könnten, wobei der Browser einen Fehler wie float unrepresentable in integer range, integer result unrepresentable, integer overflow oder Out of bounds Trunc operation meldet.
Das LLVM-Wasm-Backend vermeidet Traps, indem es mehr Code um jeden möglichen Trap hinzufügt (im Grunde wird der Wert begrenzt, falls er trappen würde). Dies kann die Codegröße erhöhen und die Geschwindigkeit verringern, wenn Sie diesen zusätzlichen Code nicht benötigen. Die richtige Lösung hierfür ist die Verwendung neuerer Wasm-Instruktionen, die nicht trappen, indem emcc oder clang mit -mnontrapping-fptoint aufgerufen werden. Dieser Code läuft jedoch möglicherweise nicht in älteren VMs.
Wenn Sie emcc zum Kompilieren nach WebAssembly verwenden, sehen Sie eine .wasm-Datei, die diesen Code enthält, sowie die übliche .js-Datei, die das Hauptziel der Kompilierung ist. Diese beiden sind so konzipiert, dass sie zusammenarbeiten: Führen Sie die .js- (oder .html-, falls angefordert) Datei aus, und sie wird den WebAssembly-Code für Sie laden und einrichten, inklusive der korrekten Konfiguration von Imports und Exports usw. Im Grunde müssen Sie sich nicht darum kümmern, ob der kompilierte Code asm.js oder WebAssembly ist; es ist nur ein Compiler-Flag, und ansonsten sollte alles einfach funktionieren (außer dass WebAssembly schneller sein sollte).
Beachten Sie, dass die .wasm-Datei nicht eigenständig ist – es ist nicht einfach, sie manuell ohne diesen .js-Code auszuführen, da sie auf die richtigen Imports angewiesen ist, die sie mit JS integrieren. Beispielsweise erhält sie Imports für Syscalls, damit sie Dinge wie die Ausgabe auf der Konsole durchführen kann. Es wird an Wegen gearbeitet, eigenständige .wasm-Dateien zu erstellen, siehe die WebAssembly Standalone Seite.
Möglicherweise sehen Sie auch zusätzlich generierte Dateien, wie eine .data-Datei, wenn Sie Dateien in das virtuelle Dateisystem vorladen. All das ist genau so wie beim Kompilieren nach asm.js. Ein Unterschied, den Sie bemerken könnten, ist das Fehlen einer .mem-Datei, die bei asm.js die statischen Speicherinitialisierungsdaten enthält; in WebAssembly können wir diese effizienter direkt in das WebAssembly-Binary packen.
WebAssembly wird von allen gängigen Browsern unterstützt, zurückreichend bis Firefox 52, Chrome 57, Safari 11 und Opera 44.
Weitere Informationen zu den in verschiedenen Browsern unterstützten WebAssembly-Features finden Sie in der WebAssembly Roadmap.
.wasm-Dateien und Kompilierung¶WebAssembly-Code wird etwas anders vorbereitet als asm.js. asm.js kann innerhalb der Haupt-JS-Datei gebündelt werden, während WebAssembly, wie bereits erwähnt, eine separate Binärdatei ist, sodass Sie mehr als eine Datei verteilen müssen.
Ein weiterer spürbarer Effekt ist, dass WebAssembly standardmäßig asynchron kompiliert wird. Das bedeutet, dass Sie warten müssen, bis die Kompilierung abgeschlossen ist, bevor Sie kompilierten Code aufrufen (indem Sie auf main() oder den onRuntimeInitialized-Callback usw. warten; dies müssen Sie auch tun, wenn andere Faktoren den Start asynchron machen, wie eine .mem-Datei für asm.js oder vorgeladene Dateidaten usw.). Sie können die asynchrone Kompilierung deaktivieren, indem Sie WASM_ASYNC_COMPILATION=0 setzen, aber das funktioniert in Chrome aufgrund aktueller Einschränkungen dort möglicherweise nicht.
Beachten Sie, dass selbst bei deaktivierter asynchroner Kompilierung das Abrufen des WebAssembly-Binarys eine asynchrone Operation sein muss (da das Web keine synchronen Binär-Downloads im Haupt-Thread erlaubt). Wenn Sie das Binary selbst abrufen können, können Sie Module['wasmBinary'] setzen; es wird dann von dort verwendet, und (bei deaktivierter asynchroner Kompilierung) sollte die Kompilierung synchron erfolgen.
Um Wasm auf effizienteste Weise über das Netzwerk auszuliefern, stellen Sie sicher, dass Ihr Webserver den korrekten MIME-Typ für .wasm-Dateien hat, welcher application/wasm lautet. Dies ermöglicht Streaming-Kompilierung, bei der der Browser mit dem Kompilieren des Codes beginnen kann, während er heruntergeladen wird.
In Apache können Sie dies tun mit
AddType application/wasm .wasm
Stellen Sie außerdem sicher, dass gzip aktiviert ist
AddOutputFilterByType DEFLATE application/wasm
Wenn Sie große .wasm-Dateien ausliefern, verbraucht der Webserver CPU-Leistung, um sie bei jeder Anfrage on-the-fly zu komprimieren. Stattdessen können Sie sie zu .wasm.gz vorkomprimieren und Content Negotiation verwenden.
Options Multiviews
RemoveType .gz
AddEncoding x-gzip .gz
AddType application/wasm .wasm