Skip to content

Commit 4187cdc

Browse files
beepster4096WaffleLapkin
authored andcommitted
Properly handle drops for tail calls
1 parent 484152d commit 4187cdc

12 files changed

+1443
-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
@@ -95,7 +95,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
9595
}
9696
ExprKind::Become { value } => {
9797
let v = &this.thir[value];
98-
let ExprKind::Scope { value, .. } = v.kind else {
98+
let ExprKind::Scope { value, lint_level, region_scope } = v.kind else {
9999
span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}")
100100
};
101101

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

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

117-
this.record_operands_moved(&args);
118+
this.record_operands_moved(&args);
118119

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

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

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

compiler/rustc_mir_build/src/build/scope.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,91 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
745745
self.cfg.terminate(block, source_info, TerminatorKind::UnwindResume);
746746
}
747747

748+
/// Sets up the drops for explict tail calls.
749+
///
750+
/// Unlike other kinds of early exits, tail calls do not go through the drop tree.
751+
/// Instead, all scheduled drops are immediately added to the CFG.
752+
pub(crate) fn break_for_tail_call(
753+
&mut self,
754+
mut block: BasicBlock,
755+
args: &[Spanned<Operand<'tcx>>],
756+
source_info: SourceInfo,
757+
) -> BlockAnd<()> {
758+
let arg_drops: Vec<_> = args
759+
.iter()
760+
.rev()
761+
.filter_map(|arg| match &arg.node {
762+
Operand::Copy(_) => bug!("copy op in tail call args"),
763+
Operand::Move(place) => {
764+
let local =
765+
place.as_local().unwrap_or_else(|| bug!("projection in tail call args"));
766+
767+
Some(DropData { source_info, local, kind: DropKind::Value })
768+
}
769+
Operand::Constant(_) => None,
770+
})
771+
.collect();
772+
773+
let mut unwind_to = self.diverge_cleanup_target(
774+
self.scopes.scopes.iter().rev().nth(1).unwrap().region_scope,
775+
DUMMY_SP,
776+
);
777+
let unwind_drops = &mut self.scopes.unwind_drops;
778+
779+
// the innermost scope contains only the destructors for the tail call arguments
780+
// we only want to drop these in case of a panic, so we skip it
781+
for scope in self.scopes.scopes[1..].iter().rev().skip(1) {
782+
// FIXME(explicit_tail_calls) code duplication with `build_scope_drops`
783+
for drop_data in scope.drops.iter().rev() {
784+
let source_info = drop_data.source_info;
785+
let local = drop_data.local;
786+
787+
match drop_data.kind {
788+
DropKind::Value => {
789+
// `unwind_to` should drop the value that we're about to
790+
// schedule. If dropping this value panics, then we continue
791+
// with the *next* value on the unwind path.
792+
debug_assert_eq!(unwind_drops.drops[unwind_to].data.local, drop_data.local);
793+
debug_assert_eq!(unwind_drops.drops[unwind_to].data.kind, drop_data.kind);
794+
unwind_to = unwind_drops.drops[unwind_to].next;
795+
796+
let mut unwind_entry_point = unwind_to;
797+
798+
// the tail call arguments must be dropped if any of these drops panic
799+
for drop in arg_drops.iter().copied() {
800+
unwind_entry_point = unwind_drops.add_drop(drop, unwind_entry_point);
801+
}
802+
803+
unwind_drops.add_entry_point(block, unwind_entry_point);
804+
805+
let next = self.cfg.start_new_block();
806+
self.cfg.terminate(
807+
block,
808+
source_info,
809+
TerminatorKind::Drop {
810+
place: local.into(),
811+
target: next,
812+
unwind: UnwindAction::Continue,
813+
replace: false,
814+
},
815+
);
816+
block = next;
817+
}
818+
DropKind::Storage => {
819+
// Only temps and vars need their storage dead.
820+
assert!(local.index() > self.arg_count);
821+
self.cfg.push(
822+
block,
823+
Statement { source_info, kind: StatementKind::StorageDead(local) },
824+
);
825+
}
826+
}
827+
}
828+
}
829+
830+
block.unit()
831+
}
832+
748833
fn leave_top_scope(&mut self, block: BasicBlock) -> BasicBlock {
749834
// If we are emitting a `drop` statement, we need to have the cached
750835
// 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: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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: 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 continue];
71+
+ drop(_2) -> [return: bb7, unwind: bb12];
72+
}
73+
74+
bb7: {
75+
StorageDead(_2);
76+
tailcall g();
77+
}
78+
79+
bb8 (cleanup): {
80+
- drop(_7) -> [return: bb9, unwind terminate(cleanup)];
81+
+ goto -> bb9;
82+
}
83+
84+
bb9 (cleanup): {
85+
drop(_5) -> [return: bb10, unwind terminate(cleanup)];
86+
}
87+
88+
bb10 (cleanup): {
89+
- drop(_4) -> [return: bb11, unwind terminate(cleanup)];
90+
+ goto -> bb14;
91+
}
92+
93+
bb11 (cleanup): {
94+
drop(_2) -> [return: bb12, unwind terminate(cleanup)];
95+
}
96+
97+
bb12 (cleanup): {
98+
resume;
99+
+ }
100+
+
101+
+ bb13 (cleanup): {
102+
+ drop(_4) -> [return: bb11, unwind terminate(cleanup)];
103+
+ }
104+
+
105+
+ bb14 (cleanup): {
106+
+ switchInt(_8) -> [0: bb11, otherwise: bb13];
107+
}
108+
}
109+

0 commit comments

Comments
 (0)