1 điểm bởi GN⁺ 2024-08-26 | 1 bình luận | Chia sẻ qua WhatsApp
  • Dozer là một trình biên dịch Rust dựa trên C thuần túy, nhằm giúp có thể sử dụng Rust ở giai đoạn bootstrap sớm hơn, và đang được viết mà không dùng C++·flex·yacc·Makefile
  • Trình biên dịch chính thức rustc được viết bằng Rust, nên bản rustc mới được build bằng phiên bản rustc trước đó; chuỗi này lần ngược về trình biên dịch Rust ban đầu viết bằng OCaml, rồi tiếp tục xuống các tầng Guile và C
  • Quy trình bootstrap Linux của Bootstrappable Builds bắt đầu từ binary seed 512 byte, rồi mở rộng dần thành trình biên dịch đơn giản, shell, tập con của C, TinyCC, yacc, coreutils, Bash, autotools, GCC và Linux
  • Hiện tại Rust chỉ xuất hiện ở giai đoạn muộn thông qua mrustc viết bằng C++, dùng để biên dịch rustc 1.56, nên trước khi C++ được đưa vào thì rất khó dùng Rust
  • Dozer hướng tới mục tiêu trở thành trình biên dịch Rust có thể bootstrap bằng TinyCC, sau đó tiến tới libcore, backend Cranelift của rustc, công cụ thay thế cargo và cuối cùng là rebuild canonical rustc/cargo

Mục tiêu và ràng buộc của Dozer

  • Dozer là một trình biên dịch Rust đang được viết bằng C thuần túy
  • Không dùng C++, cũng không dùng flex, yacc, hay Makefile
  • Mục tiêu cốt lõi là tạo ra một trình biên dịch cho phép bootstrap Rust từ C
  • Đặc biệt, nó phải có thể bootstrap trên TinyCC, và giả định hệ thống không có công cụ hữu ích nào ngoài trình biên dịch C và một shell rất cơ bản

Vấn đề trình biên dịch Rust tự build chính nó

  • Để chạy mã Rust thì cần biên dịch, và thông thường cargo build sẽ gọi rustc ở bên trong
  • Bản thân rustc cũng là một trình biên dịch Rust được viết bằng Rust, nên rustc mới sẽ được biên dịch bằng phiên bản rustc trước đó
    • rustc 1.80.0 được biên dịch bằng rustc 1.79.0
    • Chuỗi này tiếp tục nối về các phiên bản cũ hơn như rustc 1.78.0
  • Giai đoạn ban đầu lần ngược được tới Rust 0.7, nơi trình biên dịch lúc đó được viết bằng OCaml
  • Vì cũng cần cả trình biên dịch OCaml, nên chuỗi bootstrap lại tiếp tục kéo theo các triển khai ngôn ngữ khác
    • camlboot có thể biên dịch trình biên dịch OCaml bằng Guile
    • Trình thông dịch Guile được viết bằng C

Chuỗi phía dưới của Bootstrappable Builds

  • Bootstrappable Builds xử lý quy trình bootstrap toàn bộ hệ thống từ một binary seed nhỏ
  • Quy trình bootstrap Linux bắt đầu từ một binary seed 512 byte
    • Seed này chứa một trình biên dịch cực kỳ đơn giản, nhận vào các số thập lục phân và in ra các byte thô tương ứng
    • Một dãy byte hex, bỏ qua chú thích và khoảng trắng, về mặt kỹ thuật cũng được coi là mã nguồn có thể phân tích
  • Các giai đoạn sau đó dần build ra các công cụ ở mức cao hơn
    • Hệ điều hành cực kỳ đơn giản
    • Shell cơ bản
    • Trình biên dịch phát triển hơn một chút
    • Một giai đoạn trông giống mã assembly
    • Một tập con C rất cơ bản
    • Một trình biên dịch C phát triển hơn được viết bằng tập con C đó
  • Sau thêm vài bước, có thể biên dịch TinyCC, rồi tiếp tục tới yacc, coreutils cơ bản, Bash, autotools, GCC và Linux
  • Mỗi giai đoạn đều được liệt kê trong live-bootstrap parts.rst

