Kolumne: EnterpriseTales

WebAssembly: Die nächsten Schritte

Sven Kölpin

© S&S_Media

Der WebAssembly-Standard steht in den meisten Browsern zur Verfügung und wird bereits in vielen Webanwendungen verwendet. Die Features und APIs sind aktuell zwar noch vergleichsweise rudimentär, die geplanten Erweiterungen der Spezifikation aber vielversprechend. Zudem gibt es einen Standardisierungsvorschlag, der die Verwendung von WebAssembly außerhalb des Browsers ermöglicht. Zeit für einen Ausblick.

WebAssembly (Wasm) ist ein standardisiertes Binärformat, das auf einer stackbasierten virtuellen Maschine ausgeführt wird. Es wurde zwar vor dem Hintergrund entwickelt, ein Binärformat für die Webplattform zur Verfügung zu stellen und so Programmiersprachen abseits von JavaScript in den Browser zu bringen, aber Wasm ist keineswegs auf das Web beschränkt. Ähnlich wie für Java braucht man für die Ausführung von WebAssembly eine Laufzeitumgebung, die virtuelle Maschine, in der das jeweilige Binärformat in Maschinencode übersetzt und dann ausgeführt wird. Diese Umgebung kann im Fall von WebAssembly als Teil des Browsers, in Node.js oder auf beliebigen anderen Plattformen implementiert sein.

Die aktuelle Version 1.0 von WebAssembly ist eine Minimum-Viable-Product-Version (MVP), die bereits seit 2017/2018 in allen relevanten Browsern zur Verfügung steht. Dieser erste Standard enthält vornehmlich grundlegende Features, weshalb die Verwendung von Wasm auf einem vergleichsweise geringem Abstraktionslevel stattfindet. Zudem liegt der Fokus auf den Sprachen C, C++ und Rust, da sie über ein manuelles Memory-Management verfügen. Trotz der Einschränkungen ist WebAssembly allerdings bereits ein fester Bestandteil der Webplattform, der Vorteile in verschiedenen Bereichen bringen kann.

Vornehmlich wird der Performancegewinn gegenüber JavaScript als positives Merkmal von Wasm herausgestellt. Schließlich handelt es sich bei WebAssembly um ein Binärformat, das in fast nativer Geschwindigkeit ausgeführt werden kann und weniger Datenmengen verursacht. In der Realität hängt dieser Vorteil aber natürlich vom konkreten Anwendungsfall ab. Unabhängig davon garantiert WebAssembly allerdings eine weitestgehend konsistente Ausführungsgeschwindigkeit in verschiedenen Browsern.

Die Portierbarkeit von Libraries und ganzer Anwendungen aus anderen Sprachen, aktuell vornehmlich aus C und C++, ist eine weitere Stärke von WebAssembly. Ein plakatives Beispiel hierfür ist das Projekt AutoCAD, bei dem Teile einer über dreißig Jahre alten Codebasis mit der Hilfe von WebAssembly in das Web portiert werden konnten. Auch Ebay setzt bei der Implementierung eines webbasierten Barcodescanners auf WebAssembly. Dort wird eine von C++ nach Wasm portierte Bibliothek für Barcodes verwendet.

Zusätzlich bringt WebAssembly eine langersehnte Flexibilität in den Browser. Wasm ist schließlich ein standardisiertes Binärformat und deshalb, zumindest in der Theorie, ein Kompilierungsziel für beliebige Sprachen. JavaScript ist somit nicht mehr die einzige Programmiersprache, die nativ im Browser eingesetzt werden kann. Ein Ersatz für JavaScript soll WebAssembly aber offiziell nicht sein. Die Zukunft wird allerdings zeigen, ob das so bleibt.

Was fehlt?

