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

发布时间:2026/9/12 10:58:24
Rust 编译器 MIR 构建(MIR Construction)全解析:从 HIR/THIR 到 MIR 的降级过程 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 构建MIRMid-level IR是 Rust 的中级中间表示由 HIR 构建而来MIR 背景章节用于借用检查等流敏感的安全检查也服务于优化与代码生成。MIR 具有三个关键特征基于控制流图CFG、没有嵌套表达式、所有类型完全显式。MIR 构建MIR construction指的就是把 HIR 降级lowering为 MIR的过程。rustc-dev-guide 的 construction.md 是本文的主体骨架下面结合该文档与仓库源码逐层展开。二、哪些条目会被降级为 MIR根据 rustc-dev-guideHIR 到 MIR 的降级发生在以下条目上文档注明了可能不完整的清单函数体与闭包体function and closure bodiesstatic与const条目的初始化器initializers ofstaticandconstitems枚举判别式discriminant的初始化器各类胶水代码glue与 shim元组结构体的初始化函数tuple struct initializer functionsDrop 代码注意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_impltcx(tcx: TyCtxttcx, def: LocalDefId) - Bodytcx { ... let body match tcx.thir_body(def) { Err(error_reported) construct_error(tcx, def, error_reported), Ok((thir, expr)) { let build_mir |thir: Thirtcx| 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而是先构造 THIRTyped HIR再基于 THIR 工作。build_mir_inner_impl首先请求thir_body查询得到Thir及其根表达式随后根据 body 的类型函数签名 / const / global asm分别调用construct_fn或construct_const。此外它还会提前执行check_matchmatch 穷尽性检查与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/thirrustc_mir_build内的thir模块负责生成它。文档中 THIR 与 MIR builder 的对应关系可参见 THIR 子模块 与 builder 子模块。四、参数、绑定与函数体的初始降级construct_fn是函数 MIR 构建的主入口见 compiler/rustc_mir_build/src/builder/mod.rs#L451。整个降级过程可分为三步为每个参数创建局部变量local按签名中声明的参数逐一创建。为每个绑定创建局部变量例如(a, b): (i32, String)会产生3 个绑定——一个是参数本身另外两个是模式解构出来的a与b。生成字段访问从参数中读出对应字段把值写入绑定变量完成初始化。以(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!宏与BlockAndMIR 生成的两种函数模式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) - BlockAndResultType { ... }在源码中BlockAnd定义于 compiler/rustc_mir_build/src/builder/mod.rs#L413-L414并标注了#[must_use]#[must_use if you dont use one of these results, youre leaving a dangling edge] struct BlockAndT(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-L448macro_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、*_1Rvalue可以赋值给Place的东西赋值语句的右值mut _1、_1 _2Operand例如运算或函数调用的参数常量22、move _1、copy _1temporary保存值副本的临时变量中间结果_TMP6.1 四种表示的降级触发链四种表示之间的交互构成一条自噬snake bites its own tail的递归链其流程如下先把函数体降级为Rvalue从而生成对RETURN_PLACE的赋值这个Rvalue降级会触发对其参数若有的Operand降级Operand降级要么产生const操作数要么从Place移动/拷贝值从而触发Place降级而表达式降级为Place时如果表达式包含运算则可能触发创建临时变量把表达式降级为Rvalue后写入这个局部变量回到第 2 步递归继续。rustc-dev-guide 为这四种表示的交互提供了总览图mir_overview.svg以及更详细的视图 mir_detailed.svg原始点文件为同目录下的.dot文件。6.2 源码中的对应实现从源码结构看这四种表示分别对应 builder/expr 下的几个核心文件as_rvalue.rs把表达式降级为Rvalue如as_rvalue、as_local_rvalueas_operand.rs把表达式降级为Operandas_place.rs把表达式降级为Place核心类型PlaceBuilderas_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条件与无字段枚举的matchif条件以及所有变体都没有字段的enum的match会被降级为TerminatorKind::SwitchInt每一个可能的值例如if条件的0和1都对应一个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。这通常涉及把判别式读入一个新的临时变量以该临时变量为SwitchInt的分支依据各分支分别解构字段、绑定变量。模式匹配相关的检查穷尽性、非重叠等在 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 形式的关键环节。核心要点回顾触发方式mir_built查询 →build_mir_inner_impl→thir_bodyconstruct_fn/construct_const输入输出以 THIR 为输入不是 HIR产出Body基本块 statements terminator locals局部变量参数、绑定模式解构逐一分配 local绑定通过字段投影从参数读出构建风格BlockAndResultTypeunpack!宏维护游标避免悬空 CFG 边四种表达式表示Place/Rvalue/Operand/temporary互相递归触发运算符内建类型用专用Rvalue后续 codegen 为 LLVM 原语/内建函数其他类型降级为 trait 实现调用调用方法调用与函数调用统一为同一TerminatorKind分支if与 match 降级为SwitchInt带字段枚举先读判别式到临时变量再分派聚合值统一走Rvalue::Aggregate。继续深入可参考以下仓库材料MIR 数据结构的完整说明src/doc/rustc-dev-guide/src/mir/index.mdMIR 构建器源码compiler/rustc_mir_build/src/builder/mod.rs含unpack!、BlockAnd、build_mir_inner_impl、construct_fn表达式降级实现compiler/rustc_mir_build/src/builder/exprTHIR 构造与检查compiler/rustc_mir_build/src/thir查询注册compiler/rustc_mir_build/src/lib.rs四种表达式表示交互总览图mir_overview.svg 与详细视图 mir_detailed.svgMIR 后续优化与数据流分析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-level0 --emit mir查看未优化版本或使用-Z dump-mir系列选项观察各 MIR 优化 pass 前后的差异。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考