Rust xuất hiện quá muộn trong chuỗi bootstrap

  • Hiện tại Rust chỉ xuất hiện ở một giai đoạn rất muộn trong quy trình này
  • Triển khai đang được dùng là mrustc, một triển khai Rust thay thế viết bằng C++
  • mrustc có thể biên dịch rustc 1.56, rồi từ đó tiếp tục biên dịch tới mã Rust hiện đại hơn
  • Tuy nhiên, khi C++ được đưa vào chuỗi bootstrap thì trên thực tế quá trình này gần như đã hoàn tất
  • Nếu muốn dùng Rust trước thời điểm đưa C++ vào, cần một trình biên dịch Rust có thể bootstrap từ C

Tình trạng triển khai hiện tại của Dozer

  • Dozer đã được phát triển khoảng hai tháng và được viết mà không dùng các phần mở rộng
  • Hiện tại nó có thể được biên dịch bình thường bằng cả TinyCCcproc
  • Backend sử dụng QBE
  • Việc triển khai vẫn đang ở giai đoạn đầu
    • Lexer đã hoàn thành
    • Parser đã được triển khai phần lớn
    • Việc mở rộng macro/module đang được trì hoãn càng lâu càng tốt
    • Kiểm tra kiểu hiện mới chỉ hỗ trợ i32
    • Phần sinh mã vẫn còn khá thô
  • Hiện tại nó có thể biên dịch thành công đoạn mã Rust sau
fn rust_main() -> i32 {
    (2 - 1) * 6 + 3
}

Kế hoạch tiến tới rustc

  • Mục tiêu là từng bước phát triển Dozer để biên dịch được các ví dụ cơ bản dùng libc, sau đó là libcore, rồi tới rustc
  • Với việc biên dịch rustc, kế hoạch là dùng backend Cranelift
    • Backend Cranelift được viết hoàn toàn bằng Rust
    • Vì giả định không có C++, nên không thể biên dịch LLVM
  • Cũng có kế hoạch tạo ra một công cụ thay thế cargo có thể biên dịch các gói Rust bằng Dozer
  • Cần tìm và loại bỏ các tệp được sinh tự động trong mã nguồn rustc
    • Theo quy tắc của dự án Bootstrappable, mã được sinh tự động là không được phép
  • Mục tiêu cuối cùng là sau khi biên dịch được rustc và cargo, sẽ tạo ra quy trình dùng chính rustc/cargo tự biên dịch đó để biên dịch lại canonical rustc/cargo
  • Đây là công việc khó nhất mà tác giả từng đảm nhận, và dù vẫn nghi ngờ về khả năng hoàn thành, họ cho biết sẽ tiếp tục thử

