Einführung in die fortgeschrittene Arbeit mit WebAssembly und JavaScript in Node.js
\n\nWebAssembly (Wasm) ist längst keine exotische Technologie mehr nur für Browser. Im Node.js-Ökosystem eröffnet es einzigartige Möglichkeiten zur Ausführung von hochperformanten Berechnungen, die in C/C++, Rust oder Go geschrieben sind, mit minimalem Overhead. Auf fortgeschrittenem Niveau erfordert die Arbeit mit Wasm in Node.js ein tiefes Verständnis der Speicherverwaltung, asynchroner Aufrufe und der Optimierung der Datenübertragung zwischen den Laufzeitumgebungen. In diesem Artikel analysieren wir praktische Beispiele für die Integration von WebAssembly mit JavaScript unter Verwendung von Node.js.
\n\n1. Kompilierung und Laden von Wasm-Modulen in Node.js
\n\nVorbereitung der Wasm-Datei
\nZunächst benötigen wir ein kompiliertes Wasm-Modul. Nehmen wir an, wir haben eine einfache C-Funktion, die zwei Zahlen summiert:
\n\n// sum.c\nint add(int a, int b) {\n return a + b;\n}\n\nKompilieren mit Emscripten oder wasm-pack:
\nemcc sum.c -O3 -o sum.wasm --no-entry -s EXPORTED_FUNCTIONS="['_add']"\n\nLaden des Moduls in Node.js
\nIn Node.js können wir das Wasm-Modul auf zwei Arten laden: synchron mit readFileSync oder asynchron mit WebAssembly.instantiate. Betrachten wir die fortgeschrittene asynchrone Variante mit Importen:
const fs = require('fs');\nconst path = require('path');\n\nasync function loadWasmModule(filePath, imports = {}) {\n const wasmBuffer = fs.readFileSync(path.resolve(filePath));\n const result = await WebAssembly.instantiate(wasmBuffer, imports);\n return result.instance;\n}\n\n(async () => {\n const instance = await loadWasmModule('./sum.wasm');\n const { add } = instance.exports;\n console.log('Ergebnis:', add(5, 7)); // 12\n})();\n\nHier ist imports ein Objekt, das JavaScript-Funktionen enthalten kann, die von Wasm aufgerufen werden (z. B. für Logging oder Speicherarbeit).
2. Speicherverwaltung: Arbeit mit dem linearen Speicher von Wasm
\n\nEine der zentralen Herausforderungen bei der fortgeschrittenen Arbeit mit Wasm ist die Verwaltung des linearen Speichers. Wasm-Module haben ihren eigenen isolierten Speicher (ArrayBuffer), auf den JavaScript direkt über instance.exports.memory zugreifen kann. Betrachten wir ein Beispiel, bei dem wir ein Zahlen-Array von JS an Wasm übergeben, es dort verarbeiten und das Ergebnis zurückerhalten.
Beispiel: Summierung eines Arrays in C
\n// sum_array.c\nint sum_array(int* arr, int len) {\n int sum = 0;\n for(int i = 0; i < len; i++) {\n sum += arr[i];\n }\n return sum;\n}\n\nKompilieren mit Speicherexport:
\nemcc sum_array.c -O3 -o sum_array.wasm --no-entry \\\n -s EXPORTED_FUNCTIONS="['_sum_array']" \\\n -s EXPORTED_RUNTIME_METHODS="['cwrap', 'getValue']"\n\nJavaScript-Code zur Datenübergabe
\nasync function sumArrayWithWasm(numbers) {\n const instance = await loadWasmModule('./sum_array.wasm');\n const memory = instance.exports.memory;\n const sumArray = instance.exports.sum_array;\n\n // Speicher für das Array reservieren (4 Bytes pro int)\n const bytesPerElement = 4;\n const offset = instance.exports.malloc ? \n instance.exports.malloc(numbers.length * bytesPerElement) : \n 0; // Wenn kein malloc vorhanden, feste Adresse verwenden\n\n // Uint32Array zum Schreiben der Daten erstellen\n const buffer = new Uint32Array(memory.buffer, offset, numbers.length);\n buffer.set(numbers);\n\n // Wasm-Funktion aufrufen\n const result = sumArray(offset, numbers.length);\n\n // Speicher freigeben (falls free vorhanden)\n if (instance.exports.free) {\n instance.exports.free(offset);\n }\n\n return result;\n}\n\n(async () => {\n const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n const sum = await sumArrayWithWasm(arr);\n console.log('Summe des Arrays:', sum); // 55\