Wie eingangs erwähnt, handelt es sich bei WebAssembly 1.0 um eine minimale Version. Diese hat das Ziel, einen Wasm-Standard zur Verfügung zu stellen, der auf Basis von echtem Communityfeedback stetig erweitert werden kann. Dieses Vorgehen ist vor allem vor dem Hintergrund, dass das Web eine rückwärtskompatible Plattform darstellt, zu begrüßen. Eine stetige Weiterentwicklung in kleinen Iterationen hilft schließlich dabei, dass WebAssembly in die richtige Richtung entwickelt wird. Der Nachteil liegt dafür aber auf der Hand. Zum jetzigen Zeitpunkt ist die Benutzung von Wasm selbst für einfache Anwendungsfälle etwas gewöhnungsbedürftig.

Eine der großen Restriktionen in der aktuellen Version ist der Umstand, dass zwischen Wasm und dem Host (dem Browser) nur über die numerischen Typen i32, i64, f32 und f64 kommuniziert werden kann. Die Folge davon ist, dass der Austausch von nichtnumerischen und komplexen Datentypen, beispielsweise Strings, etwas umständlich indirekt über WebAssembly’s lineares Memory vollzogen werden muss (Listing 1).

(async () => {
  const memory = new WebAssembly.Memory({initial: 256});
  const {instance} = await WebAssembly.instantiateStreaming(fetch('wasmFile.wasm'), {
    env: {
      memory
    }
  });

  const parameter = "WebAssembly";
  const buffer = new TextEncoder('utf-8').encode(parameter);

  //random pointer, use some malloc-implementation in real life ;)!
  const pointer = 1024;

  const memView = new Uint8Array(memory.buffer);
  //write buffer to memory
  memView.set(buffer, pointer);

  //call wasm-function with pointer to memory
  const resultPointer = instance.exports.sayHelloTo(pointer);
  //translate pointer back to string...
})();

Tools wie Emscripten, mit denen man C/C++-Code nach WebAssembly kompilieren kann, schaffen hier Abhilfe. Diese generieren zusätzlichen JavaScript-„Kleber“ (Glue Code) und damit eine Abstraktionsebene, mit deren Hilfe die Verwendung vereinfacht wird (Listing 2). Das Ganze ist aber nicht standardisiert und verursacht viele zusätzliche Bytes, die teilweise sogar zu größeren Datenmengen führen als das eigentliche WebAssembly-Modul.

<script src="./dist/emGlue.js"></script>
<script>
  Module.onRuntimeInitialized = () => {
    const sayHelloTo = cwrap('sayHelloTo', 'string', ['string']);
    console.log(sayHelloTo('WebAssembly'));
  };
</script>

Ein weiteres fehlendes Feature ist zum aktuellen Zeitpunkt, dass WebAssembly nicht direkt mit dem DOM oder anderen Web-APIs interagieren kann. Hierzu muss heute noch der Umweg über JavaScript genommen werden. Das liegt vornehmlich daran, dass für die Interaktion mit Objekten des Hostsystems die Integration eines Garbage Collectors (GC) vonnöten ist – und den gibt es noch nicht.

Die fehlende Garbage Collection hat noch einen weiteren Nachteil. Höhere Sprachen wie Java, die kein manuelles Memory-Management haben, können nicht ohne Weiteres nach WebAssembly kompiliert werden. Für solche Sprachen gibt es aktuell keine andere Möglichkeit, als einen GC explizit in Wasm zu kompilieren und als Teil der Binärdatei auszuliefern. Das ist aber sowohl in Hinsicht auf die Dateigröße als auch bezogen auf die Effizienz des GC-Algorithmus bedenklich. AssemblyScript, eine TypeScript-ähnliche Sprache, die nach Wasm kompiliert werden kann, liefert beispielsweise einen eigenen Garbage Collector aus.

Was kommt?

Für die genannten fehlenden Features gibt es bereits verschiedene Feature-Proposals. Diese durchlaufen, ähnlich wie man es aus dem ECMA-Standardisierungsprozess kennt, fünf unterschiedliche Phasen. Dabei bedeutet Phase 1 die Stufe eines Vorschlags und Phase 5 die letztendliche Standardisierung.

