Zig e WebAssembly — Resolver Problemas com WASM
Zig compila nativamente para WebAssembly, produzindo binários pequenos e eficientes. Este guia resolve os problemas mais comuns ao trabalhar com WASM.
Escolhendo o Target WASM Correto
# WASM para browser (sem sistema operacional)
zig build -Dtarget=wasm32-freestanding
# WASM com WASI (para runtimes como Wasmtime, Wasmer)
zig build -Dtarget=wasm32-wasi
# Diferenças:
# freestanding: sem I/O, sem filesystem, para browser
# wasi: com I/O básico, para server-side WASM
Erro: Funções Não Exportadas
Sintoma: JavaScript não consegue chamar funções do WASM.
// PROBLEMA: funções não são exportadas por padrão
// SOLUÇÃO: usar export
export fn somar(a: i32, b: i32) i32 {
return a + b;
}
// Ou usar callconv(.C) para compatibilidade
fn multiplicar(a: i32, b: i32) callconv(.C) i32 {
return a * b;
}
// No build.zig — garantir exports
const lib = b.addSharedLibrary(.{
.name = "meu-modulo",
.root_source_file = b.path("src/lib.zig"),
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.os_tag = .freestanding,
}),
.optimize = optimize,
});
// Exportar símbolos específicos
lib.export_symbol_names = &.{ "somar", "multiplicar" };
// Ou não definir entry point para library
lib.entry_point = null;
Erro: “memory out of bounds”
Causa: O WASM tem memória limitada por padrão.
// No build.zig — aumentar memória inicial
lib.initial_memory = 65536 * 16; // 1MB
lib.max_memory = 65536 * 256; // 16MB
// Ou deixar a memória crescer
lib.import_memory = true; // Memória gerenciada pelo host
// No JavaScript — definir memória
const memory = new WebAssembly.Memory({
initial: 256, // 256 páginas = 16MB
maximum: 1024, // 1024 páginas = 64MB
});
const importObject = {
env: { memory: memory }
};
Problema: Carregar WASM no Browser
// Método moderno de carregar WASM
async function carregarWasm() {
const response = await fetch('modulo.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
env: {
// Imports que o Zig precisa
}
});
// Chamar funções exportadas
const resultado = instance.exports.somar(2, 3);
console.log('Resultado:', resultado); // 5
}
carregarWasm();
Problema: std.debug.print Não Funciona
Causa: Em WASM freestanding, não existe console/stdout.
// PROBLEMA: não funciona em WASM freestanding
// std.debug.print("Hello\n", .{});
// SOLUÇÃO 1: Importar função de log do JavaScript
extern fn js_log(ptr: [*]const u8, len: usize) void;
fn log(msg: []const u8) void {
js_log(msg.ptr, msg.len);
}
export fn processar() void {
log("Processando...");
}
// No JavaScript — fornecer a função de log
const importObject = {
env: {
js_log: (ptr, len) => {
const bytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
const text = new TextDecoder().decode(bytes);
console.log(text);
}
}
};
Problema: Compartilhar Strings entre Zig e JavaScript
// Zig: exportar ponteiro e tamanho
var resultado_global: []const u8 = "";
export fn processar_texto(ptr: [*]const u8, len: usize) void {
const input = ptr[0..len];
// ...processar input...
_ = input;
}
export fn get_resultado_ptr() [*]const u8 {
return resultado_global.ptr;
}
export fn get_resultado_len() usize {
return resultado_global.len;
}
// JavaScript: enviar e receber strings
function enviarString(instance, texto) {
const encoder = new TextEncoder();
const bytes = encoder.encode(texto);
// Copiar para memória WASM
const ptr = instance.exports.alocar(bytes.length);
const mem = new Uint8Array(instance.exports.memory.buffer);
mem.set(bytes, ptr);
instance.exports.processar_texto(ptr, bytes.length);
}
Problema: Allocator em WASM
// WASM freestanding não tem malloc por padrão
// Use FixedBufferAllocator ou page_allocator
// Opção 1: Buffer fixo
var buffer: [65536]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
// Opção 2: Page allocator (funciona em WASM)
const allocator = std.heap.page_allocator;
// Opção 3: Exportar allocator para JavaScript controlar
export fn alocar(tamanho: usize) ?[*]u8 {
const slice = std.heap.page_allocator.alloc(u8, tamanho) catch return null;
return slice.ptr;
}
export fn liberar(ptr: [*]u8, tamanho: usize) void {
std.heap.page_allocator.free(ptr[0..tamanho]);
}
Problema: Tamanho do WASM Grande
# Compilar para tamanho mínimo
zig build -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall
# Verificar tamanho
ls -la zig-out/lib/modulo.wasm
wc -c zig-out/lib/modulo.wasm
# Usar wasm-opt para otimizar ainda mais (binaryen)
wasm-opt -Oz modulo.wasm -o modulo-opt.wasm
# Remover seções desnecessárias
wasm-strip modulo.wasm
WASI: Rodar Fora do Browser
# Compilar para WASI
zig build -Dtarget=wasm32-wasi
# Executar com Wasmtime
wasmtime ./zig-out/bin/meu-app.wasm
# Executar com Wasmer
wasmer ./zig-out/bin/meu-app.wasm
# Com acesso a diretório
wasmtime --dir=. ./zig-out/bin/meu-app.wasm
Debugging WASM
# Compilar com debug info
zig build -Dtarget=wasm32-freestanding
# No browser: Chrome DevTools > Sources > WASM
# Firefox também suporta debugging de WASM
# Usar wasm2wat para inspecionar o módulo (wabt tools)
wasm2wat modulo.wasm -o modulo.wat
Veja Também
- FAQ Performance — WASM e performance
- Cross-Compile Falha — Compilação cruzada
- FAQ Build System — Configuração de targets
- Embedded Debug — Ambientes restritos
- Receitas — Exemplos de WASM com Zig