Skip to content

Commit 3d10c64

Browse files
authored
Rollup merge of rust-lang#91032 - eholk:generator-drop-tracking, r=nikomatsakis
Introduce drop range tracking to generator interior analysis This PR addresses cases such as this one from rust-lang#57478: ```rust struct Foo; impl !Send for Foo {} let _: impl Send = || { let guard = Foo; drop(guard); yield; }; ``` Previously, the `generator_interior` pass would unnecessarily include the type `Foo` in the generator because it was not aware of the behavior of `drop`. We fix this issue by introducing a drop range analysis that finds portions of the code where a value is guaranteed to be dropped. If a value is dropped at all suspend points, then it is no longer included in the generator type. Note that we are using "dropped" in a generic sense to include any case in which a value has been moved. That is, we do not only look at calls to the `drop` function. There are several phases to the drop tracking algorithm, and we'll go into more detail below. 1. Use `ExprUseVisitor` to find values that are consumed and borrowed. 2. `DropRangeVisitor` uses consume and borrow information to gather drop and reinitialization events, as well as build a control flow graph. 3. We then propagate drop and reinitialization information through the CFG until we reach a fix point (see `DropRanges::propagate_to_fixpoint`). 4. When recording a type (see `InteriorVisitor::record`), we check the computed drop ranges to see if that value is definitely dropped at the suspend point. If so, we skip including it in the type. ## 1. Use `ExprUseVisitor` to find values that are consumed and borrowed. We use `ExprUseVisitor` to identify the places where values are consumed. We track both the `hir_id` of the value, and the `hir_id` of the expression that consumes it. For example, in the expression `[Foo]`, the `Foo` is consumed by the array expression, so after the array expression we can consider the `Foo` temporary to be dropped. In this process, we also collect values that are borrowed. The reason is that the MIR transform for generators conservatively assumes anything borrowed is live across a suspend point (see `rustc_mir_transform::generator::locals_live_across_suspend_points`). We match this behavior here as well. ## 2. Gather drop events, reinitialization events, and control flow graph After finding the values of interest, we perform a post-order traversal over the HIR tree to find the points where these values are dropped or reinitialized. We use the post-order index of each event because this is how the existing generator interior analysis refers to the position of suspend points and the scopes of variables. During this traversal, we also record branching and merging information to handle control flow constructs such as `if`, `match`, and `loop`. This is necessary because values may be dropped along some control flow paths but not others. ## 3. Iterate to fixed point The previous pass found the interesting events and locations, but now we need to find the actual ranges where things are dropped. Upon entry, we have a list of nodes ordered by their position in the post-order traversal. Each node has a set of successors. For each node we additionally keep a bitfield with one bit per potentially consumed value. The bit is set if we the value is dropped along all paths entering this node. To compute the drop information, we first reverse the successor edges to find each node's predecessors. Then we iterate through each node, and for each node we set its dropped value bitfield to the intersection of all incoming dropped value bitfields. If any bitfield for any node changes, we re-run the propagation loop again. ## 4. Ignore dropped values across suspend points At this point we have a data structure where we can ask whether a value is guaranteed to be dropped at any post order index for the HIR tree. We use this information in `InteriorVisitor` to check whether a value in question is dropped at a particular suspend point. If it is, we do not include that value's type in the generator type. Note that we had to augment the region scope tree to include all yields in scope, rather than just the last one as we did before. r? `@nikomatsakis`
2 parents 74fbbef + 76f6b57 commit 3d10c64

25 files changed

+1488
-103
lines changed

Cargo.lock

