Skip to content

Commit e3cd2de

Browse files
beepster4096WaffleLapkin
authored andcommitted
Properly handle drops for tail calls
1 parent b837bb2 commit e3cd2de

File tree

6 files changed

+384
-15
lines changed

6 files changed

+384
-15
lines changed

compiler/rustc_mir_build/src/build/expr/stmt.rs

+21-15
Original file line numberDiff line numberDiff line change
@@ -101,31 +101,37 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
101101
),
102102
ExprKind::Become { value } => {
103103
let v = &this.thir[value];
104-
let ExprKind::Scope { value, .. } = v.kind
104+
let ExprKind::Scope { region_scope, lint_level, value, .. } = v.kind
105105
else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") };
106106

107107
let v = &this.thir[value];
108108
let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind
109109
else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") };
110110

111-
let fun = unpack!(block = this.as_local_operand(block, &this.thir[fun]));
112-
let args: Vec<_> = args
113-
.into_iter()
114-
.copied()
115-
.map(|arg| unpack!(block = this.as_local_call_operand(block, &this.thir[arg])))
116-
.collect();
111+
this.in_scope((region_scope, source_info), lint_level, |this| {
112+
let fun = unpack!(block = this.as_local_operand(block, &this.thir[fun]));
113+
let args: Vec<_> = args
114+
.into_iter()
115+
.copied()
116+
.map(|arg| {
117+
unpack!(block = this.as_local_call_operand(block, &this.thir[arg]))
118+
})
119+
.collect();
117120

118-
this.record_operands_moved(&args);
121+
this.record_operands_moved(&args);
119122

120-
debug!("expr_into_dest: fn_span={:?}", fn_span);
123+
debug!("expr_into_dest: fn_span={:?}", fn_span);
121124

122-
this.cfg.terminate(
123-
block,
124-
source_info,
125-
TerminatorKind::TailCall { func: fun, args, fn_span },
126-
);
125+
unpack!(block = this.break_for_tail_call(block));
127126

128-
this.cfg.start_new_block().unit()
127+
this.cfg.terminate(
128+
block,
129+
source_info,
130+
TerminatorKind::TailCall { func: fun, args, fn_span },
131+
);
132+
133+
this.cfg.start_new_block().unit()
134+
})
129135
}
130136
_ => {
131137
assert!(

compiler/rustc_mir_build/src/build/scope.rs

+36
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,42 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
721721
self.cfg.terminate(block, source_info, TerminatorKind::Resume);
722722
}
723723

724+
/// Sets up the drops for explict tail calls.
725+
///
726+
/// Unlike other kinds of early exits, tail calls do not go through the drop tree.
727+
/// Instead, all scheduled drops are immediately added to the CFG.
728+
pub(crate) fn break_for_tail_call(&mut self, mut block: BasicBlock) -> BlockAnd<()> {
729+
// the innermost scope contains only the destructors for the tail call arguments
730+
// we only want to drop these in case of a panic, so we skip it
731+
for scope in self.scopes.scopes[1..].iter().rev().skip(1) {
732+
for drop in scope.drops.iter().rev() {
733+
match drop.kind {
734+
DropKind::Value => {
735+
let target = self.cfg.start_new_block();
736+
let terminator = TerminatorKind::Drop {
737+
target,
738+
// The caller will handle this if needed.
739+
unwind: UnwindAction::Terminate,
740+
place: drop.local.into(),
741+
replace: false,
742+
};
743+
self.cfg.terminate(block, drop.source_info, terminator);
744+
block = target;
745+
}
746+
DropKind::Storage => {
747+
let stmt = Statement {
748+
source_info: drop.source_info,
749+
kind: StatementKind::StorageDead(drop.local),
750+
};
751+
self.cfg.push(block, stmt);
752+
}
753+
}
754+
}
755+
}
756+
757+
block.unit()
758+
}
759+
724760
// Add a dummy `Assign` statement to the CFG, with the span for the source code's `continue`
725761
// statement.
726762
fn add_dummy_assignment(&mut self, span: Span, block: BasicBlock, source_info: SourceInfo) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
- // MIR for `f` before ElaborateDrops
2+
+ // MIR for `f` after ElaborateDrops
3+
4+
fn f() -> () {
5+
let mut _0: ();
6+
let mut _1: !;
7+
let _2: std::string::String;
8+
let _6: ();
9+
let mut _7: std::string::String;
10+
+ let mut _8: bool;
11+
scope 1 {
12+
debug _a => _2;
13+
let _3: i32;
14+
scope 2 {
15+
debug _b => _3;
16+
let _4: std::string::String;
17+
scope 3 {
18+
debug _c => _4;
19+
let _5: std::string::String;
20+
scope 4 {
21+
debug _d => _5;
22+
}
23+
}
24+
}
25+
}
26+
27+
bb0: {
28+
+ _8 = const false;
29+
StorageLive(_2);
30+
_2 = String::new() -> [return: bb1, unwind continue];
31+
}
32+
33+
bb1: {
34+
StorageLive(_3);
35+
_3 = const 12_i32;
36+
StorageLive(_4);
37+
_4 = String::new() -> [return: bb2, unwind: bb11];
38+
}
39+
40+
bb2: {
41+
+ _8 = const true;
42+
StorageLive(_5);
43+
_5 = String::new() -> [return: bb3, unwind: bb10];
44+
}
45+
46+
bb3: {
47+
StorageLive(_6);
48+
StorageLive(_7);
49+
+ _8 = const false;
50+
_7 = move _4;
51+
_6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb8];
52+
}
53+
54+
bb4: {
55+
StorageDead(_7);
56+
StorageDead(_6);
57+
- drop(_5) -> [return: bb5, unwind terminate];
58+
+ drop(_5) -> [return: bb5, unwind: bb13];
59+
}
60+
61+
bb5: {
62+
StorageDead(_5);
63+
- drop(_4) -> [return: bb6, unwind terminate];
64+
+ goto -> bb6;
65+
}
66+
67+
bb6: {
68+
+ _8 = const false;
69+
StorageDead(_4);
70+
StorageDead(_3);
71+
- drop(_2) -> [return: bb7, unwind terminate];
72+
+ drop(_2) -> [return: bb7, unwind: bb13];
73+
}
74+
75+
bb7: {
76+
StorageDead(_2);
77+
tailcall g();
78+
}
79+
80+
bb8 (cleanup): {
81+
- drop(_7) -> [return: bb9, unwind terminate];
82+
+ goto -> bb9;
83+
}
84+
85+
bb9 (cleanup): {
86+
drop(_5) -> [return: bb10, unwind terminate];
87+
}
88+
89+
bb10 (cleanup): {
90+
- drop(_4) -> [return: bb11, unwind terminate];
91+
+ goto -> bb15;
92+
}
93+
94+
bb11 (cleanup): {
95+
drop(_2) -> [return: bb12, unwind terminate];
96+
}
97+
98+
bb12 (cleanup): {
99+
resume;
100+
+ }
101+
+
102+
+ bb13 (cleanup): {
103+
+ abort;
104+
+ }
105+
+
106+
+ bb14 (cleanup): {
107+
+ drop(_4) -> [return: bb11, unwind terminate];
108+
+ }
109+
+
110+
+ bb15 (cleanup): {
111+
+ switchInt(_8) -> [0: bb11, otherwise: bb14];
112+
}
113+
}
114+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// MIR for `f` after built
2+
3+
fn f() -> () {
4+
let mut _0: ();
5+
let mut _1: !;
6+
let _2: std::string::String;
7+
let _6: ();
8+
let mut _7: std::string::String;
9+
scope 1 {
10+
debug _a => _2;
11+
let _3: i32;
12+
scope 2 {
13+
debug _b => _3;
14+
let _4: std::string::String;
15+
scope 3 {
16+
debug _c => _4;
17+
let _5: std::string::String;
18+
scope 4 {
19+
debug _d => _5;
20+
}
21+
}
22+
}
23+
}
24+
25+
bb0: {
26+
StorageLive(_2);
27+
_2 = String::new() -> [return: bb1, unwind: bb17];
28+
}
29+
30+
bb1: {
31+
FakeRead(ForLet(None), _2);
32+
StorageLive(_3);
33+
_3 = const 12_i32;
34+
FakeRead(ForLet(None), _3);
35+
StorageLive(_4);
36+
_4 = String::new() -> [return: bb2, unwind: bb16];
37+
}
38+
39+
bb2: {
40+
FakeRead(ForLet(None), _4);
41+
StorageLive(_5);
42+
_5 = String::new() -> [return: bb3, unwind: bb15];
43+
}
44+
45+
bb3: {
46+
FakeRead(ForLet(None), _5);
47+
StorageLive(_6);
48+
StorageLive(_7);
49+
_7 = move _4;
50+
_6 = std::mem::drop::<String>(move _7) -> [return: bb4, unwind: bb13];
51+
}
52+
53+
bb4: {
54+
StorageDead(_7);
55+
StorageDead(_6);
56+
drop(_5) -> [return: bb5, unwind terminate];
57+
}
58+
59+
bb5: {
60+
StorageDead(_5);
61+
drop(_4) -> [return: bb6, unwind terminate];
62+
}
63+
64+
bb6: {
65+
StorageDead(_4);
66+
StorageDead(_3);
67+
drop(_2) -> [return: bb7, unwind terminate];
68+
}
69+
70+
bb7: {
71+
StorageDead(_2);
72+
tailcall g();
73+
}
74+
75+
bb8: {
76+
drop(_5) -> [return: bb9, unwind: bb15];
77+
}
78+
79+
bb9: {
80+
StorageDead(_5);
81+
drop(_4) -> [return: bb10, unwind: bb16];
82+
}
83+
84+
bb10: {
85+
StorageDead(_4);
86+
StorageDead(_3);
87+
drop(_2) -> [return: bb11, unwind: bb17];
88+
}
89+
90+
bb11: {
91+
StorageDead(_2);
92+
unreachable;
93+
}
94+
95+
bb12: {
96+
return;
97+
}
98+
99+
bb13 (cleanup): {
100+
drop(_7) -> [return: bb14, unwind terminate];
101+
}
102+
103+
bb14 (cleanup): {
104+
drop(_5) -> [return: bb15, unwind terminate];
105+
}
106+
107+
bb15 (cleanup): {
108+
drop(_4) -> [return: bb16, unwind terminate];
109+
}
110+
111+
bb16 (cleanup): {
112+
drop(_2) -> [return: bb17, unwind terminate];
113+
}
114+
115+
bb17 (cleanup): {
116+
resume;
117+
}
118+
}

tests/mir-opt/tail_call_drops.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![allow(incomplete_features)]
2+
#![feature(explicit_tail_calls)]
3+
4+
// EMIT_MIR tail_call_drops.f.built.after.mir
5+
// Expected result:
6+
// drop(_d) -> drop(_c) -> drop(_a) -> tailcall g()
7+
//
8+
// EMIT_MIR tail_call_drops.f.ElaborateDrops.diff
9+
// Expected result:
10+
// drop(_d) -> drop(_a) -> tailcall g()
11+
fn f() {
12+
13+
let _a = String::new();
14+
let _b = 12;
15+
let _c = String::new();
16+
let _d = String::new();
17+
18+
drop(_c);
19+
20+
become g();
21+
}
22+
23+
fn g() {}
24+
25+
fn main() {}

0 commit comments

Comments
 (0)