Zig vs Assembly — Quando Usar Cada Um

Introdução

A questão “Zig ou Assembly?” pode parecer estranha à primeira vista — são linguagens de níveis de abstração bastante diferentes. Porém, Zig opera tão próximo do hardware que a pergunta é genuinamente relevante para desenvolvedores de sistemas embarcados, kernels, drivers e código de alta performance.

Este artigo analisa quando Zig é suficiente para trabalho de baixo nível e quando Assembly puro (ou inline assembly em Zig) é a escolha certa. Para comparações em nível mais alto, veja Zig vs C Moderno (C23) e Zig vs Fortran para Computação Científica.

O Que é Assembly e Por Que Ainda Importa

Assembly é a representação textual das instruções de máquina de um processador. Cada instrução Assembly corresponde diretamente a uma instrução que o CPU executa. Não há abstração — você controla registradores, endereços de memória e flags diretamente.

Assembly ainda é usado em cenários específicos:

  • Bootloaders e código de inicialização: As primeiras instruções executadas por um CPU precisam ser Assembly
  • Instruções específicas de CPU: Instruções de criptografia (AES-NI), virtualização (VMX), ou controle de cache
  • Rotinas de interrupção: Handlers de interrupção que precisam salvar/restaurar estado de registradores
  • Otimização extrema: Loops críticos onde cada ciclo importa
  • Exploração de segurança: Análise de vulnerabilidades e engenharia reversa

O Que Zig Oferece que Assembly Não Oferece

Portabilidade

Código Zig compila para mais de 40 arquiteturas. Um algoritmo escrito em Zig funciona em x86_64, ARM, RISC-V, WebAssembly e mais. Assembly é específico por arquitetura — código x86 não roda em ARM.

// Este código compila para QUALQUER arquitetura suportada
fn somaVetorial(a: []const f32, b: []const f32, resultado: []f32) void {
    for (a, b, resultado) |x, y, *r| {
        r.* = x + y;
    }
}

O compilador LLVM aplica otimizações específicas de cada arquitetura automaticamente, incluindo vetorização e uso de instruções SIMD quando possível. Veja nosso tutorial de cross-compilation em Zig.

Segurança em Tempo de Compilação e Runtime

Zig detecta erros que Assembly ignora silenciosamente:

const std = @import("std");

fn acessoSeguro(dados: []const u8, indice: usize) ?u8 {
    if (indice >= dados.len) return null;
    return dados[indice];
}

// Em modo debug, bounds checking automático:
fn exemplo(dados: []const u8) void {
    _ = dados[999]; // panic em debug se dados.len <= 999
}

Veja Tratamento de Erros em Zig e Padrões Errdefer.

Produtividade

Um algoritmo de ordenação em Zig ocupa 20 linhas. Em Assembly x86_64, o mesmo algoritmo pode facilmente ocupar 200 linhas. Zig oferece structs, enums, error handling, iteradores, e todas as abstrações necessárias sem custo de runtime.

Quando Assembly é Necessário

Código de Boot

O primeiro código executado por um processador precisa ser Assembly. Zig não pode configurar a stack inicial ou entrar em modo protegido sem assembly:

// Zig pode usar inline assembly para boot code
export fn _start() callconv(.naked) noreturn {
    asm volatile (
        \\mov $0x7C00, %esp
        \\call kmain
    );
    unreachable;
}

Instruções Específicas de CPU

Algumas instruções não têm equivalente em Zig e precisam de inline assembly:

// Ler o timestamp counter do CPU
fn rdtsc() u64 {
    var low: u32 = undefined;
    var high: u32 = undefined;
    asm volatile ("rdtsc"
        : "={eax}" (low), "={edx}" (high)
    );
    return (@as(u64, high) << 32) | low;
}

// Flush de cache line
fn clflush(addr: *const anyopaque) void {
    asm volatile ("clflush (%[addr])"
        :
        : [addr] "r" (addr)
        : "memory"
    );
}

Otimização de Loops Críticos

Em casos raros, o compilador LLVM pode não gerar o código ideal para um loop crítico. Inline assembly permite controle total:

// Cópia otimizada com REP MOVSB em x86_64
fn copiaRapida(dest: [*]u8, src: [*]const u8, n: usize) void {
    asm volatile (
        "rep movsb"
        :
        : [dest] "{rdi}" (dest),
          [src] "{rsi}" (src),
          [n] "{rcx}" (n)
        : "rdi", "rsi", "rcx", "memory"
    );
}

Inline Assembly em Zig

Zig oferece suporte robusto a inline assembly, o que significa que raramente é necessário escrever arquivos Assembly separados. A sintaxe de inline assembly de Zig é mais clara que a de C/GCC:

fn multiplicar_e_somar(a: u64, b: u64, c: u64) u64 {
    // Usando a instrução MULX + ADCX quando disponível
    var resultado: u64 = undefined;
    asm volatile (
        \\mulx %[b], %[low], %[high]
        \\add %[c], %[low]
        : [low] "=r" (resultado)
        : [a] "{rdx}" (a),
          [b] "r" (b),
          [c] "r" (c)
        : "flags"
    );
    return resultado;
}

SIMD: Zig vs Assembly Manual

Zig oferece tipos vetoriais nativos que o compilador traduz para instruções SIMD automaticamente:

const Vec4f = @Vector(4, f32);

fn somaSimd(a: Vec4f, b: Vec4f) Vec4f {
    return a + b; // Compila para addps/vaddps em x86
}

fn dotProduct(a: Vec4f, b: Vec4f) f32 {
    const prod = a * b;
    return @reduce(.Add, prod);
}

Em muitos casos, o código SIMD gerado por Zig via LLVM é tão bom quanto Assembly manual. Para os raros casos onde não é, inline assembly está disponível.

Tabela Comparativa

AspectoZigAssembly
Portabilidade40+ arquiteturasUma arquitetura
ProdutividadeAltaMuito baixa
PerformanceExcelente (LLVM)Máxima (se bem escrito)
SegurançaBounds checking, type safetyNenhuma
DebugStack traces, debuggerDifícil
ManutençãoBoaMuito difícil
Curva de aprendizadoModeradaMuito alta
Inline assemblySuportadoN/A

Recomendações Práticas

Use Zig para:

  • 99% do código de sistemas, incluindo kernels e drivers
  • Código de alta performance que precisa ser portável
  • Projetos onde manutenibilidade importa
  • SIMD via tipos vetoriais nativos
  • Qualquer código que será mantido por mais de uma pessoa

Use Assembly (ou inline assembly em Zig) para:

  • Código de boot e inicialização de hardware
  • Instruções específicas de CPU sem equivalente em Zig
  • Os 1% de loops críticos onde o compilador não gera o código ideal
  • Handlers de interrupção que exigem controle total de registradores
  • Análise de segurança e engenharia reversa

Conclusão

Na prática, Zig elimina a necessidade de Assembly para a grande maioria dos cenários de programação de sistemas. O suporte a inline assembly garante que, quando Assembly é realmente necessário, ele pode ser integrado diretamente no código Zig sem arquivos separados.

A recomendação é escrever em Zig primeiro, medir performance, e recorrer a inline assembly apenas quando medições comprovarem que o compilador não está gerando código suficientemente rápido. Na maioria dos casos, o LLVM faz um trabalho excelente.

Para começar com Zig, visite Introdução ao Zig. Para performance, confira nosso guia de benchmarking e testes unitários.

Continue aprendendo Zig

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