Skip to content

Commit 0c3f0cd

Browse files
committed
Auto merge of #93752 - eholk:drop-tracking-break-continue, r=nikomatsakis
Generator drop tracking: improve break and continue handling This PR fixes two related issues. One, sometimes break or continue have a block target instead of an expression target. This seems to mainly happen with try blocks. Since the drop tracking analysis only works on expressions, if we see a block target for break or continue, we substitute the last expression of the block as the target instead. Two, break and continue were incorrectly being treated as the same, so continue would also show up as an exit from the loop or block. This patch corrects the way continue is handled by keeping a stack of loop entry points and uses those to find the target of the continue. Fixes #93197 r? `@nikomatsakis`
2 parents c5c610a + c37a906 commit 0c3f0cd

File tree

4 files changed

+114
-10
lines changed

4 files changed

+114
-10
lines changed

compiler/rustc_ast/src/ast.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ mod tests;
5454
/// ```
5555
///
5656
/// `'outer` is a label.
57-
#[derive(Clone, Encodable, Decodable, Copy, HashStable_Generic)]
57+
#[derive(Clone, Encodable, Decodable, Copy, HashStable_Generic, Eq, PartialEq)]
5858
pub struct Label {
5959
pub ident: Ident,
6060
}

compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs

+94-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use super::{
44
};
55
use hir::{
66
intravisit::{self, Visitor},
7-
Body, Expr, ExprKind, Guard, HirId,
7+
Body, Expr, ExprKind, Guard, HirId, LoopIdError,
88
};
99
use rustc_data_structures::fx::FxHashMap;
1010
use rustc_hir as hir;
@@ -85,6 +85,7 @@ struct DropRangeVisitor<'a, 'tcx> {
8585
expr_index: PostOrderId,
8686
tcx: TyCtxt<'tcx>,
8787
typeck_results: &'a TypeckResults<'tcx>,
88+
label_stack: Vec<(Option<rustc_ast::Label>, PostOrderId)>,
8889
}
8990

9091
impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
@@ -101,7 +102,15 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
101102
hir,
102103
num_exprs,
103104
);
104-
Self { hir, places, drop_ranges, expr_index: PostOrderId::from_u32(0), typeck_results, tcx }
105+
Self {
106+
hir,
107+
places,
108+
drop_ranges,
109+
expr_index: PostOrderId::from_u32(0),
110+
typeck_results,
111+
tcx,
112+
label_stack: vec![],
113+
}
105114
}
106115

107116
fn record_drop(&mut self, value: TrackedValue) {
@@ -209,6 +218,60 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
209218
self.drop_ranges.add_control_edge(self.expr_index + 1, self.expr_index + 1);
210219
}
211220
}
221+
222+
/// Map a Destination to an equivalent expression node
223+
///
224+
/// The destination field of a Break or Continue expression can target either an
225+
/// expression or a block. The drop range analysis, however, only deals in
226+
/// expression nodes, so blocks that might be the destination of a Break or Continue
227+
/// will not have a PostOrderId.
228+
///
229+
/// If the destination is an expression, this function will simply return that expression's
230+
/// hir_id. If the destination is a block, this function will return the hir_id of last
231+
/// expression in the block.
232+
fn find_target_expression_from_destination(
233+
&self,
234+
destination: hir::Destination,
235+
) -> Result<HirId, LoopIdError> {
236+
destination.target_id.map(|target| {
237+
let node = self.hir.get(target);
238+
match node {
239+
hir::Node::Expr(_) => target,
240+
hir::Node::Block(b) => find_last_block_expression(b),
241+
hir::Node::Param(..)
242+
| hir::Node::Item(..)
243+
| hir::Node::ForeignItem(..)
244+
| hir::Node::TraitItem(..)
245+
| hir::Node::ImplItem(..)
246+
| hir::Node::Variant(..)
247+
| hir::Node::Field(..)
248+
| hir::Node::AnonConst(..)
249+
| hir::Node::Stmt(..)
250+
| hir::Node::PathSegment(..)
251+
| hir::Node::Ty(..)
252+
| hir::Node::TraitRef(..)
253+
| hir::Node::Binding(..)
254+
| hir::Node::Pat(..)
255+
| hir::Node::Arm(..)
256+
| hir::Node::Local(..)
257+
| hir::Node::Ctor(..)
258+
| hir::Node::Lifetime(..)
259+
| hir::Node::GenericParam(..)
260+
| hir::Node::Visibility(..)
261+
| hir::Node::Crate(..)
262+
| hir::Node::Infer(..) => bug!("Unsupported branch target: {:?}", node),
263+
}
264+
})
265+
}
266+
}
267+
268+
fn find_last_block_expression(block: &hir::Block<'_>) -> HirId {
269+
block.expr.map_or_else(
270+
// If there is no tail expression, there will be at least one statement in the
271+
// block because the block contains a break or continue statement.
272+
|| block.stmts.last().unwrap().hir_id,
273+
|expr| expr.hir_id,
274+
)
212275
}
213276

