Skip to content

Commit 2868a32

Browse files
beepster4096WaffleLapkin
authored andcommitted
Properly handle drops for tail calls
1 parent 65f6df5 commit 2868a32

16 files changed

+2055
-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, &args, source_info));
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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,91 @@ 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(
721+
&mut self,
722+
mut block: BasicBlock,
723+
args: &[Spanned<Operand<'tcx>>],
724+
source_info: SourceInfo,
725+
) -> BlockAnd<()> {
726+
let arg_drops: Vec<_> = args
727+
.iter()
728+
.rev()
729+
.filter_map(|arg| match &arg.node {
730+
Operand::Copy(_) => bug!("copy op in tail call args"),
731+
Operand::Move(place) => {
732+
let local =
733+
place.as_local().unwrap_or_else(|| bug!("projection in tail call args"));
734+
735+
Some(DropData { source_info, local, kind: DropKind::Value })
736+
}
737+
Operand::Constant(_) => None,
738+
})
739+
.collect();
740+
741+
let mut unwind_to = self.diverge_cleanup_target(
742+
self.scopes.scopes.iter().rev().nth(1).unwrap().region_scope,
743+
DUMMY_SP,
744+
);
745+
let unwind_drops = &mut self.scopes.unwind_drops;
746+
747+
// the innermost scope contains only the destructors for the tail call arguments
748+
// we only want to drop these in case of a panic, so we skip it
749+
for scope in self.scopes.scopes[1..].iter().rev().skip(1) {
750+
// FIXME(explicit_tail_calls) code duplication with `build_scope_drops`
751+
for drop_data in scope.drops.iter().rev() {
752+
let source_info = drop_data.source_info;
753+
let local = drop_data.local;
754+
755+
match drop_data.kind {
756+
DropKind::Value => {
757+
// `unwind_to` should drop the value that we're about to
758+
// schedule. If dropping this value panics, then we continue
759+
// with the *next* value on the unwind path.
760+
debug_assert_eq!(unwind_drops.drops[unwind_to].0.local, drop_data.local);
761+
debug_assert_eq!(unwind_drops.drops[unwind_to].0.kind, drop_data.kind);
762+
unwind_to = unwind_drops.drops[unwind_to].1;
763+
764+
let mut unwind_entry_point = unwind_to;
765+
766+
// the tail call arguments must be dropped if any of these drops panic
767+
for drop in arg_drops.iter().copied() {
768+
unwind_entry_point = unwind_drops.add_drop(drop, unwind_entry_point);
769+
}
770+
771+
unwind_drops.add_entry(block, unwind_entry_point);
772+
773+
let next = self.cfg.start_new_block();
774+
self.cfg.terminate(
775+
block,
776+
source_info,
777+
TerminatorKind::Drop {
778+
place: local.into(),
779+
target: next,
780+
unwind: UnwindAction::Continue,
781+
replace: false,
782+
},
783+
);
784+
block = next;
785+
}
786+
DropKind::Storage => {
787+
// Only temps and vars need their storage dead.
788+
assert!(local.index() > self.arg_count);
789+
self.cfg.push(
790+
block,
791+
Statement { source_info, kind: StatementKind::StorageDead(local) },
792+
);
793+
}
794+
}
795+
}
796+
}
797+
798+
block.unit()
799+
}
800+
716801
fn leave_top_scope(&mut self, block: BasicBlock) -> BasicBlock {
717802
// If we are emitting a `drop` statement, we need to have the cached
718803
// diverge cleanup pads ready in case that drop panics.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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: bb12];
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: bb10];
58+
}
59+
60+
bb5: {
61+
StorageDead(_5);
62+
- drop(_4) -> [return: bb6, unwind: bb11];
63+
+ goto -> bb6;
64+
}
65+
66+
bb6: {
67+
+ _8 = const false;
68+
StorageDead(_4);
69+
StorageDead(_3);
70+
drop(_2) -> [return: bb7, unwind: bb12];
71+
}
72+
73+
bb7: {
74+
StorageDead(_2);
75+
tailcall g();
76+
}
77+
78+
bb8 (cleanup): {
79+
- drop(_7) -> [return: bb9, unwind terminate(cleanup)];
80+
+ goto -> bb9;
81+
}
82+
83+
bb9 (cleanup): {
84+
drop(_5) -> [return: bb10, unwind terminate(cleanup)];
85+
}
86+
87+
bb10 (cleanup): {
88+
- drop(_4) -> [return: bb11, unwind terminate(cleanup)];
89+
+ goto -> bb14;
90+
}
91+
92+
bb11 (cleanup): {
93+
drop(_2) -> [return: bb12, unwind terminate(cleanup)];
94+
}
95+
96+
bb12 (cleanup): {
97+
resume;
98+
+ }
99+
+
100+
+ bb13 (cleanup): {
101+
+ drop(_4) -> [return: bb11, unwind terminate(cleanup)];
102+
+ }
103+
+
104+
+ bb14 (cleanup): {
105+
+ switchInt(_8) -> [0: bb11, otherwise: bb13];
106+
}
107+
}
108+
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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: bb12];
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: bb10];
58+
}
59+
60+
bb5: {
61+
StorageDead(_5);
62+
- drop(_4) -> [return: bb6, unwind: bb11];
63+
+ goto -> bb6;
64+
}
65+
66+
bb6: {
67+
+ _8 = const false;
68+
StorageDead(_4);
69+
StorageDead(_3);
70+
drop(_2) -> [return: bb7, unwind: bb12];
71+
}
72+
73+
bb7: {
74+
StorageDead(_2);
75+
tailcall g();
76+
}
77+
78+
bb8 (cleanup): {
79+
- drop(_7) -> [return: bb9, unwind terminate(cleanup)];
80+
+ goto -> bb9;
81+
}
82+
83+
bb9 (cleanup): {
84+
drop(_5) -> [return: bb10, unwind terminate(cleanup)];
85+
}
86+
87+
bb10 (cleanup): {
88+
- drop(_4) -> [return: bb11, unwind terminate(cleanup)];
89+
+ goto -> bb14;
90+
}
91+
92+
bb11 (cleanup): {
93+
drop(_2) -> [return: bb12, unwind terminate(cleanup)];
94+
}
95+
96+
bb12 (cleanup): {
97+
resume;
98+
+ }
99+
+
100+
+ bb13 (cleanup): {
101+
+ drop(_4) -> [return: bb11, unwind terminate(cleanup)];
102+
+ }
103+
+
104+
+ bb14 (cleanup): {
105+
+ switchInt(_8) -> [0: bb11, otherwise: bb13];
106+
}
107+
}
108+

0 commit comments

Comments
 (0)