Spinel - compilador nativo AOT para Ruby
(github.com/matz)- Compilador AOT que realiza inferência de tipos do programa inteiro no código-fonte Ruby, converte para código C e gera binários nativos autônomos
- Desenvolvido diretamente por matz, criador do Ruby; o próprio backend do compilador é escrito em Ruby, em uma estrutura self-hosting que compila a si mesmo
- Cerca de 11,6x mais rápido que o miniruby (Ruby 4.1.0dev); Conway's Game of Life teve ganho de 86,7x, ackermann 74,8x e mandelbrot 58,1x
- O pipeline de compilação usa um parser baseado em Prism para converter Ruby em texto de AST, depois o backend self-hosting faz a inferência de tipos e a geração de código C, e um compilador C padrão cria um binário standalone
- Suporte a uma ampla gama de recursos do Ruby, incluindo classes, herança, blocos, tratamento de exceções, Fiber, engine de Regexp NFA embutida, Bigint com promoção automática e pattern matching
- Classes pequenas com até 8 campos escalares são alocadas automaticamente na stack como value types, eliminando completamente o overhead de GC (1 milhão de alocações: 85ms → 2ms)
- Encadeamento de strings
a + b + c + dé achatado em um único malloc, esplitdentro de loops reutilizasp_StrArraypara eliminar alocações desnecessárias - Diversas otimizações em tempo de compilação, como hoisting de comprimento invariante de loop, propagação de constantes, inline automático para métodos com até 3 instruções e encerramento antecipado da inferência iterativa (~14% de redução no tempo de bootstrap)
- Os binários gerados têm zero dependências de runtime e podem ser executados apenas com libc + libm
eval, metaprogramação, Thread e Mutex não são suportados (apenas Fiber é suportado)- Licença MIT
1 comentários
Opiniões no Hacker News
Se foi o Matz que fez, então ele certamente conhece bem os limites da semântica do Ruby, o que passa confiança
Minha dissertação de mestrado também foi sobre um compilador AOT de JS; ele até funcionava, mas as restrições sobre os dados de entrada eram grandes demais, então acabei abandonando
Na época, os desenvolvedores de JS não estavam acostumados a seguir esse tipo de restrição por conta própria, e entradas inerentemente desconhecidas, como
JSON.parse, eram um grande obstáculoHoje, com TypeScript, isso pode ser bem mais viável do que naquela época
Mesmo olhando apenas para o cálculo lambda em geral, os limites da inferência de tipos são claros, e artigos do Matt Might ou trabalhos como Shed-skin para Python mostram restrições parecidas
Fico curioso para saber o quão comuns são
eval,send,method_missingedefine_methodem código Ruby real, e também como normalmente lidam com parsing sem tipos, por exemplo entradas JSONFazer o parsing de Ruby é tão difícil que chega a ser mais complicado do que a própria tradução, então eles usam o Prism, e o resultado gerado é C
A semântica básica do Ruby em si não é tão difícil de implementar assim
Em contrapartida, eu estou preso a um antigo compilador AOT self-hosted feito em Ruby puro, e escolhi de propósito um caminho muito mais difícil ao insistir em usar um parser próprio
Aprendi cedo que os primeiros 80% podem ser montados mais ou menos e ainda assim uma boa parte do código Ruby já roda; o verdadeiro "segundo 80%" difícil está concentrado nas coisas que o Matz deixou de fora neste projeto e no mruby, como codificação e todo tipo de recurso periférico
Sinceramente, o Ruby tem várias funcionalidades que eu nunca vi em código real, então não acharia estranho se algumas fossem descontinuadas
send,method_missingedefine_methodsão muito comunsAs restrições são parecidas com as do mruby, e ainda existe espaço de uso mesmo dentro delas
Suporte a
send,method_missingedefine_methodé relativamente fácilJá suporte a eval() é extremamente doloroso
Ainda assim, uma grande parte do uso de
eval()em Ruby pode ser reduzida estaticamente à versão com bloco deinstance_eval, e nesses casos a compilação AOT fica bem mais simplesPor exemplo, se a string passada para
eval()puder ser conhecida estaticamente ou decomposta, há bastante margem para resolverNa prática, muitos usos de
eval()são desnecessários ou apenas uma forma simples de contornar limitações de introspecção, então dá para tratar com análise estáticaNo meu compilador, se isso virar gargalo, pretendo atacar essa parte primeiro
Ingestão de JSON sem tipos provavelmente também tende a usar esses mecanismos
Se você remover isso, sobra uma linguagem pequena e fácil de ler, não tão fortemente tipada quanto Crystal, mas também sem depender de metaprogramação tanto quanto o Ruby oficial
Então o potencial parece bem grande, mas no fim só o tempo vai dizer
evalcom frequênciaTalvez desse para não usar, mas para mim ele é mais ergonômico
eval,exec,define_methode o padrão de criar novas classes comClass.neweStruct.newA maior parte desses usos se concentra no boot da aplicação ou enquanto arquivos estão sendo carregados com require, o que em certo sentido já é parecido com uma etapa de compilação
Isso foi apresentado agora há pouco pelo Matz na RubyKaigi 2026
É experimental, mas foi feito em cerca de um mês com ajuda do Claude, e a demo ao vivo deu certo
O nome veio do novo gato do Matz, e o nome do gato veio do gato de Card Captor Sakura, que por sua vez faz par com uma personagem chamada Ruby
No caso de alguém como o Matz, talvez seja mais como levar de 100x para 500x
https://en.wikipedia.org/wiki/Spinel
Parece que o vídeo ainda não está ao vivo, e eles vão subindo um por um neste canal
https://www.youtube.com/@rubykaigi4884/videos
O nome do projeto também dá a sensação de ter sido escolhido de forma emocional
Sem dúvida é muito impressionante, mas parece impossível de manter sem um agente de IA
spinel_codegen.rbtem 21 mil linhas, e alguns métodos chegam a 15 níveis de aninhamentoCódigo de compilador já tende a ser difícil de deixar bonito, mas isso parece muito complicado de manter por humanos até mesmo para esse padrão
Compiladores têm fronteiras de subsistemas bem definidas e handoffs claros entre etapas, então, na verdade, estão entre as coisas mais fáceis de modularizar
O problema costuma ser fazer funcionar primeiro e depois nunca sobrar tempo para refatorar, e aí a sujeira só vai crescendo
spinel_codegen.rbestá quase no nível de um horror lovecraftianoQuando uso Claude, meu código sempre acaba virando esse tipo de espaguete, então eu estava me perguntando se estava fazendo algo errado
Mas ver qualidade de código bem ruim em vários pontos até num projeto realmente interessante feito por alguém que considero um programador de altíssimo nível me mostrou que não sou só eu
Por exemplo,
infer_comparison_type()não é o pior caso e nem é ilegível, mas existe uma implementação muito mais simples e clara, e o Claude não chega nelaSe reunisse os operadores de comparação em um
Sete tratasse cominclude?, ficaria mais curto, mais rápido, mais legível e mais fácil de manterMas o Claude sempre cai numa cadeia de if-return, e até parece desconfortável até com if-else
Minha base de código gerada pelo Claude também está cheia desse padrão, então agora sei que não sou o único
Em compensação, os outros arquivos estão bem melhores, especialmente o diretório
lib, que parece corresponder ao diretórioextdo repositório principal do Ruby e tem qualidade razoávelA API também é claramente influenciada pelo MRI Ruby, e mesmo que a implementação seja bem diferente, parece que o Matz guiou para que ela lembrasse parte da API original, então a saída ficou mais organizada
[1] https://github.com/matz/spinel/blob/98d1179670e4d6486bbd1547...
Se os testes e benchmarks passarem, já fico satisfeito por enquanto
Ainda assim, fico em dúvida se arquivos gigantes também são fáceis para IA lidar
Eu tento manter arquivos abaixo de 300 linhas e acho que código fácil para humanos entenderem também deve ser mais fácil para agentes de código
As restrições parecem ser estas
Sem eval:
eval,instance_eval,class_evalSem metaprogramação:
send,method_missing,define_method(dinâmico)Sem threads:
Thread,Mutex(Fiber é suportado)Sem encoding: suposição de UTF-8/ASCII
Sem cálculo lambda geral:
-> x { }profundamente aninhado com chamada de[]A suposição de UTF-8/ASCII, pessoalmente, não me parece uma limitação tão grande, mas o restante parece ser uma restrição real para bastante programa
E parece que dar suporte a isso de novo exigiria bastante trabalho
Uso Ruby há muito tempo e já usei todos os recursos listados, e olhando por esse lado, o que eu acabei querendo com o tempo foi justamente uma versão assim de Ruby simples
É mais simples e mais fácil de entender, mas ainda preserva a estética própria do Ruby
Agora, com LLMs, a produtividade de geração de código é tão alta que já há menos necessidade de reduzir boilerplate com metaprogramação em nome da produtividade do desenvolvedor
Isso porque o peso do código escrito manualmente pelo próprio desenvolvedor está diminuindo
A sintaxe é parecida e há um sistema de tipos estático, o que leva a código compilado mais eficiente
eval, mas a ausência de threads e mutexes é uma penaA falta de
define_methodeu entendo pelo tipo de uso que ele temMas
sendemethod_missingsão comuns em bibliotecas existentes, e também não me parecem tão difíceis de implementar, por exemplo montando uma tabela de lookup em memória em tempo de compilaçãoEntão não sei se isso foi removido de propósito ou se simplesmente ainda não chegaram lá
Espero que seja a segunda opção, mas ao menos por enquanto a compatibilidade deve impedir uso em produção
Sempre foi reduzir a quantidade de código que precisa ser lida
Isso é muito legal, e eu espero há muito tempo por um compilador AOT para Ruby
Só é uma pena não haver fallback para
evalou metaprogramação, mas imagino que a ideia tenha sido focar num subconjunto pequeno e de alto desempenhoEu gostaria que gems feitas com esse compilador AOT interagissem bem com o MRI
Empacotar ou fazer bundle do Ruby padrão com gems ainda depende de tebako, kompo, ocran, e antes também havia projetos como ruby-packer, traveling ruby e jruby warbler
É bom ter mais uma opção, mas continuo torcendo por uma solução definitiva com UX melhor para desenvolvedores
Fazia tempo demais que ele não recebia atualizações
Fico curioso sobre o motivo de não haver threads
O scheduler do Ruby e a implementação subjacente em pthread parecem coisas que poderiam funcionar bem também no lado C, então me pergunto se a ideia era buscar dependência zero
Se não houver plano de adicionar isso como extensão opcional depois, ou se não for algo apenas ainda não implementado, essa escolha parece meio estranha
Acho mais provável que simplesmente ainda não tenham chegado lá
Multithreading já é algo muito difícil de fazer direito por natureza
É surpreendente que isso tenha sido feito em pouco mais de um mês
Pode-se dizer o que for sobre IA, mas nas mãos de um desenvolvedor talentoso ela produz um ganho enorme de velocidade
Já o Matz parece funcionar só com
gem env|infoefindComo isso foi feito pelo próprio Matz, fico curioso sobre o quão realista é a chance de isso virar parte do core do Ruby no futuro
E, se isso acontecer, também me pergunto o quanto isso seria uma ameaça para o Crystal
Essas características são praticamente indispensáveis para compilar e manter programas grandes
Já isso aqui é voltado a um subconjunto limitado de Ruby, então a maioria dos gems populares de Ruby provavelmente não vai rodar como está
Como subconjunto de linguagem voltado a compilar para C, isso parece mais próximo de PreScheme
Neste estágio, eu não diria que os dois estão competindo diretamente no mesmo espaço
Ruby completo quase certamente vai precisar de JIT
[1]: https://prescheme.org/
Seria a revanche de ferramentas como Rational Unified Process e Enterprise Architect
A diferença é que, no lugar de diagramas UML, viriam arquivos markdown
Isso parece útil para o lado de ferramentas de infraestrutura
Por exemplo, dá para imaginar um bundler escrito em Ruby, mas compilado estaticamente, que também cumpra o papel de ferramenta de instalação do Ruby como o RVM
Os buildpacks Ruby atuais são escritos em Ruby, mas exigem bootstrap com bash, o que é irritante e cria edge cases
O CNB foi escrito em Rust para evitar esse problema, e a ideia de distribuir um binário único sem dependências é realmente muito poderosa