C and Rust
C and Rust
The process of compiling a C program
1. Preprocessing
Tiền xử lý là bước đầu tiên của bất kỳ quá trình biên dịch C nào. Nó loại bỏ các chú thích, mở rộng các tệp bao gồm và macro, và xử lý các lệnh biên dịch có điều kiện. Kết quả có thể được xuất ra dưới dạng tệp .i.
2. Compilation
Biên dịch là bước thứ hai. Nó lấy đầu ra của bộ tiền xử lý và mã nguồn, rồi tạo ra mã nguồn hợp ngữ (ví dụ: hello.s). Ngôn ngữ hợp ngữ là một ngôn ngữ lập trình cấp thấp (thậm chí còn thấp hơn cả C) mà vẫn dễ đọc đối với con người nhưng bao gồm các lệnh gợi nhớ có sự tương ứng chặt chẽ với các lệnh máy.
3. Assembly
Lập trình hợp ngữ là giai đoạn thứ ba của quá trình biên dịch. Nó lấy mã nguồn hợp ngữ và tạo ra một tập tin đối tượng hello.o, chứa các lệnh máy thực tế và các ký hiệu (ví dụ: tên hàm) mà con người không còn đọc được nữa vì chúng ở dạng bit.
4. Linking
Liên kết là giai đoạn cuối cùng của quá trình biên dịch. Nó nhận một hoặc nhiều tệp đối tượng hoặc thư viện làm đầu vào và kết hợp chúng để tạo ra một tệp duy nhất (thường là tệp thực thi). Trong quá trình này, nó giải quyết các tham chiếu đến các ký hiệu bên ngoài, gán địa chỉ cuối cùng cho các thủ tục/hàm và biến, và sửa đổi mã và dữ liệu để phản ánh các địa chỉ mới (một quá trình được gọi là định vị lại).
The process of compiling a Rust program
1. Invocation
Quá trình biên dịch bắt đầu khi người dùng viết một chương trình nguồn Rust dưới dạng văn bản và gọi trình biên dịch rustc. Công việc mà trình biên dịch cần thực hiện được xác định bởi các tùy chọn dòng lệnh. Ví dụ, có thể bật các tính năng hàng đêm (-Z flags), thực hiện các bản dựng chỉ kiểm tra hoặc phát ra Biểu diễn Trung gian LLVM (LLVM-IR) thay vì mã máy thực thi. Lệnh gọi thực thi rustc có thể được thực hiện gián tiếp thông qua việc sử dụng cargo.
Việc phân tích cú pháp đối số dòng lệnh diễn ra trong rustc_driver. Crate này định nghĩa cấu hình biên dịch được người dùng yêu cầu và chuyển nó cho phần còn lại của quá trình biên dịch dưới dạng rustc_interface::Config.
2. Lexing and parsing
Văn bản mã nguồn Rust thô được phân tích bởi một trình phân tích cú pháp cấp thấp nằm trong rustc_lexer. Ở giai đoạn này, văn bản nguồn được chuyển đổi thành một luồng các đơn vị mã nguồn nguyên tử được gọi là token. Trình phân tích cú pháp hỗ trợ mã hóa ký tự Unicode.
Luồng token đi qua một trình phân tích cú pháp cấp cao hơn nằm trong rustc_parse để chuẩn bị cho giai đoạn tiếp theo của quá trình biên dịch. Cấu trúc Lexer được sử dụng ở giai đoạn này để thực hiện một tập hợp các xác thực và chuyển đổi chuỗi thành các ký hiệu nội bộ. Nội bộ chuỗi là một cách lưu trữ chỉ một bản sao bất biến của mỗi giá trị chuỗi khác nhau.
Trình phân tích cú pháp có giao diện nhỏ và không phụ thuộc trực tiếp vào cơ sở hạ tầng chẩn đoán trong rustc. Thay vào đó, nó cung cấp thông tin chẩn đoán dưới dạng dữ liệu thuần túy được phát ra trong rustc_parse::lexer dưới dạng thông tin chẩn đoán thực sự. Trình phân tích cú pháp giữ nguyên thông tin đầy đủ cho cả IDE và macro thủ tục (đôi khi được gọi là "proc-macros").
Trình phân tích cú pháp dịch luồng token từ trình phân tích cú pháp thành Cây cú pháp trừu tượng (AST). Nó sử dụng phương pháp phân tích cú pháp đệ quy từ trên xuống. Các điểm vào crate cho trình phân tích cú pháp là các phương thức Parser::parse_crate_mod() và Parser::parse_mod() được tìm thấy trong rustc_parse::parser::Parser. Điểm vào phân tích cú pháp mô-đun bên ngoài là rustc_expand::module::parse_external_mod. Và điểm vào trình phân tích cú pháp macro là Parser::parse_nonterminal().
Quá trình phân tích cú pháp được thực hiện bằng một tập hợp các phương thức tiện ích của trình phân tích cú pháp, bao gồm bump, check, eat, expect, look_ahead.
Quá trình phân tích cú pháp được tổ chức theo cấu trúc ngữ nghĩa. Các phương thức parse_* riêng biệt có thể được tìm thấy trong thư mục rustc_parse. Tên tệp nguồn tuân theo tên cấu trúc.
Cách đặt tên này được sử dụng trong nhiều giai đoạn biên dịch. Bạn sẽ tìm thấy một tệp hoặc thư mục có cùng tên trong các nguồn xây dựng phân tích cú pháp, hạ cấp, kiểm tra kiểu, hạ cấp Biểu diễn Trung gian Cấp cao có Kiểu (THIR) và Biểu diễn Trung gian Cấp trung (MIR).
Việc mở rộng macro, xác thực AST, phân giải tên và kiểm tra cú pháp sớm cũng diễn ra trong giai đoạn phân tích từ vựng và cú pháp.
Các nút AST rustc_ast::ast::{Crate, Expr, Pat, ...} được trả về từ trình phân tích cú pháp trong khi API Diag tiêu chuẩn được sử dụng để xử lý lỗi. Nói chung, trình biên dịch của Rust sẽ cố gắng khắc phục lỗi bằng cách phân tích một tập hợp con của ngữ pháp Rust, đồng thời phát ra một loại lỗi.
3. AST lowering
Tiếp theo, AST được chuyển đổi thành Biểu diễn Trung gian Cấp cao (HIR), một biểu diễn thân thiện hơn với trình biên dịch của AST. Quá trình này được gọi là "hạ cấp" và bao gồm rất nhiều bước khử đường (mở rộng và chính thức hóa các cấu trúc cú pháp rút gọn) của những thứ như vòng lặp và hàm bất đồng bộ.
Sau đó, chúng ta sử dụng HIR để thực hiện suy luận kiểu (quá trình tự động phát hiện kiểu của một biểu thức), giải quyết trait (quá trình ghép nối một impl với mỗi tham chiếu đến một trait) và kiểm tra kiểu. Kiểm tra kiểu là quá trình chuyển đổi các kiểu được tìm thấy trong HIR (hir::Ty), đại diện cho những gì người dùng đã viết, thành biểu diễn nội bộ được trình biên dịch sử dụng (Ty<'tcx>). Nó được gọi là kiểm tra kiểu dữ liệu vì thông tin này được sử dụng để xác minh tính an toàn, tính đúng đắn và tính nhất quán của các kiểu dữ liệu được sử dụng trong chương trình.
4. MIR Lowering
HIR được hạ cấp hơn nữa xuống MIR (được sử dụng để kiểm tra mượn) bằng cách xây dựng THIR (một HIR được đơn giản hóa hơn nữa, được sử dụng để kiểm tra mẫu và tính đầy đủ) để chuyển đổi thành MIR.
Chúng ta thực hiện nhiều tối ưu hóa trên MIR vì nó mang tính tổng quát và điều đó cải thiện tốc độ tạo mã và biên dịch sau này. Việc thực hiện một số tối ưu hóa ở cấp độ MIR dễ dàng hơn so với cấp độ LLVM-IR. Ví dụ, LLVM dường như không thể tối ưu hóa mẫu mà simplify_try MIR-opt tìm kiếm.
Mã Rust cũng được đơn hình hóa trong quá trình tạo mã, có nghĩa là tạo bản sao của tất cả mã chung với các tham số kiểu được thay thế bằng các kiểu cụ thể. Để làm điều này, chúng ta cần thu thập một danh sách các kiểu cụ thể cần tạo mã. Điều này được gọi là thu thập đơn hình hóa và nó diễn ra ở cấp độ MIR.
5. Code generation
Tiếp theo, chúng ta bắt đầu giai đoạn được gọi đơn giản là tạo mã hoặc codegen. Giai đoạn tạo mã là khi các biểu diễn cấp cao hơn của mã nguồn được chuyển đổi thành một tệp nhị phân có thể thực thi. Vì rustc sử dụng LLVM để tạo mã, bước đầu tiên là chuyển đổi MIR sang LLVM-IR. Đây là nơi MIR thực sự được đơn hình hóa. LLVM-IR được chuyển đến LLVM, nơi thực hiện nhiều tối ưu hóa hơn nữa, tạo ra mã máy về cơ bản là mã hợp ngữ với các kiểu và chú thích cấp thấp bổ sung được thêm vào (ví dụ: một đối tượng ELF hoặc WASM). Sau đó, các thư viện/tệp nhị phân khác nhau được liên kết với nhau để tạo ra tệp nhị phân cuối cùng.
This post is licensed under CC BY 4.0 by the author.
