Introdução ao trabalho avançado com WebAssembly e TypeScript
\n\nO WebAssembly (WASM) já deixou de ser uma tecnologia experimental há muito tempo. Hoje, é uma ferramenta poderosa para resolver problemas críticos de desempenho: processamento de imagens, criptografia, simulações, compiladores e motores de jogos. No entanto, carregar um módulo WASM simplesmente no navegador é apenas a ponta do iceberg. Em um nível avançado, os desenvolvedores enfrentam a necessidade de gerenciamento eficiente de memória, transferência de estruturas de dados complexas e integração profunda com TypeScript.
\n\nNeste artigo, abordaremos as principais técnicas que permitirão que você alcance um novo nível de domínio da combinação WebAssembly + TypeScript. Você aprenderá como otimizar a transferência de dados, trabalhar com memória linear, criar abstrações de alto desempenho e depurar módulos WASM.
\n\n1. Gerenciamento de memória linear e transferência de estruturas complexas
\n\nUma das principais características do WebAssembly é o trabalho com memória linear. Ao contrário do JavaScript, onde você trabalha com objetos e coleta de lixo, no WASM você manipula bytes diretamente. Para um trabalho avançado, é necessário saber alocar, liberar e transferir memória de forma eficiente entre os dois ambientes.
\n\n1.1. Alocação de memória e trabalho com ponteiros
\n\nAo compilar código C/C++ via Emscripten ou ao usar AssemblyScript, você obtém as funções malloc e free. Em TypeScript, podemos chamá-las diretamente para gerenciamento manual de memória.
// TypeScript\ninterface WasmExports {\n malloc: (size: number) => number;\n free: (ptr: number) => void;\n processData: (inputPtr: number, inputLen: number) => number;\n}\n\nconst wasmModule: WebAssembly.Instance = await loadWasm();\nconst exports = wasmModule.exports as unknown as WasmExports;\n\n// Criamos um array de números na memória WASM\nconst data = new Float32Array([1.5, 2.7, 3.14, 4.0]);\nconst bytesPerElement = Float32Array.BYTES_PER_ELEMENT;\nconst totalBytes = data.length * bytesPerElement;\n\n// Alocamos memória e copiamos os dados\nconst ptr = exports.malloc(totalBytes);\nconst wasmMemory = new Float32Array(\n (wasmModule.exports.memory as WebAssembly.Memory).buffer,\n ptr,\n data.length\n);\nwasmMemory.set(data);\n\n// Chamamos a função WASM\nconst resultPtr = exports.processData(ptr, data.length);\n\n// Lemos o resultado\nconst result = new Float32Array(\n (wasmModule.exports.memory as WebAssembly.Memory).buffer,\n resultPtr,\n 1\n);\nconsole.log('Resultado:', result[0]);\n\n// Liberamos a memória\nexports.free(ptr);\nexports.free(resultPtr);\n\n1.2. Transferência de strings e objetos
\n\nStrings no WASM são simplesmente sequências de bytes. Para transferi-las do TypeScript, é necessário codificar a string em UTF-8 e passar o ponteiro para o início dos dados junto com o comprimento.
\n\n// Função auxiliar para codificar string\nfunction stringToWasm(str: string, exports: WasmExports): { ptr: number; len: number } {\n const encoder = new TextEncoder();\n const encoded = encoder.encode(str);\n const ptr = exports.malloc(encoded.length);\n const wasmBuffer = new Uint8Array(\n (exports.memory as WebAssembly.Memory).buffer,\n ptr,\n encoded.length\n );\n wasmBuffer.set(encoded);\n return { ptr, len: encoded.length };\n}\n\n// Uso\nconst { ptr, len } = stringToWasm("Olá, WASM!", exports);\nconst resultPtr = exports.processString(ptr, len);\nconst result = readCString(resultPtr, exports); // função de leitura de string\nexports.free(ptr);\nexports.free(resultPtr);\n\n2. Otimização de desempenho: minimizando transições
\n\nCada chamada do TypeScript para o WASM e vice-versa tem um custo indireto. Para aplicações de alto desempenho (por exemplo, um motor físico ou processamento de vídeo em tempo real), o número dessas transições deve ser mínimo.
\n\n2.1. Processamento em lote de dados
\n\nEm vez de chamar uma função WASM para cada elemento em massa