1 điểm bởi GN⁺ 2025-04-14 | 1 bình luận | Chia sẻ qua WhatsApp
  • rustc_codegen_clr là dự án tạo mã Rust sang C và .NET; cùng với việc cải thiện tỷ lệ vượt qua kiểm thử, dự án đang chuẩn bị cho bài trình bày tại Rust Week
  • Tỷ lệ vượt qua kiểm thử Rust core đã tăng từ 92% hai tháng trước lên 95,9%, và khoảng 65 bài kiểm thử còn lại dường như có nguyên nhân khá giống nhau
  • Các bản sửa gần đây tập trung vào intrinsic số nguyên 128 bit, checked arithmetic và subslicing; phía .NET cũng đã có thể chạy 96,3% các bài kiểm thử Rust core
  • Dự án nhắm tới output gần với C99 hoặc ANSI C và dùng API POSIX chuẩn để hướng đến nhiều trình biên dịch C hơn; một số trình biên dịch ANSI C đã chạy thành công các chương trình Rust rất đơn giản
  • Các tối ưu giảm kích thước file khi tạo mã C quy mô lớn và việc tái cấu trúc IR nội bộ đang được tiến hành song song, tạo nền tảng để mở rộng phạm vi chạy Rust sang các nền tảng cũ hoặc đặc thù

Bài trình bày tại Rust Week và tình hình dự án

  • Bài trình bày về dự án rustc_codegen_clr được lên lịch tại Rust Week tổ chức ở Utrecht, Hà Lan
  • Bài nói cố gắng cân bằng giữa việc dễ tiếp cận với người mới và chủ đề nâng cao là biên dịch Rust sang C
  • Trong vài tháng gần đây, việc sửa kiểm thử, tương thích với trình biên dịch C, cải thiện hiệu năng và tái cấu trúc nội bộ đã được tiến hành cùng lúc

Tỷ lệ vượt qua kiểm thử Rust core tăng

  • Tỷ lệ vượt qua kiểm thử Rust core đã tăng từ 92% hai tháng trước lên 95,9%
  • Vẫn còn khoảng 65 bài kiểm thử chưa qua, nhưng nguyên nhân có vẻ khá giống nhau, nên phạm vi bản chất của các sửa đổi còn lại đã được thu hẹp tương đối
  • Phía .NET cũng được hưởng lợi từ cùng các bản sửa, và đã có thể chạy 96,3% các bài kiểm thử Rust core

Sửa số nguyên 128 bit và checked arithmetic

  • Phần lớn cải thiện đến từ việc sửa intrinsic 128 bit, checked arithmetic và subslicing
  • Intrinsic popcount của C có ba dạng: __builtin_popcount, __builtin_popcountl, __builtin_popcountll
    • Trái với tên gọi, __builtin_popcountll hoạt động trên unsigned long long, không phải __int128_t
    • Trên x86_64 Linux và GCC, cả unsigned long lẫn unsigned long long đều là 64 bit
  • Cách triển khai cũ âm thầm cắt số nguyên 128 bit xuống 64 bit trong intrinsic đếm bit, dẫn đến kết quả sai
  • popcount 128 bit được mô phỏng bằng cách tính riêng số set bit của 64 bit thấp và 64 bit cao rồi cộng lại
  • Với overflow check của phép nhân 128 bit, do chưa tìm được cách hiệu quả nên xử lý bằng phương pháp đơn giản
    • Nếu b khác 0 và (a * b) / b == a thì được xem là không overflow
    • Không phải cách đột phá, nhưng đủ để vượt qua thêm vài bài kiểm thử

Lỗi subslicing và intrinsic fallback

  • Lỗi subslicing là vấn đề offset con trỏ dữ liệu của slice theo đơn vị byte thay vì theo phần tử do thiếu sizeof
  • Lỗi này chỉ gây hỏng khi subslicing từ cuối slice chứ không phải từ đầu, và vì đây là dạng thường dùng trong pattern matching nên đã lâu không bị phát hiện
  • Với byte slice và string slice, kích thước của byte và UTF-8 code unit đều là 1 byte nên các bài kiểm thử riêng vẫn qua, còn lỗi lộ ra trong toàn bộ test suite của Rust compiler
  • Một số intrinsic có thể dùng triển khai fallback của Rust compiler thay vì tự triển khai trực tiếp
  • carrying_mul_add cần thực hiện phép nhân trên số nguyên lớn gấp đôi input
    • Đến 64 bit thì có thể xử lý, nhưng input 128 bit cần số nguyên 256 bit
    • LLVM hỗ trợ số nguyên 256 bit, nhưng C và .NET thì không
  • Triển khai fallback của Rust compiler thực hiện phép nhân và cộng 256 bit bằng số nguyên 128 bit, nên có thể tham khảo để mô phỏng phép toán 128 bit chỉ bằng số nguyên 64 bit

