Rust 编译器 MIR 构建(MIR Construction)全解析:从 HIR/THIR 到 MIR 的降级过程
2026/9/15 15:11:11 网站建设 项目流程

Rust 编译器 MIR 构建(MIR Construction)全解析:从 HIR/THIR 到 MIR 的降级过程

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

本篇技术指南深入 rustc 编译器中"MIR 构建"(MIR construction)这一核心阶段,讲解 MIR builder 如何以 THIR 为输入、把 HIR 表示的函数与常量体降级为基于基本块(basic block)的 MIR,并剖析mir_built查询、unpack!宏、四种表达式表示以及运算符、方法调用、条件分支与聚合值构造的降级规则。读完本文,你将掌握 rustc 前端到 MIR 之间的完整调用链、MIR builder 的代码组织方式,并能在 compiler/rustc_mir_build 源码中定位对应实现,为阅读借用检查、MIR 优化与代码生成打牢基础。

一、背景:什么是 MIR 构建

MIR(Mid-level IR)是 Rust 的中级中间表示,由 HIR 构建而来(MIR 背景章节),用于借用检查等流敏感的安全检查,也服务于优化与代码生成。MIR 具有三个关键特征:基于控制流图(CFG)、没有嵌套表达式、所有类型完全显式。

"MIR 构建"(MIR construction)指的就是把 HIR 降级(lowering)为 MIR的过程。rustc-dev-guide 的 construction.md 是本文的主体骨架,下面结合该文档与仓库源码逐层展开。

二、哪些条目会被降级为 MIR

根据 rustc-dev-guide,HIR 到 MIR 的降级发生在以下条目上(文档注明了"可能不完整"的清单):

  • 函数体与闭包体(function and closure bodies)
  • staticconst条目的初始化器(initializers ofstaticandconstitems)
  • 枚举判别式(discriminant)的初始化器
  • 各类胶水代码(glue)与 shim
    • 元组结构体的初始化函数(tuple struct initializer functions)
    • Drop 代码(注意:Drop::drop函数本身并不会被直接调用,真正被调用的是编译器生成的 drop glue)
    • 没有显式Drop实现类型的 drop 实现(即自动生成的 drop glue)

也就是说,凡是需要在运行时"执行"的代码,最终都要被降级为 MIR,包括编译器自动合成的代码,而不仅仅是用户手写的函数体。

三、触发入口:mir_built查询与 builder 的调用链

降级动作由调用mir_built查询触发。在仓库源码中,mir_built的实际执行由rustc_mir_buildcrate 的 hook 提供,入口是 compiler/rustc_mir_build/src/builder/mod.rs#L67 中的build_mir_inner_impl

/// Create the MIR for a given `DefId`, including unreachable code. /// /// This is the implementation of hook `build_mir_inner_impl`, which should only /// be called by the query `mir_built`. pub(crate) fn build_mir_inner_impl<'tcx>(tcx: TyCtxt<'tcx>, def: LocalDefId) -> Body<'tcx> { ... let body = match tcx.thir_body(def) { Err(error_reported) => construct_error(tcx, def, error_reported), Ok((thir, expr)) => { let build_mir = |thir: &Thir<'tcx>| match thir.body_type { thir::BodyTy::Fn(fn_sig) => construct_fn(tcx, def, thir, expr, fn_sig), thir::BodyTy::Const(ty) | thir::BodyTy::GlobalAsm(ty) => { construct_const(tcx, def, thir, expr, ty) } }; build_mir(&thir.borrow()) } }; ... body }

这段实现揭示了一个重要事实:MIR builder 并不直接使用 HIR,而是先构造 THIR(Typed HIR),再基于 THIR 工作build_mir_inner_impl首先请求thir_body查询得到Thir及其根表达式,随后根据 body 的类型(函数签名 / const / global asm)分别调用construct_fnconstruct_const。此外它还会提前执行check_match(match 穷尽性检查)与check_tail_calls(尾调用检查),出错时直接构造错误 MIR。

这些查询在 compiler/rustc_mir_build/src/lib.rs#L19-L28 的provide函数中注册:

