Skip to content

Commit 870483a

Browse files
committed
Auto merge of #44392 - Zoxc:yield-order, r=nikomatsakis
Only consider yields coming after the expressions when computing generator interiors When looking at the scopes which temporaries of expressions can live for during computation of generator interiors, only consider yields which appear after the expression in question in the HIR.
2 parents 01c65cb + 2384dd9 commit 870483a

File tree

11 files changed

+225
-62
lines changed

11 files changed

+225
-62
lines changed

src/librustc/hir/intravisit.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,19 @@
2727
//! for more details.
2828
//!
2929
//! Note: it is an important invariant that the default visitor walks
30-
//! the body of a function in "execution order" (more concretely,
31-
//! reverse post-order with respect to the CFG implied by the AST),
32-
//! meaning that if AST node A may execute before AST node B, then A
33-
//! is visited first. The borrow checker in particular relies on this
34-
//! property.
30+
//! the body of a function in "execution order" - more concretely, if
31+
//! we consider the reverse post-order (RPO) of the CFG implied by the HIR,
32+
//! then a pre-order traversal of the HIR is consistent with the CFG RPO
33+
//! on the *initial CFG point* of each HIR node, while a post-order traversal
34+
//! of the HIR is consistent with the CFG RPO on each *final CFG point* of
35+
//! each CFG node.
36+
//!
37+
//! One thing that follows is that if HIR node A always starts/ends executing
38+
//! before HIR node B, then A appears in traversal pre/postorder before B,
39+
//! respectively. (This follows from RPO respecting CFG domination).
40+
//!
41+
//! This order consistency is required in a few places in rustc, for
42+
//! example generator inference, and possibly also HIR borrowck.
3543
3644
use syntax::abi::Abi;
3745
use syntax::ast::{NodeId, CRATE_NODE_ID, Name, Attribute};
@@ -403,10 +411,13 @@ pub fn walk_body<'v, V: Visitor<'v>>(visitor: &mut V, body: &'v Body) {
403411
}
404412

405413
pub fn walk_local<'v, V: Visitor<'v>>(visitor: &mut V, local: &'v Local) {
414+
// Intentionally visiting the expr first - the initialization expr
415+
// dominates the local's definition.
416+
walk_list!(visitor, visit_expr, &local.init);
417+
406418
visitor.visit_id(local.id);
407419
visitor.visit_pat(&local.pat);
408420
walk_list!(visitor, visit_ty, &local.ty);
409-
walk_list!(visitor, visit_expr, &local.init);
410421
}
411422

412423
pub fn walk_lifetime<'v, V: Visitor<'v>>(visitor: &mut V, lifetime: &'v Lifetime) {

src/librustc/hir/print.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1495,7 +1495,7 @@ impl<'a> State<'a> {
14951495
self.pclose()?;
14961496
}
14971497
hir::ExprYield(ref expr) => {
1498-
self.s.word("yield")?;
1498+
self.word_space("yield")?;
14991499
self.print_expr_maybe_paren(&expr, parser::PREC_JUMP)?;
15001500
}
15011501
}

src/librustc/middle/region.rs

+117-28
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use ich::{StableHashingContext, NodeIdHashingMode};
1818
use util::nodemap::{FxHashMap, FxHashSet};
1919
use ty;
2020