Hỗ trợ nhiều trình biên dịch C hơn

  • Dự án muốn tăng khả năng mã Rust chạy được trên nhiều trình biên dịch C đặc thù hơn
  • Đã có các ví dụ Rust chạy trên Game Boy, được biên dịch chỉ bằng lệnh move, hoặc chạy bằng Holly C của Temple OS
  • Không thể trực tiếp hỗ trợ các proprietary C compiler không thể tiếp cận, nhưng nếu mở rộng phạm vi hỗ trợ các obscure C compiler thì khả năng mã Rust chạy trong các môi trường đó sẽ tăng lên
  • Nhiều nền tảng không được hỗ trợ do thiếu tài liệu và khó tiếp cận
  • Liên quan đến thảo luận viết một phần Git bằng Rust, các nền tảng proprietary như NonStop không hỗ trợ Rust, LLVM hay GCC, có thể dẫn đến vấn đề giảm hoặc ngừng hỗ trợ Git
  • Nếu biên dịch Rust sang C, về lý thuyết có thể chạy Rust ở bất cứ nơi nào có C
    • Chưa chắc có thể обход được vấn đề trên mọi nền tảng
    • Để có hợp pháp trình biên dịch cho nền tảng đó cần mua máy chủ, vượt xa ngân sách
    • Có vẻ ít khả năng Rust sẽ sớm chạy được trên các nền tảng như vậy

Chiến lược output C hiện tại

  • Kế hoạch hiện tại là tạo mã tuân thủ C99 chuẩn nhiều nhất có thể hoặc gần với ANSI C
  • Vì cần một phần threading support để khởi tạo thread-local, hướng đi là chỉ dùng API POSIX chuẩn
  • Dự án đang tự có các triển khai fallback cho một số intrinsic nhất định và tăng dần danh sách đó
  • Một số trình biên dịch ANSI C đã chạy thành công các chương trình Rust rất đơn giản
  • Mục tiêu là để ngay cả các nền tảng hiện khó hỗ trợ cũng có thể được thêm tương đối dễ khi phát sinh nhu cầu

Cải thiện nhỏ về hiệu năng và kích thước file

  • Khi xuất integer literal, dự án phản ánh thực tế rằng các số nguyên nhỏ hơn 2^32 thì dạng decimal bằng hoặc ngắn hơn dạng hexadecimal
    • 255 ngắn hơn 0xFF 1 byte
    • 65536 cũng ngắn hơn 0xFFFF
    • Do prefix 0x, biểu diễn hexadecimal không nhỏ hơn trước mốc 2^32
  • Trong trường hợp cực đoan chuyển toàn bộ Rust compiler sang C, từng có lúc tạo ra C source file lên tới 1GB, nên cả mức giảm kích thước file theo tỷ lệ nhỏ cũng có tác động
  • #line directive dùng để chèn debug info cũng được làm thông minh hơn
    • Tên source file chỉ được đưa vào khi thay đổi
    • Có thể giảm đáng kể kích thước file khi dùng debug info

Tái cấu trúc nội bộ và dọn dẹp IR

  • Một số chức năng bên trong rustc_codegen_clr được tách thành crate riêng để tăng tốc incremental build
  • Công việc chuyển sang interned IR tiết kiệm bộ nhớ hơn cũng đang diễn ra
  • IR cũ có những rvalue/lvalue đặc thù không ánh xạ tốt sang C, và vấn đề trở nên lớn hơn với các tính năng Rust như dynamically sized type
  • Ví dụ, trong custom dynamically sized type như MyStr, &self.s trông có vẻ đơn giản nhưng thực tế phải xử lý fat pointer metadata
    • Nếu dùng compound literal của C99, có thể tạo struct FatPtr_str trong một dòng
    • Trong ANSI C, cần đưa datameta lần lượt vào biến tạm rồi trả về
  • Khi một dòng Rust và MIR mở rộng thành nhiều dòng C, IR cũ xử lý bằng cơ chế scope nội bộ có temporary local và sub-statement
  • Cách mới có giai đoạn setup phức tạp hơn, nhưng làm toàn bộ IR đơn giản hơn; khi giải quyết xong các trường hợp khó cuối cùng còn lại, có thể loại bỏ chức năng tương ứng của IR cũ

