JIT em 5 microssegundos: como o pgrust compila toda query SQL em tempo real
Autor de banco de dados escrito em Rust mostra a técnica de copy-and-patch que gera assembly ARM64 direto, sem LLVM, e credita a IA por baixar a barreira de entrada.

Um post publicado no blog malisper.me e discutido no Hacker News detalha como o autor construiu um compilador JIT que gera código em torno de 5 microssegundos, rápido o suficiente para compilar toda query SQL↳SQL64 conteúdosSQL Server – Como evitar SQL Injection?Data · mai 2019Azure SQL DB Managed InstanceData · abr 2019SQL Server – Como evitar SQL Injection? Pare de utilizar Query Dinâmica como EXEC(@Query)Data · abr 2019Ver tudo em Data →, e não apenas um subconjunto delas. A técnica está no coração do pgrust, um banco de dados que ele vem escrevendo em Rust, e o texto reconstrói o método passo a passo usando um motor de expressões regulares como exemplo didático.
Por que compilar em microssegundos importa
JIT (Just In Time) é a prática de gerar código de máquina em tempo de execução, aproveitando informações que só existem no runtime. Segundo o autor, feito corretamente, isso rende ganhos de 2x a 5x, às vezes mais. O caso de uso clássico são interpretadores de linguagem, que só recebem o código a executar em runtime, mas o mesmo vale para parsing de dados cujo schema é desconhecido de antemão, exatamente a situação de um banco processando SQL arbitrário.
O problema histórico é o custo de compilar. O autor afirma que nenhum banco de dados pronto para produção tem hoje um compilador JIT próprio: todos usam LLVM ou geram código C/C++, e as duas rotas sofrem com tempos de compilação altos. É por isso que sistemas como o PostgreSQL só acionam JIT (via LLVM) para queries caras, quando o custo de compilar se paga. Se cada compilação leva microssegundos em vez de milissegundos, essa conta muda: dá para compilar tudo.
Copy-and-patch: stencils em vez de LLVM
A abordagem escolhida é uma variante de copy-and-patch. A ideia é manter templates de assembly para cada operação que se quer compilar, chamados de stencils. Para compilar uma operação, pega-se o stencil correspondente e fazem-se pequenos ajustes conforme os detalhes, como preencher um estêncil de verdade. Encadeando vários stencils preenchidos, monta-se em runtime um programa com desempenho próximo ao de código escrito à mão.
No exemplo do artigo, um motor de regex minimalista (só literais e repetição ) é representado por uma AST em Rust com três nós: Literal, Concatenation e Repetition. O interpretador ingênuo dessa AST tem menos de 20 linhas, mas o benchmark mostra que ele é 10x a 20x mais lento que uma versão escrita à mão especificamente para o regex b(an). É essa lacuna que o JIT precisa fechar.
A geração de código ocorre em ARM64 no macOS. Cada stencil é uma função Rust que devolve um array de instruções de 32 bits, com os pontos variáveis preenchidos por manipulação de bits. O stencil de comparação de caractere, por exemplo, insere o byte a comparar e o deslocamento do salto de fallback direto no opcode:
fn stencil_char(byte: u8, stencil_pos: usize, fail_pos: usize) -> [u32; 4] {
[
0x39400009, // ldrb w9, [x0]
0x7100013F | ((byte as u32) << 10), // cmp w9, #byte
0x54000001 | cond_branch_offset(stencil_pos + 2, fail_pos), // b.ne fail
0x91000400, // add x0, x0, #1
]
}Algumas decisões de design deixam o código gerado enxuto: a string termina em byte nulo, então comparações de caractere falham automaticamente no fim da entrada, dispensando checagens de tamanho; e o backtracking usa uma pilha que guarda o endereço de retomada e a posição na string. Os registradores são fixados (x0 para posição e retorno, x1 e x2 para topo e base da pilha, x9 como temporário).
Do buffer à função executável
Para transformar os bytes gerados em algo chamável, o autor usa mmap para alocar memória com permissões de leitura, escrita e execução, marcada com MAP_JIT. No macOS com o modelo W^X da Apple, é preciso alternar a proteção com pthread_jit_write_protect_np antes e depois de copiar o código, e invalidar o cache de instruções com sys_icache_invalidate. Feito isso, o buffer é convertido em ponteiro de função via std::mem::transmute e chamado como qualquer código Rust.
Os números
O benchmark comparou interpretador, JIT e versão escrita à mão em diferentes tamanhos de entrada:
| Tamanho | Interpretador | JIT | Handwritten | Speedup JIT | | --- | --- | --- | --- | --- | | 9 | 45 ns | 3,8 ns | 3,8 ns | 11,7x | | 33 | 103 ns | 7,9 ns | 10,5 ns | 13,0x | | 129 | 597 ns | 30 ns | 32 ns | 19,7x | | 513 | 1.955 ns | 126 ns | 120 ns | 15,5x | | 2.049 | 8.301 ns | 470 ns | 393 ns | 17,7x |
O resultado central: o código JIT fica empatado com a versão manual, ora um pouco mais rápido, ora um pouco mais lento, e ambos superam o interpretador em mais de uma ordem de grandeza. Como o custo de compilar é de cerca de 5μs, ele se dilui rapidamente em qualquer execução repetida.
A tese sobre IA
O ângulo que rendeu a discussão no Hacker News é a afirmação do autor de que a IA↳Inteligência artificial440 conteúdosUX e IA: Transformando Experiências Digitais com Inteligência ArtificialProduto & UX · jan 2025MCP: O que é e por que você vai ouvir falar disso em breve?AI · jul 2025IA generativa e a urgência de reconstruir nossa relação com a verdadeAI · jun 2025Ver tudo em AI → foi decisiva. Ele conta que nunca havia escrito assembly de verdade, tendo só passado pelo CTF microcorruption, e que teria dificuldade real para acertar as instruções e os ajustes de bits sem ajuda. Com um agente de código, diz, bastou passar a forma geral do compilador para que os detalhes de baixo nível fossem resolvidos.
A partir daí ele desafia o meme de que "IA não ajuda porque escrever código nunca foi a parte difícil". Para compiladores JIT, argumenta, escrever o código era a parte difícil, e a raridade desse tipo de software na prática sugere que implementá-lo historicamente era custoso demais para valer a pena. A conclusão é a tese do próprio projeto: se bancos de dados sempre estiveram entre os softwares mais difíceis de construir, LLMs abrem espaço para projetos mais ambiciosos nesse território.
O que fica em aberto
O exemplo do post é um brinquedo: regex sem alternância, sem lookbehind e com a AST já parseada. Ele demonstra a mecânica, mas não a complexidade de compilar SQL real, com joins, agregações e tipos variados. O código também é específico para ARM64 no macOS, deixando de fora x86-64 e Linux↳Linux34 conteúdosKali Linux em um Servidor VPS: como, quando e por que usar?DevSecOps · dez 2024Construindo um Windows Service ou Linux Daemon com Worker Service & .NET Core – Parte 2Dev (Back & Front) · jul 2020Criando uma WebApi utilizando .NET, Linux e VSCodeDev (Back & Front) · ago 2019Ver tudo em DevSecOps →, o que importa para quem roda banco em servidor. E, sendo uma abordagem de copy-and-patch escrita à mão com auxílio de IA, a manutenção e a correção dos stencils gerados por assembly continuam sendo responsabilidade de quem revisa, um ponto que o próprio autor deixa implícito ao descrever a IA como ferramenta para "lidar com os detalhes".
Para quem constrói runtimes, engines de dados ou interpretadores no Brasil, o take prático é que a barreira de escrever um JIT próprio, em vez de depender de LLVM, ficou mais baixa, e que compilar em microssegundos habilita aplicar otimização em tempo real de forma abrangente. O código completo e o projeto estão no GitHub do pgrust.
Fonte: Hacker News
Este artigo foi escrito por Redação iMasters, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters. Saiba como produzimos no expediente.









Comentários
Ninguém comentou ainda. Começa a conversa?