1 bình luận

 
GN⁺ 2024-08-26
Ý kiến trên Hacker News
  • Nếu bootstrap Rust, có lẽ tôi sẽ tạo một proto-Rust bằng C với ít tính năng hơn Rust đầy đủ, rồi dùng proto-Rust đó để viết một compiler Rust hoàn chỉnh
    Ví dụ, proto-Rust có thể không có borrow checker, hỗ trợ macro bị hạn chế hoặc không có, có thể không giải phóng bộ nhớ, và cũng không cần tạo ra mã tốt
    Về thực chất nó sẽ gần giống C với cú pháp Rust, nhưng với người yêu thích Rust thì có vẻ vẫn tốt hơn việc viết compiler Rust bằng “C với cú pháp C” như mục tiêu của dự án này
    Tôi tò mò vì sao họ không chọn con đường này

    • Nhân tiện, mrustc, compiler Rust không viết bằng Rust tiêu biểu hiện có, cũng đã không có borrow checker
      Loại bỏ borrow checker không làm hỏng các chương trình đúng, chỉ khiến rất nhiều chương trình sai có thể biên dịch được
      Mục đích chính của mrustc là biên dịch rustc, và ta đã biết rustc có thể tự biên dịch chính nó mà không gặp lỗi borrow checker, nên như vậy là ổn
    • Mozart/Oz thực tế đã làm như vậy. Có một compiler proto-Oz viết bằng Scala, và dùng nó để biên dịch compiler thật viết bằng Oz
      Vì compiler Scala tạo ra mã không hiệu quả, sau đó họ lại dùng chính compiler thật để biên dịch lại nó
      Làm như vậy cuối cùng sẽ có một compiler thật hiệu quả, tạo ra mã tốt; quy trình này được đưa vào bản build chuẩn của ngôn ngữ
      https://github.com/mozart/mozart2
    • Như vậy rốt cuộc là dùng hai compiler, tôi không rõ ngoài phần việc bổ sung ra thì thực sự thu được gì
  • Tôi đang làm một compiler C bằng Rust như sở thích, và gọi đùa nó là Small C Compiler vì Rust đương nhiên nặng hơn C. Đây là bản nhại “Tiny C Compiler”
    Tôi dùng Cranelift làm backend, nhưng đang thiết kế toàn bộ cấu trúc compiler để có thể cắm thay thế bằng nhiều trait và dễ hack
    Tôi chưa định công bố mã nguồn mở cho đến khi nó hoạt động được ở mức nào đó, như xử lý được printf("%s", "Hello World!")
    Tôi đã định triển khai preprocessor và parser, và vì vấn đề typedef khét tiếng nên cũng đã tham gia vào rust-peg và HimeCC
    Tôi biết trong ngành người ta dùng bảng ký hiệu để duy trì ngữ cảnh typedef, nhưng có hạn chế là không thể đọc được các kiểu ở phía dưới. Tôi tò mò giải pháp kiểu học thuật là gì; thứ duy nhất tôi nghĩ ra là transactional memory
    Nếu có gì hữu ích, có lẽ cuối cùng tôi sẽ công bố

  • Thật sự rất hay, và điều thú vị là cùng kiểu vấn đề bootstrapping cũng tồn tại trong phần cứng
    Máy tính do cái gì tạo ra? Do các máy tính đã được tạo trước đó và phần mềm chạy trên chúng tạo ra. Càng nghĩ càng thấy thú vị

    • Cùng vấn đề bootstrapping đó có ở mọi thứ. Đường sá do cái gì tạo ra? Do thiết bị xây dựng tạo ra. Nhưng nếu chưa có đường thì làm sao đưa thiết bị xây dựng đó đến công trường?
      Vài tháng trước tôi gặp một người làm ở startup chuyên giao hàng/fulfillment vật liệu cho các dự án xây dựng
      Công việc này cần chuyên môn khác với giao hàng thông thường kiểu Amazon, không chỉ vì vật liệu thường có tính chất đặc biệt hoặc nguy hiểm, mà còn vì địa điểm giao hàng thường chưa có địa chỉ
      Có thể giải quyết được, nhưng dường như cần chuyên môn vượt ngoài năng lực thông thường của các hãng giao hàng hiện đại
    • Tôi từng làm ở một công ty xây dựng trung tâm dữ liệu, nơi họ muốn tạo phần mềm đến mức có thể khởi động toàn bộ trung tâm dữ liệu chỉ bằng một chiếc laptop
      Lý do là khi hợp tác với các công ty châu Âu, họ cần chứng minh với cơ quan quản lý rằng không có backdoor
      Đó là một vấn đề rất thú vị nhưng cực kỳ khó; nhóm chúng tôi chỉ tham gia gián tiếp, bằng cách chuyển dữ liệu qua một proxy để mọi dữ liệu đều có thể kiểm toán và bảo đảm không gửi đi những thứ không được phép gửi
      Tôi rời công ty trước khi dự án kết thúc, và sau đó nghe nói nó bị hủy vì quá khó
    • Nhìn vào opcode bát phân assembly của Cray-1 cũ hay opcode word của IBM System/360, ta sẽ thấy chúng được làm đơn giản đến mức đáng kinh ngạc, đủ để con người có thể tự viết byte opcode và tự assemble bằng tay
      Sau đó x86 xuất hiện mà không có ngân sách khổng lồ hay khách hàng lớn, nên họ thiết kế assembly sao cho hiệu quả và đặc nhất có thể
      Kết quả là nó mất đi những đặc tính mà các máy khác có thể có một cách thuận tiện
    • Đây là một trong những điểm hay nhất của các dự án bootstrapping như vậy và build có thể tái lập
      Về lý thuyết, bạn có thể tự tạo một máy tính rất đơn giản chỉ từ các linh kiện rời
      Nó sẽ to, kém hiệu quả và cực kỳ chậm, nhưng có thể làm cho nó tuân theo một kiến trúc tập lệnh cụ thể, rồi build chương trình bootstrap trên đó
      Khi đó bạn có thể lập luận rằng kết quả thu được từ một chiếc máy tính tệ nhưng hoàn toàn hiểu được là giống với kết quả thu được trên phần cứng hiện đại mà bạn không hoàn toàn tin tưởng
    • Đây cũng là một ý nghĩ thú vị ở cấp độ văn minh nhân loại. Nếu loài người bằng cách nào đó quay lại thời kỳ đồ đá từ thời điểm hiện tại, liệu chúng ta có thể tạo dựng lại đến trình độ bây giờ không?
      Đó là một dạng vấn đề bootstrapping. Ví dụ, các mỏ dầu hiện nay khó khai thác hơn so với 100 năm trước, nên tôi tự hỏi liệu ta có thể bootstrap lại đến mức đó không
  • Tôi hơi khó chịu vì phải bấm theo đến tận 4 lần liên kết mới tìm được phần lập luận ở mức cao giải thích lợi ích của bootstrapping
    Tôi đã kỳ vọng phần “Why” trong tiêu đề sẽ nói về điều đó
    https://bootstrappable.org/benefits.html

    • Có thể khó giải thích vì sao bootstrapping lại quan trọng. Vì vậy trong README của trình biên dịch bootstrapping của tôi cũng có mục “Why?”
      Bảo mật là một lý do lớn, và là điểm mà nhóm bootstrappable chủ yếu nhấn mạnh
      Để tránh vấn đề trusting trust và các cuộc tấn công như backdoor xz gần đây, ta cần có khả năng bootstrap mọi thứ từ mã nguồn thuần túy
      Họ xóa cả các tệp được tạo sẵn để chỉ phụ thuộc vào những thứ được viết tay và có thể kiểm toán. Ví dụ, việc bootstrap Python trở nên khá phức tạp vì trong mã nguồn có mã do các script Python tạo ra
      Cá nhân tôi lại quan tâm nhiều hơn đến khía cạnh bảo tồn văn hóa. Tôi muốn lưu giữ các phương tiện hiện đại ở những nơi như Arctic World Archive cho các nhà khảo cổ học tương lai, nhưng nếu không có cách giải mã thì điều đó vô nghĩa
      Ta có thể lưu giữ đặc tả, nhưng không thể kỳ vọng họ sẽ triển khai x265 và mọi thứ cần thiết từ con số không. Nếu lưu giữ binary, họ sẽ phải vận hành phần cứng đã nghìn năm tuổi hoặc giả lập CPU nghìn năm tuổi
      Ta cũng có thể đưa cho họ một định nghĩa Lisp đơn giản cùng mã chạy trên đó, nhưng ai sẽ triển khai x265 bằng Lisp cơ bản chứ. Điều đó không thực tế
      Vì vậy trong dự án của tôi, tôi tạo một máy ảo đơn giản và bootstrap C trên đó
      Nó có thể được port rất dễ không chỉ sang kiến trúc hiện tại mà cả các kiến trúc tương lai hoặc ngoài hành tinh. Các nhà khảo cổ học tương lai hoặc một nền văn minh ngoài hành tinh có thể triển khai VM trong một ngày, chạy bootstrap C trên đó, rồi biên dịch ffmpeg và các thứ khác để giải mã media của chúng ta
      Không có hộp đen; tất cả đều là mã nguồn mở viết tay, có thể debug và kiểm toán
      https://github.com/ludocode/onramp?tab=readme-ov-file#why-bo...
      https://en.wikipedia.org/wiki/Arctic_World_Archive
  • Hơi khó hiểu. Mãi đến giữa bài mới xuất hiện lý do bắt đầu hành trình trong tiêu đề, và ý chính là khi C++ xuất hiện trong chuỗi bootstrap thì về cơ bản quá trình bootstrap đã kết thúc, nên dù muốn dùng Rust trước đó cũng không có cách nào
    Vì vậy có vẻ mục đích là muốn có một trình biên dịch Rust được viết bằng C, cụ thể hơn là có thể bootstrap từ TinyCC trên một hệ thống được giả định là chưa có công cụ hữu ích nào
    Nhưng điều này mâu thuẫn với tiền đề ở phần đầu. rustc được biên dịch kiểu 1.80.0 bằng 1.79.0, 1.79.0 bằng 1.78.0, cứ thế ngược về đến 0.7, và trình biên dịch ở thời điểm đó được viết bằng OCaml
    Bài cũng nói có một dự án đã biên dịch thành công trình biên dịch OCaml bằng Guile, và trình thông dịch Guile được viết bằng C
    Vậy thì dường như đã có sẵn con đường không dùng C++ mà tác giả muốn, chỉ là đó không phải con đường nhóm rustc dùng hằng ngày
    Rốt cuộc động cơ không rõ ràng. Không biết tác giả muốn tạo một quy trình bootstrap dựa trên C tốt hơn, muốn biến nó thành cách bootstrap thường ngày của rustc, vì sao muốn loại bỏ bước C++, hay vì sao lại ưu tiên bước C
    Nếu chỉ làm vì muốn làm thì cũng ổn, nhưng sau khi đọc một bài khá dài, tôi vẫn không hiểu rõ mục đích nào khác

    • Về mặt kỹ thuật, bootstrap Rust từ Guile và trình biên dịch Rust 0.7 là khả thi, nhưng phải biên dịch lại trình biên dịch Rust khoảng 100 lần
      Mỗi bước mất vài giờ, và vì 1.80 yêu cầu 1.79, 1.79 yêu cầu 1.78, cứ như vậy nên không thể bỏ qua bước nào cho đến 0.7
      Ngay cả khi tự động hóa hoàn toàn, quá trình bootstrap này có thể mất vài tháng
      Hơn nữa, theo tôi biết các phiên bản rustc ban đầu chỉ xuất ra LLVM, nên dù sao muốn biên dịch LLVM cũng phải bootstrap một trình biên dịch C++
      Nếu đã có trình biên dịch C++, thì chỉ cần biên dịch mrustc là được. Hiện mrustc chỉ hỗ trợ đến rustc 1.54, nên vẫn phải biên dịch qua khoảng 35 phiên bản
      Toàn bộ quá trình này không thực tế. Mục tiêu của Dozer là bootstrap một trình biên dịch C nhỏ, biên dịch Dozer, rồi biên dịch trực tiếp rustc mới nhất
      Như vậy có thể có Rust ngay mà không cần bootstrap C++ hay các bước trung gian
  • Nếu có thể tách GCC 4 và binutils khỏi các script build gốc, tôi nghĩ có thể cắt bỏ khoảng một nửa danh sách
    Phần lớn các mục trong đó chỉ là build đi build lại các thứ kiểu autoconf và các dependency của chúng
    https://github.com/fosslinux/live-bootstrap/blob/master/part...

  • Tôi không hiểu trọng điểm. Để tạo một binary mới chạy trên máy đích, rustc phải hỗ trợ kiến trúc đích
    Nếu đã thêm hỗ trợ đó vào rustc, thì cứ để rustc tự build chính nó là xong

    • Vấn đề không hẳn là hỗ trợ kiến trúc mới, mà là có một quy trình bootstrap ngắn hơn nhiều và có thể kiểm toán
  • Thỉnh thoảng tôi tưởng tượng việc viết trình thông dịch hoặc trình biên dịch C++ bằng Scheme
    Đi thẳng từ Scheme đến GCC hiện tại có thể là một lối tắt cực lớn
    Nhưng theo quan niệm phổ biến, viết một trình biên dịch C++ gần như là bất khả thi. Dù vậy có lẽ vẫn sẽ hữu ích để học hỏi

  • Nếu nhìn toàn bộ stack bắt đầu từ sub-assembler, liệu đây có thể là một cách để né vấn đề trusting trust không?
    https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_Ref...

    • Chỉ có thể nếu kiểm toán mọi thứ và tự mình chạy toàn bộ quy trình
      Dù vậy vẫn có những thứ như https://en.m.wikipedia.org/wiki/Underhanded_C_Contest, và tôi nghĩ có một số bài dự thi trong đó mà ngay cả nếu tôi kiểm toán cũng vẫn có thể bỏ sót
    • Chẳng phải đó là điểm cốt lõi sao?
  • Khi học C một chút, tôi đã tìm xem mọi người làm những thứ giống C++ trong C như thế nào, và đã thấy các triển khai như object, exception, concurrency
    Nếu mrustc được viết bằng C++, liệu việc port mã C++ đang chạy sang C bằng các primitive C kiểu C++ như vậy có dễ hơn không?
    Có vẻ cũng có thể tận dụng khả năng tương tác mạnh giữa C++ và C để chuyển dần từng phần
    Tất nhiên tôi biết đây là một công việc port khó với nhiều cạm bẫy. Chỉ là cần nhớ rằng thứ đem ra so sánh là việc viết mới một trình biên dịch Rust bằng C
    Tôi cũng nhớ đến các trình biên dịch C++ sang C từng tồn tại trước đây. Không biết giờ còn không
    Ngay cả ngày nay, Rust to C/C++ và trình biên dịch C++ sang C mà con người có thể đọc được có vẻ vẫn hữu ích, vì chúng có thể kết hợp lợi ích về an toàn của một bên với hệ sinh thái công cụ của bên kia