21-
use std::collections::hash_map::Entry;
2221
use std::mem;
2322
use std::rc::Rc;
2423
use syntax::codemap;
@@ -250,8 +249,80 @@ pub struct ScopeTree {
250249
closure_tree: FxHashMap<hir::ItemLocalId, hir::ItemLocalId>,
251250

252251
/// If there are any `yield` nested within a scope, this map
253-
/// stores the `Span` of the first one.
254-
yield_in_scope: FxHashMap<Scope, Span>,
252+
/// stores the `Span` of the last one and its index in the
253+
/// postorder of the Visitor traversal on the HIR.
254+
///
255+
/// HIR Visitor postorder indexes might seem like a peculiar
256+
/// thing to care about. but it turns out that HIR bindings
257+
/// and the temporary results of HIR expressions are never
258+
/// storage-live at the end of HIR nodes with postorder indexes
259+
/// lower than theirs, and therefore don't need to be suspended
260+
/// at yield-points at these indexes.
261+
///
262+
/// For an example, suppose we have some code such as:
263+
/// ```rust,ignore (example)
264+
/// foo(f(), yield y, bar(g()))
265+
/// ```
266+
///
267+
/// With the HIR tree (calls numbered for expository purposes)
268+
/// ```
269+
/// Call#0(foo, [Call#1(f), Yield(y), Call#2(bar, Call#3(g))])
270+
/// ```
271+
///
272+
/// Obviously, the result of `f()` was created before the yield
273+
/// (and therefore needs to be kept valid over the yield) while
274+
/// the result of `g()` occurs after the yield (and therefore
275+
/// doesn't). If we want to infer that, we can look at the
276+
/// postorder traversal:
277+
/// ```
278+
/// `foo` `f` Call#1 `y` Yield `bar` `g` Call#3 Call#2 Call#0
279+
/// ```
280+
///
281+
/// In which we can easily see that `Call#1` occurs before the yield,
282+
/// and `Call#3` after it.
283+
///
284+
/// To see that this method works, consider:
285+
///
286+
/// Let `D` be our binding/temporary and `U` be our other HIR node, with
287+
/// `HIR-postorder(U) < HIR-postorder(D)` (in our example, U would be
288+
/// the yield and D would be one of the calls). Let's show that
289+
/// `D` is storage-dead at `U`.
290+
///
291+
/// Remember that storage-live/storage-dead refers to the state of
292+
/// the *storage*, and does not consider moves/drop flags.
293+
///
294+
/// Then:
295+
/// 1. From the ordering guarantee of HIR visitors (see
296+
/// `rustc::hir::intravisit`), `D` does not dominate `U`.
297+
/// 2. Therefore, `D` is *potentially* storage-dead at `U` (because
298+
/// we might visit `U` without ever getting to `D`).
299+
/// 3. However, we guarantee that at each HIR point, each
300+
/// binding/temporary is always either always storage-live
301+
/// or always storage-dead. This is what is being guaranteed
302+
/// by `terminating_scopes` including all blocks where the
303+
/// count of executions is not guaranteed.
304+
/// 4. By `2.` and `3.`, `D` is *statically* storage-dead at `U`,
305+
/// QED.
306+
///
307+
/// I don't think this property relies on `3.` in an essential way - it
308+
/// is probably still correct even if we have "unrestricted" terminating
309+
/// scopes. However, why use the complicated proof when a simple one
310+
/// works?
311+
///
312+
/// A subtle thing: `box` expressions, such as `box (&x, yield 2, &y)`. It
313+
/// might seem that a `box` expression creates a `Box<T>` temporary
314+
/// when it *starts* executing, at `HIR-preorder(BOX-EXPR)`. That might
315+
/// be true in the MIR desugaring, but it is not important in the semantics.
316+
///
317+
/// The reason is that semantically, until the `box` expression returns,
318+
/// the values are still owned by their containing expressions. So
319+
/// we'll see that `&x`.
320+
yield_in_scope: FxHashMap<Scope, (Span, usize)>,
321+
322+
/// The number of visit_expr and visit_pat calls done in the body.
323+
/// Used to sanity check visit_expr/visit_pat call count when
324+
/// calculating geneartor interiors.
325+
body_expr_count: FxHashMap<hir::BodyId, usize>,
255326
}
256327

257328
#[derive(Debug, Copy, Clone)]
@@ -274,6 +345,9 @@ pub struct Context {
274345
struct RegionResolutionVisitor<'a, 'tcx: 'a> {
275346
tcx: TyCtxt<'a, 'tcx, 'tcx>,
276347

348+
// The number of expressions and patterns visited in the current body
349+
expr_and_pat_count: usize,
350+
277351
// Generated scope tree:
278352
scope_tree: ScopeTree,
279353

@@ -611,10 +685,18 @@ impl<'tcx> ScopeTree {
611685
}
612686

613687
/// Checks whether the given scope contains a `yield`. If so,
614-
/// returns `Some(span)` with the span of a yield we found.
615-
pub fn yield_in_scope(&self, scope: Scope) -> Option<Span> {
688+
/// returns `Some((span, expr_count))` with the span of a yield we found and
689+
/// the number of expressions appearing before the `yield` in the body.
690+
pub fn yield_in_scope(&self, scope: Scope) -> Option<(Span, usize)> {
616691
self.yield_in_scope.get(&scope).cloned()
617692
}
693+
694+
/// Gives the number of expressions visited in a body.
695+
/// Used to sanity check visit_expr call count when
696+
/// calculating geneartor interiors.
697+
pub fn body_expr_count(&self, body_id: hir::BodyId) -> Option<usize> {
698+
self.body_expr_count.get(&body_id).map(|r| *r)
699+
}
618700
}
619701

620702
/// Records the lifetime of a local variable as `cx.var_parent`
@@ -714,6 +796,8 @@ fn resolve_pat<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>, pat: &
714796
}
715797

716798
intravisit::walk_pat(visitor, pat);
799+
800+
visitor.expr_and_pat_count += 1;
717801
}
718802

719803
fn resolve_stmt<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>, stmt: &'tcx hir::Stmt) {
@@ -804,29 +888,6 @@ fn resolve_expr<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>, expr:
804888
// record_superlifetime(new_cx, expr.callee_id);
805889
}
806890