Công việc tiếp theo

  • Thời gian thực hiện dự án đã khoảng 1,5 năm, và càng ít bug thì càng mất nhiều thời gian hơn để tìm các bug còn lại
  • Đang thực hiện phần 2 của “Rust panics under the hood”, nhằm phân tích từng bước quá trình panic của Rust
  • Riêng phần giải thích việc tạo panic message đã dài 10 phút, nên cũng đang cân nhắc chia bài viết thành hai phần
  • Dự án cũng đang làm một memory profiler nhỏ và chính xác cho Rust
    • Quy mô mã khoảng 2K LOC
    • Lịch trình khá gấp, nhưng tác giả muốn viết bài liên quan trong vài tuần tới

1 bình luận

 
GN⁺ 2025-04-14
Ý kiến trên Hacker News
  • Vẫn đang chờ ít nhất một trong các frontend hoặc backend Rust thay thế có thể bootstrap Rust trên alpha, hppa, m68k, sh4
    Ban đầu dự án rustc_codegen_gcc từng hứa điều này nhưng cuối cùng không thực hiện được
    • Không rõ là họ thực sự dùng cả bốn nền tảng đó, hay đó chỉ là một tiêu chí tùy ý để xem là hỗ trợ nền tảng đầy đủ
    • m68k-unknown-linux-gnu chẳng phải đã được merge thành target Tier-3 của Rust rồi sao? [0]
      [0] https://github.com/rust-lang/compiler-team/issues/458
    • Họ đã từ bỏ mục tiêu đó rồi à? Lần cuối tôi nghe thì nó vẫn đang được phát triển
    • Rust vẫn chưa hỗ trợ tử tế cả OpenBSD on x86_64
  • fractalfir thực sự là một người rất có tài. Tôi thường thấy trên Reddit Rust; tuy không phải kiểu người quá am hiểu về compiler, nhưng có vẻ mọi người khá thích các sản phẩm của họ
    • Theo tôi biết thì còn khá trẻ. Mong là phía trước sẽ có một tương lai tươi sáng
  • Trong câu “phần lớn các thành phần của std hoạt động khoảng 95% trên .NET và 80% trên C”, tiêu đề HN đã bỏ mất điểm rằng tỷ lệ vượt qua 95% chỉ áp dụng cho .NET
    Phía GCC/Clang chỉ là “80%” thôi
    • README đã lỗi thời. Các con số đó là từ đầu năm nay, còn hiện tại là như sau
      | .NET Core tests | 1764 | 48 | 20 | 96.29% |
      | C Core tests | 1712 | 71 | 8 | 95.59% |
  • Có nhiều ứng dụng thú vị có thể thử với thứ này. Điều đầu tiên tôi nghĩ đến là cải thiện khả năng tương tác với các ngôn ngữ khác như Python
    • Dùng PyO3 thì khả năng tương tác vốn đã rất tốt rồi. Ngoại lệ là khi mọi người muốn build phần Rust từ source và thấy phiền khi phải cài compiler Rust
      Hack này là một backend compiler Rust. Vì backend nhận đầu vào là các lệnh theo từng nền tảng, mã C không đơn giản được sinh ra sẽ không có tính portable
      Người dùng sẽ phải nhận source đã được tạo sẵn theo từng nền tảng, hoặc tự cài compiler Rust cùng backend này để tự tạo ra nó
    • Nó đem lại lợi ích mới nào mà các hàm extern "c" của Rust chưa làm được?
  • Đây là đi từ LLVM IR sang C à? Hay là từ Rust AST sang C?
    • Tôi tìm được câu trả lời trong README của dự án
      “Biểu diễn .NET IR của tôi ánh xạ tốt sang C, nhờ đó tôi có thể thêm hỗ trợ biên dịch Rust sang C chỉ với 2–3 nghìn dòng mã. Gần như toàn bộ codebase được tái sử dụng, còn mã dành riêng cho C và .NET chỉ tồn tại ở bước cuối cùng của quá trình biên dịch”
    • Đây là backend của rustc, tức là một lựa chọn thay thế cho các backend LLVM, GCC, Cranelift
      Ban đầu nó khởi đầu như một backend .NET, nhưng họ nhận ra cách tiếp cận này cũng có thể hỗ trợ sinh mã C một cách dễ dàng nên đã thêm vào. Nó chuyển những gì rustc truyền sang thành biểu diễn trung gian (IR) riêng để xử lý
  • Compiler từ Nim sang C có tỷ lệ vượt qua kiểm thử là 100%
  • Nhưng liệu nó có giữ được các bảo đảm kiểu Rust không?
    • Nếu bản thân phép chuyển đổi không có bug thì không có lý do gì là không. Các bảo đảm tĩnh đã được kiểm tra và tồn tại với tiền đề rằng nó chuyển đổi mã Rust vốn đã biên dịch được bằng compiler Rust thông thường
      Các bảo đảm động như kiểm tra phạm vi cũng có thể triển khai trên runtime C mà không vấn đề gì
    • Có lý do gì để không được không?
  • Rất tuyệt. Chiều từ C sang Rust cũng sẽ rất hay
    • Với một chương trình không tầm thường, có lẽ sẽ phải đặt toàn bộ vào một khối unsafe lớn
      C không truyền tất cả thông tin mà Rust cần biết một cách tường minh để có thể biên dịch được
    • Mark Russinovich gần đây đã có một bài nói tại hội nghị Rust ở Anh, trong đó nhắc đến nỗ lực chuyển đổi C→Rust quy mô lớn bên trong Microsoft
      https://www.youtube.com/watch?v=1VgptLwP588
    • Những công cụ như vậy đã tồn tại. Vấn đề là chúng dùng nhiều khối unsafe, và thường thì mã kết quả không có chất Rust
      Ví dụ, việc biến một state machine dựa trên biến toàn cục thành một state machine mang phong cách Rust hơn, chẳng hạn dùng enum có tên, sẽ rất khó
      Có thể sẽ làm được nếu có sự trợ giúp của AI đủ mạnh, nhưng AI hiện vẫn còn khá thiếu khả năng thực sự làm đúng điều ta muốn, nên khó nói là đã sẵn sàng. Nó cũng cần đủ bộ nhớ để đưa toàn bộ codebase C và Rust vào cửa sổ ngữ cảnh; nếu mã vượt quá một quy mô nhất định thì rất nhanh sẽ cần phần cứng cực kỳ đắt đỏ. Nếu không, giống như nhiều LLM hỗ trợ lập trình, nó sẽ sinh ra các đoạn mã độc lập không tương thích với nhau
      Dù vậy, nếu muốn mở rộng một dự án C bằng Rust hoặc viết lại dần dần, https://c2rust.com/ có thể dùng ngay
    • Xem https://github.com/immunant/c2rust
    • Bạn kỳ vọng lợi ích gì từ việc này?
  • Rust sang C? Sao phải làm vậy. Cứ dùng C là được. Nếu hiểu được Rust thì chắc chắn cũng hiểu và dùng C thành thạo được mà
    • Để có được các ưu điểm của Rust cùng với khả năng tương thích và hỗ trợ kiến trúc rộng khắp của gcc/C compiler nói chung
      Rust là một ngôn ngữ hiện đại với quản lý gói, công cụ build/test tích hợp gọn nhẹ, ít gánh nặng cũ hơn nhiều, cùng các tính năng và cú pháp bậc cao mà mọi người thực sự thích
      C cũng gọn gàng, nhưng các codebase phức tạp hưởng lợi từ một ngôn ngữ hiện đại giúp tạo ra các abstraction vững chắc trong khi vẫn giữ tốc độ của C. Tất nhiên cũng không thể bỏ qua borrow checker và an toàn bộ nhớ
    • Để có được lợi ích của Rust trên các nền tảng mà rustc không hỗ trợ. Khá dễ hiểu
    • Ở đây có vẻ cũng có khá nhiều tính chất “làm vì có thể làm”, nhưng cũng là vì có nhiều nền tảng Rust chưa hỗ trợ
      Nếu có một bộ chuyển đổi Rust→C sinh ra mã C đủ chuẩn, nó có thể lấp khoảng trống đó