Spinel - Trình biên dịch native AOT cho Ruby
(github.com/matz)- Trình biên dịch AOT chuyển mã nguồn Ruby thành mã C sau khi thực hiện suy luận kiểu cho toàn bộ chương trình, rồi tạo ra binary native độc lập
- Được chính matz, cha đẻ của Ruby, trực tiếp phát triển; phần backend của trình biên dịch cũng được viết bằng Ruby, theo cấu trúc self-hosting tự biên dịch chính nó
- Hiệu năng nhanh hơn khoảng 11,6 lần so với miniruby (Ruby 4.1.0dev); Conway's Game of Life nhanh hơn 86,7 lần, ackermann 74,8 lần, mandelbrot 58,1 lần
- Pipeline biên dịch dùng parser dựa trên Prism để chuyển Ruby thành văn bản AST, sau đó backend self-hosting thực hiện suy luận kiểu và sinh mã C, rồi dùng trình biên dịch C tiêu chuẩn để tạo binary standalone
- Hỗ trợ rộng rãi nhiều tính năng của Ruby như class, kế thừa, block, xử lý ngoại lệ, Fiber, engine Regexp NFA tích hợp, Bigint tự động nâng cấp, pattern matching
- Các class nhỏ có từ 8 trường scalar trở xuống được tự động cấp phát trên stack dưới dạng value type, loại bỏ hoàn toàn overhead của GC (1 triệu lần cấp phát: 85ms → 2ms)
- Chuỗi nối
a + b + c + dđược làm phẳng bằng một lần malloc duy nhất;splittrong vòng lặp sẽ tái sử dụng sp_StrArray để loại bỏ các lần cấp phát không cần thiết - Nhiều tối ưu hóa ở thời điểm biên dịch như hoisting độ dài bất biến trong vòng lặp, truyền hằng số, tự động inline các phương thức có không quá 3 câu lệnh, dừng sớm suy luận lặp (~giảm 14% thời gian bootstrap)
- Binary được tạo ra không có phụ thuộc runtime, có thể chạy chỉ với libc + libm
- Không hỗ trợ
eval, metaprogramming, Thread và Mutex, v.v. (chỉ hỗ trợ Fiber) - Giấy phép MIT
1 bình luận
Ý kiến trên Hacker News
Nếu là thứ do Matz làm thì hẳn ông ấy cũng hiểu rất rõ những giới hạn của Ruby semantics, nên tạo được cảm giác tin cậy
Luận văn thạc sĩ của tôi cũng là về AOT JS compiler, chạy được thật nhưng bị ràng buộc dữ liệu đầu vào quá lớn nên cuối cùng phải bỏ dở
Hồi đó các lập trình viên JS chưa quen với việc tự giác tuân thủ những ràng buộc như vậy, và các đầu vào vốn dĩ không thể biết trước như
JSON.parselà trở ngại lớnBây giờ nhờ TypeScript nên có thể thực tế hơn rất nhiều so với khi đó
Chỉ nhìn vào lambda calculus nói chung cũng thấy giới hạn của suy luận kiểu là rất rõ ràng, và các bài báo phía Matt Might hay dự án Shed-skin Python cũng bộc lộ những ràng buộc tương tự
Tôi tò mò
eval,send,method_missing,define_methodthực sự phổ biến đến mức nào trong mã Ruby ngoài đời, và cũng tò mò các đầu vào parse không kiểu, ví dụ JSON input, thường được xử lý ra saoViệc parse Ruby khó đến mức còn khó hơn cả khâu biên dịch, nên họ dùng Prism, rồi sinh ra mã C
Bản thân việc hiện thực các Ruby semantics cơ bản thì không hẳn khó đến vậy
Ngược lại, tôi đang vật lộn với một self-hosting AOT compiler cũ viết bằng Ruby thuần, vì cố chấp tự làm parser riêng nên đã tự chọn con đường khó hơn nhiều
Tôi học được khá sớm rằng 80% đầu có thể làm tương đối sơ sài mà vẫn chạy được phần lớn mã Ruby, còn “80% thứ hai” mới thực sự khó thì lại tập trung ở những thứ Matz đã bỏ ra khỏi dự án này và khỏi mruby, như encoding hay đủ loại tính năng phụ trợ
Nói thật là trong Ruby có không ít tính năng mà tôi chưa từng thấy một lần nào trong mã thực tế, nên nếu vài cái bị deprecated tôi cũng không thấy lạ
send,method_missing,define_methodthì rất phổ biếnCác ràng buộc thì tương tự mruby, nhưng ngay cả dưới các ràng buộc đó vẫn có chỗ để dùng
Hỗ trợ
send,method_missing,define_methodtương đối dễCòn hỗ trợ eval() thì cực kỳ đau đầu
Tuy vậy, một tỷ lệ lớn
eval()trong Ruby có thể được quy về phiên bản block của instance_eval theo cách tĩnh, nên trong những trường hợp đó AOT compile khá dễVí dụ nếu chuỗi truyền vào
eval()có thể biết trước hoặc phân rã được một cách tĩnh thì khả năng giải quyết sẽ lớnTrên thực tế, nhiều cách dùng
eval()là không cần thiết hoặc chỉ gần như cách né introspection đơn giản, nên có thể xử lý bằng kiểm tra tĩnhCompiler của tôi cũng định nếu chỗ đó trở thành nút thắt thì sẽ đụng vào từ đó trước
Việc ingest JSON không kiểu cũng có lẽ sẽ dùng những cơ chế như vậy
Nếu bỏ chúng đi thì sẽ còn lại một ngôn ngữ nhỏ, dễ đọc, không có type mạnh như Crystal nhưng cũng không dựa vào metaprogramming nhiều như Ruby chính thức
Vì thế tiềm năng có vẻ khá lớn, nhưng rốt cuộc vẫn phải chờ thời gian mới biết được
evalkhá thường xuyênCó thể không dùng cũng được, nhưng với tôi như vậy ergonomic hơn
eval,exec,define_method, và pattern tạo class mới bằngClass.new,Struct.newPhần lớn việc dùng chúng tập trung vào thời điểm boot của app hoặc trong lúc require file, nên ở một khía cạnh nào đó nó đã khá giống giai đoạn compile rồi
Đây là thứ Matz vừa trình bày tại RubyKaigi 2026
Dù còn mang tính thử nghiệm, ông ấy đã làm ra nó trong khoảng một tháng với sự trợ giúp của Claude, và demo trực tiếp cũng thành công
Tên gọi được lấy từ con mèo mới của Matz, còn tên con mèo thì bắt nguồn từ tên mèo trong Card Captor Sakura, nơi nó lại ghép cặp với một nhân vật tên là Ruby
Với người như Matz thì có khi là đẩy từ 100x lên 500x
https://en.wikipedia.org/wiki/Spinel
Có vẻ video vẫn chưa phát trực tiếp, và hình như đang được đăng dần từng cái lên kênh này
https://www.youtube.com/@rubykaigi4884/videos
Tên dự án cũng tạo cảm giác như được đặt theo cảm xúc
Rõ ràng là rất ấn tượng, nhưng trông có vẻ không thể bảo trì nếu không có AI agent
spinel_codegen.rbdài 21 nghìn dòng, có method lồng tới 15 cấpVốn dĩ mã compiler đã khó mà đẹp, nhưng ngay cả theo tiêu chuẩn đó thì cái này cũng có vẻ rất khó để con người quản lý
Compiler có ranh giới subsystem rõ ràng và handoff giữa các giai đoạn cũng mạch lạc, nên thực ra lại thuộc nhóm dễ modular hóa nhất
Vấn đề thường là sau khi đã làm cho nó chạy được thì không còn thời gian refactor, và thế là sự bừa bộn cứ phình ra
spinel_codegen.rbgần như ở mức eldritch horrorDùng Claude thì tôi cũng luôn ra kiểu spaghetti code thế này, nên từng tự hỏi không biết mình đang làm sai gì
Nhưng nhìn thấy cả trong một dự án thực sự thú vị do người mà tôi xem là lập trình viên top đầu tạo ra, chất lượng mã ở nhiều chỗ cũng khá tệ, thì hóa ra không phải chỉ mình tôi
Ví dụ
infer_comparison_type()chưa phải ca tệ nhất và cũng không khó đọc, nhưng vẫn có cách hiện thực đơn giản và rõ ràng hơn nhiều mà Claude lại không đi tới đượcNếu gom các toán tử so sánh vào
Setrồi xử lý bằnginclude?thì sẽ ngắn hơn, nhanh hơn, dễ đọc hơn và dễ bảo trì hơnThế mà Claude lúc nào cũng trôi về kiểu chuỗi if-return, thậm chí còn có cảm giác nó lạ lẫm cả với if-else
Codebase do Claude viết của tôi cũng đầy những pattern như vậy, nên giờ tôi biết không chỉ mình mình gặp thế
Ngược lại, các file khác tốt hơn nhiều, đặc biệt là thư mục
libcó vẻ tương ứng với thư mụcexttrong repo Ruby chính và chất lượng khá ổnAPI cũng rõ ràng chịu ảnh hưởng từ MRI Ruby, và dù phần hiện thực khác khá nhiều, có vẻ Matz đã hướng nó mô phỏng một phần API gốc nên đầu ra trông gọn gàng hơn
[1] https://github.com/matz/spinel/blob/98d1179670e4d6486bbd1547...
Chỉ cần test và benchmark qua là tạm ổn rồi
Tuy vậy, tôi vẫn nghi ngờ liệu file khổng lồ như vậy có thật sự dễ cho AI xử lý hay không
Tôi đang cố giới hạn file trong khoảng dưới 300 dòng, và nghĩ rằng mã dễ hiểu với con người thì cũng sẽ dễ cho coding agents
Các ràng buộc được nói là như sau
No eval:
eval,instance_eval,class_evalNo metaprogramming:
send,method_missing,define_method(động)No threads:
Thread,Mutex(có hỗ trợ Fiber)No encoding: giả định UTF-8/ASCII
No general lambda calculus:
-> x { }lồng sâu kèm lời gọi[]Riêng giả định UTF-8/ASCII thì cá nhân tôi không thấy là ràng buộc lớn, nhưng phần còn lại có vẻ sẽ là hạn chế thực sự với khá nhiều chương trình
Và có vẻ sẽ cần rất nhiều công sức nếu muốn đưa các phần đó trở lại
Tôi đã dùng Ruby rất lâu, và với tư cách người từng dùng hết tất cả những tính năng được liệt kê, thứ mà sau cả quá trình tiến hóa tôi lại muốn chính là phiên bản Ruby đơn giản như thế này
Nó đơn giản hơn, dễ hiểu hơn mà vẫn giữ được chất thẩm mỹ rất Ruby
Giờ nhờ LLM mà năng suất sinh code đã cao đến mức không còn cần phải dùng metaprogramming để cắt boilerplate vì năng suất lập trình viên như trước nữa
Bởi vì tỷ lệ lập trình viên trực tiếp tự viết code đang giảm đi
Cú pháp tương tự và có static type system, từ đó dẫn tới mã biên dịch hiệu quả hơn
evalthì tôi còn thấy tốt hơn, nhưng đến cả threads và mutexes cũng không có thì hơi tiếcViệc thiếu
define_methodthì xét theo công dụng của nó tôi còn hiểu đượcNhưng
sendvàmethod_missinglại rất phổ biến trong các thư viện hiện có, và cách hiện thực cũng có vẻ không đến mức quá khó, kiểu dựng bảng tra cứu trong bộ nhớ ngay lúc compileNên tôi không rõ đây là cố ý bỏ đi, hay chỉ là chưa làm tới đó
Tôi mong là vế sau, nhưng ít nhất ở thời điểm hiện tại thì vì vấn đề tương thích nên có lẽ khó đem vào thực chiến
Mà là giảm lượng code phải đọc
Cái này thật sự rất ngầu, và tôi đã chờ một AOT compiler cho Ruby từ lâu
Chỉ tiếc là không có fallback cho
evalhay metaprogramming, nhưng có vẻ họ làm vậy để tập trung vào một subset nhỏ mà hiệu năng caoTôi hy vọng gem tạo bằng AOT compiler này có thể tương tác tốt với MRI
Việc đóng gói hoặc bundle Ruby chuẩn và gem vẫn cần tebako, kompo, ocran, và trước đây cũng từng có các dự án như ruby-packer, traveling ruby, jruby warbler
Có thêm một lựa chọn nữa thì tốt, nhưng tôi vẫn mong sẽ có phiên bản quyết định cuối cùng với UX cho lập trình viên tốt hơn
Vì nó đã quá lâu không được cập nhật
Tôi thắc mắc vì sao lại no threads
Ruby scheduler và phần hiện thực pthread phía dưới có vẻ như vẫn có thể hoạt động ổn ngay cả trong vùng C, nên tôi tự hỏi có phải họ nhắm tới zero dependency không
Nếu không phải là dự định thêm optional extension sau này hay chỉ là tạm thời chưa làm, thì lựa chọn này thấy hơi lạ
Có lẽ chỉ là chưa làm tới đó thôi
Multithreading vốn đã cực khó để làm cho đúng
Việc làm ra nó chỉ trong hơn một tháng thật đáng kinh ngạc
Dù có nói gì về AI đi nữa thì khi rơi vào tay lập trình viên giỏi, nó đúng là tạo ra mức tăng tốc khủng khiếp
Còn Matz thì có cảm giác chỉ cần
gem env|infovớifindlà đủVì đây là thứ do Matz làm, tôi tò mò khả năng thực tế để nó trở thành một phần của Ruby core trong tương lai là bao nhiêu
Và nếu vậy thì nó sẽ đe dọa Crystal đến mức nào
Những đặc tính như vậy gần như thiết yếu để compile và bảo trì các chương trình lớn
Còn cái này chỉ là subset Ruby bị giới hạn, nên đa số gem Ruby phổ biến có lẽ sẽ không chạy nguyên trạng
Xét ở góc độ là một subset ngôn ngữ nhắm tới biên dịch C, nó có vẻ gần với PreScheme hơn
Ở giai đoạn hiện tại tôi không nghĩ hai bên đang cạnh tranh trực tiếp trong cùng một không gian
Ruby đầy đủ gần như chắc chắn vẫn sẽ cần JIT
[1]: https://prescheme.org/
Kiểu như màn báo thù của Rational Unified Process và các công cụ như Enterprise Architect
Khác biệt chỉ là thay vì UML diagram thì giờ thứ được đưa ra là file markdown
Cái này có vẻ sẽ hữu ích trong mảng infrastructure tools
Ví dụ có thể hình dung ra một bundler viết bằng Ruby nhưng được compile tĩnh, nên đồng thời kiêm luôn vai trò công cụ cài Ruby kiểu RVM
Buildpack Ruby hiện tại cũng viết bằng Ruby, nhưng phải bootstrap bằng bash nên rất phiền và sinh ra edge case
CNB được viết bằng Rust để tránh vấn đề đó, và ý tưởng phát hành một binary duy nhất không phụ thuộc gì thật sự rất mạnh