807-
hir::ExprYield(..) => {
808-
// Mark this expr's scope and all parent scopes as containing `yield`.
809-
let mut scope = Scope::Node(expr.hir_id.local_id);
810-
loop {
811-
match visitor.scope_tree.yield_in_scope.entry(scope) {
812-
// Another `yield` has already been found.
813-
Entry::Occupied(_) => break,
814-
815-
Entry::Vacant(entry) => {
816-
entry.insert(expr.span);
817-
}
818-
}
819-
820-
// Keep traversing up while we can.
821-
match visitor.scope_tree.parent_map.get(&scope) {
822-
// Don't cross from closure bodies to their parent.
823-
Some(&Scope::CallSite(_)) => break,
824-
Some(&superscope) => scope = superscope,
825-
None => break
826-
}
827-
}
828-
}
829-
830891
_ => {}
831892
}
832893
}
@@ -842,6 +903,25 @@ fn resolve_expr<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>, expr:
842903
_ => intravisit::walk_expr(visitor, expr)
843904
}
844905

906+
visitor.expr_and_pat_count += 1;
907+
908+
if let hir::ExprYield(..) = expr.node {
909+
// Mark this expr's scope and all parent scopes as containing `yield`.
910+
let mut scope = Scope::Node(expr.hir_id.local_id);
911+
loop {
912+
visitor.scope_tree.yield_in_scope.insert(scope,
913+
(expr.span, visitor.expr_and_pat_count));
914+
915+
// Keep traversing up while we can.
916+
match visitor.scope_tree.parent_map.get(&scope) {
917+
// Don't cross from closure bodies to their parent.
918+
Some(&Scope::CallSite(_)) => break,
919+
Some(&superscope) => scope = superscope,
920+
None => break
921+
}
922+
}
923+
}
924+
845925
visitor.cx = prev_cx;
846926
}
847927

@@ -1120,6 +1200,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RegionResolutionVisitor<'a, 'tcx> {
11201200
body_id,
11211201
self.cx.parent);
11221202

1203+
let outer_ec = mem::replace(&mut self.expr_and_pat_count, 0);
11231204
let outer_cx = self.cx;
11241205
let outer_ts = mem::replace(&mut self.terminating_scopes, FxHashSet());
11251206
self.terminating_scopes.insert(body.value.hir_id.local_id);
@@ -1165,7 +1246,12 @@ impl<'a, 'tcx> Visitor<'tcx> for RegionResolutionVisitor<'a, 'tcx> {
11651246
resolve_local(self, None, Some(&body.value));
11661247
}
11671248

