Rust起飞系列: c. 编译系统

编译系统

参考文档:

Rust编译过程

rustc 的编译过程包含两个层面的“转换阶段”:

  • 一是源代码到可执行文件的编译流程
  • 二是编译器自身的构建流程
                          ┌─────────────────────┐
                          │    源代码 (.rs)      │
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │   词法分析 (Lexer)   │  ← 普通注释 (//, /* */) 在此阶段被识别并丢弃
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │   语法分析 (Parser)   │  → 生成 AST(含宏调用、属性)
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │  宏展开 & 配置处理   │  ← 文档注释 (///, //!) 被转换为 #[doc] 属性
                          │  (Macro Expansion)   │     并在此阶段展开所有宏
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │   HIR 生成          │  → 去语法糖,结构化表示
                          │  (High-Level IR)    │
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │ 类型检查 & 借用检查  │  ← 最复杂阶段:类型推导、trait解析、生命周期、所有权
                          │  (Type & Borrowck)  │
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │   MIR 生成与优化    │  → 基于控制流图,进行多种优化
                          │  (Mid-Level IR)     │
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │   代码生成 (LLVM IR) │  → 将 MIR 转换为 LLVM IR,并交由 LLVM 优化
                          │  (Code Generation)  │
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │  链接 (Linking)      │  → 调用系统链接器,合并目标文件和库
                          └──────────┬──────────┘
                                     │
                                     ▼
                          ┌─────────────────────┐
                          │   可执行文件 / 库    │
                          └─────────────────────┘

一. 源代码的编译流程:从文本到机器码

rustc 将 Rust 源代码转换为可执行文件,主要经历以下几个关键阶段:

  1. 词法分析 (Lexing)

    • 输入:源代码 (&str)。
    • 过程rustc_lexer 将源代码字符流转换为一个个的标记(Token),如关键字、标识符、运算符等。
    • 输出:Token 流。
  2. 语法分析 (Parsing)

    • 输入:Token 流。
    • 过程rustc_parse 将 Token 流按照 Rust 语法规则,构建成一棵抽象语法树(AST)。此时,AST 中仍包含宏调用和属性。
    • 输出:AST(抽象语法树)。
  3. 宏展开与配置处理 (Macro Expansion & Configuration)

    • 输入:未展开的 AST。
    • 过程rustc_expand 负责展开所有宏,并处理 #[cfg] 等条件编译属性,移除不需要的代码。
    • 输出:一个经过展开、不再包含宏的完整 AST。
  4. 高层中间表示 (HIR) 生成

    • 输入:展开后的 AST。
    • 过程rustc_hir 将 AST 降级为更便于后续分析的高层中间表示(HIR)。HIR 对 AST 进行了结构化处理,去除了部分语法糖。
    • 输出:HIR。
  5. 类型检查与借用检查 (Type Checking & Borrow Checking)

    • 输入:HIR。
    • 过程:这是最复杂的阶段。rustc_hir_analysisrustc_borrowck 等组件会对 HIR 进行完整的类型推导、trait 解析、生命周期检查和所有权验证,确保代码的内存安全。
    • 输出:验证通过的 HIR。
  6. 中间中间表示 (MIR) 生成与优化

    • 输入:验证后的 HIR。
    • 过程rustc_mir_build 将 HIR 转换为更底层的中间中间表示(MIR)。MIR 是一种基于控制流图的表示,更适合进行借用检查和各种优化。随后,rustc_mir_transform 会对 MIR 执行一系列优化。
    • 输出:优化后的 MIR。
  7. 代码生成 (Code Generation)

    • 输入:优化后的 MIR。
    • 过程:这是后端的工作。rustc_codegen_llvm(或其他后端如 GCC、Cranelift)将 MIR 转换为 LLVM IR。LLVM IR 随后被传递给 LLVM 进行进一步的优化,并最终生成目标平台的机器码。
    • 输出:目标文件(.o 文件)或汇编代码。
  8. 链接 (Linking)

    • 输入:多个目标文件和系统库。
    • 过程:编译器调用系统链接器,将所有目标文件和必要的库组合在一起,解决符号引用。
    • 输出:最终的可执行文件或库。

二. 编译器自身的构建流程:自举 (Bootstrapping)

Rust 编译器本身也是用 Rust 写的,它的构建过程被称为“自举”,分为几个阶段:

  1. Stage 0:使用预编译好的上一版本官方 rustc 编译器(即所谓的 stage0 编译器)来编译当前 rustc 的源码。这个阶段产生的是 stage1 编译器。

  2. Stage 1:使用刚刚在 Stage 0 中生成的 stage1 编译器,再次编译当前的 rustc 源码。这个阶段产生的是 stage2 编译器。

  3. Stage 2stage2 编译器被认为是“真正的”当前版本编译器。为了验证其正确性,通常还会用它再次编译标准库,确保结果与 Stage 1 编译的一致(即 Stage 3 构建)。

    这种多阶段构建方式确保了新版本的编译器能够自我编译,是一种对编译器本身的验证。

三. 源码注释的处理

普通注释在词法分析阶段就被丢弃,而文档注释则在宏展开阶段被转换为 #[doc] 属性,供 rustdoc 等工具在后续阶段使用。

  1. 普通注释:在词法分析阶段被忽略

    普通的代码注释(如 ///* ... */在词法分析(Lexing)阶段就被识别并当作空白符(whitespace)丢弃了。它们不会进入后续的编译流程,也不会对生成的程序产生任何影响。

    rustc 的词法分析器(rustc_lexer)会将它们识别为 LineCommentBlockComment 类型的 Token,但在构建用于编译的 Token 流时,这些注释 Token 会被直接忽略。

  2. 文档注释:在宏展开阶段转换为属性

    /////! 开头的文档注释,处理方式则完全不同。它们在宏展开(Macro Expansion)阶段被编译器捕获,并转换为 #[doc = "..."] 这样的属性

    这个转换过程在编译早期发生,其关键点在于:

    • 文档注释的内容会被提取并转换成 doc 属性,附加到它们所注解的项(item)上。
    • 从编译器的角度看,/// 注释最终和 #[doc = "..."] 属性是等价的。
    • 这些 doc 属性会像其他属性一样,保存在抽象语法树(AST)中,并随之后的编译流程传递。
  3. 后续阶段

    转换为 #[doc] 属性后,文档注释的信息就可供编译器的后续阶段和其他工具使用了:

    • rustdoc:这是最主要的消费者。rustc_resolve crate 中的 rustdoc.rs 模块会解析这些 #[doc] 属性,提取其中的信息来生成 API 文档。
    • 宏展开:在宏展开过程中,编译器会特别注意文档注释,因为它们会被转换为特定的属性。
    • 抽象语法树(AST):注释的位置信息会通过 Span 与 AST 节点关联,但注释本身并不作为独立的语法结构存在。在需要保留注释的场景(如代码格式化工具)中,会通过特殊手段(如注释向量)来管理。
posted @ 2026-07-14 18:23  HEZOF  阅读(1)  评论(0)    收藏  举报