214277
impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
@@ -320,8 +383,9 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
320383
});
321384
}
322385

323-
ExprKind::Loop(body, ..) => {
386+
ExprKind::Loop(body, label, ..) => {
324387
let loop_begin = self.expr_index + 1;
388+
self.label_stack.push((label, loop_begin));
325389
if body.stmts.is_empty() && body.expr.is_none() {
326390
// For empty loops we won't have updated self.expr_index after visiting the
327391
// body, meaning we'd get an edge from expr_index to expr_index + 1, but
@@ -331,10 +395,31 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
331395
self.visit_block(body);
332396
self.drop_ranges.add_control_edge(self.expr_index, loop_begin);
333397
}
398+
self.label_stack.pop();
334399
}
335-
ExprKind::Break(hir::Destination { target_id: Ok(target), .. }, ..)
336-
| ExprKind::Continue(hir::Destination { target_id: Ok(target), .. }, ..) => {
337-
self.drop_ranges.add_control_edge_hir_id(self.expr_index, target);
400+
// Find the loop entry by searching through the label stack for either the last entry
401+
// (if label is none), or the first entry where the label matches this one. The Loop
402+
// case maintains this stack mapping labels to the PostOrderId for the loop entry.
403+
ExprKind::Continue(hir::Destination { label, .. }, ..) => self
404+
.label_stack
405+
.iter()
406+
.rev()
407+
.find(|(loop_label, _)| label.is_none() || *loop_label == label)
408+
.map_or((), |(_, target)| {
409+
self.drop_ranges.add_control_edge(self.expr_index, *target)
410+
}),
411+
412+
ExprKind::Break(destination, ..) => {
413+
// destination either points to an expression or to a block. We use
414+
// find_target_expression_from_destination to use the last expression of the block
415+
// if destination points to a block.
416+
//
417+
// We add an edge to the hir_id of the expression/block we are breaking out of, and
418+
// then in process_deferred_edges we will map this hir_id to its PostOrderId, which
419+
// will refer to the end of the block due to the post order traversal.
420+
self.find_target_expression_from_destination(destination).map_or((), |target| {
421+
self.drop_ranges.add_control_edge_hir_id(self.expr_index, target)
422+
})
338423
}
339424

340425
ExprKind::Call(f, args) => {
@@ -359,11 +444,9 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
359444
| ExprKind::Binary(..)
360445
| ExprKind::Block(..)
361446
| ExprKind::Box(..)
362-
| ExprKind::Break(..)
363447
| ExprKind::Cast(..)
364448
| ExprKind::Closure(..)
365449
| ExprKind::ConstBlock(..)
366-
| ExprKind::Continue(..)
367450
| ExprKind::DropTemps(..)
368451
| ExprKind::Err
369452
| ExprKind::Field(..)
@@ -462,11 +545,13 @@ impl DropRangesBuilder {
462545
/// Should be called after visiting the HIR but before solving the control flow, otherwise some
463546
/// edges will be missed.
464547
fn process_deferred_edges(&mut self) {
548+
trace!("processing deferred edges. post_order_map={:#?}", self.post_order_map);
465549
let mut edges = vec![];
466550
swap(&mut edges, &mut self.deferred_edges);
467551
edges.into_iter().for_each(|(from, to)| {
468-
let to = *self.post_order_map.get(&to).expect("Expression ID not found");
469552
trace!("Adding deferred edge from {:?} to {:?}", from, to);
553+
let to = *self.post_order_map.get(&to).expect("Expression ID not found");
554+
trace!("target edge PostOrderId={:?}", to);
470555
self.add_control_edge(from, to)
471556
});
472557
}

src/test/ui/async-await/issue-93197.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Regression test for #93197
22
// check-pass
33
// edition:2021
4+
// compile-flags: -Zdrop-tracking
45

56
#![feature(try_blocks)]
67

src/test/ui/generator/drop-control-flow.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// build-pass
2+
// compile-flags: -Zdrop-tracking
23

34
// FIXME(eholk): temporarily disabled while drop range tracking is disabled
45
// (see generator_interior.rs:27)
@@ -114,6 +115,22 @@ fn nested_loop() {
114115
};
115116
}
116117

118+
fn loop_continue(b: bool) {
119+
let _ = || {
120+
let mut arr = [Ptr];
121+
let mut count = 0;
122+
drop(arr);
123+
while count < 3 {
124+
count += 1;
125+
yield;
126+
if b {
127+
arr = [Ptr];
128+
continue;
129+
}
130+
}
131+
};
132+
}
133+
117134
fn main() {
118135
one_armed_if(true);
119136
if_let(Some(41));
@@ -122,4 +139,5 @@ fn main() {
122139
reinit();
123140
loop_uninit();
124141
nested_loop();
142+
loop_continue(true);
125143
}

0 commit comments

Comments
 (0)