Introdução ao trabalho avançado com WebAssembly e JavaScript no Node.js
\n\nO WebAssembly (Wasm) já deixou de ser uma tecnologia exótica para navegadores. No ecossistema Node.js, ele abre possibilidades únicas para executar computações de alto desempenho, escritas em C/C++, Rust ou Go, com sobrecarga mínima. Em nível avançado, trabalhar com Wasm no Node.js exige um profundo entendimento do gerenciamento de memória, chamadas assíncronas e otimização da transferência de dados entre ambientes de execução. Neste artigo, analisaremos exemplos práticos de integração do WebAssembly com JavaScript, usando Node.js.
\n\n1. Compilação e carregamento de módulos Wasm no Node.js
\n\nPreparação do arquivo Wasm
\nPara começar, precisaremos de um módulo Wasm compilado. Suponha que temos uma função simples em C que soma dois números:
\n\n// sum.c\nint add(int a, int b) {\n return a + b;\n}\n\nCompilamos usando Emscripten ou wasm-pack:
\nemcc sum.c -O3 -o sum.wasm --no-entry -s EXPORTED_FUNCTIONS="['_add']"\n\nCarregando o módulo no Node.js
\nNo Node.js, podemos carregar um módulo Wasm de duas maneiras: através do síncrono readFileSync ou do assíncrono WebAssembly.instantiate. Vamos considerar a variante assíncrona avançada com importação:
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('Resultado:', add(5, 7)); // 12\n})();\n\nAqui, imports é um objeto que pode conter funções JavaScript chamadas a partir do Wasm (por exemplo, para registro ou trabalho com memória).
2. Gerenciamento de memória: trabalhando com a memória linear do Wasm
\n\nUma das principais complexidades do trabalho avançado com Wasm é o gerenciamento da memória linear. Os módulos Wasm têm sua própria memória isolada (ArrayBuffer), à qual o JavaScript pode acessar diretamente através de instance.exports.memory. Vamos considerar um exemplo onde passamos um array de números do JS para o Wasm, o processamos e retornamos o resultado.
Exemplo: soma de array em 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\nCompilamos com exportação de memória:
\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\nCódigo JavaScript para transferência de dados
\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 // Alocar memória para o array (4 bytes por int)\n const bytesPerElement = 4;\n const offset = instance.exports.malloc ? \n instance.exports.malloc(numbers.length * bytesPerElement) : \n 0; // Se não houver malloc, usar endereço fixo\n\n // Criar Uint32Array para escrever os dados\n const buffer = new Uint32Array(memory.buffer, offset, numbers.length);\n buffer.set(numbers);\n\n // Chamar a função Wasm\n const result = sumArray(offset, numbers.length);\n\n // Liberar memória (se houver free)\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('Soma do array:', sum); // 55\