Das Interface Types Proposal ist ein vielversprechender Featurevorschlag, durch den die Kommunikation zwischen WASM und der Außenwelt deutlich vereinfacht wird. Mit Interface Types kann ein Wasm-Modul eigene Adapter für komplexe Datentypen definieren. Der JavaScript Glue Code, der aktuell für das Mapping von Low-Level- zu High-Level-Datentypen vonnöten ist, wird so zu einer Abstraktionsschicht innerhalb des Wasm-Moduls. Nichtnumerische Datentypen können dann sowohl als Parameter für Aufrufe nach WebAssembly als auch für Rückgabewerte aus einem WebAssembly-Modul heraus verwendet werden (Listing 3). Neben einer vereinfachten Modulnutzung ermöglicht das Proposal außerdem, dass Wasm-Module ohne Umwege über JavaScript direkt miteinander kommunizieren können. Interface Types sind allerdings zum aktuellen Zeitpunkt noch in der Phase 1 des Standardisierungsprozesses und bislang nur sehr rudimentär definiert.

(async () => {
  const {instance} = await    
      WebAssembly.instantiateStreaming(fetch('./dist/index.wasm'),/*...*/);
  //no js-glue needed 🙂
  const result = instance.exports.sayHelloTo("WebAssembly");
})();

Etwas weiter ist das Proposal für Reference Types. Es befindet sich in der Phase 3 des Standardisierungsprozesses und ist beispielsweise bereits in Chrome implementiert. Reference Types ermöglichen es, innerhalb von WebAssembly-Modulen auf Referenzen von JavaScript- beziehungsweise DOM-Objekten zuzugreifen. Dazu erweitert das Proposal das WebAssembly-Typsystem um den anyref-Type. Mit seiner Hilfe können innerhalb von Wasm-Code Referenzen auf beliebige Objekte der Hostumgebung (z. B. dem Browser) gehalten werden. Was erstmal vielversprechend klingt, hat allerdings noch einen Haken – eine Interaktion mit den Objekten ist nicht Teil des Proposals. Dafür müssen beispielsweise zunächst die eben beschriebenen Interface Types implementiert sein. Eine Kombination dieser Standards würde die direkte Verwendung der DOM- und anderer Web-APIs in Wasm ermöglichen.

Auch für Garbage Collection gibt es bereits ein Proposal. Dieses sieht aktuell nicht vor, dass ein expliziter GC als Teil der WebAssembly Runtime ausgeliefert wird. Vielmehr soll sich Wasm mit dem von der jeweiligen Hostumgebung bereitgestellten GC integrieren. Das ist sinnvoll, weil Wasm sowieso stark mit dem Hostsystem interagiert (siehe Reference Types), was durch einen einzigen GC erheblich erleichtert wird. Das Proposal ist sehr umfangreich und wurde in verschiedene Sub-Proposals unterteilt, die zunächst als Voraussetzung für GC implementiert werden müssen (z. B. Reference Types, Types Function References). Garbage Collection befindet sich in Phase 1 des Standardisierungsprozesses.

Natürlich gibt es noch viele weitere Proposals, beispielsweise für Threads, Exception Handling oder SIMD (Single Instruction, Multiple Data). Letzteres erlaubt einer einzelnen CPU-Instruktion mit verschiedenen Daten zu interagieren, was einen vielfach höheren Durchsatz ermöglicht und Wasm für bestimmte Anwendungsfälle noch schneller macht.

Manche der genannten Features sind bereits in den Browsern verwendbar, obgleich sie in einigen Fällen noch explizit aktiviert werden müssen. Beispielsweise kann in Chrome bereits mit Wasm-Threads und SIMD gearbeitet werden. Es gibt zudem mit wasm-feature-detect eine leichtgewichtige Bibliothek, mit der überprüft werden kann, welche Wasm-Features im Browser zur Verfügung stehen. So können abhängig von unterschiedlichen Browserversionen verschiedene WebAssembly-Module geladen oder – sofern möglich – Polyfills ausgeliefert werden.

Und dann gibt’s noch WASI

