
Kevin Erath
Geschäftsführer
Veröffentlicht am
12. Juli 2022

WebAssembly (WASM) ist eine Technologie zur Erstellung von sehr performanten Anwendungen, die in der Regel im Browser laufen. Die Erstellung von WASM-Anwendung kann mit verschiedenen Programmiersprachen erfolgen. Eine WASM kann in zwei Formaten vorliegen, binär oder als Text. Letzteres wird als WAT (WebAssembly Text Format) bezeichnet. WebAssembly ist dabei eine Sprache mit einem sehr niedrigen Niveau (low-level). Deshalb wird man vermutlich sehr selten eine WASM von Hand in WAT erstellen und vielmehr auf eine der höheren Programmiersprachen setzen. Dennoch habe ich mir WAT mal angeschaut, um ein besseres Verständnis dafür zu bekommen. Diese Erfahrung möchte ich hier anhand zweier kleiner Beispiele zeigen.
Eine in WASM geschriebene Funktion aufrufen
Als erstes Beispiel soll wieder einmal die Antwort auf alle Fragen ermittelt werden. Hierzu erstellen wir eine WebAssembly, die eine Funktion get_answer()
nach außen zur Verfügung stellt, die bei Aufruf 42
zurückliefert. Folgender Code ist hierfür nötig:
;; answer.wat
(module
;; Definiert unsere Funktion, welche wir dann für JavaScript eportieren
(func $the_answer(result i32)
(i32.const 42)
)
(export "get_answer" (func $the_answer))
)
Die erste Zeile zeigt, wie Kommentare in WAT aussehen. Hierzu werden zwei Semikolons ;;
verwendet. Der erste wichtige Codeabschnitt folgt in Zeile zwei und definiert ein neues Modul. Module werden als Container für z. B. Funktionen benötigt. Als Nächstes wird unsere Funktion für die Rückgabe von 42
definiert. Als Rückgabewert wird dabei ein 32-Bit Integer verwendet. Der letzte Teil exportiert die Funktion the_answer()
als get_answer
. Unter diesem Namen ist die Funktion dann von JavaScript aus aufrufbar.
Damit sind wir mit dem Code unserer ersten WebAssembly schon fertig. Zum Kompilieren kann das WebAssembly Binary Toolkit (WABT) verwendet werden. Dies ist auf GitHub.com verfügbar. Zum Erstellen wird dann folgender Befehl verwendet:
wat2wasm answer.wat
Somit haben wir unsere WebAssembly als Binärversion erstellt. Um diese auszuführen können wir ein kleines HTML, dass ein JavaScript enthält erstellen. Im JavaScript laden wir als Erstes die WebAssembly und erzeugen dann davon eine Instanz. Sobald wir die Instanz haben, können wir unsere exportierte Funktion get_answer()
aufrufen. Das Ergebnis schreiben in wir in diesem Fall der Einfachheit halber direkt ins Dokument.
<!DOCTYPE html>
<html>
<head>
<script>
fetch('./answer.wasm').then(x=>x.arrayBuffer()).then(bytes=> WebAssembly.instantiate(bytes, {})
).then(results => {
var instance = results.instance;
var result = instance.exports.get_answer();
document.write(result);
}).catch(console.error);
</script>
</head>
<body>
</body>
</html>
Möchte man dieses Beispiel nun lokal ausführen, verhindert das die Sicherheitsfunktionen des Browser (egal ob Firefox, Edge oder Chrome). Es kommt zur Fehlermeldung: Fetch API cannot load file:///answer.wasm. URL scheme must be "http" or "https" for CORS request.
Je nach Browser lässt sich diese Sicherheitsfunktion auch deaktivieren. Was natürlich nur zum Test eine gute Idee ist, im Internet sollte man so nicht unterwegs sein, was selbstverständlich ist. Dies lässt sich bei Chrome durch den Parameter --allow-file-access-from-files
realisieren. Ein Start unserer Testseite im Browser könnte dann z. B. wie folgt aussehen:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files file:///X:/wasm/answer.html
Dummerweise hilft das Ausschalten der Sicherheitsfunktion allein noch nicht, die WebAssembly zu laden. Es kommt zu einem weiteren Fehler „answer.html:5 Fetch API cannot load file:///./answer.wasm. URL scheme "file" is not supported.
„, welcher damit zusammenhängt, dass der fetch()
-Aufruf nicht mit dem file://
-Protokoll funktioniert. Eine Lösung besteht nun darin, den Aufruf von fetch()
durch eine eigene Implementierung zu ersetzen. Diese kann im einfachsten Fall so aussehen:
async function fetchLocal(url) {
return new Promise(function(resolve, _) {
var request = new XMLHttpRequest();
request.onload = function() {
resolve(new Response(request.responseText));
};
request.open('GET', url);
request.send(null);
});
}
Flugs also noch den Aufruf in Zeile 5 durch unsere neue Funktion ersetzt (fetchLocal('./answer.wasm').then(x=>...
) und ein erneuter Aufruf führt zu folgendem erfolgreichem Ergebnis:

Eine JavaScript-Funktion in WASM verwenden
Natürlich erlaubt WebAssembly auch den Zugriff auf JavaScript-Funktionen. Dies möchte ich anhand eines weiteren kleinen Beispiels zeigen. Hierzu eignet sich eine typische Hallo, Welt
Anwendung. Hierzu soll eine Funktion print_hello_world
in WASM erstellt werden, welche den Text Hallo, Welt!
dann im Browser ausgibt. Zur Ausgabe im Browser soll dabei eine JavaScript-Funktion zur Verfügung gestellt werden.
Da wir nun keine einfachen Werttypen wie im letzten Beispiel verwenden, sondern eine Zeichenkette, wird das ganze etwas mühseliger, denn wir müssen uns selbst um das Marshalling (verpacken/entpacken) der Daten über eine Speicherseite kümmern. Hierzu muss in WASM eine lineare Speicherseite angelegt und exportiert werden, auf die in JavaScript dann zugegriffen werden kann. Es folgt der Code in WAT:
;; hallo_welt.wat
(module
;; Importieren der JavaScript-Funktion print_to_log
(import "env" "write" (func $js_write (param i32)))
;; Bereitstellen einer Speicherseite
(memory $0 1)
(export "pagememory" (memory $0))
;; Füge den null-terminierten String an Adresse 0 hinzu
(data (i32.const 0) "Hallo, Welt!\00")
;; Definiert unsere Funktion und welche für JavaScript exportiert wird
(func $print_hello_world
(call $js_write (i32.const 0))
)
(export "print_hello_world" (func $print_hello_world))
)
Der Code ist eigentlich recht schnell erklärt. In Zeile 4 wird die JavaScript-Funktion write()
als js_write()
importiert. Diese wird später (Zeile 15) dann aufgerufen, um quasi den Text Hallo, Welt!
zur Ausgabe zu übergeben. Quasi deshalb, da wir ja den Text über die Speicherseite transportieren müssen. Diese Speicherseite wird in Zeile 7 angelegt und in Zeile 8 als pagememory
exportiert. Über diesen Namen kann dann in JavaScript darauf zugegriffen werden. In Zeile 11 wird der Text im Speicher hinterlegt. Wir verwenden dabei eine Null-Terminierung, was nicht zwingend notwendig ist, man könnte auch auf andere Weise die Textlänge beschreiben. In der vorletzten Zeile wird dann noch unsere print_hello_world()
Funktion exportiert und damit für JavaScript zugänglich gemacht.
Das HTML habe ich dieses Mal einfach gestaltet und das Script als weitere Datei verpackt:
<!DOCTYPE html>
<html>
<head>
<script src="./hallo_welt.js"></script>
</head>
<body>
</body>
</html>
Der JavaScript-Code sieht wie folgt aus, die Funktion fetchLocal()
habe ich dabei weggelassen:
var memory;
this.fetchLocal('./hallo_welt.wasm').then(response=>response.arrayBuffer()).then(bytes=> WebAssembly.instantiate(bytes, {
env: {
write: function write_to_document(offset) {
var a = new Uint8Array(memory.buffer);
for (var i = offset; a[i]; i++)
document.write(String.fromCharCode(a[i]));
}
}
})
).then(results => {
instance = results.instance;
memory = instance.exports.pagememory;
instance.exports.print_hello_world();
}).catch(console.error);
Interessant sind hier vor allem die Zeilen 5-9, diese definieren unsere Funktion write()
, innerhalb von JavaScript als write_to_document()
bezeichnet. Sobald diese Funktion nun innerhalb von WASM aufgerufen wird, wird der geteilte Speicher als UInt8
-Array verarbeitet, der Offset von 0 wurde dabei beim Aufruf übergeben (Falls mehrere Zeichenketten verarbeitet werden sollen). Der Speicher wird nun Zeichen für Zeichen durchlaufen und im HTML-Dokument ergänzt, bis das Endezeichen (a[i] == 0
) erreicht wird. Der Speicher (hier memory
genannt) wird vor dem Aufruf aus den Exports (Zeile 14) gespeichert.
Das Bauen erfolgt dann wieder via wat2wasm.exe hallo_welt.wat
. Das Ergebnis kann dann wieder im Browser angezeigt werden:

Ich hoffe, ich konnte einen kleinen Einblick in die Interna von WebAssembly geben. Der Quellcode findet der beiden Beispiele finden sich auch auf GitHub (TheAnswer und HalloWelt).

Hier schreibt
Kevin Erath
Als Mitbegründer und Geschäftsführer von pep.digital verbringe ich zwar nicht mehr jeden Tag ausschließlich damit, coole Lösungen für unsere Kunden zu realisieren. Trotzdem finde ich immer wieder die Zeit, mich auch mal tiefer in die Technik einzutauchen und meine Erkenntnisse hier im Blog zu teilen. Und ehrlich gesagt, das Unternehmen und unsere tollen Mitarbeiter:innen weiterzuentwickeln, macht mir mindestens genauso viel Spaß.
Quellen
Weitere interessante Artikel
Wir möchten hier nicht nur über Neuigkeiten aus dem Unternehmen berichten, sondern auch das Wissen und die Erfahrung unserer Experten teilen.

Debugging von Java Apps in Docker Containern
Wie kann ich meine dockerisierte Java-Anwendung mit IntelliJ IDEA oder Eclipse debuggen? Und wie bekomme ich IntelliJ IDEA dazu, dass Änderungen am Code während des Debuggens automatisch neu compiliert und deployt werden, ohne dass der Debug-Prozess neu gestartet werden muss?

Dirk Randhahn
Teamleiter, Softwarearchitekt

Java Features – Wahrnehmung und Verwendung
Java ist nach wie vor eine der weit verbreitetsten Programmiersprachen weltweit, und genießt weiterhin eine große Beliebtheit. Einer der Gründe ist die stetige Veränderung und Verbesserung der Sprache, und damit wollen wir uns in diesem Beitrag beschäftigen. Der Fokus liegt auf den Sprachfeatures und damit weder auf die Performance-Verbesserungen, noch auf externen Tools.

Klemens Morbe
Softwareentwickler