Skip to content

Commit e0a5370

Browse files
committed
Respond to code review comments
1 parent 32930d9 commit e0a5370

File tree

5 files changed

+265
-35
lines changed

5 files changed

+265
-35
lines changed

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

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rustc_data_structures::fx::FxHashMap;
2121
use rustc_hir as hir;
2222
use rustc_index::bit_set::BitSet;
2323
use rustc_index::vec::IndexVec;
24+
use rustc_middle::hir::map::Map;
2425
use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId};
2526
use rustc_middle::ty;
2627
use std::collections::BTreeMap;
@@ -53,15 +54,18 @@ pub fn compute_drop_ranges<'a, 'tcx>(
5354
DropRanges { tracked_value_map: drop_ranges.tracked_value_map, nodes: drop_ranges.nodes }
5455
}
5556

56-
/// Applies `f` to consumable portion of a HIR node.
57+
/// Applies `f` to consumable node in the HIR subtree pointed to by `place`.
5758
///
58-
/// The `node` parameter should be the result of calling `Map::find(place)`.
59-
fn for_each_consumable(
60-
place: TrackedValue,
61-
node: Option<Node<'_>>,
62-
mut f: impl FnMut(TrackedValue),
63-
) {
59+
/// This includes the place itself, and if the place is a reference to a local
60+
/// variable then `f` is also called on the HIR node for that variable as well.
61+
///
62+
/// For example, if `place` points to `foo()`, then `f` is called once for the
63+
/// result of `foo`. On the other hand, if `place` points to `x` then `f` will
64+
/// be called both on the `ExprKind::Path` node that represents the expression
65+
/// as well as the HirId of the local `x` itself.
66+
fn for_each_consumable<'tcx>(hir: Map<'tcx>, place: TrackedValue, mut f: impl FnMut(TrackedValue)) {
6467
f(place);
68+
let node = hir.find(place.hir_id());
6569
if let Some(Node::Expr(expr)) = node {
6670
match expr.kind {
6771
hir::ExprKind::Path(hir::QPath::Resolved(
@@ -108,15 +112,37 @@ impl TrackedValue {
108112
}
109113
}
110114

111-
impl From<&PlaceWithHirId<'_>> for TrackedValue {
112-
fn from(place_with_id: &PlaceWithHirId<'_>) -> Self {
115+
/// Represents a reason why we might not be able to convert a HirId or Place
116+
/// into a tracked value.
117+
#[derive(Debug)]
118+
enum TrackedValueConversionError {
119+
/// Place projects are not currently supported.
120+
///
121+
/// The reasoning around these is kind of subtle, so we choose to be more
122+
/// conservative around these for now. There is not reason in theory we
123+
/// cannot support these, we just have not implemented it yet.
124+
PlaceProjectionsNotSupported,
125+
}
126+
127+
impl TryFrom<&PlaceWithHirId<'_>> for TrackedValue {
128+
type Error = TrackedValueConversionError;
129+
130+
fn try_from(place_with_id: &PlaceWithHirId<'_>) -> Result<Self, Self::Error> {
131+
if !place_with_id.place.projections.is_empty() {
132+
debug!(
133+
"TrackedValue from PlaceWithHirId: {:?} has projections, which are not supported.",
134+
place_with_id
135+
);
136+
return Err(TrackedValueConversionError::PlaceProjectionsNotSupported);
137+
}
138+
113139
match place_with_id.place.base {
114140
PlaceBase::Rvalue | PlaceBase::StaticItem => {
115-
TrackedValue::Temporary(place_with_id.hir_id)
141+
Ok(TrackedValue::Temporary(place_with_id.hir_id))
116142
}
117143
PlaceBase::Local(hir_id)
118144
| PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => {
119-
TrackedValue::Variable(hir_id)
145+
Ok(TrackedValue::Variable(hir_id))
120146
}
121147
}
122148
}

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

Lines changed: 153 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,41 @@ pub(super) fn build_control_flow_graph<'tcx>(
4343
/// We are interested in points where a variables is dropped or initialized, and the control flow
4444
/// of the code. We identify locations in code by their post-order traversal index, so it is
4545
/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`.
46+
///
47+
/// We make several simplifying assumptions, with the goal of being more conservative than
48+
/// necessary rather than less conservative (since being less conservative is unsound, but more
49+
/// conservative is still safe). These assumptions are:
50+
///
51+
/// 1. Moving a variable `a` counts as a move of the whole variable.
52+
/// 2. Moving a partial path like `a.b.c` is ignored.
53+
/// 3. Reinitializing through a field (e.g. `a.b.c = 5`) counds as a reinitialization of all of
54+
/// `a`.
55+
///
56+
/// Some examples:
57+
///
58+
/// Rule 1:
59+
/// ```rust
60+
/// let mut a = (vec![0], vec![0]);
61+
/// drop(a);
62+
/// // `a` is not considered initialized.
63+
/// ```
64+
///
65+
/// Rule 2:
66+
/// ```rust
67+
/// let mut a = (vec![0], vec![0]);
68+
/// drop(a.0);
69+
/// drop(a.1);
70+
/// // `a` is still considered initialized.
71+
/// ```
72+
///
73+
/// Rule 3:
74+
/// ```rust
75+
/// let mut a = (vec![0], vec![0]);
76+
/// drop(a);
77+
/// a.1 = vec![1];
78+
/// // all of `a` is considered initialized
79+
/// ```
80+
4681
struct DropRangeVisitor<'a, 'tcx> {
4782
hir: Map<'tcx>,
4883
places: ConsumedAndBorrowedPlaces,
@@ -89,23 +124,76 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
89124
.get(&expr.hir_id)
90125
.map_or(vec![], |places| places.iter().cloned().collect());
91126
for place in places {
92-
for_each_consumable(place, self.hir.find(place.hir_id()), |value| {
93-
self.record_drop(value)
94-
});
127+
for_each_consumable(self.hir, place, |value| self.record_drop(value));
95128
}
96129
}
97130

131+
/// Marks an expression as being reinitialized.
132+
///
133+
/// Note that we always approximated on the side of things being more
134+
/// initialized than they actually are, as opposed to less. In cases such
135+
/// as `x.y = ...`, we would consider all of `x` as being initialized
136+
/// instead of just the `y` field.
137+
///
138+
/// This is because it is always safe to consider something initialized
139+
/// even when it is not, but the other way around will cause problems.
140+
///
141+
/// In the future, we will hopefully tighten up these rules to be more
142+
/// precise.
98143
fn reinit_expr(&mut self, expr: &hir::Expr<'_>) {
99-
if let ExprKind::Path(hir::QPath::Resolved(
100-
_,
101-
hir::Path { res: hir::def::Res::Local(hir_id), .. },
102-
)) = expr.kind
103-
{
104-
let location = self.expr_index;
105-
debug!("reinitializing {:?} at {:?}", hir_id, location);
106-
self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location);
107-
} else {
108-
debug!("reinitializing {:?} is not supported", expr);
144+
// Walk the expression to find the base. For example, in an expression
145+
// like `*a[i].x`, we want to find the `a` and mark that as
146+
// reinitialized.
147+
match expr.kind {
148+
ExprKind::Path(hir::QPath::Resolved(
149+
_,
150+
hir::Path { res: hir::def::Res::Local(hir_id), .. },
151+
)) => {
152+
// This is the base case, where we have found an actual named variable.
153+
154+
let location = self.expr_index;
155+
debug!("reinitializing {:?} at {:?}", hir_id, location);
156+
self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location);
157+
}
158+
159+
ExprKind::Field(base, _) => self.reinit_expr(base),
160+
161+
// Most expressions do not refer to something where we need to track
162+
// reinitializations.
163+
//
164+
// Some of these may be interesting in the future
165+
ExprKind::Path(..)
166+
| ExprKind::Box(_)
167+
| ExprKind::ConstBlock(_)
168+
| ExprKind::Array(_)
169+
| ExprKind::Call(_, _)
170+
| ExprKind::MethodCall(_, _, _, _)
171+
| ExprKind::Tup(_)
172+
| ExprKind::Binary(_, _, _)
173+
| ExprKind::Unary(_, _)
174+
| ExprKind::Lit(_)
175+
| ExprKind::Cast(_, _)
176+
| ExprKind::Type(_, _)
177+
| ExprKind::DropTemps(_)
178+
| ExprKind::Let(_, _, _)
179+
| ExprKind::If(_, _, _)
180+
| ExprKind::Loop(_, _, _, _)
181+
| ExprKind::Match(_, _, _)
182+
| ExprKind::Closure(_, _, _, _, _)
183+
| ExprKind::Block(_, _)
184+
| ExprKind::Assign(_, _, _)
185+
| ExprKind::AssignOp(_, _, _)
186+
| ExprKind::Index(_, _)
187+
| ExprKind::AddrOf(_, _, _)
188+
| ExprKind::Break(_, _)
189+
| ExprKind::Continue(_)
190+
| ExprKind::Ret(_)
191+
| ExprKind::InlineAsm(_)
192+
| ExprKind::LlvmInlineAsm(_)
193+
| ExprKind::Struct(_, _, _)
194+
| ExprKind::Repeat(_, _)
195+
| ExprKind::Yield(_, _)
196+
| ExprKind::Err => (),
109197
}
110198
}
111199

@@ -158,13 +246,47 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
158246
self.drop_ranges.add_control_edge(true_end, self.expr_index + 1);
159247
}
160248
ExprKind::Match(scrutinee, arms, ..) => {
249+
// We walk through the match expression almost like a chain of if expressions.
250+
// Here's a diagram to follow along with:
251+
//
252+
// ┌─┐
253+
// match │A│ {
254+
// ┌───┴─┘
255+
// │
256+
// ┌▼┌───►┌─┐ ┌─┐
257+
// │B│ if │C│ =>│D│,
258+
// └─┘ ├─┴──►└─┴──────┐
259+
// ┌──┘ │
260+
// ┌──┘ │
261+
// │ │
262+
// ┌▼┌───►┌─┐ ┌─┐ │
263+
// │E│ if │F│ =>│G│, │
264+
// └─┘ ├─┴──►└─┴┐ │
265+
// │ │ │
266+
// } ▼ ▼ │
267+
// ┌─┐◄───────────────────┘
268+
// │H│
269+
// └─┘
270+
//
271+
// The order we want is that the scrutinee (A) flows into the first pattern (B),
272+
// which flows into the guard (C). Then the guard either flows into the arm body
273+
// (D) or into the start of the next arm (E). Finally, the body flows to the end
274+
// of the match block (H).
275+
//
276+
// The subsequent arms follow the same ordering. First we go to the pattern, then
277+
// the guard (if present, otherwise it flows straight into the body), then into
278+
// the body and then to the end of the match expression.
279+
//
280+
// The comments below show which edge is being added.
161281
self.visit_expr(scrutinee);
162282

163283
let (guard_exit, arm_end_ids) = arms.iter().fold(
164284
(self.expr_index, vec![]),
165285
|(incoming_edge, mut arm_end_ids), hir::Arm { pat, body, guard, .. }| {
286+
// A -> B, or C -> E
166287
self.drop_ranges.add_control_edge(incoming_edge, self.expr_index + 1);
167288
self.visit_pat(pat);
289+
// B -> C and E -> F are added implicitly due to the traversal order.
168290
match guard {
169291
Some(Guard::If(expr)) => self.visit_expr(expr),
170292
Some(Guard::IfLet(pat, expr)) => {
@@ -173,17 +295,34 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
173295
}
174296
None => (),
175297
}
298+
// Likewise, C -> D and F -> G are added implicitly.
299+
300+
// Save C, F, so we can add the other outgoing edge.
176301
let to_next_arm = self.expr_index;
302+
177303
// The default edge does not get added since we also have an explicit edge,
178304
// so we also need to add an edge to the next node as well.
305+
//
306+
// This adds C -> D, F -> G
179307
self.drop_ranges.add_control_edge(self.expr_index, self.expr_index + 1);
180308
self.visit_expr(body);
309+
310+
// Save the end of the body so we can add the exit edge once we know where
311+
// the exit is.
181312
arm_end_ids.push(self.expr_index);
313+
314+
// Pass C to the next iteration, as well as vec![D]
315+
//
316+
// On the last round through, we pass F and vec![D, G] so that we can
317+
// add all the exit edges.
182318
(to_next_arm, arm_end_ids)
183319
},
184320
);
321+
// F -> H
185322
self.drop_ranges.add_control_edge(guard_exit, self.expr_index + 1);
323+
186324
arm_end_ids.into_iter().for_each(|arm_end| {
325+
// D -> H, G -> H
187326
self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1)
188327
});
189328
}
@@ -275,7 +414,7 @@ impl DropRangesBuilder {
275414
let mut tracked_value_map = FxHashMap::<_, TrackedValueIndex>::default();
276415
let mut next = <_>::from(0u32);
277416
for value in tracked_values {
278-
for_each_consumable(value, hir.find(value.hir_id()), |value| {
417+
for_each_consumable(hir, value, |value| {
279418
if !tracked_value_map.contains_key(&value) {
280419
tracked_value_map.insert(value, next);
281420
next = next + 1;

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,9 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> {
8585
"consume {:?}; diag_expr_id={:?}, using parent {:?}",
8686
place_with_id, diag_expr_id, parent
8787
);
88-
// We do not currently support partial drops or reinits, so just ignore
89-
// any places with projections.
90-
if place_with_id.place.projections.is_empty() {
91-
self.mark_consumed(parent, place_with_id.into());
92-
}
88+
place_with_id
89+
.try_into()
90+
.map_or((), |tracked_value| self.mark_consumed(parent, tracked_value));
9391
}
9492

9593
fn borrow(
@@ -98,7 +96,9 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> {
9896
_diag_expr_id: HirId,
9997
_bk: rustc_middle::ty::BorrowKind,
10098
) {
101-
self.places.borrowed.insert(place_with_id.into());
99+
place_with_id
100+
.try_into()
101+
.map_or(false, |tracked_value| self.places.borrowed.insert(tracked_value));
102102
}
103103

104104
fn mutate(

src/test/ui/generator/partial-drop.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,26 @@ fn main() {
1515
let guard = Bar { foo: Foo, x: 42 };
1616
drop(guard.foo);
1717
yield;
18-
})
18+
});
19+
20+
assert_send(|| {
21+
//~^ ERROR generator cannot be sent between threads safely
22+
// FIXME: it would be nice to make this work.
23+
let guard = Bar { foo: Foo, x: 42 };
24+
drop(guard);
25+
guard.foo = Foo;
26+
guard.x = 23;
27+
yield;
28+
});
29+
30+
assert_send(|| {
31+
//~^ ERROR generator cannot be sent between threads safely
32+
// FIXME: it would be nice to make this work.
33+
let guard = Bar { foo: Foo, x: 42 };
34+
let Bar { foo, x } = guard;
35+
drop(foo);
36+
yield;
37+
});
1938
}
2039

2140
fn assert_send<T: Send>(_: T) {}

0 commit comments

Comments
 (0)