Zig e WebAssembly — Resolver Problemas com WASM

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

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.