Wie eingangs erwähnt, ist WebAssembly zwar mit dem Fokus auf das Web entworfen worden. Als standardisiertes Binärformat können mit WASM aber auch Anwendungsfälle außerhalb vom Browser und Node.js realisiert werden. Dazu muss aber ein standardisierter Weg existieren, über den WebAssembly mit dem jeweiligen Betriebssystem interagieren kann, beispielsweise um Zugriffe auf das Dateisystem zu ermöglichen. Genau hier kommt das WebAssembly System Interface – oder kurz WASI – ins Spiel. WASI bildet ein Systeminterface für die WebAssembly-Plattform, das Low-Level-Schnittstellen zum Betriebssystem auf eine sichere Art und Weise zur Verfügung stellt. Ähnlich zu POSIX kann so über standardisierte APIs, die innerhalb der WebAssembly-Module nutzbar sind, mit dem Dateisystem, Netzwerk oder anderen Systemressourcen interagiert werden. Mit Hilfe von WASI werden WebAssembly-Module so zu beliebig portierbaren Binärdateien. In Kombination mit dem bereits erwähnten Interface Types Proposal ist es zusätzlich möglich, verschiedene WebAssembly-Module untereinander kommunizieren zu lassen und so Code aus beliebigen Ökosystemen zu kombinieren.

WASI ist in unterschiedliche Module aufgeteilt, die verschiedene Aufgabenbereiche abdecken. Aktuell wird vornehmlich das Core-Modul spezifiziert, in dem der Zugriff auf grundlegende Ressourcen, dazu zählen das Dateisystem oder Netzwerkinterfaces, spezifiziert ist. Geplant sind zukünftig auch weitere Module, die APIs für den Zugriff auf Multimedia, Sensoren oder Prozesse definieren.

Die Verwendung von WebAssembly außerhalb des Browsers bringt neben der Portierbarkeit beliebiger Sprachen und Ökosysteme vor allem auch Vorteile hinsichtlich Sicherheit und Geschwindigkeit. Beispielsweise kommt WebAssembly heute bereits beim Cloudanbieter Fastly zum Einsatz. Hier können mit Hilfe Lucet, der eigens entwickelten WebAssembly Runtime, die bereits eine frühe Version des WASI-Standards implementiert, Wasm-Module deployt werden. Laut eigener Aussage sind die WebAssembly-Instanzen zur Abhandlung eines Requests in unter 50 Mikrosekunden instanziierbar und verbrauchen dabei nur ein paar Kilobyte an Speicher. Zudem laufen alle WebAssembly-Module in einer Sandbox-Umgebung und sind somit sicher und voneinander isoliert. Neben Lucet gibt es bereits auch andere Wasm Runtimes und WASI Runtimes, die das Ausführen von WebAssembly auf beliebigen Plattformen ermöglichen. Hier sind zum Beispiel wasmtime und wasm-micro-runtime zu nennen.

Fazit

WebAssembly ist ein fester Bestandteil der Webplattform. Das zugegebenermaßen rudimentäre Featureset der MVP-Version wird stetig weiterentwickelt, sodass die Verwendung von Wasm vereinfacht und sich der Standard mit großer Wahrscheinlichkeit immer weiter etablieren wird.

Mit WASI werden ganz neue Anwendungsfälle für WebAssembly eröffnet. Am Beispiel von Fastly zeigt sich, was bereits heute und in Zukunft mit Wasm möglich sein wird. Zudem haben sich Mozilla, Intel, Red Hat und Fastly kürzlich zusammengeschlossen, um in einer „Bytecode Alliance“ den WASI-Standard weiter voranzutreiben. Es ist also zu erwarten, dass sich hier in der nächsten Zeit noch einiges tun wird.

In diesem Sinne: Stay tuned.

Geschrieben von
Sven Kölpin
Sven Kölpin
Sven Kölpin ist Enterprise Developer bei der open knowledge GmbH in Oldenburg. Sein Schwerpunkt liegt auf der Entwicklung webbasierter Enterprise-Lösungen mittels Java EE.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
4000
  Subscribe  
Benachrichtige mich zu: