HAIR 和 MIR 的构建

HIR lower到 MIR 的过程会在下面(可能不完整)这些item上进行:

  • 函数体和闭包体
  • staticconst的初始化
  • 枚举判别的初始化
  • 任何类型的胶水和补充代码
    • Tuple结构体的初始化函数
    • Drop 代码 ( Drop::drop 函数不会直接被调用)
    • 没有显式 Drop 实现的对象的drop

Lowering是通过调用mir_built查询触发的。 HIRMIR之间有一个中间表示,称为HAIR,这仅在lowering过程中使用。 HAIR的最重要特征是各种调整(在没有显式语法的情况下发生),例如隐式类型转换,自动解引用,自动引用和重载方法调用,已成为显式强制转换,解引用操作,引用操作和具体的函数调用。

HAIR的数据类型与HIR数据类型类似, 但是例如-xhair::ExprKind::Neg(hir::Expr) , 而不是hair::ExprKind::Neg(hair::Expr)。 这种shallow的特性使HAIR能够表示HIR具有的所有数据类型,而不必创建整个HIR的副本。 MIR lowering 将首先将最上面的表达式从HIR转换为HAIR(在rustc_mir_build::hair::cx::expr中),然后递归处理HAIR表达式。

Lowering会为函数签名中指定的每个参数创建局部变量。 接下来,它为指定的每个绑定创建局部变量(例如, (a, b): (i32, String))产生3个绑定,一个用于参数,两个用于绑定。 接下来,它生成字段访问,该访问从参数读取字段并将其值写入绑定变量。

在解决了初始化的情况下,lowering为函数体递归生成MIR( Block 表达式)并将结果写入RETURN_PLACE

unpack! 所有东西

生成MIR的函数有两种模式。 第一种情况,如果该函数仅生成语句,则它将以基本块作为参数,这些语句应放入该基本块。 然后可以正常返回结果:

fn generate_some_mir(&mut self, block: BasicBlock) -> ResultType {
   ...
}

但是还有其他一些函数会生成新的基本块。 例如,lowering像if foo { 22 } else { 44 }这样的表达式需要生成一个小的“菱形图”。 在这种情况下,函数将在其代码开始处使用一个基本块,并在代码生成结束时返回一个(可能)新的基本块。 BlockAnd类型用于表示此类情况:

fn generate_more_mir(&mut self, block: BasicBlock) -> BlockAnd<ResultType> {
    ...
}

当您调用这些函数时,通常有一个局部变量block,它实际上是一个“光标”。 它代表了我们要添加新的MIR的位置。 当调用generate_more_mir时,您会想更新该光标。 您可以手动执行此操作,但这很繁琐:

let mut block;
let v = match self.generate_more_mir(..) {
    BlockAnd { block: new_block, value: v } => {
        block = new_block;
        v
    }
};

For this reason, we offer a macro that lets you write let v = unpack!(block = self.generate_more_mir(...)). It simply extracts the new block and overwrites the variable block that you named in the unpack!.

因此,我们提供了一个宏,可让您编写 let v = unpack!(block = self.generate_more_mir(...))。 它简单地提取新的块并覆盖在unpack!中指明的变量block

将表达式 Lowering 到 MIR

本质上一个表达式可以有四种表示形式:

  • Place 指一个(或一部分)已经存在的内存地址(本地,静态,或者提升过的)
  • Rvalue 是可以给一个Place赋值的东西
  • Operand 是一个给像 + 这样的运算符或者一个函数调用的参数
  • 一个存放了一个值的拷贝的临时变量

下图描绘了表示之间的交互的一般概述:

点此看大图

我们首先将函数体lowering到一个 Rvalue,这样我们就可以为 RETURN_PLACE 创建一个赋值, 这个Rvalue的lowering反过来会触发其参数的Operand lowering(如果有的话) lowering Operaper会产生一个const操作数,或者移动/复制出Place,从而触发Place lowering。 如果降低的表达式包含操作,则lowering到Place的表达式可以触发创建一个临时变量。 这是蛇咬自己的尾巴的地方,我们需要触发Rvalue lowering,以将表达式的值写入本地变量。

Operator lowering

内置类型的运算符不会lower为函数调用(这将导致无限递归调用,因为trait包含了操作本身)。相反,存在用于二元和一元运算符和索引运算的Rvalue。 这些Rvalue稍后将生成为llvm基本操作或llvm内部函数。

所有其他类型的运算符都被lower为对运算符对应特征的impl的函数调用。

无论采用哪种lower方式,运算符的参数都会lower为Operand。 这意味着所有参数都是常量,或者引用局部或静态位置中已经存在的值。

方法调用的 lowering

方法调用被降低到与一般函数调用相同的TerminatorKind。 在MIR中,方法调用和一般函数调用之间不再存在差异。

条件

不带字段变量的enumif条件判断和match语句都会被lower为TerminatorKind::SwitchInt。 每个可能的值(如果为if条件判断,则对应的值为01)都有一个对应的BasicBlock。 分支的参数是表示if条件值的Operand

模式匹配

具有字段enummatch语句也被lower为TerminatorKind::SwitchInt,但是操作数是一个Place,可以在其中找到该值的判别式。 这通常涉及将判别式读取为新的临时变量。

聚合构造

任何类型的聚合值(例如结构或元组)都是通过Rvalue::Aggregate建立的。 所有字段都lower为Operator。 从本质上讲,这等效于每个聚合字段都会有一个赋值语句,再加上一个对enum的判别式的赋值。