Skip to content

Commit 9ee491b

Browse files
beepster4096WaffleLapkin
authored andcommitted
Properly handle drops for tail calls
1 parent c8d17e6 commit 9ee491b

File tree

6 files changed

+386
-18
lines changed

6 files changed

+386
-18
lines changed

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

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
9393
}
9494
ExprKind::Become { value } => {
9595
let v = &this.thir[value];
96-
let ExprKind::Scope { value, .. } = v.kind else {
96+
let ExprKind::Scope { value, lint_level, region_scope } = v.kind else {
9797
span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
9898
};
9999

@@ -102,27 +102,31 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
102102
span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
103103
};
104104

105-
let fun = unpack!(block = this.as_local_operand(block, fun));
106-
let args: Vec<_> = args
107-
.into_iter()
108-
.copied()
109-
.map(|arg| Spanned {
110-
node: unpack!(block = this.as_local_call_operand(block, arg)),
111-
span: this.thir.exprs[arg].span,
112-
})
113-
.collect();
105+
this.in_scope((region_scope, source_info), lint_level, |this| {
106+
let fun = unpack!(block = this.as_local_operand(block, fun));
107+
let args: Vec<_> = args
108+
.into_iter()
109+
.copied()
110+
.map(|arg| Spanned {
111+
node: unpack!(block = this.as_local_call_operand(block, arg)),
112+
span: this.thir.exprs[arg].span,
113+
})
114+
.collect();
114115

115-
this.record_operands_moved(&args);
116+
this.record_operands_moved(&args);
116117

117-
debug!("expr_into_dest: fn_span={:?}", fn_span);
118+
debug!("expr_into_dest: fn_span={:?}", fn_span);
118119

119-
this.cfg.terminate(
120-
block,
121-
source_info,
122-
TerminatorKind::TailCall { func: fun, args, fn_span },
123-
);
120+
unpack!(block = this.break_for_tail_call(block));
124121

125-
this.cfg.start_new_block().unit()
122+
this.cfg.terminate(
123+
block,
124+
source_info,
125+
TerminatorKind::TailCall { func: fun, args, fn_span },
126+
);
127+
128+
this.cfg.start_new_block().unit()
129+
})
126130
}
127131
_ => {
128132
assert!(

compiler/rustc_mir_build/src/build/scope.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,43 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
713713
self.cfg.terminate(block, source_info, TerminatorKind::UnwindResume);
714714
}
715715

716+
/// Sets up the drops for explict tail calls.
717+
///
718+
/// Unlike other kinds of early exits, tail calls do not go through the drop tree.
719+
/// Instead, all scheduled drops are immediately added to the CFG.
720+
pub(crate) fn break_for_tail_call(&mut self, mut block: BasicBlock) -> BlockAnd<()> {
721+
// the innermost scope contains only the destructors for the tail call arguments
722+
// we only want to drop these in case of a panic, so we skip it
723+
for scope in self.scopes.scopes[1..].iter().rev().skip(1) {
724+
for drop in scope.drops.iter().rev() {
725+
match drop.kind {
726+
DropKind::Value => {
727+
let target = self.cfg.start_new_block();
728+
let terminator = TerminatorKind::Drop {
729+
target,
730+
// The caller will handle this if needed.
731+
// FIXME(explicit_tail_calls): add new reason
732+
unwind: UnwindAction::Terminate(UnwindTerminateReason::Abi),
733+
place: drop.local.into(),
734+
replace: false,
735+
};
736+
self.cfg.terminate(block, drop.source_info, terminator);
737+
block = target;
738+
}
739+
DropKind::Storage => {
740+
let stmt = Statement {
741+
source_info: drop.source_info,
742+
kind: StatementKind::StorageDead(drop.local),
743+
};
744+
self.cfg.push(block, stmt);
745+
}
746+
}
747+
}
748+
}
749+
750+
block.unit()
751+
}
752+
716753
fn leave_top_scope(&mut self, block: BasicBlock) -> BasicBlock {
717754
// If we are emitting a `drop` statement, we need to have the cached
718755
// diverge cleanup pads ready in case that drop panics.
Lines changed: 114 additions & 0 deletions
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+
Lines changed: 118 additions & 0 deletions
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

Lines changed: 25 additions & 0 deletions
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)