Introduction au travail avancé avec WebAssembly et JavaScript dans Node.js
\n\nWebAssembly (Wasm) a depuis longtemps cessé d'être une technologie exotique réservée aux navigateurs. Dans l'écosystème Node.js, il ouvre des possibilités uniques pour exécuter des calculs haute performance écrits en C/C++, Rust ou Go, avec une surcharge minimale. Au niveau avancé, travailler avec Wasm dans Node.js nécessite une compréhension approfondie de la gestion de la mémoire, des appels asynchrones et de l'optimisation du transfert de données entre les environnements d'exécution. Dans cet article, nous analyserons des exemples pratiques d'intégration de WebAssembly avec JavaScript en utilisant Node.js.
\n\n1. Compilation et chargement des modules Wasm dans Node.js
\n\nPréparation du fichier Wasm
\nPour commencer, nous avons besoin d'un module Wasm compilé. Supposons que nous ayons une fonction C très simple qui additionne deux nombres :
\n\n// sum.c\nint add(int a, int b) {\n return a + b;\n}\n\nCompilons avec Emscripten ou wasm-pack :
\nemcc sum.c -O3 -o sum.wasm --no-entry -s EXPORTED_FUNCTIONS="['_add']"\n\nChargement du module dans Node.js
\nDans Node.js, nous pouvons charger un module Wasm de deux manières : via readFileSync synchrone ou WebAssembly.instantiate asynchrone. Examinons la variante asynchrone avancée avec import :
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('Résultat :', add(5, 7)); // 12\n})();\n\nIci, imports est un objet qui peut contenir des fonctions JavaScript appelées depuis Wasm (par exemple, pour la journalisation ou le travail avec la mémoire).
2. Gestion de la mémoire : travailler avec la mémoire linéaire de Wasm
\n\nL'une des principales difficultés du travail avancé avec Wasm est la gestion de la mémoire linéaire. Les modules Wasm ont leur propre mémoire isolée (ArrayBuffer), à laquelle JavaScript peut accéder directement via instance.exports.memory. Examinons un exemple où nous transmettons un tableau de nombres de JS à Wasm, le traitons et retournons le résultat.
Exemple : sommation d'un tableau en 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\nCompilons avec export de la mémoire :
\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\nCode JavaScript pour le transfert de données
\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 // Allouer de la mémoire pour le tableau (4 octets par int)\n const bytesPerElement = 4;\n const offset = instance.exports.malloc ? \n instance.exports.malloc(numbers.length * bytesPerElement) : \n 0; // Si pas de malloc, utiliser une adresse fixe\n\n // Créer un Uint32Array pour écrire les données\n const buffer = new Uint32Array(memory.buffer, offset, numbers.length);\n buffer.set(numbers);\n\n // Appeler la fonction Wasm\n const result = sumArray(offset, numbers.length);\n\n // Libérer la mémoire (si free existe)\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('Somme du tableau :', sum); // 55\