pub fn provide(providers: &mut Providers) { providers.queries.check_match = thir::pattern::check_match; providers.queries.lit_to_const = thir::constant::lit_to_const; providers.queries.closure_saved_names_of_captured_variables = builder::closure_saved_names_of_captured_variables; providers.queries.check_unsafety = check_unsafety::check_unsafety; providers.queries.check_tail_calls = check_tail_calls::check_tail_calls; providers.queries.thir_body = thir::cx::thir_body; providers.hooks.build_mir_inner_impl = builder::build_mir_inner_impl; }

从源码结构看,rustc_mir_build主要由builder/(MIR 构建器)与thir/(THIR 构造与相关检查)两大模块组成,THIR 的类型定义位于 compiler/rustc_middle/src/thir(rustc_mir_build内的thir模块负责生成它)。文档中 THIR 与 MIR builder 的对应关系可参见 THIR 子模块 与 builder 子模块。

四、参数、绑定与函数体的初始降级

construct_fn是函数 MIR 构建的主入口(见 compiler/rustc_mir_build/src/builder/mod.rs#L451)。整个降级过程可分为三步:

  1. 为每个参数创建局部变量(local):按签名中声明的参数逐一创建。
  2. 为每个绑定创建局部变量:例如(a, b): (i32, String)会产生3 个绑定——一个是参数本身,另外两个是模式解构出来的ab
  3. 生成字段访问:从参数中读出对应字段,把值写入绑定变量,完成初始化。

(a, b): (i32, String)为例,降级会生成大致如下的字段投影(projection)与赋值语句序列:

// 假设参数是 _1 _2 = move (_1.0: i32); // a = param.0 _3 = move (_1.1: String); // b = param.1

(实际生成的顺序、临时变量与StorageLive/StorageDead语句以编译器输出为准。)字段访问在 MIR 中体现为Place上的投影(projection),例如_1.0

完成初始化之后,构建器会递归调用一个函数,为函数体(一个Block表达式)生成 MIR,并把结果写入RETURN_PLACE——即_0,这个存放返回值的特殊"局部变量"。整个流程可以在 construct_fn 的实现 中看到:创建Builder后,在调用点作用域(call_site_scope)与参数作用域(arg_scope)中调用args_and_body(START_BLOCK, arguments, arg_scope, expr),最后在返回块上以TerminatorKind::Return收尾,并调用build_drop_trees生成析构逻辑。

五、unpack!宏与BlockAnd:MIR 生成的两种函数模式

5.1 两种函数模式

生成 MIR 的函数往往落入两种模式:

模式一:只生成 statement 的函数。它接收一个基本块参数,把 statement 追加到该块上,然后像普通函数一样返回结果:

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

模式二:可能生成新基本块的函数。例如把if foo { 22 } else { 44 }降级需要生成一个小的"菱形图"(diamond-shaped graph)。这类函数接收"代码开始处的基本块",返回"代码生成结束处的基本块"(可能是新的),用BlockAnd类型承载:

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

在源码中,BlockAnd定义于 compiler/rustc_mir_build/src/builder/mod.rs#L413-L414,并标注了#[must_use]

#[must_use = "if you don't use one of these results, you're leaving a dangling edge"] struct BlockAnd<T>(BasicBlock, T);

must_use的提示语非常直白:如果丢弃了返回值,就会留下悬空的 CFG 边——这是 MIR 构建器最容易犯的错误,编译器用类型系统强制开发者处理这个结果。

5.2 游标(cursor)模式与手写样板代码

调用模式二的函数时,常见的写法是维护一个本地变量block作为"游标"(cursor),它代表"我们正在向何处追加新的 MIR"。每调用一次generate_more_mir,都要更新这个游标。手动更新非常繁琐:

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

5.3unpack!

为此,builder 提供了unpack!,让你可以直接写:

let v = unpack!(block = self.generate_more_mir(...));

它做的事就是:解构BlockAnd,取出新块并覆盖你在unpack!中指定的block变量。宏定义见 compiler/rustc_mir_build/src/builder/mod.rs#L442-L448:

macro_rules! unpack { ($x:ident = $c:expr) => {{ let BlockAnd(b, v) = $c; $x = b; v }}; }

BlockAnd还提供了一系列便捷扩展方法(BlockAndExtension),例如into_block()可以直接把BlockAnd<()>解包为BasicBlock(见 builder/mod.rs#L416-L420),极大减少了手写样板代码。在实际构建代码中,unpack!无处不在,例如as_rvalue在降级Repeat表达式(数组重复[x; N])时会先以unpack!(block = this.as_operand(...))生成操作数,再构造Rvalue::Repeat(见 builder/expr/as_rvalue.rs#L65-L79)。

六、表达式的四种表示与降级交互

文档指出,把一个表达式降级为期望的 MIR,本质上存在四种表示(representation):

表示含义例子
Place指向一个(或一部分)已存在的内存位置:local、static 或 promoted_1_1.f*_1
Rvalue可以赋值给Place的东西(赋值语句的右值)&mut _1_1 + _2
Operand例如+运算或函数调用的参数常量22move _1copy _1
temporary保存值副本的临时变量中间结果_TMP

6.1 四种表示的降级触发链

四种表示之间的交互构成一条"自噬"(snake bites its own tail)的递归链,其流程如下:

  1. 先把函数体降级为Rvalue,从而生成对RETURN_PLACE的赋值;
  2. 这个Rvalue降级会触发对其参数(若有)的Operand降级;
  3. Operand降级要么产生const操作数,要么Place移动/拷贝值,从而触发Place降级;
  4. 而表达式降级为Place时,如果表达式包含运算,则可能触发创建临时变量,把表达式降级为Rvalue写入这个局部变量
  5. 回到第 2 步,递归继续。

rustc-dev-guide 为这四种表示的交互提供了总览图:mir_overview.svg,以及更详细的视图 mir_detailed.svg(原始点文件为同目录下的.dot文件)。

6.2 源码中的对应实现

从源码结构看,这四种表示分别对应 builder/expr 下的几个核心文件:

  • as_rvalue.rs:把表达式降级为Rvalue(如as_rvalueas_local_rvalue);
  • as_operand.rs:把表达式降级为Operand
  • as_place.rs:把表达式降级为Place(核心类型PlaceBuilder);
  • as_temp.rs:创建临时变量并写入值;
  • as_constant.rs:处理常量操作数;
  • category.rs:判定表达式所属的"类别",决定走哪条降级路径。

例如在 as_rvalue.rs#L42-L64 中,as_rvalue会根据ExprKind分支处理:Scope表达式进入in_scope并推送覆盖率点,Repeat表达式则先降级操作数再构造Rvalue::Repeat。这些"as_*"函数之间相互调用,正是文档描述的递归触发链的代码级体现。

七、运算符降级(Operator lowering)

对于内建类型上的运算符,rustc不会降级为函数调用——因为 trait 实现里包含的正是运算本身,降级为调用会形成无限递归。取而代之的是,MIR 提供了二元、一元与索引运算专用的Rvalue变体,例如:

  • 二元运算:Rvalue::BinaryOp+-*==等)
  • 一元运算:Rvalue::UnaryOp!-*等)
  • 索引操作:Rvalue::Index