1249+
if body.is_generator {
1250+
self.scope_tree.body_expr_count.insert(body_id, self.expr_and_pat_count);
1251+
}
1252+
11681253
// Restore context we had at the start.
1254+
self.expr_and_pat_count = outer_ec;
11691255
self.cx = outer_cx;
11701256
self.terminating_scopes = outer_ts;
11711257
}
@@ -1200,6 +1286,7 @@ fn region_scope_tree<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId)
12001286
let mut visitor = RegionResolutionVisitor {
12011287
tcx,
12021288
scope_tree: ScopeTree::default(),
1289+
expr_and_pat_count: 0,
12031290
cx: Context {
12041291
root_id: None,
12051292
parent: None,
@@ -1246,6 +1333,7 @@ impl<'gcx> HashStable<StableHashingContext<'gcx>> for ScopeTree {
12461333
let ScopeTree {
12471334
root_body,
12481335
root_parent,
1336+
ref body_expr_count,
12491337
ref parent_map,
12501338
ref var_map,
12511339
ref destruction_scopes,
@@ -1259,6 +1347,7 @@ impl<'gcx> HashStable<StableHashingContext<'gcx>> for ScopeTree {
12591347
root_parent.hash_stable(hcx, hasher);
12601348
});
12611349

1350+
body_expr_count.hash_stable(hcx, hasher);
12621351
parent_map.hash_stable(hcx, hasher);
12631352
var_map.hash_stable(hcx, hasher);
12641353
destruction_scopes.hash_stable(hcx, hasher);

src/librustc_borrowck/borrowck/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
857857
None
858858
};
859859

860-
if let Some(yield_span) = maybe_borrow_across_yield {
860+
if let Some((yield_span, _)) = maybe_borrow_across_yield {
861861
debug!("err_out_of_scope: opt_yield_span = {:?}", yield_span);
862862
struct_span_err!(self.tcx.sess,
863863
error_span,

src/librustc_mir/build/expr/as_rvalue.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
9696
}
9797
ExprKind::Box { value } => {
9898
let value = this.hir.mirror(value);
99-
let result = this.local_decls.push(LocalDecl::new_temp(expr.ty, expr_span));
99+
// The `Box<T>` temporary created here is not a part of the HIR,
100+
// and therefore is not considered during generator OIBIT
101+
// determination. See the comment about `box` at `yield_in_scope`.
102+
let result = this.local_decls.push(
103+
LocalDecl::new_internal(expr.ty, expr_span));
100104
this.cfg.push(block, Statement {
101105
source_info,
102106
kind: StatementKind::StorageLive(result)

src/librustc_typeck/check/generator_interior.rs

+30-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
1616
use rustc::hir::def_id::DefId;
1717
use rustc::hir::intravisit::{self, Visitor, NestedVisitorMap};
18-
use rustc::hir::{self, Body, Pat, PatKind, Expr};
18+
use rustc::hir::{self, Pat, PatKind, Expr};
1919
use rustc::middle::region;
2020
use rustc::ty::Ty;
2121
use std::rc::Rc;
@@ -26,14 +26,27 @@ struct InteriorVisitor<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
2626
fcx: &'a FnCtxt<'a, 'gcx, 'tcx>,
2727
types: FxHashMap<Ty<'tcx>, usize>,
2828
region_scope_tree: Rc<region::ScopeTree>,
29+
expr_count: usize,
2930
}
3031

3132
impl<'a, 'gcx, 'tcx> InteriorVisitor<'a, 'gcx, 'tcx> {
3233
fn record(&mut self, ty: Ty<'tcx>, scope: Option<region::Scope>, expr: Option<&'tcx Expr>) {
3334
use syntax_pos::DUMMY_SP;
3435

3536
let live_across_yield = scope.map_or(Some(DUMMY_SP), |s| {
36-
self.region_scope_tree.yield_in_scope(s)
37+
self.region_scope_tree.yield_in_scope(s).and_then(|(span, expr_count)| {
38+
// If we are recording an expression that is the last yield
39+
// in the scope, or that has a postorder CFG index larger
40+
// than the one of all of the yields, then its value can't
41+
// be storage-live (and therefore live) at any of the yields.
42+
//
43+
// See the mega-comment at `yield_in_scope` for a proof.
44+
if expr_count >= self.expr_count {
45+
Some(span)
46+
} else {
47+
None
48+
}
49+
})
3750
});
3851

3952
if let Some(span) = live_across_yield {
@@ -60,9 +73,14 @@ pub fn resolve_interior<'a, 'gcx, 'tcx>(fcx: &'a FnCtxt<'a, 'gcx, 'tcx>,
6073
fcx,
6174
types: FxHashMap(),
6275
region_scope_tree: fcx.tcx.region_scope_tree(def_id),
76+
expr_count: 0,
6377
};
6478
intravisit::walk_body(&mut visitor, body);
6579

80+
// Check that we visited the same amount of expressions and the RegionResolutionVisitor
81+
let region_expr_count = visitor.region_scope_tree.body_expr_count(body_id).unwrap();
82+
assert_eq!(region_expr_count, visitor.expr_count);
83+
6684
let mut types: Vec<_> = visitor.types.drain().collect();
6785

6886
// Sort types by insertion order
@@ -82,30 +100,34 @@ pub fn resolve_interior<'a, 'gcx, 'tcx>(fcx: &'a FnCtxt<'a, 'gcx, 'tcx>,
82100
}
83101
}
84102

103+
// This visitor has to have the same visit_expr calls as RegionResolutionVisitor in
104+
// librustc/middle/region.rs since `expr_count` is compared against the results
105+
// there.
85106
impl<'a, 'gcx, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'gcx, 'tcx> {
86107
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
87108
NestedVisitorMap::None
88109
}
89110

90-
fn visit_body(&mut self, _body: &'tcx Body) {
91-
// Closures inside are not considered part of the generator interior
92-
}
93-
94111
fn visit_pat(&mut self, pat: &'tcx Pat) {
95112
if let PatKind::Binding(..) = pat.node {
96113
let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id);
97114
let ty = self.fcx.tables.borrow().pat_ty(pat);
98115
self.record(ty, Some(scope), None);
99116
}
100117

118+
self.expr_count += 1;
119+
101120
intravisit::walk_pat(self, pat);
102121
}
103122

104123
fn visit_expr(&mut self, expr: &'tcx Expr) {
124+
intravisit::walk_expr(self, expr);
125+
126+
self.expr_count += 1;
127+
105128
let scope = self.region_scope_tree.temporary_scope(expr.hir_id.local_id);
129+
106130
let ty = self.fcx.tables.borrow().expr_ty_adjusted(expr);
107131
self.record(ty, scope, Some(expr));
108-
109-
intravisit::walk_expr(self, expr);
110132
}
111133
}

0 commit comments

Comments
 (0)