+2
Original file line numberDiff line numberDiff line change
@@ -4413,13 +4413,15 @@ dependencies = [
44134413
"rustc_attr",
44144414
"rustc_data_structures",
44154415
"rustc_errors",
4416+
"rustc_graphviz",
44164417
"rustc_hir",
44174418
"rustc_hir_pretty",
44184419
"rustc_index",
44194420
"rustc_infer",
44204421
"rustc_lint",
44214422
"rustc_macros",
44224423
"rustc_middle",
4424+
"rustc_serialize",
44234425
"rustc_session",
44244426
"rustc_span",
44254427
"rustc_target",

compiler/rustc_middle/src/middle/region.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ pub struct ScopeTree {
308308
/// The reason is that semantically, until the `box` expression returns,
309309
/// the values are still owned by their containing expressions. So
310310
/// we'll see that `&x`.
311-
pub yield_in_scope: FxHashMap<Scope, YieldData>,
311+
pub yield_in_scope: FxHashMap<Scope, Vec<YieldData>>,
312312

313313
/// The number of visit_expr and visit_pat calls done in the body.
314314
/// Used to sanity check visit_expr/visit_pat call count when
@@ -423,8 +423,8 @@ impl ScopeTree {
423423

424424
/// Checks whether the given scope contains a `yield`. If so,
425425
/// returns `Some(YieldData)`. If not, returns `None`.
426-
pub fn yield_in_scope(&self, scope: Scope) -> Option<YieldData> {
427-
self.yield_in_scope.get(&scope).cloned()
426+
pub fn yield_in_scope(&self, scope: Scope) -> Option<&Vec<YieldData>> {
427+
self.yield_in_scope.get(&scope)
428428
}
429429

430430
/// Gives the number of expressions visited in a body.

compiler/rustc_passes/src/region.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
366366
let target_scopes = visitor.fixup_scopes.drain(start_point..);
367367

368368
for scope in target_scopes {
369-
let mut yield_data = visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap();
369+
let mut yield_data =
370+
visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap().last_mut().unwrap();
370371
let count = yield_data.expr_and_pat_count;
371372
let span = yield_data.span;
372373

@@ -429,7 +430,13 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
429430
};
430431
let data =
431432
YieldData { span, expr_and_pat_count: visitor.expr_and_pat_count, source: *source };
432-
visitor.scope_tree.yield_in_scope.insert(scope, data);
433+
match visitor.scope_tree.yield_in_scope.get_mut(&scope) {
434+
Some(yields) => yields.push(data),
435+
None => {
436+
visitor.scope_tree.yield_in_scope.insert(scope, vec![data]);
437+
}
438+
}
439+
433440
if visitor.pessimistic_yield {
434441
debug!("resolve_expr in pessimistic_yield - marking scope {:?} for fixup", scope);
435442
visitor.fixup_scopes.push(scope);

compiler/rustc_typeck/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rustc_middle = { path = "../rustc_middle" }
1515
rustc_attr = { path = "../rustc_attr" }
1616
rustc_data_structures = { path = "../rustc_data_structures" }
1717
rustc_errors = { path = "../rustc_errors" }
18+
rustc_graphviz = { path = "../rustc_graphviz" }
1819
rustc_hir = { path = "../rustc_hir" }
1920
rustc_hir_pretty = { path = "../rustc_hir_pretty" }
2021
rustc_target = { path = "../rustc_target" }
@@ -27,3 +28,4 @@ rustc_infer = { path = "../rustc_infer" }
2728
rustc_trait_selection = { path = "../rustc_trait_selection" }
2829
rustc_ty_utils = { path = "../rustc_ty_utils" }
2930
rustc_lint = { path = "../rustc_lint" }
31+
rustc_serialize = { path = "../rustc_serialize" }

compiler/rustc_typeck/src/check/generator_interior.rs

+30-18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the
44
//! types computed here.
55
6+
use self::drop_ranges::DropRanges;
67
use super::FnCtxt;
78
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
89
use rustc_errors::pluralize;
@@ -19,6 +20,8 @@ use rustc_span::Span;
1920
use smallvec::SmallVec;
2021
use tracing::debug;
2122

23+
mod drop_ranges;
24+
2225
struct InteriorVisitor<'a, 'tcx> {
2326
fcx: &'a FnCtxt<'a, 'tcx>,
2427
types: FxIndexSet<ty::GeneratorInteriorTypeCause<'tcx>>,
@@ -34,6 +37,7 @@ struct InteriorVisitor<'a, 'tcx> {
3437
guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>,
3538
guard_bindings_set: HirIdSet,
3639
linted_values: HirIdSet,
40+
drop_ranges: DropRanges,
3741
}
3842

3943
impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
@@ -48,9 +52,11 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
4852
) {
4953
use rustc_span::DUMMY_SP;
5054

55+
let ty = self.fcx.resolve_vars_if_possible(ty);
56+
5157
debug!(
52-
"generator_interior: attempting to record type {:?} {:?} {:?} {:?}",
53-
ty, scope, expr, source_span
58+
"attempting to record type ty={:?}; hir_id={:?}; scope={:?}; expr={:?}; source_span={:?}; expr_count={:?}",
59+
ty, hir_id, scope, expr, source_span, self.expr_count,
5460
);
5561

5662
let live_across_yield = scope
@@ -63,29 +69,34 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
6369
//
6470
// See the mega-comment at `yield_in_scope` for a proof.
6571

66-
debug!(
67-
"comparing counts yield: {} self: {}, source_span = {:?}",
68-
yield_data.expr_and_pat_count, self.expr_count, source_span
69-
);
72+
yield_data
73+
.iter()
74+
.find(|yield_data| {
75+
debug!(
76+
"comparing counts yield: {} self: {}, source_span = {:?}",
77+
yield_data.expr_and_pat_count, self.expr_count, source_span
78+
);
79+
80+
if self.drop_ranges.is_dropped_at(hir_id, yield_data.expr_and_pat_count)
81+
{
82+
debug!("value is dropped at yield point; not recording");
83+
return false;
84+
}
7085

71-
// If it is a borrowing happening in the guard,
72-
// it needs to be recorded regardless because they
73-
// do live across this yield point.
74-
if guard_borrowing_from_pattern
75-
|| yield_data.expr_and_pat_count >= self.expr_count
76-
{
77-
Some(yield_data)
78-
} else {
79-
None
80-
}
86+
// If it is a borrowing happening in the guard,
87+
// it needs to be recorded regardless because they
88+
// do live across this yield point.
89+
guard_borrowing_from_pattern
90+
|| yield_data.expr_and_pat_count >= self.expr_count
91+
})
92+
.cloned()
8193
})
8294
})
8395
.unwrap_or_else(|| {
8496
Some(YieldData { span: DUMMY_SP, expr_and_pat_count: 0, source: self.kind.into() })
8597
});
8698

8799
if let Some(yield_data) = live_across_yield {
88-
let ty = self.fcx.resolve_vars_if_possible(ty);
89100
debug!(
90101
"type in expr = {:?}, scope = {:?}, type = {:?}, count = {}, yield_span = {:?}",
91102
expr, scope, ty, self.expr_count, yield_data.span
@@ -154,7 +165,6 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
154165
self.expr_count,
155166
expr.map(|e| e.span)
156167
);
157-
let ty = self.fcx.resolve_vars_if_possible(ty);
158168
if let Some((unresolved_type, unresolved_type_span)) =
159169
self.fcx.unresolved_type_vars(&ty)
160170
{
@@ -186,6 +196,7 @@ pub fn resolve_interior<'a, 'tcx>(
186196
guard_bindings: <_>::default(),
187197
guard_bindings_set: <_>::default(),
188198
linted_values: <_>::default(),
199+
drop_ranges: drop_ranges::compute_drop_ranges(fcx, def_id, body),
189200
};
190201
intravisit::walk_body(&mut visitor, body);
191202

@@ -313,6 +324,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
313324

314325
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
315326
let mut guard_borrowing_from_pattern = false;
327+
316328
match &expr.kind {
317329
ExprKind::Call(callee, args) => match &callee.kind {
318330
ExprKind::Path(qpath) => {

0 commit comments

Comments
 (0)