这些Rvalue在后端(如 LLVM)会被 codegen 为 LLVM 的原始运算(primitive operations)或 LLVM 内建函数(intrinsics)。

对于其他所有类型上的运算符,则降级为对该运算符对应 trait 的impl函数调用(例如自定义Add实现)。

无论走哪条路径,运算符的参数都会被降级为Operand——这意味着所有参数要么是常量,要么引用某个已存在于局部变量或 static 中的值,从而保证了 MIR"无嵌套表达式"的特性。

八、方法调用降级(Method call lowering)

方法调用(method call)会被降级为与函数调用相同的TerminatorKind。也就是说,在 MIR 层面方法调用与函数调用不再有任何区别——x.foo(args)foo(x, args)生成同类的 terminator(通常是TerminatorKind::Call)。

这是 MIR 极简化的典型体现:方法解析、自动引用/解引用等糖语法在 HIR/THIR 阶段已被消化,MIR 只保留"调用 + 参数列表 + 目标"这一最朴素的形式,调用点还会带上返回块与 unwind 块等后继信息。

九、条件与模式匹配的降级

9.1if条件与无字段枚举的match

if条件以及所有变体都没有字段的enummatch,会被降级为TerminatorKind::SwitchInt

  • 每一个可能的值(例如if条件的01)都对应一个BasicBlock,控制流据此继续;
  • 被分支的值(分支参数)同样是一个Operand,表示if条件的值。

