IRs of Rust
The Rust compiler is very complicated. It consists of many modules, and it takes multiple steps to translate a human-readable programming language to a machine code. Though it's not necessary for typical programmers to know the internals of the compiler, it'd be very helpful if we understand how the language works. In this article, I'll take a closer look at the steps the compiler takes.
Rust playground is an excellent place to play around with the compiler-internals. All the IRs in this article are generated by it. I used Rust-1.71.0-nightly, which is the newest version as I write this article.
Steps of the compilation
When a programmer compiles a Rust code, the code goes through below steps.
Rust
|
| Lexer, Parser
V
Abstract Syntax Tree
|
| Rust Compiler
V
HIR
|
| Rust Compiler
V
MIR
|
| Rust Compiler
V
LLVM IR
|
| LLVM
V
Machine Code
First, a lexer translates a human-readable code to machine-friendly tokens. The tokens are then translated to AST by a parser. Lexing, parsing and AST are not Rust-specific concepts, so I won't discuss them in this article. If you want to know about them, please refer to the wikipedia links in the article.
The AST is translated to HIR, High-level Intermediate Representation. According to the documentation, HIR is a compiler-friendly AST. It resembles the syntax of Rust very closely, except that it's simplified. HIR is lowered to THIR, Typed High-level Intermediate Representation before converted to MIR. HIR is still quite readable to human. You'll see that in later sections.
The next step is MIR, Mid-level Intermediate Representation. It's a very simplified form. Many optimizations and safety checks are done in this stage. There are blog post and documents explaining the details of MIR; you'd probably enjoy it.
After the compiler does Rust-specific analysis and optimizations on MIR, it's passed to LLVM. From here, everything is up to LLVM, which we're not interested in.
Most information in this section is from Rust Compiler Overview. Please refer to the document if you want to know more about the compiler.
Table of contents
Since this article contains very long lines of code, I provide you a Toc here.
HIR
There isn't much change in HIR compared to the original code. Let's see how the compiler translates the fizz-buzz code below to HIR.
1fn main() {
2 for i in 1..200 {
3 if i % 15 == 0 { println!("fizzbuzz"); }
4 else if i % 3 == 0 { println!("fizz"); }
5 else if i % 5 == 0 { println!("buzz"); }
6 else { println!("{i}"); }
7 }
8}
1#[prelude_import]
2use std::prelude::rust_2021::*;
3#[macro_use]
4extern crate std;
5fn main() {
6 {
7 let _t = match #[lang = "into_iter"](#[lang = "Range"]{ start: 1, end: 200, }) {
8 mut iter => loop {
9 match #[lang = "next"](&mut iter) {
10 #[lang = "None"] {} => break,
11 #[lang = "Some"] { 0: i } => {
12 if i % 15 == 0 { {
13 ::std::io::_print(<#[lang = "format_arguments"]>::new_const(&["fizzbuzz\n"]));
14 }; }
15
16 else if i % 3 == 0 { {
17 ::std::io::_print(<#[lang = "format_arguments"]>::new_const(&["fizz\n"]));
18 }; }
19
20 else if i % 5 == 0 { {
21 ::std::io::_print(<#[lang = "format_arguments"]>::new_const(&["buzz\n"]));
22 }; }
23
24 else { {
25 ::std::io::_print(
26 <#[lang = "format_arguments"]>::new_v1(
27 &["", "\n"],
28 &[<#[lang = "format_argument"]>::new_display(&i)])
29 );
30 }; }
31 }
32 }
33 },
34 };
35
36 _t
37 }
38}
I ran the compiler in release mode, but the debug mode outputs the same HIR because it doesn't optimize when generating HIR.
The first change we notice is an import of preludes. It includes very frequently used names. You can see the exhaustive list of the preludes here. Then, you can see tons of #[lang = "XXX"]
macros. It looks scary at first, but they are just shortcuts for long names. For example, #[lang = "Some"]
refers to Option::Some<T>
defined in the std library. Possible variants of the macro is defined here[0]
. The table is defined at the end of the code. I have no idea why they use such macro, but let's not get scared. With that in mind, we can understand the above code like below:
1..200
is translated toRange { start: 1, end: 200 }.into_iter()
.Range { start: 1, end: 200 }.into_iter()
is assigned toiter
.- It turns
for
intoloop
. In each loop, it matchesiter.next()
- If
iter.next()
isNone
, it breaks. - If
iter.next()
isSome(i)
, it runs the fizz-buzz code. println!
is converted to::std::io::_print
. You can see the definition of_print
here[1] .
MIR
Example 1: Push an element to a vector
I tried MIR with the fizz-buzz code above, but it was too long. I don't want you guys to suffer scrolling the endless code, so I'll use shorter code.
1fn main() {
2 let mut a = vec![1, 2, 3, 4];
3 a.push(5);
4}
Only 2 lines. I want to see how vector initializaion and push operation are expressed in MIR. Before looking at the MIR, looking at the HIR would be helpful.
1#[prelude_import]
2use std::prelude::rust_2021::*;
3#[macro_use]
4extern crate std;
5
6fn main() {
7 let mut a = <[_]>::into_vec(
8 #[rustc_box] ::alloc::boxed::Box::new([1, 2, 3, 4])
9 );
10
11 a.push(5);
12}
It turns vec!
into into_vec(Box::new())
. See the documents of Box and into_vec[2]
if you wanna know further.
Below is the MIR of the above code, generated in Debug mode.
1// WARNING: This output format is intended for human consumers only
2// and is subject to change without notice. Knock yourself out.
3fn main() -> () {
4 let mut _0: (); // return place in scope 0 at src/main.rs:1:11: 1:11
5 let mut _1: std::vec::Vec<i32>; // in scope 0 at src/main.rs:2:9: 2:14
6 let mut _2: std::boxed::Box<[i32]>; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
7 let mut _3: usize; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
8 let mut _4: usize; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
9 let mut _5: *mut u8; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
10 let mut _6: std::boxed::Box<[i32; 4]>; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
11 let _7: (); // in scope 0 at src/main.rs:3:5: 3:14
12 let mut _8: &mut std::vec::Vec<i32>; // in scope 0 at src/main.rs:3:5: 3:14
13 let mut _9: *const [i32; 4]; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
14 let mut _10: *const (); // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
15 let mut _11: usize; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
16 let mut _12: usize; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
17 let mut _13: usize; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
18 let mut _14: usize; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
19 let mut _15: bool; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
20 scope 1 {
21 debug a => _1; // in scope 1 at src/main.rs:2:9: 2:14
22 }
23 scope 2 {
24 }
25
26 bb0: {
27 _3 = SizeOf([i32; 4]); // scope 2 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
28 _4 = AlignOf([i32; 4]); // scope 2 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
29 _5 = alloc::alloc::exchange_malloc(move _3, move _4) -> bb1; // scope 2 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
30 // mir::Constant
31 // + span: /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
32 // + literal: Const { ty: unsafe fn(usize, usize) -> *mut u8 {alloc::alloc::exchange_malloc}, val: Value(<ZST>) }
33 }
34
35 bb1: {
36 _6 = ShallowInitBox(move _5, [i32; 4]); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
37 _9 = (((_6.0: std::ptr::Unique<[i32; 4]>).0: std::ptr::NonNull<[i32; 4]>).0: *const [i32; 4]); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
38 _10 = _9 as *const () (PtrToPtr); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
39 _11 = _10 as usize (Transmute); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
40 _12 = AlignOf(i32); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
41 _13 = Sub(_12, const 1_usize); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
42 _14 = BitAnd(_11, _13); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
43 _15 = Eq(_14, const 0_usize); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
44 assert(_15, "misaligned pointer dereference: address must be a multiple of {} but is {}", _12, _11) -> [success: bb7, unwind terminate]; // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
45 }
46
47 bb2: {
48 _8 = &mut _1; // scope 1 at src/main.rs:3:5: 3:14
49 _7 = Vec::<i32>::push(move _8, const 5_i32) -> [return: bb3, unwind: bb5]; // scope 1 at src/main.rs:3:5: 3:14
50 // mir::Constant
51 // + span: src/main.rs:3:7: 3:11
52 // + literal: Const { ty: for<'a> fn(&'a mut Vec<i32>, i32) {Vec::<i32>::push}, val: Value(<ZST>) }
53 }
54
55 bb3: {
56 drop(_1) -> bb4; // scope 0 at src/main.rs:4:1: 4:2
57 }
58
59 bb4: {
60 return; // scope 0 at src/main.rs:4:2: 4:2
61 }
62
63 bb5 (cleanup): {
64 drop(_1) -> [return: bb6, unwind terminate]; // scope 0 at src/main.rs:4:1: 4:2
65 }
66
67 bb6 (cleanup): {
68 resume; // scope 0 at src/main.rs:1:1: 4:2
69 }
70
71 bb7: {
72 (*_9) = [const 1_i32, const 2_i32, const 3_i32, const 4_i32]; // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
73 _2 = move _6 as std::boxed::Box<[i32]> (Pointer(Unsize)); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
74 _1 = slice::<impl [i32]>::into_vec::<std::alloc::Global>(move _2) -> bb2; // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:50:36: 55:10
75 // mir::Constant
76 // + span: /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:50:36: 50:51
77 // + user_ty: UserType(0)
78 // + literal: Const { ty: fn(Box<[i32]>) -> Vec<i32> {slice::<impl [i32]>::into_vec::<std::alloc::Global>}, val: Value(<ZST>) }
79 }
80}
A function in MIR consists of locals, scopes and basic blocks.
Locals are values stored on the stack, such as function arguments, local variables and temporary variables. They're underscored numbers. _0
is the return value of the function. In our code, _0
is ()
because the main function doesn't return anything. a
in Rust is assigned to _1
in MIR. _2
through _6
are temporary variables used by the vec!
macro. _7
is the return value of .push
method. Since it returns nothing, _7
's type is ()
.
After locals, there are scopes. They represent lexical scopes. They also contains debug information, which you can see in scope 1
above. It says a
is the name of _1
. Scopes include all the debug information of user-defined variables.
Then you see basic blocks. They represent the control flow of the code. The flow starts at bb0
, which stands for basic block 0. Each basic block contains one or more statements. The last statement is called terminator. The terminator tells which block to execute next. We call the next block successor. A block may have more than one successor.
bb0
allocates memory for a
using exchange_malloc[3]
and stores the pointer at _5
. -> bb1
after the terminator means it should go to bb1
after executing bb0
.
bb1
initializes a Box
pointer using the pointer from the previous block and checks whether the pointer is well aligned. If so, it goes to bb7
. Otherwise, it terminates. The two successors are in squared brackets after the terminator.
bb7
initializes the vector, and bb2
pushes an element. After everything is done, the runtime calls the destructor of the vector by dropping it, then terminates.
Most statements in basic blocks are assignments of an RValue to a Place. A Place is a location in memory. It consists of a local and Projections. A projection can be a deref, index, field, .. etc. Anything that comes at the right-hand side of =
can be an RValue. Note that it only defines simple forms. For complex operations like *_2 + *_3 + *_4
, it uses multiple statements and temporary variables.
Let's look at RValue more closely. One of the most frequently used variant is BinaryOp. You'll see it throughout this article. You already saw them at line 41 and 43 above. The variant consists of a BinOp and two Operands. The BinOp enum defines primitive arithmetic operators including add, sub, mul and div. The Operand enum is literally an operand of all the operations of RValue. There are 3 kinds of Operands: Copy, Move and Constant. Copy and Move represents the copy semantics and the move semantics of Rust. All the Places appear in RValue must be either copy, move or constant. You can see that many operands in the above MIR are prefixed with const
or move
. Operands without any prefix are copied ones. For example, the first operand of line 41 (_12
) is copied and the second one (const 1_usize
) is a constant.
What if we run the same code in release mode?
1// WARNING: This output format is intended for human consumers only
2// and is subject to change without notice. Knock yourself out.
3fn main() -> () {
4 let mut _0: (); // return place in scope 0 at src/main.rs:1:11: 1:11
5 let mut _1: std::vec::Vec<i32>; // in scope 0 at src/main.rs:2:9: 2:14
6 let mut _2: std::boxed::Box<[i32]>; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
7 let mut _3: usize; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
8 let mut _4: usize; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
9 let mut _5: *mut u8; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
10 let mut _6: std::boxed::Box<[i32; 4]>; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
11 let _7: (); // in scope 0 at src/main.rs:3:5: 3:14
12 let mut _8: &mut std::vec::Vec<i32>; // in scope 0 at src/main.rs:3:5: 3:14
13 let mut _9: *const [i32; 4]; // in scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
14 scope 1 {
15 debug a => _1; // in scope 1 at src/main.rs:2:9: 2:14
16 }
17 scope 2 {
18 }
19 scope 3 (inlined slice::<impl [i32]>::into_vec::<std::alloc::Global>) { // at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:50:36: 55:10
20 debug self => _2; // in scope 3 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/slice.rs:461:35: 461:39
21 }
22
23 bb0: {
24 StorageLive(_1); // scope 0 at src/main.rs:2:9: 2:14
25 StorageLive(_2); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
26 _3 = const 16_usize; // scope 2 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
27 _4 = const 4_usize; // scope 2 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
28 _5 = alloc::alloc::exchange_malloc(move _3, move _4) -> bb1; // scope 2 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
29 // mir::Constant
30 // + span: /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
31 // + literal: Const { ty: unsafe fn(usize, usize) -> *mut u8 {alloc::alloc::exchange_malloc}, val: Value(<ZST>) }
32 }
33
34 bb1: {
35 _6 = ShallowInitBox(move _5, [i32; 4]); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
36 _9 = (((_6.0: std::ptr::Unique<[i32; 4]>).0: std::ptr::NonNull<[i32; 4]>).0: *const [i32; 4]); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
37 (*_9) = [const 1_i32, const 2_i32, const 3_i32, const 4_i32]; // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:37: 54:46
38 _2 = move _6 as std::boxed::Box<[i32]> (Pointer(Unsize)); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:54:13: 54:47
39 _1 = slice::hack::into_vec::<i32, std::alloc::Global>(move _2) -> bb6; // scope 3 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/slice.rs:463:9: 463:29
40 // mir::Constant
41 // + span: /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/slice.rs:463:9: 463:23
42 // + literal: Const { ty: fn(Box<[i32]>) -> Vec<i32> {slice::hack::into_vec::<i32, std::alloc::Global>}, val: Value(<ZST>) }
43 }
44
45 bb2: {
46 StorageDead(_8); // scope 1 at src/main.rs:3:13: 3:14
47 drop(_1) -> bb3; // scope 0 at src/main.rs:4:1: 4:2
48 }
49
50 bb3: {
51 StorageDead(_1); // scope 0 at src/main.rs:4:1: 4:2
52 return; // scope 0 at src/main.rs:4:2: 4:2
53 }
54
55 bb4 (cleanup): {
56 drop(_1) -> [return: bb5, unwind terminate]; // scope 0 at src/main.rs:4:1: 4:2
57 }
58
59 bb5 (cleanup): {
60 resume; // scope 0 at src/main.rs:1:1: 4:2
61 }
62
63 bb6: {
64 StorageDead(_2); // scope 0 at /rustc/74c4821045c68d42bb8b8a7c998bdb5c2a72bd0d/library/alloc/src/macros.rs:55:9: 55:10
65 StorageLive(_8); // scope 1 at src/main.rs:3:5: 3:14
66 _8 = &mut _1; // scope 1 at src/main.rs:3:5: 3:14
67 _7 = Vec::<i32>::push(move _8, const 5_i32) -> [return: bb2, unwind: bb4]; // scope 1 at src/main.rs:3:5: 3:14
68 // mir::Constant
69 // + span: src/main.rs:3:7: 3:11
70 // + literal: Const { ty: for<'a> fn(&'a mut Vec<i32>, i32) {Vec::<i32>::push}, val: Value(<ZST>) }
71 }
72}
It's way shorter! bb0
doesn't calculate the size and the alignment of [i32; 4]
anymore. I guess the compiler calculated it and hard-coded it. There are StorageDead
and StorageLive
functions which we didn't see in debug mode. StorageLive(_1)
tells LLVM that _1
may be used later. StorageDead(_1)
means it won't be used anymore. It helps LLVM allocate stack space.
In release mode, it doens't check the alignment of the pointer anymore. It just initializes the vector in bb1
.
Reference: RustC Dev Guide
Example 2: Adder
Let's do the same thing with another simple code.
1fn main() {
2 add(3, 4);
3}
4
5fn foo(a: i32, b: i32) -> i32 {
6 if a > b { a + 1 } else { b + 1}
7}
We don't need HIR this time: it's (almost) identical to the original code. Let's go directly to the MIR.
1// WARNING: This output format is intended for human consumers only
2// and is subject to change without notice. Knock yourself out.
3fn main() -> () {
4 let mut _0: (); // return place in scope 0 at src/main.rs:1:11: 1:11
5 let _1: i32; // in scope 0 at src/main.rs:2:5: 2:14
6
7 bb0: {
8 _1 = foo(const 3_i32, const 4_i32) -> bb1; // scope 0 at src/main.rs:2:5: 2:14
9 // mir::Constant
10 // + span: src/main.rs:2:5: 2:8
11 // + literal: Const { ty: fn(i32, i32) -> i32 {foo}, val: Value(<ZST>) }
12 }
13
14 bb1: {
15 return; // scope 0 at src/main.rs:3:2: 3:2
16 }
17}
18
19fn foo(_1: i32, _2: i32) -> i32 {
20 debug a => _1; // in scope 0 at src/main.rs:5:8: 5:9
21 debug b => _2; // in scope 0 at src/main.rs:5:16: 5:17
22 let mut _0: i32; // return place in scope 0 at src/main.rs:5:27: 5:30
23 let mut _3: bool; // in scope 0 at src/main.rs:6:8: 6:13
24 let mut _4: (i32, bool); // in scope 0 at src/main.rs:6:16: 6:21
25 let mut _5: (i32, bool); // in scope 0 at src/main.rs:6:31: 6:36
26
27 bb0: {
28 _3 = Gt(_1, _2); // scope 0 at src/main.rs:6:8: 6:13
29 switchInt(move _3) -> [0: bb3, otherwise: bb1]; // scope 0 at src/main.rs:6:8: 6:13
30 }
31
32 bb1: {
33 _4 = CheckedAdd(_1, const 1_i32); // scope 0 at src/main.rs:6:16: 6:21
34 assert(!move (_4.1: bool), "attempt to compute `{} + {}`, which would overflow", _1, const 1_i32) -> bb2; // scope 0 at src/main.rs:6:16: 6:21
35 }
36
37 bb2: {
38 _0 = move (_4.0: i32); // scope 0 at src/main.rs:6:16: 6:21
39 goto -> bb5; // scope 0 at src/main.rs:6:5: 6:37
40 }
41
42 bb3: {
43 _5 = CheckedAdd(_2, const 1_i32); // scope 0 at src/main.rs:6:31: 6:36
44 assert(!move (_5.1: bool), "attempt to compute `{} + {}`, which would overflow", _2, const 1_i32) -> bb4; // scope 0 at src/main.rs:6:31: 6:36
45 }
46
47 bb4: {
48 _0 = move (_5.0: i32); // scope 0 at src/main.rs:6:31: 6:36
49 goto -> bb5; // scope 0 at src/main.rs:6:5: 6:37
50 }
51
52 bb5: {
53 return; // scope 0 at src/main.rs:7:2: 7:2
54 }
55}
Above is its MIR in debug mode. Let's look at the foo
function. Two parameters: a
and b
are assigned to _1
and _2
. You can see their debug info at the top of the function. Its return value is assigned to _0
, as usual. _3
is a temporary variable which stores the result of a > b
.
In bb0
, it computes the result of a > b
and stores it at _3
. If the result is 0, it goes to bb3
. Otherwise, it goes to bb1
. In bb1
and bb3
, it calls CheckedAdd
, not just Add
. The CheckedAdd
function returns the result and whether it overflowed. That's why the type of _4
and _5
are (i32, bool)
.
After that, it goes to bb2
or bb4
where it moves the result to _0
and returns.
In release mode, it's way simpler. See below.
1// WARNING: This output format is intended for human consumers only
2// and is subject to change without notice. Knock yourself out.
3fn main() -> () {
4 let mut _0: (); // return place in scope 0 at src/main.rs:1:11: 1:11
5 let _1: i32; // in scope 0 at src/main.rs:2:5: 2:14
6
7 bb0: {
8 StorageLive(_1); // scope 0 at src/main.rs:2:5: 2:14
9 _1 = foo(const 3_i32, const 4_i32) -> bb1; // scope 0 at src/main.rs:2:5: 2:14
10 // mir::Constant
11 // + span: src/main.rs:2:5: 2:8
12 // + literal: Const { ty: fn(i32, i32) -> i32 {foo}, val: Value(<ZST>) }
13 }
14
15 bb1: {
16 StorageDead(_1); // scope 0 at src/main.rs:2:14: 2:15
17 return; // scope 0 at src/main.rs:3:2: 3:2
18 }
19}
20
21fn foo(_1: i32, _2: i32) -> i32 {
22 debug a => _1; // in scope 0 at src/main.rs:5:8: 5:9
23 debug b => _2; // in scope 0 at src/main.rs:5:16: 5:17
24 let mut _0: i32; // return place in scope 0 at src/main.rs:5:27: 5:30
25 let mut _3: bool; // in scope 0 at src/main.rs:6:8: 6:13
26
27 bb0: {
28 StorageLive(_3); // scope 0 at src/main.rs:6:8: 6:13
29 _3 = Gt(_1, _2); // scope 0 at src/main.rs:6:8: 6:13
30 switchInt(move _3) -> [0: bb2, otherwise: bb1]; // scope 0 at src/main.rs:6:8: 6:13
31 }
32
33 bb1: {
34 _0 = Add(_1, const 1_i32); // scope 0 at src/main.rs:6:16: 6:21
35 goto -> bb3; // scope 0 at src/main.rs:6:5: 6:37
36 }
37
38 bb2: {
39 _0 = Add(_2, const 1_i32); // scope 0 at src/main.rs:6:31: 6:36
40 goto -> bb3; // scope 0 at src/main.rs:6:5: 6:37
41 }
42
43 bb3: {
44 StorageDead(_3); // scope 0 at src/main.rs:6:36: 6:37
45 return; // scope 0 at src/main.rs:7:2: 7:2
46 }
47}
I have no idea why StorageLive
and StorageDead
only appear in release mode. It's almost identical to the previous MIR, except that it doesn't check the overflow.
Example 3: Hello World
fn main() {
println!("Hello World!");
}
Let's go straight to the MIR.
1// WARNING: This output format is intended for human consumers only
2// and is subject to change without notice. Knock yourself out.
3fn main() -> () {
4 let mut _0: (); // return place in scope 0 at src/main.rs:1:11: 1:11
5 let _1: (); // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:9: 137:62
6 let mut _2: std::fmt::Arguments<'_>; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
7 let mut _3: &[&str]; // in scope 0 at src/main.rs:2:14: 2:28
8 let mut _4: &[core::fmt::ArgumentV1<'_>]; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
9 let mut _5: &[core::fmt::ArgumentV1<'_>; 0]; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
10 let mut _6: &[&str; 1]; // in scope 0 at src/main.rs:2:14: 2:28
11
12 bb0: {
13 _6 = const _; // scope 0 at src/main.rs:2:14: 2:28
14 // mir::Constant
15 // + span: src/main.rs:2:14: 2:28
16 // + literal: Const { ty: &[&str; 1], val: Unevaluated(main, [], Some(promoted[1])) }
17 _3 = _6 as &[&str] (Pointer(Unsize)); // scope 0 at src/main.rs:2:14: 2:28
18 _5 = const _; // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
19 // mir::Constant
20 // + span: /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
21 // + literal: Const { ty: &[core::fmt::ArgumentV1<'_>; 0], val: Unevaluated(main, [], Some(promoted[0])) }
22 _4 = _5 as &[core::fmt::ArgumentV1<'_>] (Pointer(Unsize)); // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
23 _2 = Arguments::<'_>::new_v1(move _3, move _4) -> bb1; // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
24 // mir::Constant
25 // + span: /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
26 // + user_ty: UserType(0)
27 // + literal: Const { ty: fn(&[&'static str], &[core::fmt::ArgumentV1<'_>]) -> Arguments<'_> {Arguments::<'_>::new_v1}, val: Value(<ZST>) }
28 }
29
30 bb1: {
31 _1 = _print(move _2) -> bb2; // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:9: 137:62
32 // mir::Constant
33 // + span: /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:9: 137:27
34 // + literal: Const { ty: for<'a> fn(Arguments<'a>) {_print}, val: Value(<ZST>) }
35 }
36
37 bb2: {
38 return; // scope 0 at src/main.rs:3:2: 3:2
39 }
40}
41
42promoted[0] in main: &[core::fmt::ArgumentV1<'_>; 0] = {
43 let mut _0: &[core::fmt::ArgumentV1<'_>; 0]; // return place in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
44 let mut _1: [core::fmt::ArgumentV1<'_>; 0]; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
45
46 bb0: {
47 _1 = []; // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
48 _0 = &_1; // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
49 return; // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/macros.rs:137:28: 137:61
50 }
51}
52
53promoted[1] in main: &[&str; 1] = {
54 let mut _0: &[&str; 1]; // return place in scope 0 at src/main.rs:2:14: 2:28
55 let mut _1: [&str; 1]; // in scope 0 at src/main.rs:2:14: 2:28
56
57 bb0: {
58 _1 = [const "Hello World!\n"]; // scope 0 at src/main.rs:2:14: 2:28
59 // mir::Constant
60 // + span: src/main.rs:2:14: 2:28
61 // + literal: Const { ty: &str, val: Value(Slice(..)) }
62 _0 = &_1; // scope 0 at src/main.rs:2:14: 2:28
63 return; // scope 0 at src/main.rs:2:14: 2:28
64 }
65}
Interesting that Hello World generates this long MIR. There are promoted
blocks which we haven't seen before. Constants in MIR are stored in a promoted
vector. The promoted
blocks initialize the constants. For example, line 13 above assigns a constant to _6
, but it doesn't tell us what the constant is. If you read the comments carefully, it says the value can be found in promoted[1]
. In the promoted[1]
block, it initializes const "Hello World!\n"
.
Example 4: Drop Elaboration
1fn main() {
2 let mut y = vec![1, 2, 3, 4];
3
4 {
5 let x = vec![5, 6, 7, 8];
6
7 if std::process::id() % 2 == 0 {
8 y = x;
9 }
10
11 }
12}
The code above is from rustc-dev-guide. MIR implements a special flag called "drop flag". It's used to track dynamic drops. In the above code, the compiler cannot know whether y = x;
will be executed or not. That means the compiler does not know when to drop [1, 2, 3, 4]
. Let's see how the compiler deals with this problem. Below is the MIR of the above code in release mode.
1// WARNING: This output format is intended for human consumers only
2// and is subject to change without notice. Knock yourself out.
3fn main() -> () {
4 let mut _0: (); // return place in scope 0 at src/main.rs:1:11: 1:11
5 let mut _1: std::vec::Vec<i32>; // in scope 0 at src/main.rs:2:9: 2:14
6 let mut _2: std::boxed::Box<[i32]>; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
7 let mut _3: usize; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
8 let mut _4: usize; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
9 let mut _5: *mut u8; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
10 let mut _6: std::boxed::Box<[i32; 4]>; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
11 let mut _8: std::boxed::Box<[i32]>; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
12 let mut _9: usize; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
13 let mut _10: usize; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
14 let mut _11: *mut u8; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
15 let mut _12: std::boxed::Box<[i32; 4]>; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
16 let mut _13: u32; // in scope 0 at src/main.rs:7:12: 7:34
17 let mut _14: u32; // in scope 0 at src/main.rs:7:12: 7:30
18 let mut _15: std::vec::Vec<i32>; // in scope 0 at src/main.rs:8:17: 8:18
19 let mut _16: bool; // in scope 0 at src/main.rs:11:5: 11:6
20 let mut _17: *const [i32; 4]; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
21 let mut _18: *const [i32; 4]; // in scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
22 scope 1 {
23 debug y => _1; // in scope 1 at src/main.rs:2:9: 2:14
24 let _7: std::vec::Vec<i32>; // in scope 1 at src/main.rs:5:13: 5:14
25 scope 3 {
26 debug x => _7; // in scope 3 at src/main.rs:5:13: 5:14
27 }
28 scope 4 {
29 }
30 scope 6 (inlined slice::<impl [i32]>::into_vec::<std::alloc::Global>) { // at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:50:36: 55:10
31 debug self => _8; // in scope 6 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/slice.rs:461:35: 461:39
32 }
33 }
34 scope 2 {
35 }
36 scope 5 (inlined slice::<impl [i32]>::into_vec::<std::alloc::Global>) { // at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:50:36: 55:10
37 debug self => _2; // in scope 5 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/slice.rs:461:35: 461:39
38 }
39
40 bb0: {
41 StorageLive(_1); // scope 0 at src/main.rs:2:9: 2:14
42 StorageLive(_2); // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
43 _3 = const 16_usize; // scope 2 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
44 _4 = const 4_usize; // scope 2 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
45 _5 = alloc::alloc::exchange_malloc(move _3, move _4) -> bb1; // scope 2 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
46 // mir::Constant
47 // + span: /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
48 // + literal: Const { ty: unsafe fn(usize, usize) -> *mut u8 {alloc::alloc::exchange_malloc}, val: Value(<ZST>) }
49 }
50
51 bb1: {
52 _6 = ShallowInitBox(move _5, [i32; 4]); // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
53 _17 = (((_6.0: std::ptr::Unique<[i32; 4]>).0: std::ptr::NonNull<[i32; 4]>).0: *const [i32; 4]); // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:37: 54:46
54 (*_17) = [const 1_i32, const 2_i32, const 3_i32, const 4_i32]; // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:37: 54:46
55 _2 = move _6 as std::boxed::Box<[i32]> (Pointer(Unsize)); // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
56 _1 = slice::hack::into_vec::<i32, std::alloc::Global>(move _2) -> bb16; // scope 5 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/slice.rs:463:9: 463:29
57 // mir::Constant
58 // + span: /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/slice.rs:463:9: 463:23
59 // + literal: Const { ty: fn(Box<[i32]>) -> Vec<i32> {slice::hack::into_vec::<i32, std::alloc::Global>}, val: Value(<ZST>) }
60 }
61
62 bb2: {
63 _12 = ShallowInitBox(move _11, [i32; 4]); // scope 1 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
64 _18 = (((_12.0: std::ptr::Unique<[i32; 4]>).0: std::ptr::NonNull<[i32; 4]>).0: *const [i32; 4]); // scope 1 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:37: 54:46
65 (*_18) = [const 5_i32, const 6_i32, const 7_i32, const 8_i32]; // scope 1 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:37: 54:46
66 _8 = move _12 as std::boxed::Box<[i32]> (Pointer(Unsize)); // scope 1 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
67 _7 = slice::hack::into_vec::<i32, std::alloc::Global>(move _8) -> [return: bb17, unwind: bb9]; // scope 6 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/slice.rs:463:9: 463:29
68 // mir::Constant
69 // + span: /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/slice.rs:463:9: 463:23
70 // + literal: Const { ty: fn(Box<[i32]>) -> Vec<i32> {slice::hack::into_vec::<i32, std::alloc::Global>}, val: Value(<ZST>) }
71 }
72
73 bb3: {
74 _13 = Rem(move _14, const 2_u32); // scope 3 at src/main.rs:7:12: 7:34
75 StorageDead(_14); // scope 3 at src/main.rs:7:33: 7:34
76 switchInt(move _13) -> [0: bb4, otherwise: bb5]; // scope 3 at src/main.rs:7:12: 7:39
77 }
78
79 bb4: {
80 StorageDead(_13); // scope 3 at src/main.rs:7:12: 7:39
81 StorageLive(_15); // scope 3 at src/main.rs:8:17: 8:18
82 _16 = const false; // scope 3 at src/main.rs:8:17: 8:18
83 _15 = move _7; // scope 3 at src/main.rs:8:17: 8:18
84 drop(_1) -> [return: bb12, unwind: bb11]; // scope 3 at src/main.rs:8:13: 8:14
85 }
86
87 bb5: {
88 StorageDead(_13); // scope 3 at src/main.rs:7:12: 7:39
89 goto -> bb6; // scope 3 at src/main.rs:7:9: 9:10
90 }
91
92 bb6: {
93 switchInt(_16) -> [0: bb7, otherwise: bb13]; // scope 1 at src/main.rs:11:5: 11:6
94 }
95
96 bb7: {
97 StorageDead(_7); // scope 1 at src/main.rs:11:5: 11:6
98 drop(_1) -> bb8; // scope 0 at src/main.rs:12:1: 12:2
99 }
100
101 bb8: {
102 StorageDead(_1); // scope 0 at src/main.rs:12:1: 12:2
103 return; // scope 0 at src/main.rs:12:2: 12:2
104 }
105
106 bb9 (cleanup): {
107 drop(_1) -> bb10; // scope 0 at src/main.rs:12:1: 12:2
108 }
109
110 bb10 (cleanup): {
111 resume; // scope 0 at src/main.rs:1:1: 12:2
112 }
113
114 bb11 (cleanup): {
115 _1 = move _15; // scope 3 at src/main.rs:8:13: 8:14
116 goto -> bb15; // scope 3 at src/main.rs:8:13: 8:14
117 }
118
119 bb12: {
120 _1 = move _15; // scope 3 at src/main.rs:8:13: 8:14
121 StorageDead(_15); // scope 3 at src/main.rs:8:17: 8:18
122 goto -> bb6; // scope 3 at src/main.rs:7:9: 9:10
123 }
124
125 bb13: {
126 drop(_7) -> [return: bb7, unwind: bb9]; // scope 1 at src/main.rs:11:5: 11:6
127 }
128
129 bb14 (cleanup): {
130 drop(_7) -> bb9; // scope 1 at src/main.rs:11:5: 11:6
131 }
132
133 bb15 (cleanup): {
134 switchInt(_16) -> [0: bb9, otherwise: bb14]; // scope 1 at src/main.rs:11:5: 11:6
135 }
136
137 bb16: {
138 StorageDead(_2); // scope 0 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:55:9: 55:10
139 StorageLive(_7); // scope 1 at src/main.rs:5:13: 5:14
140 StorageLive(_8); // scope 1 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
141 _9 = const 16_usize; // scope 4 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
142 _10 = const 4_usize; // scope 4 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
143 _11 = alloc::alloc::exchange_malloc(move _9, move _10) -> [return: bb2, unwind: bb9]; // scope 4 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
144 // mir::Constant
145 // + span: /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:54:13: 54:47
146 // + literal: Const { ty: unsafe fn(usize, usize) -> *mut u8 {alloc::alloc::exchange_malloc}, val: Value(<ZST>) }
147 }
148
149 bb17: {
150 _16 = const true; // scope 1 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:55:9: 55:10
151 StorageDead(_8); // scope 1 at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/alloc/src/macros.rs:55:9: 55:10
152 StorageLive(_13); // scope 3 at src/main.rs:7:12: 7:34
153 StorageLive(_14); // scope 3 at src/main.rs:7:12: 7:30
154 _14 = id() -> [return: bb3, unwind: bb15]; // scope 3 at src/main.rs:7:12: 7:30
155 // mir::Constant
156 // + span: src/main.rs:7:12: 7:28
157 // + literal: Const { ty: fn() -> u32 {id}, val: Value(<ZST>) }
158 }
159}
_16
is the key here. The comment says _16
is at main.rs:11:5: 11:6
, but we don't see anything there. It's the special flag that MIR created to track the lifetime of y
dynamically. You can see how it works in the Control Flow Graph below.
╭─────────────────────────────╮
│ bb0 -> bb1 -> bb16 -> bb2 │
│ init x and y │
╰─────────────────────────────╯
│
V
╭────────────────────╮ ╭────────────────╮
│ bb17 -> bb3 │ true │ bb4 │
│ _16 = true; │ ─────────> │ _16 = false; │
│ if id() % 2 == 0 │ │ drop(y); │
╰────────────────────╯ ╰────────────────╯
│ │
│ false │
V V
╭───────╮ ╭──────────╮
│ bb5 │ │ bb12 │
╰───────╯ │ y = x; │
│ ╰──────────╯
│ │
│ │
│ ╭──────────────────╮ │
╰─> │ bb6 │ <────╯
│ if _16 == true │
╰──────────────────╯
│ │ false
true │ ╰─────╮
V V
╭────────────╮ ╭────────────╮
│ bb7 │ <─────── │ bb13 │
│ drop(y); │ │ drop(x); │
╰────────────╯ ╰────────────╯
│
│
V
╭──────────╮
│ bb8 │
│ return │
╰──────────╯
It sets _16
to false
when y
is dropped. It checks _16
before exiting the function.
Terminators
So far, we have seen many kinds of terminators. A statement is a terminator if and only if it's the last statement of a basic block. You can see the exhaustive list of terminator kinds here.
If a block has only one successor, it has a Goto terminator (line 35 of this example). A condtion of an if
branch usually uses a switchInt terminator (line 30 of this example). Return literally returns from the current function (line 45 of this example). A function call in MIR always terminates a basic block. It uses this terminator (line 9 of this example and line 67 of this example). If a basic block terminates with a function call, it may have 1 or 2 successors. If the function panics, it has to unwind the stack and call destructors. For panic-able functions, the terminator has a successor for stack unwinding.
Appendix: implementation of MIR
GlobalCtxt is the central data structure that contains all the information of a compilation. It's such a gigantic type and looking at the details of the type is way beyond this article's topic. I'll focus on types that are related to this article.
Body is the type that contains all the information of a function, including the basic_blocks. A basic block has BasicBlockData, which literally contains the data of it. A BasicBlockData consists of Statements and a Terminator.
StatementKind and TerminatorKind lists all the possible kinds of statements and terminators. StatementKind includes assignment, StorageLive and StorageDead. We've seen them in this article. I explained TerminatorKind here.
Locals of a Body are stored in local_decls. Information of a local is defined in LocalDecl.
Debug information (like the one at line 22 of this example) of user defined variables are stored here. VarDebugInfo is the structing containing the debug info.