Spinel - Kompiler Native Ruby AOT
(github.com/matz)- Kompiler AOT yang mengubah source Ruby menjadi kode C setelah melakukan inferensi tipe seluruh program, lalu menghasilkan biner native yang dapat dieksekusi secara mandiri
- Dikembangkan langsung oleh pencipta Ruby, matz, dengan backend kompiler itu sendiri ditulis dalam Ruby, menggunakan struktur self-hosting yang mengompilasi dirinya sendiri
- Kinerja sekitar 11,6x lebih cepat dibanding miniruby(Ruby 4.1.0dev); Conway's Game of Life 86,7x, ackermann 74,8x, dan mandelbrot 58,1x lebih cepat
- Pipeline kompilasi mengubah Ruby menjadi teks AST dengan parser berbasis Prism, lalu backend self-hosting melakukan inferensi tipe dan generasi kode C, kemudian kompiler C standar membuat biner standalone
- Mendukung berbagai fitur Ruby secara luas seperti class, inheritance, block, exception handling, Fiber, engine Regexp NFA bawaan, Bigint dengan promosi otomatis, pattern matching, dan lainnya
- Class kecil dengan 8 field skalar atau kurang akan dialokasikan otomatis di stack sebagai value type, sehingga overhead GC hilang sepenuhnya (alokasi 1 juta objek 85ms → 2ms)
- String chaining
a + b + c + dakan diratakan dengan satu malloc, dansplitdi dalam loop akan menggunakan ulang sp_StrArray untuk menghilangkan alokasi yang tidak perlu - Berbagai optimisasi waktu kompilasi termasuk hoisting panjang yang invariant terhadap loop, constant propagation, inline otomatis untuk method 3 pernyataan atau kurang, serta penghentian dini inferensi berulang (~14% pengurangan waktu bootstrap)
- Biner yang dihasilkan tidak memiliki dependensi runtime, dan dapat dijalankan hanya dengan libc + libm
eval, metaprogramming, Thread, dan Mutex tidak didukung (hanya Fiber yang didukung)- Lisensi MIT
1 komentar
Komentar Hacker News
Karena ini buatan Matz, rasanya lebih mudah dipercaya karena dia pasti paham betul batasan semantics Ruby
Tesis master saya juga dulu tentang AOT JS compiler, dan memang sempat jalan, tapi akhirnya saya hentikan karena batasan pada data input terlalu besar
Waktu itu para developer JS belum terbiasa disiplin menjaga batasan semacam itu, dan input yang secara inheren tidak bisa diketahui seperti
JSON.parsemenjadi penghambat utamaSekarang, berkat TypeScript, mungkin ini jauh lebih realistis dibanding saat itu
Bahkan kalau hanya melihat lambda calculus umum, batas inferensi tipe sudah jelas, dan pembatasan serupa juga terlihat di paper-paper Matt Might maupun proyek Shed-skin Python
Saya penasaran seberapa sering
eval,send,method_missing, dandefine_methodmuncul di kode Ruby nyata, dan juga bagaimana biasanya input parsing tanpa tipe, misalnya input JSON, ditanganiParsing Ruby bahkan bisa lebih sulit daripada proses translasi itu sendiri, jadi mereka memakai Prism, lalu menghasilkan C sebagai output
Semantics dasar Ruby sendiri sebenarnya tidak sampai sesulit itu untuk diimplementasikan
Sebaliknya, saya sedang berkutat dengan self-hosting AOT compiler lama yang ditulis dalam Ruby murni, dan karena saya ngotot memakai parser buatan sendiri, saya sengaja memilih jalan yang jauh lebih sulit
Saya belajar sejak awal bahwa 80% pertama bisa dibuat asal jalan saja dan ternyata sudah cukup untuk menjalankan banyak kode Ruby, sedangkan “80% kedua” yang benar-benar sulit terkumpul pada hal-hal yang dihilangkan Matz dari proyek ini dan dari mruby, misalnya encoding dan segala fitur pinggiran lain
Jujur saja, Ruby punya cukup banyak fitur yang belum pernah saya lihat dipakai di kode nyata, jadi saya rasa tidak aneh kalau beberapa di antaranya akhirnya deprecated
send,method_missing, dandefine_methodsangat umumBatasannya mirip mruby, dan tetap ada kegunaannya bahkan dengan batasan seperti itu
Dukungan untuk
send,method_missing, dandefine_methodrelatif mudahSebaliknya, dukungan eval() sangat menyakitkan
Namun, porsi besar
eval()di Ruby sebenarnya bisa direduksi secara statis menjadi versi blok dari instance_eval, dan dalam kasus seperti itu AOT compilation jadi cukup mudahMisalnya, kalau string yang masuk ke
eval()bisa diketahui atau diurai secara statis, ruang penyelesaiannya jadi jauh lebih besarDalam praktiknya, banyak penggunaan
eval()itu sebenarnya tidak perlu atau hanya semacam jalan memutar untuk introspection sederhana, jadi bisa ditangani lewat analisis statisDi compiler saya juga, kalau itu menjadi bottleneck, saya akan mulai menanganinya dari sana
Ingestion JSON tanpa tipe kemungkinan besar juga memakai mekanisme seperti itu
Kalau itu semua dibuang, yang tersisa adalah bahasa yang kecil dan mudah dibaca, tidak seketat Crystal dalam typing, tapi juga tidak terlalu bergantung pada metaprogramming seperti Ruby resmi
Jadi potensinya terlihat cukup besar, tapi tetap saja baru bisa dinilai seiring waktu
evalBisa saja tidak dipakai, tapi buat saya itu lebih ergonomic
eval,exec,define_method, serta pola membuat class baru denganClass.newdanStruct.newSebagian besar penggunaannya terkonsentrasi saat boot aplikasi atau ketika file di-
require, jadi dalam beberapa hal itu sudah mirip tahap kompilasiIni adalah sesuatu yang baru saja diumumkan Matz di RubyKaigi 2026
Memang masih eksperimental, tapi dibuat hanya dalam waktu sekitar sebulan dengan bantuan Claude, dan live demo-nya juga berhasil
Namanya diambil dari kucing baru Matz, dan nama kucing itu sendiri berasal dari nama kucing di Card Captor Sakura, yang di sana juga dipasangkan dengan karakter bernama Ruby
Untuk orang seperti Matz, mungkin malah seperti mendorong 100x menjadi 500x
https://en.wikipedia.org/wiki/Spinel
Sepertinya videonya belum live, dan tampaknya sedang diunggah satu per satu ke channel ini
https://www.youtube.com/@rubykaigi4884/videos
Nama proyeknya juga terasa dipilih secara emosional
Ini jelas sangat mengesankan, tapi tampaknya mustahil dipelihara tanpa AI agent
spinel_codegen.rbpanjangnya 21 ribu baris, dan beberapa metodenya sampai bertingkat 15 levelKode compiler memang sulit dibuat indah, tapi bahkan dengan standar itu pun, ini terlihat sangat sulit dikelola oleh manusia
Compiler punya batas subsistem yang jelas dan handoff antartahap yang tegas, jadi justru termasuk salah satu jenis sistem yang paling mudah dibuat modular
Masalahnya biasanya setelah berhasil dibuat jalan, tidak ada waktu lagi untuk refactor, lalu kekotorannya terus menumpuk
spinel_codegen.rbnyaris setingkat eldritch horrorSaat memakai Claude, saya juga selalu mendapatkan spaghetti code seperti ini, jadi saya sempat bertanya-tanya apakah saya melakukan sesuatu yang salah
Tapi setelah melihat bahwa proyek yang benar-benar menarik buatan orang yang saya anggap programmer kelas atas pun kualitas kodenya cukup buruk di beberapa bagian, saya jadi tahu bahwa bukan cuma saya
Misalnya,
infer_comparison_type()bukan contoh terburuk dan juga tidak terlalu sulit dibaca, tapi sebenarnya ada implementasi yang jauh lebih sederhana dan jelas, hanya saja Claude gagal menuju ke sanaKalau operator perbandingan dikumpulkan dalam
Setlalu diproses denganinclude?, hasilnya akan lebih pendek, lebih cepat, lebih mudah dibaca, dan lebih gampang dirawatTapi Claude selalu cenderung jatuh ke rantai if-return, bahkan terkesan asing dengan if-else biasa
Codebase Claude saya juga penuh dengan pola seperti itu, dan sekarang saya sadar saya tidak sendirian
Sebaliknya, file-file lain jauh lebih baik, terutama direktori
lib, yang tampaknya berkorespondensi dengan direktoriextdi repo Ruby utama dan kualitasnya cukup bagusAPI-nya juga jelas dipengaruhi MRI Ruby, dan meskipun implementasinya cukup berbeda, tampaknya Matz mengarahkan agar sebagian API-nya menyerupai API asli sehingga output-nya terasa lebih rapi
[1] https://github.com/matz/spinel/blob/98d1179670e4d6486bbd1547...
Selama tes dan benchmark lolos, saya sudah cukup puas
Tapi saya ragu file raksasa seperti itu mudah ditangani bahkan oleh AI
Saya mencoba membatasi file di bawah 300 baris, dan saya pikir kode yang mudah dipahami manusia juga akan lebih mudah dipahami coding agents
Batasannya katanya seperti ini
No eval:
eval,instance_eval,class_evalNo metaprogramming:
send,method_missing,define_method(dinamis)No threads:
Thread,Mutex(Fiber didukung)No encoding: asumsi UTF-8/ASCII
No general lambda calculus:
-> x { }bertingkat dalam dengan pemanggilan[]Asumsi UTF-8/ASCII secara pribadi bukan batasan besar bagi saya, tapi sisanya tampaknya akan jadi batasan nyata untuk cukup banyak program
Dan untuk menambahkan semua itu lagi tampaknya perlu pekerjaan yang lumayan besar
Sebagai orang yang sudah lama memakai Ruby dan pernah menggunakan semua fitur yang disebutkan itu, justru versi Ruby yang sederhana seperti inilah yang akhirnya saya inginkan setelah sekian lama berevolusi
Lebih sederhana dan lebih mudah dipahami, tapi tetap menyisakan rasa estetika khas Ruby
Sekarang, berkat LLM, produktivitas pembuatan kode sudah begitu tinggi sehingga kita tidak lagi terlalu perlu mengurangi boilerplate lewat metaprogramming demi produktivitas developer seperti dulu
Karena porsi kode yang benar-benar ditulis langsung oleh developer sendiri juga makin berkurang
Sintaksnya mirip dan punya static type system, jadi bisa menghasilkan kode hasil kompilasi yang lebih efisien
evalmenurut saya justru lebih baik, tapi ketiadaan threads dan mutexes terasa sayangTidak adanya
define_methodmasih bisa dimengerti kalau melihat kegunaannyaTapi
senddanmethod_missingumum di library yang sudah ada, dan implementasinya rasanya tidak terlalu sulit, misalnya dengan membangun memory lookup table saat compile timeJadi saya tidak tahu apakah itu sengaja dihilangkan, atau mereka memang belum sampai ke sana
Saya berharap yang kedua, tapi setidaknya untuk saat ini tampaknya sulit dipakai di produksi karena masalah kompatibilitas
Melainkan mengurangi jumlah kode yang perlu dibaca
Ini benar-benar keren, dan saya sudah lama menunggu AOT compiler untuk Ruby
Tapi sayang tidak ada fallback untuk
evalatau metaprogramming, meski tampaknya itu memang keputusan untuk fokus pada subset kecil yang berkinerja tinggiAkan bagus kalau gem yang dibuat dengan AOT compiler ini bisa berinteraksi dengan baik dengan MRI
Untuk sisi packaging atau bundling Ruby standar dan gem, kita masih butuh tebako, kompo, dan ocran, dan dulu juga ada proyek seperti ruby-packer, traveling ruby, dan jruby warbler
Senang ada satu opsi lagi, tapi saya tetap berharap akan muncul solusi pamungkas dengan UX developer yang lebih baik
Karena terlalu lama tidak diperbarui
Saya penasaran kenapa no threads
Ruby scheduler dan implementasi pthread di bawahnya rasanya tetap bisa berjalan baik di ranah C, jadi saya sempat berpikir mungkin tujuannya zero dependency
Kalau bukan extension opsional yang rencananya akan ditambahkan nanti atau sekadar belum diimplementasikan, pilihan ini terasa agak aneh
Kemungkinan besar memang baru belum sampai ke sana
Multithreading memang dari awal sangat sulit dibuat dengan benar
Mengejutkan bahwa ini dibuat hanya dalam waktu sedikit lebih dari sebulan
Apa pun pendapat orang soal AI, di tangan developer yang kuat, itu benar-benar bisa menghasilkan percepatan yang luar biasa
Sementara Matz terasa seperti cukup dengan
gem env|infodanfindKarena ini dibuat oleh Matz, saya penasaran seberapa realistis kemungkinan bahwa ini nantinya menjadi bagian dari Ruby core
Dan kalau itu terjadi, saya juga penasaran seberapa besar ancamannya terhadap Crystal
Karakteristik seperti ini nyaris wajib untuk mengompilasi dan memelihara program besar
Sementara ini hanya subset Ruby yang terbatas, jadi sebagian besar gem Ruby populer kemungkinan tidak akan langsung berjalan apa adanya
Sebagai subset bahasa yang menargetkan kompilasi ke C, ini tampak lebih dekat ke PreScheme
Pada tahap sekarang, saya tidak melihat keduanya bersaing langsung di ranah yang sama
Ruby yang benar-benar penuh hampir pasti tetap membutuhkan JIT
[1]: https://prescheme.org/
Ini semacam momen balas dendam untuk tool seperti Rational Unified Process dan Enterprise Architect
Bedanya cuma, yang datang nanti bukan diagram UML melainkan file markdown
Ini sepertinya akan berguna di area infrastructure tools
Misalnya, bayangkan ada bundler yang ditulis dalam Ruby tapi dikompilasi secara statis, sehingga sekaligus bisa berperan seperti alat instalasi Ruby ala RVM
Buildpack Ruby yang sekarang memang ditulis dalam Ruby, tapi bootstrap-nya harus lewat bash sehingga merepotkan dan memunculkan edge case
CNB ditulis dalam Rust untuk menghindari masalah itu, dan gagasan mendistribusikan single binary tanpa dependency benar-benar sangat kuat