因此if foo { A } else { B }会生成一个以foo的布尔值为SwitchInt分支依据、分别跳转到 A/B 两块的小型菱形 CFG——这正是第 5 节提到的"菱形图"的典型场景。

9.2 带字段枚举的match(模式匹配)

对于带字段的enum变体match,同样降级为TerminatorKind::SwitchInt,但此时被分支的Operand指向一个Place该 Place 上可以找到值的判别式(discriminant)。这通常涉及:

  1. 把判别式读入一个新的临时变量
  2. 以该临时变量为SwitchInt的分支依据;
  3. 各分支分别解构字段、绑定变量。

模式匹配相关的检查(穷尽性、非重叠等)在 MIR 构建前由 compiler/rustc_mir_build/src/thir/pattern 完成(check_match查询),构建阶段则只负责生成判别式分派与字段绑定代码。

十、聚合值构造(Aggregate construction)

任意种类的聚合值(aggregate),例如结构体(struct)或元组(tuple),都通过Rvalue::Aggregate来构造:

  • 所有字段都会被降级为Operand(文档原文为Operator,结合上下文应为操作数);
  • enum而言,还会额外包含一个判别式(discriminant)赋值。

本质上,Rvalue::Aggregate相当于**"每个聚合字段一条赋值语句 + (enum 情况下的)一次判别式赋值"**的打包形式。这样既保证了 MIR 没有嵌套表达式,又让后端(LLVM 等)能够识别"一次性构造一个聚合值"这一模式,进而生成更优的代码(例如合并为一次内存写入或向量化构造)。

十一、总结与进一步阅读

MIR 构建是 rustc 将前端类型化结果(THIR)转化为可供借用检查、优化与代码生成使用的 CFG 形式的关键环节。核心要点回顾:

  1. 触发方式:mir_built查询 →build_mir_inner_implthir_body+construct_fn/construct_const
  2. 输入输出:以 THIR 为输入(不是 HIR),产出Body(基本块 + statements + terminator + locals);
  3. 局部变量:参数、绑定(模式解构)逐一分配 local,绑定通过字段投影从参数读出;
  4. 构建风格:BlockAnd<ResultType>+unpack!宏维护"游标",避免悬空 CFG 边;
  5. 四种表达式表示(Place/Rvalue/Operand/temporary)互相递归触发;
  6. 运算符:内建类型用专用Rvalue(后续 codegen 为 LLVM 原语/内建函数),其他类型降级为 trait 实现调用;
  7. 调用:方法调用与函数调用统一为同一TerminatorKind
  8. 分支:if与 match 降级为SwitchInt,带字段枚举先读判别式到临时变量再分派;
  9. 聚合值:统一走Rvalue::Aggregate

继续深入可参考以下仓库材料:

  • MIR 数据结构的完整说明:src/doc/rustc-dev-guide/src/mir/index.md
  • MIR 构建器源码:compiler/rustc_mir_build/src/builder/mod.rs(含unpack!BlockAndbuild_mir_inner_implconstruct_fn
  • 表达式降级实现:compiler/rustc_mir_build/src/builder/expr
  • THIR 构造与检查:compiler/rustc_mir_build/src/thir
  • 查询注册:compiler/rustc_mir_build/src/lib.rs
  • 四种表达式表示交互总览图:mir_overview.svg 与详细视图 mir_detailed.svg
  • MIR 后续优化与数据流分析:src/doc/rustc-dev-guide/src/mir/optimizations.md、src/doc/rustc-dev-guide/src/mir/dataflow.md、src/doc/rustc-dev-guide/src/mir/passes.md

需要留意的是:MIR 文本输出格式仅供人类阅读、随时可能变化;若想查看真实 MIR,可使用 nightly 工具链执行rustc [filename].rs -Z mir-opt-level=0 --emit mir查看未优化版本,或使用-Z dump-mir系列选项观察各 MIR 优化 pass 前后的差异。

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询