Một ngôn ngữ lập trình hệ thống không thể mãi ẩn sau lớp sương mù của trình biên dịch khác. Đến một lúc nào đó, nó phải đối mặt với thanh ghi, ABI, syscall và machine code thật.
Trong các chương trước, hành trình của DUY đi từ ý tưởng đến một binary chạy được, nhưng binary ấy được gánh trên một cây cầu tạm: compiler dịch code DUY sang mã nguồn C (C emitter), rồi dùng gcc hoặc clang để compile file C đó. Giàn giáo ấy hữu ích ở giai đoạn khởi đầu (Genesis), giúp chúng tôi nhanh chóng kiểm chứng được ý đồ thiết kế cú pháp và checker mà không phải lo nghĩ về phần cứng.
But giàn giáo không phải lục địa. Khi DUY muốn chạm đến lõi hệ thống — tự quản lý bộ nhớ, tách biệt freestanding profile, boot trực tiếp trên sắt thật (bare-metal) và tự định nghĩa lệnh gọi hệ thống (syscall) — C backend nhanh chóng bộc lộ các vết rạn kiến trúc. Trọng lực của hệ thống thật kéo chúng tôi xuống biên giới giữa phần mềm và phần cứng.
Chương này kể về chặng đường DUY học cách tự đứng trên đôi chân machine code của mình. Từ việc thử nghiệm LLVM IR V0 để thay thế C backend, vượt qua những bài học thô ráp về ABI, định hình freestanding mode, cho đến khi boot thành công một kernel viết bằng DUY trên QEMU và viết lại toàn bộ runtime của ngôn ngữ bằng chính nó.
Khi mới bắt đầu dự án, LLVM đối với tôi giống như một khối cơ sở hạ tầng khổng lồ của thế giới công nghệ — một thứ gì đó thuộc về các tập đoàn lớn và các học viện nghiên cứu. Ý tưởng tự viết một backend sinh mã trực tiếp sang LLVM IR trông có vẻ quá sức cho một dự án cá nhân ở những tuần đầu tiên. Nó giống như việc bạn cố gắng tự đúc một động cơ phản lực trong ga-ra nhà mình vậy.
Nhưng Sprint 17 buộc tôi phải đặt lại vấn đề: nếu không đi qua LLVM, làm sao DUY có thể tự biên dịch độc lập mà không bị ràng buộc bởi semantics và runtime của C? C-backend vốn dĩ là một hộp đen; chúng tôi không thể can thiệp sâu vào cách nó bố trí các biến trên thanh ghi hay cách nó tối ưu hóa dòng điều khiển.
LLVM (Low Level Virtual Machine) thực tế là một món quà phi thường của lịch sử phần mềm. Nó cung cấp một biểu diễn trung gian dạng SSA (Static Single Assignment) rất sạch sẽ và một tập hợp khổng lồ các pass tối ưu hóa được phát triển bởi những bộ óc hàng đầu thế giới. Việc này giúp cứu compiler khỏi ác mộng phải tự viết trình sinh mã máy cho từng kiến trúc CPU cụ thể như x86_64, ARM, hay RISC-V từ đầu — một công việc đòi hỏi hàng năm trời đọc tài liệu phần cứng. Thay vào đó, tôi chỉ cần dịch DUY IR sang LLVM IR, rồi để LLVM gánh vác phần còn lại.
DUY IR -> LLVM IR -> LLVM compiler (llc) -> Object File (.o)
Buổi tối chạy thử nghiệm LLVM spike đầu tiên là một trải nghiệm đáng nhớ. Tôi ngồi trong phòng tối, chỉ có ánh sáng từ màn hình thiết bị đầu cuối hắt lên. Bản emit LLVM IR đơn giản cho chương trình "hello" chỉ chứa vài dòng lệnh khai báo signature và gọi hàm puts. Khi tôi chạy lệnh llc để dịch nó thành assembly, rồi dùng clang để link ra binary chạy thật, cảm giác lúc nhìn thấy chữ "hello" in ra trên terminal rất khác với khi chạy qua C backend. Nó chạy trực tiếp từ mã máy do chính compiler của mình phát biểu cấu trúc, đi qua luồng tối ưu hóa chính quy. LLVM không còn là một cái tên xa vời; nó trở thành con đường chính để DUY biến semantics của mình thành vật lý.
Bình luận (0)
Đang tải bình luận...