Skip to content

Commit c8b602a

Browse files
beepster4096WaffleLapkin
authored andcommitted
handle drops for tail calls
1 parent 0722a1e commit c8b602a

File tree

6 files changed

+132
-25
lines changed

6 files changed

+132
-25
lines changed

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

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,28 +101,34 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
101101
),
102102
ExprKind::Become { value } => {
103103
let v = &this.thir[value];
104-
let ExprKind::Scope { value, .. } = v.kind else { span_bug!(v.span, "what {v:?}") };
104+
let ExprKind::Scope { region_scope, lint_level, value, .. } = v.kind else { span_bug!(v.span, "what {v:?}") };
105105
let v = &this.thir[value];
106106
let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind else { span_bug!(v.span, "what {v:?}") };
107107

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

115-
this.record_operands_moved(&args);
118+
this.record_operands_moved(&args);
116119

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

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

125-
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+
})
126132
}
127133
_ => {
128134
assert!(

compiler/rustc_mir_build/src/build/scope.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,8 +668,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
668668
(None, None) => {}
669669
}
670670

671-
// FIXME(explicit_tail_calls): this should drop stuff early for `become`
672-
673671
let region_scope = self.scopes.breakable_scopes[break_index].region_scope;
674672
let scope_index = self.scopes.scope_index(region_scope, span);
675673
let drops = if destination.is_some() {
@@ -723,6 +721,42 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
723721
self.cfg.terminate(block, source_info, TerminatorKind::Resume);
724722
}
725723

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+
726760
// Add a dummy `Assign` statement to the CFG, with the span for the source code's `continue`
727761
// statement.
728762
fn add_dummy_assignment(&mut self, span: Span, block: BasicBlock, source_info: SourceInfo) {

tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ error[E0597]: `local` does not live long enough
44
LL | let local = Type;
55
| ----- binding `local` declared here
66
LL | become takes_borrow(&local);
7-
| ^^^^^^- `local` dropped here while still borrowed
8-
| |
9-
| borrowed value does not live long enough
7+
| ^^^^^^ borrowed value does not live long enough
8+
LL |
9+
LL | }
10+
| - `local` dropped here while still borrowed
1011

1112
error: aborting due to previous error
1213

tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![feature(explicit_tail_calls)]
1+
#![feature(explicit_tail_calls, const_trait_impl, const_mut_refs)]
22

33
pub const fn test(_: &View) {
44
const fn takes_view(_: &View) {}
@@ -16,7 +16,7 @@ impl HasDrop {
1616
}
1717
}
1818

19-
impl Drop for HasDrop {
19+
impl const Drop for HasDrop {
2020
fn drop(&mut self) {}
2121
}
2222

tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ error[E0716]: temporary value dropped while borrowed
22
--> $DIR/ctfe-tmp-arg-drop.rs:6:23
33
|
44
LL | become takes_view(HasDrop.as_view());
5-
| ------------------^^^^^^^-----------
6-
| | | |
7-
| | | temporary value is freed at the end of this statement
5+
| ------------------^^^^^^^------------ temporary value is freed at the end of this statement
6+
| | |
87
| | creates a temporary value which is freed while still in use
98
| borrow later used here
109
|
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// run-pass
2+
#![feature(explicit_tail_calls)]
3+
use std::cell::RefCell;
4+
5+
fn main() {
6+
let tail_counter = Default::default();
7+
tail_recursive(0, &tail_counter);
8+
assert_eq!(tail_counter.into_inner(), (0..128).collect::<Vec<u8>>());
9+
10+
let simply_counter = Default::default();
11+
simply_recursive(0, &simply_counter);
12+
assert_eq!(simply_counter.into_inner(), (0..128).rev().collect::<Vec<u8>>());
13+
14+
let scope_counter = Default::default();
15+
out_of_inner_scope(&scope_counter);
16+
assert_eq!(scope_counter.into_inner(), (0..8).collect::<Vec<u8>>());
17+
}
18+
19+
fn tail_recursive(n: u8, order: &RefCell<Vec<u8>>) {
20+
if n >= 128 {
21+
return;
22+
}
23+
24+
let _local = DropCounter(n, order);
25+
26+
become tail_recursive(n + 1, order)
27+
}
28+
29+
fn simply_recursive(n: u8, order: &RefCell<Vec<u8>>) {
30+
if n >= 128 {
31+
return;
32+
}
33+
34+
let _local = DropCounter(n, order);
35+
36+
return simply_recursive(n + 1, order)
37+
}
38+
39+
fn out_of_inner_scope(order: &RefCell<Vec<u8>>) {
40+
fn inner(order: &RefCell<Vec<u8>>) {
41+
let _7 = DropCounter(7, order);
42+
let _6 = DropCounter(6, order);
43+
}
44+
45+
let _5 = DropCounter(5, order);
46+
let _4 = DropCounter(4, order);
47+
48+
if true {
49+
let _3 = DropCounter(3, order);
50+
let _2 = DropCounter(2, order);
51+
loop {
52+
let _1 = DropCounter(1, order);
53+
let _0 = DropCounter(0, order);
54+
55+
become inner(order);
56+
}
57+
}
58+
}
59+
60+
struct DropCounter<'a>(u8, &'a RefCell<Vec<u8>>);
61+
62+
impl Drop for DropCounter<'_> {
63+
#[track_caller]
64+
fn drop(&mut self) {
65+
self.1.borrow_mut().push(self.0);
66+
}
67+
}

0 commit comments

Comments
 (0)