编译器源代码的高层次概观
Crate 结构
Rust的主要存储库由src
目录组成,该目录下有许多crate。 这些crate包含标准库和编译器的源代码。 当然,本文主要针对后者。
Rustc由许多crate组成,包括rustc_ast
,rustc
,rustc_target
,rustc_codegen
,rustc_driver
等。
每个crate的源码都可以在src/libXXX
之类的目录中找到,其中XXX是crate名称。
(注:这些crate的名称和划分不是一成不变的,可能会随着时间而改变。 目前,我们倾向于采用更细粒度的划分来帮助缩短编译时间, 尽管随着增量编译的改进,这种情况可能会发生变化。)
这些crate的依赖关系结构大致是钻石形的:
rustc_driver
/ | \
/ | \
/ | \
/ v \
rustc_codegen rustc_borrowck ... rustc_metadata
\ | /
\ | /
\ | /
\ v /
rustc
|
v
rustc_ast
/ \
/ \
rustc_span rustc_builtin_macros
在这个格的顶部的rustc_driver
crate是rust编译器的"main"函数。
它没有太多的“实际代码”,而是将其他crate中定义的所有代码绑定在一起,并定义了整个执行流程。
(但是,随着我们越来越多地向 查询模型 过渡,编译的“流程”正越来越少地集中定义。)
在另一端,rustc
crate定义了其余所有编译器中使用的通用的数据结构(例如,如何表示类型,trait和程序本身)。
它也包含一些编译器本身的代码,尽管相对有限。
最后,位于中间的凸出部分中的所有crate定义了编译器的大部分内容——它们都依赖于rustc
,
因此它们可以利用在那里定义的各种类型,并且导出rustc_driver
将根据需要调用的公共子过程
(这些crate导出的内容越来越多是“查询定义”,但这些内容将在稍后介绍)。
在rustc
下面的是构成parser和错误报告机制的各种crate。
它们也是internal部分的一部分
(尽管它们确实确实会被其他的一些crate使用;但我们希望逐渐淘汰这种做法)。
编译的主要阶段
Rust编译器目前处于过渡阶段。 它曾经是一个纯粹的“基于pass”的编译器,我们在整个程序中运行了许多pass,每个过程都进行了特定的转换。 我们正在逐步将这种基于pass的代码替换为基于按需查询的替代方案。 在查询模型中,我们自结果往回工作,执行一个query来表达我们的最终目标(例如“编译此crate”)。 该查询又可以进行其他查询(例如“为我提供crate中所有模块的列表”)。 这些查询会进行其他查询,这些查询最终会在基本操作中触底,例如解析输入,运行类型检查器等等。 这种按需模式允许我们做一些令人兴奋的事情,例如只做少量工作就能完成对单个函数的类型检查。 它还有助于增量编译。 (有关定义查询的详细信息,请查看查询模型。)
无论基于pass还是查询,编译器必须执行的基本操作都是相同的。 唯一改变的是这些操作是前后调用还是按需调用。 为了编译一个Rust crate,以下是我们采取的一般步骤:
-
Parsing 输入
- 这一步将处理
.rs
文件并产生AST(“抽象语法树”) - AST是在
src/librustc_ast/ast.rs
中定义的。 它旨在紧密地匹配Rust语言的词汇语法。
- 这一步将处理
-
名称解析,宏扩展和配置
- parse完成后,我们将递归处理AST,解析路径并扩展宏。这个过程也处理
#[cfg]
节点,因此也可能把东西从AST中剥离出来。
- parse完成后,我们将递归处理AST,解析路径并扩展宏。这个过程也处理
-
降级成HIR
- 名称解析完成后,我们将AST转换为HIR,或者说“高级中间表示”。 HIR在
src/librustc_middle/hir/
中定义;该模块还包含降级代码。 - HIR是AST的轻度简化版。它比AST进行了更多处理,并且更适合随后的分析。 它不需要匹配Rust语言的语法。
- 一个简单例子:在AST中,我们保留了用户编写的括号,
因此,即使
((1 + 2)+ 3)
和1 + 2 + 3
是等效的,它们也被解析为不同的抽象语法树。 但是,在HIR中,括号节点被删除,并且这两个表达式以相同的方式表示。
- 类型检查和后续分析
- 处理HIR的重要步骤是执行类型检查。
该过程为每个HIR表达式分配类型,并且还负责解析一些“类型相关”的路径,例如字段访问
(
x.f
——我们不知道正在访问哪个字段f
,直到我们知道“x”的类型) 和关联类型(T::Item
——在知道T
是什么之前,我们无法知道Item
是什么类型)。 - 类型检查会创建“side-tables”(
TypeckTables
),其中包括表达式的类型,方法的解析方式等。 - 经过类型检查后,我们可以进行其他分析,例如访问控制检查。
- 名称解析完成后,我们将AST转换为HIR,或者说“高级中间表示”。 HIR在
-
降级成MIR并进行后续处理
- 完成类型检查后,我们可以将HIR降低为MIR(“中级IR”),这是Rust的非常脱糖的版本,非常适合借用检查和某些高级优化。
-
转换为LLVM和LLVM优化
- 从MIR,我们可以生成LLVM IR。
- 然后LLVM会运行其各种优化,这会产生许多
.o
文件(每个“codegen单位”一个)。
-
链接
- 最后,这些
.o
文件会链接在一起。
- 最后,这些